@dxos/react-ui-editor 0.3.9-main.501fe30 → 0.3.9-main.52d137d
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 +88 -183
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts +2 -2
- package/dist/types/src/automerge/automerge-plugin/PatchSemaphore.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts +2 -2
- package/dist/types/src/automerge/automerge-plugin/codeMirrorToAm.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-plugin/index.d.ts +2 -2
- package/dist/types/src/automerge/automerge-plugin/index.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts +5 -6
- package/dist/types/src/automerge/automerge-plugin/plugin.d.ts.map +1 -1
- package/dist/types/src/automerge/automerge.stories.d.ts +0 -1
- package/dist/types/src/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/automerge/demo.d.ts +25 -0
- package/dist/types/src/automerge/demo.d.ts.map +1 -0
- package/dist/types/src/components/Editor/Editor.d.ts +1 -0
- package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
- package/dist/types/src/components/Editor/Editor.stories.d.ts +8 -2
- package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Markdown/Markdown.d.ts +1 -2
- package/dist/types/src/components/Markdown/Markdown.d.ts.map +1 -1
- package/dist/types/src/components/Markdown/Markdown.stories.d.ts +1 -0
- package/dist/types/src/components/Markdown/Markdown.stories.d.ts.map +1 -1
- package/dist/types/src/components/Markdown/markdownTheme.d.ts +1 -8
- package/dist/types/src/components/Markdown/markdownTheme.d.ts.map +1 -1
- package/dist/types/src/components/RichText/RichText.stories.d.ts +3 -1
- package/dist/types/src/components/RichText/RichText.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +15 -0
- package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/{hyperlinkWidget.d.ts → hyperlink.d.ts} +3 -3
- package/dist/types/src/components/TextEditor/extensions/hyperlink.d.ts.map +1 -0
- package/dist/types/src/components/TextEditor/extensions/hyperlinkTooltip.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/extensions/index.d.ts +1 -2
- package/dist/types/src/components/TextEditor/extensions/index.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/theme.d.ts +0 -1
- package/dist/types/src/components/TextEditor/theme.d.ts.map +1 -1
- package/dist/types/stories/html/code-mirror.stories.d.ts.map +1 -1
- package/package.json +27 -26
- package/src/automerge/automerge-plugin/PatchSemaphore.ts +5 -5
- package/src/automerge/automerge-plugin/codeMirrorToAm.ts +7 -10
- package/src/automerge/automerge-plugin/index.ts +2 -2
- package/src/automerge/automerge-plugin/plugin.ts +10 -39
- package/src/automerge/automerge.stories.tsx +46 -31
- package/src/automerge/demo.ts +106 -0
- package/src/components/Editor/Editor.stories.tsx +5 -3
- package/src/components/Editor/Editor.tsx +1 -4
- package/src/components/Markdown/Markdown.stories.tsx +25 -36
- package/src/components/Markdown/Markdown.tsx +22 -47
- package/src/components/Markdown/markdownTheme.ts +1 -12
- package/src/components/RichText/RichText.stories.tsx +33 -35
- package/src/components/TextEditor/TextEditor.stories.tsx +64 -39
- package/src/components/TextEditor/TextEditor.tsx +0 -1
- package/src/components/TextEditor/extensions/{hyperlinkWidget.tsx → hyperlink.tsx} +10 -21
- package/src/components/TextEditor/extensions/hyperlinkTooltip.tsx +0 -1
- package/src/components/TextEditor/extensions/index.ts +1 -2
- package/src/components/TextEditor/theme.ts +4 -6
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts +0 -9
- package/dist/types/src/automerge/automerge-plugin/handle.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts +0 -11
- package/dist/types/src/components/TextEditor/extensions/hyperlinkDecoration.d.ts.map +0 -1
- package/dist/types/src/components/TextEditor/extensions/hyperlinkWidget.d.ts.map +0 -1
- package/src/automerge/automerge-plugin/handle.ts +0 -14
- package/src/components/TextEditor/extensions/hyperlinkDecoration.ts +0 -99
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
import { HighlightStyle, StreamLanguage } from '@codemirror/language';
|
|
8
|
+
import { tags } from '@lezer/highlight';
|
|
7
9
|
import { ArrowCircleUp } from '@phosphor-icons/react';
|
|
8
10
|
import React, { StrictMode, useState } from 'react';
|
|
9
11
|
import { createRoot } from 'react-dom/client';
|
|
@@ -13,52 +15,75 @@ import { fixedInsetFlexLayout, getSize, groupSurface, inputSurface, mx } from '@
|
|
|
13
15
|
import { withTheme } from '@dxos/storybook-utils';
|
|
14
16
|
|
|
15
17
|
import { TextEditor } from './TextEditor';
|
|
16
|
-
import { createHyperlinkTooltip,
|
|
18
|
+
import { createHyperlinkTooltip, hyperlink } from './extensions';
|
|
17
19
|
import { useTextModel } from '../../model';
|
|
18
20
|
|
|
19
|
-
const
|
|
20
|
-
'',
|
|
21
|
-
'',
|
|
22
|
-
'This is all about [DXOS](https://dxos.org); read more [here](https://docs.dxos.org/guide/getting-started.html).',
|
|
23
|
-
'',
|
|
24
|
-
'',
|
|
25
|
-
].join('\n');
|
|
21
|
+
export const nameRegex = /\{([\w_]+)}/;
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Simple Monaco language extension.
|
|
25
|
+
* https://github.com/codemirror/stream-parser/blob/main/test/test-stream-parser.ts
|
|
26
|
+
*/
|
|
27
|
+
export const promptLanguage = StreamLanguage.define<{ count: number }>({
|
|
28
|
+
startState: () => ({ count: 0 }),
|
|
29
|
+
|
|
30
|
+
token: (stream, state) => {
|
|
31
|
+
state.count++;
|
|
32
|
+
if (stream.eatSpace()) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (stream.match(/^(<\w+>)/)) {
|
|
36
|
+
return 'tagName';
|
|
37
|
+
}
|
|
38
|
+
stream.next();
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* https://codemirror.net/examples/styling
|
|
45
|
+
* https://lezer.codemirror.net/docs/ref/#highlight
|
|
46
|
+
*/
|
|
47
|
+
export const promptHighlightStyles = HighlightStyle.define([
|
|
48
|
+
{
|
|
49
|
+
tag: tags.tagName,
|
|
50
|
+
class: mx(
|
|
51
|
+
'mr-1 p-1',
|
|
52
|
+
'bg-neutral-100 dark:bg-neutral-900 text-black dark:text-white font-mono text-sm',
|
|
53
|
+
'rounded border border-neutral-300 dark:border-neutral-700',
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
export const defaultHyperLinkTooltip = createHyperlinkTooltip((el, url) => {
|
|
59
|
+
const web = new URL(url);
|
|
60
|
+
createRoot(el).render(
|
|
61
|
+
<StrictMode>
|
|
62
|
+
<div className='flex gap-1 items-center'>
|
|
63
|
+
<ArrowCircleUp className={mx(getSize(6), 'text-blue-500')} />
|
|
64
|
+
<p className='pr-1'>{web.origin}</p>
|
|
65
|
+
</div>
|
|
66
|
+
</StrictMode>,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
39
69
|
|
|
40
70
|
const Story = () => {
|
|
41
|
-
const [item] = useState({
|
|
71
|
+
const [item] = useState({
|
|
72
|
+
text: new TextObject(
|
|
73
|
+
'\n\n\nThis is all about [DXOS](https://dxos.org); read more [here](https://docs.dxos.org/guide/getting-started.html).\n\n\n',
|
|
74
|
+
),
|
|
75
|
+
});
|
|
76
|
+
|
|
42
77
|
const model = useTextModel({ text: item.text });
|
|
43
78
|
|
|
44
79
|
return (
|
|
45
|
-
<div className={mx(fixedInsetFlexLayout, groupSurface)}>
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
hyperlinkDecoration({ link: false }),
|
|
53
|
-
hyperLinkTooltip(),
|
|
54
|
-
// EditorView.domEventHandlers({
|
|
55
|
-
// mousedown: (e, view) => {},
|
|
56
|
-
// }),
|
|
57
|
-
]}
|
|
58
|
-
slots={{ root: { className: mx(inputSurface, 'p-2') } }}
|
|
59
|
-
/>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
80
|
+
<div className={mx(fixedInsetFlexLayout, groupSurface, 'p-4 gap-4')}>
|
|
81
|
+
<TextEditor
|
|
82
|
+
model={model}
|
|
83
|
+
slots={{ root: { className: mx(inputSurface, 'p-2') } }}
|
|
84
|
+
extensions={[hyperlink, defaultHyperLinkTooltip]}
|
|
85
|
+
/>
|
|
86
|
+
<pre>{JSON.stringify(model?.content?.toString(), null, 2)}</pre>
|
|
62
87
|
</div>
|
|
63
88
|
);
|
|
64
89
|
};
|
|
@@ -96,7 +96,6 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
96
96
|
: [syntaxHighlighting(defaultHighlightStyle)]),
|
|
97
97
|
|
|
98
98
|
// Replication.
|
|
99
|
-
// TODO(burdon): Move to default extensions.
|
|
100
99
|
...(content instanceof YText ? [yCollab(content, undefined)] : []),
|
|
101
100
|
|
|
102
101
|
// Custom.
|
|
@@ -15,20 +15,14 @@ import {
|
|
|
15
15
|
|
|
16
16
|
// Notes: Can't edit widget content (or cursor nav through them).
|
|
17
17
|
// - https://discuss.codemirror.net/t/focusing-inputs-within-widgets/5178/7
|
|
18
|
-
// Widgets intentionally always get set to contenteditable=false,
|
|
19
|
-
// or they would become part of CodeMirror’s editable content element.
|
|
18
|
+
// Widgets intentionally always get set to contenteditable=false, or they would become part of CodeMirror’s editable content element.
|
|
20
19
|
// You should be able to introduce new contenteditable=true child elements inside of them.
|
|
21
20
|
|
|
22
21
|
const markdownLinkRegexp = /\[([^\]]+)]\(([^)]+)\)(!?)/gi;
|
|
23
22
|
|
|
24
|
-
/**
|
|
25
|
-
* Custom link decorator.
|
|
26
|
-
* NOTE: The default markdown extension formats links by default.
|
|
27
|
-
* @deprecated
|
|
28
|
-
*/
|
|
29
23
|
const linkDecorator = () =>
|
|
30
24
|
new MatchDecorator({
|
|
31
|
-
regexp: markdownLinkRegexp,
|
|
25
|
+
regexp: markdownLinkRegexp, // regexp || defaultRegexp,
|
|
32
26
|
decorate: (add, from, to, match, view) => {
|
|
33
27
|
const [_, label, url] = match;
|
|
34
28
|
const p0 = {
|
|
@@ -51,21 +45,16 @@ const linkDecorator = () =>
|
|
|
51
45
|
end: p1.end + 1 + url.length + 2,
|
|
52
46
|
};
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
p0.end,
|
|
57
|
-
Decoration.mark({ class: 'cm-hyperlink-bracket', _attributes: { style: 'display: none' } }),
|
|
58
|
-
);
|
|
48
|
+
// TODO(burdon): Wrap with tagName?
|
|
49
|
+
add(p0.start, p0.end, Decoration.mark({ class: 'cm-hyperlink-bracket' }));
|
|
59
50
|
add(p1.start, p1.end, Decoration.mark({ class: 'cm-hyperlink-label' }));
|
|
60
|
-
add(
|
|
61
|
-
|
|
62
|
-
p2.end,
|
|
63
|
-
Decoration.mark({ class: 'cm-hyperlink-bracket', _attributes: { style: 'display: none' } }),
|
|
64
|
-
);
|
|
65
|
-
add(p3.start, p3.end, Decoration.mark({ class: 'cm-hyperlink-url', _attributes: { style: 'display: none' } }));
|
|
51
|
+
add(p2.start, p2.end, Decoration.mark({ class: 'cm-hyperlink-bracket' }));
|
|
52
|
+
add(p3.start, p3.end, Decoration.mark({ class: 'cm-hyperlink-url', __attributes: { style: 'display: none' } }));
|
|
66
53
|
},
|
|
67
54
|
});
|
|
68
55
|
|
|
56
|
+
// const anchor = (dom: HTMLAnchorElement) => dom;
|
|
57
|
+
|
|
69
58
|
export function hyperLinkExtension() {
|
|
70
59
|
return ViewPlugin.fromClass(
|
|
71
60
|
class HyperLinkView {
|
|
@@ -102,7 +91,7 @@ export function hyperLinkExtension() {
|
|
|
102
91
|
);
|
|
103
92
|
}
|
|
104
93
|
|
|
105
|
-
export const
|
|
94
|
+
export const hyperLinkStyle = EditorView.baseTheme({
|
|
106
95
|
'.cm-hyperlink-label': {
|
|
107
96
|
cursor: 'pointer',
|
|
108
97
|
textDecoration: 'underline',
|
|
@@ -116,4 +105,4 @@ export const hyperlinkStyle = EditorView.baseTheme({
|
|
|
116
105
|
},
|
|
117
106
|
});
|
|
118
107
|
|
|
119
|
-
export const
|
|
108
|
+
export const hyperlink: Extension = [hyperLinkExtension(), hyperLinkStyle];
|
|
@@ -15,7 +15,6 @@ export type OnHyperlinkHover = (el: Element, url: string) => void;
|
|
|
15
15
|
* https://codemirror.net/docs/ref/#view.hoverTooltip
|
|
16
16
|
* https://github.com/codemirror/view/blob/main/src/tooltip.ts
|
|
17
17
|
*/
|
|
18
|
-
// TODO(burdon): Create config object.
|
|
19
18
|
export const createHyperlinkTooltip = (onHover: OnHyperlinkHover, regexp = markdownLinkRegexp) =>
|
|
20
19
|
hoverTooltip((view, pos) => {
|
|
21
20
|
const { from, text } = view.state.doc.lineAt(pos);
|
|
@@ -10,7 +10,6 @@ import { tailwindConfig, type TailwindConfig } from '@dxos/react-ui-theme';
|
|
|
10
10
|
const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Minimal styles.
|
|
14
13
|
* https://codemirror.net/examples/styling
|
|
15
14
|
*/
|
|
16
15
|
// TODO(burdon): If given a "theme" suffix, `__docgen` properties are added to the object.
|
|
@@ -20,7 +19,10 @@ export const defaultStyles: {
|
|
|
20
19
|
'&.cm-focused': {
|
|
21
20
|
outline: 'none',
|
|
22
21
|
},
|
|
23
|
-
'.cm-
|
|
22
|
+
'.cm-placeholder': {
|
|
23
|
+
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
24
|
+
},
|
|
25
|
+
'& .cm-scroller': {
|
|
24
26
|
fontFamily: get(tokens, 'fontFamily.body', []).join(','),
|
|
25
27
|
overflow: 'visible',
|
|
26
28
|
},
|
|
@@ -28,8 +30,4 @@ export const defaultStyles: {
|
|
|
28
30
|
backgroundColor: 'transparent',
|
|
29
31
|
border: 'none',
|
|
30
32
|
},
|
|
31
|
-
'.cm-link': {
|
|
32
|
-
color: get(tokens, 'extend.colors.primary.500'),
|
|
33
|
-
textDecoration: 'underline',
|
|
34
|
-
},
|
|
35
33
|
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type * as A from '@dxos/automerge/automerge';
|
|
2
|
-
export type IDocHandle<T = any> = {
|
|
3
|
-
docSync(): A.Doc<T> | undefined;
|
|
4
|
-
change(callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): void;
|
|
5
|
-
changeAt(heads: A.Heads, callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): string[] | undefined;
|
|
6
|
-
addListener(event: 'change', listener: () => void): void;
|
|
7
|
-
removeListener(event: 'change', listener: () => void): void;
|
|
8
|
-
};
|
|
9
|
-
//# sourceMappingURL=handle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../../../../src/automerge/automerge-plugin/handle.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,CAAC,MAAM,2BAA2B,CAAC;AAEpD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,GAAG,IAAI;IAChC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACpE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IAEtG,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC7D,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type RangeSet, StateField } from '@codemirror/state';
|
|
2
|
-
type HyperlinkDecorationConfig = {
|
|
3
|
-
link?: boolean;
|
|
4
|
-
};
|
|
5
|
-
/**
|
|
6
|
-
* Creates a state field to replace AST elements with a hyperlink widget.
|
|
7
|
-
* https://codemirror.net/docs/ref/#state.StateField
|
|
8
|
-
*/
|
|
9
|
-
export declare const hyperlinkDecoration: ({ link }?: HyperlinkDecorationConfig) => StateField<RangeSet<any>>;
|
|
10
|
-
export {};
|
|
11
|
-
//# sourceMappingURL=hyperlinkDecoration.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hyperlinkDecoration.d.ts","sourceRoot":"","sources":["../../../../../../src/components/TextEditor/extensions/hyperlinkDecoration.ts"],"names":[],"mappings":"AAMA,OAAO,EAAoB,KAAK,QAAQ,EAAmB,UAAU,EAAoB,MAAM,mBAAmB,CAAC;AA6CnH,KAAK,yBAAyB,GAAG;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,cAAc,yBAAyB,8BAMtE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hyperlinkWidget.d.ts","sourceRoot":"","sources":["../../../../../../src/components/TextEditor/extensions/hyperlinkWidget.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAEL,KAAK,aAAa,EAElB,cAAc,EACd,UAAU,EACV,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAuD1B,wBAAgB,kBAAkB;wBAGR,cAAc;;mBAOnB,UAAU;GAwB9B;AAED,eAAO,MAAM,cAAc,WAYzB,CAAC;AAEH,eAAO,MAAM,eAAe,EAAE,SAAkD,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import type * as A from '@dxos/automerge/automerge';
|
|
6
|
-
|
|
7
|
-
export type IDocHandle<T = any> = {
|
|
8
|
-
docSync(): A.Doc<T> | undefined;
|
|
9
|
-
change(callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): void;
|
|
10
|
-
changeAt(heads: A.Heads, callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): string[] | undefined;
|
|
11
|
-
|
|
12
|
-
addListener(event: 'change', listener: () => void): void;
|
|
13
|
-
removeListener(event: 'change', listener: () => void): void;
|
|
14
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
// Copyright CodeMirror
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
import { syntaxTree } from '@codemirror/language';
|
|
7
|
-
import { type EditorState, type RangeSet, RangeSetBuilder, StateField, type Transaction } from '@codemirror/state';
|
|
8
|
-
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
|
9
|
-
|
|
10
|
-
// Adapted from:
|
|
11
|
-
// https://codemirror.net/try/?c=aW1wb3J0IHsgYmFzaWNTZXR1cCwgRWRpdG9yVmlldyB9IGZyb20gImNvZGVtaXJyb3IiCmltcG9ydCB7IG1hcmtkb3duIH0gZnJvbSAiQGNvZGVtaXJyb3IvbGFuZy1tYXJrZG93biIKaW1wb3J0IHsgc3ludGF4VHJlZSB9IGZyb20gIkBjb2RlbWlycm9yL2xhbmd1YWdlIjsKaW1wb3J0IHsgRWRpdG9yU3RhdGUsIFJhbmdlU2V0QnVpbGRlciwgU3RhdGVGaWVsZCB9IGZyb20gIkBjb2RlbWlycm9yL3N0YXRlIjsKaW1wb3J0IHsgRGVjb3JhdGlvbiwgV2lkZ2V0VHlwZSB9IGZyb20gIkBjb2RlbWlycm9yL3ZpZXciOwoKY2xhc3MgTGluayBleHRlbmRzIFdpZGdldFR5cGUgewogIGNvbnN0cnVjdG9yKHRleHQsIHVybCkgewogICAgc3VwZXIoKQogICAgdGhpcy50ZXh0ID0gdGV4dAogICAgdGhpcy51cmwgPSB1cmwKICB9CiAgZXEob3RoZXIpIHsgcmV0dXJuIHRoaXMudGV4dCA9PSBvdGhlci50ZXh0ICYmIHRoaXMudXJsID09IG90aGVyLnVybCB9CiAgdG9ET00oKSB7CiAgICBsZXQgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImEiKQogICAgbGluay50ZXh0Q29udGVudCA9IHRoaXMudGV4dAogICAgbGluay5ocmVmID0gdGhpcy51cmwKICAgIHJldHVybiBsaW5rOwogIH0KfQoKbGV0IGRlY29yYXRpb25zRmllbGQgPSBTdGF0ZUZpZWxkLmRlZmluZSh7CiAgY3JlYXRlKCkgewogICAgcmV0dXJuIERlY29yYXRpb24ubm9uZTsKICB9LAogIHVwZGF0ZShfLCB0cikgewogICAgY29uc3QgYnVpbGRlciA9IG5ldyBSYW5nZVNldEJ1aWxkZXIoKTsKICAgIGxldCBjdXJzb3IgPSB0ci5zdGF0ZS5zZWxlY3Rpb24ubWFpbi5oZWFkOwogICAgc3ludGF4VHJlZSh0ci5zdGF0ZSkuaXRlcmF0ZSh7CiAgICAgIGVudGVyOiAobm9kZSkgPT4gewogICAgICAgIGlmICgoY3Vyc29yIDwgbm9kZS5mcm9tIHx8IGN1cnNvciA+IG5vZGUudG8pICYmIG5vZGUubmFtZSA9PSAiTGluayIpIHsKICAgICAgICAgIGxldCBtYXJrcyA9IG5vZGUubm9kZS5nZXRDaGlsZHJlbigiTGlua01hcmsiKTsKICAgICAgICAgIGxldCB0ZXh0ID0gbWFya3MubGVuZ3RoID49IDIgPyB0ci5zdGF0ZS5zbGljZURvYyhtYXJrc1swXS50bywgbWFya3NbMV0uZnJvbSkgOiAiIgoKICAgICAgICAgIGxldCB1cmxOb2RlID0gbm9kZS5ub2RlLmdldENoaWxkKCJVUkwiKTsKICAgICAgICAgIGxldCB1cmwgPSB1cmxOb2RlID8gdHIuc3RhdGUuc2xpY2VEb2ModXJsTm9kZS5mcm9tLCB1cmxOb2RlLnRvKSA6ICIiCiAgICAgICAgICBidWlsZGVyLmFkZChub2RlLmZyb20sIG5vZGUudG8sIERlY29yYXRpb24ucmVwbGFjZSh7CiAgICAgICAgICAgIHdpZGdldDogbmV3IExpbmsodGV4dCwgdXJsKSwKICAgICAgICAgIH0pLAogICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgfSwKICAgIH0pOwogICAgcmV0dXJuIGJ1aWxkZXIuZmluaXNoKCk7CiAgfSwKICBwcm92aWRlOiAoZikgPT4gRWRpdG9yVmlldy5kZWNvcmF0aW9ucy5mcm9tKGYpLAp9KTsKCmxldCB2aWV3ID0gbmV3IEVkaXRvclZpZXcoewogIGRvYzogIiMgSGVsbG8gV29ybGRcblxuW1Rlc3RdKGh0dHBzOi8vZXhhbXBsZS5jb20pXG5cblxuIiwKICBleHRlbnNpb25zOiBbZGVjb3JhdGlvbnNGaWVsZCwgbWFya2Rvd24oKV0sCiAgcGFyZW50OiBkb2N1bWVudC5ib2R5LAp9KTsKCnZpZXcuZm9jdXMoKTs=
|
|
12
|
-
// https://discuss.codemirror.net/t/link-widget-is-clicked-only-if-editor-is-out-of-focus/7433
|
|
13
|
-
|
|
14
|
-
// TODO(burdon): Adapt to make all markdown editable?
|
|
15
|
-
|
|
16
|
-
// TODO(burdon): Research overlays.
|
|
17
|
-
// https://stackoverflow.com/questions/26933344/codemirror-detect-and-create-links-inside-editor
|
|
18
|
-
// https://codemirror.net/5/doc/manual.html#addOverlay
|
|
19
|
-
// https://codemirror.net/5/demo/simplemode.html
|
|
20
|
-
// https://codemirror.net/5/demo/search.html (Search dialog).
|
|
21
|
-
|
|
22
|
-
class Link extends WidgetType {
|
|
23
|
-
constructor(private readonly _anchor: number, private readonly _text: string, private readonly _url?: string) {
|
|
24
|
-
super();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
override eq(other: any) {
|
|
28
|
-
return this._text === other.text && this._url === other.url;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
toDOM(view: EditorView) {
|
|
32
|
-
const link = document.createElement('a');
|
|
33
|
-
link.setAttribute('class', 'cm-link');
|
|
34
|
-
link.textContent = this._text;
|
|
35
|
-
if (this._url) {
|
|
36
|
-
link.setAttribute('target', '_blank');
|
|
37
|
-
link.href = this._url;
|
|
38
|
-
} else {
|
|
39
|
-
link.onclick = () => {
|
|
40
|
-
view.dispatch({
|
|
41
|
-
selection: {
|
|
42
|
-
anchor: this._anchor,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return link;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type HyperlinkDecorationConfig = {
|
|
53
|
-
link?: boolean;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Creates a state field to replace AST elements with a hyperlink widget.
|
|
58
|
-
* https://codemirror.net/docs/ref/#state.StateField
|
|
59
|
-
*/
|
|
60
|
-
export const hyperlinkDecoration = ({ link }: HyperlinkDecorationConfig = {}) => {
|
|
61
|
-
return StateField.define<RangeSet<any>>({
|
|
62
|
-
create: (state) => update(state, link),
|
|
63
|
-
update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, link),
|
|
64
|
-
provide: (field) => EditorView.decorations.from(field),
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const update = (state: EditorState, link?: boolean) => {
|
|
69
|
-
const builder = new RangeSetBuilder();
|
|
70
|
-
const cursor = state.selection.main.head;
|
|
71
|
-
syntaxTree(state).iterate({
|
|
72
|
-
enter: (node) => {
|
|
73
|
-
// Check if cursor is inside text.
|
|
74
|
-
if (cursor < node.from || cursor > node.to) {
|
|
75
|
-
if (node.name === 'Link') {
|
|
76
|
-
const marks = node.node.getChildren('LinkMark');
|
|
77
|
-
const text = marks.length >= 2 ? state.sliceDoc(marks[0].to, marks[1].from) : '';
|
|
78
|
-
|
|
79
|
-
const urlNode = node.node.getChild('URL');
|
|
80
|
-
const url = urlNode ? state.sliceDoc(urlNode.from, urlNode.to) : '';
|
|
81
|
-
|
|
82
|
-
builder.add(
|
|
83
|
-
node.from,
|
|
84
|
-
node.to,
|
|
85
|
-
Decoration.replace({
|
|
86
|
-
widget: new Link(node.from, text, link ? url : undefined),
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return true;
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
return builder.finish();
|
|
99
|
-
};
|