draftly 1.0.0-alpha.2 → 2.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.
Files changed (79) hide show
  1. package/README.md +12 -0
  2. package/dist/chunk-3T55CBNZ.cjs +33 -0
  3. package/dist/chunk-3T55CBNZ.cjs.map +1 -0
  4. package/dist/chunk-5MC4T7JH.cjs +58 -0
  5. package/dist/chunk-5MC4T7JH.cjs.map +1 -0
  6. package/dist/{chunk-72ZYRGRT.cjs → chunk-BWJLMREN.cjs} +11 -9
  7. package/dist/chunk-BWJLMREN.cjs.map +1 -0
  8. package/dist/{chunk-KBQDZ5IW.cjs → chunk-CLW73JRX.cjs} +100 -75
  9. package/dist/chunk-CLW73JRX.cjs.map +1 -0
  10. package/dist/{chunk-DFQYXFOP.js → chunk-EEHILRG5.js} +26 -3
  11. package/dist/chunk-EEHILRG5.js.map +1 -0
  12. package/dist/{chunk-HPSMS2WB.js → chunk-I563H35S.js} +101 -75
  13. package/dist/chunk-I563H35S.js.map +1 -0
  14. package/dist/chunk-IAXF4SJL.js +55 -0
  15. package/dist/chunk-IAXF4SJL.js.map +1 -0
  16. package/dist/chunk-JF3WXXMJ.js +31 -0
  17. package/dist/chunk-JF3WXXMJ.js.map +1 -0
  18. package/dist/{chunk-N3WL3XPB.js → chunk-L2XSK57Y.js} +1761 -478
  19. package/dist/chunk-L2XSK57Y.js.map +1 -0
  20. package/dist/{chunk-KDEDLC3D.cjs → chunk-TBVZEK2H.cjs} +27 -2
  21. package/dist/chunk-TBVZEK2H.cjs.map +1 -0
  22. package/dist/{chunk-2B3A3VSQ.cjs → chunk-W5ALMXG2.cjs} +1808 -504
  23. package/dist/chunk-W5ALMXG2.cjs.map +1 -0
  24. package/dist/{chunk-CG4M4TC7.js → chunk-ZUI3GI3W.js} +7 -5
  25. package/dist/chunk-ZUI3GI3W.js.map +1 -0
  26. package/dist/{draftly-BLnx3uGX.d.cts → draftly-BBL-AdOl.d.cts} +5 -1
  27. package/dist/{draftly-BLnx3uGX.d.ts → draftly-BBL-AdOl.d.ts} +5 -1
  28. package/dist/editor/index.cjs +22 -14
  29. package/dist/editor/index.d.cts +2 -1
  30. package/dist/editor/index.d.ts +2 -1
  31. package/dist/editor/index.js +2 -2
  32. package/dist/index.cjs +65 -39
  33. package/dist/index.d.cts +6 -3
  34. package/dist/index.d.ts +6 -3
  35. package/dist/index.js +6 -4
  36. package/dist/lib/index.cjs +12 -0
  37. package/dist/lib/index.cjs.map +1 -0
  38. package/dist/lib/index.d.cts +16 -0
  39. package/dist/lib/index.d.ts +16 -0
  40. package/dist/lib/index.js +3 -0
  41. package/dist/lib/index.js.map +1 -0
  42. package/dist/plugins/index.cjs +27 -17
  43. package/dist/plugins/index.d.cts +144 -9
  44. package/dist/plugins/index.d.ts +144 -9
  45. package/dist/plugins/index.js +5 -3
  46. package/dist/preview/index.cjs +16 -11
  47. package/dist/preview/index.d.cts +19 -4
  48. package/dist/preview/index.d.ts +19 -4
  49. package/dist/preview/index.js +3 -2
  50. package/package.json +8 -1
  51. package/src/editor/draftly.ts +1 -0
  52. package/src/editor/plugin.ts +5 -1
  53. package/src/editor/theme.ts +1 -0
  54. package/src/editor/utils.ts +31 -0
  55. package/src/index.ts +5 -4
  56. package/src/lib/index.ts +2 -0
  57. package/src/lib/input-handler.ts +45 -0
  58. package/src/plugins/code-plugin.theme.ts +426 -0
  59. package/src/plugins/code-plugin.ts +810 -561
  60. package/src/plugins/emoji-plugin.ts +140 -0
  61. package/src/plugins/index.ts +63 -57
  62. package/src/plugins/inline-plugin.ts +305 -291
  63. package/src/plugins/math-plugin.ts +12 -0
  64. package/src/plugins/table-plugin.ts +900 -0
  65. package/src/preview/context.ts +4 -1
  66. package/src/preview/css-generator.ts +14 -1
  67. package/src/preview/index.ts +9 -1
  68. package/src/preview/preview.ts +2 -1
  69. package/src/preview/renderer.ts +21 -20
  70. package/src/preview/syntax-theme.ts +110 -0
  71. package/src/preview/types.ts +14 -0
  72. package/dist/chunk-2B3A3VSQ.cjs.map +0 -1
  73. package/dist/chunk-72ZYRGRT.cjs.map +0 -1
  74. package/dist/chunk-CG4M4TC7.js.map +0 -1
  75. package/dist/chunk-DFQYXFOP.js.map +0 -1
  76. package/dist/chunk-HPSMS2WB.js.map +0 -1
  77. package/dist/chunk-KBQDZ5IW.cjs.map +0 -1
  78. package/dist/chunk-KDEDLC3D.cjs.map +0 -1
  79. package/dist/chunk-N3WL3XPB.js.map +0 -1
@@ -1,291 +1,305 @@
1
- import { Decoration, KeyBinding } from "@codemirror/view";
2
- import { syntaxTree } from "@codemirror/language";
3
- import { DecorationContext, DecorationPlugin } from "../editor/plugin";
4
- import { createTheme } from "../editor";
5
- import { SyntaxNode } from "@lezer/common";
6
- import { toggleMarkdownStyle } from "../editor/utils";
7
- import { tags } from "@lezer/highlight";
8
- import type { MarkdownConfig, InlineParser } from "@lezer/markdown";
9
-
10
- /**
11
- * Node types for inline styling in markdown
12
- */
13
- const INLINE_TYPES = {
14
- Emphasis: "emphasis",
15
- StrongEmphasis: "strong",
16
- Strikethrough: "strikethrough",
17
- Subscript: "subscript",
18
- Superscript: "superscript",
19
- Highlight: "highlight",
20
- } as const;
21
-
22
- /**
23
- * Mark decorations for inline content
24
- */
25
- const inlineMarkDecorations = {
26
- emphasis: Decoration.mark({ class: "cm-draftly-emphasis" }),
27
- strong: Decoration.mark({ class: "cm-draftly-strong" }),
28
- strikethrough: Decoration.mark({ class: "cm-draftly-strikethrough" }),
29
- subscript: Decoration.mark({ class: "cm-draftly-subscript" }),
30
- superscript: Decoration.mark({ class: "cm-draftly-superscript" }),
31
- highlight: Decoration.mark({ class: "cm-draftly-highlight" }),
32
- // Markers (* _ ~~ ^ ~ ==)
33
- "inline-mark": Decoration.replace({}),
34
- };
35
-
36
- // Character code for '='
37
- const EQUALS = 61;
38
-
39
- // Punctuation regex for flanking checks (matches Unicode punctuation)
40
- // eslint-disable-next-line no-useless-escape
41
- let Punctuation = /[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~\xA1\u2010-\u2027]/;
42
- try {
43
- Punctuation = new RegExp("[\\p{S}|\\p{P}]", "u");
44
- } catch {
45
- // Fallback regex is used above for environments without Unicode support
46
- }
47
-
48
- // Delimiter type for highlight markers — enables nested inline parsing
49
- const HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" };
50
-
51
- /**
52
- * Inline parser for highlight syntax: ==text==
53
- * Uses addDelimiter (like Strikethrough) so nested inline styles work.
54
- */
55
- const highlightParser: InlineParser = {
56
- name: "Highlight",
57
- parse(cx, next, pos) {
58
- // Must start with ==
59
- if (next !== EQUALS || cx.char(pos + 1) !== EQUALS) return -1;
60
- // Don't match === (or more)
61
- if (cx.char(pos + 2) === EQUALS) return -1;
62
-
63
- // Flanking checks (same logic as Strikethrough)
64
- const before = cx.slice(pos - 1, pos);
65
- const after = cx.slice(pos + 2, pos + 3);
66
- const sBefore = /\s|^$/.test(before),
67
- sAfter = /\s|^$/.test(after);
68
- const pBefore = Punctuation.test(before),
69
- pAfter = Punctuation.test(after);
70
-
71
- return cx.addDelimiter(
72
- HighlightDelim,
73
- pos,
74
- pos + 2,
75
- !sAfter && (!pAfter || sBefore || pBefore),
76
- !sBefore && (!pBefore || sAfter || pAfter)
77
- );
78
- },
79
- };
80
-
81
- /**
82
- * InlinePlugin - Decorates inline markdown formatting
83
- *
84
- * Adds visual styling to inline elements:
85
- * - Emphasis (italic) - *text* or _text_
86
- * - Strong (bold) - **text** or __text__
87
- * - Strikethrough - ~~text~~
88
- * - Subscript - ~text~
89
- * - Superscript - ^text^
90
- * - Highlight - ==text==
91
- *
92
- * Hides formatting markers when cursor is not in the element
93
- */
94
- export class InlinePlugin extends DecorationPlugin {
95
- readonly name = "inline";
96
- readonly version = "1.0.0";
97
- override decorationPriority = 20;
98
- override readonly requiredNodes = [
99
- "Emphasis",
100
- "StrongEmphasis",
101
- "Strikethrough",
102
- "Subscript",
103
- "Superscript",
104
- "Highlight",
105
- "EmphasisMark",
106
- "StrikethroughMark",
107
- "SubscriptMark",
108
- "SuperscriptMark",
109
- "HighlightMark",
110
- ] as const;
111
- marks: string[] = [];
112
-
113
- constructor() {
114
- super();
115
-
116
- for (const mark of Object.keys(INLINE_TYPES)) {
117
- this.marks.push(...this.getMarkerNames(mark));
118
- }
119
- }
120
-
121
- /**
122
- * Plugin theme
123
- */
124
- override get theme() {
125
- return theme;
126
- }
127
-
128
- /**
129
- * Keyboard shortcuts for inline formatting
130
- */
131
- override getKeymap(): KeyBinding[] {
132
- return [
133
- {
134
- key: "Mod-b",
135
- run: toggleMarkdownStyle("**"),
136
- preventDefault: true,
137
- },
138
- {
139
- key: "Mod-i",
140
- run: toggleMarkdownStyle("*"),
141
- preventDefault: true,
142
- },
143
- {
144
- key: "Mod-Shift-s",
145
- run: toggleMarkdownStyle("~~"),
146
- preventDefault: true,
147
- },
148
- {
149
- key: "Mod-,",
150
- run: toggleMarkdownStyle("~"),
151
- preventDefault: true,
152
- },
153
- {
154
- key: "Mod-.",
155
- run: toggleMarkdownStyle("^"),
156
- preventDefault: true,
157
- },
158
- {
159
- key: "Mod-Shift-h",
160
- run: toggleMarkdownStyle("=="),
161
- preventDefault: true,
162
- },
163
- ];
164
- }
165
-
166
- /**
167
- * Return markdown parser extensions for highlight syntax (==text==)
168
- */
169
- override getMarkdownConfig(): MarkdownConfig {
170
- return {
171
- defineNodes: [
172
- { name: "Highlight", style: tags.emphasis },
173
- { name: "HighlightMark", style: tags.processingInstruction },
174
- ],
175
- parseInline: [highlightParser],
176
- };
177
- }
178
-
179
- /**
180
- * Build inline decorations by iterating the syntax tree
181
- */
182
- buildDecorations(ctx: DecorationContext): void {
183
- const { view, decorations } = ctx;
184
- const tree = syntaxTree(view.state);
185
-
186
- tree.iterate({
187
- enter: (node) => {
188
- const { from, to, name } = node;
189
-
190
- // Check if this is an inline type we handle
191
- const inlineType = INLINE_TYPES[name as keyof typeof INLINE_TYPES];
192
- if (!inlineType) {
193
- return;
194
- }
195
-
196
- // Add mark decoration for the content
197
- decorations.push(inlineMarkDecorations[inlineType].range(from, to));
198
-
199
- // Only hide markers when cursor is not in the element
200
- const cursorInNode = ctx.selectionOverlapsRange(from, to);
201
- if (!cursorInNode) {
202
- // Get the appropriate marker children based on type
203
- const markerNames = this.getMarkerNames(name);
204
- for (const markerName of markerNames) {
205
- const marks = node.node.getChildren(markerName);
206
- for (const mark of marks) {
207
- decorations.push(inlineMarkDecorations["inline-mark"].range(mark.from, mark.to));
208
- }
209
- }
210
- }
211
- },
212
- });
213
- }
214
-
215
- /**
216
- * Get the marker node names for a given inline type
217
- */
218
- private getMarkerNames(nodeType: string): string[] {
219
- switch (nodeType) {
220
- case "Emphasis":
221
- case "StrongEmphasis":
222
- return ["EmphasisMark"];
223
- case "Strikethrough":
224
- return ["StrikethroughMark"];
225
- case "Subscript":
226
- return ["SubscriptMark"];
227
- case "Superscript":
228
- return ["SuperscriptMark"];
229
- case "Highlight":
230
- return ["HighlightMark"];
231
- default:
232
- return [];
233
- }
234
- }
235
-
236
- override renderToHTML(node: SyntaxNode, children: string): string | null {
237
- if (this.marks.includes(node.name)) {
238
- return "";
239
- }
240
-
241
- const inlineType = INLINE_TYPES[node.name as keyof typeof INLINE_TYPES];
242
- if (!inlineType) {
243
- return null;
244
- }
245
- const className = inlineMarkDecorations[inlineType].spec.class as string;
246
-
247
- return `<span class="${className}">${children}</span>`;
248
- }
249
- }
250
-
251
- /**
252
- * Theme for inline styling
253
- */
254
- const theme = createTheme({
255
- default: {
256
- // Emphasis (italic)
257
- ".cm-draftly-emphasis": {
258
- fontStyle: "italic",
259
- },
260
-
261
- // Strong (bold)
262
- ".cm-draftly-strong": {
263
- fontWeight: "bold",
264
- },
265
-
266
- // Strikethrough
267
- ".cm-draftly-strikethrough": {
268
- textDecoration: "line-through",
269
- opacity: "0.7",
270
- },
271
-
272
- // Subscript
273
- ".cm-draftly-subscript": {
274
- fontSize: "0.75em",
275
- verticalAlign: "sub",
276
- },
277
-
278
- // Superscript
279
- ".cm-draftly-superscript": {
280
- fontSize: "0.75em",
281
- verticalAlign: "super",
282
- },
283
-
284
- // Highlight
285
- ".cm-draftly-highlight": {
286
- backgroundColor: "rgba(255, 213, 0, 0.35)",
287
- borderRadius: "2px",
288
- padding: "1px 2px",
289
- },
290
- },
291
- });
1
+ import { Decoration, KeyBinding } from "@codemirror/view";
2
+ import { syntaxTree } from "@codemirror/language";
3
+ import { DecorationContext, DecorationPlugin } from "../editor/plugin";
4
+ import { createTheme } from "../editor";
5
+ import { SyntaxNode } from "@lezer/common";
6
+ import { toggleMarkdownStyle } from "../editor/utils";
7
+ import { tags } from "@lezer/highlight";
8
+ import type { MarkdownConfig, InlineParser } from "@lezer/markdown";
9
+ import { Extension } from "@codemirror/state";
10
+ import { createWrapSelectionInputHandler } from "../lib";
11
+
12
+ /**
13
+ * Node types for inline styling in markdown
14
+ */
15
+ const INLINE_TYPES = {
16
+ Emphasis: "emphasis",
17
+ StrongEmphasis: "strong",
18
+ Strikethrough: "strikethrough",
19
+ Subscript: "subscript",
20
+ Superscript: "superscript",
21
+ Highlight: "highlight",
22
+ } as const;
23
+
24
+ /**
25
+ * Mark decorations for inline content
26
+ */
27
+ const inlineMarkDecorations = {
28
+ emphasis: Decoration.mark({ class: "cm-draftly-emphasis" }),
29
+ strong: Decoration.mark({ class: "cm-draftly-strong" }),
30
+ strikethrough: Decoration.mark({ class: "cm-draftly-strikethrough" }),
31
+ subscript: Decoration.mark({ class: "cm-draftly-subscript" }),
32
+ superscript: Decoration.mark({ class: "cm-draftly-superscript" }),
33
+ highlight: Decoration.mark({ class: "cm-draftly-highlight" }),
34
+ // Markers (* _ ~~ ^ ~ ==)
35
+ "inline-mark": Decoration.replace({}),
36
+ };
37
+
38
+ // Character code for '='
39
+ const EQUALS = 61;
40
+
41
+ // Punctuation regex for flanking checks (matches Unicode punctuation)
42
+ // eslint-disable-next-line no-useless-escape
43
+ let Punctuation = /[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~\xA1\u2010-\u2027]/;
44
+ try {
45
+ Punctuation = new RegExp("[\\p{S}|\\p{P}]", "u");
46
+ } catch {
47
+ // Fallback regex is used above for environments without Unicode support
48
+ }
49
+
50
+ // Delimiter type for highlight markers — enables nested inline parsing
51
+ const HighlightDelim = { resolve: "Highlight", mark: "HighlightMark" };
52
+
53
+ /**
54
+ * Inline parser for highlight syntax: ==text==
55
+ * Uses addDelimiter (like Strikethrough) so nested inline styles work.
56
+ */
57
+ const highlightParser: InlineParser = {
58
+ name: "Highlight",
59
+ parse(cx, next, pos) {
60
+ // Must start with ==
61
+ if (next !== EQUALS || cx.char(pos + 1) !== EQUALS) return -1;
62
+ // Don't match === (or more)
63
+ if (cx.char(pos + 2) === EQUALS) return -1;
64
+
65
+ // Flanking checks (same logic as Strikethrough)
66
+ const before = cx.slice(pos - 1, pos);
67
+ const after = cx.slice(pos + 2, pos + 3);
68
+ const sBefore = /\s|^$/.test(before),
69
+ sAfter = /\s|^$/.test(after);
70
+ const pBefore = Punctuation.test(before),
71
+ pAfter = Punctuation.test(after);
72
+
73
+ return cx.addDelimiter(
74
+ HighlightDelim,
75
+ pos,
76
+ pos + 2,
77
+ !sAfter && (!pAfter || sBefore || pBefore),
78
+ !sBefore && (!pBefore || sAfter || pAfter)
79
+ );
80
+ },
81
+ };
82
+
83
+ /**
84
+ * InlinePlugin - Decorates inline markdown formatting
85
+ *
86
+ * Adds visual styling to inline elements:
87
+ * - Emphasis (italic) - *text* or _text_
88
+ * - Strong (bold) - **text** or __text__
89
+ * - Strikethrough - ~~text~~
90
+ * - Subscript - ~text~
91
+ * - Superscript - ^text^
92
+ * - Highlight - ==text==
93
+ *
94
+ * Hides formatting markers when cursor is not in the element
95
+ */
96
+ export class InlinePlugin extends DecorationPlugin {
97
+ readonly name = "inline";
98
+ readonly version = "1.0.0";
99
+ override decorationPriority = 20;
100
+ override readonly requiredNodes = [
101
+ "Emphasis",
102
+ "StrongEmphasis",
103
+ "Strikethrough",
104
+ "Subscript",
105
+ "Superscript",
106
+ "Highlight",
107
+ "EmphasisMark",
108
+ "StrikethroughMark",
109
+ "SubscriptMark",
110
+ "SuperscriptMark",
111
+ "HighlightMark",
112
+ ] as const;
113
+ marks: string[] = [];
114
+
115
+ constructor() {
116
+ super();
117
+
118
+ for (const mark of Object.keys(INLINE_TYPES)) {
119
+ this.marks.push(...this.getMarkerNames(mark));
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Plugin theme
125
+ */
126
+ override get theme() {
127
+ return theme;
128
+ }
129
+
130
+ /**
131
+ * Keyboard shortcuts for inline formatting
132
+ */
133
+ override getKeymap(): KeyBinding[] {
134
+ return [
135
+ {
136
+ key: "Mod-b",
137
+ run: toggleMarkdownStyle("**"),
138
+ preventDefault: true,
139
+ },
140
+ {
141
+ key: "Mod-i",
142
+ run: toggleMarkdownStyle("*"),
143
+ preventDefault: true,
144
+ },
145
+ {
146
+ key: "Mod-Shift-s",
147
+ run: toggleMarkdownStyle("~~"),
148
+ preventDefault: true,
149
+ },
150
+ {
151
+ key: "Mod-,",
152
+ run: toggleMarkdownStyle("~"),
153
+ preventDefault: true,
154
+ },
155
+ {
156
+ key: "Mod-.",
157
+ run: toggleMarkdownStyle("^"),
158
+ preventDefault: true,
159
+ },
160
+ {
161
+ key: "Mod-Shift-h",
162
+ run: toggleMarkdownStyle("=="),
163
+ preventDefault: true,
164
+ },
165
+ ];
166
+ }
167
+
168
+ /**
169
+ * Intercepts inline marker typing to wrap selected text.
170
+ *
171
+ * If user types inline markers while text is selected, wraps each selected
172
+ * range with the appropriate marker:
173
+ * - * _ ~ ^ -> marker + selected + marker
174
+ * - = -> ==selected==
175
+ */
176
+ override getExtensions(): Extension[] {
177
+ return [createWrapSelectionInputHandler({ "*": "*", _: "_", "~": "~", "^": "^", "=": "==" })];
178
+ }
179
+
180
+ /**
181
+ * Return markdown parser extensions for highlight syntax (==text==)
182
+ */
183
+ override getMarkdownConfig(): MarkdownConfig {
184
+ return {
185
+ defineNodes: [
186
+ { name: "Highlight", style: tags.emphasis },
187
+ { name: "HighlightMark", style: tags.processingInstruction },
188
+ ],
189
+ parseInline: [highlightParser],
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Build inline decorations by iterating the syntax tree
195
+ */
196
+ buildDecorations(ctx: DecorationContext): void {
197
+ const { view, decorations } = ctx;
198
+ const tree = syntaxTree(view.state);
199
+
200
+ tree.iterate({
201
+ enter: (node) => {
202
+ const { from, to, name } = node;
203
+
204
+ // Check if this is an inline type we handle
205
+ const inlineType = INLINE_TYPES[name as keyof typeof INLINE_TYPES];
206
+ if (!inlineType) {
207
+ return;
208
+ }
209
+
210
+ // Add mark decoration for the content
211
+ decorations.push(inlineMarkDecorations[inlineType].range(from, to));
212
+
213
+ // Only hide markers when cursor is not in the element
214
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
215
+ if (!cursorInNode) {
216
+ // Get the appropriate marker children based on type
217
+ const markerNames = this.getMarkerNames(name);
218
+ for (const markerName of markerNames) {
219
+ const marks = node.node.getChildren(markerName);
220
+ for (const mark of marks) {
221
+ decorations.push(inlineMarkDecorations["inline-mark"].range(mark.from, mark.to));
222
+ }
223
+ }
224
+ }
225
+ },
226
+ });
227
+ }
228
+
229
+ /**
230
+ * Get the marker node names for a given inline type
231
+ */
232
+ private getMarkerNames(nodeType: string): string[] {
233
+ switch (nodeType) {
234
+ case "Emphasis":
235
+ case "StrongEmphasis":
236
+ return ["EmphasisMark"];
237
+ case "Strikethrough":
238
+ return ["StrikethroughMark"];
239
+ case "Subscript":
240
+ return ["SubscriptMark"];
241
+ case "Superscript":
242
+ return ["SuperscriptMark"];
243
+ case "Highlight":
244
+ return ["HighlightMark"];
245
+ default:
246
+ return [];
247
+ }
248
+ }
249
+
250
+ override renderToHTML(node: SyntaxNode, children: string): string | null {
251
+ if (this.marks.includes(node.name)) {
252
+ return "";
253
+ }
254
+
255
+ const inlineType = INLINE_TYPES[node.name as keyof typeof INLINE_TYPES];
256
+ if (!inlineType) {
257
+ return null;
258
+ }
259
+ const className = inlineMarkDecorations[inlineType].spec.class as string;
260
+
261
+ return `<span class="${className}">${children}</span>`;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Theme for inline styling
267
+ */
268
+ const theme = createTheme({
269
+ default: {
270
+ // Emphasis (italic)
271
+ ".cm-draftly-emphasis": {
272
+ fontStyle: "italic",
273
+ },
274
+
275
+ // Strong (bold)
276
+ ".cm-draftly-strong": {
277
+ fontWeight: "bold",
278
+ },
279
+
280
+ // Strikethrough
281
+ ".cm-draftly-strikethrough": {
282
+ textDecoration: "line-through",
283
+ opacity: "0.7",
284
+ },
285
+
286
+ // Subscript
287
+ ".cm-draftly-subscript": {
288
+ fontSize: "0.75em",
289
+ verticalAlign: "sub",
290
+ },
291
+
292
+ // Superscript
293
+ ".cm-draftly-superscript": {
294
+ fontSize: "0.75em",
295
+ verticalAlign: "super",
296
+ },
297
+
298
+ // Highlight
299
+ ".cm-draftly-highlight": {
300
+ backgroundColor: "rgba(255, 213, 0, 0.35)",
301
+ borderRadius: "2px",
302
+ padding: "1px 2px",
303
+ },
304
+ },
305
+ });
@@ -1,4 +1,5 @@
1
1
  import { Decoration, EditorView, WidgetType } from "@codemirror/view";
2
+ import { Extension } from "@codemirror/state";
2
3
  import { syntaxTree } from "@codemirror/language";
3
4
  import { DecorationContext, DecorationPlugin } from "../editor/plugin";
4
5
  import { createTheme } from "../editor";
@@ -6,6 +7,7 @@ import { SyntaxNode } from "@lezer/common";
6
7
  import { tags } from "@lezer/highlight";
7
8
  import type { MarkdownConfig, InlineParser, BlockParser, Line, BlockContext } from "@lezer/markdown";
8
9
  import katex from "katex";
10
+ import { createWrapSelectionInputHandler } from "../lib";
9
11
  // @ts-expect-error - raw import for CSS as string
10
12
  import katexCss from "katex/dist/katex.min.css?raw";
11
13
 
@@ -279,6 +281,16 @@ export class MathPlugin extends DecorationPlugin {
279
281
  return theme;
280
282
  }
281
283
 
284
+ /**
285
+ * Intercepts dollar typing to wrap selected text as inline math.
286
+ *
287
+ * If user types '$' while text is selected, wraps each selected range
288
+ * with single dollars (selected -> $selected$).
289
+ */
290
+ override getExtensions(): Extension[] {
291
+ return [createWrapSelectionInputHandler({ "$": "$" })];
292
+ }
293
+
282
294
  /**
283
295
  * Return markdown parser extensions for math syntax
284
296
  */