@kedataindo/docflow-plugins 0.0.32 → 0.0.33
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 +126 -25
- package/dist/index.d.cts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +101 -2
- package/package.json +3 -2
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
|
|
2336
|
-
var
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2694
|
-
var CommentMark =
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
|
3164
|
-
var
|
|
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 =
|
|
3318
|
+
var SlashMenuExtension = import_core12.Extension.create({
|
|
3220
3319
|
name: "slashMenu",
|
|
3221
3320
|
addProseMirrorPlugins() {
|
|
3222
3321
|
return [
|
|
3223
|
-
new
|
|
3224
|
-
key: new
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
4
|
+
"version": "0.0.33",
|
|
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
|
-
"
|
|
33
|
+
"tiptap-markdown": "0.8.10",
|
|
34
|
+
"@kedataindo/docflow-core": "0.0.31"
|
|
34
35
|
},
|
|
35
36
|
"peerDependencies": {
|
|
36
37
|
"@tiptap/core": "^2.11.0",
|