From faf5b2406bb4cc299816bec9373de0f18408b98c Mon Sep 17 00:00:00 2001 From: jaywcjlove <398188662@qq.com> Date: Thu, 18 May 2023 21:21:11 +0800 Subject: [PATCH] feat(merge): add onChange props. (#502) --- merge/README.md | 4 ++++ merge/src/Modified.tsx | 25 ++++++++++++++++++++++--- merge/src/Original.tsx | 26 ++++++++++++++++++++++---- www/src/pages/home/index.tsx | 3 ++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/merge/README.md b/merge/README.md index 6d502e135..d9390d0ac 100644 --- a/merge/README.md +++ b/merge/README.md @@ -116,6 +116,8 @@ interface ModifiedProps { [Extension(s)](https://codemirror.net/6/docs/ref/#state.Extension) to associate with this state. */ extensions?: Extension; + /** Fired whenever a change occurs to the document. */ + onChange?(value: string, viewUpdate: ViewUpdate): void; } ``` @@ -146,6 +148,8 @@ interface OriginalProps { [Extension(s)](https://codemirror.net/6/docs/ref/#state.Extension) to associate with this state. */ extensions?: Extension; + /** Fired whenever a change occurs to the document. */ + onChange?(value: string, viewUpdate: ViewUpdate): void; } ``` diff --git a/merge/src/Modified.tsx b/merge/src/Modified.tsx index d0d46ff3b..7b9ff6fa4 100644 --- a/merge/src/Modified.tsx +++ b/merge/src/Modified.tsx @@ -1,19 +1,37 @@ import { useEffect } from 'react'; -import { EditorStateConfig, Extension, StateEffect } from '@codemirror/state'; +import { EditorStateConfig, Extension, StateEffect, Annotation } from '@codemirror/state'; import { getDefaultExtensions } from '@uiw/react-codemirror'; +import { EditorView, ViewUpdate } from '@codemirror/view'; import { useStore } from './store'; +const External = Annotation.define(); + export interface ModifiedProps extends Omit { value?: EditorStateConfig['doc']; extensions?: Extension[]; + /** Fired whenever a change occurs to the document. */ + onChange?(value: string, viewUpdate: ViewUpdate): void; } export const Modified = (props: ModifiedProps): JSX.Element | null => { - const { extensions = [] } = props; + const { extensions = [], onChange } = props; const { modified, view, dispatch } = useStore(); const defaultExtensions = getDefaultExtensions(); useEffect(() => { - const data: EditorStateConfig = { extensions: [...defaultExtensions, ...extensions] }; + const updateListener = EditorView.updateListener.of((vu: ViewUpdate) => { + if ( + vu.docChanged && + typeof onChange === 'function' && + // Fix echoing of the remote changes: + // If transaction is market as remote we don't have to call `onChange` handler again + !vu.transactions.some((tr) => tr.annotation(External)) + ) { + const doc = vu.state.doc; + const value = doc.toString(); + onChange(value, vu); + } + }); + const data: EditorStateConfig = { extensions: [updateListener, ...defaultExtensions, ...extensions] }; if (modified?.doc !== props.value && view) { data.doc = props.value; dispatch!({ modified: { ...modified, ...data } }); @@ -22,6 +40,7 @@ export const Modified = (props: ModifiedProps): JSX.Element | null => { view.b.dispatch({ changes: { from: 0, to: (modifiedDoc || '').length, insert: props.value || '' }, effects: StateEffect.appendConfig.of([...defaultExtensions, ...extensions]), + annotations: [External.of(true)], }); } } diff --git a/merge/src/Original.tsx b/merge/src/Original.tsx index 9e49b1852..77f547460 100644 --- a/merge/src/Original.tsx +++ b/merge/src/Original.tsx @@ -1,19 +1,37 @@ import { useEffect } from 'react'; -import { EditorStateConfig, Extension, StateEffect } from '@codemirror/state'; -import { useStore } from './store'; +import { EditorStateConfig, Extension, StateEffect, Annotation } from '@codemirror/state'; +import { EditorView, ViewUpdate } from '@codemirror/view'; import { getDefaultExtensions } from '@uiw/react-codemirror'; +import { useStore } from './store'; + +const External = Annotation.define(); export interface OriginalProps extends Omit { value?: EditorStateConfig['doc']; extensions?: Extension[]; + /** Fired whenever a change occurs to the document. */ + onChange?(value: string, viewUpdate: ViewUpdate): void; } export const Original = (props: OriginalProps): JSX.Element | null => { - const { extensions = [] } = props; + const { extensions = [], onChange } = props; const { original, view, dispatch } = useStore(); const defaultExtensions = getDefaultExtensions(); useEffect(() => { - const data: EditorStateConfig = { extensions: [...defaultExtensions, ...extensions] }; + const updateListener = EditorView.updateListener.of((vu: ViewUpdate) => { + if ( + vu.docChanged && + typeof onChange === 'function' && + // Fix echoing of the remote changes: + // If transaction is market as remote we don't have to call `onChange` handler again + !vu.transactions.some((tr) => tr.annotation(External)) + ) { + const doc = vu.state.doc; + const value = doc.toString(); + onChange(value, vu); + } + }); + const data: EditorStateConfig = { extensions: [updateListener, ...defaultExtensions, ...extensions] }; if (original?.doc !== props.value && view) { data.doc = props.value; dispatch!({ original: { ...original, ...data } }); diff --git a/www/src/pages/home/index.tsx b/www/src/pages/home/index.tsx index d489d4174..80b18d2fc 100644 --- a/www/src/pages/home/index.tsx +++ b/www/src/pages/home/index.tsx @@ -106,7 +106,7 @@ const hyperlink: { }[] = [ { href: 'https://www.npmjs.com/package/@uiw/react-codemirror', - label: 'View On NPM', + label: 'On NPM', }, { href: 'https://codemirror.net/docs/', @@ -150,6 +150,7 @@ export default function App() { Extensions + Merge {hyperlink.map(({ href, label, style }, idx) => { return (