@milkdown/preset-commonmark 4.12.0 → 4.13.3

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.
@@ -0,0 +1,52 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { css } from '@emotion/css';
3
+ import { createCmd, createCmdKey } from '@milkdown/core';
4
+ import { createMark, createShortcut, markRule } from '@milkdown/utils';
5
+ import { toggleMark } from 'prosemirror-commands';
6
+
7
+ import { SupportedKeys } from '../supported-keys';
8
+
9
+ type Keys = SupportedKeys['Bold'];
10
+ const id = 'strong';
11
+ export const ToggleBold = createCmdKey();
12
+ export const strong = createMark<Keys>((_, utils) => {
13
+ const style = utils.getStyle(
14
+ () =>
15
+ css`
16
+ font-weight: 600;
17
+ `,
18
+ );
19
+ return {
20
+ id,
21
+ schema: {
22
+ parseDOM: [
23
+ { tag: 'b' },
24
+ { tag: 'strong' },
25
+ { style: 'font-style', getAttrs: (value) => (value === 'bold') as false },
26
+ ],
27
+ toDOM: (mark) => ['strong', { class: utils.getClassName(mark.attrs, id, style) }],
28
+ },
29
+ parser: {
30
+ match: (node) => node.type === 'strong',
31
+ runner: (state, node, markType) => {
32
+ state.openMark(markType);
33
+ state.next(node.children);
34
+ state.closeMark(markType);
35
+ },
36
+ },
37
+ serializer: {
38
+ match: (mark) => mark.type.name === id,
39
+ runner: (state, mark) => {
40
+ state.withMark(mark, 'strong');
41
+ },
42
+ },
43
+ inputRules: (markType) => [
44
+ markRule(/(?:__)([^_]+)(?:__)$/, markType),
45
+ markRule(/(?:\*\*)([^*]+)(?:\*\*)$/, markType),
46
+ ],
47
+ commands: (markType) => [createCmd(ToggleBold, () => toggleMark(markType))],
48
+ shortcuts: {
49
+ [SupportedKeys.Bold]: createShortcut(ToggleBold, 'Mod-b'),
50
+ },
51
+ };
52
+ });
@@ -0,0 +1,57 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { css } from '@emotion/css';
3
+ import { createCmd, createCmdKey } from '@milkdown/core';
4
+ import { createNode, createShortcut } from '@milkdown/utils';
5
+ import { wrapIn } from 'prosemirror-commands';
6
+ import { wrappingInputRule } from 'prosemirror-inputrules';
7
+
8
+ import { SupportedKeys } from '../supported-keys';
9
+
10
+ type Keys = SupportedKeys['Blockquote'];
11
+
12
+ const id = 'blockquote';
13
+
14
+ export const WrapInBlockquote = createCmdKey();
15
+
16
+ export const blockquote = createNode<Keys>((_, utils) => {
17
+ const style = utils.getStyle(
18
+ (themeTool) =>
19
+ css`
20
+ padding-left: 1.875rem;
21
+ line-height: 1.75rem;
22
+ border-left: 4px solid ${themeTool.palette('primary')};
23
+ * {
24
+ font-size: 1rem;
25
+ line-height: 1.5rem;
26
+ }
27
+ `,
28
+ );
29
+
30
+ return {
31
+ id,
32
+ schema: {
33
+ content: 'block+',
34
+ group: 'block',
35
+ defining: true,
36
+ parseDOM: [{ tag: 'blockquote' }],
37
+ toDOM: (node) => ['blockquote', { class: utils.getClassName(node.attrs, id, style) }, 0],
38
+ },
39
+ parser: {
40
+ match: ({ type }) => type === id,
41
+ runner: (state, node, type) => {
42
+ state.openNode(type).next(node.children).closeNode();
43
+ },
44
+ },
45
+ serializer: {
46
+ match: (node) => node.type.name === id,
47
+ runner: (state, node) => {
48
+ state.openNode('blockquote').next(node.content).closeNode();
49
+ },
50
+ },
51
+ inputRules: (nodeType) => [wrappingInputRule(/^\s*>\s$/, nodeType)],
52
+ commands: (nodeType) => [createCmd(WrapInBlockquote, () => wrapIn(nodeType))],
53
+ shortcuts: {
54
+ [SupportedKeys.Blockquote]: createShortcut(WrapInBlockquote, 'Mod-Shift-b'),
55
+ },
56
+ };
57
+ });
@@ -0,0 +1,43 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { createCmd, createCmdKey } from '@milkdown/core';
3
+ import { createNode, createShortcut } from '@milkdown/utils';
4
+ import { wrapIn } from 'prosemirror-commands';
5
+ import { wrappingInputRule } from 'prosemirror-inputrules';
6
+
7
+ import { SupportedKeys } from '../supported-keys';
8
+
9
+ type Keys = SupportedKeys['BulletList'];
10
+
11
+ export const WrapInBulletList = createCmdKey();
12
+
13
+ const id = 'bullet_list';
14
+ export const bulletList = createNode<Keys>((_, utils) => {
15
+ return {
16
+ id,
17
+ schema: {
18
+ content: 'listItem+',
19
+ group: 'block',
20
+ parseDOM: [{ tag: 'ul' }],
21
+ toDOM: (node) => {
22
+ return ['ul', { class: utils.getClassName(node.attrs, 'bullet-list') }, 0];
23
+ },
24
+ },
25
+ parser: {
26
+ match: ({ type, ordered }) => type === 'list' && !ordered,
27
+ runner: (state, node, type) => {
28
+ state.openNode(type).next(node.children).closeNode();
29
+ },
30
+ },
31
+ serializer: {
32
+ match: (node) => node.type.name === id,
33
+ runner: (state, node) => {
34
+ state.openNode('list', undefined, { ordered: false }).next(node.content).closeNode();
35
+ },
36
+ },
37
+ inputRules: (nodeType) => [wrappingInputRule(/^\s*([-+*])\s$/, nodeType)],
38
+ commands: (nodeType) => [createCmd(WrapInBulletList, () => wrapIn(nodeType))],
39
+ shortcuts: {
40
+ [SupportedKeys.BulletList]: createShortcut(WrapInBulletList, 'Mod-Shift-8'),
41
+ },
42
+ };
43
+ });
@@ -0,0 +1,312 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { css } from '@emotion/css';
3
+ import { createCmd, createCmdKey, themeToolCtx } from '@milkdown/core';
4
+ import { createNode, createShortcut } from '@milkdown/utils';
5
+ import { setBlockType } from 'prosemirror-commands';
6
+ import { textblockTypeInputRule } from 'prosemirror-inputrules';
7
+
8
+ import { SupportedKeys } from '../supported-keys';
9
+
10
+ type Keys = SupportedKeys['CodeFence'];
11
+
12
+ const languageOptions = [
13
+ '',
14
+ 'javascript',
15
+ 'typescript',
16
+ 'bash',
17
+ 'sql',
18
+ 'json',
19
+ 'html',
20
+ 'css',
21
+ 'c',
22
+ 'cpp',
23
+ 'java',
24
+ 'ruby',
25
+ 'python',
26
+ 'go',
27
+ 'rust',
28
+ 'markdown',
29
+ ];
30
+
31
+ const inputRegex = /^```(?<language>[a-z]*)? $/;
32
+
33
+ export const TurnIntoCodeFence = createCmdKey();
34
+
35
+ const id = 'fence';
36
+ export const codeFence = createNode<Keys, { languageList?: string[] }>((options, utils) => {
37
+ const style = utils.getStyle(({ palette, mixin, size, font }) => {
38
+ const { shadow, scrollbar, border } = mixin;
39
+ const { lineWidth, radius } = size;
40
+ return css`
41
+ background-color: ${palette('background')};
42
+ color: ${palette('neutral')};
43
+ font-size: 0.85rem;
44
+ padding: 1.2rem 0.4rem 1.4rem;
45
+ border-radius: ${radius};
46
+ font-family: ${font.typography};
47
+
48
+ * {
49
+ margin: 0;
50
+ }
51
+
52
+ .code-fence_select-wrapper {
53
+ position: relative;
54
+ }
55
+
56
+ .code-fence_value {
57
+ width: 10.25rem;
58
+ box-sizing: border-box;
59
+ border-radius: ${size.radius};
60
+ margin: 0 1.2rem 1.2rem;
61
+ ${border()};
62
+ ${shadow()};
63
+ cursor: pointer;
64
+ background-color: ${palette('surface')};
65
+ position: relative;
66
+ display: flex;
67
+ color: ${palette('neutral', 0.87)};
68
+ letter-spacing: 0.5px;
69
+ height: 2.625rem;
70
+ align-items: center;
71
+
72
+ & > *:last-child {
73
+ width: 2.625rem;
74
+ height: 100%;
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+ color: ${palette('solid', 0.87)};
79
+ border-left: ${lineWidth} solid ${palette('line')};
80
+
81
+ text-align: center;
82
+ transition: all 0.2s ease-in-out;
83
+ &:hover {
84
+ background: ${palette('background')};
85
+ color: ${palette('primary')};
86
+ }
87
+ }
88
+
89
+ > span:first-child {
90
+ padding-left: 1rem;
91
+ flex: 1;
92
+ font-weight: 500;
93
+ }
94
+ }
95
+
96
+ .code-fence_select-option {
97
+ list-style: none;
98
+ line-height: 2rem;
99
+ padding-left: 1rem;
100
+ cursor: pointer;
101
+ :hover {
102
+ background: ${palette('secondary', 0.12)};
103
+ color: ${palette('primary')};
104
+ }
105
+ }
106
+
107
+ .code-fence_select {
108
+ &[data-fold='true'] {
109
+ display: none;
110
+ }
111
+
112
+ font-weight: 500;
113
+ position: absolute;
114
+ z-index: 1;
115
+ top: 2.625rem;
116
+ box-sizing: border-box;
117
+ left: 1.2rem;
118
+ padding: 0.5rem 0;
119
+ max-height: 16.75rem;
120
+ width: 10.25rem;
121
+ ${border()};
122
+ ${shadow()};
123
+ background-color: ${palette('surface')};
124
+ border-top: none;
125
+ overflow-y: auto;
126
+ display: flex;
127
+ flex-direction: column;
128
+
129
+ ${scrollbar('y')}
130
+ }
131
+
132
+ code {
133
+ line-height: 1.5;
134
+ }
135
+
136
+ pre {
137
+ font-family: var(--font-code);
138
+ margin: 0 1.2rem !important;
139
+ overflow-x: scroll;
140
+ white-space: pre !important;
141
+
142
+ padding-bottom: 1.4rem;
143
+
144
+ ${scrollbar('x')}
145
+ }
146
+ `;
147
+ });
148
+
149
+ return {
150
+ id,
151
+ schema: {
152
+ content: 'text*',
153
+ group: 'block',
154
+ marks: '',
155
+ defining: true,
156
+ code: true,
157
+ attrs: {
158
+ language: {
159
+ default: '',
160
+ },
161
+ fold: {
162
+ default: true,
163
+ },
164
+ },
165
+ parseDOM: [
166
+ {
167
+ tag: 'pre',
168
+ preserveWhitespace: 'full',
169
+ getAttrs: (dom) => {
170
+ if (!(dom instanceof HTMLElement)) {
171
+ throw new Error('Parse DOM error.');
172
+ }
173
+ return { language: dom.dataset.language };
174
+ },
175
+ },
176
+ ],
177
+ toDOM: (node) => {
178
+ return [
179
+ 'div',
180
+ {
181
+ 'data-language': node.attrs.language,
182
+ class: utils.getClassName(node.attrs, 'code-fence', style),
183
+ },
184
+ ['pre', ['code', { spellCheck: 'false' }, 0]],
185
+ ];
186
+ },
187
+ },
188
+ parser: {
189
+ match: ({ type }) => type === 'code',
190
+ runner: (state, node, type) => {
191
+ const language = node.lang as string;
192
+ const value = node.value as string;
193
+ state.openNode(type, { language });
194
+ state.addText(value);
195
+ state.closeNode();
196
+ },
197
+ },
198
+ serializer: {
199
+ match: (node) => node.type.name === id,
200
+ runner: (state, node) => {
201
+ state.addNode('code', undefined, node.content.firstChild?.text || '', { lang: node.attrs.language });
202
+ },
203
+ },
204
+ inputRules: (nodeType) => [
205
+ textblockTypeInputRule(inputRegex, nodeType, ([ok, language]) => {
206
+ if (!ok) return;
207
+ return { language };
208
+ }),
209
+ ],
210
+ commands: (nodeType) => [createCmd(TurnIntoCodeFence, () => setBlockType(nodeType))],
211
+ shortcuts: {
212
+ [SupportedKeys.CodeFence]: createShortcut(TurnIntoCodeFence, 'Mod-Alt-c'),
213
+ },
214
+ view: (editor, nodeType, node, view, getPos, decorations) => {
215
+ if (options?.view) {
216
+ return options.view(editor, nodeType, node, view, getPos, decorations);
217
+ }
218
+ const container = document.createElement('div');
219
+ const selectWrapper = document.createElement('div');
220
+ const select = document.createElement('ul');
221
+ const pre = document.createElement('pre');
222
+ const code = document.createElement('code');
223
+
224
+ const valueWrapper = document.createElement('div');
225
+ valueWrapper.className = 'code-fence_value';
226
+ const value = document.createElement('span');
227
+ valueWrapper.appendChild(value);
228
+ valueWrapper.appendChild(utils.ctx.get(themeToolCtx).slots.icon('downArrow'));
229
+
230
+ select.className = 'code-fence_select';
231
+ select.addEventListener('mousedown', (e) => {
232
+ e.preventDefault();
233
+ e.stopPropagation();
234
+
235
+ if (!view.editable) return;
236
+
237
+ const el = e.target;
238
+ if (!(el instanceof HTMLLIElement)) return;
239
+ const { tr } = view.state;
240
+
241
+ view.dispatch(
242
+ tr.setNodeMarkup(getPos(), undefined, {
243
+ fold: true,
244
+ language: el.dataset.value,
245
+ }),
246
+ );
247
+ });
248
+ valueWrapper.addEventListener('mousedown', (e) => {
249
+ e.preventDefault();
250
+ e.stopPropagation();
251
+
252
+ if (!view.editable) return;
253
+ const { tr } = view.state;
254
+
255
+ view.dispatch(
256
+ tr.setNodeMarkup(getPos(), undefined, {
257
+ fold: false,
258
+ language: container.dataset.language,
259
+ }),
260
+ );
261
+ });
262
+ document.addEventListener('mousedown', () => {
263
+ if (!view.editable || select.dataset.fold === 'true') return;
264
+
265
+ const { tr } = view.state;
266
+
267
+ view.dispatch(
268
+ tr.setNodeMarkup(getPos(), undefined, {
269
+ fold: true,
270
+ language: container.dataset.language,
271
+ }),
272
+ );
273
+ });
274
+
275
+ (options?.languageList || languageOptions).forEach((lang) => {
276
+ const option = document.createElement('li');
277
+ option.className = 'code-fence_select-option';
278
+ option.innerText = lang || '--';
279
+ select.appendChild(option);
280
+ option.setAttribute('data-value', lang);
281
+ });
282
+
283
+ code.spellcheck = false;
284
+ selectWrapper.className = 'code-fence_select-wrapper';
285
+ selectWrapper.contentEditable = 'false';
286
+ selectWrapper.append(valueWrapper);
287
+ selectWrapper.append(select);
288
+ pre.append(code);
289
+
290
+ container.append(selectWrapper, pre);
291
+ container.setAttribute('class', utils.getClassName(node.attrs, 'code-fence', style));
292
+ container.setAttribute('data-language', node.attrs.language);
293
+ value.innerText = node.attrs.language || '--';
294
+ select.setAttribute('data-fold', node.attrs.fold ? 'true' : 'false');
295
+
296
+ return {
297
+ dom: container,
298
+ contentDOM: code,
299
+ update: (updatedNode) => {
300
+ if (updatedNode.type.name !== id) return false;
301
+
302
+ const lang = updatedNode.attrs.language;
303
+ container.dataset.language = lang;
304
+ value.innerText = lang || '--';
305
+ select.setAttribute('data-fold', updatedNode.attrs.fold ? 'true' : 'false');
306
+
307
+ return true;
308
+ },
309
+ };
310
+ },
311
+ };
312
+ });
@@ -0,0 +1,22 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { createNode } from '@milkdown/utils';
3
+
4
+ export const doc = createNode(() => ({
5
+ id: 'doc',
6
+ schema: {
7
+ content: 'block+',
8
+ },
9
+ parser: {
10
+ match: ({ type }) => type === 'root',
11
+ runner: (state, node, type) => {
12
+ state.injectRoot(node, type);
13
+ },
14
+ },
15
+ serializer: {
16
+ match: (node) => node.type.name === 'doc',
17
+ runner: (state, node) => {
18
+ state.openNode('root');
19
+ state.next(node.content);
20
+ },
21
+ },
22
+ }));
@@ -0,0 +1,43 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { createCmd, createCmdKey } from '@milkdown/core';
3
+ import { createNode, createShortcut } from '@milkdown/utils';
4
+
5
+ import { SupportedKeys } from '../supported-keys';
6
+
7
+ type Keys = SupportedKeys['HardBreak'];
8
+
9
+ const id = 'hardbreak';
10
+
11
+ export const InsertHardbreak = createCmdKey();
12
+
13
+ export const hardbreak = createNode<Keys>((_, utils) => ({
14
+ id,
15
+ schema: {
16
+ inline: true,
17
+ group: 'inline',
18
+ selectable: false,
19
+ parseDOM: [{ tag: 'br' }],
20
+ toDOM: (node) => ['br', { class: utils.getClassName(node.attrs, id) }],
21
+ },
22
+ parser: {
23
+ match: ({ type }) => type === 'break',
24
+ runner: (state, _, type) => {
25
+ state.addNode(type);
26
+ },
27
+ },
28
+ serializer: {
29
+ match: (node) => node.type.name === id,
30
+ runner: (state) => {
31
+ state.addNode('break');
32
+ },
33
+ },
34
+ commands: (nodeType) => [
35
+ createCmd(InsertHardbreak, () => (state, dispatch) => {
36
+ dispatch?.(state.tr.replaceSelectionWith(nodeType.create()).scrollIntoView());
37
+ return true;
38
+ }),
39
+ ],
40
+ shortcuts: {
41
+ [SupportedKeys.HardBreak]: createShortcut(InsertHardbreak, 'Shift-Enter'),
42
+ },
43
+ }));
@@ -0,0 +1,101 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+ import { css } from '@emotion/css';
3
+ import { createCmd, createCmdKey } from '@milkdown/core';
4
+ import { createNode, createShortcut } from '@milkdown/utils';
5
+ import { setBlockType } from 'prosemirror-commands';
6
+ import { textblockTypeInputRule } from 'prosemirror-inputrules';
7
+
8
+ import { SupportedKeys } from '../supported-keys';
9
+
10
+ const headingIndex = Array(5)
11
+ .fill(0)
12
+ .map((_, i) => i + 1);
13
+
14
+ type Keys =
15
+ | SupportedKeys['H1']
16
+ | SupportedKeys['H2']
17
+ | SupportedKeys['H3']
18
+ | SupportedKeys['H4']
19
+ | SupportedKeys['H5']
20
+ | SupportedKeys['H6'];
21
+
22
+ const id = 'heading';
23
+
24
+ export const TurnIntoHeading = createCmdKey<number>();
25
+
26
+ export const heading = createNode<Keys>((options, utils) => {
27
+ const headingMap: Record<number, string> = {
28
+ 1: css`
29
+ font-size: 3rem;
30
+ line-height: 3.5rem;
31
+ `,
32
+ 2: css`
33
+ font-size: 2.125rem;
34
+ line-height: 2.25rem;
35
+ `,
36
+ 3: css`
37
+ font-size: 1.5rem;
38
+ line-height: 1.5rem;
39
+ `,
40
+ };
41
+
42
+ const style = (level: number) =>
43
+ options?.headless
44
+ ? null
45
+ : css`
46
+ ${headingMap[level] || ''}
47
+ margin: 2.5rem 0 !important;
48
+ font-weight: 400;
49
+ `;
50
+
51
+ return {
52
+ id,
53
+ schema: {
54
+ content: 'inline*',
55
+ group: 'block',
56
+ attrs: {
57
+ level: {
58
+ default: 1,
59
+ },
60
+ },
61
+ parseDOM: headingIndex.map((x) => ({ tag: `h${x}`, attrs: { level: x } })),
62
+ toDOM: (node) => [
63
+ `h${node.attrs.level}`,
64
+ { class: utils.getClassName(node.attrs, `heading h${node.attrs.level}`, style(node.attrs.level)) },
65
+ 0,
66
+ ],
67
+ },
68
+ parser: {
69
+ match: ({ type }) => type === id,
70
+ runner: (state, node, type) => {
71
+ const depth = node.depth as number;
72
+ state.openNode(type, { level: depth });
73
+ state.next(node.children);
74
+ state.closeNode();
75
+ },
76
+ },
77
+ serializer: {
78
+ match: (node) => node.type.name === id,
79
+ runner: (state, node) => {
80
+ state.openNode('heading', undefined, { depth: node.attrs.level });
81
+ state.next(node.content);
82
+ state.closeNode();
83
+ },
84
+ },
85
+ inputRules: (nodeType) =>
86
+ headingIndex.map((x) =>
87
+ textblockTypeInputRule(new RegExp(`^(#{1,${x}})\\s$`), nodeType, () => ({
88
+ level: x,
89
+ })),
90
+ ),
91
+ commands: (nodeType) => [createCmd(TurnIntoHeading, (level = 1) => setBlockType(nodeType, { level }))],
92
+ shortcuts: {
93
+ [SupportedKeys.H1]: createShortcut(TurnIntoHeading, 'Mod-Alt-1', 1),
94
+ [SupportedKeys.H2]: createShortcut(TurnIntoHeading, 'Mod-Alt-2', 2),
95
+ [SupportedKeys.H3]: createShortcut(TurnIntoHeading, 'Mod-Alt-3', 3),
96
+ [SupportedKeys.H4]: createShortcut(TurnIntoHeading, 'Mod-Alt-4', 4),
97
+ [SupportedKeys.H5]: createShortcut(TurnIntoHeading, 'Mod-Alt-5', 5),
98
+ [SupportedKeys.H6]: createShortcut(TurnIntoHeading, 'Mod-Alt-6', 6),
99
+ },
100
+ };
101
+ });