@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 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.singleLine ? EditorState.transactionFilter.of((tr) => {
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
- setTimeout(() => {
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
- { singleLine: true, caret: view.state.field(activePropField, false)?.caret }
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
- { singleLine: true, caret }
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.0",
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",