@mdfn/source 0.1.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/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # @mdfn/source
2
+
3
+ Code-oriented Markdown source editing with shared diagnostics and one-way
4
+ controller transactions. The source editor and visual editor can mount
5
+ independently against the same mdfn controller for source, split, and fallback
6
+ modes.
package/dist/index.cjs ADDED
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MDFN_SOURCE_VERSION: () => MDFN_SOURCE_VERSION,
24
+ createModeController: () => createModeController,
25
+ createPreview: () => createPreview,
26
+ createSourceEditor: () => createSourceEditor,
27
+ sourceInternals: () => sourceInternals
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var import_commands = require("@codemirror/commands");
31
+ var import_lang_markdown = require("@codemirror/lang-markdown");
32
+ var import_language = require("@codemirror/language");
33
+ var import_lint = require("@codemirror/lint");
34
+ var import_search = require("@codemirror/search");
35
+ var import_state = require("@codemirror/state");
36
+ var import_view = require("@codemirror/view");
37
+ var import_core = require("@mdfn/core");
38
+ var import_render = require("@mdfn/render");
39
+ function diagnostics(controller) {
40
+ const length = controller.getState().markdown.length;
41
+ return controller.getState().diagnostics.map((entry) => ({
42
+ from: Math.max(0, Math.min(length, entry.source?.from ?? 0)),
43
+ to: Math.max(0, Math.min(length, entry.source?.to ?? entry.source?.from ?? 0)),
44
+ severity: entry.severity === "error" ? "error" : entry.severity === "warning" ? "warning" : "info",
45
+ message: entry.message,
46
+ source: entry.extension ?? "mdfn"
47
+ }));
48
+ }
49
+ function createSourceEditor(options) {
50
+ if (typeof document === "undefined") throw new Error("MDFN_SOURCE_BROWSER_REQUIRED");
51
+ let syncing = false;
52
+ let readOnly = options.readOnly ?? false;
53
+ const readOnlyConfig = new import_state.Compartment();
54
+ const extensionKeys = Object.entries(options.controller.extensions.keymap).map(([key, command]) => ({ key, run: () => !readOnly && options.controller.run(command) }));
55
+ const extensions = [
56
+ options.lineNumbers === false ? [] : (0, import_view.lineNumbers)(),
57
+ (0, import_view.drawSelection)(),
58
+ (0, import_view.dropCursor)(),
59
+ (0, import_view.highlightActiveLine)(),
60
+ (0, import_language.bracketMatching)(),
61
+ (0, import_language.syntaxHighlighting)(import_language.defaultHighlightStyle, { fallback: true }),
62
+ (0, import_lang_markdown.markdown)({ base: import_lang_markdown.markdownLanguage }),
63
+ (0, import_lint.lintGutter)(),
64
+ (0, import_lint.linter)(() => diagnostics(options.controller), { delay: 100 }),
65
+ import_view.keymap.of([
66
+ ...extensionKeys,
67
+ { key: "Mod-z", run: () => !readOnly && options.controller.undo() },
68
+ { key: "Mod-Shift-z", run: () => !readOnly && options.controller.redo() },
69
+ { key: "Mod-y", run: () => !readOnly && options.controller.redo() },
70
+ import_commands.indentWithTab,
71
+ ...import_commands.defaultKeymap,
72
+ ...import_search.searchKeymap
73
+ ]),
74
+ readOnlyConfig.of([
75
+ import_state.EditorState.readOnly.of(readOnly),
76
+ import_view.EditorView.editable.of(!readOnly)
77
+ ]),
78
+ import_view.EditorView.contentAttributes.of({ "aria-label": options.ariaLabel ?? "Markdown source", "data-mdfn-editor": "source" }),
79
+ import_view.EditorView.domEventHandlers({
80
+ focus: () => {
81
+ options.onFocus?.();
82
+ return false;
83
+ },
84
+ blur: () => {
85
+ options.onBlur?.();
86
+ return false;
87
+ }
88
+ }),
89
+ import_view.EditorView.updateListener.of((update) => {
90
+ if (syncing || !update.docChanged && !update.selectionSet) return;
91
+ let transaction = new import_core.Transaction().withSource("source");
92
+ if (update.docChanged) {
93
+ let current = options.controller.getState().markdown;
94
+ let offset = 0;
95
+ const corrections = [];
96
+ const pasted = update.transactions.some((entry) => entry.isUserEvent("input.paste"));
97
+ const rules = pasted ? options.controller.extensions.pasteRules : options.controller.extensions.inputRules;
98
+ update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
99
+ const from = fromA + offset;
100
+ const to = toA + offset;
101
+ const rawInsert = inserted.toString();
102
+ const insert = (0, import_core.applyExtensionTextRules)(rawInsert, current, from, to, rules);
103
+ transaction = transaction.replaceSource(from, to, insert);
104
+ current = `${current.slice(0, from)}${insert}${current.slice(to)}`;
105
+ offset += insert.length - (toA - fromA);
106
+ if (insert !== rawInsert) corrections.push({ from: fromB, to: toB, insert });
107
+ });
108
+ if (corrections.length > 0) {
109
+ syncing = true;
110
+ update.view.dispatch({ changes: corrections });
111
+ syncing = false;
112
+ }
113
+ }
114
+ const main = update.view.state.selection.main;
115
+ transaction = transaction.setSelection({ kind: "text", anchor: main.anchor, head: main.head });
116
+ try {
117
+ options.controller.dispatch(transaction);
118
+ } catch {
119
+ const canonical = options.controller.getState();
120
+ const current = update.view.state.doc.toString();
121
+ const selection = canonical.selection?.kind === "text" ? {
122
+ anchor: Math.min(canonical.selection.anchor, canonical.markdown.length),
123
+ head: Math.min(canonical.selection.head, canonical.markdown.length)
124
+ } : void 0;
125
+ syncing = true;
126
+ update.view.dispatch({
127
+ changes: { from: 0, to: current.length, insert: canonical.markdown },
128
+ ...selection ? { selection } : {}
129
+ });
130
+ syncing = false;
131
+ }
132
+ })
133
+ ];
134
+ const controllerSelection = options.controller.getState().selection;
135
+ const initialSelection = controllerSelection?.kind === "text" ? { anchor: Math.min(controllerSelection.anchor, options.controller.getState().markdown.length), head: Math.min(controllerSelection.head, options.controller.getState().markdown.length) } : void 0;
136
+ const view = new import_view.EditorView({
137
+ parent: options.target,
138
+ state: import_state.EditorState.create({ doc: options.controller.getState().markdown, selection: initialSelection, extensions })
139
+ });
140
+ const unsubscribe = options.controller.subscribe((change) => {
141
+ if (change.source === "source") return;
142
+ const current = view.state.doc.toString();
143
+ const selection = change.current.selection?.kind === "text" ? { anchor: change.current.selection.anchor, head: change.current.selection.head } : void 0;
144
+ const documentChanged = current !== change.current.markdown;
145
+ if (!documentChanged && !change.selectionChanged) return;
146
+ syncing = true;
147
+ view.dispatch({
148
+ ...documentChanged ? { changes: { from: 0, to: current.length, insert: change.current.markdown } } : {},
149
+ ...selection ? { selection } : {}
150
+ });
151
+ syncing = false;
152
+ });
153
+ return {
154
+ view,
155
+ focus() {
156
+ view.focus();
157
+ },
158
+ setReadOnly(value) {
159
+ readOnly = value;
160
+ view.dispatch({
161
+ effects: readOnlyConfig.reconfigure([
162
+ import_state.EditorState.readOnly.of(readOnly),
163
+ import_view.EditorView.editable.of(!readOnly)
164
+ ])
165
+ });
166
+ },
167
+ destroy() {
168
+ unsubscribe();
169
+ view.destroy();
170
+ }
171
+ };
172
+ }
173
+ function createPreview(controller, policy) {
174
+ const rendered = (0, import_render.renderHtml)(controller.getState().document, {
175
+ ...policy,
176
+ extensions: policy?.extensions ?? controller.extensions
177
+ });
178
+ return { html: rendered.html, diagnostics: rendered.diagnostics };
179
+ }
180
+ function createModeController(initial = "visual") {
181
+ let mode = initial;
182
+ let destroyed = false;
183
+ const listeners = /* @__PURE__ */ new Set();
184
+ return {
185
+ getMode() {
186
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
187
+ return mode;
188
+ },
189
+ setMode(next) {
190
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
191
+ if (next === mode) return;
192
+ mode = next;
193
+ listeners.forEach((listener) => listener(mode));
194
+ },
195
+ subscribe(listener) {
196
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
197
+ listeners.add(listener);
198
+ return () => listeners.delete(listener);
199
+ },
200
+ destroy() {
201
+ destroyed = true;
202
+ listeners.clear();
203
+ }
204
+ };
205
+ }
206
+ var MDFN_SOURCE_VERSION = "0.1.0";
207
+ var sourceInternals = Object.freeze({ smallestSourceChange: import_core.smallestSourceChange });
208
+ // Annotate the CommonJS export names for ESM import in node:
209
+ 0 && (module.exports = {
210
+ MDFN_SOURCE_VERSION,
211
+ createModeController,
212
+ createPreview,
213
+ createSourceEditor,
214
+ sourceInternals
215
+ });
216
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { defaultKeymap, indentWithTab } from \"@codemirror/commands\";\nimport { markdown, markdownLanguage } from \"@codemirror/lang-markdown\";\nimport { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from \"@codemirror/language\";\nimport { linter, lintGutter, type Diagnostic as CodeMirrorDiagnostic } from \"@codemirror/lint\";\nimport { searchKeymap } from \"@codemirror/search\";\nimport { Compartment, EditorState as CodeMirrorState } from \"@codemirror/state\";\nimport { drawSelection, dropCursor, EditorView, highlightActiveLine, keymap, lineNumbers } from \"@codemirror/view\";\nimport { applyExtensionTextRules, smallestSourceChange, Transaction, type EditorController } from \"@mdfn/core\";\nimport { renderHtml, type RenderPolicy } from \"@mdfn/render\";\n\nexport type EditorMode = \"visual\" | \"source\" | \"split\" | \"preview\" | \"read-only\";\n\nexport interface SourceEditorOptions {\n readonly target: HTMLElement;\n readonly controller: EditorController;\n readonly readOnly?: boolean;\n readonly lineNumbers?: boolean;\n readonly ariaLabel?: string;\n readonly onFocus?: () => void;\n readonly onBlur?: () => void;\n}\n\nexport interface SourceEditor {\n readonly view: EditorView;\n focus(): void;\n setReadOnly(readOnly: boolean): void;\n destroy(): void;\n}\n\nfunction diagnostics(controller: EditorController): CodeMirrorDiagnostic[] {\n const length = controller.getState().markdown.length;\n return controller.getState().diagnostics.map((entry) => ({\n from: Math.max(0, Math.min(length, entry.source?.from ?? 0)),\n to: Math.max(0, Math.min(length, entry.source?.to ?? entry.source?.from ?? 0)),\n severity: entry.severity === \"error\" ? \"error\" : entry.severity === \"warning\" ? \"warning\" : \"info\",\n message: entry.message,\n source: entry.extension ?? \"mdfn\",\n }));\n}\n\nexport function createSourceEditor(options: SourceEditorOptions): SourceEditor {\n if (typeof document === \"undefined\") throw new Error(\"MDFN_SOURCE_BROWSER_REQUIRED\");\n let syncing = false;\n let readOnly = options.readOnly ?? false;\n const readOnlyConfig = new Compartment();\n const extensionKeys = Object.entries(options.controller.extensions.keymap).map(([key, command]) => ({ key, run: () => !readOnly && options.controller.run(command) }));\n const extensions = [\n options.lineNumbers === false ? [] : lineNumbers(),\n drawSelection(),\n dropCursor(),\n highlightActiveLine(),\n bracketMatching(),\n syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n markdown({ base: markdownLanguage }),\n lintGutter(),\n linter(() => diagnostics(options.controller), { delay: 100 }),\n keymap.of([\n ...extensionKeys,\n { key: \"Mod-z\", run: () => !readOnly && options.controller.undo() },\n { key: \"Mod-Shift-z\", run: () => !readOnly && options.controller.redo() },\n { key: \"Mod-y\", run: () => !readOnly && options.controller.redo() },\n indentWithTab,\n ...defaultKeymap,\n ...searchKeymap,\n ]),\n readOnlyConfig.of([\n CodeMirrorState.readOnly.of(readOnly),\n EditorView.editable.of(!readOnly),\n ]),\n EditorView.contentAttributes.of({ \"aria-label\": options.ariaLabel ?? \"Markdown source\", \"data-mdfn-editor\": \"source\" }),\n EditorView.domEventHandlers({\n focus: () => { options.onFocus?.(); return false; },\n blur: () => { options.onBlur?.(); return false; },\n }),\n EditorView.updateListener.of((update) => {\n if (syncing || (!update.docChanged && !update.selectionSet)) return;\n let transaction = new Transaction().withSource(\"source\");\n if (update.docChanged) {\n let current = options.controller.getState().markdown;\n let offset = 0;\n const corrections: Array<{ from: number; to: number; insert: string }> = [];\n const pasted = update.transactions.some((entry) => entry.isUserEvent(\"input.paste\"));\n const rules = pasted ? options.controller.extensions.pasteRules : options.controller.extensions.inputRules;\n update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {\n const from = fromA + offset;\n const to = toA + offset;\n const rawInsert = inserted.toString();\n const insert = applyExtensionTextRules(rawInsert, current, from, to, rules);\n transaction = transaction.replaceSource(from, to, insert);\n current = `${current.slice(0, from)}${insert}${current.slice(to)}`;\n offset += insert.length - (toA - fromA);\n if (insert !== rawInsert) corrections.push({ from: fromB, to: toB, insert });\n });\n if (corrections.length > 0) {\n syncing = true;\n update.view.dispatch({ changes: corrections });\n syncing = false;\n }\n }\n const main = update.view.state.selection.main;\n transaction = transaction.setSelection({ kind: \"text\", anchor: main.anchor, head: main.head });\n try {\n options.controller.dispatch(transaction);\n } catch {\n const canonical = options.controller.getState();\n const current = update.view.state.doc.toString();\n const selection = canonical.selection?.kind === \"text\"\n ? {\n anchor: Math.min(canonical.selection.anchor, canonical.markdown.length),\n head: Math.min(canonical.selection.head, canonical.markdown.length),\n }\n : undefined;\n syncing = true;\n update.view.dispatch({\n changes: { from: 0, to: current.length, insert: canonical.markdown },\n ...(selection ? { selection } : {}),\n });\n syncing = false;\n }\n }),\n ];\n const controllerSelection = options.controller.getState().selection;\n const initialSelection = controllerSelection?.kind === \"text\"\n ? { anchor: Math.min(controllerSelection.anchor, options.controller.getState().markdown.length), head: Math.min(controllerSelection.head, options.controller.getState().markdown.length) }\n : undefined;\n const view = new EditorView({\n parent: options.target,\n state: CodeMirrorState.create({ doc: options.controller.getState().markdown, selection: initialSelection, extensions }),\n });\n const unsubscribe = options.controller.subscribe((change) => {\n if (change.source === \"source\") return;\n const current = view.state.doc.toString();\n const selection = change.current.selection?.kind === \"text\"\n ? { anchor: change.current.selection.anchor, head: change.current.selection.head }\n : undefined;\n const documentChanged = current !== change.current.markdown;\n if (!documentChanged && !change.selectionChanged) return;\n syncing = true;\n view.dispatch({\n ...(documentChanged ? { changes: { from: 0, to: current.length, insert: change.current.markdown } } : {}),\n ...(selection ? { selection } : {}),\n });\n syncing = false;\n });\n return {\n view,\n focus() { view.focus(); },\n setReadOnly(value) {\n readOnly = value;\n view.dispatch({\n effects: readOnlyConfig.reconfigure([\n CodeMirrorState.readOnly.of(readOnly),\n EditorView.editable.of(!readOnly),\n ]),\n });\n },\n destroy() { unsubscribe(); view.destroy(); },\n };\n}\n\nexport interface PreviewResult {\n readonly html: string;\n readonly diagnostics: ReturnType<typeof renderHtml>[\"diagnostics\"];\n}\n\nexport function createPreview(controller: EditorController, policy?: RenderPolicy): PreviewResult {\n const rendered = renderHtml(controller.getState().document, {\n ...policy,\n extensions: policy?.extensions ?? controller.extensions,\n });\n return { html: rendered.html, diagnostics: rendered.diagnostics };\n}\n\nexport interface ModeController {\n getMode(): EditorMode;\n setMode(mode: EditorMode): void;\n subscribe(listener: (mode: EditorMode) => void): () => void;\n destroy(): void;\n}\n\nexport function createModeController(initial: EditorMode = \"visual\"): ModeController {\n let mode = initial;\n let destroyed = false;\n const listeners = new Set<(mode: EditorMode) => void>();\n return {\n getMode() { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); return mode; },\n setMode(next) { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); if (next === mode) return; mode = next; listeners.forEach((listener) => listener(mode)); },\n subscribe(listener) { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); listeners.add(listener); return () => listeners.delete(listener); },\n destroy() { destroyed = true; listeners.clear(); },\n };\n}\n\nexport const MDFN_SOURCE_VERSION = \"0.1.0\" as const;\n\nexport const sourceInternals = Object.freeze({ smallestSourceChange });\n"],"mappings":"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","names":["CodeMirrorState"]}
@@ -0,0 +1,39 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import { EditorController, smallestSourceChange } from '@mdfn/core';
3
+ import { renderHtml, RenderPolicy } from '@mdfn/render';
4
+
5
+ type EditorMode = "visual" | "source" | "split" | "preview" | "read-only";
6
+ interface SourceEditorOptions {
7
+ readonly target: HTMLElement;
8
+ readonly controller: EditorController;
9
+ readonly readOnly?: boolean;
10
+ readonly lineNumbers?: boolean;
11
+ readonly ariaLabel?: string;
12
+ readonly onFocus?: () => void;
13
+ readonly onBlur?: () => void;
14
+ }
15
+ interface SourceEditor {
16
+ readonly view: EditorView;
17
+ focus(): void;
18
+ setReadOnly(readOnly: boolean): void;
19
+ destroy(): void;
20
+ }
21
+ declare function createSourceEditor(options: SourceEditorOptions): SourceEditor;
22
+ interface PreviewResult {
23
+ readonly html: string;
24
+ readonly diagnostics: ReturnType<typeof renderHtml>["diagnostics"];
25
+ }
26
+ declare function createPreview(controller: EditorController, policy?: RenderPolicy): PreviewResult;
27
+ interface ModeController {
28
+ getMode(): EditorMode;
29
+ setMode(mode: EditorMode): void;
30
+ subscribe(listener: (mode: EditorMode) => void): () => void;
31
+ destroy(): void;
32
+ }
33
+ declare function createModeController(initial?: EditorMode): ModeController;
34
+ declare const MDFN_SOURCE_VERSION: "0.1.0";
35
+ declare const sourceInternals: Readonly<{
36
+ smallestSourceChange: typeof smallestSourceChange;
37
+ }>;
38
+
39
+ export { type EditorMode, MDFN_SOURCE_VERSION, type ModeController, type PreviewResult, type SourceEditor, type SourceEditorOptions, createModeController, createPreview, createSourceEditor, sourceInternals };
@@ -0,0 +1,39 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ import { EditorController, smallestSourceChange } from '@mdfn/core';
3
+ import { renderHtml, RenderPolicy } from '@mdfn/render';
4
+
5
+ type EditorMode = "visual" | "source" | "split" | "preview" | "read-only";
6
+ interface SourceEditorOptions {
7
+ readonly target: HTMLElement;
8
+ readonly controller: EditorController;
9
+ readonly readOnly?: boolean;
10
+ readonly lineNumbers?: boolean;
11
+ readonly ariaLabel?: string;
12
+ readonly onFocus?: () => void;
13
+ readonly onBlur?: () => void;
14
+ }
15
+ interface SourceEditor {
16
+ readonly view: EditorView;
17
+ focus(): void;
18
+ setReadOnly(readOnly: boolean): void;
19
+ destroy(): void;
20
+ }
21
+ declare function createSourceEditor(options: SourceEditorOptions): SourceEditor;
22
+ interface PreviewResult {
23
+ readonly html: string;
24
+ readonly diagnostics: ReturnType<typeof renderHtml>["diagnostics"];
25
+ }
26
+ declare function createPreview(controller: EditorController, policy?: RenderPolicy): PreviewResult;
27
+ interface ModeController {
28
+ getMode(): EditorMode;
29
+ setMode(mode: EditorMode): void;
30
+ subscribe(listener: (mode: EditorMode) => void): () => void;
31
+ destroy(): void;
32
+ }
33
+ declare function createModeController(initial?: EditorMode): ModeController;
34
+ declare const MDFN_SOURCE_VERSION: "0.1.0";
35
+ declare const sourceInternals: Readonly<{
36
+ smallestSourceChange: typeof smallestSourceChange;
37
+ }>;
38
+
39
+ export { type EditorMode, MDFN_SOURCE_VERSION, type ModeController, type PreviewResult, type SourceEditor, type SourceEditorOptions, createModeController, createPreview, createSourceEditor, sourceInternals };
package/dist/index.js ADDED
@@ -0,0 +1,187 @@
1
+ // src/index.ts
2
+ import { defaultKeymap, indentWithTab } from "@codemirror/commands";
3
+ import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
4
+ import { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from "@codemirror/language";
5
+ import { linter, lintGutter } from "@codemirror/lint";
6
+ import { searchKeymap } from "@codemirror/search";
7
+ import { Compartment, EditorState as CodeMirrorState } from "@codemirror/state";
8
+ import { drawSelection, dropCursor, EditorView, highlightActiveLine, keymap, lineNumbers } from "@codemirror/view";
9
+ import { applyExtensionTextRules, smallestSourceChange, Transaction } from "@mdfn/core";
10
+ import { renderHtml } from "@mdfn/render";
11
+ function diagnostics(controller) {
12
+ const length = controller.getState().markdown.length;
13
+ return controller.getState().diagnostics.map((entry) => ({
14
+ from: Math.max(0, Math.min(length, entry.source?.from ?? 0)),
15
+ to: Math.max(0, Math.min(length, entry.source?.to ?? entry.source?.from ?? 0)),
16
+ severity: entry.severity === "error" ? "error" : entry.severity === "warning" ? "warning" : "info",
17
+ message: entry.message,
18
+ source: entry.extension ?? "mdfn"
19
+ }));
20
+ }
21
+ function createSourceEditor(options) {
22
+ if (typeof document === "undefined") throw new Error("MDFN_SOURCE_BROWSER_REQUIRED");
23
+ let syncing = false;
24
+ let readOnly = options.readOnly ?? false;
25
+ const readOnlyConfig = new Compartment();
26
+ const extensionKeys = Object.entries(options.controller.extensions.keymap).map(([key, command]) => ({ key, run: () => !readOnly && options.controller.run(command) }));
27
+ const extensions = [
28
+ options.lineNumbers === false ? [] : lineNumbers(),
29
+ drawSelection(),
30
+ dropCursor(),
31
+ highlightActiveLine(),
32
+ bracketMatching(),
33
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
34
+ markdown({ base: markdownLanguage }),
35
+ lintGutter(),
36
+ linter(() => diagnostics(options.controller), { delay: 100 }),
37
+ keymap.of([
38
+ ...extensionKeys,
39
+ { key: "Mod-z", run: () => !readOnly && options.controller.undo() },
40
+ { key: "Mod-Shift-z", run: () => !readOnly && options.controller.redo() },
41
+ { key: "Mod-y", run: () => !readOnly && options.controller.redo() },
42
+ indentWithTab,
43
+ ...defaultKeymap,
44
+ ...searchKeymap
45
+ ]),
46
+ readOnlyConfig.of([
47
+ CodeMirrorState.readOnly.of(readOnly),
48
+ EditorView.editable.of(!readOnly)
49
+ ]),
50
+ EditorView.contentAttributes.of({ "aria-label": options.ariaLabel ?? "Markdown source", "data-mdfn-editor": "source" }),
51
+ EditorView.domEventHandlers({
52
+ focus: () => {
53
+ options.onFocus?.();
54
+ return false;
55
+ },
56
+ blur: () => {
57
+ options.onBlur?.();
58
+ return false;
59
+ }
60
+ }),
61
+ EditorView.updateListener.of((update) => {
62
+ if (syncing || !update.docChanged && !update.selectionSet) return;
63
+ let transaction = new Transaction().withSource("source");
64
+ if (update.docChanged) {
65
+ let current = options.controller.getState().markdown;
66
+ let offset = 0;
67
+ const corrections = [];
68
+ const pasted = update.transactions.some((entry) => entry.isUserEvent("input.paste"));
69
+ const rules = pasted ? options.controller.extensions.pasteRules : options.controller.extensions.inputRules;
70
+ update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
71
+ const from = fromA + offset;
72
+ const to = toA + offset;
73
+ const rawInsert = inserted.toString();
74
+ const insert = applyExtensionTextRules(rawInsert, current, from, to, rules);
75
+ transaction = transaction.replaceSource(from, to, insert);
76
+ current = `${current.slice(0, from)}${insert}${current.slice(to)}`;
77
+ offset += insert.length - (toA - fromA);
78
+ if (insert !== rawInsert) corrections.push({ from: fromB, to: toB, insert });
79
+ });
80
+ if (corrections.length > 0) {
81
+ syncing = true;
82
+ update.view.dispatch({ changes: corrections });
83
+ syncing = false;
84
+ }
85
+ }
86
+ const main = update.view.state.selection.main;
87
+ transaction = transaction.setSelection({ kind: "text", anchor: main.anchor, head: main.head });
88
+ try {
89
+ options.controller.dispatch(transaction);
90
+ } catch {
91
+ const canonical = options.controller.getState();
92
+ const current = update.view.state.doc.toString();
93
+ const selection = canonical.selection?.kind === "text" ? {
94
+ anchor: Math.min(canonical.selection.anchor, canonical.markdown.length),
95
+ head: Math.min(canonical.selection.head, canonical.markdown.length)
96
+ } : void 0;
97
+ syncing = true;
98
+ update.view.dispatch({
99
+ changes: { from: 0, to: current.length, insert: canonical.markdown },
100
+ ...selection ? { selection } : {}
101
+ });
102
+ syncing = false;
103
+ }
104
+ })
105
+ ];
106
+ const controllerSelection = options.controller.getState().selection;
107
+ const initialSelection = controllerSelection?.kind === "text" ? { anchor: Math.min(controllerSelection.anchor, options.controller.getState().markdown.length), head: Math.min(controllerSelection.head, options.controller.getState().markdown.length) } : void 0;
108
+ const view = new EditorView({
109
+ parent: options.target,
110
+ state: CodeMirrorState.create({ doc: options.controller.getState().markdown, selection: initialSelection, extensions })
111
+ });
112
+ const unsubscribe = options.controller.subscribe((change) => {
113
+ if (change.source === "source") return;
114
+ const current = view.state.doc.toString();
115
+ const selection = change.current.selection?.kind === "text" ? { anchor: change.current.selection.anchor, head: change.current.selection.head } : void 0;
116
+ const documentChanged = current !== change.current.markdown;
117
+ if (!documentChanged && !change.selectionChanged) return;
118
+ syncing = true;
119
+ view.dispatch({
120
+ ...documentChanged ? { changes: { from: 0, to: current.length, insert: change.current.markdown } } : {},
121
+ ...selection ? { selection } : {}
122
+ });
123
+ syncing = false;
124
+ });
125
+ return {
126
+ view,
127
+ focus() {
128
+ view.focus();
129
+ },
130
+ setReadOnly(value) {
131
+ readOnly = value;
132
+ view.dispatch({
133
+ effects: readOnlyConfig.reconfigure([
134
+ CodeMirrorState.readOnly.of(readOnly),
135
+ EditorView.editable.of(!readOnly)
136
+ ])
137
+ });
138
+ },
139
+ destroy() {
140
+ unsubscribe();
141
+ view.destroy();
142
+ }
143
+ };
144
+ }
145
+ function createPreview(controller, policy) {
146
+ const rendered = renderHtml(controller.getState().document, {
147
+ ...policy,
148
+ extensions: policy?.extensions ?? controller.extensions
149
+ });
150
+ return { html: rendered.html, diagnostics: rendered.diagnostics };
151
+ }
152
+ function createModeController(initial = "visual") {
153
+ let mode = initial;
154
+ let destroyed = false;
155
+ const listeners = /* @__PURE__ */ new Set();
156
+ return {
157
+ getMode() {
158
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
159
+ return mode;
160
+ },
161
+ setMode(next) {
162
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
163
+ if (next === mode) return;
164
+ mode = next;
165
+ listeners.forEach((listener) => listener(mode));
166
+ },
167
+ subscribe(listener) {
168
+ if (destroyed) throw new Error("MDFN_MODE_CONTROLLER_DESTROYED");
169
+ listeners.add(listener);
170
+ return () => listeners.delete(listener);
171
+ },
172
+ destroy() {
173
+ destroyed = true;
174
+ listeners.clear();
175
+ }
176
+ };
177
+ }
178
+ var MDFN_SOURCE_VERSION = "0.1.0";
179
+ var sourceInternals = Object.freeze({ smallestSourceChange });
180
+ export {
181
+ MDFN_SOURCE_VERSION,
182
+ createModeController,
183
+ createPreview,
184
+ createSourceEditor,
185
+ sourceInternals
186
+ };
187
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { defaultKeymap, indentWithTab } from \"@codemirror/commands\";\nimport { markdown, markdownLanguage } from \"@codemirror/lang-markdown\";\nimport { bracketMatching, defaultHighlightStyle, syntaxHighlighting } from \"@codemirror/language\";\nimport { linter, lintGutter, type Diagnostic as CodeMirrorDiagnostic } from \"@codemirror/lint\";\nimport { searchKeymap } from \"@codemirror/search\";\nimport { Compartment, EditorState as CodeMirrorState } from \"@codemirror/state\";\nimport { drawSelection, dropCursor, EditorView, highlightActiveLine, keymap, lineNumbers } from \"@codemirror/view\";\nimport { applyExtensionTextRules, smallestSourceChange, Transaction, type EditorController } from \"@mdfn/core\";\nimport { renderHtml, type RenderPolicy } from \"@mdfn/render\";\n\nexport type EditorMode = \"visual\" | \"source\" | \"split\" | \"preview\" | \"read-only\";\n\nexport interface SourceEditorOptions {\n readonly target: HTMLElement;\n readonly controller: EditorController;\n readonly readOnly?: boolean;\n readonly lineNumbers?: boolean;\n readonly ariaLabel?: string;\n readonly onFocus?: () => void;\n readonly onBlur?: () => void;\n}\n\nexport interface SourceEditor {\n readonly view: EditorView;\n focus(): void;\n setReadOnly(readOnly: boolean): void;\n destroy(): void;\n}\n\nfunction diagnostics(controller: EditorController): CodeMirrorDiagnostic[] {\n const length = controller.getState().markdown.length;\n return controller.getState().diagnostics.map((entry) => ({\n from: Math.max(0, Math.min(length, entry.source?.from ?? 0)),\n to: Math.max(0, Math.min(length, entry.source?.to ?? entry.source?.from ?? 0)),\n severity: entry.severity === \"error\" ? \"error\" : entry.severity === \"warning\" ? \"warning\" : \"info\",\n message: entry.message,\n source: entry.extension ?? \"mdfn\",\n }));\n}\n\nexport function createSourceEditor(options: SourceEditorOptions): SourceEditor {\n if (typeof document === \"undefined\") throw new Error(\"MDFN_SOURCE_BROWSER_REQUIRED\");\n let syncing = false;\n let readOnly = options.readOnly ?? false;\n const readOnlyConfig = new Compartment();\n const extensionKeys = Object.entries(options.controller.extensions.keymap).map(([key, command]) => ({ key, run: () => !readOnly && options.controller.run(command) }));\n const extensions = [\n options.lineNumbers === false ? [] : lineNumbers(),\n drawSelection(),\n dropCursor(),\n highlightActiveLine(),\n bracketMatching(),\n syntaxHighlighting(defaultHighlightStyle, { fallback: true }),\n markdown({ base: markdownLanguage }),\n lintGutter(),\n linter(() => diagnostics(options.controller), { delay: 100 }),\n keymap.of([\n ...extensionKeys,\n { key: \"Mod-z\", run: () => !readOnly && options.controller.undo() },\n { key: \"Mod-Shift-z\", run: () => !readOnly && options.controller.redo() },\n { key: \"Mod-y\", run: () => !readOnly && options.controller.redo() },\n indentWithTab,\n ...defaultKeymap,\n ...searchKeymap,\n ]),\n readOnlyConfig.of([\n CodeMirrorState.readOnly.of(readOnly),\n EditorView.editable.of(!readOnly),\n ]),\n EditorView.contentAttributes.of({ \"aria-label\": options.ariaLabel ?? \"Markdown source\", \"data-mdfn-editor\": \"source\" }),\n EditorView.domEventHandlers({\n focus: () => { options.onFocus?.(); return false; },\n blur: () => { options.onBlur?.(); return false; },\n }),\n EditorView.updateListener.of((update) => {\n if (syncing || (!update.docChanged && !update.selectionSet)) return;\n let transaction = new Transaction().withSource(\"source\");\n if (update.docChanged) {\n let current = options.controller.getState().markdown;\n let offset = 0;\n const corrections: Array<{ from: number; to: number; insert: string }> = [];\n const pasted = update.transactions.some((entry) => entry.isUserEvent(\"input.paste\"));\n const rules = pasted ? options.controller.extensions.pasteRules : options.controller.extensions.inputRules;\n update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {\n const from = fromA + offset;\n const to = toA + offset;\n const rawInsert = inserted.toString();\n const insert = applyExtensionTextRules(rawInsert, current, from, to, rules);\n transaction = transaction.replaceSource(from, to, insert);\n current = `${current.slice(0, from)}${insert}${current.slice(to)}`;\n offset += insert.length - (toA - fromA);\n if (insert !== rawInsert) corrections.push({ from: fromB, to: toB, insert });\n });\n if (corrections.length > 0) {\n syncing = true;\n update.view.dispatch({ changes: corrections });\n syncing = false;\n }\n }\n const main = update.view.state.selection.main;\n transaction = transaction.setSelection({ kind: \"text\", anchor: main.anchor, head: main.head });\n try {\n options.controller.dispatch(transaction);\n } catch {\n const canonical = options.controller.getState();\n const current = update.view.state.doc.toString();\n const selection = canonical.selection?.kind === \"text\"\n ? {\n anchor: Math.min(canonical.selection.anchor, canonical.markdown.length),\n head: Math.min(canonical.selection.head, canonical.markdown.length),\n }\n : undefined;\n syncing = true;\n update.view.dispatch({\n changes: { from: 0, to: current.length, insert: canonical.markdown },\n ...(selection ? { selection } : {}),\n });\n syncing = false;\n }\n }),\n ];\n const controllerSelection = options.controller.getState().selection;\n const initialSelection = controllerSelection?.kind === \"text\"\n ? { anchor: Math.min(controllerSelection.anchor, options.controller.getState().markdown.length), head: Math.min(controllerSelection.head, options.controller.getState().markdown.length) }\n : undefined;\n const view = new EditorView({\n parent: options.target,\n state: CodeMirrorState.create({ doc: options.controller.getState().markdown, selection: initialSelection, extensions }),\n });\n const unsubscribe = options.controller.subscribe((change) => {\n if (change.source === \"source\") return;\n const current = view.state.doc.toString();\n const selection = change.current.selection?.kind === \"text\"\n ? { anchor: change.current.selection.anchor, head: change.current.selection.head }\n : undefined;\n const documentChanged = current !== change.current.markdown;\n if (!documentChanged && !change.selectionChanged) return;\n syncing = true;\n view.dispatch({\n ...(documentChanged ? { changes: { from: 0, to: current.length, insert: change.current.markdown } } : {}),\n ...(selection ? { selection } : {}),\n });\n syncing = false;\n });\n return {\n view,\n focus() { view.focus(); },\n setReadOnly(value) {\n readOnly = value;\n view.dispatch({\n effects: readOnlyConfig.reconfigure([\n CodeMirrorState.readOnly.of(readOnly),\n EditorView.editable.of(!readOnly),\n ]),\n });\n },\n destroy() { unsubscribe(); view.destroy(); },\n };\n}\n\nexport interface PreviewResult {\n readonly html: string;\n readonly diagnostics: ReturnType<typeof renderHtml>[\"diagnostics\"];\n}\n\nexport function createPreview(controller: EditorController, policy?: RenderPolicy): PreviewResult {\n const rendered = renderHtml(controller.getState().document, {\n ...policy,\n extensions: policy?.extensions ?? controller.extensions,\n });\n return { html: rendered.html, diagnostics: rendered.diagnostics };\n}\n\nexport interface ModeController {\n getMode(): EditorMode;\n setMode(mode: EditorMode): void;\n subscribe(listener: (mode: EditorMode) => void): () => void;\n destroy(): void;\n}\n\nexport function createModeController(initial: EditorMode = \"visual\"): ModeController {\n let mode = initial;\n let destroyed = false;\n const listeners = new Set<(mode: EditorMode) => void>();\n return {\n getMode() { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); return mode; },\n setMode(next) { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); if (next === mode) return; mode = next; listeners.forEach((listener) => listener(mode)); },\n subscribe(listener) { if (destroyed) throw new Error(\"MDFN_MODE_CONTROLLER_DESTROYED\"); listeners.add(listener); return () => listeners.delete(listener); },\n destroy() { destroyed = true; listeners.clear(); },\n };\n}\n\nexport const MDFN_SOURCE_VERSION = \"0.1.0\" as const;\n\nexport const sourceInternals = Object.freeze({ smallestSourceChange });\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@mdfn/source",
3
+ "version": "0.1.0",
4
+ "description": "Source editor, diagnostics, and source-visual synchronization for mdfn",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" } },
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
12
+ "test": "vitest run",
13
+ "typecheck": "tsc --noEmit -p tsconfig.json",
14
+ "lint": "tsc --noEmit -p tsconfig.json",
15
+ "prepack": "npm run build"
16
+ },
17
+ "dependencies": {
18
+ "@codemirror/commands": "^6.10.4",
19
+ "@codemirror/lang-markdown": "^6.5.2",
20
+ "@codemirror/language": "^6.12.4",
21
+ "@codemirror/lint": "^6.9.7",
22
+ "@codemirror/search": "^6.7.1",
23
+ "@codemirror/state": "^6.7.1",
24
+ "@codemirror/view": "^6.43.8",
25
+ "@mdfn/core": "0.1.0",
26
+ "@mdfn/markdown": "0.1.0",
27
+ "@mdfn/render": "0.1.0"
28
+ },
29
+ "devDependencies": { "jsdom": "^26.0.0", "tsup": "^8.0.0", "typescript": "^5.3.0", "vitest": "3.2.4" },
30
+ "files": ["dist", "README.md"],
31
+ "license": "MIT",
32
+ "engines": { "node": ">=20 <25" },
33
+ "repository": { "type": "git", "url": "git+https://github.com/21nCo/super-functions.git", "directory": "mdfn/source" },
34
+ "publishConfig": { "access": "public" },
35
+ "sideEffects": false,
36
+ "mdfn": { "status": "stable-candidate", "layer": "browser", "schemaVersion": 1, "kernel": "codemirror" }
37
+ }