From 55e7105a53da61b63f26ebdd01a832e28973f7e0 Mon Sep 17 00:00:00 2001 From: stream-pipe Date: Mon, 7 Jul 2025 20:51:42 +0800 Subject: [PATCH 1/5] feat: add props.root for Renderer --- .../core/feature-root_2025-07-07-12-51.json | 11 +++++++++++ .../react/feature-root_2025-07-07-12-51.json | 11 +++++++++++ packages/text-editor/core/src/editor.ts | 4 +++- packages/text-editor/react/src/renderer.tsx | 3 +++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 common/changes/@coze-editor/core/feature-root_2025-07-07-12-51.json create mode 100644 common/changes/@coze-editor/react/feature-root_2025-07-07-12-51.json diff --git a/common/changes/@coze-editor/core/feature-root_2025-07-07-12-51.json b/common/changes/@coze-editor/core/feature-root_2025-07-07-12-51.json new file mode 100644 index 00000000..d7e28686 --- /dev/null +++ b/common/changes/@coze-editor/core/feature-root_2025-07-07-12-51.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/core", + "comment": "add props.root for Renderer", + "type": "minor" + } + ], + "packageName": "@coze-editor/core", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/common/changes/@coze-editor/react/feature-root_2025-07-07-12-51.json b/common/changes/@coze-editor/react/feature-root_2025-07-07-12-51.json new file mode 100644 index 00000000..b9cca179 --- /dev/null +++ b/common/changes/@coze-editor/react/feature-root_2025-07-07-12-51.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/react", + "comment": "add props.root for Renderer", + "type": "minor" + } + ], + "packageName": "@coze-editor/react", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/packages/text-editor/core/src/editor.ts b/packages/text-editor/core/src/editor.ts index d37202f8..dc2d5a5e 100644 --- a/packages/text-editor/core/src/editor.ts +++ b/packages/text-editor/core/src/editor.ts @@ -29,6 +29,7 @@ disableEditContext(); // eslint-disable-next-line @typescript-eslint/no-unused-vars interface EditorOptions { parent: HTMLElement; + root?: Document | ShadowRoot; defaultValue?: string; options: T; extensions?: Extension[]; @@ -223,7 +224,7 @@ function create[]>({ } function render(opts: EditorOptions): API { - const { parent, defaultValue } = opts; + const { parent, root, defaultValue } = opts; let { extensions: userExtensions } = opts; if (!Array.isArray(userExtensions)) { @@ -240,6 +241,7 @@ function create[]>({ const view = new EditorView({ parent, + root, state: EditorState.create({ doc: defaultValue ?? '', extensions: [...extensions, ...userExtensions], diff --git a/packages/text-editor/react/src/renderer.tsx b/packages/text-editor/react/src/renderer.tsx index 70035b85..dd6e64e3 100644 --- a/packages/text-editor/react/src/renderer.tsx +++ b/packages/text-editor/react/src/renderer.tsx @@ -41,6 +41,7 @@ type InferRendererProps[]> = { className?: string; }; defaultValue?: string; + root?: Document | ShadowRoot; options?: Partial>; extensions?: Extension[]; didMount?: (api: InferEditorAPIFromPlugins) => void; @@ -55,6 +56,7 @@ function Renderer[]>( const { plugins, defaultValue, + root, options, domProps = {}, extensions, @@ -79,6 +81,7 @@ function Renderer[]>( const exported = render({ parent: ref.current!, + root, defaultValue, options: options ?? {}, extensions, From d8a55c606513e01cc2a40e4eae8ebc93ff0ce55c Mon Sep 17 00:00:00 2001 From: stream-pipe Date: Tue, 8 Jul 2025 15:48:57 +0800 Subject: [PATCH 2/5] fix: fix TypeScript type --- .../feature-root_2025-07-08-07-49.json | 11 +++++++++++ .../react-components/src/placeholder/index.tsx | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 common/changes/@coze-editor/react-components/feature-root_2025-07-08-07-49.json diff --git a/common/changes/@coze-editor/react-components/feature-root_2025-07-08-07-49.json b/common/changes/@coze-editor/react-components/feature-root_2025-07-08-07-49.json new file mode 100644 index 00000000..3ab43520 --- /dev/null +++ b/common/changes/@coze-editor/react-components/feature-root_2025-07-08-07-49.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/react-components", + "comment": "fix TypeScript type", + "type": "patch" + } + ], + "packageName": "@coze-editor/react-components", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/packages/text-editor/react-components/src/placeholder/index.tsx b/packages/text-editor/react-components/src/placeholder/index.tsx index 4e95f409..4b1e6a49 100644 --- a/packages/text-editor/react-components/src/placeholder/index.tsx +++ b/packages/text-editor/react-components/src/placeholder/index.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MIT import { createPortal } from 'react-dom'; -import { type ReactNode } from 'react'; +import { type ReactPortal, type ReactNode } from 'react'; import { useHTMLElement, useInjectorEffect } from '@coze-editor/react-hooks'; import { @@ -10,7 +10,7 @@ import { activeLinePlaceholder, } from '@coze-editor/extension-placeholder'; -function Placeholder({ children }: { children?: ReactNode }): ReactNode { +function Placeholder({ children }: { children?: ReactNode }): ReactPortal { const element = useHTMLElement('span'); useInjectorEffect( @@ -27,7 +27,7 @@ interface ActiveLinePlaceholderProps { function ActiveLinePlaceholder({ children, -}: ActiveLinePlaceholderProps): ReactNode { +}: ActiveLinePlaceholderProps): ReactPortal { const element = useHTMLElement('span'); useInjectorEffect( From f1df2baf5546e65a11bfe8dfa68a0c8dff29cc93 Mon Sep 17 00:00:00 2001 From: stream-pipe Date: Tue, 22 Jul 2025 22:55:41 +0800 Subject: [PATCH 3/5] feat: add onTrigger for Mention --- .../feature-root_2025-07-22-14-55.json | 11 +++++++++++ .../react-components/src/mention/extension.ts | 12 +++++++++++- .../react-components/src/mention/types.ts | 5 +++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 common/changes/@coze-editor/react-components/feature-root_2025-07-22-14-55.json diff --git a/common/changes/@coze-editor/react-components/feature-root_2025-07-22-14-55.json b/common/changes/@coze-editor/react-components/feature-root_2025-07-22-14-55.json new file mode 100644 index 00000000..6c9899ce --- /dev/null +++ b/common/changes/@coze-editor/react-components/feature-root_2025-07-22-14-55.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/react-components", + "comment": "add onTrigger for Mention", + "type": "minor" + } + ], + "packageName": "@coze-editor/react-components", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/packages/text-editor/react-components/src/mention/extension.ts b/packages/text-editor/react-components/src/mention/extension.ts index 9dbe1f1f..b34872b7 100644 --- a/packages/text-editor/react-components/src/mention/extension.ts +++ b/packages/text-editor/react-components/src/mention/extension.ts @@ -40,7 +40,7 @@ function mention(options: MentionOptions): Extension { }, update(value, tr) { const options = tr.startState.facet(mentionConfig); - const { search = true, onOpenChange, onSearch } = options; + const { search = true, onOpenChange, onSearch, onTrigger } = options; let { show } = value; @@ -65,6 +65,16 @@ function mention(options: MentionOptions): Extension { } else if (hasTrigger(options)) { triggerContext = options.trigger(tr); } + + if (triggerContext && typeof onTrigger === 'function') { + onTrigger({ + triggerContext: { + from: triggerContext.from, + to: triggerContext.to, + triggerCharacter: triggerContext.triggerCharacter, + }, + }); + } } const isSelectUserEvent = tr.isUserEvent('select'); diff --git a/packages/text-editor/react-components/src/mention/types.ts b/packages/text-editor/react-components/src/mention/types.ts index b35c4829..4cb8a9dd 100644 --- a/packages/text-editor/react-components/src/mention/types.ts +++ b/packages/text-editor/react-components/src/mention/types.ts @@ -15,6 +15,10 @@ interface MentionSearchEvent { value: string; } +interface MentionTriggerEvent { + triggerContext: Pick; +} + interface SharedMentionOptions { search?: | { @@ -28,6 +32,7 @@ interface SharedMentionOptions { | boolean; onOpenChange?: (e: MentionOpenChangeEvent) => void; onSearch?: (e: MentionSearchEvent) => void; + onTrigger?: (e: MentionTriggerEvent) => void; } type TriggerCharactersMentionOptions = SharedMentionOptions & { From 8e9d6cc35ac905c194a8d734dcce05ce0d274c93 Mon Sep 17 00:00:00 2001 From: stream-pipe Date: Tue, 22 Jul 2025 23:30:49 +0800 Subject: [PATCH 4/5] fix: onTrigger is not invoked as expected --- .../feature-root_2025-07-22-15-30.json | 11 +++++++++++ .../react-components/src/mention/react.tsx | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 common/changes/@coze-editor/react-components/feature-root_2025-07-22-15-30.json diff --git a/common/changes/@coze-editor/react-components/feature-root_2025-07-22-15-30.json b/common/changes/@coze-editor/react-components/feature-root_2025-07-22-15-30.json new file mode 100644 index 00000000..7a74223d --- /dev/null +++ b/common/changes/@coze-editor/react-components/feature-root_2025-07-22-15-30.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/react-components", + "comment": "onTrigger is not invoked as expected", + "type": "patch" + } + ], + "packageName": "@coze-editor/react-components", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/packages/text-editor/react-components/src/mention/react.tsx b/packages/text-editor/react-components/src/mention/react.tsx index 2a8c3081..3ede61e4 100644 --- a/packages/text-editor/react-components/src/mention/react.tsx +++ b/packages/text-editor/react-components/src/mention/react.tsx @@ -23,6 +23,11 @@ function Mention(props: MentionOptions) { return propsRef.current.onSearch(...args); } }, + onTrigger(...args) { + if (typeof propsRef.current.onTrigger === 'function') { + return propsRef.current.onTrigger(...args); + } + }, }; return injector.inject([ mention( From cb43bb2402a27367d12b550f4a349068b65564d4 Mon Sep 17 00:00:00 2001 From: stream-pipe Date: Fri, 25 Jul 2025 00:20:53 +0800 Subject: [PATCH 5/5] fix: fix sougou input issues --- .../feature-root_2025-07-24-16-21.json | 11 ++++ packages/text-editor/dev/src/index.tsx | 2 +- .../preset-prompt/src/fix-sougou-input.ts | 58 +++++++++++++++++++ .../text-editor/preset-prompt/src/index.ts | 3 +- 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 common/changes/@coze-editor/preset-prompt/feature-root_2025-07-24-16-21.json create mode 100644 packages/text-editor/preset-prompt/src/fix-sougou-input.ts diff --git a/common/changes/@coze-editor/preset-prompt/feature-root_2025-07-24-16-21.json b/common/changes/@coze-editor/preset-prompt/feature-root_2025-07-24-16-21.json new file mode 100644 index 00000000..437dbcf1 --- /dev/null +++ b/common/changes/@coze-editor/preset-prompt/feature-root_2025-07-24-16-21.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@coze-editor/preset-prompt", + "comment": "fix sougou input issues", + "type": "patch" + } + ], + "packageName": "@coze-editor/preset-prompt", + "email": "stream-pipe@users.noreply.github.com" +} diff --git a/packages/text-editor/dev/src/index.tsx b/packages/text-editor/dev/src/index.tsx index 8df952d3..0a27dddb 100644 --- a/packages/text-editor/dev/src/index.tsx +++ b/packages/text-editor/dev/src/index.tsx @@ -1,5 +1,5 @@ import { createRoot } from 'react-dom/client'; -import Page from './pages/diff'; +import Page from './pages/prompt'; import './index.css'; createRoot(document.getElementById('app')!).render( diff --git a/packages/text-editor/preset-prompt/src/fix-sougou-input.ts b/packages/text-editor/preset-prompt/src/fix-sougou-input.ts new file mode 100644 index 00000000..170b73bd --- /dev/null +++ b/packages/text-editor/preset-prompt/src/fix-sougou-input.ts @@ -0,0 +1,58 @@ +import { EditorState } from '@codemirror/state'; + +const pairs: Record = { + ':': ':', + ',': ',', +}; + +const fixSougouInput = EditorState.transactionFilter.of(tr => { + let allow = true; + tr.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => { + const insertChar = insert.toString(); + const originalChar = pairs[insertChar]; + + // how to repro with sougou input -> type 11:2 by order inside slot + // : is inserted, and {#/...#} is removed + // feature: + // - : is inserted after : + // - {#/...#} is replaced with : + if ( + originalChar && + // {#/ and #} have 5 chars + toA - fromA > 5 && + // :is replaced with : + tr.startState.sliceDoc(fromA - 1, fromA) === originalChar && + tr.startState.sliceDoc(fromA, fromA + 3) === '{#/' && + tr.startState.sliceDoc(toA - 2, toA) === '#}' + ) { + allow = false; + } + + // how to repro with sougou input -> type 1:2 by order inside slot + // 2 will appear after {#/...#} + // feature: + // - :is replaced with : + // - selection is moved more than one character + // - new selection is moved after slot {/#...#} + if ( + originalChar && + tr.isUserEvent('input.type') && + Math.abs(tr.newSelection.main.to - tr.startState.selection.main.to) > 5 && + tr.newSelection.main.to >= 2 && + tr.newDoc.sliceString( + tr.newSelection.main.to - 2, + tr.newSelection.main.to, + ) === '#}' + ) { + allow = false; + } + }); + + if (!allow) { + return []; + } + + return tr; +}); + +export { fixSougouInput }; diff --git a/packages/text-editor/preset-prompt/src/index.ts b/packages/text-editor/preset-prompt/src/index.ts index 5b38618c..5408d2b6 100644 --- a/packages/text-editor/preset-prompt/src/index.ts +++ b/packages/text-editor/preset-prompt/src/index.ts @@ -22,6 +22,7 @@ import { import { Prec } from '@codemirror/state'; import { languageSupport, markdownLanguage, promptLanguage } from './language'; +import { fixSougouInput } from './fix-sougou-input'; // const forEachFocusableWidget = ({ view }: { view: EditorView }) => { // return (callback: (from, to, widget) => void) => { @@ -52,7 +53,7 @@ const preset = [ api('undo', undo), api('redo', redo), api('transformTextInSelection', transformTextInSelection), - extension([Prec.high(focusableKeymap)]), + extension([Prec.high(focusableKeymap), fixSougouInput]), ]; type EditorAPI = InferEditorAPIFromPlugins;