@dxos/ui-editor 0.0.0
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/LICENSE +8 -0
- package/README.md +21 -0
- package/package.json +121 -0
- package/src/defaults.ts +34 -0
- package/src/extensions/annotations.ts +55 -0
- package/src/extensions/autocomplete/autocomplete.ts +151 -0
- package/src/extensions/autocomplete/index.ts +8 -0
- package/src/extensions/autocomplete/match.ts +46 -0
- package/src/extensions/autocomplete/placeholder.ts +117 -0
- package/src/extensions/autocomplete/typeahead.ts +87 -0
- package/src/extensions/automerge/automerge.test.tsx +76 -0
- package/src/extensions/automerge/automerge.ts +105 -0
- package/src/extensions/automerge/cursor.ts +28 -0
- package/src/extensions/automerge/defs.ts +31 -0
- package/src/extensions/automerge/index.ts +5 -0
- package/src/extensions/automerge/sync.ts +79 -0
- package/src/extensions/automerge/update-automerge.ts +50 -0
- package/src/extensions/automerge/update-codemirror.ts +115 -0
- package/src/extensions/autoscroll.ts +165 -0
- package/src/extensions/awareness/awareness-provider.ts +127 -0
- package/src/extensions/awareness/awareness.ts +315 -0
- package/src/extensions/awareness/index.ts +6 -0
- package/src/extensions/blast.ts +363 -0
- package/src/extensions/blocks.ts +131 -0
- package/src/extensions/bookmarks.ts +77 -0
- package/src/extensions/comments.ts +579 -0
- package/src/extensions/debug.ts +15 -0
- package/src/extensions/dnd.ts +39 -0
- package/src/extensions/factories.ts +284 -0
- package/src/extensions/focus.ts +36 -0
- package/src/extensions/folding.ts +63 -0
- package/src/extensions/hashtag.ts +68 -0
- package/src/extensions/index.ts +34 -0
- package/src/extensions/json.ts +57 -0
- package/src/extensions/listener.ts +32 -0
- package/src/extensions/markdown/action.ts +117 -0
- package/src/extensions/markdown/bundle.ts +105 -0
- package/src/extensions/markdown/changes.test.ts +26 -0
- package/src/extensions/markdown/changes.ts +149 -0
- package/src/extensions/markdown/debug.ts +44 -0
- package/src/extensions/markdown/decorate.ts +622 -0
- package/src/extensions/markdown/formatting.test.ts +498 -0
- package/src/extensions/markdown/formatting.ts +1265 -0
- package/src/extensions/markdown/highlight.ts +183 -0
- package/src/extensions/markdown/image.ts +118 -0
- package/src/extensions/markdown/index.ts +13 -0
- package/src/extensions/markdown/link.ts +50 -0
- package/src/extensions/markdown/parser.test.ts +75 -0
- package/src/extensions/markdown/styles.ts +135 -0
- package/src/extensions/markdown/table.ts +150 -0
- package/src/extensions/mention.ts +41 -0
- package/src/extensions/modal.ts +24 -0
- package/src/extensions/modes.ts +41 -0
- package/src/extensions/outliner/commands.ts +270 -0
- package/src/extensions/outliner/editor.test.ts +33 -0
- package/src/extensions/outliner/editor.ts +184 -0
- package/src/extensions/outliner/index.ts +7 -0
- package/src/extensions/outliner/menu.ts +128 -0
- package/src/extensions/outliner/outliner.test.ts +100 -0
- package/src/extensions/outliner/outliner.ts +167 -0
- package/src/extensions/outliner/selection.ts +50 -0
- package/src/extensions/outliner/tree.test.ts +168 -0
- package/src/extensions/outliner/tree.ts +317 -0
- package/src/extensions/preview/index.ts +5 -0
- package/src/extensions/preview/preview.ts +193 -0
- package/src/extensions/replacer.test.ts +75 -0
- package/src/extensions/replacer.ts +93 -0
- package/src/extensions/scrolling.ts +189 -0
- package/src/extensions/selection.ts +100 -0
- package/src/extensions/state.ts +7 -0
- package/src/extensions/submit.ts +62 -0
- package/src/extensions/tags/extended-markdown.test.ts +263 -0
- package/src/extensions/tags/extended-markdown.ts +78 -0
- package/src/extensions/tags/index.ts +7 -0
- package/src/extensions/tags/streamer.ts +243 -0
- package/src/extensions/tags/xml-tags.ts +507 -0
- package/src/extensions/tags/xml-util.test.ts +48 -0
- package/src/extensions/tags/xml-util.ts +93 -0
- package/src/extensions/typewriter.ts +68 -0
- package/src/index.ts +14 -0
- package/src/styles/index.ts +7 -0
- package/src/styles/markdown.ts +26 -0
- package/src/styles/theme.ts +293 -0
- package/src/styles/tokens.ts +17 -0
- package/src/types/index.ts +5 -0
- package/src/types/types.ts +32 -0
- package/src/util/cursor.ts +56 -0
- package/src/util/debug.ts +56 -0
- package/src/util/decorations.ts +21 -0
- package/src/util/dom.ts +36 -0
- package/src/util/facet.ts +13 -0
- package/src/util/index.ts +10 -0
- package/src/util/util.ts +29 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { markdownLanguage } from '@codemirror/lang-markdown';
|
|
6
|
+
import { HighlightStyle } from '@codemirror/language';
|
|
7
|
+
import { Tag, styleTags, tags } from '@lezer/highlight';
|
|
8
|
+
import { type MarkdownConfig, Table } from '@lezer/markdown';
|
|
9
|
+
|
|
10
|
+
import { fontBody, markdownTheme } from '../../styles';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Custom tags defined and processed by the GFM lezer extension.
|
|
14
|
+
* https://github.com/lezer-parser/markdown
|
|
15
|
+
* https://github.com/lezer-parser/markdown/blob/main/src/markdown.ts
|
|
16
|
+
*/
|
|
17
|
+
export const markdownTags = {
|
|
18
|
+
Blockquote: Tag.define(),
|
|
19
|
+
CodeMark: Tag.define(),
|
|
20
|
+
CodeText: Tag.define(),
|
|
21
|
+
EmphasisMark: Tag.define(),
|
|
22
|
+
HeaderMark: Tag.define(),
|
|
23
|
+
InlineCode: Tag.define(),
|
|
24
|
+
LinkLabel: Tag.define(),
|
|
25
|
+
LinkReference: Tag.define(),
|
|
26
|
+
ListMark: Tag.define(),
|
|
27
|
+
QuoteMark: Tag.define(),
|
|
28
|
+
URL: Tag.define(),
|
|
29
|
+
|
|
30
|
+
// Custom.
|
|
31
|
+
TableCell: Tag.define(),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// TODO(burdon): Customize table parser (make all content monospace).
|
|
35
|
+
// https://github.com/lezer-parser/markdown/blob/main/src/extension.ts
|
|
36
|
+
Table.defineNodes?.forEach((node: any) => {
|
|
37
|
+
switch (node?.name) {
|
|
38
|
+
case 'TableCell': {
|
|
39
|
+
node.style = markdownTags.TableCell;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const markdownTagsExtensions: MarkdownConfig[] = [
|
|
46
|
+
Table,
|
|
47
|
+
{
|
|
48
|
+
props: [styleTags(markdownTags)],
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export type HighlightOptions = {};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Styling based on `lezer` parser tags.
|
|
56
|
+
* https://codemirror.net/examples/styling
|
|
57
|
+
* https://github.com/lezer-parser/highlight
|
|
58
|
+
* https://github.com/lezer-parser/highlight/blob/main/src/highlight.ts#L427
|
|
59
|
+
* https://lezer.codemirror.net/docs/ref/#highlight.tags (list of tags)
|
|
60
|
+
*
|
|
61
|
+
* Examples:
|
|
62
|
+
* - https://github.com/codemirror/language/blob/main/src/highlight.ts#L194
|
|
63
|
+
* - https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts#L115
|
|
64
|
+
*/
|
|
65
|
+
export const markdownHighlightStyle = (_options: HighlightOptions = {}) => {
|
|
66
|
+
return HighlightStyle.define(
|
|
67
|
+
[
|
|
68
|
+
{
|
|
69
|
+
tag: [
|
|
70
|
+
tags.keyword,
|
|
71
|
+
tags.name,
|
|
72
|
+
tags.deleted,
|
|
73
|
+
tags.character,
|
|
74
|
+
tags.propertyName,
|
|
75
|
+
tags.macroName,
|
|
76
|
+
tags.color,
|
|
77
|
+
tags.constant(tags.name),
|
|
78
|
+
tags.standard(tags.name),
|
|
79
|
+
tags.definition(tags.name),
|
|
80
|
+
tags.separator,
|
|
81
|
+
tags.typeName,
|
|
82
|
+
tags.className,
|
|
83
|
+
tags.number,
|
|
84
|
+
tags.changed,
|
|
85
|
+
tags.annotation,
|
|
86
|
+
tags.modifier,
|
|
87
|
+
tags.self,
|
|
88
|
+
tags.namespace,
|
|
89
|
+
tags.operator,
|
|
90
|
+
tags.operatorKeyword,
|
|
91
|
+
tags.escape,
|
|
92
|
+
tags.regexp,
|
|
93
|
+
tags.special(tags.string),
|
|
94
|
+
tags.meta,
|
|
95
|
+
tags.comment,
|
|
96
|
+
tags.atom,
|
|
97
|
+
tags.bool,
|
|
98
|
+
tags.special(tags.variableName),
|
|
99
|
+
tags.processingInstruction,
|
|
100
|
+
tags.string,
|
|
101
|
+
tags.inserted,
|
|
102
|
+
tags.invalid,
|
|
103
|
+
],
|
|
104
|
+
// TODO(burdon): Explain.
|
|
105
|
+
color: 'inherit !important',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Markdown marks.
|
|
109
|
+
{
|
|
110
|
+
tag: [
|
|
111
|
+
tags.meta,
|
|
112
|
+
tags.processingInstruction,
|
|
113
|
+
markdownTags.LinkLabel,
|
|
114
|
+
markdownTags.LinkReference,
|
|
115
|
+
markdownTags.ListMark,
|
|
116
|
+
],
|
|
117
|
+
class: markdownTheme.mark,
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Markdown marks.
|
|
121
|
+
{
|
|
122
|
+
tag: [
|
|
123
|
+
//
|
|
124
|
+
markdownTags.CodeMark,
|
|
125
|
+
markdownTags.HeaderMark,
|
|
126
|
+
markdownTags.QuoteMark,
|
|
127
|
+
markdownTags.EmphasisMark,
|
|
128
|
+
],
|
|
129
|
+
class: markdownTheme.mark,
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// E.g., code block language (after ```).
|
|
133
|
+
{
|
|
134
|
+
tag: [
|
|
135
|
+
//
|
|
136
|
+
tags.function(tags.variableName),
|
|
137
|
+
tags.labelName,
|
|
138
|
+
],
|
|
139
|
+
class: markdownTheme.codeMark,
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// Fonts.
|
|
143
|
+
{
|
|
144
|
+
tag: [tags.monospace, tags.comment],
|
|
145
|
+
class: 'font-mono',
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// Headings.
|
|
149
|
+
{ tag: tags.heading1, class: markdownTheme.heading(1) },
|
|
150
|
+
{ tag: tags.heading2, class: markdownTheme.heading(2) },
|
|
151
|
+
{ tag: tags.heading3, class: markdownTheme.heading(3) },
|
|
152
|
+
{ tag: tags.heading4, class: markdownTheme.heading(4) },
|
|
153
|
+
{ tag: tags.heading5, class: markdownTheme.heading(5) },
|
|
154
|
+
{ tag: tags.heading6, class: markdownTheme.heading(6) },
|
|
155
|
+
|
|
156
|
+
// Emphasis.
|
|
157
|
+
{ tag: tags.emphasis, class: 'italic' },
|
|
158
|
+
{ tag: tags.strong, class: 'font-bold' },
|
|
159
|
+
{ tag: tags.strikethrough, class: 'line-through' },
|
|
160
|
+
|
|
161
|
+
// NOTE: The `markdown` extension configures extensions for `lezer` to parse markdown tokens (incl. below).
|
|
162
|
+
// However, since `codeLanguages` is also defined, the `lezer` will not parse fenced code blocks,
|
|
163
|
+
// when a language is specified. In this case, the syntax highlighting extensions will colorize
|
|
164
|
+
// the code, but all other CSS properties will be inherited.
|
|
165
|
+
// IMPORTANT: Therefore, the fenced code block will use the base editor font unless changed by an extension.
|
|
166
|
+
{
|
|
167
|
+
tag: [markdownTags.CodeText, markdownTags.InlineCode],
|
|
168
|
+
class: markdownTheme.code,
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
tag: [markdownTags.TableCell],
|
|
173
|
+
class: 'font-mono',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
{
|
|
177
|
+
scope: markdownLanguage,
|
|
178
|
+
all: {
|
|
179
|
+
fontFamily: fontBody,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { type EditorState, type Extension, type Range, StateField, type Transaction } from '@codemirror/state';
|
|
7
|
+
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
|
8
|
+
|
|
9
|
+
import { focusField } from '../focus';
|
|
10
|
+
|
|
11
|
+
export type ImageOptions = {};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create image decorations.
|
|
15
|
+
*/
|
|
16
|
+
export const image = (_options: ImageOptions = {}): Extension => {
|
|
17
|
+
return [
|
|
18
|
+
StateField.define<DecorationSet>({
|
|
19
|
+
create: (state) => {
|
|
20
|
+
return Decoration.set(buildDecorations(state, 0, state.doc.length));
|
|
21
|
+
},
|
|
22
|
+
update: (value: DecorationSet, tr: Transaction) => {
|
|
23
|
+
if (!tr.docChanged && !tr.selection) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Find range of changes and cursor changes.
|
|
28
|
+
const cursor = tr.state.selection.main.head;
|
|
29
|
+
const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
|
|
30
|
+
let from = Math.min(cursor, oldCursor);
|
|
31
|
+
let to = Math.max(cursor, oldCursor);
|
|
32
|
+
tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
|
33
|
+
from = Math.min(from, fromB);
|
|
34
|
+
to = Math.max(to, toB);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Expand to cover lines.
|
|
38
|
+
from = tr.state.doc.lineAt(from).from;
|
|
39
|
+
to = tr.state.doc.lineAt(to).to;
|
|
40
|
+
|
|
41
|
+
return value.map(tr.changes).update({
|
|
42
|
+
filterFrom: from,
|
|
43
|
+
filterTo: to,
|
|
44
|
+
filter: () => false,
|
|
45
|
+
add: buildDecorations(tr.state, from, to),
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
49
|
+
}),
|
|
50
|
+
];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const buildDecorations = (state: EditorState, from: number, to: number) => {
|
|
54
|
+
const decorations: Range<Decoration>[] = [];
|
|
55
|
+
const cursor = state.selection.main.head;
|
|
56
|
+
syntaxTree(state).iterate({
|
|
57
|
+
enter: (node) => {
|
|
58
|
+
if (node.name === 'Image') {
|
|
59
|
+
const urlNode = node.node.getChild('URL');
|
|
60
|
+
if (urlNode) {
|
|
61
|
+
const hide = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
|
|
62
|
+
|
|
63
|
+
const url = state.sliceDoc(urlNode.from, urlNode.to);
|
|
64
|
+
// Some plugins might be using custom URLs; avoid attempts to render those URLs.
|
|
65
|
+
if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
preloadImage(url);
|
|
70
|
+
decorations.push(
|
|
71
|
+
Decoration.replace({
|
|
72
|
+
block: true, // Prevent cursor from entering.
|
|
73
|
+
widget: new ImageWidget(url),
|
|
74
|
+
}).range(hide ? node.from : node.to, node.to),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
from,
|
|
80
|
+
to,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return decorations;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const preloaded = new Set<string>();
|
|
87
|
+
|
|
88
|
+
const preloadImage = (url: string) => {
|
|
89
|
+
if (!preloaded.has(url)) {
|
|
90
|
+
const img = document.createElement('img');
|
|
91
|
+
img.src = url;
|
|
92
|
+
preloaded.add(url);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
class ImageWidget extends WidgetType {
|
|
97
|
+
constructor(readonly _url: string) {
|
|
98
|
+
super();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override eq(other: this) {
|
|
102
|
+
return this._url === other._url;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override toDOM(view: EditorView) {
|
|
106
|
+
const img = document.createElement('img');
|
|
107
|
+
img.setAttribute('src', this._url);
|
|
108
|
+
img.setAttribute('class', 'cm-image');
|
|
109
|
+
// If focused, hide image until successfully loaded to avoid flickering effects.
|
|
110
|
+
if (view.state.field(focusField)) {
|
|
111
|
+
img.onload = () => img.classList.add('cm-loaded-image');
|
|
112
|
+
} else {
|
|
113
|
+
img.classList.add('cm-loaded-image');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return img;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export * from './action';
|
|
6
|
+
export * from './bundle';
|
|
7
|
+
export * from './debug';
|
|
8
|
+
export * from './decorate';
|
|
9
|
+
export * from './formatting';
|
|
10
|
+
export * from './highlight';
|
|
11
|
+
export * from './image';
|
|
12
|
+
export * from './link';
|
|
13
|
+
export * from './table';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
// Copyright CodeMirror
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import { syntaxTree } from '@codemirror/language';
|
|
7
|
+
import { hoverTooltip } from '@codemirror/view';
|
|
8
|
+
import { type SyntaxNode } from '@lezer/common';
|
|
9
|
+
|
|
10
|
+
import { tooltipContent } from '@dxos/ui-theme';
|
|
11
|
+
|
|
12
|
+
import { type RenderCallback } from '../../types';
|
|
13
|
+
|
|
14
|
+
export const linkTooltip = (renderTooltip: RenderCallback<{ url: string }>) => {
|
|
15
|
+
return hoverTooltip(
|
|
16
|
+
(view, pos, side) => {
|
|
17
|
+
const syntax = syntaxTree(view.state).resolveInner(pos, side);
|
|
18
|
+
let link = null;
|
|
19
|
+
for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
|
|
20
|
+
link = node.name === 'Link' ? node : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const url = link && link.getChild('URL');
|
|
24
|
+
if (!url || !link) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const urlText = view.state.sliceDoc(url.from, url.to);
|
|
29
|
+
if (urlText.startsWith('dxn')) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
pos: link.from,
|
|
34
|
+
end: link.to,
|
|
35
|
+
// NOTE: Forcing above causes the tooltip to flicker.
|
|
36
|
+
// above: true,
|
|
37
|
+
create: () => {
|
|
38
|
+
const el = document.createElement('div');
|
|
39
|
+
el.className = tooltipContent({});
|
|
40
|
+
renderTooltip(el, { url: urlText }, view);
|
|
41
|
+
return { dom: el, offset: { x: 0, y: 4 } };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
// NOTE: 0 = default of 300ms.
|
|
47
|
+
hoverTime: 1,
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import { describe, test } from 'vitest';
|
|
9
|
+
|
|
10
|
+
describe('parser', () => {
|
|
11
|
+
// test.only('list-mark', () => {
|
|
12
|
+
// const newParser = parser.configure({
|
|
13
|
+
// parseBlock: [
|
|
14
|
+
// {
|
|
15
|
+
// name: 'ListItem',
|
|
16
|
+
// parse: (cx, line) => {
|
|
17
|
+
// console.log(`[${line.text}]`, cx.lineStart, line.text.length);
|
|
18
|
+
// // line.skipSpace(1);
|
|
19
|
+
// return true;
|
|
20
|
+
// },
|
|
21
|
+
// },
|
|
22
|
+
// ],
|
|
23
|
+
// });
|
|
24
|
+
//
|
|
25
|
+
// {
|
|
26
|
+
// const result = newParser.parse(' - ');
|
|
27
|
+
// testTree(result, 'Document(BulletList(ListItem(ListMark)))');
|
|
28
|
+
// }
|
|
29
|
+
// {
|
|
30
|
+
// const result = newParser.parse('-x');
|
|
31
|
+
// testTree(result, 'Document(Paragraph)');
|
|
32
|
+
// }
|
|
33
|
+
// {
|
|
34
|
+
// const result = newParser.parse('- x');
|
|
35
|
+
// testTree(result, 'Document(BulletList(ListItem(ListMark,Paragraph)))');
|
|
36
|
+
// }
|
|
37
|
+
// });
|
|
38
|
+
|
|
39
|
+
// https://www.markdownguide.org/basic-syntax/#lists-1
|
|
40
|
+
test('lists', () => {
|
|
41
|
+
// Indented list must have 4 spaces.
|
|
42
|
+
const result = parser.parse(
|
|
43
|
+
[
|
|
44
|
+
'# H1',
|
|
45
|
+
'1. one',
|
|
46
|
+
'2. two',
|
|
47
|
+
'3. three',
|
|
48
|
+
' 1. four',
|
|
49
|
+
'',
|
|
50
|
+
// TODO(burdon): Test list termination without heading as break.
|
|
51
|
+
'# H2',
|
|
52
|
+
'1. one',
|
|
53
|
+
].join('\n'),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
testTree(
|
|
57
|
+
result,
|
|
58
|
+
[
|
|
59
|
+
'Document(',
|
|
60
|
+
'ATXHeading1(HeaderMark)',
|
|
61
|
+
'OrderedList(',
|
|
62
|
+
'ListItem(ListMark,Paragraph),',
|
|
63
|
+
'ListItem(ListMark,Paragraph),',
|
|
64
|
+
'ListItem(ListMark,Paragraph,OrderedList(ListItem(ListMark,Paragraph)))',
|
|
65
|
+
')',
|
|
66
|
+
'',
|
|
67
|
+
'ATXHeading1(HeaderMark)',
|
|
68
|
+
'OrderedList(',
|
|
69
|
+
'ListItem(ListMark,Paragraph),',
|
|
70
|
+
')',
|
|
71
|
+
')',
|
|
72
|
+
].join(''),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EditorView } from '@codemirror/view';
|
|
6
|
+
|
|
7
|
+
import { fontMono } from '../../styles';
|
|
8
|
+
|
|
9
|
+
export const bulletListIndentationWidth = 24;
|
|
10
|
+
export const orderedListIndentationWidth = 36; // TODO(burdon): Make variable length based on number of digits.
|
|
11
|
+
|
|
12
|
+
export const formattingStyles = EditorView.theme({
|
|
13
|
+
/**
|
|
14
|
+
* Horizontal rule.
|
|
15
|
+
*/
|
|
16
|
+
'& .cm-hr': {
|
|
17
|
+
display: 'inline-block',
|
|
18
|
+
width: '100%',
|
|
19
|
+
height: '0',
|
|
20
|
+
verticalAlign: 'middle',
|
|
21
|
+
borderTop: '1px solid var(--dx-cmSeparator)',
|
|
22
|
+
opacity: 0.5,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Lists.
|
|
27
|
+
*/
|
|
28
|
+
'& .cm-list-item': {},
|
|
29
|
+
'& .cm-list-mark': {
|
|
30
|
+
display: 'inline-block',
|
|
31
|
+
textAlign: 'right',
|
|
32
|
+
paddingRight: '0.5em',
|
|
33
|
+
fontVariant: 'tabular-nums',
|
|
34
|
+
},
|
|
35
|
+
'& .cm-list-mark-bullet': {
|
|
36
|
+
width: `${bulletListIndentationWidth}px`,
|
|
37
|
+
},
|
|
38
|
+
'& .cm-list-mark-ordered': {
|
|
39
|
+
width: `${orderedListIndentationWidth}px`,
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Blockquote.
|
|
44
|
+
*/
|
|
45
|
+
'& .cm-blockquote': {
|
|
46
|
+
background: 'var(--dx-cmCodeblock)',
|
|
47
|
+
borderLeft: '2px solid var(--dx-cmSeparator)',
|
|
48
|
+
paddingLeft: '1rem',
|
|
49
|
+
margin: '0',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Code and codeblocks.
|
|
54
|
+
*/
|
|
55
|
+
'& .cm-code': {
|
|
56
|
+
fontFamily: fontMono,
|
|
57
|
+
},
|
|
58
|
+
'& .cm-codeblock-line': {
|
|
59
|
+
background: 'var(--dx-cmCodeblock)',
|
|
60
|
+
paddingInline: '1rem !important',
|
|
61
|
+
},
|
|
62
|
+
'& .cm-codeblock-start': {
|
|
63
|
+
borderTopLeftRadius: '.25rem',
|
|
64
|
+
borderTopRightRadius: '.25rem',
|
|
65
|
+
},
|
|
66
|
+
'& .cm-codeblock-end': {
|
|
67
|
+
borderBottomLeftRadius: '.25rem',
|
|
68
|
+
borderBottomRightRadius: '.25rem',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Task list.
|
|
73
|
+
*/
|
|
74
|
+
'& .cm-task': {
|
|
75
|
+
display: 'inline-flex',
|
|
76
|
+
width: `${bulletListIndentationWidth}px`,
|
|
77
|
+
height: '20px',
|
|
78
|
+
},
|
|
79
|
+
'& .cm-task-checkbox': {
|
|
80
|
+
display: 'grid',
|
|
81
|
+
margin: '0',
|
|
82
|
+
transform: 'translateY(2px)',
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Table.
|
|
87
|
+
*/
|
|
88
|
+
'.cm-table *': {
|
|
89
|
+
fontFamily: fontMono,
|
|
90
|
+
textDecoration: 'none !important',
|
|
91
|
+
},
|
|
92
|
+
'.cm-table-head': {
|
|
93
|
+
padding: '2px 16px 2px 0px',
|
|
94
|
+
textAlign: 'left',
|
|
95
|
+
borderBottom: '1px solid var(--dx-cmSeparator)',
|
|
96
|
+
color: 'var(--dx-subdued)',
|
|
97
|
+
},
|
|
98
|
+
'.cm-table-cell': {
|
|
99
|
+
padding: '2px 16px 2px 0px',
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Image.
|
|
104
|
+
*/
|
|
105
|
+
'.cm-image': {
|
|
106
|
+
display: 'block',
|
|
107
|
+
height: '0',
|
|
108
|
+
},
|
|
109
|
+
'.cm-image.cm-loaded-image': {
|
|
110
|
+
height: 'auto',
|
|
111
|
+
borderTop: '0.5rem solid transparent',
|
|
112
|
+
borderBottom: '0.5rem solid transparent',
|
|
113
|
+
},
|
|
114
|
+
'.cm-image-with-loader': {
|
|
115
|
+
display: 'block',
|
|
116
|
+
opacity: '0',
|
|
117
|
+
transitionDuration: '350ms',
|
|
118
|
+
transitionProperty: 'opacity',
|
|
119
|
+
},
|
|
120
|
+
'.cm-image-with-loader.cm-loaded-image': {
|
|
121
|
+
opacity: '1',
|
|
122
|
+
},
|
|
123
|
+
'.cm-image-wrapper': {
|
|
124
|
+
'grid-template-columns': '1fr',
|
|
125
|
+
display: 'grid',
|
|
126
|
+
margin: '0.5rem 0',
|
|
127
|
+
overflow: 'hidden',
|
|
128
|
+
transitionDuration: '350ms',
|
|
129
|
+
transitionProperty: 'height',
|
|
130
|
+
'& > *': {
|
|
131
|
+
'grid-row-start': 1,
|
|
132
|
+
'grid-column-start': 1,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|