diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff652f1..04a2ed6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4429,7 +4429,7 @@ snapshots: '@next/eslint-plugin-next': 16.1.6 eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) @@ -4452,7 +4452,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -4467,14 +4467,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) transitivePeerDependencies: - supports-color @@ -4489,7 +4489,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 diff --git a/src/app/appendix/plugin-migration-from-v5-to-v6/page.mdx b/src/app/appendix/plugin-migration-from-v5-to-v6/page.mdx index 278e16d..c855245 100644 --- a/src/app/appendix/plugin-migration-from-v5-to-v6/page.mdx +++ b/src/app/appendix/plugin-migration-from-v5-to-v6/page.mdx @@ -237,11 +237,121 @@ env.clipboard.writeText('Hello, Inkdrop!') `env.clipboard` also provides `readHTML()`, `readImageAsDataURL()`, `writeImage(dataUrl)`, `availableFormats()`, `write({ text, html })`, and `saveAsImageAttachment()`. See the `clipboard` property on the [Environment](https://github.com/inkdropapp/types/blob/main/src/environment.d.ts) for details. +## UI themes + +UI themes (packages with `"theme": "ui"` in `package.json`) style the app chrome — sidebar, note list, toolbar, drop-downs, modals — by overriding **CSS variables** in `:root`. That model is unchanged in v6: you still set the same component variables (`--sidebar-background`, `--note-list-bar-background`, `--editor-background`, …). What changed is the **primitive color tokens** those variables build on. Like every other plugin, UI themes also [drop LESS for plain CSS](#less-is-deprecated) and should bump `engines.inkdrop` to `^6.x`. + +The stock [Solarized Light UI](https://github.com/inkdropapp/solarized-light-ui/) and [Solarized Dark UI](https://github.com/inkdropapp/solarized-dark-ui/) themes have been updated for v6 and make good references. + +### Update the manifest and tooling + +Bump the engine, the dev-helpers (they provide the v6 `dev-server` and `generate-palette` binaries), and add a `prepublishOnly` script so `palette.json` stays current: + +Before: + +```json +{ + "devDependencies": { + "@inkdropapp/theme-dev-helpers": "^0.3.6" + }, + "engines": { + "inkdrop": "^5.9.0" + } +} +``` + +After: + +```json +{ + "scripts": { + "prepublishOnly": "generate-palette" + }, + "devDependencies": { + "@inkdropapp/theme-dev-helpers": "^0.4.0" + }, + "engines": { + "inkdrop": "^6.0.0" + } +} +``` + +The `styleSheets`, `theme`, and `themeAppearance` fields are unchanged. + +### Use the v6 design tokens + +The biggest thing to be aware of is that the shared color palette was overhauled. v6's [`@inkdropapp/css`](https://github.com/inkdropapp/css) (loaded before your theme) provides a set of **Tailwind-inspired design tokens** to build on instead of hard-coding colors: + +- A neutral grayscale ramp, `--hsl-neutral-50` … `--hsl-neutral-950` (`50` = lightest, `950` = darkest) +- Accent families at the same scale — `blue`, `cyan`, `green`, `yellow`, `orange`, `red`, `violet`, `pink` — e.g. `--hsl-blue-500`, `--hsl-red-600` +- `--color--` wrappers (e.g. `--color-neutral-50`) that resolve the matching `--hsl-*` triplet to an `hsl()` color + +```css +:root { + --page-background: var(--color-neutral-50); + --text-color: var(--color-neutral-600); + --primary-color: hsl(var(--hsl-blue-500) / 90%); +} +``` + +If your v5 theme referenced palette tokens that no longer exist, repoint them at the tokens above. (For example, Solarized Light dropped its old base16-style grayscale and semantic aliases in favor of the `neutral` ramp, and switched its `--hsl-magenta-*` references to `--hsl-pink-*`.) The full set of color tokens is published in the [`@inkdropapp/css`](https://github.com/inkdropapp/css) repository. + +### Override the built-in variables + +Colors are only part of it. The app defines named CSS variables for nearly every UI surface, and your theme overrides just the ones you want to change. For the full list, refer to the source CSS files in [`@inkdropapp/css`](https://github.com/inkdropapp/css), which the app loads before your theme: + +- [`tokens.css`](https://github.com/inkdropapp/css/blob/main/tokens.css) — design tokens +- [`ui.css`](https://github.com/inkdropapp/css/blob/main/ui.css) — the main file that defines CSS variables for UI components (sidebar, note list, editor chrome, modals, …) +- [`status.css`](https://github.com/inkdropapp/css/blob/main/status.css) — note status colors (`--note-status-*`) +- [`tags.css`](https://github.com/inkdropapp/css/blob/main/tags.css) — tag colors (`--tag-*`) +- [`task-progress.css`](https://github.com/inkdropapp/css/blob/main/task-progress.css) — task progress view colors (`--task-progress-view-*`) + +### Recommended: recolor by overriding the ramps in `tokens.css` + +Rather than repointing every component variable by hand, keep a custom palette by **redefining the primitive ramps themselves** in a `tokens.css` listed first in `styleSheets`. Because the app's component variables resolve through `--hsl-neutral-*` / `--hsl--*`, overriding those ramps recolors the whole UI from one place; your `theme.css` then only needs the handful of component variables that differ from the default. + +```css +/* tokens.css — map your palette onto the v6 ramps */ +:root { + --hsl-neutral-50: 44deg 87% 94%; + --hsl-neutral-100: 46deg 42% 88%; + /* …through --hsl-neutral-950… */ + + --hsl-blue-500: 205deg 69% 49%; + + /* re-declare the --color-* wrappers so they pick up the overrides */ + --color-neutral-50: hsl(var(--hsl-neutral-50)); + /* … */ +} +``` + + + Redefine both the `--hsl-*` ramps and their `--color-*` wrappers. Components + reference both forms, and re-declaring the wrapper guarantees it resolves + against your overridden `--hsl-*` value. + + +### Move preview styling out + +If your v5 UI theme styled preview content directly (e.g. `.mde-preview h1`, `.mde-preview em`), drop those rules — preview content belongs in a dedicated **preview theme**. For any UI-level rule you keep, note that the container class was renamed `.mde-preview` → `.mde-preview-container` ([see above](#css-selectors-for-keymaps-and-commands)). + +### Generate `palette.json` + +v6 themes ship a `palette.json` — a JSON dump of the theme's variables, used for theme previews and (in the future) the mobile app. Generate it with the `generate-palette` binary from `@inkdropapp/theme-dev-helpers`: + +```sh +npx generate-palette +``` + +Commit the result, and the `prepublishOnly` script above keeps it in sync on every publish. See [Creating a UI Theme](/guides/create-a-theme#creating-a-ui-theme) for the full dev loop (`npx dev-server`, hot reloading, and the component preview). + ## Syntax themes -Syntax themes (packages with `"theme": "syntax"` in `package.json`) styled **CodeMirror 5** in v5. Inkdrop 6 runs on [CodeMirror 6](https://codemirror.net/), so a theme moves from styling CodeMirror's DOM directly to setting **CSS variables**, and from CodeMirror 5 mode tokens (`.cm-*`) to CodeMirror 6 / [Lezer](https://lezer.codemirror.net/) highlight classes (`.tok-*`). +Syntax themes (packages with `"theme": "syntax"` in `package.json`) styled **CodeMirror 5** in v5. +Inkdrop 6 runs on [CodeMirror 6](https://codemirror.net/), so a theme moves from styling CodeMirror's DOM directly to setting **CSS variables**, and from CodeMirror 5 mode tokens (`.cm-*`) to CodeMirror 6 / [Lezer](https://lezer.codemirror.net/) highlight classes (`.tok-*`). -The stock [`default-light`](https://github.com/inkdropapp/cm6-themes/tree/main/packages/default-light) and [`default-dark`](https://github.com/inkdropapp/cm6-themes/tree/main/packages/default-dark) themes are maintained as reference implementations. Like every other plugin, themes also [drop LESS for plain CSS](#less-is-deprecated), and should bump `engines.inkdrop` to `^6.x`. +The stock [`default-light`](https://github.com/inkdropapp/default-light-syntax-theme) and [`default-dark`](https://github.com/inkdropapp/default-dark-syntax-theme) themes are maintained as reference implementations. +Like every other plugin, themes also [drop LESS for plain CSS](#less-is-deprecated), and should bump `engines.inkdrop` to `^6.x`. ### Editor chrome: DOM selectors → CSS variables @@ -259,7 +369,7 @@ You no longer style CodeMirror's DOM. Replace the old `.CodeMirror-*` rules with | `.CodeMirror-activeline-background` | `--editor-active-line-background-color` | | `.CodeMirror-matchingbracket` | `--editor-matching-bracket-outline`, `--editor-matching-bracket-background-color` | -See the full list of `--editor-*` and `--md-*` variables at the top of the reference theme's [`styles/index.css`](https://github.com/inkdropapp/cm6-themes/blob/main/packages/default-light/styles/index.css). +See the full list of `--editor-*` and `--md-*` variables at the top of the reference theme's [`styles/index.css`](https://github.com/inkdropapp/default-light-syntax-theme/blob/master/styles/index.css). ### Syntax tokens: `.cm-*` → `.tok-*` @@ -326,3 +436,139 @@ Inkdrop ships a primitive color palette — Tailwind-style `--hsl---` / `--color--`) for values, the same way syntax and UI themes do. + +For the few things the variables don't cover — coloring `strong`, `em`, or the headings, for example — you can still write ordinary rules inside `.mde-preview`: + +```css +.mde-preview { + h1, + h2, + h3, + h4, + h5, + h6 { + color: var(--color-yellow-500); + } + em { + color: var(--color-green-500); + } +} +``` + +### The variables + +The full list lives in the `:root` block of [`markdown.css`](https://github.com/inkdropapp/css/blob/main/markdown.css). The most commonly themed ones: + +| Area | Variable(s) | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Links | `--mde-preview-link-color` | +| Headings | `--mde-preview-heading-border-color`, `--mde-preview-heading-muted-text-color` | +| Blockquotes | `--mde-preview-blockquote-text-color`, `--mde-preview-blockquote-border-color` | +| Inline code | `--mde-preview-inline-code-text-color`, `--mde-preview-inline-code-background-color`, `--mde-preview-inline-code-border-color` / `-border-width` | +| Code blocks | `--mde-preview-codeblock-background-color`, `--mde-preview-codeblock-border-color`, `--mde-preview-codeblock-text-color`, `--mde-preview-codeblock-meta-*` | +| Tables | `--mde-preview-table-head-*`, `--mde-preview-table-border-color`, `--mde-preview-table-row-*` | +| Highlight (`==mark==`) | `--mde-preview-inline-mark-text-color`, `--mde-preview-inline-mark-background-color`, `--mde-preview-inline-mark-underline-color` | +| Task lists | `--mde-preview-task-list-checked-color` | +| Footnotes | `--mde-preview-footnote-*` | +| Keyboard keys | `--mde-preview-kbd-*` | +| GFM alerts | `--gfm-alert-note`, `--gfm-alert-tip`, `--gfm-alert-important`, `--gfm-alert-warning`, `--gfm-alert-caution` | + +### Code blocks: frame vs. tokens + +A preview theme styles the code block **frame** — background, border, and the meta/title bar — through the `--mde-preview-codeblock-*` variables above. The **syntax highlighting inside** the block (the `.tok-*` classes under `.mde-preview .codeblock`) belongs to the **syntax theme**, not the preview theme. See [Syntax themes](#syntax-themes) for that side. + + + The preview content root is .mde-preview — that's the element + your variable overrides target. Don't confuse it with{' '} + .mde-preview-container, the outer wrapper renamed for [keymap and + command scoping](#css-selectors-for-keymaps-and-commands). + + +See [Creating a Preview Theme](/guides/create-a-theme#creating-a-preview-theme) for the full dev loop (`ipm init --type theme-preview`, `ipm link --dev`, and hot reloading). diff --git a/src/app/components/button/page.mdx b/src/app/components/button/page.mdx new file mode 100644 index 0000000..9f37d6e --- /dev/null +++ b/src/app/components/button/page.mdx @@ -0,0 +1,70 @@ +export const metadata = { + title: 'Button', + description: 'A built-in styled React button component of Inkdrop with optional icon and tooltip support', +} + +# Button + +A built-in React [component](/modules/component-manager) of Inkdrop that renders a styled button +with optional icon and tooltip support. +In addition to the props below, `Button` accepts all standard +[` + ) +} + +export default SaveButton +``` + +## See also + +- [CommandButton](/components/command-button) — a `Button` that dispatches an Inkdrop command when clicked. diff --git a/src/app/components/command-button/page.mdx b/src/app/components/command-button/page.mdx new file mode 100644 index 0000000..4b71f58 --- /dev/null +++ b/src/app/components/command-button/page.mdx @@ -0,0 +1,59 @@ +export const metadata = { + title: 'CommandButton', + description: 'A built-in React component of Inkdrop that dispatches a command when clicked and shows its keybinding in a tooltip', +} + +# CommandButton + +A built-in React [component](/modules/component-manager) of Inkdrop that extends [Button](/components/button). +When clicked, it dispatches an Inkdrop [command](/modules/command-registry), and its tooltip automatically +shows the command's keybinding. + +To get the class of the `CommandButton` component: + +```tsx +import type { CommandButtonProps } from '@inkdropapp/types' + +const CommandButton = + inkdrop.components.getComponentClass('CommandButton') +``` + +## Props + +`CommandButton` accepts all [Button](/components/button) props in addition to the following: + +```typescript +type CommandButtonProps = ButtonProps & { + // The command to dispatch when the button is clicked. + command?: string + // The DOM element to dispatch the command on. Defaults to document.body. + commandTarget?: HTMLElement + // The detail payload passed with the dispatched command. + commandDetail?: any +} +``` + +## Example + +```tsx +import React from 'react' +import type { CommandButtonProps } from '@inkdropapp/types' + +const SaveButton: React.FC = () => { + const CommandButton = + inkdrop.components.getComponentClass('CommandButton') + + return ( + + Save + + ) +} + +export default SaveButton +``` + +## See also + +- [Button](/components/button) — the base button component. +- [Command Registry](/modules/command-registry) — how Inkdrop commands are registered and dispatched. diff --git a/src/app/components/dialog/page.mdx b/src/app/components/dialog/page.mdx index b5e8679..7cbd68b 100644 --- a/src/app/components/dialog/page.mdx +++ b/src/app/components/dialog/page.mdx @@ -1,6 +1,6 @@ export const metadata = { title: 'Dialog', - description: 'A built-in React component of Inkdrop that allows you to display a simple modal dialog with buttons', + description: 'A built-in React component of Inkdrop that allows you to display a modal dialog with a title, content, and actions', } # Dialog @@ -9,37 +9,88 @@ export const metadata = { Available since v3.5.1. -A built-in React [component](/modules/component-manager) of Inkdrop that allows you to display a simple modal dialog with buttons. -To get the class of `Dialog` component: +A built-in React [component](/modules/component-manager) of Inkdrop that displays a modal dialog. +It is built on top of the [Modal](/components/modal) component and provides +`Title`, `Content`, and `Actions` sub-components for laying out a dialog. -```js -const Dialog = inkdrop.components.getComponentClass('Dialog') +To get the class of the `Dialog` component: + +```tsx +import type { Dialog } from '@inkdropapp/types' + +const Dialog = inkdrop.components.getComponentClass('Dialog') as Dialog ``` + +`Dialog` is cast to its type so that the `Dialog.Title`, `Dialog.Content`, and +`Dialog.Actions` sub-components are typed. A plain +`getComponentClass('Dialog')` only types the props. + + ## Props +`Dialog` accepts all [Modal](/components/modal) props in addition to the following: + ```typescript -type Props = { - className?: string, - children?: React.Node, - visible: boolean, - hiding: boolean, - onBackdropClick?: () => any, - autofocus?: boolean +type DialogProps = ModalProps & { + className?: string + children?: React.ReactNode + // Render the dialog at a larger width. + large?: boolean } ``` -## Example +### Sub-components + + + + The header of the dialog. + + ```typescript + type DialogTitleProps = { + className?: string + children?: React.ReactNode + } + ``` + + + The main body of the dialog. + + ```typescript + type DialogContentProps = { + className?: string + // Lay out the content as a flex container. + flex?: boolean + // Remove the default padding. + noPadding?: boolean + children?: React.ReactNode + // The CSS `overflow` value applied to the content. + overflow?: React.CSSProperties['overflow'] + } + ``` + + + The footer of the dialog, typically containing buttons. + + ```typescript + type DialogActionsProps = { + className?: string + children?: React.ReactNode + } + ``` + + -```js -'use babel' +## Example +```tsx import React, { useEffect, useCallback } from 'react' import { logger, useModal } from 'inkdrop' +import type { Dialog as DialogType } from '@inkdropapp/types' -const HelloMessageDialog = props => { +const HelloMessageDialog: React.FC = () => { const modal = useModal() - const { Dialog } = inkdrop.components.classes + const Dialog = inkdrop.components.getComponentClass('Dialog') as DialogType const toggle = useCallback(() => { modal.show() @@ -75,4 +126,5 @@ It produces: ## See also +- [Modal](/components/modal) — the lower-level overlay component that `Dialog` builds on. - [The plugin tutorial](/guides/plugin-word-count#understanding-the-generated-code) that uses the `Dialog` component. diff --git a/src/app/components/modal/page.mdx b/src/app/components/modal/page.mdx new file mode 100644 index 0000000..bf5adcd --- /dev/null +++ b/src/app/components/modal/page.mdx @@ -0,0 +1,73 @@ +export const metadata = { + title: 'Modal', + description: 'A built-in React component of Inkdrop that renders a modal overlay with backdrop, focus management, and animations', +} + +# Modal + +A built-in React [component](/modules/component-manager) of Inkdrop that renders a modal overlay. +It handles backdrop clicks, the Esc key, focus management, and enter/exit animations. +For a higher-level dialog with a title, content, and actions, use the [Dialog](/components/dialog) component. + +To get the class of the `Modal` component: + +```tsx +import type { ModalProps } from '@inkdropapp/types' + +const Modal = inkdrop.components.getComponentClass('Modal') +``` + +## Props + +```typescript +type ModalProps = { + className?: string + children: React.ReactNode + // Whether the modal is visible. + visible: boolean + // Called when the backdrop is clicked. + onBackdropClick?: () => any + // Called when the Esc key is pressed. + onEscKeyDown?: () => any + // Move focus into the modal when it opens. Defaults to true. + autofocus?: boolean + // Restore focus to the previously focused element when it closes. + autoRestoreFocus?: boolean + // Play enter/exit animations. Defaults to true. + animate?: boolean +} +``` + +## Example + +```tsx +import React, { useCallback, useEffect } from 'react' +import { useModal } from 'inkdrop' +import type { ModalProps } from '@inkdropapp/types' + +const MyOverlay: React.FC = () => { + const modal = useModal() + const Modal = inkdrop.components.getComponentClass('Modal') + + const toggle = useCallback(() => modal.show(), []) + + useEffect(() => { + const sub = inkdrop.commands.add(document.body, { + 'yourplugin:toggle': toggle + }) + return () => sub.dispose() + }, [toggle]) + + return ( + +
Hello, world!
+
+ ) +} + +export default MyOverlay +``` + +## See also + +- [Dialog](/components/dialog) — a higher-level dialog built on top of `Modal`. diff --git a/src/app/components/notebook-list-bar/page.mdx b/src/app/components/notebook-list-bar/page.mdx new file mode 100644 index 0000000..150e18d --- /dev/null +++ b/src/app/components/notebook-list-bar/page.mdx @@ -0,0 +1,62 @@ +export const metadata = { + title: 'NotebookListBar', + description: 'A built-in searchable React component of Inkdrop for selecting a notebook', +} + +# NotebookListBar + +A built-in React [component](/modules/component-manager) of Inkdrop that renders a searchable list of +notebooks, letting the user pick one. + +To get the class of the `NotebookListBar` component: + +```tsx +import type { NotebookListBarProps } from '@inkdropapp/types' + +const NotebookListBar = + inkdrop.components.getComponentClass('NotebookListBar') +``` + +## Props + +```typescript +type NotebookListBarProps = { + // Placeholder text for the search input. Defaults to 'Search'. + placeholder?: string + // Icon name for the search input. Defaults to 'book-close-2'. + icon?: string + // Whether to show a "(Root)" option at the top. + showRoot?: boolean + // Book IDs to hide from the list. + hiddenBooks?: string[] + // Called when a notebook is selected. `bookId` is null if "(Root)" was selected. + onItemSelect: (bookId: string | null) => any +} +``` + +## Example + +```tsx +import React, { useCallback } from 'react' +import { logger } from 'inkdrop' +import type { NotebookListBarProps } from '@inkdropapp/types' + +const NotebookPicker: React.FC = () => { + const NotebookListBar = + inkdrop.components.getComponentClass('NotebookListBar') + + const onItemSelect = useCallback((bookId: string | null) => { + logger.debug('Selected notebook:', bookId) + }, []) + + return ( + + ) +} + +export default NotebookPicker +``` diff --git a/src/app/guides/create-a-theme/page.mdx b/src/app/guides/create-a-theme/page.mdx index b9878cb..da014be 100644 --- a/src/app/guides/create-a-theme/page.mdx +++ b/src/app/guides/create-a-theme/page.mdx @@ -1,11 +1,12 @@ export const metadata = { title: 'Creating a theme', - description: - 'A tutorial on how to create a theme', + description: 'A tutorial on how to create a theme' } - This guide targets Inkdrop v5.9.0 or later. + This guide targets Inkdrop 6. If you're migrating a theme from Inkdrop 5, see + the [Plugin Migration Guide from v5 to + v6](/appendix/plugin-migration-from-v5-to-v6). # Creating a theme @@ -21,12 +22,12 @@ Inkdrop supports three types of themes: UI, Syntax and Preview: ![Theme Types](/images/creating-a-theme_types.png) -Themes can be changed from Preferences which you can open by selecting the _Inkdrop > Preferences_ on macOS or _File > Settings_ on Windows and Linux, and clicking the _Themes_ tab on the left hand navigation. +Themes can be changed from Preferences which you can open by selecting the **Inkdrop > Preferences** on macOS or **File > Settings** on Windows and Linux, and clicking the **Themes** tab on the left hand navigation. Themes can be also installed with [Inkdrop Plugin Manager](https://docs.inkdrop.app/manual/extend-inkdrop-with-plugins). ## Prerequisites -- [Bun](https://bun.sh/) - It's an all-in-one TypeScript runtime, which is used in the Inkdrop's toolchain. +- [Node.js](https://nodejs.org/) (>= 24) - Used to install dependencies and run the theme development tools (`npx dev-server`, `npx generate-palette`). - [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) on Windows - It's required to run the development server on Windows. ## Getting Started @@ -38,6 +39,25 @@ Themes are pretty straightforward but it's still helpful to be familiar with a f - Your theme's `package.json` must contain a `theme` key with a value of `ui`, `syntax` or `preview` for Inkdrop to recognize and load it as a theme. - You can find existing themes to install or fork in the [Inkdrop themes registry](https://my.inkdrop.app/plugins/theme). +### Scaffolding a theme with `ipm` + +The quickest way to start is the [`ipm` CLI](https://github.com/inkdropapp/ipm-cli), which scaffolds a ready-to-edit theme into a new directory. Pick the `--type` that matches the theme you want to build: + +```shell +# A syntax (editor) theme → creates ./motif-syntax +$ ipm init motif --type theme-syntax + +# A UI (app chrome) theme → creates ./motif-dark-ui +$ ipm init motif-dark-ui --type theme-ui + +# A preview (Markdown) theme → creates ./motif-preview +$ ipm init motif --type theme-preview +``` + +`ipm` appends the matching `-syntax`, `-ui`, or `-preview` suffix automatically when you leave it off. For UI themes it also sets `themeAppearance` from the name when it contains `light` or `dark`, and asks you otherwise. Run `ipm init` with no arguments to launch an interactive wizard that prompts for the name and type. + +Each theme type is covered step by step below, starting from its `ipm init` command. + ### Enabling Development Mode **Development Mode** enables the "**Inspect Element**" context menu in Inkdrop, which helps you build your themes easier. @@ -52,7 +72,7 @@ You can check how it's styled with themes in the Developer Tools like this: ![DevTools](/images/creating-a-theme_devtools.png) -Check out Google's [extensive tutorial](https://developers.google.com/web/tools/chrome-devtools/iterate/inspect-styles/?utm_source=dcc&utm_medium=redirect&utm_campaign=2016q3) for a short introduction. +Check out Google's [extensive tutorial](https://developer.chrome.com/docs/devtools/) for a short introduction. ### Installing the dev-tools plugin @@ -74,56 +94,61 @@ To stop, select the menu **Plugins → dev-tools → Stop hot reloading themes** Let's create your first syntax theme which is called `motif-syntax`. To create a syntax theme, do the following: -1. Click **Use this template** on [the syntax theme template repository on GitHub](https://github.com/inkdropapp/inkdrop-default-light-syntax-theme) to create a new repository - Use this template -2. Clone your repository to the directory named `motif-syntax` in the local filesystem -3. Open a terminal in the theme's directory -4. Change the name of the theme in the theme's `package.json` file -5. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). -6. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R -7. Enable the theme via the "_Syntax Theme_" drop-down in the "Themes" tab of the Preferences -8. Enable hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** +1. Run `ipm init motif --type theme-syntax` to scaffold the theme into `./motif-syntax` (`ipm` adds the `-syntax` suffix for you). +2. Open a terminal in the `motif-syntax` directory. +3. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). +4. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R +5. Enable the theme via the "_Syntax Theme_" drop-down in the "Themes" tab of the Preferences +6. Enable hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** Now you are ready to make changes! -Open up `styles/index.css` to change the various color variables which have already been defined. -For example, turn `--base00` into `#f4c2c1`. +Open up `styles/index.css`. The scaffold ships the same complete, commented stylesheet as the stock [`default-light`](https://github.com/inkdropapp/default-light-syntax-theme) theme, so every variable and class you might customize is already there for reference. What Inkdrop actually reads is three groups of declarations: -Then, modify the various selectors that have already been defined. -These selectors style different parts of code in the editor such as comments, strings and the line numbers in the gutter. -You may notice that the editor is built based on [CodeMirror](https://codemirror.net/), you can also import various existing theme built for CodeMirror. +- The **editor chrome** — `--editor-*` variables for the background, gutter, cursor, selection, tooltips, brackets, and more. +- The **syntax tokens** — [Lezer](https://lezer.codemirror.net/) highlight classes named `.tok-*` (`.tok-keyword`, `.tok-string`, `.tok-comment`, …), scoped under `.cm-editor` and `.mde-preview .codeblock` (the latter styles rendered preview code blocks). +- The **Markdown-specific tokens** — `--md-*` variables that style Markdown-specific syntax, such as fenced (triple-backtick) code blocks, inline code, highlight marks, and tables. -After making changes, reload the app to reflected changes. +Set those to whatever colors you like — for example, reference [`@inkdropapp/css`](https://github.com/inkdropapp/css) primitives such as `hsl(var(--hsl-slate-800))` directly. The default theme also defines a base16-style palette at the top (`--hsl-base00`–`--hsl-base0F` and their `--bd-*` aliases) and points its other variables at it, which is a handy way to recolor everything from one place — but that's a convention specific to the default theme, not something your theme has to follow. + +The editor runs on [CodeMirror 6](https://codemirror.net/), so these `--editor-*` variables and `.tok-*` classes replace the old CodeMirror 5 `.CodeMirror-*` / `.cm-*` selectors. See [Syntax themes](/appendix/plugin-migration-from-v5-to-v6#syntax-themes) in the migration guide for the full reference and the v5 → v6 selector mapping. +After making changes, the app automatically reflects changes via the dev-tools hot-reloading. - Note: It's advised to not specify a font-family in your syntax theme because it will override the Font Family field in Inkdrop's settings. If you still like to recommend a font that goes well with your theme, we suggest you do so in your README. + Note: It's advised to not specify a font-family{' '} + in your syntax theme because it will override the Font Family field in + Inkdrop's settings. If you still like to recommend a font that goes well with + your theme, we suggest you do so in your README. +### Example syntax themes + +- [Solarized Light Syntax](https://github.com/inkdropapp/solarized-light-syntax/) +- [Solarized Dark Syntax](https://github.com/inkdropapp/solarized-dark-syntax/) + ## Creating a UI Theme - Tip: UI themes should end with -ui or for dark theme-dark-ui. + Tip: UI themes should end with -ui or for dark + theme-dark-ui. Let's create your first UI theme which is called `motif-dark-ui`. To create a UI theme, do the following: -1. Click **Use this template** on [the UI theme template repository on GitHub](https://github.com/inkdropapp/inkdrop-default-light-ui-theme) to create a new repository - Use this template -2. Clone your repository to the directory named `motif-dark-ui` in the local filesystem -3. Open a terminal in the theme's directory -4. Change the name of the theme in the theme's `package.json` file. If your theme is going to be a dark theme, you should also change the `themeAppearance` key to `dark` in the `package.json` file. -5. Run `bun install` to install dependencies -6. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). -7. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R -8. Enable the theme via the "_UI Theme_" drop-down in the "Themes" tab of the Preferences -9. Start hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** -10. Run `bunx dev-server` to start the development server -11. Open the URL displayed in your console in a web browser -12. Edit your theme's `styles/theme.css` file to customize the theme -13. Run `bunx generate-palette` to generate a `palette.json` file -14. Commit the generated `palette.json` file to your repository -15. Publish your theme +1. Run `ipm init motif-dark-ui --type theme-ui` to scaffold the theme into `./motif-dark-ui`. Because the name contains `dark`, `ipm` sets `"themeAppearance": "dark"` in `package.json` for you — when the name has neither `light` nor `dark`, it asks which one to use. +2. Open a terminal in the `motif-dark-ui` directory. +3. Run `npm install` to install dependencies. +4. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). +5. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R +6. Enable the theme via the "_UI Theme_" drop-down in the "Themes" tab of the Preferences +7. Start hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** +8. Run `npx dev-server` to start the development server +9. Open the URL displayed in your console in a web browser +10. Edit your theme's `styles/theme.css` file to customize the theme +11. Run `npx generate-palette` to generate a `palette.json` file +12. Commit the generated `palette.json` file to your repository +13. Publish your theme ### Customizing Theme Variables @@ -135,6 +160,14 @@ This approach saves you from handling CSS selector specificities, making your th After creating a repository, there is an empty CSS file in `styles/theme.css`. You are going to add only the CSS variables that you want to change in this file. +For the full list of variables you can override, refer to the source CSS files in the [`@inkdropapp/css`](https://github.com/inkdropapp/css) package, which the app loads before your theme: + +- [`tokens.css`](https://github.com/inkdropapp/css/blob/main/tokens.css) — design tokens +- [`ui.css`](https://github.com/inkdropapp/css/blob/main/ui.css) — the main file that defines CSS variables related to UI components +- [`status.css`](https://github.com/inkdropapp/css/blob/main/status.css) — note status colors (`--note-status-*`) +- [`tags.css`](https://github.com/inkdropapp/css/blob/main/tags.css) — tag colors (`--tag-*`) +- [`task-progress.css`](https://github.com/inkdropapp/css/blob/main/task-progress.css) — task progress view colors (`--task-progress-view-*`) + ### Running the Development Server Let's check out what variables are available in Inkdrop. @@ -142,7 +175,7 @@ Inkdrop provides a helper tool to list all the CSS variables that are used in th Run the following command in the theme project root directory: ```sh -bunx dev-server +npx dev-server # # VITE v5.4.9 ready in 710 ms # @@ -152,7 +185,8 @@ bunx dev-server ``` - For Windows users: PowerShell is not supported. You have to run it on WSL. + For Windows users: PowerShell is not supported. You have to + run it on WSL. The development server will start. @@ -168,6 +202,8 @@ There are three tabs in the tool: - Variables starting with `--hsl-` define HSL color parameters, such as `215deg 28% 17%`, which are useful for adjusting opacity, for example, `hsl(var(--hsl-gray-800 / 30%))` - **Components**: Previews some components with the current theme. The tool supports hot reloadig, so it will update colors as you modify the variables. +The full set of color tokens is also published in the [`@inkdropapp/css`](https://github.com/inkdropapp/css) repository, where you can browse every available `--color-*` and `--hsl-*` variable. + ### Acrylic background support The app supports acrylic background, which adds a frosted glass effect to the window background. @@ -207,7 +243,7 @@ This file is planned to be used for displaying a preview of your theme and also Run the following command before publishing in the root theme project directory: ```sh -bunx generate-palette +npx generate-palette ``` You can commit the generated `palette.json` file to your repository. @@ -223,28 +259,25 @@ You can commit the generated `palette.json` file to your repository. Tip: Preview themes should end with -preview. - - If you only want to change the preview colors to match your UI theme, make these adjustments in your UI theme rather than creating a new preview theme. - Preview themes are meant for customizing document styles, not the app's UI. - - Let's create your first theme which is called `motif-preview`. To create a preview theme, do the following: -1. Click **Use this template** on [the UI theme template repository on GitHub](https://github.com/inkdropapp/inkdrop-github-preview-theme) to create a new repository - Use this template -2. Clone your repository to the directory named `motif-preview` in the local filesystem -3. Open a terminal in the theme's directory -4. Change the name of the theme in the theme's `package.json` file -5. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). -6. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R -7. Enable the theme via the "_Preview Theme_" drop-down in the "Themes" tab of the Preferences -8. Start hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** +1. Run `ipm init motif --type theme-preview` to scaffold the theme into `./motif-preview` (`ipm` adds the `-preview` suffix for you). +2. Open a terminal in the `motif-preview` directory. +3. Run `ipm link --dev` to symlink your repository to `/inkdrop/dev/packages`. You can find the user data directory as described on [this page](https://docs.inkdrop.app/reference/user-data-directory). +4. Reload Inkdrop using Alt+Cmd+Shift+R / Alt+Ctrl+R +5. Enable the theme via the "_Preview Theme_" drop-down in the "Themes" tab of the Preferences +6. Start hot reloading by selecting the menu **Plugins → dev-tools → Start hot reloading themes** Now you are ready to make changes! After making changes, reload the app to reflected changes. +### Example preview themes + +- [Solarized Light Preview](https://github.com/inkdropapp/solarized-light-preview/) +- [Solarized Dark Preview](https://github.com/inkdropapp/solarized-dark-preview/) + ## Publish your theme Once you're happy with your theme and would like to share it with other Inkdrop users, it's time to publish it. 🎉 diff --git a/src/app/guides/list-of-commands/page.mdx b/src/app/guides/list-of-commands/page.mdx index ac3f4ae..7902b97 100644 --- a/src/app/guides/list-of-commands/page.mdx +++ b/src/app/guides/list-of-commands/page.mdx @@ -1,7 +1,7 @@ export const metadata = { title: 'List of commands', description: - 'How to use ES modules in your plugin with Rollup', + 'The list of built-in commands you can bind to keystrokes or invoke from plugins.', } # List of commands @@ -20,7 +20,11 @@ inkdrop.commands.dispatch(document.body, 'core:open-note', { - [Application commands](#application-commands) - [Core commands](#core-commands) +- [Telescope commands](#telescope-commands) +- [Sidebar commands](#sidebar-commands) - [Editor commands](#editor-commands) +- [AI commands](#ai-commands) +- [Table editor commands](#table-editor-commands) - [Export commands](#export-commands) - [Import commands](#import-commands) - [View commands](#view-commands) @@ -47,6 +51,14 @@ Shows a dialog about Inkdrop. Arranges windows listed in the Window menu in front of all other windows. +### application:check-for-update + +Checks for available app updates. + +### application:cycle-through-windows {{ warning: 'macOS only' }} + +Cycles through open Inkdrop windows. + ### application:hide {{ warning: 'macOS only' }} Hides Inkdrop windows. @@ -75,6 +87,10 @@ Opens up the preference window. Opens up the Inkdrop website. +### application:open-website-signup + +Opens up the Inkdrop sign-up page. + ### application:quit Quits the app. @@ -87,21 +103,17 @@ Opens up the Inkdrop forum. Opens up the list of software licenses. -### application:show-and-focus-main-window - -Show and focus Inkdrop main window. - -### application:toggle-main-window - -Toggle Inkdrop main window. - ### application:unhide-all-applications {{ warning: 'macOS only' }} Unhides all apps, including Inkdrop. ### application:view-help -Opens up the documentation website. +Opens up the user manual. + +### application:view-api-doc + +Opens up the API documentation website. ### application:zoom {{ warning: 'macOS only' }} @@ -111,10 +123,6 @@ Toggles the size and location of the window between its standard state and its u Perform data sync from the last checkpointer. -### application:sync-db-full - -Perform data sync from the beginning. - ### application:quick-note Create a note and show it in a separate window. @@ -141,6 +149,24 @@ Copy links to notes as Markdown links. By default, copy currently selected on th +### core:copy-note-uri {{ selector: 'body' }} + +Copy `inkdrop://` URIs to notes. By default, copy currently selected on the note list. + + + + (Optional) The ID(s) of the note(s) to process. + + + +### core:copy-link-address {{ selector: 'body' }} + +Copy the link address of an `` element under the event target in the preview pane. + +### core:copy-preview-selection {{ selector: 'body' }} + +Copy the currently selected text in the preview pane to the clipboard. + ### core:cut {{ selector: 'body' }} Delete the selected text and copy it to clipboard. @@ -192,6 +218,10 @@ Adds a filter condition with specified tag. ### core:find {{ selector: 'body' }} +Activates the search bar. + +### core:find-focus {{ selector: 'body' }} + Moves focus to the search bar. ### core:find-clear {{ selector: 'body' }} @@ -202,6 +232,12 @@ Clears the search bar text. Moves focus to the search bar and switch the search scope to global. + + + Whether to move focus to the search bar. Default: `true`. + + + ### core:focus-next {{ selector: 'body' }} Moves focus to the next focusable UI component. @@ -230,6 +266,10 @@ Inserts a horizontal rule: `* * *`. Inserts a link. +### core:insert-image {{ selector: '.mde-cm-wrapper' }} + +Shows a file picker and inserts the selected image into the editor at the cursor position. + ### core:move-notebook It's a private command for now @@ -250,10 +290,36 @@ Moves forward through the note open history. Creates a new note and open it in the editor. + + + (Optional) The notebook ID to create the note in. + + + +### core:new-note-in-separate-window {{ selector: 'body' }} + +Creates a new note in a separate window. Uses the currently selected notebook if applicable. + ### core:new-note-from-selection {{ selector: 'body' }} Creates a new note from the selected text on the editor. +### core:new-note-from-link {{ selector: 'body' }} + +Creates a new note from a link that targets an unresolved note in the preview pane. + + + + The title of the new note (also used as the link label). + + + The line of the editor where the link appears. + + + Whether to open the new note in a separate window. + + + ### core:new-notebook {{ selector: 'body' }} Shows up a dialog for creating a new notebook. @@ -274,6 +340,26 @@ Lists all notes on the note list bar. +### core:note-list-show-pinned-notes {{ selector: 'body' }} + +Lists only the pinned notes on the note list bar. + + + + Whether to select the first note or not. Default: `true` + + + +### core:note-list-show-notes-in-trash {{ selector: 'body' }} + +Lists notes in the trash on the note list bar. + + + + Whether to select the first note or not. Default: `true` + + + ### core:note-list-show-notes-in-book {{ selector: 'body' }} List notes in specified notebook on the note list bar @@ -339,11 +425,11 @@ Select the previous item on the note tags bar. Select the next item on the note tags bar. -### core:note-tags-bar-item-list-select-remove-item-before {{ selector: '.note-tags-bar-item-list' }} +### core:note-tags-bar-item-list-remove-item-before {{ selector: '.note-tags-bar-item-list' }} Remove the selected item and select its previous item on the note tags bar. -### core:note-tags-bar-item-list-select-remove-item {{ selector: '.note-tags-bar-item-list' }} +### core:note-tags-bar-item-list-remove-item {{ selector: '.note-tags-bar-item-list' }} Remove the target item on the note tags bar. @@ -361,6 +447,10 @@ Opens the first note of the note list bar in the editor. Opens the next note of currently selected note on the note list bar. +### core:open-prev-note {{ selector: 'body' }} + +Opens the previous note of currently selected note on the note list bar. + ### core:close-note {{ selector: 'body' }} Closes the currently editing note. @@ -373,14 +463,23 @@ Opens a note with specified note ID. The ID of the note to open. + + (Optional) Open the specific revision of the note. + - Whether to select the correspond note in the note list bar. + Whether to select the correspond note in the note list bar. Default: `true`. - Remember it in the navigation history. + Remember it in the navigation history. Default: `true`. - Whether to open the note in a separate window. + Whether to open the note in a separate window. Default: `false`. + + + The editor view mode to switch to after opening. One of `"edit"`, `"preview"`, or `"sidebyside"`. + + + Whether to focus the editor after opening. Default: `false`. @@ -394,14 +493,6 @@ Opens notes in separate windows. By default, opens notes selected on the note li -### core:open-prev-note {{ selector: 'body' }} - -Opens the previous note of currently selected note on the note list bar. - -### core:open-prev-note {{ selector: 'body' }} - -Opens the previous note of currently selected note on the note list bar. - ### core:open-tag-settings {{ selector: 'body' }} Opens up the settings window for the tag with specified ID. @@ -420,6 +511,23 @@ Pastes a text from clipboard. Pastes a text from clipboard as plain text. +### core:pin-note-to-top {{ selector: 'body' }} + +Pin or unpin notes to the top of the note list. By default, applies to notes currently selected on the note list. + + + + (Optional) The ID(s) of the note(s) to pin or unpin. + + + (Optional) The desired pinned state. If omitted, toggles the current state of the first target note. + + + +### core:popup-app-menu {{ selector: 'body' }} + +Shows the application menu as a popup context menu (useful on Windows / Linux where the menu bar is hidden). + ### core:redo {{ selector: 'body' }} Redoes the last undone change. @@ -470,18 +578,6 @@ Shows up a dialog for viewing note change history with specified ID. -### core:sidebar-focus {{ selector: 'body' }} - -Moves focus to the side bar. - -### core:sidebar-select-next-item {{ selector: 'body' }} - -Select next item of the side bar menu. - -### core:sidebar-select-prev-item {{ selector: 'body' }} - -Select previous item of the side bar menu. - ### core:strikethrough {{ selector: '.mde-cm-wrapper' }} Toggles strikethrough to the selected text. @@ -534,6 +630,16 @@ Toggles task list to the selected lines. Toggles inline code block to the selected text. + + + Whether to limit the toggle to the current selection only. + + + +### core:toggle-inline-mark-tag {{ selector: '.mde-cm-wrapper' }} + +Toggles an inline `` tag around the selected text. + ### core:undo {{ selector: '.mde-cm-wrapper' }} Undoes the last change. @@ -574,12 +680,136 @@ Inserts a caution alert block. --- +## Telescope commands + +[Telescope](https://docs.inkdrop.app/manual/quick-finder) is the quick finder for commands, notebooks, tags, and more. + +### core:toggle-telescope {{ selector: 'body' }} + +Opens Telescope if it is closed, or closes it if it is open. + +### core:show-telescope {{ selector: 'body' }} + +Opens Telescope. + + + + (Optional) Restrict the search to a specific source (e.g. commands, notebooks, tags). + + + (Optional) Restrict the search to a specific workspace. + + + (Optional) The initially selected item. + + + (Optional) Behavior when canceling, e.g. `"close"`. + + + +### core:close-telescope {{ selector: 'body' }} + +Closes Telescope. + +### core:telescope-clear-query {{ selector: 'body' }} + +Clears the Telescope query input. + +### core:telescope-clear-active-source {{ selector: 'body' }} + +Clears the active scoped source in Telescope. + +### core:telescope-commands {{ selector: 'body' }} + +Opens Telescope scoped to the commands source. + +### core:telescope-notebooks {{ selector: 'body' }} + +Opens Telescope scoped to the notebooks source. + +### core:telescope-tags {{ selector: 'body' }} + +Opens Telescope scoped to the tags source. + +--- + +## Sidebar commands + +### core:sidebar-focus {{ selector: 'body' }} + +Moves focus to the side bar. + +### core:sidebar-select-next-item {{ selector: 'body' }} + +Select the next item of the side bar menu. + +### core:sidebar-select-prev-item {{ selector: 'body' }} + +Select the previous item of the side bar menu. + +### core:sidebar-select-item {{ selector: 'body' }} + +Activate the currently selected item. + +### core:sidebar-disclose-item {{ selector: 'body' }} + +Toggle the disclosure (expand/collapse) of the currently selected item. + +### core:sidebar-expand-item {{ selector: 'body' }} + +Expand the currently selected item. + +### core:sidebar-collapse-item {{ selector: 'body' }} + +Collapse the currently selected item. + +### core:sidebar-scroll-to-selection {{ selector: 'body' }} + +Scrolls the side bar to the currently selected item. + +### core:sidebar-open-workspace-menu {{ selector: 'body' }} + +Open the workspace menu for a notebook. + + + + (Optional) The notebook ID. If omitted, opens the current workspace. + + + Whether to select the first note in the workspace. Default: `true`. + + + +### core:sidebar-hide-workspace-menu {{ selector: 'body' }} + +Hide the workspace menu. + +### core:sidebar-workspace-go-up {{ selector: 'body' }} + +Navigate one level up in the workspace menu. + +--- + ## Editor commands ### editor:clear-find {{ selector: 'body' }} Clears the text in the editor search bar. + + + Whether to keep the search bar focused after clearing. + + + +### editor:toggle-regex {{ selector: '.cm-editor' }} + +Toggles regex matching in the editor search bar. + +### editor:toggle-case-sensitive {{ selector: '.cm-editor' }} + +Toggles case-sensitive matching in the editor search bar. + ### editor:change-note-status-none {{ selector: 'body' }} Set the note status of the editing note to None. @@ -636,6 +866,10 @@ Deletes the wrapped line left. Deletes the wrapped line right. +### editor:edit-with-ai {{ selector: '.mde-cm-wrapper' }} + +Opens the AI inline assistant to edit the currently selected text. Does nothing if there is no selection. + ### editor:find {{ selector: 'body' }} Toggles the editor search bar. @@ -653,8 +887,11 @@ Locates the previous item in the editor that matches criteria. Finds given text in the editor. - - String, the text to find. + + The text to find. + + + Whether to parse the text as a search query (with operators). @@ -726,13 +963,85 @@ Moves the cursor to one page down. Moves the cursor to one page up. -### editor:go-word-left {{ selector: '.mde-cm-wrapper' }} +### editor:go-syntax-left {{ selector: '.mde-cm-wrapper' }} + +Moves the cursor one syntax node to the left. + +### editor:go-syntax-right {{ selector: '.mde-cm-wrapper' }} + +Moves the cursor one syntax node to the right. + +### editor:select-char-left {{ selector: '.mde-cm-wrapper' }} + +Extends the selection by one character to the left. + +### editor:select-char-right {{ selector: '.mde-cm-wrapper' }} + +Extends the selection by one character to the right. + +### editor:select-syntax-left {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one syntax node to the left. + +### editor:select-syntax-right {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one syntax node to the right. + +### editor:select-group-left {{ selector: '.mde-cm-wrapper' }} + +Extends the selection by one group to the left. + +### editor:select-group-right {{ selector: '.mde-cm-wrapper' }} + +Extends the selection by one group to the right. + +### editor:select-line-up {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one line up. + +### editor:select-line-down {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one line down. + +### editor:select-line-start {{ selector: '.mde-cm-wrapper' }} + +Extends the selection to the line start. + +### editor:select-line-end {{ selector: '.mde-cm-wrapper' }} + +Extends the selection to the line end. + +### editor:select-line-left {{ selector: '.mde-cm-wrapper' }} + +Extends the selection to the line boundary on the left. -Moves the cursor to one word left. +### editor:select-line-right {{ selector: '.mde-cm-wrapper' }} -### editor:go-word-right {{ selector: '.mde-cm-wrapper' }} +Extends the selection to the line boundary on the right. -Moves the cursor to one word right. +### editor:select-page-up {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one page up. + +### editor:select-page-down {{ selector: '.mde-cm-wrapper' }} + +Extends the selection one page down. + +### editor:select-doc-start {{ selector: '.mde-cm-wrapper' }} + +Extends the selection to the start of the document. + +### editor:select-doc-end {{ selector: '.mde-cm-wrapper' }} + +Extends the selection to the end of the document. + +### editor:move-line-up {{ selector: '.mde-cm-wrapper' }} + +Moves the current line(s) up. + +### editor:move-line-down {{ selector: '.mde-cm-wrapper' }} + +Moves the current line(s) down. ### editor:indent {{ selector: '.mde-cm-wrapper' }} @@ -801,7 +1110,11 @@ Deletes the line at the cursor. ### editor:new-line {{ selector: '.mde-cm-wrapper' }} -Inserts a new line below the cursor. +Inserts a plain new line at the cursor without continuing any Markdown markup. + +### editor:new-line-continue-markup {{ selector: '.mde-cm-wrapper' }} + +Inserts a new line and continues the current Markdown markup — list markers, ordered-list numbers, task list checkboxes, and blockquotes — then indents to match the surrounding block. ### editor:open-line {{ selector: '.mde-cm-wrapper' }} @@ -815,6 +1128,14 @@ Redoes the last selection. Initiates the replace sequence in the editor. +### editor:replace-next {{ selector: '.cm-editor' }} + +Replaces the next occurrence in the editor. + +### editor:replace-all {{ selector: '.cm-editor' }} + +Replaces all occurrences in the editor. + ### editor:replace-selection {{ selector: '.mde-cm-wrapper' }} Replaces selected text with the given text. @@ -881,6 +1202,195 @@ Syncs the scroll position of the preview pane with the same position of the edit Moves focus to the title input bar of the editor. +### editor:focus-and-go-doc-start {{ selector: 'body' }} + +Focuses the editor and moves the cursor to the start of the document. + +### editor:move-focus-to-next {{ selector: 'body' }} + +Moves focus from the title input bar to the next editable area. + +### editor:insert-text {{ selector: 'body' }} + +Inserts the given text at the current cursor position. + + + + The text to insert. + + + (Optional) Selection to set after insertion, with `anchor` (number) and optional `head` (number) offsets from the insertion position. + + + +### editor:open-link {{ selector: '.mde-cm-wrapper' }} + +Opens the URL at the cursor in the default browser. + +### editor:copy-link {{ selector: '.mde-cm-wrapper' }} + +Copies the URL at the cursor to the clipboard. + +### editor:paste-url {{ selector: '.mde-cm-wrapper' }} + +Pastes a URL at the cursor. If text is selected, wraps it as a Markdown link. + + + + The URL to paste. + + + +### editor:start-completion {{ selector: '.mde-cm-wrapper' }} + +Manually triggers the autocompletion popup. + +### editor:accept-completion {{ selector: '.mde-cm-wrapper' }} + +Accepts the currently highlighted completion suggestion. + +### editor:close-completion {{ selector: '.mde-cm-wrapper' }} + +Closes the autocompletion popup. + +### editor:move-completion-selection-up {{ selector: '.mde-cm-wrapper' }} + +Moves the highlighted completion suggestion up. + +### editor:move-completion-selection-down {{ selector: '.mde-cm-wrapper' }} + +Moves the highlighted completion suggestion down. + +### editor:move-completion-selection-page-up {{ selector: '.mde-cm-wrapper' }} + +Moves the highlighted completion suggestion up by a page. + +### editor:move-completion-selection-page-down {{ selector: '.mde-cm-wrapper' }} + +Moves the highlighted completion suggestion down by a page. + +### editor:refresh {{ selector: 'body' }} + +Requests the editor to recompute its measurements (after layout changes). + +### editor:set-doc {{ selector: 'body' }} + +Replaces the entire editor content with the given body. + + + + The new document body. + + + +### editor:toggle-line-numbers {{ selector: 'body' }} + +Toggles line numbers in the editor. + +### editor:toggle-line-wrapping {{ selector: 'body' }} + +Toggles soft line wrapping in the editor. + +### editor:toggle-readable-line-length {{ selector: 'body' }} + +Toggles a maximum readable line length in the editor. + +### editor:toggle-limit-max-width {{ selector: 'body' }} + +Toggles whether the editor limits its maximum content width. + +### editor:add-code-language {{ selector: 'body' }} + +Registers a code language with the Markdown editor for fenced-code syntax highlighting. + + + + A CodeMirror `LanguageDescription` instance. + + + +### editor:remove-code-language {{ selector: 'body' }} + +Removes a previously registered code language. + + + + The name of the language to remove. + + + +### editor:add-extension {{ selector: 'body' }} + +Adds a CodeMirror extension to the editor. + + + + A CodeMirror `Extension`. + + + +### editor:remove-extension {{ selector: 'body' }} + +Removes a previously added CodeMirror extension. + + + + The extension to remove. + + + +### editor:add-markdown-extension {{ selector: 'body' }} + +Adds a Lezer Markdown extension to the parser. + + + + A Lezer `MarkdownExtension`. + + + +### editor:remove-markdown-extension {{ selector: 'body' }} + +Removes a previously added Markdown extension. + + + + The extension to remove. + + + +### editor:add-slash-commands {{ selector: 'body' }} + +Registers additional items for the slash-command menu. + + + + The slash commands to add. + + + +### editor:remove-slash-commands {{ selector: 'body' }} + +Unregisters previously added slash-command items. + + + + The slash commands to remove. + + + +### editor:new-table-row-and-continue {{ selector: '.mde-cm-wrapper' }} + +Inserts a new row in the table at the cursor and continues editing. + +### editor:move-to-next-table-cell {{ selector: '.mde-cm-wrapper' }} + +Moves the cursor to the next cell of the table at the cursor. + +### editor:move-to-previous-table-cell {{ selector: '.mde-cm-wrapper' }} + +Moves the cursor to the previous cell of the table at the cursor. + ### editor:toggle-overwrite {{ selector: '.mde-cm-wrapper' }} Toggles overwrite mode. @@ -923,6 +1433,143 @@ Hide editor drawer. --- +## AI commands + +Commands for managing the AI inline assistant. These are global commands you typically invoke programmatically (for example, from a plugin) to customize the built-in prompt presets shown when editing the selection with AI. + +### ai:add-inline-assistant-prompt-presets {{ selector: 'body' }} + +Adds prompt presets to the AI inline assistant. A preset whose ID matches an existing one replaces it; the rest are appended to the list. + + + + An array of preset objects, each with the following keys: + + + + A unique identifier for the preset. + + + The label shown in the preset list. + + + The prompt sent to the AI when the preset is selected. + + + + + + +### ai:remove-inline-assistant-prompt-presets {{ selector: 'body' }} + +Removes prompt presets from the AI inline assistant by ID. + + + + The IDs of the presets to remove. + + + +--- + +## Table editor commands + +These commands operate while the cursor is inside a Markdown table. + +### table-editor:select-cell {{ selector: '.mde-cm-wrapper' }} + +Selects the contents of the current cell. + +### table-editor:next-cell {{ selector: '.mde-cm-wrapper' }} + +Moves to the next cell. + +### table-editor:previous-cell {{ selector: '.mde-cm-wrapper' }} + +Moves to the previous cell. + +### table-editor:next-row {{ selector: '.mde-cm-wrapper' }} + +Moves to the next row. + +### table-editor:escape {{ selector: '.mde-cm-wrapper' }} + +Exits the table editing mode. + +### table-editor:move-left {{ selector: '.mde-cm-wrapper' }} + +Moves one cell to the left. + +### table-editor:move-right {{ selector: '.mde-cm-wrapper' }} + +Moves one cell to the right. + +### table-editor:move-up {{ selector: '.mde-cm-wrapper' }} + +Moves one cell up. + +### table-editor:move-down {{ selector: '.mde-cm-wrapper' }} + +Moves one cell down. + +### table-editor:align-left {{ selector: '.mde-cm-wrapper' }} + +Aligns the current column to the left. + +### table-editor:align-right {{ selector: '.mde-cm-wrapper' }} + +Aligns the current column to the right. + +### table-editor:align-center {{ selector: '.mde-cm-wrapper' }} + +Aligns the current column to the center. + +### table-editor:align-none {{ selector: '.mde-cm-wrapper' }} + +Removes column alignment. + +### table-editor:move-row-up {{ selector: '.mde-cm-wrapper' }} + +Moves the current row up. + +### table-editor:move-row-down {{ selector: '.mde-cm-wrapper' }} + +Moves the current row down. + +### table-editor:move-column-left {{ selector: '.mde-cm-wrapper' }} + +Moves the current column to the left. + +### table-editor:move-column-right {{ selector: '.mde-cm-wrapper' }} + +Moves the current column to the right. + +### table-editor:delete-column {{ selector: '.mde-cm-wrapper' }} + +Deletes the current column. + +### table-editor:delete-row {{ selector: '.mde-cm-wrapper' }} + +Deletes the current row. + +### table-editor:insert-column {{ selector: '.mde-cm-wrapper' }} + +Inserts a new column. + +### table-editor:insert-row {{ selector: '.mde-cm-wrapper' }} + +Inserts a new row. + +### table-editor:format {{ selector: '.mde-cm-wrapper' }} + +Reformats the current table. + +### table-editor:format-all {{ selector: '.mde-cm-wrapper' }} + +Reformats all tables in the note. + +--- + ## Export commands ### export-as-html:export {{ selector: 'body' }} @@ -957,25 +1604,41 @@ Imports a note from a HTML file. ## View commands -### view:sort-by-date-created {{ selector: 'body' }} +### view:sort-by-title-in-asc {{ selector: 'body' }} + +Sorts the note list by title in ascending order. + +### view:sort-by-title-in-desc {{ selector: 'body' }} + +Sorts the note list by title in descending order. + +### view:sort-by-date-created-in-asc {{ selector: 'body' }} -Sorts the note list by date created. +Sorts the note list by date created in ascending order. -### view:sort-by-date-updated {{ selector: 'body' }} +### view:sort-by-date-created-in-desc {{ selector: 'body' }} -Sorts the note list by date updated. +Sorts the note list by date created in descending order. -### view:sort-by-title {{ selector: 'body' }} +### view:sort-by-date-updated-in-asc {{ selector: 'body' }} -Sorts the note list by title. +Sorts the note list by date updated in ascending order. -### view:sort-in-ascending {{ selector: 'body' }} +### view:sort-by-date-updated-in-desc {{ selector: 'body' }} -Sorts the note list in ascending order. +Sorts the note list by date updated in descending order. -### view:sort-in-descending {{ selector: 'body' }} +### view:show-editor {{ selector: 'body' }} -Sorts the note list in descending order. +Shows the editor pane. + +### view:show-preview {{ selector: 'body' }} + +Shows the preview pane. + +### view:show-side-by-side {{ selector: 'body' }} + +Shows the editor and preview side by side. ### view:toggle-distraction-free {{ selector: 'body' }} @@ -1009,10 +1672,6 @@ Decreases the font size in the editor. Increases the font size in the editor. -### window:install-shell-commands {{ selector: 'body' }} - -Installs `ipm` command to your system. - ### window:new-inkdrop-window {{ selector: 'body' }} Opens up a new window. diff --git a/src/app/guides/plugin-word-count/page.mdx b/src/app/guides/plugin-word-count/page.mdx index 62b3656..b8da7ff 100644 --- a/src/app/guides/plugin-word-count/page.mdx +++ b/src/app/guides/plugin-word-count/page.mdx @@ -1,7 +1,6 @@ export const metadata = { title: 'Plugin: Word count', - description: - 'A tutorial on how to create a plugin', + description: 'A tutorial on how to create a plugin' } # Plugin: Word count @@ -10,10 +9,10 @@ Let's get started by writing a very simple plugin and looking at some of the too ## Scaffolding Your Plugin -You can generate code scaffolding by running following command: +You can generate code scaffolding by running following [`ipm` command](https://github.com/inkdropapp/ipm-cli): ```shell -$ ipm init -p wordcount +$ ipm init wordcount ``` You can see that `ipm` has created about a dozen files that make up the plugin. Let's take a look at each of them to get an idea of how a plugin is structured, then we can modify them to get our word count functionality. @@ -24,26 +23,29 @@ The basic plugin layout is as follows: wordcount/ ├── keymaps │   └── wordcount.json -├── lib -│   ├── wordcount-message-dialog.js -│   └── wordcount.js ├── menus │   └── wordcount.json +├── src +│   ├── WordcountMessageDialog.tsx +│   ├── env.ts +│   └── index.ts +├── styles +│   └── wordcount.css ├── package.json -└── styles - └── wordcount.less +├── tsconfig.json +└── tsdown.config.js ``` -Not every plugin will have (or need) all of these directories. Let's see what some of these are so we can start messing with them. +The plugin is written in TypeScript under `src/`. Running `npm run build` compiles it with [tsdown](https://tsdown.dev/) into a `lib/` directory of plain CommonJS — that build output is what Inkdrop actually loads, and what the `main` field points to. Not every plugin will have (or need) all of these directories. Let's see what some of these are so we can start messing with them. ### package.json -Similar to [Node modules](https://docs.npmjs.com/getting-started/what-is-npm), Inkdrop plugins contain a `package.json` file in their top-level directory. This file contains metadata about the plugin, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded. +Similar to [Node modules](https://docs.npmjs.com/about-npm), Inkdrop plugins contain a `package.json` file in their top-level directory. This file contains metadata about the plugin, such as the path to its "main" module, library dependencies, and manifests specifying the order in which its resources should be loaded. In addition to some of the regular [Node `package.json` keys](https://docs.npmjs.com/files/package.json) available, Inkdrop `package.json` files have their own additions. -- `main`: the path to the JavaScript file that's the entry point to your package. If this is missing, Inkdrop will default to looking for an `index.js`. -- `styles`: an Array of Strings identifying the order of the style sheets your plugin needs to load. If not specified, style sheets in the `styles` directory are added alphabetically. +- `main`: the path to the JavaScript file that's the entry point to your package. With the TypeScript scaffold this points at the **compiled** output (`./lib/index`), not your `src/` source. If this is missing, Inkdrop will default to looking for an `index.js`. +- `styleSheets`: an Array of Strings identifying the order of the style sheets your plugin needs to load. If not specified, style sheets in the `styles` directory are added alphabetically. - `keymaps`: an Array of Strings identifying the order of the key mappings your plugin needs to load. If not specified, mappings in the `keymaps` directory are added alphabetically. - `menus`: an Array of Strings identifying the order of the menu mappings your plugin needs to load. If not specified, mappings in the `menus` directory are added alphabetically. @@ -52,55 +54,72 @@ The `package.json` in the plugin we've just generated looks like this currently: ```json { "name": "wordcount", - "main": "./lib/wordcount", + "main": "./lib/index", "version": "0.0.0", "description": "A short description of your package", "keywords": [], - "repository": "https://github.com/inkdropapp/wordcount", + "repository": "https://github.com/your-username/inkdrop-wordcount", "license": "MIT", - "engines": { - "inkdrop": "^5.x" + "styleSheets": ["wordcount.css"], + "engines": { "inkdrop": "^6.0.0" }, + "scripts": { + "dev": "tsdown --watch", + "build": "tsdown", + "prepublishOnly": "npm run build" }, - "dependencies": {} + "devDependencies": { + "@inkdropapp/types": "^0.0.8", + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", + "tsdown": "^0.22.2", + "typescript": "^6.0.3" + } } ``` One of the first things you should do is ensure that this information is filled out. The name, description, repository URL the project will be at, and the license can all be filled out immediately. The other information we'll get into more detail on as we go. -Do not forget to update the repository URL. The one generated for you is invalid by design and will prevent you from publishing your package until updated. + Do not forget to update the repository URL. The one generated for you is + invalid by design and will prevent you from publishing your package until + updated. - Inkdrop is planned to bump up its major version to 6, which has breaking changes to the UI theming system. If your plugin is not a theme, you can target the engine version to `{"inkdrop": ">=5.6.0 <7"}` in your `package.json` file. + This plugin targets Inkdrop 6 (`"inkdrop": "^6.0.0"`). The scaffold is written + in TypeScript, compiled to plain CommonJS in `lib/` by + [tsdown](https://tsdown.dev/), and typed against + [`@inkdropapp/types`](https://github.com/inkdropapp/types). If you're + upgrading an existing plugin from Inkdrop 5, follow the [Plugin Migration + Guide from v5 to v6](/appendix/plugin-migration-from-v5-to-v6). ### Source Code -If you want to extend Inkdrop's behavior, your plugin should contain a single top-level module, which you export from whichever file is indicated by the `main` key in your `package.json` file. In the plugin we just generated, the main package file is `lib/wordcount.js`. The remainder of your code should be placed in the lib directory, and required from your top-level file. If the `main` key is not in your `package.json` file, it will look for `index.js` as the main entry point. +If you want to extend Inkdrop's behavior, your plugin should contain a single top-level module, which you export from your entry file in `src/`. In the plugin we just generated, the entry is `src/index.ts`; tsdown compiles everything under `src/` into CommonJS in `lib/`, and the `main` key points at that build output (`./lib/index`). The rest of your code lives alongside the entry file in `src/` and is imported from it. Your plugin's top-level module is a singleton object that manages the lifecycle of your extensions to Inkdrop. Even if your plugin creates ten different components and appends them to different parts of the DOM, it's all managed from your top-level object. -Your plugin's top-level module can implement the following basic methods: +Your plugin's top-level module can implement the following basic methods. In Inkdrop 6, both receive the `inkdrop` [Environment](/modules/environment) as their first argument: -- `activate()`: This **optional** method is called when your plugin is activated. Use this to do initialization work when your plugin is started (like setting up React Components or binding events). -- `deactivate()`: This **optional** method is called when the window is shutting down. If your plugin is watching any files or holding external resources in any other way, release them here. If you're just subscribing to things on window, you don't need to worry because that's getting torn down anyway. +- `activate(env)`: This **optional** method is called when your plugin is activated. Use this to do initialization work when your plugin is started (like setting up React Components or binding events). `env` is the same instance exposed globally as `inkdrop` — a [future release will sandbox the global away](/appendix/plugin-migration-from-v5-to-v6#activate-receives-the-inkdrop-environment), so prefer the argument. +- `deactivate(env)`: This **optional** method is called when the window is shutting down. If your plugin is watching any files or holding external resources in any other way, release them here. If you're just subscribing to things on window, you don't need to worry because that's getting torn down anyway. ### Style Sheets -Style sheets for your plugin should be placed in the `styles` directory. Any style sheets in this directory will be loaded and attached to the DOM when your plugin is activated. Style sheets can be written as CSS or [Less](http://lesscss.org/), but Less is recommended. +Style sheets for your plugin should be placed in the `styles` directory. Any style sheets in this directory will be loaded and attached to the DOM when your plugin is activated. In Inkdrop 6, write them as plain **CSS** — the app has [dropped LESS support](/appendix/plugin-migration-from-v5-to-v6#less-is-deprecated), since modern CSS supports nested selectors and variables natively. Ideally, you won't need much in the way of styling. Inkdrop's styles are based on [Semantic UI](http://semantic-ui.com/) which provides a standard set of components which define both the colors and UI elements for any plugin that fits into Inkdrop seamlessly. -If you _do_ need special styling, try to keep only structural styles in the plugin style sheets. If you must specify colors and sizing, these should be taken from the pre-defined CSS Variables where you can find at [variables.less](https://github.com/inkdropapp/inkdrop-ui-theme-template/blob/master/src/definitions/globals/variables.less) like this: +If you _do_ need special styling, try to keep only structural styles in the plugin style sheets. If you must specify colors and sizing, take them from Inkdrop's predefined CSS variables and design tokens. Every `--color-*` and `--hsl-*` token is published in the [`@inkdropapp/css`](https://github.com/inkdropapp/css) repository. Use them like this: -```less +```css .special-button.highlighted { background-color: var(--highlight-background); } ``` -An optional `styles` array in your `package.json` can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically. +An optional `styleSheets` array in your `package.json` can list the style sheets by name to specify a loading order; otherwise, style sheets are loaded alphabetically. ### Keymaps @@ -170,17 +189,17 @@ It's recommended to specify a context menu item for commands that are linked to ```json { "context-menu": { - ".CodeMirror": [ + ".cm-editor": [ { "label": "Toggle Word Count", - "command": "empty:toggle" + "command": "wordcount:toggle" } ] } } ``` -This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Inkdrop text editor pane. +This adds a "Toggle Word Count" menu option to the menu that pops up when you right-click in an Inkdrop text editor pane. The editor's root element is `.cm-editor` in Inkdrop 6 — it runs on [CodeMirror 6](https://codemirror.net/), so the v5 `.CodeMirror` selector no longer matches. ![Context Menu](/images/plugin-word-count_context-menu.png) @@ -215,39 +234,71 @@ Currently with the generated package we have, if we run that `wordcount:toggle` ![Dialog](/images/plugin-word-count_dialog.png) -### Linking to the App Locally +### Building and Linking to the App Locally + +Because the plugin is written in TypeScript, you first need to build it into the `lib/` directory that `main` points to. Install the dependencies and start a watch build that rebuilds on every change: -You can install the plugin locally for development. +```sh +npm install +npm run dev # or `npm run build` for a one-off production build +``` -Run `ipm link --dev` to symlink your repository to `/dev/packages` in [the user data directory](https://docs.inkdrop.app/manual/basic-usage#user-data-directory). +Then symlink the plugin into Inkdrop for local development. Run `ipm link --dev` to symlink your repository to `dev/packages` in [the user data directory](https://docs.inkdrop.app/manual/basic-usage#user-data-directory). Let Inkdrop run in **Development Mode** by selecting the menu _Inkdrop > Preferences_ on macOS or _File > Settings_ on Windows and Linux, clicking the _General_ tab on the left hand navigation, and check the "_Development Mode_", then reload the app by pressing `Alt+Cmd+Shift+R` / `Alt+Ctrl+R`. ### Understanding the Generated Code -Let's take a look at the code in our `lib` directory and see what is happening. +Let's take a look at the code in our `src` directory and see what is happening. There are three files: + +- `src/index.ts` — the main module, pointed to (after compilation to `lib/`) by the `main` field in `package.json`. It manages the plugin lifecycle. +- `src/WordcountMessageDialog.tsx` — a React component that renders the plugin's modal dialog. +- `src/env.ts` — a small helper that captures the `inkdrop` environment passed to `activate()` so the rest of the plugin can reach it. + +#### The environment helper (`src/env.ts`) -There are two files in our lib directory. One is the main file (`lib/wordcount.js`), which is pointed to in the `package.json` file as the main file to execute for this plugin. This file handles the logic of the whole plugin. +In Inkdrop 6, `activate()` receives the `inkdrop` [Environment](/modules/environment) as its first argument, and a future release will sandbox the global `inkdrop` reference away. So instead of reaching for the global, the scaffold captures the environment once and shares it through a small module: -The second file is a React Component class, `lib/wordcount-message-dialog.js`, which handles the UI elements of the plugin. Let's look at this file first: +```ts +import type { Environment } from '@inkdropapp/types' -```jsx -'use babel' +let captured: Environment | undefined +export function setEnv(env: Environment | undefined): void { + captured = env +} + +export function getEnv(): Environment { + if (!captured) { + throw new Error('[wordcount] env accessed before activate()') + } + return captured +} +``` + +`activate()` calls `setEnv(env)`, and any other module can then call `getEnv()` to use the environment. See [`activate()` receives the inkdrop environment](/appendix/plugin-migration-from-v5-to-v6#activate-receives-the-inkdrop-environment) for the rationale. + +#### The dialog component (`src/WordcountMessageDialog.tsx`) + +Inkdrop is built with [React](https://react.dev/), and you make UI components with it. `WordcountMessageDialog` is [a function component](https://react.dev/learn/your-first-component) that renders the modal message dialog shown when the command is toggled: + +```tsx import React, { useEffect, useCallback } from 'react' import { logger, useModal } from 'inkdrop' +import type { Dialog as DialogClass } from '@inkdropapp/types' +import { getEnv } from './env' -const WordcountMessageDialog = props => { +export const WordcountMessageDialog: React.FC = () => { const modal = useModal() - const { Dialog } = inkdrop.components.classes + const Dialog = inkdrop.components.getComponentClass('Dialog') as DialogClass const toggle = useCallback(() => { modal.show() logger.debug('Wordcount was toggled!') - }, []) + }, [modal]) useEffect(() => { - const sub = inkdrop.commands.add(document.body, { + const sub = getEnv().commands.add(document.body, { 'wordcount:toggle': toggle }) return () => sub.dispose() @@ -265,67 +316,65 @@ const WordcountMessageDialog = props => { ) } - -export default WordcountMessageDialog ``` -Inkdrop is built with [React](https://facebook.github.io/react/), and you can make UI components with it. -The `WordcountMessageDialog` defines a React Component which shows a modal message dialog on toggling from the command. -This component is [a function component](https://reactjs.org/docs/components-and-props.html) that renders the message dialog using `Dialog` built-in component imported from [the component manager](/modules/component-manager) by doing like so: +It grabs the built-in `Dialog` component from [the component manager](/modules/component-manager): -```js -const { Dialog } = inkdrop.components.classes +```ts +const Dialog = inkdrop.components.getComponentClass('Dialog') as DialogClass ``` -It uses `useModal` built-in React Hook method to manage the dialog visibility state. -You can show the dialog by calling `modal.show()` and hide with `modal.hide()` method. -To toggle the dialog by a command, it defines a callback function with React's [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback) as following: +`getComponentClass` replaces v5's `inkdrop.components.classes.Dialog`. The `as DialogClass` cast restores the `Dialog.Title` / `Dialog.Content` / `Dialog.Actions` sub-component types from [`@inkdropapp/types`](https://github.com/inkdropapp/types). -```js +It uses the `useModal` built-in React Hook to manage the dialog's visibility state. You can show the dialog by calling `modal.show()` and hide it with `modal.close()`. To toggle the dialog from a command, it defines a callback function with React's [useCallback](https://react.dev/reference/react/useCallback): + +```ts const toggle = useCallback(() => { modal.show() logger.debug('Wordcount was toggled!') -}, []) +}, [modal]) ``` -Then, it binds the callback function with the `"wordcount:toggle"` command with React's [useEffect](https://reactjs.org/docs/hooks-effect.html): +Then, it binds the callback to the `"wordcount:toggle"` command with React's [useEffect](https://react.dev/reference/react/useEffect), reaching the command registry through `getEnv()`: -```js +```ts useEffect(() => { - const sub = inkdrop.commands.add(document.body, { + const sub = getEnv().commands.add(document.body, { 'wordcount:toggle': toggle }) return () => sub.dispose() }, [toggle]) ``` -The [`inkdrop.commands.add` method](/modules/command-registry#register-a-command) returns an instance of the [CompositeDisposable](/event-subscription/composite-disposable) class. -It automatically unbinds the command when the component is unmounted from the DOM by calling `CompositeDisposable#dispose()` method. +The [`commands.add` method](/modules/command-registry#register-a-command) returns an instance of the [CompositeDisposable](/event-subscription/composite-disposable) class. Returning its `dispose` from the effect automatically unbinds the command when the component is unmounted from the DOM. -The second file we have is the main entry point to the plugin. Again, because it's referenced in the `package.json` file. Let's take a look at that file, it's pretty simple: +#### The main module (`src/index.ts`) -```js -'use babel' +The entry point wires everything together. It's a class that implements `IInkdropPlugin`, registering the dialog on `activate()` and cleaning up on `deactivate()`: -import WordcountMessageDialog from './wordcount-message-dialog' +```ts +import type { Environment, IInkdropPlugin } from '@inkdropapp/types' +import { setEnv } from './env' +import { WordcountMessageDialog } from './WordcountMessageDialog' -module.exports = { - activate() { - inkdrop.components.registerClass(WordcountMessageDialog) - inkdrop.layouts.addComponentToLayout('modals', 'WordcountMessageDialog') - }, +class InkdropPlugin implements IInkdropPlugin { + activate(env: Environment) { + setEnv(env) + env.components.registerClass(WordcountMessageDialog) + env.layouts.addComponentToLayout('modals', 'WordcountMessageDialog') + } - deactivate() { - inkdrop.layouts.removeComponentFromLayout( - 'modals', - 'WordcountMessageDialog' - ) - inkdrop.components.deleteClass(WordcountMessageDialog) + deactivate(env: Environment) { + env.layouts.removeComponentFromLayout('modals', 'WordcountMessageDialog') + env.components.deleteClass(WordcountMessageDialog) + setEnv(undefined) } } + +export default new InkdropPlugin() ``` -In the `activate` method called on activation of the plugin, it registers the `WordcountMessageDialog` class to a [Inkdrop component registry](/modules/component-manager) and adds it to a hidden modal layout. +On activation it stores the environment with `setEnv(env)`, registers the `WordcountMessageDialog` class with the [component registry](/modules/component-manager), and adds it to a hidden `modals` layout. On deactivation it tears all of that down and clears the captured environment. See also: [Component Manager](/modules/component-manager) and [Layout Manager](/modules/layout-manager) @@ -348,7 +397,7 @@ So, let's review the actual flow in this plugin. So now that we understand what is happening, let's modify the code so that our little modal message dialog shows us the current word count instead of static text. We'll do this in a very simple way. When the dialog is toggled, we'll count the words right before displaying the modal. So let's do this in the `toggle` command. -If we add some code to count the words and ask the React component to update itself, we'll have something as following in `lib/wordcount-message-dialog.js`. +If we add some code to count the words and ask the React component to update itself, we'll have something as following in `src/WordcountMessageDialog.tsx`. First, import `useState` method from `react`: ```js @@ -375,11 +424,11 @@ In this example, we use [useSelector](https://react-redux.js.org/api/hooks) func const noteBody = useSelector(selectEditingNoteBody) ``` -The `selectEditingNoteBody` function should be defined outside the component function like so: +The `selectEditingNoteBody` function should be defined outside the component function like so. Typing the state shape inline keeps the selector strict-mode clean: -```js -const selectEditingNoteBody = ({ editingNote }) => - editingNote ? editingNote.body : '' +```ts +const selectEditingNoteBody = (state: { editingNote?: { body: string } }) => + state.editingNote ? state.editingNote.body : '' ``` Now, let's define a function that counts the words: @@ -392,11 +441,11 @@ const countWords = useCallback(() => { We call it when toggling the dialog: -```js +```ts const toggle = useCallback(() => { setCount(countWords()) modal.show() -}, [countWords]) +}, [countWords, modal]) ``` Finally, we tell our message dialog to display the word count. @@ -415,21 +464,21 @@ return ( ) ``` -Now, `lib/wordcount-message-dialog.js` looks like this: - -```js -'use babel' +Now, `src/WordcountMessageDialog.tsx` looks like this: +```tsx import React, { useState, useEffect, useCallback } from 'react' import { logger, useModal } from 'inkdrop' import { useSelector } from 'react-redux' +import type { Dialog as DialogClass } from '@inkdropapp/types' +import { getEnv } from './env' -const selectEditingNoteBody = ({ editingNote }) => - editingNote ? editingNote.body : '' +const selectEditingNoteBody = (state: { editingNote?: { body: string } }) => + state.editingNote ? state.editingNote.body : '' -const WordcountMessageDialog = props => { +export const WordcountMessageDialog: React.FC = () => { const modal = useModal() - const { Dialog } = inkdrop.components.classes + const Dialog = inkdrop.components.getComponentClass('Dialog') as DialogClass const [count, setCount] = useState(0) const noteBody = useSelector(selectEditingNoteBody) @@ -440,10 +489,10 @@ const WordcountMessageDialog = props => { const toggle = useCallback(() => { setCount(countWords()) modal.show() - }, [countWords]) + }, [countWords, modal]) useEffect(() => { - const sub = inkdrop.commands.add(document.body, { + const sub = getEnv().commands.add(document.body, { 'wordcount:toggle': toggle }) return () => sub.dispose() @@ -461,8 +510,6 @@ const WordcountMessageDialog = props => { ) } - -export default WordcountMessageDialog ``` Pretty simple! We take the count number that was passed in and place it into a string that we then stick into the element that our component is controlling. @@ -471,7 +518,7 @@ Pretty simple! We take the count number that was passed in and place it into a s ## Basic Debugging -You'll notice a few `console.log` statements in the code. One of the cool things about Inkdrop being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development. +You'll notice a `logger.debug` call in the code, which writes to the developer console. One of the cool things about Inkdrop being built on Chromium is that you can use some of the same debugging tools available to you that you have when doing web development. To open up the Developer Console, press Alt+Cmd+I on macOS or Alt+Ctrl+I on Linux and Windows, or choose the menu option _Developer > Toggle Developer Tools_. @@ -483,7 +530,14 @@ From here you can inspect objects, run code and view console output just as thou Now that our simple plugin is working and tested, let's go ahead and publish it so it's available to the world. -Inkdrop bundles a command line utility called `ipm` which we first used back in [Using ipm (Inkdrop Plugin Manager)](https://docs.inkdrop.app/manual/extend-inkdrop-with-plugins#command-line-ipm---inkdrop-plugin-manager) to search for and install plugins via the command line. The `ipm` command can also be used to publish Inkdrop plugins to the public registry and update them. +Inkdrop plugins are published with the [`ipm` (Inkdrop Plugin Manager) CLI](https://github.com/inkdropapp/ipm-cli) — the same tool we used to scaffold and link the plugin. In Inkdrop 6 it's a standalone package; if you haven't installed and authenticated it yet, do so once: + +```sh +npm install -g @inkdropapp/ipm-cli +ipm configure +``` + +Besides searching for and installing plugins, `ipm` publishes them to the public registry and updates them. ### Prepare Your Package @@ -491,9 +545,7 @@ There are a few things you should double check before publishing: - Your `package.json` file has `name`, `description`, and `repository` fields. - Your `package.json` file has a `version` field with a value of "0.0.0". -- Your `package.json` file has an `engines` field that contains an entry for Inkdrop. - - If your plugin is a theme, it should be: `"engines": {"inkdrop": "^5.x"}`. - - Otherwise, it should be: `"engines": {"inkdrop": ">=5.6.0 <7"}`. +- Your `package.json` file has an `engines` field that contains an entry for Inkdrop. For an Inkdrop 6 plugin or theme, it should be: `"engines": {"inkdrop": "^6.0.0"}`. - Your plugin has a `README.md` file at the root. - Change the repository url in the `package.json` file to match the URL of your repository. - Your plugin is in a Git repository that has been pushed to [GitHub](https://github.com/). Follow [this guide](http://guides.github.com/overviews/desktop) if your plugin isn't already on GitHub. @@ -504,40 +556,33 @@ Before you publish a plugin it is a good idea to check ahead of time if a plugin You can do that by visiting https://my.inkdrop.app/plugins/wordcount to see if the plugin already exists. If it does, update your plugin's name to something that is available before proceeding. -Now let's review what the `ipm publish` command does: +The recommended flow is to bump the version with [`npm version`](https://docs.npmjs.com/cli/commands/npm-version), then publish with `ipm publish`: -1. Registers the plugin name on my.inkdrop.app if it is being published for the first time. -2. Updates the `version` field in the `package.json` file and commits it. -3. Creates a new [Git tag](http://git-scm.com/book/en/Git-Basics-Tagging) for the version being published. -4. Pushes the tag and current branch up to GitHub. -5. Updates my.inkdrop.app with the new version being published. +```bash +cd path/to/wordcount -Now run the following commands to publish your package: +# Bump package.json, commit, and create a matching git tag +npm version minor -```bash -cd ~/github/my-package -ipm publish minor +# Build (via the prepublishOnly script) and publish to the registry +ipm publish ``` -Your package is now published and available on my.inkdrop.app. Head on over to https://my.inkdrop.app/plugins/wordcount to see your plugin's page. - -With `ipm publish`, you can bump the version and publish by using +`npm version ` updates the `version` field in `package.json`, commits the change, and creates a `v` Git tag. `ipm publish` then runs your `prepublishOnly` script (which builds the plugin into `lib/`), registers the plugin on my.inkdrop.app the first time you publish, and publishes the new version. Push the commit and tag to GitHub so the release is available to everyone: ```bash -ipm publish +git push --follow-tags ``` -where `` can be `major`, `minor` and `patch`. - -The `major` option to the publish command tells ipm to increment the first number of the version before publishing so the published version will be `1.0.0` and the Git tag created will be `v1.0.0`. - -The `minor` option to the publish command tells ipm to increment the second number of the version before publishing so the published version will be `0.1.0` and the Git tag created will be `v0.1.0`. +Your package is now published and available on my.inkdrop.app. Head on over to https://my.inkdrop.app/plugins/wordcount to see your plugin's page. -The `patch` option to the publish command tells ipm to increment the third number of the version before publishing so the published version will be `0.0.1` and the Git tag created will be `v0.0.1`. +`` can be `major`, `minor`, or `patch`: -Use `major` when you make a change that breaks backwards compatibility, like changing defaults or removing features. Use `minor` when adding new functionality or making improvements on existing code. Use `patch` when you fix a bug that was causing incorrect behaviour. Check out [semantic versioning](http://semver.org/) to learn more about best practices for versioning your plugin releases. +- `major` increments the first number (`1.0.0`) — use it for changes that break backwards compatibility, like changing defaults or removing features. +- `minor` increments the second number (`0.1.0`) — use it when adding new functionality or making improvements on existing code. +- `patch` increments the third number (`0.0.1`) — use it when you fix a bug that was causing incorrect behaviour. -You can also run `ipm help publish` to see all the available options and `ipm help` to see all the other available commands. +Check out [semantic versioning](https://semver.org/) to learn more about best practices for versioning your plugin releases. You can pass `--dry-run` to `ipm publish` to simulate the process without publishing, run `ipm help publish` to see all the available options, and `ipm help` to see all the other available commands. ## Summary diff --git a/src/app/modules/component-manager/page.mdx b/src/app/modules/component-manager/page.mdx index de96096..3e07fcd 100644 --- a/src/app/modules/component-manager/page.mdx +++ b/src/app/modules/component-manager/page.mdx @@ -16,18 +16,22 @@ You can register your React component classes to the component registry. Then, the registered components can be added to layouts. Below example registers `MyDialog` class to the component registry and adds it to `modal` layout so that you can show it as a modal view. -```js -module.exports = { - activate() { - inkdrop.components.registerClass(MyDialog) - inkdrop.layouts.addComponentToLayout('modal', 'MyDialog') - }, - - deactivate() { - inkdrop.layouts.removeComponentFromLayout('modal', 'MyDialog') - inkdrop.components.deleteClass(MyDialog) - }, +```ts +import type { Environment, IInkdropPlugin } from '@inkdropapp/types' + +class YourPlugin implements IInkdropPlugin { + activate(env: Environment) { + env.components.registerClass(MyDialog) + env.layouts.addComponentToLayout('modal', 'MyDialog') + } + + deactivate(env: Environment) { + env.layouts.removeComponentFromLayout('modal', 'MyDialog') + env.components.deleteClass(MyDialog) + } } + +export default new YourPlugin() ``` --- diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index efe13ad..83aff66 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -338,7 +338,11 @@ export const navigation: Array = [ { title: 'Components', links: [ + { title: 'Modal', href: '/components/modal' }, { title: 'Dialog', href: '/components/dialog' }, + { title: 'Button', href: '/components/button' }, + { title: 'CommandButton', href: '/components/command-button' }, + { title: 'NotebookListBar', href: '/components/notebook-list-bar' }, { title: 'MessageDialog', href: '/components/message-dialog' } ] }, diff --git a/typography.ts b/typography.ts index 656ebfc..4d86ede 100644 --- a/typography.ts +++ b/typography.ts @@ -114,16 +114,16 @@ const config = { marginTop: theme('spacing.3'), marginBottom: theme('spacing.3') }, - '> ul > li > *:first-child': { + '> ul > li > *:first-child:not(a)': { marginTop: theme('spacing.5') }, - '> ul > li > *:last-child': { + '> ul > li > *:last-child:not(a)': { marginBottom: theme('spacing.5') }, - '> ol > li > *:first-child': { + '> ol > li > *:first-child:not(a)': { marginTop: theme('spacing.5') }, - '> ol > li > *:last-child': { + '> ol > li > *:last-child:not(a)': { marginBottom: theme('spacing.5') }, 'ul ul, ul ol, ol ul, ol ol': {