Skip to content

Commit faf5b24

Browse files
committed
feat(merge): add onChange props. (#502)
1 parent d48bb95 commit faf5b24

File tree

4 files changed

+50
-8
lines changed

4 files changed

+50
-8
lines changed

merge/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ interface ModifiedProps {
116116
[Extension(s)](https://codemirror.net/6/docs/ref/#state.Extension) to associate with this state.
117117
*/
118118
extensions?: Extension;
119+
/** Fired whenever a change occurs to the document. */
120+
onChange?(value: string, viewUpdate: ViewUpdate): void;
119121
}
120122
```
121123

@@ -146,6 +148,8 @@ interface OriginalProps {
146148
[Extension(s)](https://codemirror.net/6/docs/ref/#state.Extension) to associate with this state.
147149
*/
148150
extensions?: Extension;
151+
/** Fired whenever a change occurs to the document. */
152+
onChange?(value: string, viewUpdate: ViewUpdate): void;
149153
}
150154
```
151155

merge/src/Modified.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
import { useEffect } from 'react';
2-
import { EditorStateConfig, Extension, StateEffect } from '@codemirror/state';
2+
import { EditorStateConfig, Extension, StateEffect, Annotation } from '@codemirror/state';
33
import { getDefaultExtensions } from '@uiw/react-codemirror';
4+
import { EditorView, ViewUpdate } from '@codemirror/view';
45
import { useStore } from './store';
56

7+
const External = Annotation.define<boolean>();
8+
69
export interface ModifiedProps extends Omit<EditorStateConfig, 'doc'> {
710
value?: EditorStateConfig['doc'];
811
extensions?: Extension[];
12+
/** Fired whenever a change occurs to the document. */
13+
onChange?(value: string, viewUpdate: ViewUpdate): void;
914
}
1015

1116
export const Modified = (props: ModifiedProps): JSX.Element | null => {
12-
const { extensions = [] } = props;
17+
const { extensions = [], onChange } = props;
1318
const { modified, view, dispatch } = useStore();
1419
const defaultExtensions = getDefaultExtensions();
1520
useEffect(() => {
16-
const data: EditorStateConfig = { extensions: [...defaultExtensions, ...extensions] };
21+
const updateListener = EditorView.updateListener.of((vu: ViewUpdate) => {
22+
if (
23+
vu.docChanged &&
24+
typeof onChange === 'function' &&
25+
// Fix echoing of the remote changes:
26+
// If transaction is market as remote we don't have to call `onChange` handler again
27+
!vu.transactions.some((tr) => tr.annotation(External))
28+
) {
29+
const doc = vu.state.doc;
30+
const value = doc.toString();
31+
onChange(value, vu);
32+
}
33+
});
34+
const data: EditorStateConfig = { extensions: [updateListener, ...defaultExtensions, ...extensions] };
1735
if (modified?.doc !== props.value && view) {
1836
data.doc = props.value;
1937
dispatch!({ modified: { ...modified, ...data } });
@@ -22,6 +40,7 @@ export const Modified = (props: ModifiedProps): JSX.Element | null => {
2240
view.b.dispatch({
2341
changes: { from: 0, to: (modifiedDoc || '').length, insert: props.value || '' },
2442
effects: StateEffect.appendConfig.of([...defaultExtensions, ...extensions]),
43+
annotations: [External.of(true)],
2544
});
2645
}
2746
}

merge/src/Original.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
import { useEffect } from 'react';
2-
import { EditorStateConfig, Extension, StateEffect } from '@codemirror/state';
3-
import { useStore } from './store';
2+
import { EditorStateConfig, Extension, StateEffect, Annotation } from '@codemirror/state';
3+
import { EditorView, ViewUpdate } from '@codemirror/view';
44
import { getDefaultExtensions } from '@uiw/react-codemirror';
5+
import { useStore } from './store';
6+
7+
const External = Annotation.define<boolean>();
58

69
export interface OriginalProps extends Omit<EditorStateConfig, 'doc'> {
710
value?: EditorStateConfig['doc'];
811
extensions?: Extension[];
12+
/** Fired whenever a change occurs to the document. */
13+
onChange?(value: string, viewUpdate: ViewUpdate): void;
914
}
1015

1116
export const Original = (props: OriginalProps): JSX.Element | null => {
12-
const { extensions = [] } = props;
17+
const { extensions = [], onChange } = props;
1318
const { original, view, dispatch } = useStore();
1419
const defaultExtensions = getDefaultExtensions();
1520
useEffect(() => {
16-
const data: EditorStateConfig = { extensions: [...defaultExtensions, ...extensions] };
21+
const updateListener = EditorView.updateListener.of((vu: ViewUpdate) => {
22+
if (
23+
vu.docChanged &&
24+
typeof onChange === 'function' &&
25+
// Fix echoing of the remote changes:
26+
// If transaction is market as remote we don't have to call `onChange` handler again
27+
!vu.transactions.some((tr) => tr.annotation(External))
28+
) {
29+
const doc = vu.state.doc;
30+
const value = doc.toString();
31+
onChange(value, vu);
32+
}
33+
});
34+
const data: EditorStateConfig = { extensions: [updateListener, ...defaultExtensions, ...extensions] };
1735
if (original?.doc !== props.value && view) {
1836
data.doc = props.value;
1937
dispatch!({ original: { ...original, ...data } });

www/src/pages/home/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const hyperlink: {
106106
}[] = [
107107
{
108108
href: 'https://www.npmjs.com/package/@uiw/react-codemirror',
109-
label: 'View On NPM',
109+
label: 'On NPM',
110110
},
111111
{
112112
href: 'https://codemirror.net/docs/',
@@ -150,6 +150,7 @@ export default function App() {
150150
<Link to="/extensions" className="extensions">
151151
Extensions
152152
</Link>
153+
<Link to="/merge/document">Merge</Link>
153154
{hyperlink.map(({ href, label, style }, idx) => {
154155
return (
155156
<a key={idx} target="_blank" rel="noopener noreferrer" href={href} style={style}>

0 commit comments

Comments
 (0)