Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@coze-editor/core",
"comment": "add props.root for Renderer",
"type": "minor"
}
],
"packageName": "@coze-editor/core",
"email": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@coze-editor/preset-prompt",
"comment": "fix sougou input issues",
"type": "patch"
}
],
"packageName": "@coze-editor/preset-prompt",
"email": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@coze-editor/react-components",
"comment": "fix TypeScript type",
"type": "patch"
}
],
"packageName": "@coze-editor/react-components",
"email": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@coze-editor/react-components",
"comment": "add onTrigger for Mention",
"type": "minor"
}
],
"packageName": "@coze-editor/react-components",
"email": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -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": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@coze-editor/react",
"comment": "add props.root for Renderer",
"type": "minor"
}
],
"packageName": "@coze-editor/react",
"email": "[email protected]"
}
4 changes: 3 additions & 1 deletion packages/text-editor/core/src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ disableEditContext();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface EditorOptions<T, U> {
parent: HTMLElement;
root?: Document | ShadowRoot;
defaultValue?: string;
options: T;
extensions?: Extension[];
Expand Down Expand Up @@ -223,7 +224,7 @@ function create<T extends EditorPluginSpec<string, any, any>[]>({
}

function render(opts: EditorOptions<Values, Events>): API {
const { parent, defaultValue } = opts;
const { parent, root, defaultValue } = opts;
let { extensions: userExtensions } = opts;

if (!Array.isArray(userExtensions)) {
Expand All @@ -240,6 +241,7 @@ function create<T extends EditorPluginSpec<string, any, any>[]>({

const view = new EditorView({
parent,
root,
state: EditorState.create({
doc: defaultValue ?? '',
extensions: [...extensions, ...userExtensions],
Expand Down
2 changes: 1 addition & 1 deletion packages/text-editor/dev/src/index.tsx
Original file line number Diff line number Diff line change
@@ -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(
Expand Down
58 changes: 58 additions & 0 deletions packages/text-editor/preset-prompt/src/fix-sougou-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EditorState } from '@codemirror/state';

const pairs: Record<string, string> = {
':': ':',
',': ',',
};

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 };
3 changes: 2 additions & 1 deletion packages/text-editor/preset-prompt/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<typeof preset>;
Expand Down
12 changes: 11 additions & 1 deletion packages/text-editor/react-components/src/mention/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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');
Expand Down
5 changes: 5 additions & 0 deletions packages/text-editor/react-components/src/mention/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions packages/text-editor/react-components/src/mention/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface MentionSearchEvent {
value: string;
}

interface MentionTriggerEvent {
triggerContext: Pick<TriggerContext, 'from' | 'to' | 'triggerCharacter'>;
}

interface SharedMentionOptions {
search?:
| {
Expand All @@ -28,6 +32,7 @@ interface SharedMentionOptions {
| boolean;
onOpenChange?: (e: MentionOpenChangeEvent) => void;
onSearch?: (e: MentionSearchEvent) => void;
onTrigger?: (e: MentionTriggerEvent) => void;
}

type TriggerCharactersMentionOptions = SharedMentionOptions & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
// 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 {
placeholder,
activeLinePlaceholder,
} from '@coze-editor/extension-placeholder';

function Placeholder({ children }: { children?: ReactNode }): ReactNode {
function Placeholder({ children }: { children?: ReactNode }): ReactPortal {
const element = useHTMLElement('span');

useInjectorEffect(
Expand All @@ -27,7 +27,7 @@ interface ActiveLinePlaceholderProps {

function ActiveLinePlaceholder({
children,
}: ActiveLinePlaceholderProps): ReactNode {
}: ActiveLinePlaceholderProps): ReactPortal {
const element = useHTMLElement('span');

useInjectorEffect(
Expand Down
3 changes: 3 additions & 0 deletions packages/text-editor/react/src/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type InferRendererProps<T extends EditorPluginSpec<string, any, any>[]> = {
className?: string;
};
defaultValue?: string;
root?: Document | ShadowRoot;
options?: Partial<InferValues<T[number]>>;
extensions?: Extension[];
didMount?: (api: InferEditorAPIFromPlugins<T>) => void;
Expand All @@ -55,6 +56,7 @@ function Renderer<T extends EditorPluginSpec<string, any, any>[]>(
const {
plugins,
defaultValue,
root,
options,
domProps = {},
extensions,
Expand All @@ -79,6 +81,7 @@ function Renderer<T extends EditorPluginSpec<string, any, any>[]>(

const exported = render({
parent: ref.current!,
root,
defaultValue,
options: options ?? {},
extensions,
Expand Down