@aicayzer/inkkit 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE CHANGED
@@ -2,5 +2,28 @@ InkKit includes editor behavior and source from PadPad and Memos.
2
2
  Copyright (c) 2026 August Cayzer. Licensed under the MIT License.
3
3
  Sources: https://github.com/aicayzer/padpad and https://github.com/aicayzer/memos
4
4
 
5
- Milkdown and other runtime dependencies retain their own licenses. They are
6
- installed as dependencies rather than embedded in this package.
5
+ The scoped table styles in src/style.css are adapted from prosemirror-tables.
6
+ Source: https://github.com/ProseMirror/prosemirror-tables
7
+
8
+ Copyright (C) 2015-2016 by Marijn Haverbeke <marijnh@gmail.com> and others
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in
18
+ all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ THE SOFTWARE.
27
+
28
+ Milkdown and other runtime dependencies retain their own licences. They are
29
+ installed as dependencies.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # InkKit
2
2
 
3
- InkKit is a TypeScript Markdown editor for offline web views and the web. It provides readable clipboard output, editable tables, optional host-managed images, and preservation of original Markdown and unsupported syntax.
3
+ InkKit is a reusable Markdown editor for simple writing tools and richer document apps. It provides formatted editing, source preservation, portable clipboard content and optional host-managed images, while your app controls its appearance, files and interface.
4
+
5
+ Write in formatted or source mode with shared undo/redo. Edit tables, task lists, footnotes, reference links, highlights and callouts. Preview bounded Mermaid diagrams offline. Copy readable text and semantic HTML, explicitly copy Markdown source, or capture a complete printable document. Unsupported syntax remains editable literal Markdown.
6
+
7
+ **Version status:** 0.0.7 is published. This branch documents the **unpublished 0.0.8 candidate**, adding read-only and text-input policies, command availability, scoped appearance, labels and shortcuts. Installing from npm currently gets the published release; the new controls require the candidate.
4
8
 
5
9
  ```sh
6
10
  npm install @aicayzer/inkkit
@@ -10,37 +14,35 @@ npm install @aicayzer/inkkit
10
14
  import { InkKitEditor } from '@aicayzer/inkkit'
11
15
  import '@aicayzer/inkkit/style.css'
12
16
 
13
- const editor = await InkKitEditor.mount(document.getElementById('editor')!, {
17
+ const root = document.getElementById('editor')!
18
+ const editor = await InkKitEditor.mount(root, {
14
19
  changed(text, generation) {
15
- /* Notify the host. */
20
+ // Notify the host; capture a fresh snapshot when saving.
16
21
  },
17
22
  stateChanged(state) {
18
- /* Update formatting controls. */
23
+ // Update the host's formatting controls.
19
24
  },
20
25
  openLink(href) {
21
- /* Open through the host. */
26
+ // Open through the host.
22
27
  },
23
28
  copy(text) {
24
- /* Write code-block text to the clipboard. */
29
+ // Write code-block text to the clipboard.
25
30
  },
26
31
  })
32
+
27
33
  editor.loadDocument({
28
34
  documentId: 'note',
29
35
  generation: 1,
30
36
  format: 'md',
31
- text: '# Hello\n',
37
+ text: '# Hello\n\nStart writing.\n',
32
38
  })
33
39
  const snapshot = editor.snapshot(1)
34
40
  ```
35
41
 
36
- Markdown includes CommonMark, task lists, strikethrough, autolinks, GFM tables, editable footnotes and reference-style links, bounded callouts, highlights, author comments and offline Mermaid diagrams. Unsupported constructs remain literal Markdown. TXT mode treats all text literally. Images require a host adapter. RTF files remain outside the package scope.
37
-
38
- Table controls include complete row/column movement and explicit stable text or numeric sorting. Ordinary spreadsheet paste preserves cell boundaries, replaces selected cells and grows supported tables. Hosts call these operations through the public facade; each is undoable.
39
-
40
- `await editor.printableSnapshot(1)` returns a complete printable HTML document, print styles and portable image bytes. It excludes author comments, includes folded content and reports unavailable assets or a changed document. Client apps own Print/PDF commands, dialogs and file generation.
42
+ InkKit runs on the web and in offline web views. Hosts use its public TypeScript facade and own toolbars, menus, persistence, clipboard bridges, Print/PDF actions and image storage. Images require an adapter; private references stay opaque. TXT stays literal. RTF files and richer media are outside current support.
41
43
 
42
- See [supported syntax](docs/supported-syntax.md), [the host API](docs/api.md), [preservation and clipboard behavior](docs/preservation.md), [development](docs/development.md), and [releases](docs/releasing.md).
44
+ Start with the [feature guide](docs/supported-syntax.md) and [simple and rich integration examples](docs/integration.md). The [documentation index](docs/README.md) links to precise API, preservation, playground, verification and publication guidance.
43
45
 
44
- Delivery scope and release status are tracked in the [InkKit Project](https://github.com/users/aicayzer/projects/3) and [milestones](https://github.com/aicayzer/inkkit/milestones).
46
+ Scope and release status live in the [InkKit Project](https://github.com/users/aicayzer/projects/3) and [version milestones](https://github.com/aicayzer/inkkit/milestones). Planned search, viewport and media extensions are tracked there; they are not capabilities of this candidate.
45
47
 
46
48
  MIT licensed. InkKit builds on the public PadPad and Memos editors; see [NOTICE](NOTICE).
@@ -1,3 +1,4 @@
1
+ import type { EditorView } from '@milkdown/kit/prose/view';
1
2
  import type { Parent, RootContent } from 'mdast';
2
3
  interface Callout extends Parent {
3
4
  type: 'inkkitCallout';
@@ -17,6 +18,7 @@ declare module 'mdast' {
17
18
  inkkitCallout: Callout;
18
19
  }
19
20
  }
21
+ export declare function revealCalloutAncestors(view: EditorView, position: number): void;
20
22
  export declare const remarkCalloutsPlugin: import("@milkdown/kit/utils").$Remark<"inkkitCallouts", unknown>;
21
23
  export declare const calloutSchema: import("@milkdown/kit/utils").$Node;
22
24
  export declare const calloutView: import("@milkdown/kit/utils").$Prose;
package/dist/callouts.js CHANGED
@@ -1,8 +1,20 @@
1
1
  import { $node, $prose, $remark } from '@milkdown/kit/utils';
2
+ import { editorLabels } from './labels.js';
2
3
  import { remarkCtx } from '@milkdown/kit/core';
3
4
  import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
4
5
  import { defaultHandlers } from 'mdast-util-to-markdown';
5
6
  const kinds = new Set(['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION']);
7
+ export function revealCalloutAncestors(view, position) {
8
+ const node = view.domAtPos(position).node;
9
+ let element = node instanceof HTMLElement ? node : node.parentElement;
10
+ while (element && element !== view.dom) {
11
+ if (element.getAttribute('data-inkkit-folded') === 'true')
12
+ element
13
+ .querySelector(':scope > .inkkit-callout-header > [data-inkkit-callout-toggle]')
14
+ ?.click();
15
+ element = element.parentElement;
16
+ }
17
+ }
6
18
  function plainHeader(processor, source) {
7
19
  const node = processor.parse(source).children[0];
8
20
  return node?.type === 'paragraph' &&
@@ -227,7 +239,7 @@ export const calloutSchema = $node('inkkit_callout', (ctx) => ({
227
239
  },
228
240
  },
229
241
  }));
230
- export const calloutView = $prose(() => new Plugin({
242
+ export const calloutView = $prose((ctx) => new Plugin({
231
243
  key: new PluginKey('inkkitCalloutView'),
232
244
  props: {
233
245
  nodeViews: {
@@ -255,8 +267,10 @@ export const calloutView = $prose(() => new Plugin({
255
267
  dom.setAttribute('data-inkkit-folded', String(folded));
256
268
  button.hidden = !node.attrs.fold;
257
269
  button.setAttribute('aria-expanded', String(!folded));
258
- button.setAttribute('aria-label', `${folded ? 'Expand' : 'Collapse'} ${title.textContent}`);
259
- button.textContent = folded ? 'Expand' : 'Collapse';
270
+ button.setAttribute('aria-label', `${folded ? editorLabels(ctx).expandCallout : editorLabels(ctx).collapseCallout} ${title.textContent}`);
271
+ button.textContent = folded
272
+ ? editorLabels(ctx).expandCallout
273
+ : editorLabels(ctx).collapseCallout;
260
274
  };
261
275
  const toggle = () => {
262
276
  folded = !folded;
@@ -1,15 +1,16 @@
1
1
  import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
2
2
  import { Decoration, DecorationSet } from '@milkdown/kit/prose/view';
3
3
  import { $prose } from '@milkdown/kit/utils';
4
+ import { editorLabels } from './labels.js';
4
5
  const copyIcon = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2"/></svg>';
5
6
  const doneIcon = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 8.5l3 3 6-6.5"/></svg>';
6
7
  // The button reads the block's text when clicked, so the same widget can stay
7
8
  // mounted while the block is edited.
8
- function copyButton(onCopy) {
9
+ function copyButton(onCopy, label) {
9
10
  const button = document.createElement('button');
10
11
  button.type = 'button';
11
12
  button.className = 'copy';
12
- button.setAttribute('aria-label', 'Copy');
13
+ button.setAttribute('aria-label', label);
13
14
  button.innerHTML = copyIcon;
14
15
  button.addEventListener('mousedown', (event) => event.preventDefault());
15
16
  button.addEventListener('click', () => {
@@ -25,7 +26,7 @@ function copyButton(onCopy) {
25
26
  return button;
26
27
  }
27
28
  export function codeCopyPlugin(onCopy) {
28
- return $prose(() => new Plugin({
29
+ return $prose((ctx) => new Plugin({
29
30
  key: new PluginKey('codeCopy'),
30
31
  props: {
31
32
  decorations(state) {
@@ -33,7 +34,7 @@ export function codeCopyPlugin(onCopy) {
33
34
  state.doc.descendants((node, pos) => {
34
35
  if (node.type.name !== 'code_block')
35
36
  return true;
36
- widgets.push(Decoration.widget(pos + 1, () => copyButton(onCopy), {
37
+ widgets.push(Decoration.widget(pos + 1, () => copyButton(onCopy, editorLabels(ctx).copyCode), {
37
38
  side: -1,
38
39
  key: 'copy',
39
40
  ignoreSelection: true,
@@ -45,7 +46,7 @@ export function codeCopyPlugin(onCopy) {
45
46
  },
46
47
  }));
47
48
  }
48
- export const placeholderPlugin = $prose(() => new Plugin({
49
+ export const placeholderPlugin = $prose((ctx) => new Plugin({
49
50
  key: new PluginKey('placeholder'),
50
51
  props: {
51
52
  decorations(state) {
@@ -58,7 +59,7 @@ export const placeholderPlugin = $prose(() => new Plugin({
58
59
  return DecorationSet.create(state.doc, [
59
60
  Decoration.node(0, first.nodeSize, {
60
61
  class: 'empty',
61
- 'data-placeholder': 'Start typing…',
62
+ 'data-placeholder': editorLabels(ctx).placeholder,
62
63
  }),
63
64
  ]);
64
65
  },
package/dist/dialect.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { editorViewCtx, serializerCtx } from '@milkdown/kit/core';
2
- import { commonmark, hardbreakSchema, imageSchema, paragraphSchema, remarkInlineLinkPlugin, } from '@milkdown/kit/preset/commonmark';
2
+ import { commonmark, docSchema, hardbreakSchema, imageSchema, paragraphSchema, remarkInlineLinkPlugin, } from '@milkdown/kit/preset/commonmark';
3
3
  import { extendListItemSchemaForTask, strikethroughAttr, strikethroughInputRule, strikethroughKeymap, strikethroughSchema, toggleStrikethroughCommand, wrapInTaskListInputRule, tableSchema, tableHeaderRowSchema, tableHeaderSchema, tableRowSchema, tableCellSchema, tableEditingPlugin, tableKeymap, goToNextTableCellCommand, goToPrevTableCellCommand, insertTableCommand, addRowBeforeCommand, addRowAfterCommand, addColBeforeCommand, addColAfterCommand, selectRowCommand, selectColCommand, deleteSelectedCellsCommand, setAlignCommand, exitTable, } from '@milkdown/kit/preset/gfm';
4
- import { $remark } from '@milkdown/kit/utils';
4
+ import { $node, $remark } from '@milkdown/kit/utils';
5
5
  import { autolinkInputRule } from './autolink.js';
6
+ import { sourceAttribute } from './source.js';
6
7
  import { normalizeTableAlignment, normalizeTableColumnAlignment, } from './table-alignment.js';
7
8
  import { preservingCodeBlocks, remarkCodeFences, fencedCodeHandler, } from './code-fences.js';
8
9
  import { literalBlock, createLiteralPreservation } from './literals.js';
@@ -274,6 +275,13 @@ export function createDialect(images) {
274
275
  // Protect unsupported inline HTML before the empty-line plugin consumes break nodes.
275
276
  createLiteralPreservation(images),
276
277
  commonmarkWithLiteralReferences,
278
+ $node('doc', () => ({
279
+ ...docSchema.schema,
280
+ attrs: {
281
+ ...docSchema.schema.attrs,
282
+ [sourceAttribute]: { default: null },
283
+ },
284
+ })),
277
285
  references,
278
286
  callouts,
279
287
  comments,
package/dist/editor.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { type EditorLabels } from './labels.js';
1
2
  import { type TableCommand, type TableOptions } from './tables.js';
2
3
  import { type DocumentInput, type DocumentSnapshot, type ImageAdapter, type ClipboardInput, type ClipboardOutput, type PrintableDocument } from './types.js';
4
+ import { type Heading } from './outline.js';
3
5
  export type Mark = 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'code' | 'link';
4
6
  export type Block = {
5
7
  type: 'paragraph';
@@ -15,6 +17,7 @@ export type Block = {
15
17
  } | {
16
18
  type: 'taskList';
17
19
  };
20
+ export type EditingMode = 'source' | 'formatted';
18
21
  export interface CaretState {
19
22
  marks: Mark[];
20
23
  block: Block;
@@ -22,11 +25,55 @@ export interface CaretState {
22
25
  quoted: boolean;
23
26
  }
24
27
  export type FormatCommand = 'heading' | 'paragraph' | 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'code' | 'codeBlock' | 'quote' | 'bulletList' | 'orderedList' | 'taskList' | 'link';
25
- /** The bindings the app sets, by shortcut name; each runs a format command. */
28
+ /** Host bindings by documented formatting, history or table shortcut name. */
26
29
  export type Keymap = Record<string, string[]>;
30
+ export interface TextInputPreferences {
31
+ spellcheck?: boolean;
32
+ autocorrect?: boolean;
33
+ autocapitalize?: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters';
34
+ }
35
+ export interface EditorOptions {
36
+ images?: ImageAdapter;
37
+ editable?: boolean;
38
+ textInput?: TextInputPreferences;
39
+ labels?: Partial<EditorLabels>;
40
+ keymap?: Keymap;
41
+ }
42
+ export interface CommandState {
43
+ documentId: string;
44
+ generation: number;
45
+ revision: number;
46
+ format: 'md' | 'txt';
47
+ mode: EditingMode;
48
+ editable: boolean;
49
+ composing: boolean;
50
+ pending: boolean;
51
+ caret: CaretState;
52
+ table?: {
53
+ row: number;
54
+ column: number;
55
+ rows: number;
56
+ columns: number;
57
+ };
58
+ commands: {
59
+ undo: boolean;
60
+ redo: boolean;
61
+ insertText: boolean;
62
+ paste: boolean;
63
+ replace: boolean;
64
+ replaceSource: boolean;
65
+ insertImages: boolean;
66
+ insertPaths: boolean;
67
+ insertFootnote: boolean;
68
+ editReferenceDefinition: boolean;
69
+ format: Record<FormatCommand, boolean>;
70
+ table: Record<TableCommand, boolean>;
71
+ };
72
+ }
27
73
  export interface EditorEvents {
28
74
  changed(markdown: string, generation: number): void;
29
75
  stateChanged(state: CaretState): void;
76
+ commandStateChanged?(state: CommandState): void;
30
77
  openLink(href: string): void;
31
78
  copy(text: string): void;
32
79
  error?(error: Error): void;
@@ -37,7 +84,6 @@ export declare class InkKitEditor {
37
84
  private readonly options;
38
85
  private editor;
39
86
  private lastMarkdown;
40
- private baseline;
41
87
  private documentId;
42
88
  private formatType;
43
89
  private revision;
@@ -45,25 +91,57 @@ export declare class InkKitEditor {
45
91
  private operationSequence;
46
92
  private plainComposing;
47
93
  private plainSearch;
94
+ private literalMatch?;
95
+ private beforePlainInput?;
96
+ private mode;
97
+ private sourcePreservations;
98
+ private outlineEpoch;
48
99
  private ready;
49
100
  private destroyed;
50
101
  private preservation?;
51
102
  private pasteController;
52
103
  private plain;
53
104
  private root;
105
+ private ownsRootClass;
54
106
  private clickHandler?;
55
107
  private footnoteKeyHandler?;
56
108
  private copyHandler?;
57
109
  private originalSource;
58
110
  private generation;
111
+ private reportingSuppressed;
59
112
  private loading;
113
+ private writable;
114
+ private inputPreferences;
115
+ private policyEpoch;
116
+ private literalKeys;
117
+ private configuredKeys;
118
+ private readOnlyKeys;
60
119
  private changePlugin;
61
120
  private keys;
62
121
  private constructor();
63
122
  private context;
64
123
  private assertAlive;
65
124
  private assertCurrent;
66
- private plainSource;
125
+ private assertMutation;
126
+ private get composing();
127
+ get editable(): boolean;
128
+ setEditable(editable: boolean): void;
129
+ setTextInputPreferences(preferences: TextInputPreferences): void;
130
+ private applyInputPolicy;
131
+ private publishCommandState;
132
+ commandState(expectedGeneration?: number): CommandState;
133
+ private canQuote;
134
+ private policyPlugin;
135
+ private get literalSurface();
136
+ get editingMode(): EditingMode;
137
+ private currentText;
138
+ setEditingMode(mode: EditingMode, expectedGeneration?: number): boolean;
139
+ private updateSurface;
140
+ replaceSource(text: string, expectedGeneration?: number): boolean;
141
+ private applySource;
142
+ undo(expectedGeneration?: number): boolean;
143
+ redo(expectedGeneration?: number): boolean;
144
+ private replaceLiteralSelection;
67
145
  snapshot(expectedGeneration?: number): DocumentSnapshot;
68
146
  printableSnapshot(expectedGeneration?: number): Promise<PrintableDocument>;
69
147
  clipboardSnapshot(all?: boolean): Promise<ClipboardOutput>;
@@ -73,13 +151,18 @@ export declare class InkKitEditor {
73
151
  table(command: TableCommand, options?: TableOptions): boolean;
74
152
  destroy(): Promise<void>;
75
153
  private keymapPlugin;
76
- static mount(root: HTMLElement, events: EditorEvents, options?: {
77
- images?: ImageAdapter;
78
- }): Promise<InkKitEditor>;
154
+ private copySourceSelection;
155
+ static mount(root: HTMLElement, events: EditorEvents, options?: EditorOptions): Promise<InkKitEditor>;
79
156
  loadDocument(input: DocumentInput): void;
80
157
  reloadDocument(input: DocumentInput): void;
81
158
  private load;
82
- find(text: string): void;
159
+ find(text: string, expectedGeneration?: number): void;
160
+ replace(search: string, replacement: string, expectedGeneration?: number): boolean;
161
+ replaceAll(search: string, replacement: string, expectedGeneration?: number): number;
162
+ private replaceMatches;
163
+ private outlineContext;
164
+ headings(expectedGeneration?: number): readonly Heading[];
165
+ navigateHeading(heading: Heading): boolean;
83
166
  /** Buffered native typing follows the same input rules as direct typing. */
84
167
  insertText(text: string, generation: number): boolean;
85
168
  /** Let existing keymaps handle buffered editing commands before native fallback. */