@dxos/react-ui-editor 0.4.2-main.ec5936e → 0.4.2
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/dist/lib/browser/index.mjs +185 -233
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +0 -20
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +0 -20
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts +2 -22
- package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts +0 -20
- package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +10 -7
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +6 -23
- package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/{sync.d.ts → semaphore.d.ts} +5 -6
- package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/index.d.ts +0 -1
- package/dist/types/src/styles/index.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +16 -13
- package/src/components/TextEditor/TextEditor.stories.tsx +14 -11
- package/src/components/TextEditor/TextEditor.tsx +3 -3
- package/src/components/TextEditor/automerge.stories.tsx +44 -38
- package/src/components/TextEditor/hooks.stories.tsx +16 -15
- package/src/components/Toolbar/Toolbar.stories.tsx +12 -16
- package/src/components/Toolbar/Toolbar.tsx +129 -156
- package/src/extensions/automerge/automerge.ts +21 -5
- package/src/extensions/automerge/{sync.ts → semaphore.ts} +34 -32
- package/src/index.ts +1 -1
- package/src/styles/index.ts +0 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +0 -1
- package/dist/types/src/styles/layout.d.ts +0 -2
- package/dist/types/src/styles/layout.d.ts.map +0 -1
- package/dist/types/src/translations.d.ts +0 -20
- package/dist/types/src/translations.d.ts.map +0 -1
- package/src/styles/layout.ts +0 -6
- package/src/translations.ts +0 -25
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
type Icon,
|
|
7
|
+
CaretRight,
|
|
7
8
|
ChatText,
|
|
8
9
|
Code,
|
|
9
10
|
CodeBlock,
|
|
@@ -22,25 +23,14 @@ import {
|
|
|
22
23
|
TextHFive,
|
|
23
24
|
TextHSix,
|
|
24
25
|
TextItalic,
|
|
25
|
-
Quotes,
|
|
26
|
-
CaretDown,
|
|
27
26
|
} from '@phosphor-icons/react';
|
|
28
27
|
import { createContext } from '@radix-ui/react-context';
|
|
29
28
|
import React, { type PropsWithChildren } from 'react';
|
|
30
29
|
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
ElevationProvider,
|
|
34
|
-
Select,
|
|
35
|
-
type ThemedClassName,
|
|
36
|
-
Toolbar as NaturalToolbar,
|
|
37
|
-
type ToolbarToggleGroupItemProps,
|
|
38
|
-
useTranslation,
|
|
39
|
-
} from '@dxos/react-ui';
|
|
40
|
-
import { getSize } from '@dxos/react-ui-theme';
|
|
30
|
+
import { Button, type ButtonProps, DensityProvider, Select } from '@dxos/react-ui';
|
|
31
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
41
32
|
|
|
42
33
|
import { type Formatting } from '../../extensions';
|
|
43
|
-
import { translationKey } from '../../translations';
|
|
44
34
|
|
|
45
35
|
const HeadingIcons: { [key: string]: Icon } = {
|
|
46
36
|
'0': Paragraph,
|
|
@@ -76,49 +66,61 @@ export type Action = {
|
|
|
76
66
|
data?: any;
|
|
77
67
|
};
|
|
78
68
|
|
|
79
|
-
export type ToolbarProps =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}>
|
|
84
|
-
>;
|
|
69
|
+
export type ToolbarProps = PropsWithChildren<{
|
|
70
|
+
state: Formatting | null;
|
|
71
|
+
onAction?: (action: Action) => void;
|
|
72
|
+
}>;
|
|
85
73
|
|
|
86
74
|
const [ToolbarContextProvider, useToolbarContext] = createContext<ToolbarProps>('Toolbar');
|
|
87
75
|
|
|
88
|
-
const ToolbarRoot = ({ children, onAction,
|
|
76
|
+
const ToolbarRoot = ({ children, onAction, state }: ToolbarProps) => {
|
|
89
77
|
return (
|
|
90
78
|
<ToolbarContextProvider onAction={onAction} state={state}>
|
|
91
79
|
<DensityProvider density='fine'>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</NaturalToolbar.Root>
|
|
96
|
-
</ElevationProvider>
|
|
80
|
+
<div role='toolbar' className='flex w-full shrink-0 p-1 gap-4 items-center whitespace-nowrap overflow-hidden'>
|
|
81
|
+
{children}
|
|
82
|
+
</div>
|
|
97
83
|
</DensityProvider>
|
|
98
84
|
</ToolbarContextProvider>
|
|
99
85
|
);
|
|
100
86
|
};
|
|
101
87
|
|
|
102
|
-
type ToolbarButtonProps =
|
|
88
|
+
type ToolbarButtonProps = {
|
|
103
89
|
Icon: Icon;
|
|
104
|
-
|
|
90
|
+
disable?: (state: Formatting) => boolean;
|
|
91
|
+
getState?: (state: Formatting) => boolean;
|
|
92
|
+
onClick: (state: Formatting | null) => Action | undefined;
|
|
93
|
+
} & NonNullable<Pick<ButtonProps, 'title'>>;
|
|
105
94
|
|
|
106
|
-
const
|
|
107
|
-
const
|
|
95
|
+
const ToolbarButton = ({ Icon, onClick, title, getState, disable }: ToolbarButtonProps) => {
|
|
96
|
+
const { onAction, state } = useToolbarContext('ToolbarButton');
|
|
97
|
+
const active = getState && state ? getState(state) : false;
|
|
98
|
+
const disabled = disable && state ? disable(state) : false;
|
|
99
|
+
|
|
100
|
+
const handleClick = (event: React.MouseEvent) => {
|
|
101
|
+
const action = onClick(state);
|
|
102
|
+
if (action) {
|
|
103
|
+
onAction?.(action);
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
108
107
|
|
|
109
|
-
const ToolbarButton = ({ Icon, children, ...props }: ToolbarButtonProps) => {
|
|
110
108
|
return (
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
<Button
|
|
110
|
+
variant='ghost'
|
|
111
|
+
classNames={mx('p-2', active && 'ring-[1px]')}
|
|
112
|
+
onMouseDown={handleClick}
|
|
113
|
+
title={title}
|
|
114
|
+
disabled={disabled}
|
|
115
|
+
>
|
|
116
|
+
<Icon className={getSize(5)} />
|
|
117
|
+
</Button>
|
|
115
118
|
);
|
|
116
119
|
};
|
|
117
120
|
|
|
118
|
-
const ToolbarSeparator = () => <div
|
|
121
|
+
const ToolbarSeparator = () => <div className='grow' />;
|
|
119
122
|
|
|
120
123
|
const MarkdownHeading = () => {
|
|
121
|
-
const { t } = useTranslation(translationKey);
|
|
122
124
|
const { onAction, state } = useToolbarContext('MarkdownFormatting');
|
|
123
125
|
const blockType = state ? state.blockType : 'paragraph';
|
|
124
126
|
const header = blockType && /heading(\d)/.exec(blockType);
|
|
@@ -130,16 +132,11 @@ const MarkdownHeading = () => {
|
|
|
130
132
|
value={value ?? '0'}
|
|
131
133
|
onValueChange={(value) => onAction?.({ type: 'heading', data: parseInt(value) })}
|
|
132
134
|
>
|
|
133
|
-
<Select.
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
<HeadingIcon className={iconStyles} />
|
|
137
|
-
<CaretDown className={getSize(2)} weight='bold' />
|
|
138
|
-
</NaturalToolbar.Button>
|
|
139
|
-
</Select.Trigger>
|
|
135
|
+
<Select.TriggerButton>
|
|
136
|
+
<HeadingIcon className={getSize(5)} />
|
|
137
|
+
</Select.TriggerButton>
|
|
140
138
|
<Select.Portal>
|
|
141
139
|
<Select.Content>
|
|
142
|
-
<Select.ScrollUpButton />
|
|
143
140
|
<Select.Viewport>
|
|
144
141
|
<Select.Option value='0'>Paragraph</Select.Option>
|
|
145
142
|
{[1, 2, 3, 4, 5, 6].map((level) => (
|
|
@@ -148,117 +145,99 @@ const MarkdownHeading = () => {
|
|
|
148
145
|
</Select.Option>
|
|
149
146
|
))}
|
|
150
147
|
</Select.Viewport>
|
|
151
|
-
<Select.ScrollDownButton />
|
|
152
|
-
<Select.Arrow />
|
|
153
148
|
</Select.Content>
|
|
154
149
|
</Select.Portal>
|
|
155
150
|
</Select.Root>
|
|
156
151
|
);
|
|
157
152
|
};
|
|
158
153
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const markdownLists = [
|
|
194
|
-
{ key: 'bullet' as const, Icon: ListBullets, actionType: 'list-bullet' as const },
|
|
195
|
-
{ key: 'ordered' as const, Icon: ListNumbers, actionType: 'list-ordered' as const },
|
|
196
|
-
{ key: 'task' as const, Icon: ListChecks, actionType: 'list-tasks' as const },
|
|
197
|
-
];
|
|
154
|
+
const MarkdownStyles = () => (
|
|
155
|
+
<div role='none'>
|
|
156
|
+
<ToolbarButton
|
|
157
|
+
Icon={TextB}
|
|
158
|
+
title='String'
|
|
159
|
+
disable={(s) => s.blockType === 'codeblock'}
|
|
160
|
+
getState={(s) => s.strong}
|
|
161
|
+
onClick={(s) => ({ type: 'strong', data: s ? !s.strong : null })}
|
|
162
|
+
/>
|
|
163
|
+
<ToolbarButton
|
|
164
|
+
Icon={TextItalic}
|
|
165
|
+
title='Emphasis'
|
|
166
|
+
disable={(s) => s.blockType === 'codeblock'}
|
|
167
|
+
getState={(s) => s.emphasis}
|
|
168
|
+
onClick={(s) => ({ type: 'emphasis', data: s ? !s.emphasis : null })}
|
|
169
|
+
/>
|
|
170
|
+
<ToolbarButton
|
|
171
|
+
Icon={TextStrikethrough}
|
|
172
|
+
title='Strike-through'
|
|
173
|
+
disable={(s) => s.blockType === 'codeblock'}
|
|
174
|
+
getState={(s) => s.strikethrough}
|
|
175
|
+
onClick={(s) => ({ type: 'strikethrough', data: s ? !s.strikethrough : null })}
|
|
176
|
+
/>
|
|
177
|
+
<ToolbarButton
|
|
178
|
+
Icon={Code}
|
|
179
|
+
title='Inline code'
|
|
180
|
+
disable={(s) => s.blockType === 'codeblock'}
|
|
181
|
+
getState={(s) => s.code}
|
|
182
|
+
onClick={(s) => ({ type: 'code', data: s ? !s.code : null })}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
198
186
|
|
|
199
|
-
const MarkdownLists = () =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
187
|
+
const MarkdownLists = () => (
|
|
188
|
+
<div role='none'>
|
|
189
|
+
<ToolbarButton
|
|
190
|
+
Icon={ListBullets}
|
|
191
|
+
title='Bullet list'
|
|
192
|
+
getState={(s) => s.listStyle === 'bullet'}
|
|
193
|
+
onClick={(s) => ({ type: 'list-bullet', data: s ? s.listStyle !== 'bullet' : null })}
|
|
194
|
+
/>
|
|
195
|
+
<ToolbarButton
|
|
196
|
+
Icon={ListNumbers}
|
|
197
|
+
title='Numbered list'
|
|
198
|
+
getState={(s) => s.listStyle === 'ordered'}
|
|
199
|
+
onClick={(s) => ({ type: 'list-ordered', data: s ? s.listStyle !== 'ordered' : null })}
|
|
200
|
+
/>
|
|
201
|
+
<ToolbarButton
|
|
202
|
+
Icon={ListChecks}
|
|
203
|
+
title='Task list'
|
|
204
|
+
getState={(s) => s.listStyle === 'task'}
|
|
205
|
+
onClick={(s) => ({ type: 'list-tasks', data: s ? s.listStyle !== 'task' : null })}
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
217
209
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
210
|
+
const MarkdownBlocks = () => (
|
|
211
|
+
<div role='none'>
|
|
212
|
+
<ToolbarButton
|
|
213
|
+
Icon={CaretRight}
|
|
214
|
+
title='Block quote'
|
|
215
|
+
getState={(s) => s.blockQuote}
|
|
216
|
+
onClick={(s) => ({ type: 'blockquote', data: s ? !s.blockQuote : null })}
|
|
217
|
+
/>
|
|
218
|
+
<ToolbarButton
|
|
219
|
+
Icon={CodeBlock}
|
|
220
|
+
title='Code block'
|
|
221
|
+
disable={(s) => !s.blankLine}
|
|
222
|
+
getState={(s) => s.blockType === 'codeblock'}
|
|
223
|
+
onClick={(s) => ({ type: 'codeblock', data: s ? s.blockType !== 'codeblock' : null })}
|
|
224
|
+
/>
|
|
225
|
+
<ToolbarButton Icon={Table} title='Table' disable={(s) => !s.blankLine} onClick={() => ({ type: 'table' })} />
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
234
228
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</ToolbarButton>
|
|
248
|
-
))}
|
|
249
|
-
</NaturalToolbar.ToggleGroup>
|
|
250
|
-
<NaturalToolbar.Button
|
|
251
|
-
variant='ghost'
|
|
252
|
-
disabled={!state?.blankLine}
|
|
253
|
-
onClick={() => onAction?.({ type: 'table' })}
|
|
254
|
-
classNames={buttonStyles}
|
|
255
|
-
>
|
|
256
|
-
<Table className={iconStyles} />
|
|
257
|
-
<span className='sr-only'>{t('table label')}</span>
|
|
258
|
-
</NaturalToolbar.Button>
|
|
259
|
-
</>
|
|
260
|
-
);
|
|
261
|
-
};
|
|
229
|
+
const MarkdownLinks = () => (
|
|
230
|
+
<div role='none'>
|
|
231
|
+
<ToolbarButton
|
|
232
|
+
Icon={Link}
|
|
233
|
+
title='Link'
|
|
234
|
+
getState={(s) => s.link}
|
|
235
|
+
onClick={(s) => ({ type: 'link', data: s ? !s.link : null })}
|
|
236
|
+
/>
|
|
237
|
+
{/* <ToolbarButton Icon={At} title='Mention' onClick={() => ({ type: 'mention' })} /> */}
|
|
238
|
+
{/* <ToolbarButton Icon={Image} title='Image' onClick={() => ({ type: 'image' })} /> */}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
262
241
|
|
|
263
242
|
const MarkdownStandard = () => (
|
|
264
243
|
<>
|
|
@@ -266,19 +245,13 @@ const MarkdownStandard = () => (
|
|
|
266
245
|
<MarkdownStyles />
|
|
267
246
|
<MarkdownLists />
|
|
268
247
|
<MarkdownBlocks />
|
|
248
|
+
<MarkdownLinks />
|
|
269
249
|
</>
|
|
270
250
|
);
|
|
271
251
|
|
|
272
|
-
const MarkdownExtended = () =>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return (
|
|
276
|
-
<NaturalToolbar.Button variant='ghost' onClick={() => onAction?.({ type: 'comment' })} classNames={buttonStyles}>
|
|
277
|
-
<ChatText className={iconStyles} />
|
|
278
|
-
<span className='sr-only'>{t('comment label')}</span>
|
|
279
|
-
</NaturalToolbar.Button>
|
|
280
|
-
);
|
|
281
|
-
};
|
|
252
|
+
const MarkdownExtended = () => (
|
|
253
|
+
<Toolbar.Button Icon={ChatText} title='Create comment' onClick={() => ({ type: 'comment' })} />
|
|
254
|
+
);
|
|
282
255
|
|
|
283
256
|
export const Toolbar = {
|
|
284
257
|
Root: ToolbarRoot,
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
// Ref: https://github.com/automerge/automerge-codemirror
|
|
5
5
|
//
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { invertedEffects } from '@codemirror/commands';
|
|
8
|
+
import { StateField, type Extension, type StateEffect } from '@codemirror/state';
|
|
8
9
|
import { EditorView, ViewPlugin } from '@codemirror/view';
|
|
9
10
|
|
|
10
11
|
import { next as A, type Prop } from '@dxos/automerge/automerge';
|
|
11
12
|
|
|
12
13
|
import { cursorConverter } from './cursor';
|
|
13
14
|
import { updateHeadsEffect, type IDocHandle, isReconcile, type State } from './defs';
|
|
14
|
-
import {
|
|
15
|
+
import { PatchSemaphore } from './semaphore';
|
|
15
16
|
import { Cursor } from '../cursor';
|
|
16
17
|
|
|
17
18
|
export type AutomergeOptions = {
|
|
@@ -54,7 +55,7 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
54
55
|
},
|
|
55
56
|
});
|
|
56
57
|
|
|
57
|
-
const
|
|
58
|
+
const semaphore = new PatchSemaphore(handle, syncState);
|
|
58
59
|
|
|
59
60
|
return [
|
|
60
61
|
Cursor.converter.of(cursorConverter(handle, path)),
|
|
@@ -72,7 +73,7 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
_handleChange() {
|
|
75
|
-
|
|
76
|
+
semaphore.reconcile(this._view);
|
|
76
77
|
}
|
|
77
78
|
},
|
|
78
79
|
),
|
|
@@ -80,8 +81,23 @@ export const automerge = ({ handle, path }: AutomergeOptions): Extension => {
|
|
|
80
81
|
// Reconcile local updates.
|
|
81
82
|
EditorView.updateListener.of(({ view, changes }) => {
|
|
82
83
|
if (!changes.empty) {
|
|
83
|
-
|
|
84
|
+
semaphore.reconcile(view);
|
|
84
85
|
}
|
|
85
86
|
}),
|
|
87
|
+
|
|
88
|
+
// TODO(burdon): Record undo transactions.
|
|
89
|
+
// See https://github.com/yjs/y-codemirror.next/tree/main
|
|
90
|
+
// https://codemirror.net/examples/inverted-effect
|
|
91
|
+
invertedEffects.of((tr) => {
|
|
92
|
+
// Each change results it three transactions: insert, delete, insert.
|
|
93
|
+
const effects: StateEffect<any>[] = [];
|
|
94
|
+
if (!tr.changes.empty) {
|
|
95
|
+
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
|
96
|
+
// effects.push(effectType.of({});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return effects;
|
|
101
|
+
}),
|
|
86
102
|
];
|
|
87
103
|
};
|
|
@@ -12,11 +12,11 @@ import { next as A } from '@dxos/automerge/automerge';
|
|
|
12
12
|
import {
|
|
13
13
|
getLastHeads,
|
|
14
14
|
getPath,
|
|
15
|
-
|
|
16
|
-
type State,
|
|
15
|
+
isReconcile,
|
|
17
16
|
reconcileAnnotation,
|
|
18
17
|
updateHeads,
|
|
19
|
-
|
|
18
|
+
type IDocHandle,
|
|
19
|
+
type State,
|
|
20
20
|
} from './defs';
|
|
21
21
|
import { updateAutomerge } from './update-automerge';
|
|
22
22
|
import { updateCodeMirror } from './update-codemirror';
|
|
@@ -24,8 +24,8 @@ import { updateCodeMirror } from './update-codemirror';
|
|
|
24
24
|
/**
|
|
25
25
|
* Implements three-way merge (on each mutation).
|
|
26
26
|
*/
|
|
27
|
-
export class
|
|
28
|
-
private
|
|
27
|
+
export class PatchSemaphore {
|
|
28
|
+
private _inReconcile = false;
|
|
29
29
|
|
|
30
30
|
// prettier-ignore
|
|
31
31
|
constructor(
|
|
@@ -33,44 +33,46 @@ export class Syncer {
|
|
|
33
33
|
private readonly _state: StateField<State>
|
|
34
34
|
) {}
|
|
35
35
|
|
|
36
|
-
reconcile(view: EditorView
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
reconcile(view: EditorView) {
|
|
37
|
+
if (!this._inReconcile) {
|
|
38
|
+
this._inReconcile = true;
|
|
39
|
+
this.doReconcile(view);
|
|
40
|
+
this._inReconcile = false;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
this._pending = true;
|
|
43
|
-
if (editor) {
|
|
44
|
-
this.onEditorChange(view);
|
|
45
|
-
} else {
|
|
46
|
-
this.onAutomergeChange(view);
|
|
47
|
-
}
|
|
48
|
-
this._pending = false;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
|
|
54
|
-
const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
|
|
44
|
+
private doReconcile(view: EditorView) {
|
|
45
|
+
const path = getPath(view.state, this._state);
|
|
55
46
|
|
|
56
|
-
|
|
47
|
+
// Get the heads before the unreconciled transactions are applied.
|
|
48
|
+
const oldHeads = getLastHeads(view.state, this._state);
|
|
49
|
+
let selection = view.state.selection;
|
|
50
|
+
|
|
51
|
+
// First undo all the unreconciled transactions.
|
|
52
|
+
const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
|
|
53
|
+
const toInvert = transactions.slice().reverse();
|
|
54
|
+
for (const tr of toInvert) {
|
|
55
|
+
const inverted = tr.changes.invert(tr.startState.doc);
|
|
56
|
+
selection = selection.map(inverted);
|
|
57
57
|
view.dispatch({
|
|
58
|
-
|
|
59
|
-
annotations: reconcileAnnotation.of(
|
|
58
|
+
changes: inverted,
|
|
59
|
+
annotations: reconcileAnnotation.of(true),
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// Apply the unreconciled transactions to the document.
|
|
64
|
+
// NOTE: null and undefined each come from automerge and repo respectively.
|
|
65
|
+
let newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
|
|
66
|
+
if (newHeads === null || newHeads === undefined) {
|
|
67
|
+
// TODO(alexjg): this is the call that's resetting the editor state on click.
|
|
68
|
+
newHeads = A.getHeads(this._handle.docSync()!);
|
|
69
|
+
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
const
|
|
71
|
+
// Now get the diff between the updated state of the document and the heads and apply that to the codemirror doc.
|
|
72
|
+
const diff = A.equals(oldHeads, newHeads) ? [] : A.diff(this._handle.docSync()!, oldHeads, newHeads);
|
|
72
73
|
updateCodeMirror(view, selection, path, diff);
|
|
73
74
|
|
|
75
|
+
// Update automerge state.
|
|
74
76
|
view.dispatch({
|
|
75
77
|
effects: updateHeads(newHeads),
|
|
76
78
|
annotations: reconcileAnnotation.of(false),
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,6 @@ export { type EditorView, keymap } from '@codemirror/view';
|
|
|
10
10
|
export * from './components';
|
|
11
11
|
export * from './extensions';
|
|
12
12
|
export * from './hooks';
|
|
13
|
-
export { getToken
|
|
13
|
+
export { getToken } from './styles';
|
|
14
14
|
export * from './themes';
|
|
15
15
|
export * from './util';
|
package/src/styles/index.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/automerge/sync.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,KAAK,EAIX,MAAM,QAAQ,CAAC;AAIhB;;GAEG;AACH,qBAAa,MAAM;IAKf,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAS;gBAIN,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC;IAG5C,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO;IAe3C,cAAc,CAAC,IAAI,EAAE,UAAU;IAa/B,iBAAiB,CAAC,IAAI,EAAE,UAAU;CAenC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../../src/styles/layout.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,4GACuE,CAAC"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export declare const translationKey = "react-ui-editor";
|
|
2
|
-
declare const _default: {
|
|
3
|
-
'en-US': {
|
|
4
|
-
"react-ui-editor": {
|
|
5
|
-
'strong label': string;
|
|
6
|
-
'emphasis label': string;
|
|
7
|
-
'strikethrough label': string;
|
|
8
|
-
'code label': string;
|
|
9
|
-
'bullet label': string;
|
|
10
|
-
'ordered label': string;
|
|
11
|
-
'task label': string;
|
|
12
|
-
'blockquote label': string;
|
|
13
|
-
'codeblock label': string;
|
|
14
|
-
'comment label': string;
|
|
15
|
-
'heading label': string;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
}[];
|
|
19
|
-
export default _default;
|
|
20
|
-
//# sourceMappingURL=translations.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,oBAAoB,CAAC;;;;;;;;;;;;;;;;;;AAEhD,wBAkBE"}
|
package/src/styles/layout.ts
DELETED
package/src/translations.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
export const translationKey = 'react-ui-editor';
|
|
6
|
-
|
|
7
|
-
export default [
|
|
8
|
-
{
|
|
9
|
-
'en-US': {
|
|
10
|
-
[translationKey]: {
|
|
11
|
-
'strong label': 'Strong',
|
|
12
|
-
'emphasis label': 'Emphasis',
|
|
13
|
-
'strikethrough label': 'Strike-through',
|
|
14
|
-
'code label': 'Code',
|
|
15
|
-
'bullet label': 'Bullet list',
|
|
16
|
-
'ordered label': 'Numbered list',
|
|
17
|
-
'task label': 'Task list',
|
|
18
|
-
'blockquote label': 'Block quote',
|
|
19
|
-
'codeblock label': 'Code block',
|
|
20
|
-
'comment label': 'Create comment',
|
|
21
|
-
'heading label': 'Heading level',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
];
|