draftly 0.1.0-alpha.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.
@@ -0,0 +1,347 @@
1
+ import { Decoration, EditorView, WidgetType } from "@codemirror/view";
2
+ import { syntaxTree } from "@codemirror/language";
3
+ import { Extension } from "@codemirror/state";
4
+ import { DecorationContext, DecorationPlugin } from "../plugin";
5
+ import DOMPurify from "dompurify";
6
+
7
+ /**
8
+ * Mark decorations for HTML content
9
+ */
10
+ const htmlMarkDecorations = {
11
+ "html-tag": Decoration.mark({ class: "cm-draftly-html-tag" }),
12
+ "html-comment": Decoration.mark({ class: "cm-draftly-html-comment" }),
13
+ };
14
+
15
+ /**
16
+ * Line decorations for HTML blocks (when visible)
17
+ */
18
+ const htmlLineDecorations = {
19
+ "html-block": Decoration.line({ class: "cm-draftly-line-html-block" }),
20
+ "hidden-line": Decoration.line({ class: "cm-draftly-hidden-line" }),
21
+ };
22
+
23
+ /**
24
+ * Widget to render sanitized HTML (block)
25
+ */
26
+ class HTMLPreviewWidget extends WidgetType {
27
+ constructor(readonly html: string) {
28
+ super();
29
+ }
30
+
31
+ eq(other: HTMLPreviewWidget): boolean {
32
+ return other.html === this.html;
33
+ }
34
+
35
+ toDOM() {
36
+ const div = document.createElement("div");
37
+ div.className = "cm-draftly-html-preview";
38
+ div.innerHTML = DOMPurify.sanitize(this.html);
39
+ return div;
40
+ }
41
+
42
+ ignoreEvent() {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Widget to render sanitized inline HTML
49
+ */
50
+ class InlineHTMLPreviewWidget extends WidgetType {
51
+ constructor(readonly html: string) {
52
+ super();
53
+ }
54
+
55
+ eq(other: InlineHTMLPreviewWidget): boolean {
56
+ return other.html === this.html;
57
+ }
58
+
59
+ toDOM() {
60
+ const span = document.createElement("span");
61
+ span.className = "cm-draftly-inline-html-preview";
62
+ span.innerHTML = DOMPurify.sanitize(this.html);
63
+ return span;
64
+ }
65
+
66
+ ignoreEvent() {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ interface HTMLGroup {
72
+ from: number;
73
+ to: number;
74
+ }
75
+
76
+ interface HTMLTagInfo {
77
+ from: number;
78
+ to: number;
79
+ tagName: string;
80
+ isClosing: boolean;
81
+ isSelfClosing: boolean;
82
+ }
83
+
84
+ interface InlineHTMLElement {
85
+ from: number;
86
+ to: number;
87
+ content: string;
88
+ }
89
+
90
+ /**
91
+ * Parse an HTML tag to extract its name and type
92
+ */
93
+ function parseHTMLTag(content: string): { tagName: string; isClosing: boolean; isSelfClosing: boolean } | null {
94
+ const match = content.match(/^<\s*(\/?)([a-zA-Z][a-zA-Z0-9-]*)[^>]*(\/?)>$/);
95
+ if (!match) return null;
96
+
97
+ return {
98
+ tagName: match[2]!.toLowerCase(),
99
+ isClosing: match[1] === "/",
100
+ isSelfClosing:
101
+ match[3] === "/" ||
102
+ ["br", "hr", "img", "input", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"].includes(
103
+ match[2]!.toLowerCase()
104
+ ),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * HTMLPlugin - Decorates and Renders HTML in markdown
110
+ */
111
+ export class HTMLPlugin extends DecorationPlugin {
112
+ readonly name = "html";
113
+ readonly version = "1.0.0";
114
+
115
+ override get decorationPriority(): number {
116
+ return 30;
117
+ }
118
+
119
+ override getExtensions(): Extension[] {
120
+ return [htmlTheme];
121
+ }
122
+
123
+ override buildDecorations(ctx: DecorationContext): void {
124
+ const { view, decorations } = ctx;
125
+ const tree = syntaxTree(view.state);
126
+
127
+ // Collect blocks and inline tags
128
+ const htmlGroups: HTMLGroup[] = [];
129
+ const htmlTags: HTMLTagInfo[] = [];
130
+
131
+ tree.iterate({
132
+ enter: (node) => {
133
+ const { from, to, name } = node;
134
+
135
+ // Handle HTML Comments
136
+ if (name === "Comment") {
137
+ decorations.push(htmlMarkDecorations["html-comment"].range(from, to));
138
+ return;
139
+ }
140
+
141
+ // Collect inline HTML tags for pairing
142
+ if (name === "HTMLTag") {
143
+ const content = view.state.sliceDoc(from, to);
144
+ const parsed = parseHTMLTag(content);
145
+ if (parsed) {
146
+ htmlTags.push({
147
+ from,
148
+ to,
149
+ tagName: parsed.tagName,
150
+ isClosing: parsed.isClosing,
151
+ isSelfClosing: parsed.isSelfClosing,
152
+ });
153
+ }
154
+ }
155
+
156
+ // Handle HTML Blocks - Collect for grouping
157
+ if (name === "HTMLBlock") {
158
+ const last = htmlGroups[htmlGroups.length - 1];
159
+ if (last) {
160
+ const gap = view.state.sliceDoc(last.to, from);
161
+ if (!gap.trim()) {
162
+ last.to = to;
163
+ return;
164
+ }
165
+ }
166
+ htmlGroups.push({ from, to });
167
+ }
168
+ },
169
+ });
170
+
171
+ // Find complete inline HTML elements (must be on same line)
172
+ const inlineElements: InlineHTMLElement[] = [];
173
+ const usedTags = new Set<number>(); // Track used tag indices
174
+
175
+ for (let i = 0; i < htmlTags.length; i++) {
176
+ if (usedTags.has(i)) continue;
177
+
178
+ const openTag = htmlTags[i]!;
179
+ if (openTag.isClosing) continue;
180
+
181
+ // Handle self-closing tags
182
+ if (openTag.isSelfClosing) {
183
+ inlineElements.push({
184
+ from: openTag.from,
185
+ to: openTag.to,
186
+ content: view.state.sliceDoc(openTag.from, openTag.to),
187
+ });
188
+ usedTags.add(i);
189
+ continue;
190
+ }
191
+
192
+ // Find matching closing tag (must be on same line)
193
+ const openLine = view.state.doc.lineAt(openTag.from);
194
+ let depth = 1;
195
+ let closeTagIndex: number | null = null;
196
+
197
+ for (let j = i + 1; j < htmlTags.length && depth > 0; j++) {
198
+ const tag = htmlTags[j]!;
199
+
200
+ // Stop if we've gone past the open tag's line
201
+ if (tag.from > openLine.to) break;
202
+
203
+ if (tag.tagName === openTag.tagName) {
204
+ if (tag.isClosing) {
205
+ depth--;
206
+ if (depth === 0) {
207
+ closeTagIndex = j;
208
+ }
209
+ } else if (!tag.isSelfClosing) {
210
+ depth++;
211
+ }
212
+ }
213
+ }
214
+
215
+ if (closeTagIndex !== null) {
216
+ const closeTag = htmlTags[closeTagIndex]!;
217
+ inlineElements.push({
218
+ from: openTag.from,
219
+ to: closeTag.to,
220
+ content: view.state.sliceDoc(openTag.from, closeTag.to),
221
+ });
222
+
223
+ // Mark all tags within this range as used (to handle nesting)
224
+ for (let k = i; k <= closeTagIndex; k++) {
225
+ usedTags.add(k);
226
+ }
227
+ }
228
+ }
229
+
230
+ // Sort by position and filter out overlapping elements (keep outermost)
231
+ inlineElements.sort((a, b) => a.from - b.from);
232
+ const filteredElements: InlineHTMLElement[] = [];
233
+ let lastEnd = -1;
234
+
235
+ for (const elem of inlineElements) {
236
+ if (elem.from >= lastEnd) {
237
+ filteredElements.push(elem);
238
+ lastEnd = elem.to;
239
+ }
240
+ }
241
+
242
+ // Apply decorations for inline elements
243
+ for (const elem of filteredElements) {
244
+ const cursorInRange = ctx.cursorInRange(elem.from, elem.to);
245
+
246
+ if (cursorInRange) {
247
+ // Show source - find and style the tags within this element
248
+ for (const tag of htmlTags) {
249
+ if (tag.from >= elem.from && tag.to <= elem.to) {
250
+ decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
251
+ }
252
+ }
253
+ } else {
254
+ // Render preview
255
+ decorations.push(
256
+ Decoration.replace({
257
+ widget: new InlineHTMLPreviewWidget(elem.content),
258
+ }).range(elem.from, elem.to)
259
+ );
260
+ }
261
+ }
262
+
263
+ // Style any remaining unprocessed tags (orphan tags)
264
+ for (let i = 0; i < htmlTags.length; i++) {
265
+ if (!usedTags.has(i)) {
266
+ const tag = htmlTags[i]!;
267
+ decorations.push(htmlMarkDecorations["html-tag"].range(tag.from, tag.to));
268
+ }
269
+ }
270
+
271
+ // Process gathered HTML block groups
272
+ for (const group of htmlGroups) {
273
+ const { from, to } = group;
274
+
275
+ const nodeLineStart = view.state.doc.lineAt(from);
276
+ const nodeLineEnd = view.state.doc.lineAt(to);
277
+ const cursorInRange = ctx.cursorInRange(nodeLineStart.from, nodeLineEnd.to);
278
+
279
+ if (cursorInRange) {
280
+ for (let i = nodeLineStart.number; i <= nodeLineEnd.number; i++) {
281
+ const line = view.state.doc.line(i);
282
+ decorations.push(htmlLineDecorations["html-block"].range(line.from));
283
+ }
284
+ } else {
285
+ const htmlContent = view.state.sliceDoc(from, to);
286
+
287
+ decorations.push(
288
+ Decoration.replace({
289
+ widget: new HTMLPreviewWidget(htmlContent.trim()),
290
+ }).range(from, nodeLineStart.to)
291
+ );
292
+
293
+ for (let i = nodeLineStart.number + 1; i <= nodeLineEnd.number; i++) {
294
+ const line = view.state.doc.line(i);
295
+ decorations.push(htmlLineDecorations["hidden-line"].range(line.from));
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Theme for HTML styling
304
+ */
305
+ const htmlTheme = EditorView.theme({
306
+ ".cm-draftly-html-tag": {
307
+ color: "#6a737d",
308
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
309
+ fontSize: "0.85em",
310
+ },
311
+
312
+ ".cm-draftly-html-comment": {
313
+ color: "#6a737d",
314
+ fontStyle: "italic",
315
+ fontFamily: "var(--font-jetbrains-mono, monospace)",
316
+ fontSize: "0.85em",
317
+ opacity: 0.5,
318
+ },
319
+
320
+ ".cm-draftly-line-html-block": {
321
+ backgroundColor: "rgba(0, 0, 0, 0.02)",
322
+ },
323
+
324
+ ".cm-draftly-hidden-line": {
325
+ display: "none",
326
+ },
327
+
328
+ ".cm-draftly-html-preview": {
329
+ display: "inline-block",
330
+ width: "100%",
331
+ verticalAlign: "top",
332
+ margin: "0",
333
+ whiteSpace: "normal",
334
+ lineHeight: "1.4",
335
+ },
336
+ ".cm-draftly-html-preview > *:first-child": {
337
+ marginTop: "0",
338
+ },
339
+ ".cm-draftly-html-preview > *:last-child": {
340
+ marginBottom: "0",
341
+ },
342
+
343
+ ".cm-draftly-inline-html-preview": {
344
+ display: "inline",
345
+ whiteSpace: "normal",
346
+ },
347
+ });
@@ -0,0 +1,152 @@
1
+ import { Decoration, EditorView } from "@codemirror/view";
2
+ import { syntaxTree } from "@codemirror/language";
3
+ import { DecorationContext, DecorationPlugin } from "../plugin";
4
+ import { Extension } from "@codemirror/state";
5
+
6
+ /**
7
+ * Node types for inline styling in markdown
8
+ */
9
+ const INLINE_TYPES = {
10
+ Emphasis: "emphasis",
11
+ StrongEmphasis: "strong",
12
+ Strikethrough: "strikethrough",
13
+ Subscript: "subscript",
14
+ Superscript: "superscript",
15
+ } as const;
16
+
17
+ /**
18
+ * Mark decorations for inline content
19
+ */
20
+ const inlineMarkDecorations = {
21
+ emphasis: Decoration.mark({ class: "cm-draftly-emphasis" }),
22
+ strong: Decoration.mark({ class: "cm-draftly-strong" }),
23
+ strikethrough: Decoration.mark({ class: "cm-draftly-strikethrough" }),
24
+ subscript: Decoration.mark({ class: "cm-draftly-subscript" }),
25
+ superscript: Decoration.mark({ class: "cm-draftly-superscript" }),
26
+ // Markers (* _ ~~ ^ ~)
27
+ "inline-mark": Decoration.mark({ class: "cm-draftly-inline-mark" }),
28
+ };
29
+
30
+ /**
31
+ * InlinePlugin - Decorates inline markdown formatting
32
+ *
33
+ * Adds visual styling to inline elements:
34
+ * - Emphasis (italic) - *text* or _text_
35
+ * - Strong (bold) - **text** or __text__
36
+ * - Strikethrough - ~~text~~
37
+ * - Subscript - ~text~
38
+ * - Superscript - ^text^
39
+ *
40
+ * Hides formatting markers when cursor is not in the element
41
+ */
42
+ export class InlinePlugin extends DecorationPlugin {
43
+ readonly name = "inline";
44
+ readonly version = "1.0.0";
45
+
46
+ /**
47
+ * Moderate priority for inline styling
48
+ */
49
+ override get decorationPriority(): number {
50
+ return 20;
51
+ }
52
+
53
+ /**
54
+ * Get the extensions for this plugin (theme)
55
+ */
56
+ override getExtensions(): Extension[] {
57
+ return [inlineTheme];
58
+ }
59
+
60
+ /**
61
+ * Build inline decorations by iterating the syntax tree
62
+ */
63
+ override buildDecorations(ctx: DecorationContext): void {
64
+ const { view, decorations } = ctx;
65
+ const tree = syntaxTree(view.state);
66
+
67
+ tree.iterate({
68
+ enter: (node) => {
69
+ const { from, to, name } = node;
70
+
71
+ // Check if this is an inline type we handle
72
+ const inlineType = INLINE_TYPES[name as keyof typeof INLINE_TYPES];
73
+ if (!inlineType) {
74
+ return;
75
+ }
76
+
77
+ // Add mark decoration for the content
78
+ decorations.push(inlineMarkDecorations[inlineType].range(from, to));
79
+
80
+ // Only hide markers when cursor is not in the element
81
+ const cursorInNode = ctx.selectionOverlapsRange(from, to);
82
+ if (!cursorInNode) {
83
+ // Get the appropriate marker children based on type
84
+ const markerNames = this.getMarkerNames(name);
85
+ for (const markerName of markerNames) {
86
+ const marks = node.node.getChildren(markerName);
87
+ for (const mark of marks) {
88
+ decorations.push(inlineMarkDecorations["inline-mark"].range(mark.from, mark.to));
89
+ }
90
+ }
91
+ }
92
+ },
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Get the marker node names for a given inline type
98
+ */
99
+ private getMarkerNames(nodeType: string): string[] {
100
+ switch (nodeType) {
101
+ case "Emphasis":
102
+ case "StrongEmphasis":
103
+ return ["EmphasisMark"];
104
+ case "Strikethrough":
105
+ return ["StrikethroughMark"];
106
+ case "Subscript":
107
+ return ["SubscriptMark"];
108
+ case "Superscript":
109
+ return ["SuperscriptMark"];
110
+ default:
111
+ return [];
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Theme for inline styling
118
+ */
119
+ const inlineTheme = EditorView.theme({
120
+ // Emphasis (italic)
121
+ ".cm-draftly-emphasis": {
122
+ fontStyle: "italic",
123
+ },
124
+
125
+ // Strong (bold)
126
+ ".cm-draftly-strong": {
127
+ fontWeight: "bold",
128
+ },
129
+
130
+ // Strikethrough
131
+ ".cm-draftly-strikethrough": {
132
+ textDecoration: "line-through",
133
+ opacity: "0.7",
134
+ },
135
+
136
+ // Subscript
137
+ ".cm-draftly-subscript": {
138
+ fontSize: "0.75em",
139
+ verticalAlign: "sub",
140
+ },
141
+
142
+ // Superscript
143
+ ".cm-draftly-superscript": {
144
+ fontSize: "0.75em",
145
+ verticalAlign: "super",
146
+ },
147
+
148
+ // Inline markers (* _ ~~ ^ ~) - hidden when not focused
149
+ ".cm-draftly-inline-mark": {
150
+ display: "none",
151
+ },
152
+ });
@@ -0,0 +1,211 @@
1
+ import { Decoration, EditorView, WidgetType } from "@codemirror/view";
2
+ import { syntaxTree } from "@codemirror/language";
3
+ import { Extension } from "@codemirror/state";
4
+ import { DecorationContext, DecorationPlugin } from "../plugin";
5
+
6
+ /**
7
+ * Mark decorations for list items
8
+ */
9
+ const listMarkDecorations = {
10
+ "list-mark-ul": Decoration.mark({ class: "cm-draftly-list-mark-ul" }),
11
+ "list-mark-ol": Decoration.mark({ class: "cm-draftly-list-mark-ol" }),
12
+ "task-marker": Decoration.mark({ class: "cm-draftly-task-marker" }),
13
+ };
14
+
15
+ /**
16
+ * Task checkbox widget
17
+ */
18
+ export class TaskCheckboxWidget extends WidgetType {
19
+ constructor(readonly checked: boolean) {
20
+ super();
21
+ }
22
+
23
+ eq(other: TaskCheckboxWidget): boolean {
24
+ return other.checked === this.checked;
25
+ }
26
+
27
+ toDOM(view: EditorView): HTMLElement {
28
+ const wrap = document.createElement("span");
29
+ wrap.className = `cm-draftly-task-checkbox ${this.checked ? "checked" : ""}`;
30
+ wrap.setAttribute("aria-hidden", "true");
31
+
32
+ const checkbox = document.createElement("input");
33
+ checkbox.type = "checkbox";
34
+ checkbox.checked = this.checked;
35
+ checkbox.tabIndex = -1;
36
+
37
+ checkbox.addEventListener("mousedown", (e) => {
38
+ e.preventDefault();
39
+ const pos = view.posAtDOM(wrap);
40
+ // Find the task marker in the document and toggle it
41
+ const line = view.state.doc.lineAt(pos);
42
+ const match = line.text.match(/^(\s*[-*+]\s*)\[([ xX])\]/);
43
+ if (match) {
44
+ const markerStart = line.from + match[1]!.length + 1;
45
+ const newChar = this.checked ? " " : "x";
46
+ view.dispatch({
47
+ changes: { from: markerStart, to: markerStart + 1, insert: newChar },
48
+ });
49
+ }
50
+ });
51
+
52
+ wrap.appendChild(checkbox);
53
+
54
+ // Add a label for better accessibility and click area
55
+ // const label = document.createElement("span");
56
+ // wrap.appendChild(label);
57
+
58
+ return wrap;
59
+ }
60
+
61
+ ignoreEvent(): boolean {
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * ListPlugin - Decorates markdown lists
68
+ *
69
+ * Handles:
70
+ * - Unordered lists (bullet points) - Auto-styles markers (*, -, +)
71
+ * - Ordered lists (numbered) - Auto-styles numbers (1., 2.)
72
+ * - Task lists (checkboxes) - Replaces [ ]/[x] with interactive widget when not editing
73
+ */
74
+ export class ListPlugin extends DecorationPlugin {
75
+ readonly name = "list";
76
+ readonly version = "1.0.0";
77
+
78
+ /**
79
+ * Moderate priority
80
+ */
81
+ override get decorationPriority(): number {
82
+ return 20;
83
+ }
84
+
85
+ /**
86
+ * Get the extensions for this plugin (theme)
87
+ */
88
+ override getExtensions(): Extension[] {
89
+ return [listTheme];
90
+ }
91
+
92
+ /**
93
+ * Build list decorations by iterating the syntax tree
94
+ */
95
+ override buildDecorations(ctx: DecorationContext): void {
96
+ const { view, decorations } = ctx;
97
+ const tree = syntaxTree(view.state);
98
+
99
+ tree.iterate({
100
+ enter: (node) => {
101
+ const { from, to, name } = node;
102
+ const line = view.state.doc.lineAt(from);
103
+ const cursorInLine = ctx.cursorInRange(line.from, line.to);
104
+
105
+ // Handle list markers (bullets, numbers)
106
+ if (name === "ListMark") {
107
+ // Determine list type by checking grandparent (BulletList vs OrderedList)
108
+ // Parent is ListItem, Grandparent is the list container
109
+ const parent = node.node.parent;
110
+ const grandparent = parent?.parent;
111
+ const listType = grandparent?.name;
112
+
113
+ if (!cursorInLine) {
114
+ if (listType === "OrderedList") {
115
+ decorations.push(listMarkDecorations["list-mark-ol"].range(from, to));
116
+ } else {
117
+ // Default to generic/unordered for BulletList or others
118
+ decorations.push(listMarkDecorations["list-mark-ul"].range(from, to));
119
+ }
120
+ }
121
+ }
122
+
123
+ // Handle task lists
124
+ if (name === "TaskMarker") {
125
+ const text = view.state.sliceDoc(from, to);
126
+ const isChecked = text.includes("x") || text.includes("X");
127
+
128
+ if (cursorInLine) {
129
+ // If editing the line, just style the marker text
130
+ decorations.push(listMarkDecorations["task-marker"].range(from, to));
131
+ } else {
132
+ // If not editing, REPLACE the marker text with the widget
133
+ decorations.push(
134
+ Decoration.replace({
135
+ widget: new TaskCheckboxWidget(isChecked),
136
+ }).range(from, to)
137
+ );
138
+ }
139
+ }
140
+ },
141
+ });
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Theme for list styling
147
+ */
148
+ const listTheme = EditorView.theme({
149
+ // Unordered List markers (*, -, +)
150
+ ".cm-draftly-list-mark-ul": {
151
+ position: "relative",
152
+ },
153
+
154
+ ".cm-draftly-list-mark-ul > span": {
155
+ visibility: "hidden",
156
+ },
157
+
158
+ ".cm-draftly-list-mark-ul::after": {
159
+ content: '"•"',
160
+ position: "absolute",
161
+ left: "50%",
162
+ top: "50%",
163
+ transform: "translate(-50%, -50%)",
164
+ color: "var(--color-link)",
165
+ fontWeight: "bold",
166
+ pointerEvents: "none",
167
+ },
168
+
169
+ // Ordered List markers (1., 2.)
170
+ ".cm-draftly-list-mark-ol": {
171
+ color: "var(--draftly-highlight, #a4a4a4)",
172
+ fontFamily: "monospace",
173
+ marginRight: "2px",
174
+ },
175
+
176
+ // Task markers text ([ ] or [x]) - visible only when editing
177
+ ".cm-draftly-task-marker": {
178
+ color: "var(--draftly-highlight, #a4a4a4)",
179
+ fontFamily: "monospace",
180
+ },
181
+
182
+ // Task Checkbox Widget
183
+ ".cm-draftly-task-checkbox": {
184
+ display: "inline-flex",
185
+ verticalAlign: "middle",
186
+ marginRight: "0.3em",
187
+ cursor: "pointer",
188
+ userSelect: "none",
189
+ alignItems: "center",
190
+ height: "1.2em",
191
+ },
192
+
193
+ ".cm-draftly-task-checkbox input": {
194
+ cursor: "pointer",
195
+ margin: 0,
196
+ width: "1.1em",
197
+ height: "1.1em",
198
+ appearance: "none",
199
+ border: "1px solid",
200
+ borderRadius: "0.25em",
201
+ backgroundColor: "transparent",
202
+ position: "relative",
203
+ },
204
+
205
+ ".cm-draftly-task-checkbox.checked input::after": {
206
+ content: '"✓"',
207
+ position: "absolute",
208
+ left: "1px",
209
+ top: "-3px",
210
+ },
211
+ });
@@ -0,0 +1,9 @@
1
+ import { DraftlyPlugin } from "../plugin";
2
+ import { HeadingPlugin } from "./heading-plugin";
3
+ import { InlinePlugin } from "./inline-plugin";
4
+ import { ListPlugin } from "./list-plugin";
5
+ import { HTMLPlugin } from "./html-plugin";
6
+
7
+ const defaultPlugins: DraftlyPlugin[] = [new HeadingPlugin(), new InlinePlugin(), new ListPlugin(), new HTMLPlugin()];
8
+
9
+ export { defaultPlugins };