@kedataindo/docflow-plugins 0.0.32 → 0.0.34

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/dist/index.cjs CHANGED
@@ -70,8 +70,10 @@ __export(index_exports, {
70
70
  headingsPlugin: () => headingsPlugin,
71
71
  highlightPlugin: () => highlightPlugin,
72
72
  imagePlugin: () => imagePlugin,
73
+ insertMarkdownBlock: () => insertMarkdownBlock,
73
74
  linkPlugin: () => linkPlugin,
74
75
  listsPlugin: () => listsPlugin,
76
+ markdownToFragment: () => markdownToFragment,
75
77
  nextCitationId: () => nextCitationId,
76
78
  onSlashStateChange: () => onSlashStateChange,
77
79
  pageBreakPlugin: () => pageBreakPlugin,
@@ -2332,11 +2334,95 @@ var citationPlugin = (0, import_docflow_core17.definePlugin)({
2332
2334
  });
2333
2335
 
2334
2336
  // src/ai.ts
2335
- var import_core8 = require("@tiptap/core");
2336
- var import_state = require("@tiptap/pm/state");
2337
+ var import_core9 = require("@tiptap/core");
2338
+ var import_state2 = require("@tiptap/pm/state");
2337
2339
  var import_view = require("@tiptap/pm/view");
2340
+ var import_tiptap_markdown = require("tiptap-markdown");
2338
2341
  var import_docflow_core18 = require("@kedataindo/docflow-core");
2339
- var aiPluginKey = new import_state.PluginKey("docflow-ai");
2342
+
2343
+ // src/markdownInsert.ts
2344
+ var import_core8 = require("@tiptap/core");
2345
+ var import_model = require("@tiptap/pm/model");
2346
+ var import_state = require("@tiptap/pm/state");
2347
+ var import_transform = require("@tiptap/pm/transform");
2348
+ function mdParser(editor) {
2349
+ const storage = editor.storage.markdown;
2350
+ if (!storage || typeof storage !== "object") return void 0;
2351
+ const parser = storage.parser;
2352
+ if (!parser || typeof parser !== "object" || !("parse" in parser)) return void 0;
2353
+ return parser;
2354
+ }
2355
+ function markdownToFragment(editor, markdown, opts) {
2356
+ const parser = mdParser(editor);
2357
+ if (!parser) return null;
2358
+ let html;
2359
+ try {
2360
+ html = parser.parse(markdown, { inline: opts.inline });
2361
+ } catch {
2362
+ return null;
2363
+ }
2364
+ if (typeof html !== "string") return null;
2365
+ const content = (0, import_core8.createNodeFromContent)(html, editor.schema, {
2366
+ slice: true,
2367
+ parseOptions: { preserveWhitespace: "full" }
2368
+ });
2369
+ if (content instanceof import_model.Fragment) return content;
2370
+ return import_model.Fragment.from(content);
2371
+ }
2372
+ function selectionToInsertionEnd(tr, startLen, bias) {
2373
+ const last = tr.steps.length - 1;
2374
+ if (last < startLen) return;
2375
+ const step = tr.steps[last];
2376
+ if (!(step instanceof import_transform.ReplaceStep || step instanceof import_transform.ReplaceAroundStep)) return;
2377
+ const map = tr.mapping.maps[last];
2378
+ let end = 0;
2379
+ map.forEach((_from, _to, _newFrom, newTo) => {
2380
+ if (end === 0) end = newTo;
2381
+ });
2382
+ tr.setSelection(import_state.Selection.near(tr.doc.resolve(end), bias));
2383
+ }
2384
+ function insertMarkdownBlock(editor, view, from, to, markdown) {
2385
+ const fragment = markdownToFragment(editor, markdown, { inline: false });
2386
+ if (!fragment) {
2387
+ view.dispatch(view.state.tr.insertText(markdown, from, to));
2388
+ return;
2389
+ }
2390
+ const tr = view.state.tr;
2391
+ let f = from;
2392
+ let t = to;
2393
+ if (f === t) {
2394
+ let onlyBlock = true;
2395
+ fragment.forEach((n) => {
2396
+ if (!n.isBlock) onlyBlock = false;
2397
+ });
2398
+ if (onlyBlock) {
2399
+ const $pos = tr.doc.resolve(f);
2400
+ const parent = $pos.parent;
2401
+ if (parent.isTextblock && !parent.type.spec.code && !parent.childCount) {
2402
+ f -= 1;
2403
+ t += 1;
2404
+ }
2405
+ }
2406
+ }
2407
+ let onlyText = true;
2408
+ fragment.forEach((n) => {
2409
+ if (!n.isText || n.marks.length > 0) onlyText = false;
2410
+ });
2411
+ if (onlyText) {
2412
+ let text = "";
2413
+ fragment.forEach((n) => {
2414
+ if (n.isText) text += n.text ?? "";
2415
+ });
2416
+ tr.insertText(text, f, t);
2417
+ } else {
2418
+ tr.replaceWith(f, t, fragment);
2419
+ }
2420
+ selectionToInsertionEnd(tr, 0, -1);
2421
+ view.dispatch(tr);
2422
+ }
2423
+
2424
+ // src/ai.ts
2425
+ var aiPluginKey = new import_state2.PluginKey("docflow-ai");
2340
2426
  var CONTEXT_CHARS = 1500;
2341
2427
  function getAIPreview(editor) {
2342
2428
  return editor.storage.ai ? editor.storage.ai.preview ?? null : null;
@@ -2377,7 +2463,7 @@ function stopStream(editor) {
2377
2463
  storage.abort?.abort();
2378
2464
  storage.abort = null;
2379
2465
  }
2380
- var AIExtension = import_core8.Extension.create({
2466
+ var AIExtension = import_core9.Extension.create({
2381
2467
  name: "ai",
2382
2468
  addStorage() {
2383
2469
  return {
@@ -2473,7 +2559,12 @@ var AIExtension = import_core8.Extension.create({
2473
2559
  if (!dispatch) return true;
2474
2560
  const tr = state.tr;
2475
2561
  if (preview.text.trim()) {
2476
- tr.replaceWith(preview.from, preview.to, state.schema.text(preview.text));
2562
+ const fragment = markdownToFragment(editor, preview.text, { inline: true });
2563
+ if (fragment) {
2564
+ tr.replaceWith(preview.from, preview.to, fragment);
2565
+ } else {
2566
+ tr.replaceWith(preview.from, preview.to, state.schema.text(preview.text));
2567
+ }
2477
2568
  }
2478
2569
  tr.setMeta(aiPluginKey, { type: "clear" });
2479
2570
  dispatch(tr);
@@ -2567,7 +2658,7 @@ var AIExtension = import_core8.Extension.create({
2567
2658
  };
2568
2659
  }
2569
2660
  return [
2570
- new import_state.Plugin({
2661
+ new import_state2.Plugin({
2571
2662
  key: aiPluginKey,
2572
2663
  state: {
2573
2664
  init: () => null,
@@ -2685,13 +2776,21 @@ var AIExtension = import_core8.Extension.create({
2685
2776
  });
2686
2777
  var aiPlugin = (0, import_docflow_core18.definePlugin)({
2687
2778
  id: "ai",
2688
- tiptapExtensions: [AIExtension],
2779
+ // The `tiptap-markdown` `Markdown` extension is registered alongside AI so
2780
+ // that `editor.storage.markdown.parser` is available on every editor that
2781
+ // can stream AI content. It adds a schema-aware markdown↔HTML bridge
2782
+ // (markdown-it under the hood) used by the AI Insert/accept paths to turn
2783
+ // streamed `| col | col |` tables / `# headings` / `**bold**` into real
2784
+ // nodes instead of literal pipe/asterisk text. Its `insertContentAt` /
2785
+ // `setContent` command overrides are intentionally NOT used by the AI
2786
+ // paths (they force `inline:true`); see `markdownInsert.ts`.
2787
+ tiptapExtensions: [AIExtension, import_tiptap_markdown.Markdown.configure({ html: true, linkify: true, breaks: false })],
2689
2788
  slashCommands: [{ name: "AI", description: "Generate text with AI", command: "aiGenerate" }]
2690
2789
  });
2691
2790
 
2692
2791
  // src/comment.ts
2693
- var import_core9 = require("@tiptap/core");
2694
- var CommentMark = import_core9.Mark.create({
2792
+ var import_core10 = require("@tiptap/core");
2793
+ var CommentMark = import_core10.Mark.create({
2695
2794
  name: "comment",
2696
2795
  // Comments are inclusive (typing inside an existing comment extends
2697
2796
  // the mark range — that's the typical inline-comment UX). Excluding
@@ -2730,7 +2829,7 @@ var commentPlugin = {
2730
2829
  };
2731
2830
 
2732
2831
  // src/smartElements.ts
2733
- var import_core10 = require("@tiptap/core");
2832
+ var import_core11 = require("@tiptap/core");
2734
2833
  var import_docflow_core19 = require("@kedataindo/docflow-core");
2735
2834
  function todayISO() {
2736
2835
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -2748,7 +2847,7 @@ var DEFAULT_STATUSES = ["To Do", "In Progress", "Review", "Approved"];
2748
2847
  function slugify(s) {
2749
2848
  return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2750
2849
  }
2751
- var DateChipNode = import_core10.Node.create({
2850
+ var DateChipNode = import_core11.Node.create({
2752
2851
  name: "dateChip",
2753
2852
  group: "inline",
2754
2853
  inline: true,
@@ -2768,7 +2867,7 @@ var DateChipNode = import_core10.Node.create({
2768
2867
  return [{ tag: 'span[data-node-type="date-chip"]' }];
2769
2868
  },
2770
2869
  renderHTML({ node, HTMLAttributes }) {
2771
- return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2870
+ return ["span", (0, import_core11.mergeAttributes)(HTMLAttributes, {
2772
2871
  "data-node-type": "date-chip",
2773
2872
  class: "docs-chip docs-chip--date"
2774
2873
  }), formatDate(node.attrs.date)];
@@ -2821,7 +2920,7 @@ var DateChipNode = import_core10.Node.create({
2821
2920
  };
2822
2921
  }
2823
2922
  });
2824
- var PeopleChipNode = import_core10.Node.create({
2923
+ var PeopleChipNode = import_core11.Node.create({
2825
2924
  name: "peopleChip",
2826
2925
  group: "inline",
2827
2926
  inline: true,
@@ -2846,7 +2945,7 @@ var PeopleChipNode = import_core10.Node.create({
2846
2945
  return [{ tag: 'span[data-node-type="people-chip"]' }];
2847
2946
  },
2848
2947
  renderHTML({ node, HTMLAttributes }) {
2849
- return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
2948
+ return ["span", (0, import_core11.mergeAttributes)(HTMLAttributes, {
2850
2949
  "data-node-type": "people-chip",
2851
2950
  class: "docs-chip docs-chip--people"
2852
2951
  }), `@${node.attrs.name || "user"}`];
@@ -2883,7 +2982,7 @@ var PeopleChipNode = import_core10.Node.create({
2883
2982
  };
2884
2983
  }
2885
2984
  });
2886
- var FileChipNode = import_core10.Node.create({
2985
+ var FileChipNode = import_core11.Node.create({
2887
2986
  name: "fileChip",
2888
2987
  group: "inline",
2889
2988
  inline: true,
@@ -2908,7 +3007,7 @@ var FileChipNode = import_core10.Node.create({
2908
3007
  return [{ tag: 'span[data-node-type="file-chip"]' }];
2909
3008
  },
2910
3009
  renderHTML({ node, HTMLAttributes }) {
2911
- return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
3010
+ return ["span", (0, import_core11.mergeAttributes)(HTMLAttributes, {
2912
3011
  "data-node-type": "file-chip",
2913
3012
  class: "docs-chip docs-chip--file"
2914
3013
  }), node.attrs.name || "file"];
@@ -2945,7 +3044,7 @@ var FileChipNode = import_core10.Node.create({
2945
3044
  };
2946
3045
  }
2947
3046
  });
2948
- var DropdownChipNode = import_core10.Node.create({
3047
+ var DropdownChipNode = import_core11.Node.create({
2949
3048
  name: "dropdownChip",
2950
3049
  group: "inline",
2951
3050
  inline: true,
@@ -2976,7 +3075,7 @@ var DropdownChipNode = import_core10.Node.create({
2976
3075
  return [{ tag: 'span[data-node-type="dropdown-chip"]' }];
2977
3076
  },
2978
3077
  renderHTML({ node, HTMLAttributes }) {
2979
- return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
3078
+ return ["span", (0, import_core11.mergeAttributes)(HTMLAttributes, {
2980
3079
  "data-node-type": "dropdown-chip",
2981
3080
  class: "docs-chip docs-chip--dropdown"
2982
3081
  }), node.attrs.selected || ""];
@@ -3028,7 +3127,7 @@ var DropdownChipNode = import_core10.Node.create({
3028
3127
  };
3029
3128
  }
3030
3129
  });
3031
- var LocationChipNode = import_core10.Node.create({
3130
+ var LocationChipNode = import_core11.Node.create({
3032
3131
  name: "locationChip",
3033
3132
  group: "inline",
3034
3133
  inline: true,
@@ -3064,7 +3163,7 @@ var LocationChipNode = import_core10.Node.create({
3064
3163
  return [{ tag: 'span[data-node-type="location-chip"]' }];
3065
3164
  },
3066
3165
  renderHTML({ node, HTMLAttributes }) {
3067
- return ["span", (0, import_core10.mergeAttributes)(HTMLAttributes, {
3166
+ return ["span", (0, import_core11.mergeAttributes)(HTMLAttributes, {
3068
3167
  "data-node-type": "location-chip",
3069
3168
  class: "docs-chip docs-chip--location"
3070
3169
  }), node.attrs.label || ""];
@@ -3160,8 +3259,8 @@ var smartElementsPlugin = (0, import_docflow_core19.definePlugin)({
3160
3259
  });
3161
3260
 
3162
3261
  // src/slashMenu.ts
3163
- var import_core11 = require("@tiptap/core");
3164
- var import_state2 = require("@tiptap/pm/state");
3262
+ var import_core12 = require("@tiptap/core");
3263
+ var import_state3 = require("@tiptap/pm/state");
3165
3264
  var import_docflow_core20 = require("@kedataindo/docflow-core");
3166
3265
  var slashState = {
3167
3266
  open: false,
@@ -3216,12 +3315,12 @@ function getRegisteredCommands() {
3216
3315
  return true;
3217
3316
  });
3218
3317
  }
3219
- var SlashMenuExtension = import_core11.Extension.create({
3318
+ var SlashMenuExtension = import_core12.Extension.create({
3220
3319
  name: "slashMenu",
3221
3320
  addProseMirrorPlugins() {
3222
3321
  return [
3223
- new import_state2.Plugin({
3224
- key: new import_state2.PluginKey("slashMenu"),
3322
+ new import_state3.Plugin({
3323
+ key: new import_state3.PluginKey("slashMenu"),
3225
3324
  props: {
3226
3325
  handleTextInput(view, from, _to, text) {
3227
3326
  if (text === "/") {
@@ -3383,8 +3482,10 @@ var defaultPlugins = [
3383
3482
  headingsPlugin,
3384
3483
  highlightPlugin,
3385
3484
  imagePlugin,
3485
+ insertMarkdownBlock,
3386
3486
  linkPlugin,
3387
3487
  listsPlugin,
3488
+ markdownToFragment,
3388
3489
  nextCitationId,
3389
3490
  onSlashStateChange,
3390
3491
  pageBreakPlugin,
package/dist/index.d.cts CHANGED
@@ -5,6 +5,8 @@ import { Extension, Node, Editor, Mark } from '@tiptap/core';
5
5
  import { CiteEngine } from './citations.cjs';
6
6
  export { CSL_LOCALE_EN_US, CSL_STYLES, CitationAttrs, CitationCluster, CitationMode, CiteEngineOptions, CslStyleInfo, DEFAULT_CSL_STYLE, buildCitationNodes, nextCitationId, sanitizeCiteprocHtml } from './citations.cjs';
7
7
  import { PluginKey } from '@tiptap/pm/state';
8
+ import { Fragment } from '@tiptap/pm/model';
9
+ import { EditorView } from '@tiptap/pm/view';
8
10
 
9
11
  declare function getCitationEngine(editor: Editor): CiteEngine | null;
10
12
  /**
@@ -174,6 +176,46 @@ declare module '@tiptap/core' {
174
176
  declare const AIExtension: Extension<any, any>;
175
177
  declare const aiPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
176
178
 
179
+ /**
180
+ * Parse a markdown string into a ProseMirror `Fragment` using the editor's
181
+ * live schema.
182
+ *
183
+ * - `inline: false` keeps BLOCK structure (tables, lists, headings) — for the
184
+ * chat sidebar Insert, where a streamed answer may contain a table.
185
+ * - `inline: true` unwraps the leading paragraph so the result is inline
186
+ * content — for the inline `/ai` transform, which replaces an in-paragraph
187
+ * selection range.
188
+ *
189
+ * Returns `null` when the `tiptap-markdown` `Markdown` extension isn't
190
+ * registered; callers MUST fall back to `schema.text(md)` to keep the
191
+ * pre-markdown behavior.
192
+ */
193
+ declare function markdownToFragment(editor: Editor, markdown: string, opts: {
194
+ inline: boolean;
195
+ }): Fragment | null;
196
+ /**
197
+ * Insert BLOCK markdown at the range `[from, to)` in ONE dispatch, dispatching
198
+ * directly on `view` (NOT via `editor.commands`) so the Markdown extension's
199
+ * forced-inline `insertContentAt` override is bypassed and a table survives.
200
+ *
201
+ * Mirrors TipTap's native `insertContentAt` for HTML strings:
202
+ * 1. parse markdown → HTML → schema Fragment (block-preserving),
203
+ * 2. if the range is collapsed inside an EMPTY paragraph AND the fragment is
204
+ * all block nodes, expand the range by one on each side so the empty
205
+ * paragraph is REPLACED by the block content (no nested-in-paragraph
206
+ * block, no leftover empty line),
207
+ * 3. plain-text fast path keeps current marks (rare for markdown, but
208
+ * matches `insertText` semantics for pure-prose turns),
209
+ * 4. `tr.replaceWith` (ProseMirror's `replaceStep` splits the parent as
210
+ * needed so a block fits at an inline position),
211
+ * 5. move the selection to the end of the inserted content.
212
+ *
213
+ * Falls back to `tr.insertText(md, from, to)` when the Markdown extension
214
+ * isn't registered — preserving the pre-markdown plain-text Insert so the
215
+ * feature degrades gracefully.
216
+ */
217
+ declare function insertMarkdownBlock(editor: Editor, view: EditorView, from: number, to: number, markdown: string): void;
218
+
177
219
  /**
178
220
  * Phase 9 P9-4 — comment plugin.
179
221
  *
@@ -264,4 +306,4 @@ declare const slashMenuPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
264
306
 
265
307
  declare const defaultPlugins: _kedata_indonesia_docflow_core.DocsEditorPlugin[];
266
308
 
267
- export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
309
+ export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, insertMarkdownBlock, linkPlugin, listsPlugin, markdownToFragment, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { Extension, Node, Editor, Mark } from '@tiptap/core';
5
5
  import { CiteEngine } from './citations.js';
6
6
  export { CSL_LOCALE_EN_US, CSL_STYLES, CitationAttrs, CitationCluster, CitationMode, CiteEngineOptions, CslStyleInfo, DEFAULT_CSL_STYLE, buildCitationNodes, nextCitationId, sanitizeCiteprocHtml } from './citations.js';
7
7
  import { PluginKey } from '@tiptap/pm/state';
8
+ import { Fragment } from '@tiptap/pm/model';
9
+ import { EditorView } from '@tiptap/pm/view';
8
10
 
9
11
  declare function getCitationEngine(editor: Editor): CiteEngine | null;
10
12
  /**
@@ -174,6 +176,46 @@ declare module '@tiptap/core' {
174
176
  declare const AIExtension: Extension<any, any>;
175
177
  declare const aiPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
176
178
 
179
+ /**
180
+ * Parse a markdown string into a ProseMirror `Fragment` using the editor's
181
+ * live schema.
182
+ *
183
+ * - `inline: false` keeps BLOCK structure (tables, lists, headings) — for the
184
+ * chat sidebar Insert, where a streamed answer may contain a table.
185
+ * - `inline: true` unwraps the leading paragraph so the result is inline
186
+ * content — for the inline `/ai` transform, which replaces an in-paragraph
187
+ * selection range.
188
+ *
189
+ * Returns `null` when the `tiptap-markdown` `Markdown` extension isn't
190
+ * registered; callers MUST fall back to `schema.text(md)` to keep the
191
+ * pre-markdown behavior.
192
+ */
193
+ declare function markdownToFragment(editor: Editor, markdown: string, opts: {
194
+ inline: boolean;
195
+ }): Fragment | null;
196
+ /**
197
+ * Insert BLOCK markdown at the range `[from, to)` in ONE dispatch, dispatching
198
+ * directly on `view` (NOT via `editor.commands`) so the Markdown extension's
199
+ * forced-inline `insertContentAt` override is bypassed and a table survives.
200
+ *
201
+ * Mirrors TipTap's native `insertContentAt` for HTML strings:
202
+ * 1. parse markdown → HTML → schema Fragment (block-preserving),
203
+ * 2. if the range is collapsed inside an EMPTY paragraph AND the fragment is
204
+ * all block nodes, expand the range by one on each side so the empty
205
+ * paragraph is REPLACED by the block content (no nested-in-paragraph
206
+ * block, no leftover empty line),
207
+ * 3. plain-text fast path keeps current marks (rare for markdown, but
208
+ * matches `insertText` semantics for pure-prose turns),
209
+ * 4. `tr.replaceWith` (ProseMirror's `replaceStep` splits the parent as
210
+ * needed so a block fits at an inline position),
211
+ * 5. move the selection to the end of the inserted content.
212
+ *
213
+ * Falls back to `tr.insertText(md, from, to)` when the Markdown extension
214
+ * isn't registered — preserving the pre-markdown plain-text Insert so the
215
+ * feature degrades gracefully.
216
+ */
217
+ declare function insertMarkdownBlock(editor: Editor, view: EditorView, from: number, to: number, markdown: string): void;
218
+
177
219
  /**
178
220
  * Phase 9 P9-4 — comment plugin.
179
221
  *
@@ -264,4 +306,4 @@ declare const slashMenuPlugin: _kedata_indonesia_docflow_core.DocsEditorPlugin;
264
306
 
265
307
  declare const defaultPlugins: _kedata_indonesia_docflow_core.DocsEditorPlugin[];
266
308
 
267
- export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, linkPlugin, listsPlugin, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
309
+ export { AIExtension, type AIPreviewState, BibliographyNode, CitationEngineExtension, CitationNode, CiteEngine, type CommentMarkAttrs, CommentMark as CommentMarkExtension, DateChipNode, DropdownChipNode, FileChipNode, FootnoteNode, LocationChipNode, PageBreak, PeopleChipNode, type PlaceholderPluginOptions, SlashMenuExtension, TocEntryNode, TocNode, TocPageNumNode, aiPlugin, aiPluginKey, alignmentPlugin, blockquotePlugin, citationPlugin, codeBlockPlugin, collectHeadings, commentPlugin, createPlaceholderPlugin, defaultPlugins, fontSizePlugin, footnotePlugin, formattingPlugin, getAIPreview, getCitationEngine, headingsPlugin, highlightPlugin, imagePlugin, insertMarkdownBlock, linkPlugin, listsPlugin, markdownToFragment, onSlashStateChange, pageBreakPlugin, placeholderPlugin, regenerateToc, registerSlashCommands, slashMenuPlugin, slashState, smartElementsPlugin, tablePlugin, textColorPlugin, tocPlugin };
package/dist/index.js CHANGED
@@ -853,7 +853,91 @@ var highlightPlugin = definePlugin16({
853
853
  import { Extension as Extension2 } from "@tiptap/core";
854
854
  import { Plugin, PluginKey } from "@tiptap/pm/state";
855
855
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
856
+ import { Markdown } from "tiptap-markdown";
856
857
  import { definePlugin as definePlugin17 } from "@kedataindo/docflow-core";
858
+
859
+ // src/markdownInsert.ts
860
+ import { createNodeFromContent } from "@tiptap/core";
861
+ import { Fragment } from "@tiptap/pm/model";
862
+ import { Selection } from "@tiptap/pm/state";
863
+ import { ReplaceAroundStep, ReplaceStep } from "@tiptap/pm/transform";
864
+ function mdParser(editor) {
865
+ const storage = editor.storage.markdown;
866
+ if (!storage || typeof storage !== "object") return void 0;
867
+ const parser = storage.parser;
868
+ if (!parser || typeof parser !== "object" || !("parse" in parser)) return void 0;
869
+ return parser;
870
+ }
871
+ function markdownToFragment(editor, markdown, opts) {
872
+ const parser = mdParser(editor);
873
+ if (!parser) return null;
874
+ let html;
875
+ try {
876
+ html = parser.parse(markdown, { inline: opts.inline });
877
+ } catch {
878
+ return null;
879
+ }
880
+ if (typeof html !== "string") return null;
881
+ const content = createNodeFromContent(html, editor.schema, {
882
+ slice: true,
883
+ parseOptions: { preserveWhitespace: "full" }
884
+ });
885
+ if (content instanceof Fragment) return content;
886
+ return Fragment.from(content);
887
+ }
888
+ function selectionToInsertionEnd(tr, startLen, bias) {
889
+ const last = tr.steps.length - 1;
890
+ if (last < startLen) return;
891
+ const step = tr.steps[last];
892
+ if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) return;
893
+ const map = tr.mapping.maps[last];
894
+ let end = 0;
895
+ map.forEach((_from, _to, _newFrom, newTo) => {
896
+ if (end === 0) end = newTo;
897
+ });
898
+ tr.setSelection(Selection.near(tr.doc.resolve(end), bias));
899
+ }
900
+ function insertMarkdownBlock(editor, view, from, to, markdown) {
901
+ const fragment = markdownToFragment(editor, markdown, { inline: false });
902
+ if (!fragment) {
903
+ view.dispatch(view.state.tr.insertText(markdown, from, to));
904
+ return;
905
+ }
906
+ const tr = view.state.tr;
907
+ let f = from;
908
+ let t = to;
909
+ if (f === t) {
910
+ let onlyBlock = true;
911
+ fragment.forEach((n) => {
912
+ if (!n.isBlock) onlyBlock = false;
913
+ });
914
+ if (onlyBlock) {
915
+ const $pos = tr.doc.resolve(f);
916
+ const parent = $pos.parent;
917
+ if (parent.isTextblock && !parent.type.spec.code && !parent.childCount) {
918
+ f -= 1;
919
+ t += 1;
920
+ }
921
+ }
922
+ }
923
+ let onlyText = true;
924
+ fragment.forEach((n) => {
925
+ if (!n.isText || n.marks.length > 0) onlyText = false;
926
+ });
927
+ if (onlyText) {
928
+ let text = "";
929
+ fragment.forEach((n) => {
930
+ if (n.isText) text += n.text ?? "";
931
+ });
932
+ tr.insertText(text, f, t);
933
+ } else {
934
+ tr.replaceWith(f, t, fragment);
935
+ }
936
+ selectionToInsertionEnd(tr, 0, -1);
937
+ view.dispatch(tr);
938
+ }
939
+
940
+ // src/ai.ts
857
941
  var aiPluginKey = new PluginKey("docflow-ai");
858
942
  var CONTEXT_CHARS = 1500;
859
943
  function getAIPreview(editor) {
@@ -991,7 +1075,12 @@ var AIExtension = Extension2.create({
991
1075
  if (!dispatch) return true;
992
1076
  const tr = state.tr;
993
1077
  if (preview.text.trim()) {
994
- tr.replaceWith(preview.from, preview.to, state.schema.text(preview.text));
1078
+ const fragment = markdownToFragment(editor, preview.text, { inline: true });
1079
+ if (fragment) {
1080
+ tr.replaceWith(preview.from, preview.to, fragment);
1081
+ } else {
1082
+ tr.replaceWith(preview.from, preview.to, state.schema.text(preview.text));
1083
+ }
995
1084
  }
996
1085
  tr.setMeta(aiPluginKey, { type: "clear" });
997
1086
  dispatch(tr);
@@ -1203,7 +1292,15 @@ var AIExtension = Extension2.create({
1203
1292
  });
1204
1293
  var aiPlugin = definePlugin17({
1205
1294
  id: "ai",
1206
- tiptapExtensions: [AIExtension],
1295
+ // The `tiptap-markdown` `Markdown` extension is registered alongside AI so
1296
+ // that `editor.storage.markdown.parser` is available on every editor that
1297
+ // can stream AI content. It adds a schema-aware markdown↔HTML bridge
1298
+ // (markdown-it under the hood) used by the AI Insert/accept paths to turn
1299
+ // streamed `| col | col |` tables / `# headings` / `**bold**` into real
1300
+ // nodes instead of literal pipe/asterisk text. Its `insertContentAt` /
1301
+ // `setContent` command overrides are intentionally NOT used by the AI
1302
+ // paths (they force `inline:true`); see `markdownInsert.ts`.
1303
+ tiptapExtensions: [AIExtension, Markdown.configure({ html: true, linkify: true, breaks: false })],
1207
1304
  slashCommands: [{ name: "AI", description: "Generate text with AI", command: "aiGenerate" }]
1208
1305
  });
1209
1306
 
@@ -1900,8 +1997,10 @@ export {
1900
1997
  headingsPlugin,
1901
1998
  highlightPlugin,
1902
1999
  imagePlugin,
2000
+ insertMarkdownBlock,
1903
2001
  linkPlugin,
1904
2002
  listsPlugin,
2003
+ markdownToFragment,
1905
2004
  nextCitationId,
1906
2005
  onSlashStateChange,
1907
2006
  pageBreakPlugin,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-plugins",
3
3
  "license": "UNLICENSED",
4
- "version": "0.0.32",
4
+ "version": "0.0.34",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -30,7 +30,8 @@
30
30
  "@tiptap/extension-highlight": "^2.27.2",
31
31
  "@tiptap/pm": "^2.11.0",
32
32
  "citeproc": "^2.4.63",
33
- "@kedataindo/docflow-core": "0.0.30"
33
+ "tiptap-markdown": "0.8.10",
34
+ "@kedataindo/docflow-core": "0.0.32"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "@tiptap/core": "^2.11.0",