Skip to content

Plugin

A Kiku plugin is a JavaScript module named _kiku_plugin.js. This module must export a named variable called plugin. The type definitions for this module are available here.

INFO

In addition to the JavaScript module, you may include a _kiku_plugin.css file for custom plugin styling.

ts
import type {
  createComputed,
  createContext,
  createEffect,
  createMemo,
  createResource,
  createSignal,
  ErrorBoundary,
  For,
  getOwner,
  lazy,
  Match,
  onCleanup,
  onMount,
  runWithOwner,
  Show,
  Suspense,
  Switch,
  untrack,
  useContext,
} from "solid-js";
import type h from "solid-js/h";
import type { JSX } from "solid-js/jsx-runtime";
import type { createStore } from "solid-js/store";
import type { Portal } from "solid-js/web";
import type { UseKanjiContext } from "#/components/_kiku_lazy/KanjiContext";
import type { UseAnkiFieldContext } from "#/components/shared/AnkiFieldsContext";
import type { UseBreakpointContext } from "#/components/shared/BreakpointContext";
import type { UseCardContext } from "#/components/shared/CardContext";
import type { UseConfigContext } from "#/components/shared/ConfigContext";
import type { UseGeneralContext } from "#/components/shared/GeneralContext";
import type { PitchInfo } from "#/util/hatsuon";
import type { AnkiBackFields, AnkiFrontFields } from "#/util/types";

/**
 * The Plugin Context (Ctx) provides the essential building blocks for creating
 * interactive and reactive UI elements within Kiku.
 *
 * It includes core Solid.js primitives, lifecycle hooks, and bridges to
 * Anki's internal state and APIs.
 */
export type Ctx = {
  /**
   * The Hyperscript function for creating UI elements.
   * Kiku uses Solid.js with Hyperscript to ensure high performance and reactivity.
   * @example h('div', { class: 'text-red-500' }, 'Hello Kiku')
   */
  h: typeof h;

  // --- Solid.js Reactive Primitives ---
  /** Creates a reactive signal. [getter, setter] = createSignal(initialValue) */
  createSignal: typeof createSignal;
  /** Creates a reactive effect that re-runs when its dependencies change. */
  createEffect: typeof createEffect;
  /** Creates a read-only signal derived from other reactive signals. */
  createMemo: typeof createMemo;
  /** Handles asynchronous data fetching and state management. */
  createResource: typeof createResource;
  /** Creates a computation that immediately runs and tracks dependencies. */
  createComputed: typeof createComputed;
  /** Creates a deep-reactive proxy object. Useful for complex state. */
  createStore: typeof createStore;

  // --- Lifecycle Hooks ---
  /** Registers a function to run after the component is mounted to the DOM. */
  onMount: typeof onMount;
  /** Registers a function to run when the component is unmounted. */
  onCleanup: typeof onCleanup;

  // --- Reactive Context & Scoping ---
  /** Creates a Context object for dependency injection. */
  createContext: typeof createContext;
  /** Subscribes to a previously created Context. */
  useContext: typeof useContext;
  /** Defers loading of a component until it is needed. */
  lazy: typeof lazy;
  /** Returns the current reactive owner. */
  getOwner: typeof getOwner;
  /** Runs a function within a specific reactive owner scope. */
  runWithOwner: typeof runWithOwner;
  /** Executes a block of code without tracking any reactive dependencies. */
  untrack: typeof untrack;

  // --- Core UI Components ---
  /** Handles uncaught errors in the component tree. */
  ErrorBoundary: typeof ErrorBoundary;
  /** Optimally renders a list of items. */
  For: typeof For;
  /** Renders content into a different DOM node (e.g., for modals). */
  Portal: typeof Portal;
  /** Conditionally renders elements: <Show when={condition}>...</Show> */
  Show: typeof Show;
  /** Handles loading states for asynchronous resources. */
  Suspense: typeof Suspense;
  /** Conditional rendering with multiple branches (Switch/Match). */
  Switch: typeof Switch;
  /** A branch within a Switch component. */
  Match: typeof Match;

  // --- Anki Data & Bridge ---
  /**
   * The raw field data of the current Anki card.
   * These are strings as defined in your Anki note type.
   */
  ankiFields: AnkiFrontFields | AnkiBackFields;

  // --- Kiku Hooks ---
  /** Provides reactive access to all Anki fields. */
  useAnkiFieldContext: UseAnkiFieldContext;
  /** Provides information about the current screen size/breakpoint. */
  useBreakpointContext: UseBreakpointContext;
  /** Provides the current card state (side, ID, etc.). */
  useCardContext: UseCardContext;
  /** Provides access to Kiku's global configuration. */
  useConfigContext: UseConfigContext;
  /** Provides access to Kiku's global general state. */
  useGeneralContext: UseGeneralContext;
};

/**
 * The main interface for a Kiku Plugin module.
 *
 * To create a plugin, your `_kiku_plugin.js` file must export a
 * constant named `plugin` that satisfies this interface.
 */
export type KikuPlugin = {
  /**
   * Customizes the External Links section on the back of the card.
   * This is typically used to add links to custom dictionary sites.
   */
  ExternalLinks?: (props: {
    /** The original Kiku external links. */
    DefaultExternalLinks: () => JSX.Element;
    ctx: Ctx;
  }) => JSX.Element | JSX.Element[];

  /**
   * Replaces or extends the primary Sentence display.
   * Use this to add custom translations or text-processing logic.
   */
  Sentence?: (props: {
    /** The original Kiku sentence display. */
    DefaultSentence: () => JSX.Element;
    ctx: Ctx;
  }) => JSX.Element | JSX.Element[];

  /**
   * Replaces or extends the Footer section at the bottom of the card.
   */
  Footer?: (props: {
    /** The original Kiku footer. */
    DefaultFooter: () => JSX.Element;
    ctx: Ctx;
  }) => JSX.Element | JSX.Element[];

  /**
   * Customizes the Pitch Accent visualizations.
   * This is called for every pitch info entry found for the word.
   */
  Pitch?: (props: {
    /** Data about the pitch (morae, accent index, etc.). */
    pitchInfo: PitchInfo;
    /** The index if multiple pitch entries exist. */
    index: number;
    /** The original Kiku pitch component. */
    DefaultPitch: (props: {
      pitchInfo: PitchInfo;
      index: number;
      ref?: (ref: HTMLDivElement) => void;
    }) => JSX.Element;
    ctx: Ctx;
  }) => JSX.Element | JSX.Element[];

  /**
   * Customizes the extended Kanji Information section.
   * This section appears when clicking a kanji to see its details.
   */
  KanjiInfoExtra?: (props: {
    /** True if the user is in the dedicated Kanji Page view. */
    inKanjiPage?: boolean;
    /** The original Kiku kanji info. */
    DefaultKanjiInfoExtra: () => JSX.Element;
    /**
     * Pre-rendered UI sections for the Kanji Detail view.
     * You can return these within your `KanjiInfoExtra` plugin hook.
     */
    sections: {
      VisuallySimilar: () => JSX.Element;
      ComposedOf: () => JSX.Element;
      UsedIn: () => JSX.Element;
      Meanings: () => JSX.Element;
      Related: () => JSX.Element;
    };
    /** Refs to the checkboxes in the UI for toggling sections. */
    checkboxRef: {
      visuallySimilar: undefined | HTMLInputElement;
      composedOf: undefined | HTMLInputElement;
      usedIn: undefined | HTMLInputElement;
      meanings: undefined | HTMLInputElement;
      related: undefined | HTMLInputElement;
    };
    /** Hook to access the current Kanji's data reactively. */
    useKanjiContext: UseKanjiContext;
    ctx: Ctx;
  }) => JSX.Element | JSX.Element[];

  /**
   * Lifecycle Hook: Called once when the plugin module is loaded.
   * Ideal for global styling, event listeners, or persistent state.
   */
  onPluginLoad?: (props: { ctx: Ctx }) => void;

  /**
   * Lifecycle Hook: Called when the Kiku Settings menu is opened.
   * Use this to perform side-effects when settings open.
   */
  onSettingsMount?: (props: { ctx: Ctx }) => void;

  /**
   * Filters images extracted from the glossary.
   * Return true to keep the image, false to exclude it.
   */
  glossaryImagesFilter?: (img: HTMLImageElement) => boolean;
};

The plugin system is currently very basic, but more APIs will be added in the future. Check out the examples for more advanced usage.