@dxos/react-ui-editor 0.4.4 → 0.4.5-main.17763ce

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.
Files changed (46) hide show
  1. package/dist/lib/browser/index.mjs +421 -540
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +5 -5
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
  7. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  8. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  9. package/dist/types/src/extensions/cursor-line-margin.d.ts +8 -0
  10. package/dist/types/src/extensions/cursor-line-margin.d.ts.map +1 -0
  11. package/dist/types/src/extensions/index.d.ts +1 -0
  12. package/dist/types/src/extensions/index.d.ts.map +1 -1
  13. package/dist/types/src/extensions/markdown/decorate.d.ts +5 -0
  14. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
  15. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  16. package/dist/types/src/extensions/markdown/index.d.ts +1 -4
  17. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  18. package/dist/types/src/extensions/markdown/link.d.ts +1 -11
  19. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  20. package/dist/types/src/styles/layout.d.ts +2 -2
  21. package/dist/types/src/styles/layout.d.ts.map +1 -1
  22. package/package.json +31 -28
  23. package/src/components/TextEditor/MarkdownEditor.stories.tsx +29 -37
  24. package/src/components/TextEditor/hooks.stories.tsx +5 -17
  25. package/src/components/Toolbar/Toolbar.stories.tsx +4 -7
  26. package/src/components/Toolbar/Toolbar.tsx +0 -1
  27. package/src/extensions/cursor-line-margin.ts +92 -0
  28. package/src/extensions/index.ts +1 -0
  29. package/src/extensions/markdown/decorate.ts +301 -0
  30. package/src/extensions/markdown/formatting.test.ts +17 -0
  31. package/src/extensions/markdown/formatting.ts +67 -76
  32. package/src/extensions/markdown/index.ts +1 -4
  33. package/src/extensions/markdown/link.ts +23 -185
  34. package/src/styles/layout.ts +3 -2
  35. package/dist/types/src/extensions/markdown/code.d.ts +0 -2
  36. package/dist/types/src/extensions/markdown/code.d.ts.map +0 -1
  37. package/dist/types/src/extensions/markdown/heading.d.ts +0 -6
  38. package/dist/types/src/extensions/markdown/heading.d.ts.map +0 -1
  39. package/dist/types/src/extensions/markdown/hr.d.ts +0 -2
  40. package/dist/types/src/extensions/markdown/hr.d.ts.map +0 -1
  41. package/dist/types/src/extensions/markdown/tasklist.d.ts +0 -3
  42. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +0 -1
  43. package/src/extensions/markdown/code.ts +0 -162
  44. package/src/extensions/markdown/heading.ts +0 -59
  45. package/src/extensions/markdown/hr.ts +0 -71
  46. package/src/extensions/markdown/tasklist.ts +0 -110
@@ -1,162 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import {
8
- ViewPlugin,
9
- type DecorationSet,
10
- EditorView,
11
- type ViewUpdate,
12
- Decoration,
13
- type BlockInfo,
14
- WidgetType,
15
- } from '@codemirror/view';
16
-
17
- import { mx } from '@dxos/react-ui-theme';
18
-
19
- import { getToken } from '../../styles';
20
-
21
- // TODO(burdon): Reconcile with theme.
22
- const styles = EditorView.baseTheme({
23
- '& .cm-code': {
24
- fontFamily: getToken('fontFamily.mono', []).join(','),
25
- },
26
- '& .cm-codeblock-line': {
27
- paddingInline: '1rem !important',
28
- },
29
-
30
- '& .cm-codeblock-end': {
31
- display: 'inline-block',
32
- width: '100%',
33
- position: 'relative',
34
- '&::after': {
35
- position: 'absolute',
36
- inset: 0,
37
- content: '""',
38
- },
39
- },
40
- '& .cm-codeblock-first': {
41
- '&::after': {
42
- borderTopLeftRadius: '.5rem',
43
- borderTopRightRadius: '.5rem',
44
- },
45
- },
46
- '& .cm-codeblock-last': {
47
- '&::after': {
48
- borderBottomLeftRadius: '.5rem',
49
- borderBottomRightRadius: '.5rem',
50
- },
51
- },
52
-
53
- '&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
54
- background: getToken('extend.semanticColors.input.light'),
55
- mixBlendMode: 'darken',
56
- },
57
- '&dark .cm-codeblock-line, &dark .cm-activeLine.cm-codeblock-line': {
58
- background: getToken('extend.semanticColors.input.dark'),
59
- mixBlendMode: 'lighten',
60
- },
61
- '&light .cm-codeblock-first, &light .cm-codeblock-last': {
62
- mixBlendMode: 'darken',
63
- '&::after': {
64
- background: getToken('extend.semanticColors.input.light'),
65
- },
66
- },
67
- '&dark .cm-codeblock-first, &dark .cm-codeblock-last': {
68
- mixBlendMode: 'lighten',
69
- '&::after': {
70
- background: getToken('extend.semanticColors.input.dark'),
71
- },
72
- },
73
- });
74
-
75
- // TODO(burdon): Start from EditorView.lineBlockAt(pos).
76
- const getLineRange = (lines: BlockInfo[], from: number, to: number) => {
77
- const start = lines.findIndex((line) => line.from >= from);
78
- const end = lines.findIndex((line) => line.to >= to);
79
- return [start, end];
80
- };
81
-
82
- /**
83
- * Widget for first/last line of code block (read-only).
84
- */
85
- // TODO(burdon): Add copy-to-clipboard button.
86
- class LineWidget extends WidgetType {
87
- constructor(private readonly _className: string) {
88
- super();
89
- }
90
-
91
- override toDOM() {
92
- const el = document.createElement('span');
93
- el.innerHTML = ' ';
94
- el.className = mx('cm-code cm-codeblock', this._className);
95
- return el;
96
- }
97
- }
98
-
99
- const top = new LineWidget('cm-codeblock-end cm-codeblock-first');
100
- const bottom = new LineWidget('cm-codeblock-end cm-codeblock-last');
101
-
102
- const buildDecorations = (view: EditorView): DecorationSet => {
103
- const builder = new RangeSetBuilder<Decoration>();
104
- const { state } = view;
105
- const blocks = view.viewportLineBlocks;
106
- const cursor = state.selection.main.head;
107
-
108
- for (const { from, to } of view.visibleRanges) {
109
- syntaxTree(state).iterate({
110
- enter: (node) => {
111
- if (node.name === 'FencedCode') {
112
- // FencedCode > CodeMark > [CodeInfo] > CodeText > CodeMark
113
- const editing = !view.state.readOnly && cursor >= node.from && cursor <= node.to;
114
- const range = getLineRange(blocks, node.from, node.to);
115
- for (let i = range[0]; i <= range[1]; i++) {
116
- const block = blocks[i];
117
- if (!editing && (i === range[0] || i === range[1])) {
118
- builder.add(block.from, block.to, Decoration.replace({ widget: i === range[0] ? top : bottom }));
119
- } else {
120
- builder.add(
121
- block.from,
122
- block.from,
123
- Decoration.line({
124
- class: mx(
125
- 'cm-code cm-codeblock-line',
126
- i === range[0] && 'cm-codeblock-first',
127
- i === range[1] && 'cm-codeblock-last',
128
- ),
129
- }),
130
- );
131
- }
132
- }
133
- }
134
- },
135
- from,
136
- to,
137
- });
138
- }
139
-
140
- return builder.finish();
141
- };
142
-
143
- export const code = () => {
144
- return [
145
- ViewPlugin.fromClass(
146
- class {
147
- decorations: DecorationSet;
148
- constructor(view: EditorView) {
149
- this.decorations = buildDecorations(view);
150
- }
151
-
152
- update(update: ViewUpdate) {
153
- this.decorations = buildDecorations(update.view);
154
- }
155
- },
156
- {
157
- decorations: (value) => value.decorations,
158
- },
159
- ),
160
- styles,
161
- ];
162
- };
@@ -1,59 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { Decoration, type DecorationSet, type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
8
-
9
- const buildDecorations = (view: EditorView): DecorationSet => {
10
- const builder = new RangeSetBuilder<Decoration>();
11
- const { state } = view;
12
- const cursor = state.selection.main.head;
13
-
14
- for (const { from, to } of view.visibleRanges) {
15
- syntaxTree(state).iterate({
16
- enter: (node) => {
17
- switch (node.name) {
18
- case 'ATXHeading1':
19
- case 'ATXHeading2':
20
- case 'ATXHeading3':
21
- case 'ATXHeading4':
22
- case 'ATXHeading5':
23
- case 'ATXHeading6': {
24
- const mark = node.node.getChild('HeaderMark');
25
- if (mark) {
26
- if (!view.hasFocus || view.state.readOnly || cursor < node.from || cursor > node.to) {
27
- builder.add(mark.from, mark.to + 1, Decoration.replace({}));
28
- }
29
- }
30
- }
31
- }
32
- },
33
- from,
34
- to,
35
- });
36
- }
37
-
38
- return builder.finish();
39
- };
40
-
41
- export const heading = () => {
42
- return [
43
- ViewPlugin.fromClass(
44
- class {
45
- decorations: DecorationSet;
46
- constructor(view: EditorView) {
47
- this.decorations = buildDecorations(view);
48
- }
49
-
50
- update(update: ViewUpdate) {
51
- this.decorations = buildDecorations(update.view);
52
- }
53
- },
54
- {
55
- decorations: (value) => value.decorations,
56
- },
57
- ),
58
- ];
59
- };
@@ -1,71 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
-
9
- import { getToken } from '../../styles';
10
-
11
- // TODO(burdon): Reconcile with theme.
12
- const styles = EditorView.baseTheme({
13
- '& .cm-hr': {
14
- // Note that block-level decorations should not have vertical margins,
15
- borderTop: `1px solid ${getToken('extend.colors.neutral.200')}`,
16
- },
17
- });
18
-
19
- class HorizontalRuleWidget extends WidgetType {
20
- override toDOM() {
21
- const el = document.createElement('div');
22
- el.className = 'cm-hr';
23
- return el;
24
- }
25
- }
26
-
27
- const decoration = Decoration.replace({ widget: new HorizontalRuleWidget() });
28
-
29
- const buildDecorations = (view: EditorView): DecorationSet => {
30
- const builder = new RangeSetBuilder<Decoration>();
31
- const { state } = view;
32
- const cursor = state.selection.main.head;
33
-
34
- for (const { from, to } of view.visibleRanges) {
35
- syntaxTree(state).iterate({
36
- enter: (node) => {
37
- if (node.name === 'HorizontalRule') {
38
- // Check if cursor is inside text.
39
- if (cursor <= node.from || cursor >= node.to) {
40
- builder.add(node.from, node.to, decoration);
41
- }
42
- }
43
- },
44
- from,
45
- to,
46
- });
47
- }
48
-
49
- return builder.finish();
50
- };
51
-
52
- export const hr = () => {
53
- return [
54
- ViewPlugin.fromClass(
55
- class {
56
- decorations: DecorationSet;
57
- constructor(view: EditorView) {
58
- this.decorations = buildDecorations(view);
59
- }
60
-
61
- update(update: ViewUpdate) {
62
- this.decorations = buildDecorations(update.view);
63
- }
64
- },
65
- {
66
- decorations: (value) => value.decorations,
67
- },
68
- ),
69
- styles,
70
- ];
71
- };
@@ -1,110 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { syntaxTree } from '@codemirror/language';
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { EditorView, Decoration, WidgetType, ViewPlugin, type DecorationSet, type ViewUpdate } from '@codemirror/view';
8
-
9
- import { getToken } from '../../styles';
10
-
11
- // TODO(burdon): Reconcile with theme.
12
- const styles = EditorView.baseTheme({
13
- '& .cm-task': {
14
- color: getToken('extend.colors.blue.500'),
15
- },
16
- '& .cm-task-checkbox': {
17
- marginLeft: '4px',
18
- marginRight: '4px',
19
- },
20
- });
21
-
22
- class CheckboxWidget extends WidgetType {
23
- constructor(private _checked: boolean) {
24
- super();
25
- }
26
-
27
- override eq(other: this) {
28
- return this._checked === other._checked;
29
- }
30
-
31
- override toDOM(view: EditorView) {
32
- const input = document.createElement('input');
33
- input.className = 'cm-task-checkbox';
34
- input.type = 'checkbox';
35
- input.checked = this._checked;
36
- if (view.state.readOnly) {
37
- input.setAttribute('disabled', 'true');
38
- } else {
39
- input.onmousedown = (event: Event) => {
40
- const pos = view.posAtDOM(input);
41
- const text = view.state.sliceDoc(pos, pos + 3);
42
- if (text === (this._checked ? '[x]' : '[ ]')) {
43
- view.dispatch({
44
- changes: { from: pos + 1, to: pos + 2, insert: this._checked ? ' ' : 'x' },
45
- });
46
- event.preventDefault();
47
- }
48
- };
49
- }
50
-
51
- return input;
52
- }
53
-
54
- override ignoreEvent() {
55
- return false;
56
- }
57
- }
58
-
59
- const checkedDecoration = Decoration.replace({ widget: new CheckboxWidget(true) });
60
- const uncheckedDecoration = Decoration.replace({ widget: new CheckboxWidget(false) });
61
-
62
- const buildDecorations = (view: EditorView): DecorationSet => {
63
- const builder = new RangeSetBuilder<Decoration>();
64
- const { state } = view;
65
- const cursor = state.selection.main.head;
66
-
67
- for (const { from, to } of view.visibleRanges) {
68
- syntaxTree(state).iterate({
69
- enter: (node) => {
70
- if (node.name === 'TaskMarker') {
71
- // Check if cursor is inside text.
72
- if (cursor < node.from || cursor > node.to) {
73
- const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
74
- builder.add(node.from - 2, node.from - 1, Decoration.mark({ class: 'cm-task' }));
75
- builder.add(node.from, node.to, checked ? checkedDecoration : uncheckedDecoration);
76
- }
77
- }
78
- },
79
- from,
80
- to,
81
- });
82
- }
83
-
84
- return builder.finish();
85
- };
86
-
87
- export type TasklistOptions = {};
88
-
89
- export const tasklist = (options: TasklistOptions = {}) => {
90
- return [
91
- ViewPlugin.fromClass(
92
- class {
93
- decorations: DecorationSet;
94
- constructor(view: EditorView) {
95
- this.decorations = buildDecorations(view);
96
- }
97
-
98
- update(update: ViewUpdate) {
99
- if (update.docChanged || update.viewportChanged || update.selectionSet) {
100
- this.decorations = buildDecorations(update.view);
101
- }
102
- }
103
- },
104
- {
105
- decorations: (value) => value.decorations,
106
- },
107
- ),
108
- styles,
109
- ];
110
- };