@dxos/react-ui-editor 0.6.7 → 0.6.8-main.3be982f

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 (109) hide show
  1. package/dist/lib/browser/index.mjs +1019 -796
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/{hooks/InputMode.stories.d.ts → InputMode.stories.d.ts} +6 -4
  5. package/dist/types/src/InputMode.stories.d.ts.map +1 -0
  6. package/dist/types/src/{hooks/TextEditor.stories.d.ts → TextEditor.stories.d.ts} +43 -26
  7. package/dist/types/src/TextEditor.stories.d.ts.map +1 -0
  8. package/dist/types/src/components/Toolbar/Toolbar.d.ts +9 -9
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/defaults.d.ts +10 -0
  11. package/dist/types/src/defaults.d.ts.map +1 -0
  12. package/dist/types/src/extensions/autocomplete.d.ts +2 -2
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +6 -4
  15. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  18. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  19. package/dist/types/src/extensions/command/state.d.ts +1 -1
  20. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  21. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  22. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  23. package/dist/types/src/extensions/folding.d.ts +7 -0
  24. package/dist/types/src/extensions/folding.d.ts.map +1 -0
  25. package/dist/types/src/extensions/index.d.ts +1 -0
  26. package/dist/types/src/extensions/index.d.ts.map +1 -1
  27. package/dist/types/src/extensions/markdown/decorate.d.ts +5 -1
  28. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  29. package/dist/types/src/extensions/markdown/formatting.d.ts +9 -9
  30. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  31. package/dist/types/src/extensions/markdown/image.d.ts +1 -1
  32. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  33. package/dist/types/src/extensions/markdown/link-paste.d.ts +6 -0
  34. package/dist/types/src/extensions/markdown/link-paste.d.ts.map +1 -0
  35. package/dist/types/src/extensions/markdown/link-paste.test.d.ts +2 -0
  36. package/dist/types/src/extensions/markdown/link-paste.test.d.ts.map +1 -0
  37. package/dist/types/src/extensions/markdown/parser.test.d.ts +2 -0
  38. package/dist/types/src/extensions/markdown/parser.test.d.ts.map +1 -0
  39. package/dist/types/src/extensions/state.d.ts.map +1 -1
  40. package/dist/types/src/extensions/util/error.d.ts +2 -0
  41. package/dist/types/src/extensions/util/error.d.ts.map +1 -0
  42. package/dist/types/src/extensions/util/index.d.ts +3 -1
  43. package/dist/types/src/extensions/util/index.d.ts.map +1 -1
  44. package/dist/types/src/extensions/util/overlap.d.ts.map +1 -1
  45. package/dist/types/src/extensions/util/react.d.ts +3 -0
  46. package/dist/types/src/extensions/util/react.d.ts.map +1 -0
  47. package/dist/types/src/hooks/useActionHandler.d.ts +1 -1
  48. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  49. package/dist/types/src/index.d.ts +1 -2
  50. package/dist/types/src/index.d.ts.map +1 -1
  51. package/dist/types/src/styles/index.d.ts +1 -1
  52. package/dist/types/src/styles/index.d.ts.map +1 -1
  53. package/dist/types/src/styles/markdown.d.ts +0 -1
  54. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  55. package/dist/types/src/styles/theme.d.ts +36 -0
  56. package/dist/types/src/styles/theme.d.ts.map +1 -0
  57. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  58. package/dist/types/src/translations.d.ts +2 -0
  59. package/dist/types/src/translations.d.ts.map +1 -1
  60. package/dist/types/src/util.d.ts.map +1 -1
  61. package/package.json +26 -29
  62. package/src/{hooks/InputMode.stories.tsx → InputMode.stories.tsx} +6 -11
  63. package/src/{hooks/TextEditor.stories.tsx → TextEditor.stories.tsx} +139 -92
  64. package/src/components/Toolbar/Toolbar.tsx +17 -9
  65. package/src/defaults.ts +28 -0
  66. package/src/extensions/autocomplete.ts +24 -18
  67. package/src/extensions/automerge/automerge.stories.tsx +4 -6
  68. package/src/extensions/comments.ts +4 -0
  69. package/src/extensions/factories.ts +3 -2
  70. package/src/extensions/folding.tsx +34 -0
  71. package/src/extensions/index.ts +1 -0
  72. package/src/extensions/markdown/bundle.ts +1 -1
  73. package/src/extensions/markdown/decorate.ts +359 -129
  74. package/src/extensions/markdown/formatting.ts +10 -12
  75. package/src/extensions/markdown/image.ts +3 -1
  76. package/src/extensions/markdown/link-paste.test.ts +28 -0
  77. package/src/extensions/markdown/link-paste.ts +104 -0
  78. package/src/extensions/markdown/parser.test.ts +47 -0
  79. package/src/extensions/markdown/table.ts +21 -24
  80. package/src/extensions/util/error.ts +15 -0
  81. package/src/extensions/util/index.ts +3 -1
  82. package/src/extensions/util/overlap.ts +1 -0
  83. package/src/extensions/util/react.tsx +15 -0
  84. package/src/hooks/useTextEditor.ts +1 -1
  85. package/src/index.ts +2 -2
  86. package/src/styles/index.ts +1 -1
  87. package/src/styles/markdown.ts +4 -3
  88. package/src/{themes/default.ts → styles/theme.ts} +51 -43
  89. package/src/styles/tokens.ts +0 -1
  90. package/src/translations.ts +2 -0
  91. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +0 -57
  92. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +0 -1
  93. package/dist/types/src/extensions/markdown/linkPaste.d.ts +0 -16
  94. package/dist/types/src/extensions/markdown/linkPaste.d.ts.map +0 -1
  95. package/dist/types/src/extensions/markdown/linkPaste.test.d.ts +0 -2
  96. package/dist/types/src/extensions/markdown/linkPaste.test.d.ts.map +0 -1
  97. package/dist/types/src/hooks/InputMode.stories.d.ts.map +0 -1
  98. package/dist/types/src/hooks/TextEditor.stories.d.ts.map +0 -1
  99. package/dist/types/src/styles/layout.d.ts +0 -4
  100. package/dist/types/src/styles/layout.d.ts.map +0 -1
  101. package/dist/types/src/themes/default.d.ts +0 -14
  102. package/dist/types/src/themes/default.d.ts.map +0 -1
  103. package/dist/types/src/themes/index.d.ts +0 -2
  104. package/dist/types/src/themes/index.d.ts.map +0 -1
  105. package/src/components/Toolbar/Toolbar.stories.tsx +0 -119
  106. package/src/extensions/markdown/linkPaste.test.ts +0 -45
  107. package/src/extensions/markdown/linkPaste.ts +0 -113
  108. package/src/styles/layout.ts +0 -9
  109. package/src/themes/index.ts +0 -5
@@ -26,10 +26,10 @@ import { useMemo, useState } from 'react';
26
26
  // the field only holds true when *all* selected text has the style,
27
27
  // or when the selection is a cursor inside such a style.
28
28
  export type Formatting = {
29
- blankLine: boolean;
29
+ blankLine?: boolean;
30
30
  // The type of the block at the selection.
31
31
  // If multiple different block types are selected, this will hold null.
32
- blockType:
32
+ blockType?:
33
33
  | 'codeblock'
34
34
  | 'heading1'
35
35
  | 'heading2'
@@ -41,19 +41,19 @@ export type Formatting = {
41
41
  | 'tablecell'
42
42
  | null;
43
43
  // Whether all selected text is wrapped in a blockquote.
44
- blockQuote: boolean;
44
+ blockQuote?: boolean;
45
45
  // Whether the selected text is strong.
46
- strong: boolean;
46
+ strong?: boolean;
47
47
  // Whether the selected text is emphasized.
48
- emphasis: boolean;
48
+ emphasis?: boolean;
49
49
  // Whether the selected text is stricken through.
50
- strikethrough: boolean;
50
+ strikethrough?: boolean;
51
51
  // Whether the selected text is inline code.
52
- code: boolean;
52
+ code?: boolean;
53
53
  // Whether there are links in the selected text.
54
- link: boolean;
54
+ link?: boolean;
55
55
  // If all selected blocks have the same (innermost) list style, that is indicated here.
56
- listStyle: null | 'ordered' | 'bullet' | 'task';
56
+ listStyle?: null | 'ordered' | 'bullet' | 'task';
57
57
  };
58
58
 
59
59
  export const formattingEquals = (a: Formatting, b: Formatting) =>
@@ -705,7 +705,6 @@ export const addList = (type: List): StateCommand => {
705
705
  renumberListItems(next.firstChild, last.counter + 1, changes, state.doc);
706
706
  }
707
707
  }
708
- ('Oeswe');
709
708
  const changeSet = state.changes(changes);
710
709
  dispatch(
711
710
  state.update({
@@ -1249,7 +1248,6 @@ export const getFormatting = (state: EditorState): Formatting => {
1249
1248
  */
1250
1249
  export const useFormattingState = (): [Formatting | undefined, Extension] => {
1251
1250
  const [state, setState] = useState<Formatting>();
1252
-
1253
1251
  const observer = useMemo(
1254
1252
  () =>
1255
1253
  EditorView.updateListener.of((update) => {
@@ -1263,7 +1261,7 @@ export const useFormattingState = (): [Formatting | undefined, Extension] => {
1263
1261
  });
1264
1262
  }
1265
1263
  }),
1266
- [setState],
1264
+ [],
1267
1265
  );
1268
1266
 
1269
1267
  return [state, observer];
@@ -8,7 +8,7 @@ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemir
8
8
 
9
9
  export type ImageOptions = {};
10
10
 
11
- export const image = (options: ImageOptions = {}): Extension => {
11
+ export const image = (_options: ImageOptions = {}): Extension => {
12
12
  return StateField.define<DecorationSet>({
13
13
  create: (state) => {
14
14
  return Decoration.set(buildDecorations(0, state.doc.length, state));
@@ -17,6 +17,7 @@ export const image = (options: ImageOptions = {}): Extension => {
17
17
  if (!tr.docChanged && !tr.selection) {
18
18
  return value;
19
19
  }
20
+
20
21
  // Find range of changes and cursor changes.
21
22
  const cursor = tr.state.selection.main.head;
22
23
  const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
@@ -26,6 +27,7 @@ export const image = (options: ImageOptions = {}): Extension => {
26
27
  from = Math.min(from, fromB);
27
28
  to = Math.max(to, toB);
28
29
  });
30
+
29
31
  // Expand to cover lines.
30
32
  from = tr.state.doc.lineAt(from).from;
31
33
  to = tr.state.doc.lineAt(to).to;
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { expect } from 'chai';
6
+
7
+ import { describe, test } from '@dxos/test';
8
+
9
+ import { createLinkLabel } from './link-paste';
10
+
11
+ const testCases = [
12
+ { input: 'https://www.example.com', expected: 'example.com' },
13
+ { input: 'http://example.com', expected: 'example.com' },
14
+ { input: 'https://example.com/', expected: 'example.com' },
15
+ { input: 'https://www.example.com/', expected: 'example.com' },
16
+ { input: 'https://example.com/test', expected: 'example.com/test' },
17
+ { input: 'https://www.example.com/test', expected: 'example.com/test' },
18
+ { input: 'https://example.com?name=value', expected: 'example.com' },
19
+ { input: 'ftp://example.com', expected: 'ftp://example.com' },
20
+ ];
21
+
22
+ describe('links', () => {
23
+ test('createLinkLabel', () => {
24
+ testCases.forEach(({ input, expected }) => {
25
+ expect(createLinkLabel(new URL(input))).to.eq(expected);
26
+ });
27
+ });
28
+ });
@@ -0,0 +1,104 @@
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
+ export const linkPastePlugin = ViewPlugin.fromClass(
11
+ class implements PluginValue {
12
+ update(update: ViewUpdate) {
13
+ for (const tr of update.transactions) {
14
+ const event = tr.annotation(Transaction.userEvent);
15
+ if (event === 'input.paste') {
16
+ const changes = tr.changes;
17
+ if (changes.empty) {
18
+ return;
19
+ }
20
+
21
+ changes.iterChangedRanges((fromA, toA, fromB, toB) => {
22
+ const insertedUrl = getValidUrl(update.view.state.sliceDoc(fromB, toB));
23
+ if (insertedUrl && isValidPosition(update.view.state, fromB)) {
24
+ // We might be pasting over an existing text.
25
+ const replacedText = tr.startState.sliceDoc(fromA, toA);
26
+ setTimeout(() => {
27
+ update.view.dispatch(
28
+ update.view.state.update({
29
+ changes: { from: fromA, to: toB, insert: createLink(insertedUrl, replacedText) },
30
+ }),
31
+ );
32
+ });
33
+ }
34
+ });
35
+ }
36
+ }
37
+ }
38
+ },
39
+ );
40
+
41
+ const createLink = (url: URL, label: string): string => {
42
+ // Check if image.
43
+ // Example: https://dxos.network/dxos-logotype-blue.png
44
+ const { host, pathname } = url;
45
+ const [, extension] = pathname.split('.');
46
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
47
+ if (imageExtensions.includes(extension)) {
48
+ return `![${label || host}](${url})`;
49
+ }
50
+
51
+ if (!label) {
52
+ label = createLinkLabel(url);
53
+ }
54
+
55
+ return `[${label}](${url})`;
56
+ };
57
+
58
+ export const createLinkLabel = (url: URL): string => {
59
+ let { protocol, host, pathname } = url;
60
+ if (protocol === 'http:' || protocol === 'https:') {
61
+ protocol = '';
62
+ }
63
+
64
+ // NOTE(Zan): Consult: https://github.com/dxos/dxos/issues/7331 before changing this.
65
+ // Remove 'www.' if at the beginning of the URL
66
+ host = host.replace(/^www\./, '');
67
+
68
+ return [protocol, host].filter(Boolean).join('//') + (pathname !== '/' ? pathname : '');
69
+ };
70
+
71
+ /**
72
+ * Returns a valid URL if appropriate for a link.
73
+ */
74
+ const getValidUrl = (str: string): URL | undefined => {
75
+ const validProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
76
+ try {
77
+ const url = new URL(str);
78
+ if (!validProtocols.includes(url.protocol)) {
79
+ return undefined;
80
+ }
81
+
82
+ return url;
83
+ } catch (_err) {
84
+ return undefined;
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Traverses the syntax tree upwards from the position.
90
+ */
91
+ const isValidPosition = (state: EditorState, pos: number): boolean => {
92
+ const invalidPositions = new Set(['Link', 'LinkMark', 'Code', 'FencedCode']);
93
+ const tree = syntaxTree(state);
94
+ let node: SyntaxNode | null = tree.resolveInner(pos, -1);
95
+ while (node) {
96
+ if (invalidPositions.has(node.name)) {
97
+ return false;
98
+ }
99
+
100
+ node = node.parent;
101
+ }
102
+
103
+ return true;
104
+ };
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ // @ts-ignore
6
+ import { testTree } from '@lezer/generator/test';
7
+ import { parser } from '@lezer/markdown';
8
+
9
+ import { describe, test } from '@dxos/test';
10
+
11
+ describe('parser', () => {
12
+ // https://www.markdownguide.org/basic-syntax/#lists-1
13
+ test('lists', () => {
14
+ // Indented list must have 4 spaces.
15
+ const result = parser.parse(
16
+ [
17
+ '# H1',
18
+ '1. one',
19
+ '2. two',
20
+ '3. three',
21
+ ' 1. four',
22
+ '',
23
+ // TODO(burdon): Test list termination without heading as break.
24
+ '# H2',
25
+ '1. one',
26
+ ].join('\n'),
27
+ );
28
+ testTree(
29
+ result,
30
+ [
31
+ 'Document(',
32
+ 'ATXHeading1(HeaderMark)',
33
+ 'OrderedList(',
34
+ 'ListItem(ListMark,Paragraph),',
35
+ 'ListItem(ListMark,Paragraph),',
36
+ 'ListItem(ListMark,Paragraph,OrderedList(ListItem(ListMark,Paragraph)))',
37
+ ')',
38
+ '',
39
+ 'ATXHeading1(HeaderMark)',
40
+ 'OrderedList(',
41
+ 'ListItem(ListMark,Paragraph),',
42
+ ')',
43
+ ')',
44
+ ].join(''),
45
+ );
46
+ });
47
+ });
@@ -39,7 +39,7 @@ type Table = {
39
39
  rows?: string[][];
40
40
  };
41
41
 
42
- const update = (state: EditorState, options: TableOptions) => {
42
+ const update = (state: EditorState, _options: TableOptions) => {
43
43
  const builder = new RangeSetBuilder();
44
44
  const cursor = state.selection.main.head;
45
45
 
@@ -114,31 +114,28 @@ class TableWidget extends WidgetType {
114
114
  }
115
115
 
116
116
  override toDOM(view: EditorView) {
117
- const table = document.createElement('table');
118
-
119
- {
120
- const header = table.appendChild(document.createElement('thead'));
121
- const tr = header.appendChild(document.createElement('tr'));
122
- this._table.header?.forEach((cell) => {
123
- const th = document.createElement('th');
124
- th.setAttribute('class', 'cm-table-head');
125
- tr.appendChild(th).textContent = cell;
117
+ const div = document.createElement('div');
118
+ const table = div.appendChild(document.createElement('table'));
119
+
120
+ const header = table.appendChild(document.createElement('thead'));
121
+ const tr = header.appendChild(document.createElement('tr'));
122
+ this._table.header?.forEach((cell) => {
123
+ const th = document.createElement('th');
124
+ th.setAttribute('class', 'cm-table-head');
125
+ tr.appendChild(th).textContent = cell;
126
+ });
127
+
128
+ const body = table.appendChild(document.createElement('tbody'));
129
+ this._table.rows?.forEach((row) => {
130
+ const tr = body.appendChild(document.createElement('tr'));
131
+ row.forEach((cell) => {
132
+ const td = document.createElement('td');
133
+ td.setAttribute('class', 'cm-table-cell');
134
+ tr.appendChild(td).textContent = cell;
126
135
  });
127
- }
128
-
129
- {
130
- const body = table.appendChild(document.createElement('tbody'));
131
- this._table.rows?.forEach((row) => {
132
- const tr = body.appendChild(document.createElement('tr'));
133
- row.forEach((cell) => {
134
- const td = document.createElement('td');
135
- td.setAttribute('class', 'cm-table-cell');
136
- tr.appendChild(td).textContent = cell;
137
- });
138
- });
139
- }
136
+ });
140
137
 
141
- return table;
138
+ return div;
142
139
  }
143
140
 
144
141
  override ignoreEvent(e: Event) {
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { log } from '@dxos/log';
6
+
7
+ export const wrapWithCatch = (fn: (...args: any[]) => any) => {
8
+ return (...args: any[]) => {
9
+ try {
10
+ return fn(...args);
11
+ } catch (err) {
12
+ log.catch(err);
13
+ }
14
+ };
15
+ };
@@ -2,5 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './overlap';
6
5
  export * from './dom';
6
+ export * from './error';
7
+ export * from './overlap';
8
+ export * from './react';
@@ -1,6 +1,7 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
+
4
5
  import { type Range } from '../types';
5
6
 
6
7
  /**
@@ -0,0 +1,15 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { type ReactNode } from 'react';
6
+ import { createRoot } from 'react-dom/client';
7
+
8
+ import { ThemeProvider } from '@dxos/react-ui';
9
+ import { defaultTx } from '@dxos/react-ui-theme';
10
+
11
+ export const renderRoot = (node: ReactNode) => {
12
+ const el = document.createElement('div');
13
+ createRoot(el).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
14
+ return el;
15
+ };
@@ -99,7 +99,7 @@ export const useTextEditor = (
99
99
  selection: initialSelection,
100
100
  extensions: [
101
101
  id && documentId.of(id),
102
- // TODO(burdon): Doesn't catch errors in keymap functions.
102
+ // NOTE: Doesn't catch errors in keymap functions.
103
103
  EditorView.exceptionSink.of((err) => {
104
104
  log.catch(err);
105
105
  }),
package/src/index.ts CHANGED
@@ -11,9 +11,9 @@ export { tags } from '@lezer/highlight';
11
11
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
12
12
 
13
13
  export * from './components';
14
+ export * from './defaults';
14
15
  export * from './extensions';
15
16
  export * from './hooks';
16
- export { getToken, editorWithToolbarLayout, editorFillLayoutRoot, editorFillLayoutEditor } from './styles';
17
- export * from './themes';
18
17
  export * from './util';
18
+
19
19
  export { translations };
@@ -3,5 +3,5 @@
3
3
  //
4
4
 
5
5
  export * from './markdown';
6
+ export * from './theme';
6
7
  export * from './tokens';
7
- export * from './layout';
@@ -8,7 +8,7 @@ export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
8
8
 
9
9
  // TODO(burdon): Better way to align vertically than negative margin? Font-specific?
10
10
  // https://tailwindcss.com/docs/font-weight
11
- export const headings: Record<HeadingLevel, string> = {
11
+ const headings: Record<HeadingLevel, string> = {
12
12
  1: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-4xl',
13
13
  2: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-3xl',
14
14
  3: 'mbs-4 mbe-2 font-medium text-inherit no-underline text-2xl',
@@ -17,8 +17,9 @@ export const headings: Record<HeadingLevel, string> = {
17
17
  6: 'mbs-4 mbe-2 font-medium text-inherit no-underline',
18
18
  };
19
19
 
20
+ // TODO(burdon): Themes.
20
21
  export const heading = (level: HeadingLevel) => {
21
- return headings[level];
22
+ return mx(headings[level], 'dark:text-primary-400');
22
23
  };
23
24
 
24
25
  export const text = 'text-neutral-800 dark:text-neutral-200';
@@ -38,7 +39,7 @@ export const codeBlock = 'mlb-2 font-mono bg-neutral-500/10 p-3 rounded';
38
39
 
39
40
  export const inlineUrl = mx(code, 'px-1');
40
41
 
41
- export const blockquote = mx('pl-1 mr-1 border-is-4 border-primary-500/70 dark:border-primary-500/30', light);
42
+ export const blockquote = mx('pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 text-transparent');
42
43
 
43
44
  export const horizontalRule =
44
45
  'flex mlb-4 border-b text-neutral-100 dark:text-neutral-900 border-neutral-200 dark:border-neutral-800';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import get from 'lodash.get';
6
6
 
7
- import { type ThemeStyles, tokens } from '../styles';
7
+ import { type ThemeStyles, tokens } from './tokens';
8
8
 
9
9
  // TODO(burdon): Can we use @apply and import css file?
10
10
  // https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply?
@@ -17,61 +17,56 @@ import { type ThemeStyles, tokens } from '../styles';
17
17
  * - https://github.com/codemirror/view/blob/main/src/theme.ts
18
18
  * - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts
19
19
  *
20
+ * Main layout:
21
+ * https://codemirror.net/examples/styling
22
+ *
23
+ * <div class="cm-editor [cm-focused] [generated classes]">
24
+ * <div class="cm-scroller">
25
+ * <div class="cm-gutters">
26
+ * <div class="cm-gutter [...]">
27
+ * <div class="cm-gutterElement">...</div>
28
+ * </div>
29
+ * </div>
30
+ * <div class="cm-content" role="textbox" contenteditable="true">
31
+ * <div class="cm-line"></div>
32
+ * </div>
33
+ * <div class="cm-selectionLayer">
34
+ * <div class="cm-selectionBackground"></div>
35
+ * </div>
36
+ * <div class="cm-cursorLayer">
37
+ * <div class="cm-cursor"></div>
38
+ * </div>
39
+ * </div>
40
+ * </div>
41
+ *
20
42
  * NOTE: Use one of '&', '&light', and '&dark' prefix to scope instance.
21
43
  * NOTE: `light` and `dark` selectors are preprocessed by CodeMirror and can only be in the base theme.
22
44
  */
23
45
  export const defaultTheme: ThemeStyles = {
24
- //
25
- // Main layout:
26
- // https://codemirror.net/examples/styling
27
- //
28
- // <div class="cm-editor [cm-focused] [generated classes]">
29
- // <div class="cm-scroller">
30
- // <div class="cm-gutters">
31
- // <div class="cm-gutter [...]">
32
- // <div class="cm-gutterElement">...</div>
33
- // </div>
34
- // </div>
35
- // <div class="cm-content" role="textbox" contenteditable="true">
36
- // <div class="cm-line"></div>
37
- // </div>
38
- // <div class="cm-selectionLayer">
39
- // <div class="cm-selectionBackground"></div>
40
- // </div>
41
- // <div class="cm-cursorLayer">
42
- // <div class="cm-cursor"></div>
43
- // </div>
44
- // </div>
45
- // </div>
46
- //
47
-
48
46
  '&': {},
49
47
  '&.cm-focused': {
50
48
  outline: 'none',
51
49
  },
52
50
 
51
+ // Scroller.
53
52
  // NOTE: See https://codemirror.net/docs/guide (DOM Structure).
54
53
  '.cm-scroller': {
55
- // TODO(burdon): Reconcile with docs: https://codemirror.net/docs/guide
56
- // Inside of that is the scroller element. If the editor has its own scrollbar, this one should be styled with overflow: auto. But it doesn't have to—the editor also supports growing to accomodate its content, or growing up to a certain max-height and then scrolling.
57
54
  overflowY: 'auto',
58
55
  fontFamily: get(tokens, 'fontFamily.body', []).join(','),
59
56
  lineHeight: 1.5,
60
57
  },
61
58
 
59
+ // Content.
62
60
  '.cm-content': {
63
- // TODO(burdon): Is it possible to remove the padding value from CM's default theme?
64
- // padding: 'unset',
61
+ padding: 'unset',
65
62
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
66
63
  fontSize: '16px',
67
64
  },
68
65
  '&light .cm-content': {
69
66
  color: get(tokens, 'extend.semanticColors.base.fg.light', 'black'),
70
- caretColor: 'black',
71
67
  },
72
68
  '&dark .cm-content': {
73
- color: get(tokens, 'extend.semanticColors.base.fg.dark', 'white'),
74
- caretColor: 'white',
69
+ color: get(tokens, 'extend.semanticColors.base.fg.dark', 'red'),
75
70
  },
76
71
 
77
72
  //
@@ -103,8 +98,8 @@ export const defaultTheme: ThemeStyles = {
103
98
  //
104
99
  // gutter
105
100
  //
106
- '.cm-gutterElement': {
107
- width: '48px',
101
+ '.cm-lineNumbers': {
102
+ minWidth: '36px',
108
103
  },
109
104
 
110
105
  //
@@ -150,9 +145,7 @@ export const defaultTheme: ThemeStyles = {
150
145
  //
151
146
  // tooltip
152
147
  //
153
- '.cm-tooltip': {
154
- border: 'none',
155
- },
148
+ '.cm-tooltip': {},
156
149
  '&light .cm-tooltip': {
157
150
  background: `${get(tokens, 'extend.colors.neutral.100')} !important`,
158
151
  },
@@ -165,21 +158,27 @@ export const defaultTheme: ThemeStyles = {
165
158
  // autocomplete
166
159
  // https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
167
160
  //
168
- '.cm-tooltip-autocomplete': {
161
+ '.cm-tooltip.cm-tooltip-autocomplete': {
169
162
  marginTop: '4px',
170
163
  marginLeft: '-3px',
171
164
  },
172
- '.cm-tooltip-autocomplete > ul': {
165
+ '.cm-tooltip.cm-tooltip-autocomplete > ul': {
173
166
  maxHeight: '20em !important',
174
167
  },
175
- '.cm-tooltip-autocomplete > ul > li': {},
176
- '.cm-tooltip-autocomplete > ul > li[aria-selected]': {},
168
+ '.cm-tooltip.cm-tooltip-autocomplete > ul > li': {},
169
+ '.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]': {},
177
170
  // TODO(burdon): Can we add a class prefix to avoid adding !important?
178
171
  '.cm-tooltip.cm-tooltip-autocomplete > ul > completion-section': {
179
172
  paddingLeft: '4px !important',
180
173
  borderBottom: 'none !important',
181
174
  color: get(tokens, 'extend.colors.primary.500'),
182
175
  },
176
+ '.cm-tooltip.cm-completionInfo': {
177
+ border: get(tokens, 'extend.colors.neutral.500'),
178
+ width: '360px !important',
179
+ margin: '-10px 1px 0 1px',
180
+ padding: '8px !important',
181
+ },
183
182
  '.cm-completionIcon': {
184
183
  display: 'none',
185
184
  },
@@ -200,9 +199,8 @@ export const defaultTheme: ThemeStyles = {
200
199
  },
201
200
  '.cm-table-head': {
202
201
  padding: '2px 16px 2px 0px',
203
- borderBottom: `1px solid ${get(tokens, 'extend.colors.neutral.500')}`,
204
- fontWeight: 100,
205
202
  textAlign: 'left',
203
+ borderBottom: `1px solid ${get(tokens, 'extend.colors.primary.500')}`,
206
204
  color: get(tokens, 'extend.colors.neutral.500'),
207
205
  },
208
206
  '.cm-table-cell': {
@@ -236,6 +234,16 @@ export const defaultTheme: ThemeStyles = {
236
234
  // return acc;
237
235
  // }, {}),
238
236
 
237
+ // TODO(burdon): Override vars --cm-background.
238
+ // https://www.npmjs.com/package/codemirror-theme-vars
239
+
240
+ /**
241
+ * Gutters
242
+ */
243
+ '.cm-gutters': {
244
+ background: 'transparent',
245
+ },
246
+
239
247
  /**
240
248
  * Panels
241
249
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -11,7 +11,6 @@ export type ThemeStyles = {
11
11
  [selector: string]: StyleSpec;
12
12
  };
13
13
 
14
- // TODO(thure): Why export the whole theme? Can this be done differently?
15
14
  export const tokens: TailwindConfig['theme'] = tailwindConfig({}).theme;
16
15
 
17
16
  export const getToken = (path: string, defaultValue: any = undefined) => get(tokens, path, defaultValue);
@@ -19,6 +19,8 @@ export default [
19
19
  'blockquote label': 'Block quote',
20
20
  'codeblock label': 'Code block',
21
21
  'comment label': 'Create comment',
22
+ 'selection overlaps existing comment label': 'Selection overlaps existing comment',
23
+ 'select text to comment label': 'Select text to comment',
22
24
  'image label': 'Insert image',
23
25
  'heading label': 'Heading level',
24
26
  'table label': 'Create table',