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.
- package/dist/index.cjs +1144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +257 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +1128 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/draftly.ts +198 -0
- package/src/index.ts +7 -0
- package/src/plugin.ts +245 -0
- package/src/plugins/heading-plugin.ts +163 -0
- package/src/plugins/html-plugin.ts +347 -0
- package/src/plugins/inline-plugin.ts +152 -0
- package/src/plugins/list-plugin.ts +211 -0
- package/src/plugins/plugins.ts +9 -0
- package/src/theme.ts +86 -0
- package/src/utils.ts +21 -0
- package/src/view-plugin.ts +304 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Decoration, EditorView, KeyBinding, ViewUpdate, WidgetType } from "@codemirror/view";
|
|
2
|
+
import { Extension, Range } from "@codemirror/state";
|
|
3
|
+
import { MarkdownConfig } from "@lezer/markdown";
|
|
4
|
+
import { DraftlyConfig } from "./draftly";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Context passed to plugin lifecycle methods
|
|
8
|
+
*/
|
|
9
|
+
export interface PluginContext {
|
|
10
|
+
/** Current configuration */
|
|
11
|
+
readonly config: DraftlyConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Plugin configuration schema
|
|
16
|
+
*/
|
|
17
|
+
export interface PluginConfig {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decoration context passed to plugin decoration builders
|
|
23
|
+
* Provides access to view state and decoration collection
|
|
24
|
+
*/
|
|
25
|
+
export interface DecorationContext {
|
|
26
|
+
/** The EditorView instance (readonly) */
|
|
27
|
+
readonly view: EditorView;
|
|
28
|
+
|
|
29
|
+
/** Array to push decorations into (will be sorted automatically) */
|
|
30
|
+
readonly decorations: Range<Decoration>[];
|
|
31
|
+
|
|
32
|
+
/** Check if selection overlaps with a range (to show raw markdown) */
|
|
33
|
+
selectionOverlapsRange(from: number, to: number): boolean;
|
|
34
|
+
|
|
35
|
+
/** Check if cursor is within a range */
|
|
36
|
+
cursorInRange(from: number, to: number): boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Abstract base class for all draftly plugins
|
|
41
|
+
*
|
|
42
|
+
* Implements OOP principles:
|
|
43
|
+
* - Abstraction: abstract name/version must be implemented by subclasses
|
|
44
|
+
* - Encapsulation: private _config, protected _context
|
|
45
|
+
* - Inheritance: specialized plugin classes can extend this
|
|
46
|
+
*/
|
|
47
|
+
export abstract class DraftlyPlugin {
|
|
48
|
+
/** Unique plugin identifier (abstract - must be implemented) */
|
|
49
|
+
abstract readonly name: string;
|
|
50
|
+
|
|
51
|
+
/** Plugin version (abstract - must be implemented) */
|
|
52
|
+
abstract readonly version: string;
|
|
53
|
+
|
|
54
|
+
/** Plugin dependencies - names of required plugins */
|
|
55
|
+
readonly dependencies: string[] = [];
|
|
56
|
+
|
|
57
|
+
/** Private configuration storage */
|
|
58
|
+
private _config: PluginConfig = {};
|
|
59
|
+
|
|
60
|
+
/** Protected context - accessible to subclasses */
|
|
61
|
+
protected _context: PluginContext | null = null;
|
|
62
|
+
|
|
63
|
+
/** Get plugin configuration */
|
|
64
|
+
get config(): PluginConfig {
|
|
65
|
+
return this._config;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Set plugin configuration */
|
|
69
|
+
set config(value: PluginConfig) {
|
|
70
|
+
this._config = value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Get plugin context */
|
|
74
|
+
get context(): PluginContext | null {
|
|
75
|
+
return this._context;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================
|
|
79
|
+
// EXTENSION METHODS (overridable by subclasses)
|
|
80
|
+
// ============================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Return CodeMirror extensions for this plugin
|
|
84
|
+
* Override to provide custom extensions
|
|
85
|
+
*/
|
|
86
|
+
getExtensions(): Extension[] {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Return markdown parser extensions
|
|
92
|
+
* Override to extend markdown parsing
|
|
93
|
+
*/
|
|
94
|
+
getMarkdownConfig(): MarkdownConfig | null {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return keybindings for this plugin
|
|
100
|
+
* Override to add custom keyboard shortcuts
|
|
101
|
+
*/
|
|
102
|
+
getKeymap(): KeyBinding[] {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================
|
|
107
|
+
// DECORATION METHODS (overridable by subclasses)
|
|
108
|
+
// ============================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Decoration priority (higher = applied later)
|
|
112
|
+
* Override to customize priority. Default: 100
|
|
113
|
+
*/
|
|
114
|
+
get decorationPriority(): number {
|
|
115
|
+
return 100;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build decorations for the current view state
|
|
120
|
+
* Override to contribute decorations to the editor
|
|
121
|
+
*
|
|
122
|
+
* @param ctx - Decoration context with view and decoration array
|
|
123
|
+
*/
|
|
124
|
+
buildDecorations(_ctx: DecorationContext): void {
|
|
125
|
+
// Default implementation does nothing
|
|
126
|
+
// Subclasses override to add decorations
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ============================================
|
|
130
|
+
// LIFECYCLE HOOKS (overridable by subclasses)
|
|
131
|
+
// ============================================
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Called when plugin is registered with draftly
|
|
135
|
+
* Override to perform initialization
|
|
136
|
+
*
|
|
137
|
+
* @param context - Plugin context with configuration
|
|
138
|
+
*/
|
|
139
|
+
onRegister(context: PluginContext): void | Promise<void> {
|
|
140
|
+
this._context = context;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Called when plugin is unregistered
|
|
145
|
+
* Override to perform cleanup
|
|
146
|
+
*/
|
|
147
|
+
onUnregister(): void {
|
|
148
|
+
this._context = null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Called when EditorView is created and ready
|
|
153
|
+
* Override to perform view-specific initialization
|
|
154
|
+
*
|
|
155
|
+
* @param view - The EditorView instance
|
|
156
|
+
*/
|
|
157
|
+
onViewReady(_view: EditorView): void {
|
|
158
|
+
// Default implementation does nothing
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Called on view updates (document changes, selection changes, etc.)
|
|
163
|
+
* Override to react to editor changes
|
|
164
|
+
*
|
|
165
|
+
* @param update - The ViewUpdate with change information
|
|
166
|
+
*/
|
|
167
|
+
onViewUpdate(_update: ViewUpdate): void {
|
|
168
|
+
// Default implementation does nothing
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============================================
|
|
172
|
+
// ACCESSIBILITY METHODS (overridable by subclasses)
|
|
173
|
+
// ============================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Return ARIA attributes to add to the editor
|
|
177
|
+
* Override to improve accessibility
|
|
178
|
+
*/
|
|
179
|
+
getAriaAttributes(): Record<string, string> {
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================
|
|
184
|
+
// VIEW CONTRIBUTIONS (overridable by subclasses)
|
|
185
|
+
// ============================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Return widget types this plugin provides
|
|
189
|
+
* Override to contribute custom widgets
|
|
190
|
+
*/
|
|
191
|
+
getWidgets(): WidgetType[] {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================
|
|
196
|
+
// PROTECTED UTILITIES (for subclasses)
|
|
197
|
+
// ============================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Helper to get current editor state
|
|
201
|
+
* @param view - The EditorView instance
|
|
202
|
+
*/
|
|
203
|
+
protected getState(view: EditorView) {
|
|
204
|
+
return view.state;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Helper to get current document
|
|
209
|
+
* @param view - The EditorView instance
|
|
210
|
+
*/
|
|
211
|
+
protected getDocument(view: EditorView) {
|
|
212
|
+
return view.state.doc;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Base class for plugins that primarily contribute decorations
|
|
218
|
+
* Extends DraftlyPlugin with decoration-focused defaults
|
|
219
|
+
*/
|
|
220
|
+
export abstract class DecorationPlugin extends DraftlyPlugin {
|
|
221
|
+
/**
|
|
222
|
+
* Decoration priority - lower than default for decoration plugins
|
|
223
|
+
* Override to customize
|
|
224
|
+
*/
|
|
225
|
+
override get decorationPriority(): number {
|
|
226
|
+
return 50;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Subclasses must implement this to provide decorations
|
|
231
|
+
* @param ctx - Decoration context
|
|
232
|
+
*/
|
|
233
|
+
abstract override buildDecorations(ctx: DecorationContext): void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Base class for plugins that add syntax/parser extensions
|
|
238
|
+
* Extends DraftlyPlugin with syntax-focused requirements
|
|
239
|
+
*/
|
|
240
|
+
export abstract class SyntaxPlugin extends DraftlyPlugin {
|
|
241
|
+
/**
|
|
242
|
+
* Subclasses must implement this to provide markdown config
|
|
243
|
+
*/
|
|
244
|
+
abstract override getMarkdownConfig(): MarkdownConfig;
|
|
245
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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 ATX headings in markdown
|
|
8
|
+
*/
|
|
9
|
+
const HEADING_TYPES = ["ATXHeading1", "ATXHeading2", "ATXHeading3", "ATXHeading4", "ATXHeading5", "ATXHeading6"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mark decorations for heading content
|
|
13
|
+
*/
|
|
14
|
+
const headingMarkDecorations = {
|
|
15
|
+
"heading-1": Decoration.mark({ class: "cm-draftly-h1" }),
|
|
16
|
+
"heading-2": Decoration.mark({ class: "cm-draftly-h2" }),
|
|
17
|
+
"heading-3": Decoration.mark({ class: "cm-draftly-h3" }),
|
|
18
|
+
"heading-4": Decoration.mark({ class: "cm-draftly-h4" }),
|
|
19
|
+
"heading-5": Decoration.mark({ class: "cm-draftly-h5" }),
|
|
20
|
+
"heading-6": Decoration.mark({ class: "cm-draftly-h6" }),
|
|
21
|
+
"heading-mark": Decoration.mark({ class: "cm-draftly-heading-mark" }),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Line decorations for heading lines
|
|
26
|
+
*/
|
|
27
|
+
const headingLineDecorations = {
|
|
28
|
+
"heading-1": Decoration.line({ class: "cm-draftly-line-h1" }),
|
|
29
|
+
"heading-2": Decoration.line({ class: "cm-draftly-line-h2" }),
|
|
30
|
+
"heading-3": Decoration.line({ class: "cm-draftly-line-h3" }),
|
|
31
|
+
"heading-4": Decoration.line({ class: "cm-draftly-line-h4" }),
|
|
32
|
+
"heading-5": Decoration.line({ class: "cm-draftly-line-h5" }),
|
|
33
|
+
"heading-6": Decoration.line({ class: "cm-draftly-line-h6" }),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* HeadingPlugin - Decorates markdown headings
|
|
38
|
+
*
|
|
39
|
+
* Adds visual styling to ATX headings (# through ######)
|
|
40
|
+
* - Line decorations for the entire heading line
|
|
41
|
+
* - Mark decorations for heading content
|
|
42
|
+
* - Hides # markers when cursor is not in the heading
|
|
43
|
+
*/
|
|
44
|
+
export class HeadingPlugin extends DecorationPlugin {
|
|
45
|
+
readonly name = "heading";
|
|
46
|
+
readonly version = "1.0.0";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Higher priority to ensure headings are styled first
|
|
50
|
+
*/
|
|
51
|
+
override get decorationPriority(): number {
|
|
52
|
+
return 10;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the extensions for this plugin
|
|
57
|
+
*/
|
|
58
|
+
getExtensions(): Extension[] {
|
|
59
|
+
return [headingTheme];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build heading decorations by iterating the syntax tree
|
|
64
|
+
*/
|
|
65
|
+
override buildDecorations(ctx: DecorationContext): void {
|
|
66
|
+
const { view, decorations } = ctx;
|
|
67
|
+
const tree = syntaxTree(view.state);
|
|
68
|
+
|
|
69
|
+
tree.iterate({
|
|
70
|
+
enter: (node) => {
|
|
71
|
+
const { from, to, name } = node;
|
|
72
|
+
|
|
73
|
+
if (!HEADING_TYPES.includes(name)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const level = parseInt(name.slice(-1), 10);
|
|
78
|
+
const headingClass = `heading-${level}` as keyof typeof headingMarkDecorations;
|
|
79
|
+
const lineClass = `heading-${level}` as keyof typeof headingLineDecorations;
|
|
80
|
+
|
|
81
|
+
// Add line decoration
|
|
82
|
+
const line = view.state.doc.lineAt(from);
|
|
83
|
+
decorations.push(headingLineDecorations[lineClass].range(line.from));
|
|
84
|
+
|
|
85
|
+
// Add mark decoration for the heading content
|
|
86
|
+
decorations.push(headingMarkDecorations[headingClass].range(from, to + 1));
|
|
87
|
+
|
|
88
|
+
// Find and style the heading marker (#)
|
|
89
|
+
// Only hide when cursor is not in the heading
|
|
90
|
+
const cursorInNode = ctx.selectionOverlapsRange(from, to);
|
|
91
|
+
if (!cursorInNode) {
|
|
92
|
+
const headingMark = node.node.getChild("HeaderMark");
|
|
93
|
+
if (headingMark) {
|
|
94
|
+
decorations.push(headingMarkDecorations["heading-mark"].range(headingMark.from, headingMark.to + 1));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const headingTheme = EditorView.theme({
|
|
103
|
+
".cm-draftly-h1": {
|
|
104
|
+
fontSize: "2em",
|
|
105
|
+
fontWeight: "bold",
|
|
106
|
+
fontFamily: "sans-serif",
|
|
107
|
+
textDecoration: "none",
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
".cm-draftly-h2": {
|
|
111
|
+
fontSize: "1.75em",
|
|
112
|
+
fontWeight: "bold",
|
|
113
|
+
fontFamily: "sans-serif",
|
|
114
|
+
textDecoration: "none",
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
".cm-draftly-h3": {
|
|
118
|
+
fontSize: "1.5em",
|
|
119
|
+
fontWeight: "bold",
|
|
120
|
+
fontFamily: "sans-serif",
|
|
121
|
+
textDecoration: "none",
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
".cm-draftly-h4": {
|
|
125
|
+
fontSize: "1.25em",
|
|
126
|
+
fontWeight: "bold",
|
|
127
|
+
fontFamily: "sans-serif",
|
|
128
|
+
textDecoration: "none",
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
".cm-draftly-h5": {
|
|
132
|
+
fontSize: "1em",
|
|
133
|
+
fontWeight: "bold",
|
|
134
|
+
fontFamily: "sans-serif",
|
|
135
|
+
textDecoration: "none",
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
".cm-draftly-h6": {
|
|
139
|
+
fontSize: "0.75em",
|
|
140
|
+
fontWeight: "bold",
|
|
141
|
+
fontFamily: "sans-serif",
|
|
142
|
+
textDecoration: "none",
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Heading line styles
|
|
146
|
+
".cm-draftly-line-h1": {
|
|
147
|
+
paddingTop: "1.5em",
|
|
148
|
+
paddingBottom: "0.5em",
|
|
149
|
+
},
|
|
150
|
+
".cm-draftly-line-h2": {
|
|
151
|
+
paddingTop: "1.25em",
|
|
152
|
+
paddingBottom: "0.5em",
|
|
153
|
+
},
|
|
154
|
+
".cm-draftly-line-h3, .cm-draftly-line-h4, .cm-draftly-line-h5, .cm-draftly-line-h6": {
|
|
155
|
+
paddingTop: "1em",
|
|
156
|
+
paddingBottom: "0.5em",
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Heading mark (# symbols)
|
|
160
|
+
".cm-draftly-heading-mark": {
|
|
161
|
+
display: "none",
|
|
162
|
+
},
|
|
163
|
+
});
|