@dxos/react-ui-editor 0.6.9 → 0.6.10-main.3cfcc89
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 +759 -732
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/InputMode.stories.d.ts +2 -1
- package/dist/types/src/InputMode.stories.d.ts.map +1 -1
- package/dist/types/src/TextEditor.stories.d.ts +20 -13
- package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +5 -1
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +3 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts +2 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/dnd.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +2 -2
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/folding.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
- package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
- package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
- package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -0
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/styles/theme.d.ts +1 -1
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts +2 -5
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/package.json +26 -27
- package/src/InputMode.stories.tsx +1 -1
- package/src/TextEditor.stories.tsx +125 -77
- package/src/components/Toolbar/Toolbar.tsx +91 -92
- package/src/defaults.ts +16 -11
- package/src/extensions/annotations.ts +2 -2
- package/src/extensions/autocomplete.ts +4 -1
- package/src/extensions/automerge/automerge.stories.tsx +1 -1
- package/src/extensions/awareness/awareness.ts +1 -1
- package/src/extensions/comments.ts +11 -45
- package/src/extensions/dnd.ts +3 -5
- package/src/extensions/factories.ts +8 -8
- package/src/extensions/folding.tsx +3 -4
- package/src/extensions/markdown/action.ts +1 -0
- package/src/extensions/markdown/bundle.ts +3 -1
- package/src/extensions/markdown/{link-paste.test.ts → changes.test.ts} +2 -2
- package/src/extensions/markdown/changes.ts +148 -0
- package/src/extensions/markdown/debug.ts +44 -0
- package/src/extensions/markdown/decorate.ts +35 -108
- package/src/extensions/markdown/formatting.ts +1 -2
- package/src/extensions/markdown/highlight.ts +2 -2
- package/src/extensions/markdown/index.ts +1 -0
- package/src/extensions/markdown/parser.test.ts +29 -0
- package/src/extensions/markdown/styles.ts +103 -0
- package/src/index.ts +0 -2
- package/src/styles/theme.ts +85 -147
- package/src/styles/tokens.ts +6 -6
- package/src/translations.ts +1 -0
- package/dist/types/src/extensions/markdown/link-paste.d.ts +0 -9
- package/dist/types/src/extensions/markdown/link-paste.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts +0 -2
- package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +0 -1
- package/src/extensions/markdown/link-paste.ts +0 -107
package/src/styles/theme.ts
CHANGED
|
@@ -4,14 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { type StyleSpec } from 'style-mod';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { fontBody } from './tokens';
|
|
8
8
|
|
|
9
9
|
export type ThemeStyles = Record<string, StyleSpec>;
|
|
10
10
|
|
|
11
|
-
// TODO(burdon): Factor out theme.
|
|
12
|
-
// TODO(burdon): Can we use @apply and import css file?
|
|
13
|
-
// https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply?
|
|
14
|
-
|
|
15
11
|
/**
|
|
16
12
|
* Minimal styles.
|
|
17
13
|
* https://codemirror.net/examples/styling
|
|
@@ -43,8 +39,8 @@ export type ThemeStyles = Record<string, StyleSpec>;
|
|
|
43
39
|
* </div>
|
|
44
40
|
* </div>
|
|
45
41
|
*
|
|
46
|
-
* NOTE: Use one of '&', '&light', and '&dark' prefix to scope instance.
|
|
47
42
|
* NOTE: `light` and `dark` selectors are preprocessed by CodeMirror and can only be in the base theme.
|
|
43
|
+
* NOTE: Use 'unset' to remove default CM style.
|
|
48
44
|
*/
|
|
49
45
|
export const defaultTheme: ThemeStyles = {
|
|
50
46
|
'&': {},
|
|
@@ -65,16 +61,11 @@ export const defaultTheme: ThemeStyles = {
|
|
|
65
61
|
*/
|
|
66
62
|
'.cm-content': {
|
|
67
63
|
padding: 'unset',
|
|
64
|
+
fontFamily: fontBody,
|
|
68
65
|
// NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
|
|
69
66
|
fontSize: '16px',
|
|
70
|
-
fontFamily: getToken('fontFamily.body'),
|
|
71
67
|
lineHeight: 1.5,
|
|
72
|
-
|
|
73
|
-
'&light .cm-content': {
|
|
74
|
-
color: getToken('extend.semanticColors.base.fg.light', 'black'),
|
|
75
|
-
},
|
|
76
|
-
'&dark .cm-content': {
|
|
77
|
-
color: getToken('extend.semanticColors.base.fg.dark', 'white'),
|
|
68
|
+
color: 'unset',
|
|
78
69
|
},
|
|
79
70
|
|
|
80
71
|
/**
|
|
@@ -82,107 +73,85 @@ export const defaultTheme: ThemeStyles = {
|
|
|
82
73
|
* NOTE: Gutters should have the same top margin as the content.
|
|
83
74
|
*/
|
|
84
75
|
'.cm-gutters': {
|
|
85
|
-
background: '
|
|
76
|
+
background: 'unset',
|
|
86
77
|
},
|
|
87
78
|
'.cm-gutter': {},
|
|
88
79
|
'.cm-gutterElement': {
|
|
80
|
+
fontSize: '16px',
|
|
89
81
|
lineHeight: 1.5,
|
|
90
82
|
},
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
'&light .cm-cursor, &light .cm-dropCursor': {
|
|
96
|
-
borderLeft: '2px solid black',
|
|
97
|
-
},
|
|
98
|
-
'&dark .cm-cursor, &dark .cm-dropCursor': {
|
|
99
|
-
borderLeft: '2px solid white',
|
|
100
|
-
},
|
|
101
|
-
'&light .cm-placeholder': {
|
|
102
|
-
color: getToken('extend.semanticColors.description.light', 'rgba(0,0,0,.2)'),
|
|
103
|
-
},
|
|
104
|
-
'&dark .cm-placeholder': {
|
|
105
|
-
color: getToken('extend.semanticColors.description.dark', 'rgba(255,255,255,.2)'),
|
|
84
|
+
'.cm-lineNumbers': {
|
|
85
|
+
minWidth: '36px',
|
|
106
86
|
},
|
|
107
87
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Line.
|
|
90
|
+
*/
|
|
111
91
|
'.cm-line': {
|
|
112
92
|
paddingInline: 0,
|
|
113
93
|
},
|
|
114
94
|
'.cm-activeLine': {
|
|
115
|
-
background: '
|
|
95
|
+
background: 'var(--dx-hoverSurface)',
|
|
116
96
|
},
|
|
117
97
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
'.cm-
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
// Selection
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
'&light .cm-selectionBackground': {
|
|
130
|
-
background: getToken('extend.colors.primary.100'),
|
|
131
|
-
},
|
|
132
|
-
'&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
|
|
133
|
-
background: getToken('extend.colors.primary.200'),
|
|
134
|
-
},
|
|
135
|
-
'&dark .cm-selectionBackground': {
|
|
136
|
-
background: getToken('extend.colors.primary.700'),
|
|
98
|
+
/**
|
|
99
|
+
* Cursor (layer).
|
|
100
|
+
*/
|
|
101
|
+
'.cm-cursor, .cm-dropCursor': {
|
|
102
|
+
borderLeft: '2px solid var(--dx-cmCursor)',
|
|
137
103
|
},
|
|
138
|
-
'
|
|
139
|
-
|
|
104
|
+
'.cm-placeholder': {
|
|
105
|
+
color: 'var(--dx-subdued)',
|
|
140
106
|
},
|
|
141
107
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Selection (layer).
|
|
110
|
+
*/
|
|
111
|
+
'.cm-selectionBackground': {
|
|
112
|
+
background: 'var(--dx-cmSelection)',
|
|
113
|
+
},
|
|
145
114
|
|
|
146
|
-
|
|
147
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Search.
|
|
117
|
+
* NOTE: Matches comment.
|
|
118
|
+
*/
|
|
119
|
+
'.cm-searchMatch': {
|
|
120
|
+
margin: '0 -3px',
|
|
121
|
+
padding: '3px',
|
|
122
|
+
borderRadius: '3px',
|
|
123
|
+
background: 'var(--dx-cmHighlightSurface)',
|
|
124
|
+
color: 'var(--dx-cmHighlight)',
|
|
148
125
|
},
|
|
149
|
-
'
|
|
150
|
-
|
|
126
|
+
'.cm-searchMatch-selected': {
|
|
127
|
+
textDecoration: 'underline',
|
|
151
128
|
},
|
|
152
129
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Link.
|
|
132
|
+
*/
|
|
156
133
|
'.cm-link': {
|
|
157
134
|
textDecorationLine: 'underline',
|
|
158
135
|
textDecorationThickness: '1px',
|
|
159
136
|
textUnderlineOffset: '2px',
|
|
160
137
|
borderRadius: '.125rem',
|
|
161
|
-
fontFamily: getToken('fontFamily.body'),
|
|
162
138
|
},
|
|
163
|
-
'
|
|
164
|
-
color:
|
|
165
|
-
},
|
|
166
|
-
'&dark .cm-link > span': {
|
|
167
|
-
color: getToken('extend.colors.primary.400'),
|
|
139
|
+
'.cm-link > span': {
|
|
140
|
+
color: 'var(--dx-accentText)',
|
|
168
141
|
},
|
|
169
142
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'.cm-tooltip': {
|
|
174
|
-
|
|
175
|
-
background: `${getToken('extend.colors.neutral.100')} !important`,
|
|
176
|
-
},
|
|
177
|
-
'&dark .cm-tooltip': {
|
|
178
|
-
background: `${getToken('extend.colors.neutral.900')} !important`,
|
|
143
|
+
/**
|
|
144
|
+
* Tooltip.
|
|
145
|
+
*/
|
|
146
|
+
'.cm-tooltip': {
|
|
147
|
+
background: 'var(--dx-base)',
|
|
179
148
|
},
|
|
180
149
|
'.cm-tooltip-below': {},
|
|
181
150
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Autocomplete.
|
|
153
|
+
* https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
|
|
154
|
+
*/
|
|
186
155
|
'.cm-tooltip.cm-tooltip-autocomplete': {
|
|
187
156
|
marginTop: '4px',
|
|
188
157
|
marginLeft: '-3px',
|
|
@@ -195,31 +164,27 @@ export const defaultTheme: ThemeStyles = {
|
|
|
195
164
|
'.cm-tooltip.cm-tooltip-autocomplete > ul > completion-section': {
|
|
196
165
|
paddingLeft: '4px !important',
|
|
197
166
|
borderBottom: 'none !important',
|
|
198
|
-
color:
|
|
167
|
+
color: 'var(--dx-accentText)',
|
|
199
168
|
},
|
|
200
169
|
'.cm-tooltip.cm-completionInfo': {
|
|
201
|
-
border: getToken('extend.colors.neutral.500'),
|
|
202
170
|
width: '360px !important',
|
|
203
171
|
margin: '-10px 1px 0 1px',
|
|
204
172
|
padding: '8px !important',
|
|
173
|
+
borderColor: 'var(--dx-separator)',
|
|
205
174
|
},
|
|
206
175
|
'.cm-completionIcon': {
|
|
207
176
|
display: 'none',
|
|
208
177
|
},
|
|
209
178
|
'.cm-completionLabel': {
|
|
210
|
-
fontFamily:
|
|
179
|
+
fontFamily: fontBody,
|
|
211
180
|
},
|
|
212
181
|
'.cm-completionMatchedText': {
|
|
213
182
|
textDecoration: 'none !important',
|
|
214
183
|
opacity: 0.5,
|
|
215
184
|
},
|
|
216
185
|
|
|
217
|
-
// TODO(burdon): Override vars --cm-background.
|
|
218
|
-
// https://www.npmjs.com/package/codemirror-theme-vars
|
|
219
|
-
|
|
220
186
|
/**
|
|
221
187
|
* Panels
|
|
222
|
-
* TODO(burdon): Needs styling attention (esp. dark mode).
|
|
223
188
|
* https://github.com/codemirror/search/blob/main/src/search.ts#L745
|
|
224
189
|
*
|
|
225
190
|
* Find/replace panel.
|
|
@@ -233,76 +198,49 @@ export const defaultTheme: ThemeStyles = {
|
|
|
233
198
|
* </div>
|
|
234
199
|
* </div
|
|
235
200
|
*/
|
|
201
|
+
// TODO(burdon): Apply react-ui-theme or replace panel.
|
|
236
202
|
'.cm-panels': {},
|
|
237
203
|
'.cm-panel': {
|
|
238
|
-
fontFamily:
|
|
204
|
+
fontFamily: fontBody,
|
|
205
|
+
backgroundColor: 'var(--dx-base)',
|
|
239
206
|
},
|
|
240
|
-
'.cm-panel input
|
|
241
|
-
|
|
207
|
+
'.cm-panel input, .cm-panel button, .cm-panel label': {
|
|
208
|
+
fontFamily: fontBody,
|
|
209
|
+
fontSize: '14px',
|
|
210
|
+
all: 'unset',
|
|
211
|
+
margin: '3px !important',
|
|
212
|
+
padding: '2px 6px !important',
|
|
213
|
+
outline: '1px solid transparent',
|
|
242
214
|
},
|
|
243
|
-
'
|
|
244
|
-
|
|
215
|
+
'.cm-panel input, .cm-panel button': {
|
|
216
|
+
backgroundColor: 'var(--dx-input)',
|
|
245
217
|
},
|
|
246
|
-
'
|
|
247
|
-
|
|
218
|
+
'.cm-panel input:focus, .cm-panel button:focus': {
|
|
219
|
+
outline: '1px solid var(--dx-accentFocusIndicator)',
|
|
248
220
|
},
|
|
249
|
-
'.cm-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
border: 'none',
|
|
254
|
-
'&:active': {
|
|
255
|
-
backgroundImage: 'none',
|
|
256
|
-
},
|
|
221
|
+
'.cm-panel label': {
|
|
222
|
+
display: 'inline-flex',
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
cursor: 'pointer',
|
|
257
225
|
},
|
|
258
|
-
'
|
|
259
|
-
|
|
260
|
-
'
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
'
|
|
264
|
-
|
|
265
|
-
},
|
|
226
|
+
'.cm-panel input.cm-textfield': {},
|
|
227
|
+
'.cm-panel input[type=checkbox]': {
|
|
228
|
+
width: '8px',
|
|
229
|
+
height: '8px',
|
|
230
|
+
marginRight: '6px !important',
|
|
231
|
+
padding: '2px !important',
|
|
232
|
+
color: 'var(--dx-accentFocusIndicator)',
|
|
266
233
|
},
|
|
267
|
-
'
|
|
268
|
-
background: getToken('extend.colors.neutral.800'),
|
|
234
|
+
'.cm-panel button': {
|
|
269
235
|
'&:hover': {
|
|
270
|
-
|
|
236
|
+
backgroundColor: 'var(--dx-accentSurfaceHover) !important',
|
|
271
237
|
},
|
|
272
238
|
'&:active': {
|
|
273
|
-
|
|
239
|
+
backgroundColor: 'var(--dx-accentSurfaceHover)',
|
|
274
240
|
},
|
|
275
241
|
},
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
// table
|
|
281
|
-
//
|
|
282
|
-
'.cm-table *': {
|
|
283
|
-
fontFamily: `${getToken('fontFamily.mono')} !important`,
|
|
284
|
-
textDecoration: 'none !important',
|
|
285
|
-
},
|
|
286
|
-
'.cm-table-head': {
|
|
287
|
-
padding: '2px 16px 2px 0px',
|
|
288
|
-
textAlign: 'left',
|
|
289
|
-
borderBottom: `1px solid ${getToken('extend.colors.primary.500')}`,
|
|
290
|
-
color: getToken('extend.colors.neutral.500'),
|
|
291
|
-
},
|
|
292
|
-
'.cm-table-cell': {
|
|
293
|
-
padding: '2px 16px 2px 0px',
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
//
|
|
297
|
-
// image
|
|
298
|
-
//
|
|
299
|
-
'.cm-image': {
|
|
300
|
-
display: 'block',
|
|
301
|
-
height: '0',
|
|
302
|
-
},
|
|
303
|
-
'.cm-image.cm-loaded-image': {
|
|
304
|
-
height: 'auto',
|
|
305
|
-
borderTop: '0.5rem solid transparent',
|
|
306
|
-
borderBottom: '0.5rem solid transparent',
|
|
242
|
+
'.cm-panel.cm-search': {
|
|
243
|
+
padding: '4px',
|
|
244
|
+
borderTop: '1px solid var(--dx-separator)',
|
|
307
245
|
},
|
|
308
246
|
};
|
package/src/styles/tokens.ts
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import get from 'lodash.get';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
|
|
7
|
+
import { tokens } from '@dxos/react-ui-theme';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Replace with CSS vars.
|
|
10
|
+
* Returns the tailwind token value.
|
|
14
11
|
*/
|
|
15
|
-
|
|
12
|
+
const getToken = (path: string, defaultValue?: string | string[]): string => {
|
|
16
13
|
const value = get(tokens, path, defaultValue);
|
|
17
14
|
return value?.toString() ?? '';
|
|
18
15
|
};
|
|
16
|
+
|
|
17
|
+
export const fontBody = getToken('fontFamily.body');
|
|
18
|
+
export const fontMono = getToken('fontFamily.mono');
|
package/src/translations.ts
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
|
2
|
-
/**
|
|
3
|
-
* Formats pasted URLs as markdown links and images.
|
|
4
|
-
*/
|
|
5
|
-
export declare const linkPastePlugin: ViewPlugin<{
|
|
6
|
-
update(update: ViewUpdate): void;
|
|
7
|
-
}>;
|
|
8
|
-
export declare const createLinkLabel: (url: URL) => string;
|
|
9
|
-
//# sourceMappingURL=link-paste.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"link-paste.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/link-paste.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAoB,MAAM,kBAAkB,CAAC;AAGjF;;GAEG;AACH,eAAO,MAAM,eAAe;mBAET,UAAU;EA2B5B,CAAC;AAmBF,eAAO,MAAM,eAAe,QAAS,GAAG,KAAG,MAW1C,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"link-paste.test.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/markdown/link-paste.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { syntaxTree } from '@codemirror/language';
|
|
6
|
-
import { type EditorState, Transaction } from '@codemirror/state';
|
|
7
|
-
import { ViewPlugin, type ViewUpdate, type PluginValue } from '@codemirror/view';
|
|
8
|
-
import { type SyntaxNode } from '@lezer/common';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Formats pasted URLs as markdown links and images.
|
|
12
|
-
*/
|
|
13
|
-
export const linkPastePlugin = ViewPlugin.fromClass(
|
|
14
|
-
class implements PluginValue {
|
|
15
|
-
update(update: ViewUpdate) {
|
|
16
|
-
for (const tr of update.transactions) {
|
|
17
|
-
const event = tr.annotation(Transaction.userEvent);
|
|
18
|
-
if (event === 'input.paste') {
|
|
19
|
-
const changes = tr.changes;
|
|
20
|
-
if (changes.empty) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
|
25
|
-
const insertedUrl = getValidUrl(update.view.state.sliceDoc(fromB, toB));
|
|
26
|
-
if (insertedUrl && isValidPosition(update.view.state, fromB)) {
|
|
27
|
-
// We might be pasting over an existing text.
|
|
28
|
-
const replacedText = tr.startState.sliceDoc(fromA, toA);
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
update.view.dispatch(
|
|
31
|
-
update.view.state.update({
|
|
32
|
-
changes: { from: fromA, to: toB, insert: createLink(insertedUrl, replacedText) },
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const createLink = (url: URL, label: string): string => {
|
|
45
|
-
// Check if image.
|
|
46
|
-
// Example: https://dxos.network/dxos-logotype-blue.png
|
|
47
|
-
const { host, pathname } = url;
|
|
48
|
-
const [, extension] = pathname.split('.');
|
|
49
|
-
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
|
|
50
|
-
if (imageExtensions.includes(extension)) {
|
|
51
|
-
return ``;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (!label) {
|
|
55
|
-
label = createLinkLabel(url);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return `[${label}](${url})`;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const createLinkLabel = (url: URL): string => {
|
|
62
|
-
let { protocol, host, pathname } = url;
|
|
63
|
-
if (protocol === 'http:' || protocol === 'https:') {
|
|
64
|
-
protocol = '';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// NOTE(Zan): Consult: https://github.com/dxos/dxos/issues/7331 before changing this.
|
|
68
|
-
// Remove 'www.' if at the beginning of the URL
|
|
69
|
-
host = host.replace(/^www\./, '');
|
|
70
|
-
|
|
71
|
-
return [protocol, host].filter(Boolean).join('//') + (pathname !== '/' ? pathname : '');
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Returns a valid URL if appropriate for a link.
|
|
76
|
-
*/
|
|
77
|
-
const getValidUrl = (str: string): URL | undefined => {
|
|
78
|
-
const validProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
|
|
79
|
-
try {
|
|
80
|
-
const url = new URL(str);
|
|
81
|
-
if (!validProtocols.includes(url.protocol)) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return url;
|
|
86
|
-
} catch (_err) {
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Traverses the syntax tree upwards from the position.
|
|
93
|
-
*/
|
|
94
|
-
const isValidPosition = (state: EditorState, pos: number): boolean => {
|
|
95
|
-
const invalidPositions = new Set(['Link', 'LinkMark', 'Code', 'FencedCode']);
|
|
96
|
-
const tree = syntaxTree(state);
|
|
97
|
-
let node: SyntaxNode | null = tree.resolveInner(pos, -1);
|
|
98
|
-
while (node) {
|
|
99
|
-
if (invalidPositions.has(node.name)) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
node = node.parent;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return true;
|
|
107
|
-
};
|