prosemirror-completion 0.0.1

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/commands.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { EditorState, Transaction } from 'prosemirror-state';
2
+ import { CompletionResult } from './types';
3
+ /**
4
+ * 插入补全内容 - 支持 HTML 直接渲染和 ProseMirror Node
5
+ */
6
+ export declare function insertCompletion(state: EditorState, result: CompletionResult): Transaction;
7
+ /**
8
+ * 取消补全
9
+ */
10
+ export declare function cancelCompletion(state: EditorState): Transaction;
11
+ /**
12
+ * 检查是否有活动的补全
13
+ */
14
+ export declare function hasActiveCompletion(state: EditorState): boolean;
15
+ /**
16
+ * 获取当前补全建议
17
+ */
18
+ export declare function getActiveSuggestion(state: EditorState): CompletionResult | null;
19
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,KAAK,EACV,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAqIjB;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,gBAAgB,GACvB,WAAW,CA+Cb;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,CAIhE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAK/D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,GACjB,gBAAgB,GAAG,IAAI,CAGzB"}
@@ -0,0 +1,33 @@
1
+ import { Decoration, DecorationSet } from 'prosemirror-view';
2
+ import { EditorState } from 'prosemirror-state';
3
+ import { Node } from 'prosemirror-model';
4
+ import { CompletionOptions, CompletionResult, CompletionPluginState } from './types';
5
+ /**
6
+ * 创建 ghost text 的 decoration
7
+ */
8
+ export declare function createGhostDecoration(pos: number, result: CompletionResult, options: CompletionOptions): Decoration;
9
+ /**
10
+ * 解析补全结果为纯文本
11
+ */
12
+ export declare function parseCompletionResult(result: CompletionResult): string;
13
+ /**
14
+ * 将补全结果转换为 ProseMirror 可以插入的片段
15
+ */
16
+ export declare function createCompletionFragment(result: CompletionResult, state: EditorState): {
17
+ text: string;
18
+ html?: string;
19
+ node?: Node;
20
+ };
21
+ /**
22
+ * 创建空的 DecorationSet
23
+ */
24
+ export declare function emptyDecorations(state: EditorState | null): DecorationSet;
25
+ /**
26
+ * 更新 DecorationSet,添加 ghost decoration
27
+ */
28
+ export declare function updateGhostDecoration(state: EditorState | null, pluginState: CompletionPluginState, pos: number, result: CompletionResult, options: CompletionOptions): DecorationSet;
29
+ /**
30
+ * 创建空的 DecorationSet
31
+ */
32
+ export declare function clearDecorations(state: EditorState | null): DecorationSet;
33
+ //# sourceMappingURL=decorations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorations.d.ts","sourceRoot":"","sources":["../src/decorations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,iBAAiB,GACzB,UAAU,CAcZ;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAkBtE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,WAAW,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,CAoB9C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,aAAa,CAKzE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,WAAW,EAAE,qBAAqB,EAClC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,iBAAiB,GACzB,aAAa,CAOf;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,aAAa,CAEzE"}
package/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("prosemirror-state"),f=require("prosemirror-view"),z="prosemirror-completion",u=new b.PluginKey("prosemirror-completion");function M(e,o,c){const n=document.createElement("span");n.className=c.ghostClassName??"prosemirror-ghost-text";const t=p(o);return n.textContent=t,f.Decoration.widget(e,n,{side:1,stopEvent:()=>!0,key:"ghost-completion"})}function p(e){if(typeof e=="string")return e;if("plain"in e&&e.plain!==void 0)return e.plain;if("html"in e&&e.html!==void 0){const o=document.createElement("div");return o.innerHTML=e.html,o.textContent??""}return"prosemirror"in e&&e.prosemirror?e.prosemirror.textContent??"":""}function E(e,o){return typeof e=="string"?{text:e}:"prosemirror"in e?{text:e.prosemirror.textContent??"",node:e.prosemirror}:"plain"in e&&"html"in e?{text:e.plain,html:e.html}:"html"in e?{text:p(e),html:e.html}:"plain"in e?{text:e.plain}:{text:""}}function g(e){return e?f.DecorationSet.create(e.doc,[]):f.DecorationSet.empty}function v(e,o,c,n,t){if(!t.showGhost||!n||!e)return g(e);const r=M(c,n,t);return f.DecorationSet.create(e.doc,[r])}function x(e){return g(e)}function q(e){if(typeof e!="string"&&"html"in e&&e.html!==void 0)return e.html}function F(e){if(typeof e!="string"&&"prosemirror"in e&&e.prosemirror)return e.prosemirror}function O(e,o){const c=new DOMParser().parseFromString(e,"text/html");try{const n=[],t=c.body;for(let r=0;r<t.childNodes.length;r++){const l=t.childNodes[r];if(l.nodeType===3){const i=l.textContent??"";i&&n.push(o.schema.text(i))}else if(l.nodeType===1){const s=L(l,o);s&&n.push(s)}}return n.length===0?null:o.schema.nodes.doc.create(null,n)}catch{return null}}function L(e,o){var n,t,r,l;switch(e.tagName.toLowerCase()){case"p":return(n=o.schema.nodes.paragraph)==null?void 0:n.create(null,$(e,o));case"strong":case"b":return o.schema.text(e.textContent??"",[(t=o.schema.marks.strong)==null?void 0:t.create()]);case"em":case"i":return o.schema.text(e.textContent??"",[(r=o.schema.marks.em)==null?void 0:r.create()]);case"code":return o.schema.text(e.textContent??"",[(l=o.schema.marks.code)==null?void 0:l.create()]);case"br":return o.schema.text(`
2
+ `);default:return o.schema.text(e.textContent??"")}}function $(e,o){const c=[];for(let n=0;n<e.childNodes.length;n++){const t=e.childNodes[n];if(t.nodeType===3){const r=t.textContent??"";r&&c.push(o.schema.text(r))}else if(t.nodeType===1){const r=L(t,o);r&&c.push(r)}}return c}function S(e,o){const c=u.getState(e);if(!c||c.triggerPos===null)return e.tr;const n=F(o),t=q(o),r=p(o),l=e.selection.from;let i,s=0;if(n)i=e.tr.insert(l,n.content),s=i.doc.content.size-e.doc.content.size;else if(t){const m=O(t,e);m&&m.childCount>0?(i=e.tr.insert(l,m.content),s=i.doc.content.size-e.doc.content.size):(i=e.tr.insertText(r,l),s=r.length)}else i=e.tr.insertText(r,l),s=r.length;const a=l+s;return i.setSelection(b.TextSelection.create(i.doc,a)),i.setMeta("prosemirror-completion",{type:"apply"}),i}function H(e){const o=e.tr;return o.setMeta("prosemirror-completion",{type:"cancel"}),o}function R(e){const o=u.getState(e);return!!(o!=null&&o.activeSuggestion&&(o==null?void 0:o.triggerPos)!==null)}function B(e){const o=u.getState(e);return(o==null?void 0:o.activeSuggestion)??null}function k(e,o,c){const{activeSuggestion:n,triggerPos:t}=c;if(!n||t===null)return!1;if(o.key==="Tab"&&!o.shiftKey){o.preventDefault();const r=S(e.state,n);return e.dispatch(r),!0}if(o.key==="Escape"){o.preventDefault();const r={type:"cancel"};return e.dispatch(e.state.tr.setMeta("prosemirror-completion",r)),!0}return!1}function I(e,o){const{activeSuggestion:c,triggerPos:n}=o;if(!c||n===null)return!1;const{from:t,to:r}=e.state.selection;return t<n}function P(e,o){const c=e.doc.resolve(o),n=c.parent;let t="",r=c.parentOffset;for(let l=0;l<n.childCount;l++){const i=n.child(l);if(i.isText){const s=i.text??"";if(r<=s.length){t+=s.slice(0,r);break}else t+=s,r-=s.length}else if(t+=" ",r-=1,r<=0)break}return t}function N(e,o){const c=e.doc.resolve(o),n=c.parent;let t="",r=c.parentOffset,l=!1;for(let i=0;i<n.childCount;i++){const s=n.child(i);if(s.isText){const a=s.text??"";l?t+=a:r<a.length?(t+=a.slice(r),l=!0):r-=a.length}else l||r<=0?(t+=" ",l=!0):r-=1}return t}function K(e){const{beforeText:o}=e;return o.includes("```")||/^\s*(function|const|let|var|class|import|export|if|for|while)/.test(o.slice(-50))?"code":/^\s*(#{1,6}\s|>|\*|\d+\.|\[|!\[)/.test(o.slice(-20))||o.includes("**")||o.includes("__")?"markdown":"common"}function w(e,o){const{from:c}=e.selection,n=P(e,c),t=o.minTriggerLength??3;return(n.split(/[\s\n]+/).pop()??"").length>=t}function W(e,o){let c=null;return(...n)=>{c&&clearTimeout(c),c=setTimeout(()=>{e(...n),c=null},o)}}function _(e){const o=e.debounceMs??300,c=e.minTriggerLength??3;return new b.Plugin({key:u,state:{init(){return{activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:g(null),debounceTimer:null,options:e}},apply(n,t){const r=n.getMeta("prosemirror-completion");if(r)switch(r.type){case"suggest":return{...t,activeSuggestion:r.result,triggerPos:r.pos,isLoading:!1,abortController:null,decorations:v({doc:n.doc},t,r.pos,r.result,e),debounceTimer:null};case"apply":return{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:x({doc:n.doc}),debounceTimer:null};case"cancel":return t.abortController&&t.abortController.abort(),t.debounceTimer&&clearTimeout(t.debounceTimer),{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:x({doc:n.doc}),debounceTimer:null};case"loading":return{...t,isLoading:r.isLoading}}return!t.activeSuggestion||t.triggerPos===null?{...t,decorations:t.decorations.map(n.mapping,n.doc)}:{...t,decorations:t.decorations.map(n.mapping,n.doc)}}},props:{decorations(n){var t;return((t=this.getState(n))==null?void 0:t.decorations)??null},handleKeyDown(n,t){const r=u.getState(n.state);return r?k(n,t,r):!1}},view(n){const t=n,r=(()=>{let i=null,s=null;return a=>{i&&clearTimeout(i),s&&s.abort(),i=setTimeout(async()=>{const d=new AbortController;s=d,t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!0}));try{const C=t.state.doc.resolve(a).parent,T=P(t.state,a),y=N(t.state,a),A=e.getPromptType?e.getPromptType({abortController:d,parent:C,pos:a,beforeText:T,afterText:y,promptType:"common",state:t.state}):K({abortController:d,parent:C,pos:a,beforeText:T,afterText:y,promptType:"common",state:t.state}),D={abortController:d,parent:C,pos:a,beforeText:T,afterText:y,promptType:A,state:t.state},G=await Promise.resolve(e.callCompletion(D));if(d.signal.aborted)return;t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"suggest",result:G,pos:a})),e.onChange&&e.onChange(D,t)}catch(h){if(d.signal.aborted)return;console.error("Completion error:",h)}finally{t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!1}))}i=null,s=null},o);const m=u.getState(t.state);m&&m.debounceTimer&&clearTimeout(m.debounceTimer)}})(),l=i=>{w(t.state,{...e,minTriggerLength:c})&&r(i)};return{update:(i,s)=>{const a=u.getState(i.state);if(a){if(a.activeSuggestion){const{from:m}=i.state.selection,d=i.state.selection.eq(s.selection)===!1;(i.state.doc!==s.doc||d)&&i.dispatch(i.state.tr.setMeta("prosemirror-completion",{type:"cancel"}))}i.state.doc!==s.doc&&l(i.state.selection.from)}},destroy:()=>{const i=u.getState(t.state);i!=null&&i.abortController&&i.abortController.abort(),i!=null&&i.debounceTimer&&clearTimeout(i.debounceTimer)}}}})}exports.cancelCompletion=H;exports.clearDecorations=x;exports.completionMetaKey=z;exports.completionPluginKey=u;exports.createCompletionFragment=E;exports.createCompletionPlugin=_;exports.createGhostDecoration=M;exports.debounce=W;exports.defaultGetPromptType=K;exports.emptyDecorations=g;exports.getActiveSuggestion=B;exports.getTextAfterCursor=N;exports.getTextBeforeCursor=P;exports.handleKeyDown=k;exports.hasActiveCompletion=R;exports.insertCompletion=S;exports.parseCompletionResult=p;exports.shouldCancelCompletion=I;exports.shouldTriggerCompletion=w;exports.updateGhostDecoration=v;
3
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/types.ts","../src/decorations.ts","../src/commands.ts","../src/keymap.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import type { Node } from \"prosemirror-model\";\nimport type { EditorState, Transaction } from \"prosemirror-state\";\nimport type { EditorView, DecorationSet } from \"prosemirror-view\";\nimport { PluginKey } from \"prosemirror-state\";\n\n/**\n * 补全返回结果格式\n */\nexport type CompletionResult =\n | string\n | { plain: string; html?: string }\n | { html: string }\n | { prosemirror: Node };\n\n/**\n * Prompt 类型建议\n */\nexport type PromptType = \"common\" | \"code\" | \"markdown\" | string;\n\n/**\n * 补全上下文\n */\nexport interface CompletionContext {\n /** 用于取消请求的 AbortController */\n abortController: AbortController;\n /** 当前位置的父节点 */\n parent: Node;\n /** 当前位置 */\n pos: number;\n /** 光标前的文本 */\n beforeText: string;\n /** 光标后的文本 */\n afterText: string;\n /** 建议的 prompt 类型 */\n promptType: PromptType;\n /** 编辑器状态 */\n state: EditorState;\n}\n\n/**\n * 补全插件配置选项\n */\nexport interface CompletionOptions {\n /**\n * 防抖时间(毫秒)\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * 触发补全的最小字符数\n * @default 3\n */\n minTriggerLength?: number;\n\n /**\n * 补全函数 - 接收上下文,返回补全结果\n */\n callCompletion: (\n context: CompletionContext,\n ) => Promise<CompletionResult> | CompletionResult;\n\n /**\n * 确定当前上下文适合的 prompt 类型\n */\n getPromptType?: (context: CompletionContext) => PromptType;\n\n /**\n * 用户打字时的回调\n */\n onChange?: (context: CompletionContext, view: EditorView) => void;\n\n /**\n * 补全退出时的回调(按 Esc 或点击别处)\n */\n onExit?: (view: EditorView) => void;\n\n /**\n * 补全应用时的回调(按 Tab)\n */\n onApply?: (result: CompletionResult, view: EditorView) => void;\n\n /**\n * 自定义 ghost text 的 CSS 类名\n * @default \"prosemirror-ghost-text\"\n */\n ghostClassName?: string;\n\n /**\n * 是否显示 ghost text\n * @default true\n */\n showGhost?: boolean;\n}\n\n/**\n * 内部插件状态\n */\nexport interface CompletionPluginState {\n /** 当前激活的补全建议 */\n activeSuggestion: CompletionResult | null;\n /** 补全触发位置 */\n triggerPos: number | null;\n /** 是否正在加载 */\n isLoading: boolean;\n /** 当前 AbortController */\n abortController: AbortController | null;\n /** DecorationSet */\n decorations: DecorationSet;\n /** 防抖计时器 */\n debounceTimer: ReturnType<typeof setTimeout> | null;\n /** 插件配置 */\n options: CompletionOptions;\n}\n\n/**\n * Transaction meta key\n */\nexport const completionMetaKey = \"prosemirror-completion\";\n\n/**\n * 插件 Key\n */\nexport const completionPluginKey = new PluginKey<CompletionPluginState>(\n \"prosemirror-completion\",\n);\n\n/**\n * Action 类型\n */\nexport type CompletionAction =\n | { type: \"start\"; pos: number }\n | { type: \"suggest\"; result: CompletionResult; pos: number }\n | { type: \"apply\" }\n | { type: \"cancel\" }\n | { type: \"loading\"; isLoading: boolean };\n","import { Decoration, DecorationSet } from \"prosemirror-view\";\nimport type { EditorState } from \"prosemirror-state\";\nimport type { Node } from \"prosemirror-model\";\nimport type {\n CompletionOptions,\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\n\n/**\n * 创建 ghost text 的 decoration\n */\nexport function createGhostDecoration(\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): Decoration {\n const dom = document.createElement(\"span\");\n dom.className = options.ghostClassName ?? \"prosemirror-ghost-text\";\n\n // 解析补全结果\n const text = parseCompletionResult(result);\n dom.textContent = text;\n\n // side: 1 确保它在光标后,stopEvent 阻止用户点击灰字导致光标偏移\n return Decoration.widget(pos, dom, {\n side: 1,\n stopEvent: () => true,\n key: \"ghost-completion\",\n });\n}\n\n/**\n * 解析补全结果为纯文本\n */\nexport function parseCompletionResult(result: CompletionResult): string {\n if (typeof result === \"string\") {\n return result;\n }\n if (\"plain\" in result && result.plain !== undefined) {\n return result.plain;\n }\n if (\"html\" in result && result.html !== undefined) {\n // 简单去除 HTML 标签获取纯文本\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = result.html;\n return tmp.textContent ?? \"\";\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n // 从 ProseMirror Node 提取文本内容\n return result.prosemirror.textContent ?? \"\";\n }\n return \"\";\n}\n\n/**\n * 将补全结果转换为 ProseMirror 可以插入的片段\n */\nexport function createCompletionFragment(\n result: CompletionResult,\n state: EditorState\n): { text: string; html?: string; node?: Node } {\n if (typeof result === \"string\") {\n return { text: result };\n }\n if (\"prosemirror\" in result) {\n return { text: result.prosemirror.textContent ?? \"\", node: result.prosemirror };\n }\n if (\"plain\" in result && \"html\" in result) {\n return { text: result.plain, html: result.html };\n }\n if (\"html\" in result) {\n return {\n text: parseCompletionResult(result),\n html: result.html,\n };\n }\n if (\"plain\" in result) {\n return { text: result.plain };\n }\n return { text: \"\" };\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function emptyDecorations(state: EditorState | null): DecorationSet {\n if (!state) {\n return DecorationSet.empty;\n }\n return DecorationSet.create(state.doc, []);\n}\n\n/**\n * 更新 DecorationSet,添加 ghost decoration\n */\nexport function updateGhostDecoration(\n state: EditorState | null,\n pluginState: CompletionPluginState,\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): DecorationSet {\n if (!options.showGhost || !result || !state) {\n return emptyDecorations(state);\n }\n\n const deco = createGhostDecoration(pos, result, options);\n return DecorationSet.create(state.doc, [deco]);\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function clearDecorations(state: EditorState | null): DecorationSet {\n return emptyDecorations(state);\n}\n","import type { EditorState, Transaction } from \"prosemirror-state\";\nimport { TextSelection } from \"prosemirror-state\";\nimport { Node as PMNode } from \"prosemirror-model\";\nimport type {\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\nimport { completionPluginKey } from \"./types\";\nimport { parseCompletionResult } from \"./decorations\";\n\n/**\n * 获取补全结果的 HTML 内容\n */\nfunction getCompletionHTML(result: CompletionResult): string | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"html\" in result && result.html !== undefined) {\n return result.html;\n }\n return undefined;\n}\n\n/**\n * 获取补全结果的 ProseMirror Node\n */\nfunction getCompletionNode(result: CompletionResult): PMNode | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n return result.prosemirror;\n }\n return undefined;\n}\n\n/**\n * 将 HTML 字符串解析为 ProseMirror 文档片段\n */\nfunction parseHTMLToFragment(\n html: string,\n state: EditorState\n): PMNode | null {\n // 使用 DOMParser 将 HTML 解析为 DOM\n const dom = new DOMParser().parseFromString(html, \"text/html\");\n\n try {\n const nodes: PMNode[] = [];\n const body = dom.body;\n\n for (let i = 0; i < body.childNodes.length; i++) {\n const child = body.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const element = child as HTMLElement;\n const parsed = parseElement(element, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n if (nodes.length === 0) {\n return null;\n }\n\n // 创建文档片段\n return state.schema.nodes.doc.create(null, nodes);\n } catch {\n return null;\n }\n}\n\n/**\n * 解析单个 HTML 元素为 ProseMirror 节点\n */\nfunction parseElement(element: HTMLElement, state: EditorState): PMNode | null {\n const tagName = element.tagName.toLowerCase();\n\n // 处理常见标签\n switch (tagName) {\n case \"p\":\n return state.schema.nodes.paragraph?.create(\n null,\n parseInlineContent(element, state)\n );\n case \"strong\":\n case \"b\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.strong?.create()]\n );\n case \"em\":\n case \"i\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.em?.create()]\n );\n case \"code\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.code?.create()]\n );\n case \"br\":\n return state.schema.text(\"\\n\");\n default:\n // 对于未知标签,尝试解析其文本内容\n return state.schema.text(element.textContent ?? \"\");\n }\n}\n\n/**\n * 解析内联内容\n */\nfunction parseInlineContent(element: HTMLElement, state: EditorState): PMNode[] {\n const nodes: PMNode[] = [];\n\n for (let i = 0; i < element.childNodes.length; i++) {\n const child = element.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const parsed = parseElement(child as HTMLElement, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n return nodes;\n}\n\n/**\n * 插入补全内容 - 支持 HTML 直接渲染和 ProseMirror Node\n */\nexport function insertCompletion(\n state: EditorState,\n result: CompletionResult\n): Transaction {\n const pluginState = completionPluginKey.getState(state) as\n | CompletionPluginState\n | undefined;\n\n if (!pluginState || pluginState.triggerPos === null) {\n return state.tr;\n }\n\n const node = getCompletionNode(result);\n const html = getCompletionHTML(result);\n const text = parseCompletionResult(result);\n const pos = state.selection.from;\n\n let tr: Transaction;\n let insertedSize = 0;\n\n // 优先处理 prosemirror 类型\n if (node) {\n // 直接插入 ProseMirror Node\n tr = state.tr.insert(pos, node.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else if (html) {\n // 如果有 HTML 内容,尝试插入富文本\n const fragment = parseHTMLToFragment(html, state);\n if (fragment && fragment.childCount > 0) {\n tr = state.tr.insert(pos, fragment.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else {\n // 回退到纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n } else {\n // 纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n\n // 将光标移到插入内容之后\n const newPos = pos + insertedSize;\n tr.setSelection(TextSelection.create(tr.doc, newPos));\n\n // 添加 meta 标记已应用\n tr.setMeta(\"prosemirror-completion\", { type: \"apply\" });\n\n return tr;\n}\n\n/**\n * 取消补全\n */\nexport function cancelCompletion(state: EditorState): Transaction {\n const tr = state.tr;\n tr.setMeta(\"prosemirror-completion\", { type: \"cancel\" });\n return tr;\n}\n\n/**\n * 检查是否有活动的补全\n */\nexport function hasActiveCompletion(state: EditorState): boolean {\n const pluginState = completionPluginKey.getState(state);\n return !!(\n pluginState?.activeSuggestion && pluginState?.triggerPos !== null\n );\n}\n\n/**\n * 获取当前补全建议\n */\nexport function getActiveSuggestion(\n state: EditorState\n): CompletionResult | null {\n const pluginState = completionPluginKey.getState(state);\n return pluginState?.activeSuggestion ?? null;\n}\n","import type { EditorView } from \"prosemirror-view\";\nimport type { CompletionPluginState, CompletionAction } from \"./types\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 键盘事件处理器\n * 处理 Tab(应用)、Esc(取消)、和其他按键\n */\nexport function handleKeyDown(\n view: EditorView,\n event: KeyboardEvent,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n // 没有激活的补全时,不拦截\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n // Tab: 应用补全\n if (event.key === \"Tab\" && !event.shiftKey) {\n event.preventDefault();\n // 使用 insertCompletion 实际插入文本\n const tr = insertCompletion(view.state, activeSuggestion);\n view.dispatch(tr);\n return true;\n }\n\n // Escape: 取消补全\n if (event.key === \"Escape\") {\n event.preventDefault();\n const action: CompletionAction = { type: \"cancel\" };\n view.dispatch(view.state.tr.setMeta(\"prosemirror-completion\", action));\n return true;\n }\n\n // 其他按键:更新状态,让 state.apply 处理\n return false;\n}\n\n/**\n * 检查是否需要取消补全(光标移出触发区域等)\n */\nexport function shouldCancelCompletion(\n view: EditorView,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n const { from, to } = view.state.selection;\n\n // 如果光标不在触发位置之后,取消补全\n if (from < triggerPos) {\n return true;\n }\n\n return false;\n}\n","import type { EditorState } from \"prosemirror-state\";\nimport type {\n CompletionContext,\n CompletionOptions,\n PromptType,\n} from \"./types\";\n\n/**\n * 获取光标前的文本内容\n */\nexport function getTextBeforeCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n // 获取从节点开始到光标位置的文本\n let text = \"\";\n let offset = $pos.parentOffset;\n\n // 遍历子节点收集文本\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (offset <= childText.length) {\n text += childText.slice(0, offset);\n break;\n } else {\n text += childText;\n offset -= childText.length;\n }\n } else {\n // 非文本节点,记录为空格或换行\n text += \" \";\n offset -= 1;\n if (offset <= 0) break;\n }\n }\n\n return text;\n}\n\n/**\n * 获取光标后的文本内容\n */\nexport function getTextAfterCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n let text = \"\";\n let offset = $pos.parentOffset;\n let started = false;\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (!started) {\n if (offset < childText.length) {\n text += childText.slice(offset);\n started = true;\n } else {\n offset -= childText.length;\n }\n } else {\n text += childText;\n }\n } else {\n if (started || offset <= 0) {\n text += \" \";\n started = true;\n } else {\n offset -= 1;\n }\n }\n }\n\n return text;\n}\n\n/**\n * 默认的 prompt 类型检测\n */\nexport function defaultGetPromptType(context: CompletionContext): PromptType {\n const { beforeText } = context;\n\n // 检测代码块\n if (\n beforeText.includes(\"```\") ||\n /^\\s*(function|const|let|var|class|import|export|if|for|while)/.test(\n beforeText.slice(-50)\n )\n ) {\n return \"code\";\n }\n\n // 检测 markdown 特征\n if (\n /^\\s*(#{1,6}\\s|>|\\*|\\d+\\.|\\[|!\\[)/.test(beforeText.slice(-20)) ||\n beforeText.includes(\"**\") ||\n beforeText.includes(\"__\")\n ) {\n return \"markdown\";\n }\n\n return \"common\";\n}\n\n/**\n * 检查是否应该触发补全\n */\nexport function shouldTriggerCompletion(\n state: EditorState,\n options: CompletionOptions\n): boolean {\n const { from } = state.selection;\n const beforeText = getTextBeforeCursor(state, from);\n\n // 检查最小触发长度\n const minLength = options.minTriggerLength ?? 3;\n\n // 获取最后一个\"词\"的长度(以空格或标点分隔)\n const lastWord = beforeText.split(/[\\s\\n]+/).pop() ?? \"\";\n\n return lastWord.length >= minLength;\n}\n\n/**\n * 防抖函数\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n fn: T,\n ms: number\n): (...args: Parameters<T>) => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n fn(...args);\n timer = null;\n }, ms);\n };\n}\n","import { Plugin } from \"prosemirror-state\";\nimport type { EditorView } from \"prosemirror-view\";\nimport type {\n CompletionOptions,\n CompletionPluginState,\n CompletionAction,\n} from \"./types\";\nimport {\n completionPluginKey,\n} from \"./types\";\nimport {\n emptyDecorations,\n clearDecorations,\n updateGhostDecoration,\n} from \"./decorations\";\nimport { handleKeyDown, shouldCancelCompletion } from \"./keymap\";\nimport {\n getTextBeforeCursor,\n getTextAfterCursor,\n defaultGetPromptType,\n shouldTriggerCompletion,\n} from \"./utils\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 创建补全插件\n */\nexport function createCompletionPlugin(\n options: CompletionOptions\n): Plugin<CompletionPluginState> {\n const debounceMs = options.debounceMs ?? 300;\n const minTriggerLength = options.minTriggerLength ?? 3;\n\n return new Plugin<CompletionPluginState>({\n key: completionPluginKey,\n\n state: {\n init(): CompletionPluginState {\n return {\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: emptyDecorations(null),\n debounceTimer: null,\n options,\n };\n },\n\n apply(tr, pluginState): CompletionPluginState {\n const action = tr.getMeta(\n \"prosemirror-completion\"\n ) as CompletionAction | null;\n\n // 处理 action\n if (action) {\n switch (action.type) {\n case \"suggest\":\n return {\n ...pluginState,\n activeSuggestion: action.result,\n triggerPos: action.pos,\n isLoading: false,\n abortController: null,\n decorations: updateGhostDecoration(\n { doc: tr.doc } as import(\"prosemirror-state\").EditorState,\n pluginState,\n action.pos,\n action.result,\n options\n ),\n debounceTimer: null,\n };\n\n case \"apply\": {\n // 清理 decoration,补全内容已插入\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n }\n\n case \"cancel\":\n // 取消之前的请求\n if (pluginState.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n\n case \"loading\":\n return {\n ...pluginState,\n isLoading: action.isLoading,\n };\n }\n }\n\n // 如果没有 active suggestion,正常返回\n if (!pluginState.activeSuggestion || pluginState.triggerPos === null) {\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n }\n\n // 映射 decorations\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? null;\n },\n\n handleKeyDown(view: EditorView, event: KeyboardEvent): boolean {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return false;\n\n return handleKeyDown(view, event, pluginState);\n },\n },\n\n view(editorView) {\n const view = editorView;\n\n // 防抖处理补全请求\n const debouncedRequest = (() => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let lastAbortController: AbortController | null = null;\n\n return (pos: number) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n // 取消之前的请求\n if (lastAbortController) {\n lastAbortController.abort();\n }\n\n timer = setTimeout(async () => {\n const abortController = new AbortController();\n lastAbortController = abortController;\n\n // 更新 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: true,\n })\n );\n\n try {\n const $pos = view.state.doc.resolve(pos);\n const parent = $pos.parent;\n const beforeText = getTextBeforeCursor(view.state, pos);\n const afterText = getTextAfterCursor(view.state, pos);\n const promptType = options.getPromptType\n ? options.getPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n })\n : defaultGetPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n });\n\n const context = {\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType,\n state: view.state,\n };\n\n // 调用补全函数\n const result = await Promise.resolve(options.callCompletion(context));\n\n // 如果已经被取消,不更新\n if (abortController.signal.aborted) {\n return;\n }\n\n // 更新建议\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"suggest\",\n result,\n pos,\n })\n );\n\n // 调用 onChange 回调\n if (options.onChange) {\n options.onChange(context, view);\n }\n } catch (error) {\n if (abortController.signal.aborted) {\n return;\n }\n console.error(\"Completion error:\", error);\n } finally {\n // 清除 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: false,\n })\n );\n }\n\n timer = null;\n lastAbortController = null;\n }, debounceMs);\n\n // 保存 timer 到插件状态\n const currentState = completionPluginKey.getState(view.state);\n if (currentState && currentState.debounceTimer) {\n clearTimeout(currentState.debounceTimer);\n }\n };\n })();\n\n // 监听文档和选择变化\n const handleInput = (from: number) => {\n // 检查是否应该触发补全\n if (!shouldTriggerCompletion(view.state, { ...options, minTriggerLength })) {\n return;\n }\n // 触发新的补全请求\n debouncedRequest(from);\n };\n\n // 使用 ProseMirror 的 update 监听文档和选择变化\n return {\n update: (view, prevState) => {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return;\n\n // 检查是否有 active suggestion\n if (pluginState.activeSuggestion) {\n const { from } = view.state.selection;\n const selectionChanged = view.state.selection.eq(prevState.selection) === false;\n const docChanged = view.state.doc !== prevState.doc;\n\n // 如果有补全,检查是否需要取消\n if (docChanged || selectionChanged) {\n // 取消当前补全\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"cancel\",\n })\n );\n }\n }\n\n // 检查是否应该触发新的补全\n if (view.state.doc !== prevState.doc) {\n handleInput(view.state.selection.from);\n }\n },\n destroy: () => {\n // 清理\n const pluginState = completionPluginKey.getState(view.state);\n if (pluginState?.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState?.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n },\n };\n },\n });\n}\n"],"names":["completionMetaKey","completionPluginKey","PluginKey","createGhostDecoration","pos","result","options","dom","text","parseCompletionResult","Decoration","tmp","createCompletionFragment","state","emptyDecorations","DecorationSet","updateGhostDecoration","pluginState","deco","clearDecorations","getCompletionHTML","getCompletionNode","parseHTMLToFragment","html","nodes","body","i","child","parsed","parseElement","element","_a","parseInlineContent","_b","_c","_d","insertCompletion","node","tr","insertedSize","fragment","newPos","TextSelection","cancelCompletion","hasActiveCompletion","getActiveSuggestion","handleKeyDown","view","event","activeSuggestion","triggerPos","action","shouldCancelCompletion","from","to","getTextBeforeCursor","$pos","offset","childText","getTextAfterCursor","started","defaultGetPromptType","context","beforeText","shouldTriggerCompletion","minLength","debounce","fn","ms","timer","args","createCompletionPlugin","debounceMs","minTriggerLength","Plugin","editorView","debouncedRequest","lastAbortController","abortController","parent","afterText","promptType","error","currentState","handleInput","prevState","selectionChanged"],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './types';
2
+ export * from './plugin';
3
+ export * from './commands';
4
+ export * from './decorations';
5
+ export * from './utils';
6
+ export * from './keymap';
7
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC"}
package/index.iife.js ADDED
@@ -0,0 +1,3 @@
1
+ var ProseMirrorCompletion=function(a,C,g){"use strict";const w="prosemirror-completion",d=new C.PluginKey("prosemirror-completion");function D(e,n,c){const o=document.createElement("span");o.className=c.ghostClassName??"prosemirror-ghost-text";const t=p(n);return o.textContent=t,g.Decoration.widget(e,o,{side:1,stopEvent:()=>!0,key:"ghost-completion"})}function p(e){if(typeof e=="string")return e;if("plain"in e&&e.plain!==void 0)return e.plain;if("html"in e&&e.html!==void 0){const n=document.createElement("div");return n.innerHTML=e.html,n.textContent??""}return"prosemirror"in e&&e.prosemirror?e.prosemirror.textContent??"":""}function z(e,n){return typeof e=="string"?{text:e}:"prosemirror"in e?{text:e.prosemirror.textContent??"",node:e.prosemirror}:"plain"in e&&"html"in e?{text:e.plain,html:e.html}:"html"in e?{text:p(e),html:e.html}:"plain"in e?{text:e.plain}:{text:""}}function h(e){return e?g.DecorationSet.create(e.doc,[]):g.DecorationSet.empty}function v(e,n,c,o,t){if(!t.showGhost||!o||!e)return h(e);const r=D(c,o,t);return g.DecorationSet.create(e.doc,[r])}function T(e){return h(e)}function E(e){if(typeof e!="string"&&"html"in e&&e.html!==void 0)return e.html}function F(e){if(typeof e!="string"&&"prosemirror"in e&&e.prosemirror)return e.prosemirror}function O(e,n){const c=new DOMParser().parseFromString(e,"text/html");try{const o=[],t=c.body;for(let r=0;r<t.childNodes.length;r++){const l=t.childNodes[r];if(l.nodeType===3){const i=l.textContent??"";i&&o.push(n.schema.text(i))}else if(l.nodeType===1){const s=L(l,n);s&&o.push(s)}}return o.length===0?null:n.schema.nodes.doc.create(null,o)}catch{return null}}function L(e,n){var o,t,r,l;switch(e.tagName.toLowerCase()){case"p":return(o=n.schema.nodes.paragraph)==null?void 0:o.create(null,$(e,n));case"strong":case"b":return n.schema.text(e.textContent??"",[(t=n.schema.marks.strong)==null?void 0:t.create()]);case"em":case"i":return n.schema.text(e.textContent??"",[(r=n.schema.marks.em)==null?void 0:r.create()]);case"code":return n.schema.text(e.textContent??"",[(l=n.schema.marks.code)==null?void 0:l.create()]);case"br":return n.schema.text(`
2
+ `);default:return n.schema.text(e.textContent??"")}}function $(e,n){const c=[];for(let o=0;o<e.childNodes.length;o++){const t=e.childNodes[o];if(t.nodeType===3){const r=t.textContent??"";r&&c.push(n.schema.text(r))}else if(t.nodeType===1){const r=L(t,n);r&&c.push(r)}}return c}function k(e,n){const c=d.getState(e);if(!c||c.triggerPos===null)return e.tr;const o=F(n),t=E(n),r=p(n),l=e.selection.from;let i,s=0;if(o)i=e.tr.insert(l,o.content),s=i.doc.content.size-e.doc.content.size;else if(t){const m=O(t,e);m&&m.childCount>0?(i=e.tr.insert(l,m.content),s=i.doc.content.size-e.doc.content.size):(i=e.tr.insertText(r,l),s=r.length)}else i=e.tr.insertText(r,l),s=r.length;const u=l+s;return i.setSelection(C.TextSelection.create(i.doc,u)),i.setMeta("prosemirror-completion",{type:"apply"}),i}function H(e){const n=e.tr;return n.setMeta("prosemirror-completion",{type:"cancel"}),n}function R(e){const n=d.getState(e);return!!(n!=null&&n.activeSuggestion&&(n==null?void 0:n.triggerPos)!==null)}function q(e){const n=d.getState(e);return(n==null?void 0:n.activeSuggestion)??null}function N(e,n,c){const{activeSuggestion:o,triggerPos:t}=c;if(!o||t===null)return!1;if(n.key==="Tab"&&!n.shiftKey){n.preventDefault();const r=k(e.state,o);return e.dispatch(r),!0}if(n.key==="Escape"){n.preventDefault();const r={type:"cancel"};return e.dispatch(e.state.tr.setMeta("prosemirror-completion",r)),!0}return!1}function B(e,n){const{activeSuggestion:c,triggerPos:o}=n;if(!c||o===null)return!1;const{from:t,to:r}=e.state.selection;return t<o}function y(e,n){const c=e.doc.resolve(n),o=c.parent;let t="",r=c.parentOffset;for(let l=0;l<o.childCount;l++){const i=o.child(l);if(i.isText){const s=i.text??"";if(r<=s.length){t+=s.slice(0,r);break}else t+=s,r-=s.length}else if(t+=" ",r-=1,r<=0)break}return t}function S(e,n){const c=e.doc.resolve(n),o=c.parent;let t="",r=c.parentOffset,l=!1;for(let i=0;i<o.childCount;i++){const s=o.child(i);if(s.isText){const u=s.text??"";l?t+=u:r<u.length?(t+=u.slice(r),l=!0):r-=u.length}else l||r<=0?(t+=" ",l=!0):r-=1}return t}function K(e){const{beforeText:n}=e;return n.includes("```")||/^\s*(function|const|let|var|class|import|export|if|for|while)/.test(n.slice(-50))?"code":/^\s*(#{1,6}\s|>|\*|\d+\.|\[|!\[)/.test(n.slice(-20))||n.includes("**")||n.includes("__")?"markdown":"common"}function A(e,n){const{from:c}=e.selection,o=y(e,c),t=n.minTriggerLength??3;return(o.split(/[\s\n]+/).pop()??"").length>=t}function I(e,n){let c=null;return(...o)=>{c&&clearTimeout(c),c=setTimeout(()=>{e(...o),c=null},n)}}function W(e){const n=e.debounceMs??300,c=e.minTriggerLength??3;return new C.Plugin({key:d,state:{init(){return{activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:h(null),debounceTimer:null,options:e}},apply(o,t){const r=o.getMeta("prosemirror-completion");if(r)switch(r.type){case"suggest":return{...t,activeSuggestion:r.result,triggerPos:r.pos,isLoading:!1,abortController:null,decorations:v({doc:o.doc},t,r.pos,r.result,e),debounceTimer:null};case"apply":return{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:T({doc:o.doc}),debounceTimer:null};case"cancel":return t.abortController&&t.abortController.abort(),t.debounceTimer&&clearTimeout(t.debounceTimer),{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:T({doc:o.doc}),debounceTimer:null};case"loading":return{...t,isLoading:r.isLoading}}return!t.activeSuggestion||t.triggerPos===null?{...t,decorations:t.decorations.map(o.mapping,o.doc)}:{...t,decorations:t.decorations.map(o.mapping,o.doc)}}},props:{decorations(o){var t;return((t=this.getState(o))==null?void 0:t.decorations)??null},handleKeyDown(o,t){const r=d.getState(o.state);return r?N(o,t,r):!1}},view(o){const t=o,r=(()=>{let i=null,s=null;return u=>{i&&clearTimeout(i),s&&s.abort(),i=setTimeout(async()=>{const f=new AbortController;s=f,t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!0}));try{const x=t.state.doc.resolve(u).parent,P=y(t.state,u),M=S(t.state,u),_=e.getPromptType?e.getPromptType({abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:"common",state:t.state}):K({abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:"common",state:t.state}),G={abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:_,state:t.state},j=await Promise.resolve(e.callCompletion(G));if(f.signal.aborted)return;t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"suggest",result:j,pos:u})),e.onChange&&e.onChange(G,t)}catch(b){if(f.signal.aborted)return;console.error("Completion error:",b)}finally{t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!1}))}i=null,s=null},n);const m=d.getState(t.state);m&&m.debounceTimer&&clearTimeout(m.debounceTimer)}})(),l=i=>{A(t.state,{...e,minTriggerLength:c})&&r(i)};return{update:(i,s)=>{const u=d.getState(i.state);if(u){if(u.activeSuggestion){const{from:m}=i.state.selection,f=i.state.selection.eq(s.selection)===!1;(i.state.doc!==s.doc||f)&&i.dispatch(i.state.tr.setMeta("prosemirror-completion",{type:"cancel"}))}i.state.doc!==s.doc&&l(i.state.selection.from)}},destroy:()=>{const i=d.getState(t.state);i!=null&&i.abortController&&i.abortController.abort(),i!=null&&i.debounceTimer&&clearTimeout(i.debounceTimer)}}}})}return a.cancelCompletion=H,a.clearDecorations=T,a.completionMetaKey=w,a.completionPluginKey=d,a.createCompletionFragment=z,a.createCompletionPlugin=W,a.createGhostDecoration=D,a.debounce=I,a.defaultGetPromptType=K,a.emptyDecorations=h,a.getActiveSuggestion=q,a.getTextAfterCursor=S,a.getTextBeforeCursor=y,a.handleKeyDown=N,a.hasActiveCompletion=R,a.insertCompletion=k,a.parseCompletionResult=p,a.shouldCancelCompletion=B,a.shouldTriggerCompletion=A,a.updateGhostDecoration=v,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"}),a}({},ProseMirrorState,ProseMirrorView);
3
+ //# sourceMappingURL=index.iife.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.iife.js","sources":["../src/types.ts","../src/decorations.ts","../src/commands.ts","../src/keymap.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import type { Node } from \"prosemirror-model\";\nimport type { EditorState, Transaction } from \"prosemirror-state\";\nimport type { EditorView, DecorationSet } from \"prosemirror-view\";\nimport { PluginKey } from \"prosemirror-state\";\n\n/**\n * 补全返回结果格式\n */\nexport type CompletionResult =\n | string\n | { plain: string; html?: string }\n | { html: string }\n | { prosemirror: Node };\n\n/**\n * Prompt 类型建议\n */\nexport type PromptType = \"common\" | \"code\" | \"markdown\" | string;\n\n/**\n * 补全上下文\n */\nexport interface CompletionContext {\n /** 用于取消请求的 AbortController */\n abortController: AbortController;\n /** 当前位置的父节点 */\n parent: Node;\n /** 当前位置 */\n pos: number;\n /** 光标前的文本 */\n beforeText: string;\n /** 光标后的文本 */\n afterText: string;\n /** 建议的 prompt 类型 */\n promptType: PromptType;\n /** 编辑器状态 */\n state: EditorState;\n}\n\n/**\n * 补全插件配置选项\n */\nexport interface CompletionOptions {\n /**\n * 防抖时间(毫秒)\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * 触发补全的最小字符数\n * @default 3\n */\n minTriggerLength?: number;\n\n /**\n * 补全函数 - 接收上下文,返回补全结果\n */\n callCompletion: (\n context: CompletionContext,\n ) => Promise<CompletionResult> | CompletionResult;\n\n /**\n * 确定当前上下文适合的 prompt 类型\n */\n getPromptType?: (context: CompletionContext) => PromptType;\n\n /**\n * 用户打字时的回调\n */\n onChange?: (context: CompletionContext, view: EditorView) => void;\n\n /**\n * 补全退出时的回调(按 Esc 或点击别处)\n */\n onExit?: (view: EditorView) => void;\n\n /**\n * 补全应用时的回调(按 Tab)\n */\n onApply?: (result: CompletionResult, view: EditorView) => void;\n\n /**\n * 自定义 ghost text 的 CSS 类名\n * @default \"prosemirror-ghost-text\"\n */\n ghostClassName?: string;\n\n /**\n * 是否显示 ghost text\n * @default true\n */\n showGhost?: boolean;\n}\n\n/**\n * 内部插件状态\n */\nexport interface CompletionPluginState {\n /** 当前激活的补全建议 */\n activeSuggestion: CompletionResult | null;\n /** 补全触发位置 */\n triggerPos: number | null;\n /** 是否正在加载 */\n isLoading: boolean;\n /** 当前 AbortController */\n abortController: AbortController | null;\n /** DecorationSet */\n decorations: DecorationSet;\n /** 防抖计时器 */\n debounceTimer: ReturnType<typeof setTimeout> | null;\n /** 插件配置 */\n options: CompletionOptions;\n}\n\n/**\n * Transaction meta key\n */\nexport const completionMetaKey = \"prosemirror-completion\";\n\n/**\n * 插件 Key\n */\nexport const completionPluginKey = new PluginKey<CompletionPluginState>(\n \"prosemirror-completion\",\n);\n\n/**\n * Action 类型\n */\nexport type CompletionAction =\n | { type: \"start\"; pos: number }\n | { type: \"suggest\"; result: CompletionResult; pos: number }\n | { type: \"apply\" }\n | { type: \"cancel\" }\n | { type: \"loading\"; isLoading: boolean };\n","import { Decoration, DecorationSet } from \"prosemirror-view\";\nimport type { EditorState } from \"prosemirror-state\";\nimport type { Node } from \"prosemirror-model\";\nimport type {\n CompletionOptions,\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\n\n/**\n * 创建 ghost text 的 decoration\n */\nexport function createGhostDecoration(\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): Decoration {\n const dom = document.createElement(\"span\");\n dom.className = options.ghostClassName ?? \"prosemirror-ghost-text\";\n\n // 解析补全结果\n const text = parseCompletionResult(result);\n dom.textContent = text;\n\n // side: 1 确保它在光标后,stopEvent 阻止用户点击灰字导致光标偏移\n return Decoration.widget(pos, dom, {\n side: 1,\n stopEvent: () => true,\n key: \"ghost-completion\",\n });\n}\n\n/**\n * 解析补全结果为纯文本\n */\nexport function parseCompletionResult(result: CompletionResult): string {\n if (typeof result === \"string\") {\n return result;\n }\n if (\"plain\" in result && result.plain !== undefined) {\n return result.plain;\n }\n if (\"html\" in result && result.html !== undefined) {\n // 简单去除 HTML 标签获取纯文本\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = result.html;\n return tmp.textContent ?? \"\";\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n // 从 ProseMirror Node 提取文本内容\n return result.prosemirror.textContent ?? \"\";\n }\n return \"\";\n}\n\n/**\n * 将补全结果转换为 ProseMirror 可以插入的片段\n */\nexport function createCompletionFragment(\n result: CompletionResult,\n state: EditorState\n): { text: string; html?: string; node?: Node } {\n if (typeof result === \"string\") {\n return { text: result };\n }\n if (\"prosemirror\" in result) {\n return { text: result.prosemirror.textContent ?? \"\", node: result.prosemirror };\n }\n if (\"plain\" in result && \"html\" in result) {\n return { text: result.plain, html: result.html };\n }\n if (\"html\" in result) {\n return {\n text: parseCompletionResult(result),\n html: result.html,\n };\n }\n if (\"plain\" in result) {\n return { text: result.plain };\n }\n return { text: \"\" };\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function emptyDecorations(state: EditorState | null): DecorationSet {\n if (!state) {\n return DecorationSet.empty;\n }\n return DecorationSet.create(state.doc, []);\n}\n\n/**\n * 更新 DecorationSet,添加 ghost decoration\n */\nexport function updateGhostDecoration(\n state: EditorState | null,\n pluginState: CompletionPluginState,\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): DecorationSet {\n if (!options.showGhost || !result || !state) {\n return emptyDecorations(state);\n }\n\n const deco = createGhostDecoration(pos, result, options);\n return DecorationSet.create(state.doc, [deco]);\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function clearDecorations(state: EditorState | null): DecorationSet {\n return emptyDecorations(state);\n}\n","import type { EditorState, Transaction } from \"prosemirror-state\";\nimport { TextSelection } from \"prosemirror-state\";\nimport { Node as PMNode } from \"prosemirror-model\";\nimport type {\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\nimport { completionPluginKey } from \"./types\";\nimport { parseCompletionResult } from \"./decorations\";\n\n/**\n * 获取补全结果的 HTML 内容\n */\nfunction getCompletionHTML(result: CompletionResult): string | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"html\" in result && result.html !== undefined) {\n return result.html;\n }\n return undefined;\n}\n\n/**\n * 获取补全结果的 ProseMirror Node\n */\nfunction getCompletionNode(result: CompletionResult): PMNode | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n return result.prosemirror;\n }\n return undefined;\n}\n\n/**\n * 将 HTML 字符串解析为 ProseMirror 文档片段\n */\nfunction parseHTMLToFragment(\n html: string,\n state: EditorState\n): PMNode | null {\n // 使用 DOMParser 将 HTML 解析为 DOM\n const dom = new DOMParser().parseFromString(html, \"text/html\");\n\n try {\n const nodes: PMNode[] = [];\n const body = dom.body;\n\n for (let i = 0; i < body.childNodes.length; i++) {\n const child = body.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const element = child as HTMLElement;\n const parsed = parseElement(element, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n if (nodes.length === 0) {\n return null;\n }\n\n // 创建文档片段\n return state.schema.nodes.doc.create(null, nodes);\n } catch {\n return null;\n }\n}\n\n/**\n * 解析单个 HTML 元素为 ProseMirror 节点\n */\nfunction parseElement(element: HTMLElement, state: EditorState): PMNode | null {\n const tagName = element.tagName.toLowerCase();\n\n // 处理常见标签\n switch (tagName) {\n case \"p\":\n return state.schema.nodes.paragraph?.create(\n null,\n parseInlineContent(element, state)\n );\n case \"strong\":\n case \"b\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.strong?.create()]\n );\n case \"em\":\n case \"i\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.em?.create()]\n );\n case \"code\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.code?.create()]\n );\n case \"br\":\n return state.schema.text(\"\\n\");\n default:\n // 对于未知标签,尝试解析其文本内容\n return state.schema.text(element.textContent ?? \"\");\n }\n}\n\n/**\n * 解析内联内容\n */\nfunction parseInlineContent(element: HTMLElement, state: EditorState): PMNode[] {\n const nodes: PMNode[] = [];\n\n for (let i = 0; i < element.childNodes.length; i++) {\n const child = element.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const parsed = parseElement(child as HTMLElement, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n return nodes;\n}\n\n/**\n * 插入补全内容 - 支持 HTML 直接渲染和 ProseMirror Node\n */\nexport function insertCompletion(\n state: EditorState,\n result: CompletionResult\n): Transaction {\n const pluginState = completionPluginKey.getState(state) as\n | CompletionPluginState\n | undefined;\n\n if (!pluginState || pluginState.triggerPos === null) {\n return state.tr;\n }\n\n const node = getCompletionNode(result);\n const html = getCompletionHTML(result);\n const text = parseCompletionResult(result);\n const pos = state.selection.from;\n\n let tr: Transaction;\n let insertedSize = 0;\n\n // 优先处理 prosemirror 类型\n if (node) {\n // 直接插入 ProseMirror Node\n tr = state.tr.insert(pos, node.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else if (html) {\n // 如果有 HTML 内容,尝试插入富文本\n const fragment = parseHTMLToFragment(html, state);\n if (fragment && fragment.childCount > 0) {\n tr = state.tr.insert(pos, fragment.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else {\n // 回退到纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n } else {\n // 纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n\n // 将光标移到插入内容之后\n const newPos = pos + insertedSize;\n tr.setSelection(TextSelection.create(tr.doc, newPos));\n\n // 添加 meta 标记已应用\n tr.setMeta(\"prosemirror-completion\", { type: \"apply\" });\n\n return tr;\n}\n\n/**\n * 取消补全\n */\nexport function cancelCompletion(state: EditorState): Transaction {\n const tr = state.tr;\n tr.setMeta(\"prosemirror-completion\", { type: \"cancel\" });\n return tr;\n}\n\n/**\n * 检查是否有活动的补全\n */\nexport function hasActiveCompletion(state: EditorState): boolean {\n const pluginState = completionPluginKey.getState(state);\n return !!(\n pluginState?.activeSuggestion && pluginState?.triggerPos !== null\n );\n}\n\n/**\n * 获取当前补全建议\n */\nexport function getActiveSuggestion(\n state: EditorState\n): CompletionResult | null {\n const pluginState = completionPluginKey.getState(state);\n return pluginState?.activeSuggestion ?? null;\n}\n","import type { EditorView } from \"prosemirror-view\";\nimport type { CompletionPluginState, CompletionAction } from \"./types\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 键盘事件处理器\n * 处理 Tab(应用)、Esc(取消)、和其他按键\n */\nexport function handleKeyDown(\n view: EditorView,\n event: KeyboardEvent,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n // 没有激活的补全时,不拦截\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n // Tab: 应用补全\n if (event.key === \"Tab\" && !event.shiftKey) {\n event.preventDefault();\n // 使用 insertCompletion 实际插入文本\n const tr = insertCompletion(view.state, activeSuggestion);\n view.dispatch(tr);\n return true;\n }\n\n // Escape: 取消补全\n if (event.key === \"Escape\") {\n event.preventDefault();\n const action: CompletionAction = { type: \"cancel\" };\n view.dispatch(view.state.tr.setMeta(\"prosemirror-completion\", action));\n return true;\n }\n\n // 其他按键:更新状态,让 state.apply 处理\n return false;\n}\n\n/**\n * 检查是否需要取消补全(光标移出触发区域等)\n */\nexport function shouldCancelCompletion(\n view: EditorView,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n const { from, to } = view.state.selection;\n\n // 如果光标不在触发位置之后,取消补全\n if (from < triggerPos) {\n return true;\n }\n\n return false;\n}\n","import type { EditorState } from \"prosemirror-state\";\nimport type {\n CompletionContext,\n CompletionOptions,\n PromptType,\n} from \"./types\";\n\n/**\n * 获取光标前的文本内容\n */\nexport function getTextBeforeCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n // 获取从节点开始到光标位置的文本\n let text = \"\";\n let offset = $pos.parentOffset;\n\n // 遍历子节点收集文本\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (offset <= childText.length) {\n text += childText.slice(0, offset);\n break;\n } else {\n text += childText;\n offset -= childText.length;\n }\n } else {\n // 非文本节点,记录为空格或换行\n text += \" \";\n offset -= 1;\n if (offset <= 0) break;\n }\n }\n\n return text;\n}\n\n/**\n * 获取光标后的文本内容\n */\nexport function getTextAfterCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n let text = \"\";\n let offset = $pos.parentOffset;\n let started = false;\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (!started) {\n if (offset < childText.length) {\n text += childText.slice(offset);\n started = true;\n } else {\n offset -= childText.length;\n }\n } else {\n text += childText;\n }\n } else {\n if (started || offset <= 0) {\n text += \" \";\n started = true;\n } else {\n offset -= 1;\n }\n }\n }\n\n return text;\n}\n\n/**\n * 默认的 prompt 类型检测\n */\nexport function defaultGetPromptType(context: CompletionContext): PromptType {\n const { beforeText } = context;\n\n // 检测代码块\n if (\n beforeText.includes(\"```\") ||\n /^\\s*(function|const|let|var|class|import|export|if|for|while)/.test(\n beforeText.slice(-50)\n )\n ) {\n return \"code\";\n }\n\n // 检测 markdown 特征\n if (\n /^\\s*(#{1,6}\\s|>|\\*|\\d+\\.|\\[|!\\[)/.test(beforeText.slice(-20)) ||\n beforeText.includes(\"**\") ||\n beforeText.includes(\"__\")\n ) {\n return \"markdown\";\n }\n\n return \"common\";\n}\n\n/**\n * 检查是否应该触发补全\n */\nexport function shouldTriggerCompletion(\n state: EditorState,\n options: CompletionOptions\n): boolean {\n const { from } = state.selection;\n const beforeText = getTextBeforeCursor(state, from);\n\n // 检查最小触发长度\n const minLength = options.minTriggerLength ?? 3;\n\n // 获取最后一个\"词\"的长度(以空格或标点分隔)\n const lastWord = beforeText.split(/[\\s\\n]+/).pop() ?? \"\";\n\n return lastWord.length >= minLength;\n}\n\n/**\n * 防抖函数\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n fn: T,\n ms: number\n): (...args: Parameters<T>) => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n fn(...args);\n timer = null;\n }, ms);\n };\n}\n","import { Plugin } from \"prosemirror-state\";\nimport type { EditorView } from \"prosemirror-view\";\nimport type {\n CompletionOptions,\n CompletionPluginState,\n CompletionAction,\n} from \"./types\";\nimport {\n completionPluginKey,\n} from \"./types\";\nimport {\n emptyDecorations,\n clearDecorations,\n updateGhostDecoration,\n} from \"./decorations\";\nimport { handleKeyDown, shouldCancelCompletion } from \"./keymap\";\nimport {\n getTextBeforeCursor,\n getTextAfterCursor,\n defaultGetPromptType,\n shouldTriggerCompletion,\n} from \"./utils\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 创建补全插件\n */\nexport function createCompletionPlugin(\n options: CompletionOptions\n): Plugin<CompletionPluginState> {\n const debounceMs = options.debounceMs ?? 300;\n const minTriggerLength = options.minTriggerLength ?? 3;\n\n return new Plugin<CompletionPluginState>({\n key: completionPluginKey,\n\n state: {\n init(): CompletionPluginState {\n return {\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: emptyDecorations(null),\n debounceTimer: null,\n options,\n };\n },\n\n apply(tr, pluginState): CompletionPluginState {\n const action = tr.getMeta(\n \"prosemirror-completion\"\n ) as CompletionAction | null;\n\n // 处理 action\n if (action) {\n switch (action.type) {\n case \"suggest\":\n return {\n ...pluginState,\n activeSuggestion: action.result,\n triggerPos: action.pos,\n isLoading: false,\n abortController: null,\n decorations: updateGhostDecoration(\n { doc: tr.doc } as import(\"prosemirror-state\").EditorState,\n pluginState,\n action.pos,\n action.result,\n options\n ),\n debounceTimer: null,\n };\n\n case \"apply\": {\n // 清理 decoration,补全内容已插入\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n }\n\n case \"cancel\":\n // 取消之前的请求\n if (pluginState.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n\n case \"loading\":\n return {\n ...pluginState,\n isLoading: action.isLoading,\n };\n }\n }\n\n // 如果没有 active suggestion,正常返回\n if (!pluginState.activeSuggestion || pluginState.triggerPos === null) {\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n }\n\n // 映射 decorations\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? null;\n },\n\n handleKeyDown(view: EditorView, event: KeyboardEvent): boolean {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return false;\n\n return handleKeyDown(view, event, pluginState);\n },\n },\n\n view(editorView) {\n const view = editorView;\n\n // 防抖处理补全请求\n const debouncedRequest = (() => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let lastAbortController: AbortController | null = null;\n\n return (pos: number) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n // 取消之前的请求\n if (lastAbortController) {\n lastAbortController.abort();\n }\n\n timer = setTimeout(async () => {\n const abortController = new AbortController();\n lastAbortController = abortController;\n\n // 更新 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: true,\n })\n );\n\n try {\n const $pos = view.state.doc.resolve(pos);\n const parent = $pos.parent;\n const beforeText = getTextBeforeCursor(view.state, pos);\n const afterText = getTextAfterCursor(view.state, pos);\n const promptType = options.getPromptType\n ? options.getPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n })\n : defaultGetPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n });\n\n const context = {\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType,\n state: view.state,\n };\n\n // 调用补全函数\n const result = await Promise.resolve(options.callCompletion(context));\n\n // 如果已经被取消,不更新\n if (abortController.signal.aborted) {\n return;\n }\n\n // 更新建议\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"suggest\",\n result,\n pos,\n })\n );\n\n // 调用 onChange 回调\n if (options.onChange) {\n options.onChange(context, view);\n }\n } catch (error) {\n if (abortController.signal.aborted) {\n return;\n }\n console.error(\"Completion error:\", error);\n } finally {\n // 清除 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: false,\n })\n );\n }\n\n timer = null;\n lastAbortController = null;\n }, debounceMs);\n\n // 保存 timer 到插件状态\n const currentState = completionPluginKey.getState(view.state);\n if (currentState && currentState.debounceTimer) {\n clearTimeout(currentState.debounceTimer);\n }\n };\n })();\n\n // 监听文档和选择变化\n const handleInput = (from: number) => {\n // 检查是否应该触发补全\n if (!shouldTriggerCompletion(view.state, { ...options, minTriggerLength })) {\n return;\n }\n // 触发新的补全请求\n debouncedRequest(from);\n };\n\n // 使用 ProseMirror 的 update 监听文档和选择变化\n return {\n update: (view, prevState) => {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return;\n\n // 检查是否有 active suggestion\n if (pluginState.activeSuggestion) {\n const { from } = view.state.selection;\n const selectionChanged = view.state.selection.eq(prevState.selection) === false;\n const docChanged = view.state.doc !== prevState.doc;\n\n // 如果有补全,检查是否需要取消\n if (docChanged || selectionChanged) {\n // 取消当前补全\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"cancel\",\n })\n );\n }\n }\n\n // 检查是否应该触发新的补全\n if (view.state.doc !== prevState.doc) {\n handleInput(view.state.selection.from);\n }\n },\n destroy: () => {\n // 清理\n const pluginState = completionPluginKey.getState(view.state);\n if (pluginState?.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState?.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n },\n };\n },\n });\n}\n"],"names":["completionMetaKey","completionPluginKey","PluginKey","createGhostDecoration","pos","result","options","dom","text","parseCompletionResult","Decoration","tmp","createCompletionFragment","state","emptyDecorations","DecorationSet","updateGhostDecoration","pluginState","deco","clearDecorations","getCompletionHTML","getCompletionNode","parseHTMLToFragment","html","nodes","body","i","child","parsed","parseElement","element","_a","parseInlineContent","_b","_c","_d","insertCompletion","node","tr","insertedSize","fragment","newPos","TextSelection","cancelCompletion","hasActiveCompletion","getActiveSuggestion","handleKeyDown","view","event","activeSuggestion","triggerPos","action","shouldCancelCompletion","from","to","getTextBeforeCursor","$pos","offset","childText","getTextAfterCursor","started","defaultGetPromptType","context","beforeText","shouldTriggerCompletion","minLength","debounce","fn","ms","timer","args","createCompletionPlugin","debounceMs","minTriggerLength","Plugin","editorView","debouncedRequest","lastAbortController","abortController","parent","afterText","promptType","error","currentState","handleInput","prevState","selectionChanged"],"mappings":"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"}
package/index.mjs ADDED
@@ -0,0 +1,411 @@
1
+ import { PluginKey as D, TextSelection as k, Plugin as N } from "prosemirror-state";
2
+ import { Decoration as w, DecorationSet as x } from "prosemirror-view";
3
+ const W = "prosemirror-completion", m = new D(
4
+ "prosemirror-completion"
5
+ );
6
+ function K(e, o, c) {
7
+ const n = document.createElement("span");
8
+ n.className = c.ghostClassName ?? "prosemirror-ghost-text";
9
+ const t = T(o);
10
+ return n.textContent = t, w.widget(e, n, {
11
+ side: 1,
12
+ stopEvent: () => !0,
13
+ key: "ghost-completion"
14
+ });
15
+ }
16
+ function T(e) {
17
+ if (typeof e == "string")
18
+ return e;
19
+ if ("plain" in e && e.plain !== void 0)
20
+ return e.plain;
21
+ if ("html" in e && e.html !== void 0) {
22
+ const o = document.createElement("div");
23
+ return o.innerHTML = e.html, o.textContent ?? "";
24
+ }
25
+ return "prosemirror" in e && e.prosemirror ? e.prosemirror.textContent ?? "" : "";
26
+ }
27
+ function _(e, o) {
28
+ return typeof e == "string" ? { text: e } : "prosemirror" in e ? { text: e.prosemirror.textContent ?? "", node: e.prosemirror } : "plain" in e && "html" in e ? { text: e.plain, html: e.html } : "html" in e ? {
29
+ text: T(e),
30
+ html: e.html
31
+ } : "plain" in e ? { text: e.plain } : { text: "" };
32
+ }
33
+ function C(e) {
34
+ return e ? x.create(e.doc, []) : x.empty;
35
+ }
36
+ function S(e, o, c, n, t) {
37
+ if (!t.showGhost || !n || !e)
38
+ return C(e);
39
+ const r = K(c, n, t);
40
+ return x.create(e.doc, [r]);
41
+ }
42
+ function b(e) {
43
+ return C(e);
44
+ }
45
+ function z(e) {
46
+ if (typeof e != "string" && "html" in e && e.html !== void 0)
47
+ return e.html;
48
+ }
49
+ function A(e) {
50
+ if (typeof e != "string" && "prosemirror" in e && e.prosemirror)
51
+ return e.prosemirror;
52
+ }
53
+ function E(e, o) {
54
+ const c = new DOMParser().parseFromString(e, "text/html");
55
+ try {
56
+ const n = [], t = c.body;
57
+ for (let r = 0; r < t.childNodes.length; r++) {
58
+ const l = t.childNodes[r];
59
+ if (l.nodeType === 3) {
60
+ const i = l.textContent ?? "";
61
+ i && n.push(o.schema.text(i));
62
+ } else if (l.nodeType === 1) {
63
+ const s = P(l, o);
64
+ s && n.push(s);
65
+ }
66
+ }
67
+ return n.length === 0 ? null : o.schema.nodes.doc.create(null, n);
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+ function P(e, o) {
73
+ var n, t, r, l;
74
+ switch (e.tagName.toLowerCase()) {
75
+ case "p":
76
+ return (n = o.schema.nodes.paragraph) == null ? void 0 : n.create(
77
+ null,
78
+ G(e, o)
79
+ );
80
+ case "strong":
81
+ case "b":
82
+ return o.schema.text(
83
+ e.textContent ?? "",
84
+ [(t = o.schema.marks.strong) == null ? void 0 : t.create()]
85
+ );
86
+ case "em":
87
+ case "i":
88
+ return o.schema.text(
89
+ e.textContent ?? "",
90
+ [(r = o.schema.marks.em) == null ? void 0 : r.create()]
91
+ );
92
+ case "code":
93
+ return o.schema.text(
94
+ e.textContent ?? "",
95
+ [(l = o.schema.marks.code) == null ? void 0 : l.create()]
96
+ );
97
+ case "br":
98
+ return o.schema.text(`
99
+ `);
100
+ default:
101
+ return o.schema.text(e.textContent ?? "");
102
+ }
103
+ }
104
+ function G(e, o) {
105
+ const c = [];
106
+ for (let n = 0; n < e.childNodes.length; n++) {
107
+ const t = e.childNodes[n];
108
+ if (t.nodeType === 3) {
109
+ const r = t.textContent ?? "";
110
+ r && c.push(o.schema.text(r));
111
+ } else if (t.nodeType === 1) {
112
+ const r = P(t, o);
113
+ r && c.push(r);
114
+ }
115
+ }
116
+ return c;
117
+ }
118
+ function $(e, o) {
119
+ const c = m.getState(e);
120
+ if (!c || c.triggerPos === null)
121
+ return e.tr;
122
+ const n = A(o), t = z(o), r = T(o), l = e.selection.from;
123
+ let i, s = 0;
124
+ if (n)
125
+ i = e.tr.insert(l, n.content), s = i.doc.content.size - e.doc.content.size;
126
+ else if (t) {
127
+ const u = E(t, e);
128
+ u && u.childCount > 0 ? (i = e.tr.insert(l, u.content), s = i.doc.content.size - e.doc.content.size) : (i = e.tr.insertText(r, l), s = r.length);
129
+ } else
130
+ i = e.tr.insertText(r, l), s = r.length;
131
+ const a = l + s;
132
+ return i.setSelection(k.create(i.doc, a)), i.setMeta("prosemirror-completion", { type: "apply" }), i;
133
+ }
134
+ function B(e) {
135
+ const o = e.tr;
136
+ return o.setMeta("prosemirror-completion", { type: "cancel" }), o;
137
+ }
138
+ function j(e) {
139
+ const o = m.getState(e);
140
+ return !!(o != null && o.activeSuggestion && (o == null ? void 0 : o.triggerPos) !== null);
141
+ }
142
+ function J(e) {
143
+ const o = m.getState(e);
144
+ return (o == null ? void 0 : o.activeSuggestion) ?? null;
145
+ }
146
+ function F(e, o, c) {
147
+ const { activeSuggestion: n, triggerPos: t } = c;
148
+ if (!n || t === null)
149
+ return !1;
150
+ if (o.key === "Tab" && !o.shiftKey) {
151
+ o.preventDefault();
152
+ const r = $(e.state, n);
153
+ return e.dispatch(r), !0;
154
+ }
155
+ if (o.key === "Escape") {
156
+ o.preventDefault();
157
+ const r = { type: "cancel" };
158
+ return e.dispatch(e.state.tr.setMeta("prosemirror-completion", r)), !0;
159
+ }
160
+ return !1;
161
+ }
162
+ function Q(e, o) {
163
+ const { activeSuggestion: c, triggerPos: n } = o;
164
+ if (!c || n === null)
165
+ return !1;
166
+ const { from: t, to: r } = e.state.selection;
167
+ return t < n;
168
+ }
169
+ function L(e, o) {
170
+ const c = e.doc.resolve(o), n = c.parent;
171
+ let t = "", r = c.parentOffset;
172
+ for (let l = 0; l < n.childCount; l++) {
173
+ const i = n.child(l);
174
+ if (i.isText) {
175
+ const s = i.text ?? "";
176
+ if (r <= s.length) {
177
+ t += s.slice(0, r);
178
+ break;
179
+ } else
180
+ t += s, r -= s.length;
181
+ } else if (t += " ", r -= 1, r <= 0) break;
182
+ }
183
+ return t;
184
+ }
185
+ function H(e, o) {
186
+ const c = e.doc.resolve(o), n = c.parent;
187
+ let t = "", r = c.parentOffset, l = !1;
188
+ for (let i = 0; i < n.childCount; i++) {
189
+ const s = n.child(i);
190
+ if (s.isText) {
191
+ const a = s.text ?? "";
192
+ l ? t += a : r < a.length ? (t += a.slice(r), l = !0) : r -= a.length;
193
+ } else
194
+ l || r <= 0 ? (t += " ", l = !0) : r -= 1;
195
+ }
196
+ return t;
197
+ }
198
+ function O(e) {
199
+ const { beforeText: o } = e;
200
+ return o.includes("```") || /^\s*(function|const|let|var|class|import|export|if|for|while)/.test(
201
+ o.slice(-50)
202
+ ) ? "code" : /^\s*(#{1,6}\s|>|\*|\d+\.|\[|!\[)/.test(o.slice(-20)) || o.includes("**") || o.includes("__") ? "markdown" : "common";
203
+ }
204
+ function q(e, o) {
205
+ const { from: c } = e.selection, n = L(e, c), t = o.minTriggerLength ?? 3;
206
+ return (n.split(/[\s\n]+/).pop() ?? "").length >= t;
207
+ }
208
+ function U(e, o) {
209
+ let c = null;
210
+ return (...n) => {
211
+ c && clearTimeout(c), c = setTimeout(() => {
212
+ e(...n), c = null;
213
+ }, o);
214
+ };
215
+ }
216
+ function V(e) {
217
+ const o = e.debounceMs ?? 300, c = e.minTriggerLength ?? 3;
218
+ return new N({
219
+ key: m,
220
+ state: {
221
+ init() {
222
+ return {
223
+ activeSuggestion: null,
224
+ triggerPos: null,
225
+ isLoading: !1,
226
+ abortController: null,
227
+ decorations: C(null),
228
+ debounceTimer: null,
229
+ options: e
230
+ };
231
+ },
232
+ apply(n, t) {
233
+ const r = n.getMeta(
234
+ "prosemirror-completion"
235
+ );
236
+ if (r)
237
+ switch (r.type) {
238
+ case "suggest":
239
+ return {
240
+ ...t,
241
+ activeSuggestion: r.result,
242
+ triggerPos: r.pos,
243
+ isLoading: !1,
244
+ abortController: null,
245
+ decorations: S(
246
+ { doc: n.doc },
247
+ t,
248
+ r.pos,
249
+ r.result,
250
+ e
251
+ ),
252
+ debounceTimer: null
253
+ };
254
+ case "apply":
255
+ return {
256
+ ...t,
257
+ activeSuggestion: null,
258
+ triggerPos: null,
259
+ isLoading: !1,
260
+ abortController: null,
261
+ decorations: b({ doc: n.doc }),
262
+ debounceTimer: null
263
+ };
264
+ case "cancel":
265
+ return t.abortController && t.abortController.abort(), t.debounceTimer && clearTimeout(t.debounceTimer), {
266
+ ...t,
267
+ activeSuggestion: null,
268
+ triggerPos: null,
269
+ isLoading: !1,
270
+ abortController: null,
271
+ decorations: b({ doc: n.doc }),
272
+ debounceTimer: null
273
+ };
274
+ case "loading":
275
+ return {
276
+ ...t,
277
+ isLoading: r.isLoading
278
+ };
279
+ }
280
+ return !t.activeSuggestion || t.triggerPos === null ? {
281
+ ...t,
282
+ decorations: t.decorations.map(n.mapping, n.doc)
283
+ } : {
284
+ ...t,
285
+ decorations: t.decorations.map(n.mapping, n.doc)
286
+ };
287
+ }
288
+ },
289
+ props: {
290
+ decorations(n) {
291
+ var t;
292
+ return ((t = this.getState(n)) == null ? void 0 : t.decorations) ?? null;
293
+ },
294
+ handleKeyDown(n, t) {
295
+ const r = m.getState(n.state);
296
+ return r ? F(n, t, r) : !1;
297
+ }
298
+ },
299
+ view(n) {
300
+ const t = n, r = /* @__PURE__ */ (() => {
301
+ let i = null, s = null;
302
+ return (a) => {
303
+ i && clearTimeout(i), s && s.abort(), i = setTimeout(async () => {
304
+ const d = new AbortController();
305
+ s = d, t.dispatch(
306
+ t.state.tr.setMeta("prosemirror-completion", {
307
+ type: "loading",
308
+ isLoading: !0
309
+ })
310
+ );
311
+ try {
312
+ const p = t.state.doc.resolve(a).parent, g = L(t.state, a), h = H(t.state, a), M = e.getPromptType ? e.getPromptType({
313
+ abortController: d,
314
+ parent: p,
315
+ pos: a,
316
+ beforeText: g,
317
+ afterText: h,
318
+ promptType: "common",
319
+ state: t.state
320
+ }) : O({
321
+ abortController: d,
322
+ parent: p,
323
+ pos: a,
324
+ beforeText: g,
325
+ afterText: h,
326
+ promptType: "common",
327
+ state: t.state
328
+ }), y = {
329
+ abortController: d,
330
+ parent: p,
331
+ pos: a,
332
+ beforeText: g,
333
+ afterText: h,
334
+ promptType: M,
335
+ state: t.state
336
+ }, v = await Promise.resolve(e.callCompletion(y));
337
+ if (d.signal.aborted)
338
+ return;
339
+ t.dispatch(
340
+ t.state.tr.setMeta("prosemirror-completion", {
341
+ type: "suggest",
342
+ result: v,
343
+ pos: a
344
+ })
345
+ ), e.onChange && e.onChange(y, t);
346
+ } catch (f) {
347
+ if (d.signal.aborted)
348
+ return;
349
+ console.error("Completion error:", f);
350
+ } finally {
351
+ t.dispatch(
352
+ t.state.tr.setMeta("prosemirror-completion", {
353
+ type: "loading",
354
+ isLoading: !1
355
+ })
356
+ );
357
+ }
358
+ i = null, s = null;
359
+ }, o);
360
+ const u = m.getState(t.state);
361
+ u && u.debounceTimer && clearTimeout(u.debounceTimer);
362
+ };
363
+ })(), l = (i) => {
364
+ q(t.state, { ...e, minTriggerLength: c }) && r(i);
365
+ };
366
+ return {
367
+ update: (i, s) => {
368
+ const a = m.getState(i.state);
369
+ if (a) {
370
+ if (a.activeSuggestion) {
371
+ const { from: u } = i.state.selection, d = i.state.selection.eq(s.selection) === !1;
372
+ (i.state.doc !== s.doc || d) && i.dispatch(
373
+ i.state.tr.setMeta("prosemirror-completion", {
374
+ type: "cancel"
375
+ })
376
+ );
377
+ }
378
+ i.state.doc !== s.doc && l(i.state.selection.from);
379
+ }
380
+ },
381
+ destroy: () => {
382
+ const i = m.getState(t.state);
383
+ i != null && i.abortController && i.abortController.abort(), i != null && i.debounceTimer && clearTimeout(i.debounceTimer);
384
+ }
385
+ };
386
+ }
387
+ });
388
+ }
389
+ export {
390
+ B as cancelCompletion,
391
+ b as clearDecorations,
392
+ W as completionMetaKey,
393
+ m as completionPluginKey,
394
+ _ as createCompletionFragment,
395
+ V as createCompletionPlugin,
396
+ K as createGhostDecoration,
397
+ U as debounce,
398
+ O as defaultGetPromptType,
399
+ C as emptyDecorations,
400
+ J as getActiveSuggestion,
401
+ H as getTextAfterCursor,
402
+ L as getTextBeforeCursor,
403
+ F as handleKeyDown,
404
+ j as hasActiveCompletion,
405
+ $ as insertCompletion,
406
+ T as parseCompletionResult,
407
+ Q as shouldCancelCompletion,
408
+ q as shouldTriggerCompletion,
409
+ S as updateGhostDecoration
410
+ };
411
+ //# sourceMappingURL=index.mjs.map
package/index.mjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/types.ts","../src/decorations.ts","../src/commands.ts","../src/keymap.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import type { Node } from \"prosemirror-model\";\nimport type { EditorState, Transaction } from \"prosemirror-state\";\nimport type { EditorView, DecorationSet } from \"prosemirror-view\";\nimport { PluginKey } from \"prosemirror-state\";\n\n/**\n * 补全返回结果格式\n */\nexport type CompletionResult =\n | string\n | { plain: string; html?: string }\n | { html: string }\n | { prosemirror: Node };\n\n/**\n * Prompt 类型建议\n */\nexport type PromptType = \"common\" | \"code\" | \"markdown\" | string;\n\n/**\n * 补全上下文\n */\nexport interface CompletionContext {\n /** 用于取消请求的 AbortController */\n abortController: AbortController;\n /** 当前位置的父节点 */\n parent: Node;\n /** 当前位置 */\n pos: number;\n /** 光标前的文本 */\n beforeText: string;\n /** 光标后的文本 */\n afterText: string;\n /** 建议的 prompt 类型 */\n promptType: PromptType;\n /** 编辑器状态 */\n state: EditorState;\n}\n\n/**\n * 补全插件配置选项\n */\nexport interface CompletionOptions {\n /**\n * 防抖时间(毫秒)\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * 触发补全的最小字符数\n * @default 3\n */\n minTriggerLength?: number;\n\n /**\n * 补全函数 - 接收上下文,返回补全结果\n */\n callCompletion: (\n context: CompletionContext,\n ) => Promise<CompletionResult> | CompletionResult;\n\n /**\n * 确定当前上下文适合的 prompt 类型\n */\n getPromptType?: (context: CompletionContext) => PromptType;\n\n /**\n * 用户打字时的回调\n */\n onChange?: (context: CompletionContext, view: EditorView) => void;\n\n /**\n * 补全退出时的回调(按 Esc 或点击别处)\n */\n onExit?: (view: EditorView) => void;\n\n /**\n * 补全应用时的回调(按 Tab)\n */\n onApply?: (result: CompletionResult, view: EditorView) => void;\n\n /**\n * 自定义 ghost text 的 CSS 类名\n * @default \"prosemirror-ghost-text\"\n */\n ghostClassName?: string;\n\n /**\n * 是否显示 ghost text\n * @default true\n */\n showGhost?: boolean;\n}\n\n/**\n * 内部插件状态\n */\nexport interface CompletionPluginState {\n /** 当前激活的补全建议 */\n activeSuggestion: CompletionResult | null;\n /** 补全触发位置 */\n triggerPos: number | null;\n /** 是否正在加载 */\n isLoading: boolean;\n /** 当前 AbortController */\n abortController: AbortController | null;\n /** DecorationSet */\n decorations: DecorationSet;\n /** 防抖计时器 */\n debounceTimer: ReturnType<typeof setTimeout> | null;\n /** 插件配置 */\n options: CompletionOptions;\n}\n\n/**\n * Transaction meta key\n */\nexport const completionMetaKey = \"prosemirror-completion\";\n\n/**\n * 插件 Key\n */\nexport const completionPluginKey = new PluginKey<CompletionPluginState>(\n \"prosemirror-completion\",\n);\n\n/**\n * Action 类型\n */\nexport type CompletionAction =\n | { type: \"start\"; pos: number }\n | { type: \"suggest\"; result: CompletionResult; pos: number }\n | { type: \"apply\" }\n | { type: \"cancel\" }\n | { type: \"loading\"; isLoading: boolean };\n","import { Decoration, DecorationSet } from \"prosemirror-view\";\nimport type { EditorState } from \"prosemirror-state\";\nimport type { Node } from \"prosemirror-model\";\nimport type {\n CompletionOptions,\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\n\n/**\n * 创建 ghost text 的 decoration\n */\nexport function createGhostDecoration(\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): Decoration {\n const dom = document.createElement(\"span\");\n dom.className = options.ghostClassName ?? \"prosemirror-ghost-text\";\n\n // 解析补全结果\n const text = parseCompletionResult(result);\n dom.textContent = text;\n\n // side: 1 确保它在光标后,stopEvent 阻止用户点击灰字导致光标偏移\n return Decoration.widget(pos, dom, {\n side: 1,\n stopEvent: () => true,\n key: \"ghost-completion\",\n });\n}\n\n/**\n * 解析补全结果为纯文本\n */\nexport function parseCompletionResult(result: CompletionResult): string {\n if (typeof result === \"string\") {\n return result;\n }\n if (\"plain\" in result && result.plain !== undefined) {\n return result.plain;\n }\n if (\"html\" in result && result.html !== undefined) {\n // 简单去除 HTML 标签获取纯文本\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = result.html;\n return tmp.textContent ?? \"\";\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n // 从 ProseMirror Node 提取文本内容\n return result.prosemirror.textContent ?? \"\";\n }\n return \"\";\n}\n\n/**\n * 将补全结果转换为 ProseMirror 可以插入的片段\n */\nexport function createCompletionFragment(\n result: CompletionResult,\n state: EditorState\n): { text: string; html?: string; node?: Node } {\n if (typeof result === \"string\") {\n return { text: result };\n }\n if (\"prosemirror\" in result) {\n return { text: result.prosemirror.textContent ?? \"\", node: result.prosemirror };\n }\n if (\"plain\" in result && \"html\" in result) {\n return { text: result.plain, html: result.html };\n }\n if (\"html\" in result) {\n return {\n text: parseCompletionResult(result),\n html: result.html,\n };\n }\n if (\"plain\" in result) {\n return { text: result.plain };\n }\n return { text: \"\" };\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function emptyDecorations(state: EditorState | null): DecorationSet {\n if (!state) {\n return DecorationSet.empty;\n }\n return DecorationSet.create(state.doc, []);\n}\n\n/**\n * 更新 DecorationSet,添加 ghost decoration\n */\nexport function updateGhostDecoration(\n state: EditorState | null,\n pluginState: CompletionPluginState,\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): DecorationSet {\n if (!options.showGhost || !result || !state) {\n return emptyDecorations(state);\n }\n\n const deco = createGhostDecoration(pos, result, options);\n return DecorationSet.create(state.doc, [deco]);\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function clearDecorations(state: EditorState | null): DecorationSet {\n return emptyDecorations(state);\n}\n","import type { EditorState, Transaction } from \"prosemirror-state\";\nimport { TextSelection } from \"prosemirror-state\";\nimport { Node as PMNode } from \"prosemirror-model\";\nimport type {\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\nimport { completionPluginKey } from \"./types\";\nimport { parseCompletionResult } from \"./decorations\";\n\n/**\n * 获取补全结果的 HTML 内容\n */\nfunction getCompletionHTML(result: CompletionResult): string | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"html\" in result && result.html !== undefined) {\n return result.html;\n }\n return undefined;\n}\n\n/**\n * 获取补全结果的 ProseMirror Node\n */\nfunction getCompletionNode(result: CompletionResult): PMNode | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n return result.prosemirror;\n }\n return undefined;\n}\n\n/**\n * 将 HTML 字符串解析为 ProseMirror 文档片段\n */\nfunction parseHTMLToFragment(\n html: string,\n state: EditorState\n): PMNode | null {\n // 使用 DOMParser 将 HTML 解析为 DOM\n const dom = new DOMParser().parseFromString(html, \"text/html\");\n\n try {\n const nodes: PMNode[] = [];\n const body = dom.body;\n\n for (let i = 0; i < body.childNodes.length; i++) {\n const child = body.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const element = child as HTMLElement;\n const parsed = parseElement(element, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n if (nodes.length === 0) {\n return null;\n }\n\n // 创建文档片段\n return state.schema.nodes.doc.create(null, nodes);\n } catch {\n return null;\n }\n}\n\n/**\n * 解析单个 HTML 元素为 ProseMirror 节点\n */\nfunction parseElement(element: HTMLElement, state: EditorState): PMNode | null {\n const tagName = element.tagName.toLowerCase();\n\n // 处理常见标签\n switch (tagName) {\n case \"p\":\n return state.schema.nodes.paragraph?.create(\n null,\n parseInlineContent(element, state)\n );\n case \"strong\":\n case \"b\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.strong?.create()]\n );\n case \"em\":\n case \"i\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.em?.create()]\n );\n case \"code\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.code?.create()]\n );\n case \"br\":\n return state.schema.text(\"\\n\");\n default:\n // 对于未知标签,尝试解析其文本内容\n return state.schema.text(element.textContent ?? \"\");\n }\n}\n\n/**\n * 解析内联内容\n */\nfunction parseInlineContent(element: HTMLElement, state: EditorState): PMNode[] {\n const nodes: PMNode[] = [];\n\n for (let i = 0; i < element.childNodes.length; i++) {\n const child = element.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const parsed = parseElement(child as HTMLElement, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n return nodes;\n}\n\n/**\n * 插入补全内容 - 支持 HTML 直接渲染和 ProseMirror Node\n */\nexport function insertCompletion(\n state: EditorState,\n result: CompletionResult\n): Transaction {\n const pluginState = completionPluginKey.getState(state) as\n | CompletionPluginState\n | undefined;\n\n if (!pluginState || pluginState.triggerPos === null) {\n return state.tr;\n }\n\n const node = getCompletionNode(result);\n const html = getCompletionHTML(result);\n const text = parseCompletionResult(result);\n const pos = state.selection.from;\n\n let tr: Transaction;\n let insertedSize = 0;\n\n // 优先处理 prosemirror 类型\n if (node) {\n // 直接插入 ProseMirror Node\n tr = state.tr.insert(pos, node.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else if (html) {\n // 如果有 HTML 内容,尝试插入富文本\n const fragment = parseHTMLToFragment(html, state);\n if (fragment && fragment.childCount > 0) {\n tr = state.tr.insert(pos, fragment.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else {\n // 回退到纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n } else {\n // 纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n\n // 将光标移到插入内容之后\n const newPos = pos + insertedSize;\n tr.setSelection(TextSelection.create(tr.doc, newPos));\n\n // 添加 meta 标记已应用\n tr.setMeta(\"prosemirror-completion\", { type: \"apply\" });\n\n return tr;\n}\n\n/**\n * 取消补全\n */\nexport function cancelCompletion(state: EditorState): Transaction {\n const tr = state.tr;\n tr.setMeta(\"prosemirror-completion\", { type: \"cancel\" });\n return tr;\n}\n\n/**\n * 检查是否有活动的补全\n */\nexport function hasActiveCompletion(state: EditorState): boolean {\n const pluginState = completionPluginKey.getState(state);\n return !!(\n pluginState?.activeSuggestion && pluginState?.triggerPos !== null\n );\n}\n\n/**\n * 获取当前补全建议\n */\nexport function getActiveSuggestion(\n state: EditorState\n): CompletionResult | null {\n const pluginState = completionPluginKey.getState(state);\n return pluginState?.activeSuggestion ?? null;\n}\n","import type { EditorView } from \"prosemirror-view\";\nimport type { CompletionPluginState, CompletionAction } from \"./types\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 键盘事件处理器\n * 处理 Tab(应用)、Esc(取消)、和其他按键\n */\nexport function handleKeyDown(\n view: EditorView,\n event: KeyboardEvent,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n // 没有激活的补全时,不拦截\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n // Tab: 应用补全\n if (event.key === \"Tab\" && !event.shiftKey) {\n event.preventDefault();\n // 使用 insertCompletion 实际插入文本\n const tr = insertCompletion(view.state, activeSuggestion);\n view.dispatch(tr);\n return true;\n }\n\n // Escape: 取消补全\n if (event.key === \"Escape\") {\n event.preventDefault();\n const action: CompletionAction = { type: \"cancel\" };\n view.dispatch(view.state.tr.setMeta(\"prosemirror-completion\", action));\n return true;\n }\n\n // 其他按键:更新状态,让 state.apply 处理\n return false;\n}\n\n/**\n * 检查是否需要取消补全(光标移出触发区域等)\n */\nexport function shouldCancelCompletion(\n view: EditorView,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n const { from, to } = view.state.selection;\n\n // 如果光标不在触发位置之后,取消补全\n if (from < triggerPos) {\n return true;\n }\n\n return false;\n}\n","import type { EditorState } from \"prosemirror-state\";\nimport type {\n CompletionContext,\n CompletionOptions,\n PromptType,\n} from \"./types\";\n\n/**\n * 获取光标前的文本内容\n */\nexport function getTextBeforeCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n // 获取从节点开始到光标位置的文本\n let text = \"\";\n let offset = $pos.parentOffset;\n\n // 遍历子节点收集文本\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (offset <= childText.length) {\n text += childText.slice(0, offset);\n break;\n } else {\n text += childText;\n offset -= childText.length;\n }\n } else {\n // 非文本节点,记录为空格或换行\n text += \" \";\n offset -= 1;\n if (offset <= 0) break;\n }\n }\n\n return text;\n}\n\n/**\n * 获取光标后的文本内容\n */\nexport function getTextAfterCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n let text = \"\";\n let offset = $pos.parentOffset;\n let started = false;\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (!started) {\n if (offset < childText.length) {\n text += childText.slice(offset);\n started = true;\n } else {\n offset -= childText.length;\n }\n } else {\n text += childText;\n }\n } else {\n if (started || offset <= 0) {\n text += \" \";\n started = true;\n } else {\n offset -= 1;\n }\n }\n }\n\n return text;\n}\n\n/**\n * 默认的 prompt 类型检测\n */\nexport function defaultGetPromptType(context: CompletionContext): PromptType {\n const { beforeText } = context;\n\n // 检测代码块\n if (\n beforeText.includes(\"```\") ||\n /^\\s*(function|const|let|var|class|import|export|if|for|while)/.test(\n beforeText.slice(-50)\n )\n ) {\n return \"code\";\n }\n\n // 检测 markdown 特征\n if (\n /^\\s*(#{1,6}\\s|>|\\*|\\d+\\.|\\[|!\\[)/.test(beforeText.slice(-20)) ||\n beforeText.includes(\"**\") ||\n beforeText.includes(\"__\")\n ) {\n return \"markdown\";\n }\n\n return \"common\";\n}\n\n/**\n * 检查是否应该触发补全\n */\nexport function shouldTriggerCompletion(\n state: EditorState,\n options: CompletionOptions\n): boolean {\n const { from } = state.selection;\n const beforeText = getTextBeforeCursor(state, from);\n\n // 检查最小触发长度\n const minLength = options.minTriggerLength ?? 3;\n\n // 获取最后一个\"词\"的长度(以空格或标点分隔)\n const lastWord = beforeText.split(/[\\s\\n]+/).pop() ?? \"\";\n\n return lastWord.length >= minLength;\n}\n\n/**\n * 防抖函数\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n fn: T,\n ms: number\n): (...args: Parameters<T>) => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n fn(...args);\n timer = null;\n }, ms);\n };\n}\n","import { Plugin } from \"prosemirror-state\";\nimport type { EditorView } from \"prosemirror-view\";\nimport type {\n CompletionOptions,\n CompletionPluginState,\n CompletionAction,\n} from \"./types\";\nimport {\n completionPluginKey,\n} from \"./types\";\nimport {\n emptyDecorations,\n clearDecorations,\n updateGhostDecoration,\n} from \"./decorations\";\nimport { handleKeyDown, shouldCancelCompletion } from \"./keymap\";\nimport {\n getTextBeforeCursor,\n getTextAfterCursor,\n defaultGetPromptType,\n shouldTriggerCompletion,\n} from \"./utils\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 创建补全插件\n */\nexport function createCompletionPlugin(\n options: CompletionOptions\n): Plugin<CompletionPluginState> {\n const debounceMs = options.debounceMs ?? 300;\n const minTriggerLength = options.minTriggerLength ?? 3;\n\n return new Plugin<CompletionPluginState>({\n key: completionPluginKey,\n\n state: {\n init(): CompletionPluginState {\n return {\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: emptyDecorations(null),\n debounceTimer: null,\n options,\n };\n },\n\n apply(tr, pluginState): CompletionPluginState {\n const action = tr.getMeta(\n \"prosemirror-completion\"\n ) as CompletionAction | null;\n\n // 处理 action\n if (action) {\n switch (action.type) {\n case \"suggest\":\n return {\n ...pluginState,\n activeSuggestion: action.result,\n triggerPos: action.pos,\n isLoading: false,\n abortController: null,\n decorations: updateGhostDecoration(\n { doc: tr.doc } as import(\"prosemirror-state\").EditorState,\n pluginState,\n action.pos,\n action.result,\n options\n ),\n debounceTimer: null,\n };\n\n case \"apply\": {\n // 清理 decoration,补全内容已插入\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n }\n\n case \"cancel\":\n // 取消之前的请求\n if (pluginState.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n\n case \"loading\":\n return {\n ...pluginState,\n isLoading: action.isLoading,\n };\n }\n }\n\n // 如果没有 active suggestion,正常返回\n if (!pluginState.activeSuggestion || pluginState.triggerPos === null) {\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n }\n\n // 映射 decorations\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? null;\n },\n\n handleKeyDown(view: EditorView, event: KeyboardEvent): boolean {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return false;\n\n return handleKeyDown(view, event, pluginState);\n },\n },\n\n view(editorView) {\n const view = editorView;\n\n // 防抖处理补全请求\n const debouncedRequest = (() => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let lastAbortController: AbortController | null = null;\n\n return (pos: number) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n // 取消之前的请求\n if (lastAbortController) {\n lastAbortController.abort();\n }\n\n timer = setTimeout(async () => {\n const abortController = new AbortController();\n lastAbortController = abortController;\n\n // 更新 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: true,\n })\n );\n\n try {\n const $pos = view.state.doc.resolve(pos);\n const parent = $pos.parent;\n const beforeText = getTextBeforeCursor(view.state, pos);\n const afterText = getTextAfterCursor(view.state, pos);\n const promptType = options.getPromptType\n ? options.getPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n })\n : defaultGetPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n });\n\n const context = {\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType,\n state: view.state,\n };\n\n // 调用补全函数\n const result = await Promise.resolve(options.callCompletion(context));\n\n // 如果已经被取消,不更新\n if (abortController.signal.aborted) {\n return;\n }\n\n // 更新建议\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"suggest\",\n result,\n pos,\n })\n );\n\n // 调用 onChange 回调\n if (options.onChange) {\n options.onChange(context, view);\n }\n } catch (error) {\n if (abortController.signal.aborted) {\n return;\n }\n console.error(\"Completion error:\", error);\n } finally {\n // 清除 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: false,\n })\n );\n }\n\n timer = null;\n lastAbortController = null;\n }, debounceMs);\n\n // 保存 timer 到插件状态\n const currentState = completionPluginKey.getState(view.state);\n if (currentState && currentState.debounceTimer) {\n clearTimeout(currentState.debounceTimer);\n }\n };\n })();\n\n // 监听文档和选择变化\n const handleInput = (from: number) => {\n // 检查是否应该触发补全\n if (!shouldTriggerCompletion(view.state, { ...options, minTriggerLength })) {\n return;\n }\n // 触发新的补全请求\n debouncedRequest(from);\n };\n\n // 使用 ProseMirror 的 update 监听文档和选择变化\n return {\n update: (view, prevState) => {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return;\n\n // 检查是否有 active suggestion\n if (pluginState.activeSuggestion) {\n const { from } = view.state.selection;\n const selectionChanged = view.state.selection.eq(prevState.selection) === false;\n const docChanged = view.state.doc !== prevState.doc;\n\n // 如果有补全,检查是否需要取消\n if (docChanged || selectionChanged) {\n // 取消当前补全\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"cancel\",\n })\n );\n }\n }\n\n // 检查是否应该触发新的补全\n if (view.state.doc !== prevState.doc) {\n handleInput(view.state.selection.from);\n }\n },\n destroy: () => {\n // 清理\n const pluginState = completionPluginKey.getState(view.state);\n if (pluginState?.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState?.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n },\n };\n },\n });\n}\n"],"names":["completionMetaKey","completionPluginKey","PluginKey","createGhostDecoration","pos","result","options","dom","text","parseCompletionResult","Decoration","tmp","createCompletionFragment","state","emptyDecorations","DecorationSet","updateGhostDecoration","pluginState","deco","clearDecorations","getCompletionHTML","getCompletionNode","parseHTMLToFragment","html","nodes","body","i","child","parsed","parseElement","element","_a","parseInlineContent","_b","_c","_d","insertCompletion","node","tr","insertedSize","fragment","newPos","TextSelection","cancelCompletion","hasActiveCompletion","getActiveSuggestion","handleKeyDown","view","event","activeSuggestion","triggerPos","action","shouldCancelCompletion","from","to","getTextBeforeCursor","$pos","offset","childText","getTextAfterCursor","started","defaultGetPromptType","context","beforeText","shouldTriggerCompletion","minLength","debounce","fn","ms","timer","args","createCompletionPlugin","debounceMs","minTriggerLength","Plugin","editorView","debouncedRequest","lastAbortController","abortController","parent","afterText","promptType","error","currentState","handleInput","prevState","selectionChanged"],"mappings":"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"}
package/index.umd.js ADDED
@@ -0,0 +1,3 @@
1
+ (function(s,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("prosemirror-state"),require("prosemirror-view")):typeof define=="function"&&define.amd?define(["exports","prosemirror-state","prosemirror-view"],g):(s=typeof globalThis<"u"?globalThis:s||self,g(s.ProseMirrorCompletion={},s.ProseMirrorState,s.ProseMirrorView))})(this,function(s,g,p){"use strict";const G="prosemirror-completion",d=new g.PluginKey("prosemirror-completion");function v(e,o,c){const n=document.createElement("span");n.className=c.ghostClassName??"prosemirror-ghost-text";const t=h(o);return n.textContent=t,p.Decoration.widget(e,n,{side:1,stopEvent:()=>!0,key:"ghost-completion"})}function h(e){if(typeof e=="string")return e;if("plain"in e&&e.plain!==void 0)return e.plain;if("html"in e&&e.html!==void 0){const o=document.createElement("div");return o.innerHTML=e.html,o.textContent??""}return"prosemirror"in e&&e.prosemirror?e.prosemirror.textContent??"":""}function z(e,o){return typeof e=="string"?{text:e}:"prosemirror"in e?{text:e.prosemirror.textContent??"",node:e.prosemirror}:"plain"in e&&"html"in e?{text:e.plain,html:e.html}:"html"in e?{text:h(e),html:e.html}:"plain"in e?{text:e.plain}:{text:""}}function C(e){return e?p.DecorationSet.create(e.doc,[]):p.DecorationSet.empty}function D(e,o,c,n,t){if(!t.showGhost||!n||!e)return C(e);const r=v(c,n,t);return p.DecorationSet.create(e.doc,[r])}function T(e){return C(e)}function E(e){if(typeof e!="string"&&"html"in e&&e.html!==void 0)return e.html}function q(e){if(typeof e!="string"&&"prosemirror"in e&&e.prosemirror)return e.prosemirror}function F(e,o){const c=new DOMParser().parseFromString(e,"text/html");try{const n=[],t=c.body;for(let r=0;r<t.childNodes.length;r++){const a=t.childNodes[r];if(a.nodeType===3){const i=a.textContent??"";i&&n.push(o.schema.text(i))}else if(a.nodeType===1){const l=L(a,o);l&&n.push(l)}}return n.length===0?null:o.schema.nodes.doc.create(null,n)}catch{return null}}function L(e,o){var n,t,r,a;switch(e.tagName.toLowerCase()){case"p":return(n=o.schema.nodes.paragraph)==null?void 0:n.create(null,O(e,o));case"strong":case"b":return o.schema.text(e.textContent??"",[(t=o.schema.marks.strong)==null?void 0:t.create()]);case"em":case"i":return o.schema.text(e.textContent??"",[(r=o.schema.marks.em)==null?void 0:r.create()]);case"code":return o.schema.text(e.textContent??"",[(a=o.schema.marks.code)==null?void 0:a.create()]);case"br":return o.schema.text(`
2
+ `);default:return o.schema.text(e.textContent??"")}}function O(e,o){const c=[];for(let n=0;n<e.childNodes.length;n++){const t=e.childNodes[n];if(t.nodeType===3){const r=t.textContent??"";r&&c.push(o.schema.text(r))}else if(t.nodeType===1){const r=L(t,o);r&&c.push(r)}}return c}function k(e,o){const c=d.getState(e);if(!c||c.triggerPos===null)return e.tr;const n=q(o),t=E(o),r=h(o),a=e.selection.from;let i,l=0;if(n)i=e.tr.insert(a,n.content),l=i.doc.content.size-e.doc.content.size;else if(t){const m=F(t,e);m&&m.childCount>0?(i=e.tr.insert(a,m.content),l=i.doc.content.size-e.doc.content.size):(i=e.tr.insertText(r,a),l=r.length)}else i=e.tr.insertText(r,a),l=r.length;const u=a+l;return i.setSelection(g.TextSelection.create(i.doc,u)),i.setMeta("prosemirror-completion",{type:"apply"}),i}function $(e){const o=e.tr;return o.setMeta("prosemirror-completion",{type:"cancel"}),o}function H(e){const o=d.getState(e);return!!(o!=null&&o.activeSuggestion&&(o==null?void 0:o.triggerPos)!==null)}function R(e){const o=d.getState(e);return(o==null?void 0:o.activeSuggestion)??null}function N(e,o,c){const{activeSuggestion:n,triggerPos:t}=c;if(!n||t===null)return!1;if(o.key==="Tab"&&!o.shiftKey){o.preventDefault();const r=k(e.state,n);return e.dispatch(r),!0}if(o.key==="Escape"){o.preventDefault();const r={type:"cancel"};return e.dispatch(e.state.tr.setMeta("prosemirror-completion",r)),!0}return!1}function j(e,o){const{activeSuggestion:c,triggerPos:n}=o;if(!c||n===null)return!1;const{from:t,to:r}=e.state.selection;return t<n}function y(e,o){const c=e.doc.resolve(o),n=c.parent;let t="",r=c.parentOffset;for(let a=0;a<n.childCount;a++){const i=n.child(a);if(i.isText){const l=i.text??"";if(r<=l.length){t+=l.slice(0,r);break}else t+=l,r-=l.length}else if(t+=" ",r-=1,r<=0)break}return t}function S(e,o){const c=e.doc.resolve(o),n=c.parent;let t="",r=c.parentOffset,a=!1;for(let i=0;i<n.childCount;i++){const l=n.child(i);if(l.isText){const u=l.text??"";a?t+=u:r<u.length?(t+=u.slice(r),a=!0):r-=u.length}else a||r<=0?(t+=" ",a=!0):r-=1}return t}function K(e){const{beforeText:o}=e;return o.includes("```")||/^\s*(function|const|let|var|class|import|export|if|for|while)/.test(o.slice(-50))?"code":/^\s*(#{1,6}\s|>|\*|\d+\.|\[|!\[)/.test(o.slice(-20))||o.includes("**")||o.includes("__")?"markdown":"common"}function A(e,o){const{from:c}=e.selection,n=y(e,c),t=o.minTriggerLength??3;return(n.split(/[\s\n]+/).pop()??"").length>=t}function B(e,o){let c=null;return(...n)=>{c&&clearTimeout(c),c=setTimeout(()=>{e(...n),c=null},o)}}function I(e){const o=e.debounceMs??300,c=e.minTriggerLength??3;return new g.Plugin({key:d,state:{init(){return{activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:C(null),debounceTimer:null,options:e}},apply(n,t){const r=n.getMeta("prosemirror-completion");if(r)switch(r.type){case"suggest":return{...t,activeSuggestion:r.result,triggerPos:r.pos,isLoading:!1,abortController:null,decorations:D({doc:n.doc},t,r.pos,r.result,e),debounceTimer:null};case"apply":return{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:T({doc:n.doc}),debounceTimer:null};case"cancel":return t.abortController&&t.abortController.abort(),t.debounceTimer&&clearTimeout(t.debounceTimer),{...t,activeSuggestion:null,triggerPos:null,isLoading:!1,abortController:null,decorations:T({doc:n.doc}),debounceTimer:null};case"loading":return{...t,isLoading:r.isLoading}}return!t.activeSuggestion||t.triggerPos===null?{...t,decorations:t.decorations.map(n.mapping,n.doc)}:{...t,decorations:t.decorations.map(n.mapping,n.doc)}}},props:{decorations(n){var t;return((t=this.getState(n))==null?void 0:t.decorations)??null},handleKeyDown(n,t){const r=d.getState(n.state);return r?N(n,t,r):!1}},view(n){const t=n,r=(()=>{let i=null,l=null;return u=>{i&&clearTimeout(i),l&&l.abort(),i=setTimeout(async()=>{const f=new AbortController;l=f,t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!0}));try{const x=t.state.doc.resolve(u).parent,P=y(t.state,u),M=S(t.state,u),W=e.getPromptType?e.getPromptType({abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:"common",state:t.state}):K({abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:"common",state:t.state}),w={abortController:f,parent:x,pos:u,beforeText:P,afterText:M,promptType:W,state:t.state},_=await Promise.resolve(e.callCompletion(w));if(f.signal.aborted)return;t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"suggest",result:_,pos:u})),e.onChange&&e.onChange(w,t)}catch(b){if(f.signal.aborted)return;console.error("Completion error:",b)}finally{t.dispatch(t.state.tr.setMeta("prosemirror-completion",{type:"loading",isLoading:!1}))}i=null,l=null},o);const m=d.getState(t.state);m&&m.debounceTimer&&clearTimeout(m.debounceTimer)}})(),a=i=>{A(t.state,{...e,minTriggerLength:c})&&r(i)};return{update:(i,l)=>{const u=d.getState(i.state);if(u){if(u.activeSuggestion){const{from:m}=i.state.selection,f=i.state.selection.eq(l.selection)===!1;(i.state.doc!==l.doc||f)&&i.dispatch(i.state.tr.setMeta("prosemirror-completion",{type:"cancel"}))}i.state.doc!==l.doc&&a(i.state.selection.from)}},destroy:()=>{const i=d.getState(t.state);i!=null&&i.abortController&&i.abortController.abort(),i!=null&&i.debounceTimer&&clearTimeout(i.debounceTimer)}}}})}s.cancelCompletion=$,s.clearDecorations=T,s.completionMetaKey=G,s.completionPluginKey=d,s.createCompletionFragment=z,s.createCompletionPlugin=I,s.createGhostDecoration=v,s.debounce=B,s.defaultGetPromptType=K,s.emptyDecorations=C,s.getActiveSuggestion=R,s.getTextAfterCursor=S,s.getTextBeforeCursor=y,s.handleKeyDown=N,s.hasActiveCompletion=H,s.insertCompletion=k,s.parseCompletionResult=h,s.shouldCancelCompletion=j,s.shouldTriggerCompletion=A,s.updateGhostDecoration=D,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
3
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/types.ts","../src/decorations.ts","../src/commands.ts","../src/keymap.ts","../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import type { Node } from \"prosemirror-model\";\nimport type { EditorState, Transaction } from \"prosemirror-state\";\nimport type { EditorView, DecorationSet } from \"prosemirror-view\";\nimport { PluginKey } from \"prosemirror-state\";\n\n/**\n * 补全返回结果格式\n */\nexport type CompletionResult =\n | string\n | { plain: string; html?: string }\n | { html: string }\n | { prosemirror: Node };\n\n/**\n * Prompt 类型建议\n */\nexport type PromptType = \"common\" | \"code\" | \"markdown\" | string;\n\n/**\n * 补全上下文\n */\nexport interface CompletionContext {\n /** 用于取消请求的 AbortController */\n abortController: AbortController;\n /** 当前位置的父节点 */\n parent: Node;\n /** 当前位置 */\n pos: number;\n /** 光标前的文本 */\n beforeText: string;\n /** 光标后的文本 */\n afterText: string;\n /** 建议的 prompt 类型 */\n promptType: PromptType;\n /** 编辑器状态 */\n state: EditorState;\n}\n\n/**\n * 补全插件配置选项\n */\nexport interface CompletionOptions {\n /**\n * 防抖时间(毫秒)\n * @default 300\n */\n debounceMs?: number;\n\n /**\n * 触发补全的最小字符数\n * @default 3\n */\n minTriggerLength?: number;\n\n /**\n * 补全函数 - 接收上下文,返回补全结果\n */\n callCompletion: (\n context: CompletionContext,\n ) => Promise<CompletionResult> | CompletionResult;\n\n /**\n * 确定当前上下文适合的 prompt 类型\n */\n getPromptType?: (context: CompletionContext) => PromptType;\n\n /**\n * 用户打字时的回调\n */\n onChange?: (context: CompletionContext, view: EditorView) => void;\n\n /**\n * 补全退出时的回调(按 Esc 或点击别处)\n */\n onExit?: (view: EditorView) => void;\n\n /**\n * 补全应用时的回调(按 Tab)\n */\n onApply?: (result: CompletionResult, view: EditorView) => void;\n\n /**\n * 自定义 ghost text 的 CSS 类名\n * @default \"prosemirror-ghost-text\"\n */\n ghostClassName?: string;\n\n /**\n * 是否显示 ghost text\n * @default true\n */\n showGhost?: boolean;\n}\n\n/**\n * 内部插件状态\n */\nexport interface CompletionPluginState {\n /** 当前激活的补全建议 */\n activeSuggestion: CompletionResult | null;\n /** 补全触发位置 */\n triggerPos: number | null;\n /** 是否正在加载 */\n isLoading: boolean;\n /** 当前 AbortController */\n abortController: AbortController | null;\n /** DecorationSet */\n decorations: DecorationSet;\n /** 防抖计时器 */\n debounceTimer: ReturnType<typeof setTimeout> | null;\n /** 插件配置 */\n options: CompletionOptions;\n}\n\n/**\n * Transaction meta key\n */\nexport const completionMetaKey = \"prosemirror-completion\";\n\n/**\n * 插件 Key\n */\nexport const completionPluginKey = new PluginKey<CompletionPluginState>(\n \"prosemirror-completion\",\n);\n\n/**\n * Action 类型\n */\nexport type CompletionAction =\n | { type: \"start\"; pos: number }\n | { type: \"suggest\"; result: CompletionResult; pos: number }\n | { type: \"apply\" }\n | { type: \"cancel\" }\n | { type: \"loading\"; isLoading: boolean };\n","import { Decoration, DecorationSet } from \"prosemirror-view\";\nimport type { EditorState } from \"prosemirror-state\";\nimport type { Node } from \"prosemirror-model\";\nimport type {\n CompletionOptions,\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\n\n/**\n * 创建 ghost text 的 decoration\n */\nexport function createGhostDecoration(\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): Decoration {\n const dom = document.createElement(\"span\");\n dom.className = options.ghostClassName ?? \"prosemirror-ghost-text\";\n\n // 解析补全结果\n const text = parseCompletionResult(result);\n dom.textContent = text;\n\n // side: 1 确保它在光标后,stopEvent 阻止用户点击灰字导致光标偏移\n return Decoration.widget(pos, dom, {\n side: 1,\n stopEvent: () => true,\n key: \"ghost-completion\",\n });\n}\n\n/**\n * 解析补全结果为纯文本\n */\nexport function parseCompletionResult(result: CompletionResult): string {\n if (typeof result === \"string\") {\n return result;\n }\n if (\"plain\" in result && result.plain !== undefined) {\n return result.plain;\n }\n if (\"html\" in result && result.html !== undefined) {\n // 简单去除 HTML 标签获取纯文本\n const tmp = document.createElement(\"div\");\n tmp.innerHTML = result.html;\n return tmp.textContent ?? \"\";\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n // 从 ProseMirror Node 提取文本内容\n return result.prosemirror.textContent ?? \"\";\n }\n return \"\";\n}\n\n/**\n * 将补全结果转换为 ProseMirror 可以插入的片段\n */\nexport function createCompletionFragment(\n result: CompletionResult,\n state: EditorState\n): { text: string; html?: string; node?: Node } {\n if (typeof result === \"string\") {\n return { text: result };\n }\n if (\"prosemirror\" in result) {\n return { text: result.prosemirror.textContent ?? \"\", node: result.prosemirror };\n }\n if (\"plain\" in result && \"html\" in result) {\n return { text: result.plain, html: result.html };\n }\n if (\"html\" in result) {\n return {\n text: parseCompletionResult(result),\n html: result.html,\n };\n }\n if (\"plain\" in result) {\n return { text: result.plain };\n }\n return { text: \"\" };\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function emptyDecorations(state: EditorState | null): DecorationSet {\n if (!state) {\n return DecorationSet.empty;\n }\n return DecorationSet.create(state.doc, []);\n}\n\n/**\n * 更新 DecorationSet,添加 ghost decoration\n */\nexport function updateGhostDecoration(\n state: EditorState | null,\n pluginState: CompletionPluginState,\n pos: number,\n result: CompletionResult,\n options: CompletionOptions\n): DecorationSet {\n if (!options.showGhost || !result || !state) {\n return emptyDecorations(state);\n }\n\n const deco = createGhostDecoration(pos, result, options);\n return DecorationSet.create(state.doc, [deco]);\n}\n\n/**\n * 创建空的 DecorationSet\n */\nexport function clearDecorations(state: EditorState | null): DecorationSet {\n return emptyDecorations(state);\n}\n","import type { EditorState, Transaction } from \"prosemirror-state\";\nimport { TextSelection } from \"prosemirror-state\";\nimport { Node as PMNode } from \"prosemirror-model\";\nimport type {\n CompletionResult,\n CompletionPluginState,\n} from \"./types\";\nimport { completionPluginKey } from \"./types\";\nimport { parseCompletionResult } from \"./decorations\";\n\n/**\n * 获取补全结果的 HTML 内容\n */\nfunction getCompletionHTML(result: CompletionResult): string | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"html\" in result && result.html !== undefined) {\n return result.html;\n }\n return undefined;\n}\n\n/**\n * 获取补全结果的 ProseMirror Node\n */\nfunction getCompletionNode(result: CompletionResult): PMNode | undefined {\n if (typeof result === \"string\") {\n return undefined;\n }\n if (\"prosemirror\" in result && result.prosemirror) {\n return result.prosemirror;\n }\n return undefined;\n}\n\n/**\n * 将 HTML 字符串解析为 ProseMirror 文档片段\n */\nfunction parseHTMLToFragment(\n html: string,\n state: EditorState\n): PMNode | null {\n // 使用 DOMParser 将 HTML 解析为 DOM\n const dom = new DOMParser().parseFromString(html, \"text/html\");\n\n try {\n const nodes: PMNode[] = [];\n const body = dom.body;\n\n for (let i = 0; i < body.childNodes.length; i++) {\n const child = body.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const element = child as HTMLElement;\n const parsed = parseElement(element, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n if (nodes.length === 0) {\n return null;\n }\n\n // 创建文档片段\n return state.schema.nodes.doc.create(null, nodes);\n } catch {\n return null;\n }\n}\n\n/**\n * 解析单个 HTML 元素为 ProseMirror 节点\n */\nfunction parseElement(element: HTMLElement, state: EditorState): PMNode | null {\n const tagName = element.tagName.toLowerCase();\n\n // 处理常见标签\n switch (tagName) {\n case \"p\":\n return state.schema.nodes.paragraph?.create(\n null,\n parseInlineContent(element, state)\n );\n case \"strong\":\n case \"b\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.strong?.create()]\n );\n case \"em\":\n case \"i\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.em?.create()]\n );\n case \"code\":\n return state.schema.text(\n element.textContent ?? \"\",\n [state.schema.marks.code?.create()]\n );\n case \"br\":\n return state.schema.text(\"\\n\");\n default:\n // 对于未知标签,尝试解析其文本内容\n return state.schema.text(element.textContent ?? \"\");\n }\n}\n\n/**\n * 解析内联内容\n */\nfunction parseInlineContent(element: HTMLElement, state: EditorState): PMNode[] {\n const nodes: PMNode[] = [];\n\n for (let i = 0; i < element.childNodes.length; i++) {\n const child = element.childNodes[i];\n if (child.nodeType === 3) { // TEXT_NODE\n const text = child.textContent ?? \"\";\n if (text) {\n nodes.push(state.schema.text(text));\n }\n } else if (child.nodeType === 1) { // ELEMENT_NODE\n const parsed = parseElement(child as HTMLElement, state);\n if (parsed) {\n nodes.push(parsed);\n }\n }\n }\n\n return nodes;\n}\n\n/**\n * 插入补全内容 - 支持 HTML 直接渲染和 ProseMirror Node\n */\nexport function insertCompletion(\n state: EditorState,\n result: CompletionResult\n): Transaction {\n const pluginState = completionPluginKey.getState(state) as\n | CompletionPluginState\n | undefined;\n\n if (!pluginState || pluginState.triggerPos === null) {\n return state.tr;\n }\n\n const node = getCompletionNode(result);\n const html = getCompletionHTML(result);\n const text = parseCompletionResult(result);\n const pos = state.selection.from;\n\n let tr: Transaction;\n let insertedSize = 0;\n\n // 优先处理 prosemirror 类型\n if (node) {\n // 直接插入 ProseMirror Node\n tr = state.tr.insert(pos, node.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else if (html) {\n // 如果有 HTML 内容,尝试插入富文本\n const fragment = parseHTMLToFragment(html, state);\n if (fragment && fragment.childCount > 0) {\n tr = state.tr.insert(pos, fragment.content);\n insertedSize = tr.doc.content.size - state.doc.content.size;\n } else {\n // 回退到纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n } else {\n // 纯文本插入\n tr = state.tr.insertText(text, pos);\n insertedSize = text.length;\n }\n\n // 将光标移到插入内容之后\n const newPos = pos + insertedSize;\n tr.setSelection(TextSelection.create(tr.doc, newPos));\n\n // 添加 meta 标记已应用\n tr.setMeta(\"prosemirror-completion\", { type: \"apply\" });\n\n return tr;\n}\n\n/**\n * 取消补全\n */\nexport function cancelCompletion(state: EditorState): Transaction {\n const tr = state.tr;\n tr.setMeta(\"prosemirror-completion\", { type: \"cancel\" });\n return tr;\n}\n\n/**\n * 检查是否有活动的补全\n */\nexport function hasActiveCompletion(state: EditorState): boolean {\n const pluginState = completionPluginKey.getState(state);\n return !!(\n pluginState?.activeSuggestion && pluginState?.triggerPos !== null\n );\n}\n\n/**\n * 获取当前补全建议\n */\nexport function getActiveSuggestion(\n state: EditorState\n): CompletionResult | null {\n const pluginState = completionPluginKey.getState(state);\n return pluginState?.activeSuggestion ?? null;\n}\n","import type { EditorView } from \"prosemirror-view\";\nimport type { CompletionPluginState, CompletionAction } from \"./types\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 键盘事件处理器\n * 处理 Tab(应用)、Esc(取消)、和其他按键\n */\nexport function handleKeyDown(\n view: EditorView,\n event: KeyboardEvent,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n // 没有激活的补全时,不拦截\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n // Tab: 应用补全\n if (event.key === \"Tab\" && !event.shiftKey) {\n event.preventDefault();\n // 使用 insertCompletion 实际插入文本\n const tr = insertCompletion(view.state, activeSuggestion);\n view.dispatch(tr);\n return true;\n }\n\n // Escape: 取消补全\n if (event.key === \"Escape\") {\n event.preventDefault();\n const action: CompletionAction = { type: \"cancel\" };\n view.dispatch(view.state.tr.setMeta(\"prosemirror-completion\", action));\n return true;\n }\n\n // 其他按键:更新状态,让 state.apply 处理\n return false;\n}\n\n/**\n * 检查是否需要取消补全(光标移出触发区域等)\n */\nexport function shouldCancelCompletion(\n view: EditorView,\n pluginState: CompletionPluginState\n): boolean {\n const { activeSuggestion, triggerPos } = pluginState;\n\n if (!activeSuggestion || triggerPos === null) {\n return false;\n }\n\n const { from, to } = view.state.selection;\n\n // 如果光标不在触发位置之后,取消补全\n if (from < triggerPos) {\n return true;\n }\n\n return false;\n}\n","import type { EditorState } from \"prosemirror-state\";\nimport type {\n CompletionContext,\n CompletionOptions,\n PromptType,\n} from \"./types\";\n\n/**\n * 获取光标前的文本内容\n */\nexport function getTextBeforeCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n // 获取从节点开始到光标位置的文本\n let text = \"\";\n let offset = $pos.parentOffset;\n\n // 遍历子节点收集文本\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (offset <= childText.length) {\n text += childText.slice(0, offset);\n break;\n } else {\n text += childText;\n offset -= childText.length;\n }\n } else {\n // 非文本节点,记录为空格或换行\n text += \" \";\n offset -= 1;\n if (offset <= 0) break;\n }\n }\n\n return text;\n}\n\n/**\n * 获取光标后的文本内容\n */\nexport function getTextAfterCursor(state: EditorState, pos: number): string {\n const $pos = state.doc.resolve(pos);\n const node = $pos.parent;\n\n let text = \"\";\n let offset = $pos.parentOffset;\n let started = false;\n\n for (let i = 0; i < node.childCount; i++) {\n const child = node.child(i);\n if (child.isText) {\n const childText = child.text ?? \"\";\n if (!started) {\n if (offset < childText.length) {\n text += childText.slice(offset);\n started = true;\n } else {\n offset -= childText.length;\n }\n } else {\n text += childText;\n }\n } else {\n if (started || offset <= 0) {\n text += \" \";\n started = true;\n } else {\n offset -= 1;\n }\n }\n }\n\n return text;\n}\n\n/**\n * 默认的 prompt 类型检测\n */\nexport function defaultGetPromptType(context: CompletionContext): PromptType {\n const { beforeText } = context;\n\n // 检测代码块\n if (\n beforeText.includes(\"```\") ||\n /^\\s*(function|const|let|var|class|import|export|if|for|while)/.test(\n beforeText.slice(-50)\n )\n ) {\n return \"code\";\n }\n\n // 检测 markdown 特征\n if (\n /^\\s*(#{1,6}\\s|>|\\*|\\d+\\.|\\[|!\\[)/.test(beforeText.slice(-20)) ||\n beforeText.includes(\"**\") ||\n beforeText.includes(\"__\")\n ) {\n return \"markdown\";\n }\n\n return \"common\";\n}\n\n/**\n * 检查是否应该触发补全\n */\nexport function shouldTriggerCompletion(\n state: EditorState,\n options: CompletionOptions\n): boolean {\n const { from } = state.selection;\n const beforeText = getTextBeforeCursor(state, from);\n\n // 检查最小触发长度\n const minLength = options.minTriggerLength ?? 3;\n\n // 获取最后一个\"词\"的长度(以空格或标点分隔)\n const lastWord = beforeText.split(/[\\s\\n]+/).pop() ?? \"\";\n\n return lastWord.length >= minLength;\n}\n\n/**\n * 防抖函数\n */\nexport function debounce<T extends (...args: unknown[]) => unknown>(\n fn: T,\n ms: number\n): (...args: Parameters<T>) => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n return (...args: Parameters<T>) => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n fn(...args);\n timer = null;\n }, ms);\n };\n}\n","import { Plugin } from \"prosemirror-state\";\nimport type { EditorView } from \"prosemirror-view\";\nimport type {\n CompletionOptions,\n CompletionPluginState,\n CompletionAction,\n} from \"./types\";\nimport {\n completionPluginKey,\n} from \"./types\";\nimport {\n emptyDecorations,\n clearDecorations,\n updateGhostDecoration,\n} from \"./decorations\";\nimport { handleKeyDown, shouldCancelCompletion } from \"./keymap\";\nimport {\n getTextBeforeCursor,\n getTextAfterCursor,\n defaultGetPromptType,\n shouldTriggerCompletion,\n} from \"./utils\";\nimport { insertCompletion } from \"./commands\";\n\n/**\n * 创建补全插件\n */\nexport function createCompletionPlugin(\n options: CompletionOptions\n): Plugin<CompletionPluginState> {\n const debounceMs = options.debounceMs ?? 300;\n const minTriggerLength = options.minTriggerLength ?? 3;\n\n return new Plugin<CompletionPluginState>({\n key: completionPluginKey,\n\n state: {\n init(): CompletionPluginState {\n return {\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: emptyDecorations(null),\n debounceTimer: null,\n options,\n };\n },\n\n apply(tr, pluginState): CompletionPluginState {\n const action = tr.getMeta(\n \"prosemirror-completion\"\n ) as CompletionAction | null;\n\n // 处理 action\n if (action) {\n switch (action.type) {\n case \"suggest\":\n return {\n ...pluginState,\n activeSuggestion: action.result,\n triggerPos: action.pos,\n isLoading: false,\n abortController: null,\n decorations: updateGhostDecoration(\n { doc: tr.doc } as import(\"prosemirror-state\").EditorState,\n pluginState,\n action.pos,\n action.result,\n options\n ),\n debounceTimer: null,\n };\n\n case \"apply\": {\n // 清理 decoration,补全内容已插入\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n }\n\n case \"cancel\":\n // 取消之前的请求\n if (pluginState.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n return {\n ...pluginState,\n activeSuggestion: null,\n triggerPos: null,\n isLoading: false,\n abortController: null,\n decorations: clearDecorations({ doc: tr.doc } as import(\"prosemirror-state\").EditorState),\n debounceTimer: null,\n };\n\n case \"loading\":\n return {\n ...pluginState,\n isLoading: action.isLoading,\n };\n }\n }\n\n // 如果没有 active suggestion,正常返回\n if (!pluginState.activeSuggestion || pluginState.triggerPos === null) {\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n }\n\n // 映射 decorations\n return {\n ...pluginState,\n decorations: pluginState.decorations.map(tr.mapping, tr.doc),\n };\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? null;\n },\n\n handleKeyDown(view: EditorView, event: KeyboardEvent): boolean {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return false;\n\n return handleKeyDown(view, event, pluginState);\n },\n },\n\n view(editorView) {\n const view = editorView;\n\n // 防抖处理补全请求\n const debouncedRequest = (() => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n let lastAbortController: AbortController | null = null;\n\n return (pos: number) => {\n if (timer) {\n clearTimeout(timer);\n }\n\n // 取消之前的请求\n if (lastAbortController) {\n lastAbortController.abort();\n }\n\n timer = setTimeout(async () => {\n const abortController = new AbortController();\n lastAbortController = abortController;\n\n // 更新 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: true,\n })\n );\n\n try {\n const $pos = view.state.doc.resolve(pos);\n const parent = $pos.parent;\n const beforeText = getTextBeforeCursor(view.state, pos);\n const afterText = getTextAfterCursor(view.state, pos);\n const promptType = options.getPromptType\n ? options.getPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n })\n : defaultGetPromptType({\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType: \"common\",\n state: view.state,\n });\n\n const context = {\n abortController,\n parent,\n pos,\n beforeText,\n afterText,\n promptType,\n state: view.state,\n };\n\n // 调用补全函数\n const result = await Promise.resolve(options.callCompletion(context));\n\n // 如果已经被取消,不更新\n if (abortController.signal.aborted) {\n return;\n }\n\n // 更新建议\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"suggest\",\n result,\n pos,\n })\n );\n\n // 调用 onChange 回调\n if (options.onChange) {\n options.onChange(context, view);\n }\n } catch (error) {\n if (abortController.signal.aborted) {\n return;\n }\n console.error(\"Completion error:\", error);\n } finally {\n // 清除 loading 状态\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"loading\",\n isLoading: false,\n })\n );\n }\n\n timer = null;\n lastAbortController = null;\n }, debounceMs);\n\n // 保存 timer 到插件状态\n const currentState = completionPluginKey.getState(view.state);\n if (currentState && currentState.debounceTimer) {\n clearTimeout(currentState.debounceTimer);\n }\n };\n })();\n\n // 监听文档和选择变化\n const handleInput = (from: number) => {\n // 检查是否应该触发补全\n if (!shouldTriggerCompletion(view.state, { ...options, minTriggerLength })) {\n return;\n }\n // 触发新的补全请求\n debouncedRequest(from);\n };\n\n // 使用 ProseMirror 的 update 监听文档和选择变化\n return {\n update: (view, prevState) => {\n const pluginState = completionPluginKey.getState(view.state);\n if (!pluginState) return;\n\n // 检查是否有 active suggestion\n if (pluginState.activeSuggestion) {\n const { from } = view.state.selection;\n const selectionChanged = view.state.selection.eq(prevState.selection) === false;\n const docChanged = view.state.doc !== prevState.doc;\n\n // 如果有补全,检查是否需要取消\n if (docChanged || selectionChanged) {\n // 取消当前补全\n view.dispatch(\n view.state.tr.setMeta(\"prosemirror-completion\", {\n type: \"cancel\",\n })\n );\n }\n }\n\n // 检查是否应该触发新的补全\n if (view.state.doc !== prevState.doc) {\n handleInput(view.state.selection.from);\n }\n },\n destroy: () => {\n // 清理\n const pluginState = completionPluginKey.getState(view.state);\n if (pluginState?.abortController) {\n pluginState.abortController.abort();\n }\n if (pluginState?.debounceTimer) {\n clearTimeout(pluginState.debounceTimer);\n }\n },\n };\n },\n });\n}\n"],"names":["completionMetaKey","completionPluginKey","PluginKey","createGhostDecoration","pos","result","options","dom","text","parseCompletionResult","Decoration","tmp","createCompletionFragment","state","emptyDecorations","DecorationSet","updateGhostDecoration","pluginState","deco","clearDecorations","getCompletionHTML","getCompletionNode","parseHTMLToFragment","html","nodes","body","i","child","parsed","parseElement","element","_a","parseInlineContent","_b","_c","_d","insertCompletion","node","tr","insertedSize","fragment","newPos","TextSelection","cancelCompletion","hasActiveCompletion","getActiveSuggestion","handleKeyDown","view","event","activeSuggestion","triggerPos","action","shouldCancelCompletion","from","to","getTextBeforeCursor","$pos","offset","childText","getTextAfterCursor","started","defaultGetPromptType","context","beforeText","shouldTriggerCompletion","minLength","debounce","fn","ms","timer","args","createCompletionPlugin","debounceMs","minTriggerLength","Plugin","editorView","debouncedRequest","lastAbortController","abortController","parent","afterText","promptType","error","currentState","handleInput","prevState","selectionChanged"],"mappings":"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"}
package/keymap.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { EditorView } from 'prosemirror-view';
2
+ import { CompletionPluginState } from './types';
3
+ /**
4
+ * 键盘事件处理器
5
+ * 处理 Tab(应用)、Esc(取消)、和其他按键
6
+ */
7
+ export declare function handleKeyDown(view: EditorView, event: KeyboardEvent, pluginState: CompletionPluginState): boolean;
8
+ /**
9
+ * 检查是否需要取消补全(光标移出触发区域等)
10
+ */
11
+ export declare function shouldCancelCompletion(view: EditorView, pluginState: CompletionPluginState): boolean;
12
+ //# sourceMappingURL=keymap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keymap.d.ts","sourceRoot":"","sources":["../src/keymap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAoB,MAAM,SAAS,CAAC;AAGvE;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,qBAAqB,GACjC,OAAO,CA2BT;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,UAAU,EAChB,WAAW,EAAE,qBAAqB,GACjC,OAAO,CAeT"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "prosemirror-completion",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./index.cjs",
6
+ "module": "./index.mjs",
7
+ "types": "./index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "import": "./index.mjs",
13
+ "require": "./index.cjs"
14
+ },
15
+ "./types": {
16
+ "types": "./types.d.ts",
17
+ "default": "./types.js"
18
+ }
19
+ },
20
+ "peerDependencies": {
21
+ "prosemirror-model": "^1.19.4",
22
+ "prosemirror-state": "^1.4.3",
23
+ "prosemirror-transform": "^1.7.3",
24
+ "prosemirror-view": "^1.33.3"
25
+ }
26
+ }
package/plugin.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import { CompletionOptions, CompletionPluginState } from './types';
3
+ /**
4
+ * 创建补全插件
5
+ */
6
+ export declare function createCompletionPlugin(options: CompletionOptions): Plugin<CompletionPluginState>;
7
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EAEtB,MAAM,SAAS,CAAC;AAkBjB;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,qBAAqB,CAAC,CAqR/B"}
package/prompts.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { CompletionContext, PromptType } from './types';
2
+ /**
3
+ * 通用文本补全 prompt
4
+ */
5
+ export declare function buildCommonPrompt(context: CompletionContext): string;
6
+ /**
7
+ * 代码补全 prompt
8
+ */
9
+ export declare function buildCodePrompt(context: CompletionContext): string;
10
+ /**
11
+ * Markdown 补全 prompt
12
+ */
13
+ export declare function buildMarkdownPrompt(context: CompletionContext): string;
14
+ /**
15
+ * JavaScript/TypeScript 代码补全
16
+ */
17
+ export declare function buildJavaScriptPrompt(context: CompletionContext): string;
18
+ /**
19
+ * Python 代码补全
20
+ */
21
+ export declare function buildPythonPrompt(context: CompletionContext): string;
22
+ /**
23
+ * 根据 prompt 类型选择对应的 builder
24
+ */
25
+ export declare function getPromptBuilder(type: PromptType): typeof buildCodePrompt;
26
+ /**
27
+ * 构建完整的 prompt
28
+ */
29
+ export declare function buildPrompt(context: CompletionContext, type?: PromptType): string;
30
+ /**
31
+ * 检测代码语言
32
+ */
33
+ export declare function detectLanguage(beforeText: string): string;
34
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE7D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMpE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CASlE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAQxE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAQpE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,0BAUhD;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,iBAAiB,EAC1B,IAAI,CAAC,EAAE,UAAU,GAChB,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA2BzD"}
package/types.d.ts ADDED
@@ -0,0 +1,128 @@
1
+ import { Node } from 'prosemirror-model';
2
+ import { EditorState, PluginKey } from 'prosemirror-state';
3
+ import { EditorView, DecorationSet } from 'prosemirror-view';
4
+ /**
5
+ * 补全返回结果格式
6
+ */
7
+ export type CompletionResult = string | {
8
+ plain: string;
9
+ html?: string;
10
+ } | {
11
+ html: string;
12
+ } | {
13
+ prosemirror: Node;
14
+ };
15
+ /**
16
+ * Prompt 类型建议
17
+ */
18
+ export type PromptType = "common" | "code" | "markdown" | string;
19
+ /**
20
+ * 补全上下文
21
+ */
22
+ export interface CompletionContext {
23
+ /** 用于取消请求的 AbortController */
24
+ abortController: AbortController;
25
+ /** 当前位置的父节点 */
26
+ parent: Node;
27
+ /** 当前位置 */
28
+ pos: number;
29
+ /** 光标前的文本 */
30
+ beforeText: string;
31
+ /** 光标后的文本 */
32
+ afterText: string;
33
+ /** 建议的 prompt 类型 */
34
+ promptType: PromptType;
35
+ /** 编辑器状态 */
36
+ state: EditorState;
37
+ }
38
+ /**
39
+ * 补全插件配置选项
40
+ */
41
+ export interface CompletionOptions {
42
+ /**
43
+ * 防抖时间(毫秒)
44
+ * @default 300
45
+ */
46
+ debounceMs?: number;
47
+ /**
48
+ * 触发补全的最小字符数
49
+ * @default 3
50
+ */
51
+ minTriggerLength?: number;
52
+ /**
53
+ * 补全函数 - 接收上下文,返回补全结果
54
+ */
55
+ callCompletion: (context: CompletionContext) => Promise<CompletionResult> | CompletionResult;
56
+ /**
57
+ * 确定当前上下文适合的 prompt 类型
58
+ */
59
+ getPromptType?: (context: CompletionContext) => PromptType;
60
+ /**
61
+ * 用户打字时的回调
62
+ */
63
+ onChange?: (context: CompletionContext, view: EditorView) => void;
64
+ /**
65
+ * 补全退出时的回调(按 Esc 或点击别处)
66
+ */
67
+ onExit?: (view: EditorView) => void;
68
+ /**
69
+ * 补全应用时的回调(按 Tab)
70
+ */
71
+ onApply?: (result: CompletionResult, view: EditorView) => void;
72
+ /**
73
+ * 自定义 ghost text 的 CSS 类名
74
+ * @default "prosemirror-ghost-text"
75
+ */
76
+ ghostClassName?: string;
77
+ /**
78
+ * 是否显示 ghost text
79
+ * @default true
80
+ */
81
+ showGhost?: boolean;
82
+ }
83
+ /**
84
+ * 内部插件状态
85
+ */
86
+ export interface CompletionPluginState {
87
+ /** 当前激活的补全建议 */
88
+ activeSuggestion: CompletionResult | null;
89
+ /** 补全触发位置 */
90
+ triggerPos: number | null;
91
+ /** 是否正在加载 */
92
+ isLoading: boolean;
93
+ /** 当前 AbortController */
94
+ abortController: AbortController | null;
95
+ /** DecorationSet */
96
+ decorations: DecorationSet;
97
+ /** 防抖计时器 */
98
+ debounceTimer: ReturnType<typeof setTimeout> | null;
99
+ /** 插件配置 */
100
+ options: CompletionOptions;
101
+ }
102
+ /**
103
+ * Transaction meta key
104
+ */
105
+ export declare const completionMetaKey = "prosemirror-completion";
106
+ /**
107
+ * 插件 Key
108
+ */
109
+ export declare const completionPluginKey: PluginKey<CompletionPluginState>;
110
+ /**
111
+ * Action 类型
112
+ */
113
+ export type CompletionAction = {
114
+ type: "start";
115
+ pos: number;
116
+ } | {
117
+ type: "suggest";
118
+ result: CompletionResult;
119
+ pos: number;
120
+ } | {
121
+ type: "apply";
122
+ } | {
123
+ type: "cancel";
124
+ } | {
125
+ type: "loading";
126
+ isLoading: boolean;
127
+ };
128
+ //# sourceMappingURL=types.d.ts.map
package/types.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/utils.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { EditorState } from 'prosemirror-state';
2
+ import { CompletionContext, CompletionOptions, PromptType } from './types';
3
+ /**
4
+ * 获取光标前的文本内容
5
+ */
6
+ export declare function getTextBeforeCursor(state: EditorState, pos: number): string;
7
+ /**
8
+ * 获取光标后的文本内容
9
+ */
10
+ export declare function getTextAfterCursor(state: EditorState, pos: number): string;
11
+ /**
12
+ * 默认的 prompt 类型检测
13
+ */
14
+ export declare function defaultGetPromptType(context: CompletionContext): PromptType;
15
+ /**
16
+ * 检查是否应该触发补全
17
+ */
18
+ export declare function shouldTriggerCompletion(state: EditorState, options: CompletionOptions): boolean;
19
+ /**
20
+ * 防抖函数
21
+ */
22
+ export declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): (...args: Parameters<T>) => void;
23
+ //# sourceMappingURL=utils.d.ts.map
package/utils.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACX,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CA6B3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAiC1E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAuB3E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAWT;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,EAAE,EAAE,CAAC,EACL,EAAE,EAAE,MAAM,GACT,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAYlC"}