@openmarkdown-dev/editor 0.1.0 → 0.1.1
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 +1 -1
- package/dist/editor.js +61 -15
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -16,8 +16,8 @@ const editor = mountEditor(document.getElementById("editor")!, createEditorState
|
|
|
16
16
|
const bytes = saveMarkdownFile(editor.getDoc(), file); // unedited: the file's own bytes
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
- **Lossless round-trip:** open a Markdown file and save it without editing, and the file is byte-for-byte what it was. What an edit may rewrite is listed under [Fidelity](#fidelity), and the [fidelity page](https://openmarkdown.dev/editor/docs/fidelity/) has a check you can run yourself.
|
|
19
20
|
- **Live preview:** headings, lists, tables, math, Mermaid diagrams, callouts and YAML properties render in place; the Markdown shows under the caret.
|
|
20
|
-
- **Your file stays yours:** open and save without editing, and the file is byte-for-byte what it was.
|
|
21
21
|
- **Tables in place:** edit a cell where it is drawn.
|
|
22
22
|
- **Images:** paste or drop them in, drag a corner to resize.
|
|
23
23
|
- **Framework-free:** plain TypeScript and DOM. Works with React, Vue, Svelte or nothing. Not build-free: with no bundler, [self-host it](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/docs/guides/self-hosting.md).
|
package/dist/editor.js
CHANGED
|
@@ -1377,6 +1377,16 @@ function tableArrowNav(p, row, col, dir) {
|
|
|
1377
1377
|
function flattenCellInsert(text) {
|
|
1378
1378
|
return text.replace(/\r?\n/g, " ");
|
|
1379
1379
|
}
|
|
1380
|
+
function tableCellInsert(text, before) {
|
|
1381
|
+
let run = /\\*$/.exec(before)[0].length;
|
|
1382
|
+
let out = "";
|
|
1383
|
+
for (const ch of flattenCellInsert(text)) {
|
|
1384
|
+
if (ch === "|" && run % 2 === 0) out += "\\";
|
|
1385
|
+
out += ch;
|
|
1386
|
+
run = ch === "\\" ? run + 1 : 0;
|
|
1387
|
+
}
|
|
1388
|
+
return out;
|
|
1389
|
+
}
|
|
1380
1390
|
function tableRangeAt(state, pos) {
|
|
1381
1391
|
for (const bias of [-1, 1]) {
|
|
1382
1392
|
let node = treeUpTo(state, pos).resolveInner(pos, bias);
|
|
@@ -3930,18 +3940,7 @@ function buildPartEditor(outer, text, hooks, opts = {}) {
|
|
|
3930
3940
|
}
|
|
3931
3941
|
})
|
|
3932
3942
|
),
|
|
3933
|
-
opts.
|
|
3934
|
-
let broken = false;
|
|
3935
|
-
tr.changes.iterChanges((_a, _b, _c, _d, inserted) => {
|
|
3936
|
-
if (/\r?\n/.test(inserted.toString())) broken = true;
|
|
3937
|
-
});
|
|
3938
|
-
if (!broken) return tr;
|
|
3939
|
-
const changes = [];
|
|
3940
|
-
tr.changes.iterChanges((fromA, toA, _b, _c, inserted) => {
|
|
3941
|
-
changes.push({ from: fromA, to: toA, insert: flattenCellInsert(inserted.toString()) });
|
|
3942
|
-
});
|
|
3943
|
-
return { changes, selection: tr.selection, userEvent: tr.annotation(Transaction.userEvent) };
|
|
3944
|
-
}) : [],
|
|
3943
|
+
opts.accept ? acceptFilter(opts.accept) : [],
|
|
3945
3944
|
EditorView.updateListener.of((u) => {
|
|
3946
3945
|
if (!u.docChanged) return;
|
|
3947
3946
|
if (u.transactions.some((t) => t.annotation(reseed))) return;
|
|
@@ -3966,7 +3965,7 @@ function buildPartEditor(outer, text, hooks, opts = {}) {
|
|
|
3966
3965
|
})
|
|
3967
3966
|
});
|
|
3968
3967
|
parts.set(host, view);
|
|
3969
|
-
|
|
3968
|
+
queueMicrotask(() => {
|
|
3970
3969
|
if (!host.isConnected) return;
|
|
3971
3970
|
const selection = entrySelection(opts.caret, view.state.doc.length, pointerPlaced);
|
|
3972
3971
|
if (!selection) return;
|
|
@@ -3993,6 +3992,29 @@ function reseedPart(view, text) {
|
|
|
3993
3992
|
annotations: reseed.of(true)
|
|
3994
3993
|
});
|
|
3995
3994
|
}
|
|
3995
|
+
function acceptFilter(accept) {
|
|
3996
|
+
return EditorState.transactionFilter.of((tr) => {
|
|
3997
|
+
if (!tr.docChanged) return tr;
|
|
3998
|
+
const changes = [];
|
|
3999
|
+
let rewritten = false;
|
|
4000
|
+
tr.changes.iterChanges((fromA, toA, _b, _c, inserted) => {
|
|
4001
|
+
const text = inserted.toString();
|
|
4002
|
+
const ok = accept(text, tr.startState.sliceDoc(0, fromA));
|
|
4003
|
+
if (ok !== text) rewritten = true;
|
|
4004
|
+
changes.push({ from: fromA, to: toA, insert: ok });
|
|
4005
|
+
});
|
|
4006
|
+
if (!rewritten) return tr;
|
|
4007
|
+
const next = tr.startState.changes(changes);
|
|
4008
|
+
const back = tr.changes.invert(tr.startState.doc);
|
|
4009
|
+
const selection = tr.selection ? EditorSelection.create(
|
|
4010
|
+
tr.selection.ranges.map(
|
|
4011
|
+
(r) => EditorSelection.range(next.mapPos(back.mapPos(r.anchor, -1), 1), next.mapPos(back.mapPos(r.head, -1), 1))
|
|
4012
|
+
),
|
|
4013
|
+
tr.selection.mainIndex
|
|
4014
|
+
) : void 0;
|
|
4015
|
+
return { changes: next, selection, userEvent: tr.annotation(Transaction.userEvent) };
|
|
4016
|
+
});
|
|
4017
|
+
}
|
|
3996
4018
|
function headText(doc) {
|
|
3997
4019
|
const n = Math.min(doc.lines, 100);
|
|
3998
4020
|
return doc.sliceString(0, doc.line(n).to);
|
|
@@ -4198,7 +4220,7 @@ class PropertiesWidget extends WidgetType {
|
|
|
4198
4220
|
},
|
|
4199
4221
|
onKey: (e) => this.onKey(e, view, index, part)
|
|
4200
4222
|
},
|
|
4201
|
-
{
|
|
4223
|
+
{ accept: flattenCellInsert, caret: view.state.field(activePropField, false)?.caret }
|
|
4202
4224
|
);
|
|
4203
4225
|
return host;
|
|
4204
4226
|
}
|
|
@@ -4408,6 +4430,29 @@ const undoLanding = ViewPlugin.define((view) => ({
|
|
|
4408
4430
|
if (land) queueMicrotask(() => settleLanding(view, u.state, land));
|
|
4409
4431
|
}
|
|
4410
4432
|
}));
|
|
4433
|
+
function strandedIn(state) {
|
|
4434
|
+
const hooks = state.facet(blockCaret);
|
|
4435
|
+
if (!hooks) return null;
|
|
4436
|
+
const sel = state.selection.main;
|
|
4437
|
+
if (!sel.empty) return null;
|
|
4438
|
+
const pos = sel.head;
|
|
4439
|
+
const block = hooks.editable(state).find((b) => b.from < pos && pos < b.to);
|
|
4440
|
+
if (!block || hooks.leave(state).length > 0) return null;
|
|
4441
|
+
return { block, pos };
|
|
4442
|
+
}
|
|
4443
|
+
const strandedCaret = ViewPlugin.define((view) => ({
|
|
4444
|
+
update(u) {
|
|
4445
|
+
if (!u.docChanged && !u.selectionSet) return;
|
|
4446
|
+
if (u.transactions.some((tr) => tr.isUserEvent("undo") || tr.isUserEvent("redo"))) return;
|
|
4447
|
+
if (!strandedIn(u.state)) return;
|
|
4448
|
+
queueMicrotask(() => {
|
|
4449
|
+
if (!view.dom.isConnected || view.state !== u.state || view.composing) return;
|
|
4450
|
+
const hit = strandedIn(view.state);
|
|
4451
|
+
const hooks = view.state.facet(blockCaret);
|
|
4452
|
+
if (hit && hooks) hooks.enterAt(view, hit.block, hit.pos);
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
}));
|
|
4411
4456
|
function navigateTo(view, pos) {
|
|
4412
4457
|
const line = view.state.doc.lineAt(Math.max(0, Math.min(pos, view.state.doc.length)));
|
|
4413
4458
|
view.dispatch({
|
|
@@ -4876,7 +4921,7 @@ class TableWidget extends WidgetType {
|
|
|
4876
4921
|
},
|
|
4877
4922
|
onKey: (e, part) => this.onCellKey(e, view, part, row, col)
|
|
4878
4923
|
},
|
|
4879
|
-
{
|
|
4924
|
+
{ accept: tableCellInsert, caret }
|
|
4880
4925
|
);
|
|
4881
4926
|
if (align && align !== "none") host.style.textAlign = align;
|
|
4882
4927
|
return host;
|
|
@@ -7295,6 +7340,7 @@ function buildEditorState(options, loadMermaid2) {
|
|
|
7295
7340
|
// Undo / redo land at the change, not at history's old caret (spec:
|
|
7296
7341
|
// caret-rules R2 / R2a / R2b) — one filter for every undo entry point.
|
|
7297
7342
|
undoLanding,
|
|
7343
|
+
strandedCaret,
|
|
7298
7344
|
drawSelection(),
|
|
7299
7345
|
// Drop caret: draws a cursor at the drop position while dragging over the
|
|
7300
7346
|
// editor, so a sidebar file drag (sidebar-drag-link) shows exactly where
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmarkdown-dev/editor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A live-preview Markdown editor on CodeMirror 6 that edits the Markdown source itself, for a lossless round-trip: open and save without editing, and the file is byte-for-byte what it was.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"markdown-editor",
|
|
34
34
|
"editor",
|
|
35
35
|
"codemirror",
|
|
36
|
+
"codemirror-6",
|
|
36
37
|
"live-preview",
|
|
37
38
|
"wysiwyg",
|
|
38
39
|
"embeddable",
|