@gjermundgaraba/pixel-paper 0.1.1 → 0.2.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.
@@ -0,0 +1,438 @@
1
+ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
+ import { safeHref, safeImageSource } from "../link-policy.js";
3
+ import { liveMarkdownTheme } from "./theme.js";
4
+ import { syntaxTree } from "@codemirror/language";
5
+ import { Prec, RangeSetBuilder, StateEffect, StateField } from "@codemirror/state";
6
+ import { Decoration, EditorView, ViewPlugin, WidgetType, keymap } from "@codemirror/view";
7
+ // src/markdown-editor/live/live.ts
8
+ const UNKNOWN_HEIGHT = -1;
9
+ const TABLE_ROW_HEIGHT = 32;
10
+ const IMAGE_ESTIMATED_HEIGHT = 240;
11
+ const TABLE_HINT = "Click to edit the table";
12
+ var DrawnWidget = class extends WidgetType {
13
+ drawing;
14
+ constructor(drawing) {
15
+ super();
16
+ this.drawing = drawing;
17
+ }
18
+ eq(other) {
19
+ return __foldkitBrandViewResult((other.drawing.key === this.drawing.key), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#eq");
20
+ }
21
+ toDOM(view) {
22
+ return __foldkitBrandViewResult((this.drawing.draw(view)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#toDOM");
23
+ }
24
+ get estimatedHeight() {
25
+ return __foldkitBrandViewResult((this.drawing.estimatedHeight), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#estimatedHeight");
26
+ }
27
+ ignoreEvent() {
28
+ return __foldkitBrandViewResult((this.drawing.takesOwnEvents), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#ignoreEvent");
29
+ }
30
+ };
31
+ const spanOf = (className, text) => {
32
+ const span = document.createElement("span");
33
+ span.className = className;
34
+ span.textContent = text;
35
+ return __foldkitBrandViewResult((span), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#spanOf");
36
+ };
37
+ const checkboxWidget = (isChecked) => __foldkitBrandViewResult((new DrawnWidget({
38
+ key: `checkbox:${isChecked}`,
39
+ draw: () => {
40
+ const box = document.createElement("input");
41
+ box.type = "checkbox";
42
+ box.checked = isChecked;
43
+ box.className = "lmd-check";
44
+ box.tabIndex = -1;
45
+ return __foldkitBrandViewResult((box), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#draw");
46
+ },
47
+ estimatedHeight: UNKNOWN_HEIGHT,
48
+ takesOwnEvents: false
49
+ })), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#checkboxWidget");
50
+ const bulletWidget = new DrawnWidget({
51
+ key: "bullet",
52
+ draw: () => __foldkitBrandViewResult((spanOf("lmd-bullet", "•")), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#draw~2"),
53
+ estimatedHeight: UNKNOWN_HEIGHT,
54
+ takesOwnEvents: true
55
+ });
56
+ const imageWidget = (source, alt) => __foldkitBrandViewResult((new DrawnWidget({
57
+ key: `image:${source}\n${alt}`,
58
+ draw: () => {
59
+ const box = spanOf("lmd-img-box", "");
60
+ const safeSource = safeImageSource(source, document.baseURI);
61
+ if (safeSource === void 0) {
62
+ box.append(spanOf("lmd-img-note", alt === "" ? "Image not shown" : alt));
63
+ return __foldkitBrandViewResult((box), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#draw~3");
64
+ }
65
+ const image = document.createElement("img");
66
+ image.className = "lmd-img";
67
+ image.alt = alt;
68
+ image.referrerPolicy = "no-referrer";
69
+ image.addEventListener("error", () => {
70
+ image.replaceWith(spanOf("lmd-img-note", alt === "" ? "Image not found" : `Image not found: ${alt}`));
71
+ }, { once: true });
72
+ image.src = safeSource;
73
+ box.append(image);
74
+ return __foldkitBrandViewResult((box), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#draw~3");
75
+ },
76
+ estimatedHeight: IMAGE_ESTIMATED_HEIGHT,
77
+ takesOwnEvents: false
78
+ })), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#imageWidget");
79
+ const isList = (node) => __foldkitBrandViewResult((node?.name === "BulletList" || node?.name === "OrderedList"), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#isList");
80
+ const hide = Decoration.replace({});
81
+ const mark = (className) => __foldkitBrandViewResult((Decoration.mark({ class: className })), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#mark");
82
+ const line = (className) => __foldkitBrandViewResult((Decoration.line({ class: className })), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#line");
83
+ const INLINE = /* @__PURE__ */ new Map([
84
+ ["Emphasis", "lmd-em"],
85
+ ["StrongEmphasis", "lmd-strong"],
86
+ ["Strikethrough", "lmd-strike"],
87
+ ["InlineCode", "lmd-code"],
88
+ ["Link", "lmd-link"]
89
+ ]);
90
+ const HIDDEN_MARKS = /* @__PURE__ */ new Set([
91
+ "EmphasisMark",
92
+ "StrikethroughMark",
93
+ "LinkMark",
94
+ "URL"
95
+ ]);
96
+ const ATX_HEADING = /^ATXHeading(?<level>\d)$/u;
97
+ const ANGLE_BRACKETED = /^<(?<destination>.*)>$/su;
98
+ const EMAIL = /^[^\s:@]+@[^\s:@]+$/u;
99
+ const URL_OWNERS = /* @__PURE__ */ new Set([
100
+ "Link",
101
+ "Image",
102
+ "LinkReference"
103
+ ]);
104
+ const isPlainBrackets = (node) => __foldkitBrandViewResult(((node.name === "Link" || node.name === "Image") && node.getChild("URL") === null), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#isPlainBrackets");
105
+ const isBareUrl = (node) => __foldkitBrandViewResult((node.name === "URL" && !URL_OWNERS.has(node.parent?.name ?? "")), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#isBareUrl");
106
+ /** The live layer's decorations over the visible ranges, as a ViewPlugin may provide them. */
107
+ const liveDecorations = (state, visibleRanges, isFocused) => {
108
+ const { ranges } = state.selection;
109
+ const touched = (from, to) => __foldkitBrandViewResult((isFocused && ranges.some((range) => __foldkitBrandViewResult((range.from <= to && range.to >= from), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~2"))), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#touched");
110
+ const activeLine = (position) => {
111
+ const { from, to } = state.doc.lineAt(position);
112
+ return __foldkitBrandViewResult((touched(from, to)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#activeLine");
113
+ };
114
+ const placed = [];
115
+ const add = (from, to, decoration) => {
116
+ placed.push({
117
+ from,
118
+ to,
119
+ decoration,
120
+ isReplace: decoration === hide
121
+ });
122
+ };
123
+ const replace = (from, to, widget) => {
124
+ placed.push({
125
+ from,
126
+ to,
127
+ decoration: Decoration.replace({ widget }),
128
+ isReplace: true
129
+ });
130
+ };
131
+ const addLine = (position, decoration) => {
132
+ placed.push({
133
+ from: position,
134
+ to: position,
135
+ decoration,
136
+ isReplace: false
137
+ });
138
+ };
139
+ const addLines = (from, to, className) => {
140
+ for (let position = from; position <= to;) {
141
+ const { from: start, to: end } = state.doc.lineAt(position);
142
+ addLine(start, line(className));
143
+ position = end + 1;
144
+ }
145
+ };
146
+ const hideWithFollowingSpace = (from, to) => {
147
+ add(from, state.sliceDoc(to, to + 1) === " " ? to + 1 : to, hide);
148
+ };
149
+ for (const { from, to } of visibleRanges) syntaxTree(state).iterate({
150
+ from,
151
+ to,
152
+ enter: (node) => {
153
+ const { name } = node;
154
+ const { parent } = node.node;
155
+ const level = ATX_HEADING.exec(name)?.groups?.["level"];
156
+ if (level !== void 0) {
157
+ addLine(state.doc.lineAt(node.from).from, line(`lmd-h lmd-h${level}`));
158
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
159
+ }
160
+ if (name === "HeaderMark" && parent?.name.startsWith("ATXHeading") === true) {
161
+ if (!activeLine(node.from)) hideWithFollowingSpace(node.from, node.to);
162
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
163
+ }
164
+ if (name === "LinkReference") return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
165
+ if (isPlainBrackets(node.node)) return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
166
+ if (name === "LinkMark" && parent !== null && isPlainBrackets(parent)) return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
167
+ if (name === "Escape") {
168
+ if (!touched(node.from, node.to)) add(node.from, node.from + 1, hide);
169
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
170
+ }
171
+ if (name === "Image") {
172
+ const url = node.node.getChild("URL");
173
+ if (url !== null && !touched(node.from, node.to)) {
174
+ const alt = state.sliceDoc(node.from + 2, Math.max(node.from + 2, url.from - 2));
175
+ replace(node.from, node.to, imageWidget(state.sliceDoc(url.from, url.to).replace(ANGLE_BRACKETED, "$<destination>"), alt));
176
+ }
177
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
178
+ }
179
+ const inlineClass = INLINE.get(name);
180
+ if (inlineClass !== void 0) {
181
+ add(node.from, node.to, mark(inlineClass));
182
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
183
+ }
184
+ if (isBareUrl(node.node)) {
185
+ add(node.from, node.to, mark("lmd-link"));
186
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
187
+ }
188
+ if (parent !== null && (HIDDEN_MARKS.has(name) || name === "CodeMark" && parent.name === "InlineCode")) {
189
+ if (!touched(parent.from, parent.to)) add(node.from, node.to, hide);
190
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
191
+ }
192
+ if (name === "FencedCode" || name === "CodeBlock") {
193
+ addLines(node.from, node.to, "lmd-codeblock");
194
+ return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
195
+ }
196
+ if (name === "Blockquote") {
197
+ addLines(node.from, node.to, "lmd-quote");
198
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
199
+ }
200
+ if (name === "QuoteMark") {
201
+ if (!activeLine(node.from)) hideWithFollowingSpace(node.from, node.to);
202
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
203
+ }
204
+ if (name === "HorizontalRule") {
205
+ if (!activeLine(node.from)) {
206
+ addLine(state.doc.lineAt(node.from).from, line("lmd-hr"));
207
+ add(node.from, node.to, hide);
208
+ }
209
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
210
+ }
211
+ if (name === "Table") {
212
+ addLines(node.from, node.to, "lmd-table");
213
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
214
+ }
215
+ if (name === "ListMark" && parent !== null && isList(parent.parent)) {
216
+ if (activeLine(node.from)) return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
217
+ if (parent.getChild("Task") !== null) hideWithFollowingSpace(node.from, node.to);
218
+ else if (parent.parent?.name === "BulletList") replace(node.from, node.to, bulletWidget);
219
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
220
+ }
221
+ if (name === "TaskMarker" && !touched(node.from, node.to)) {
222
+ const isChecked = /x/iu.test(state.sliceDoc(node.from, node.to));
223
+ replace(node.from, node.to, checkboxWidget(isChecked));
224
+ if (isChecked && parent !== null) add(node.to, parent.to, mark("lmd-done"));
225
+ }
226
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter");
227
+ }
228
+ });
229
+ placed.sort((left, right) => __foldkitBrandViewResult((left.from - right.from || left.decoration.startSide - right.decoration.startSide), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~3"));
230
+ const staysOnItsLine = (from, to) => __foldkitBrandViewResult((to <= state.doc.lineAt(from).to), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#staysOnItsLine");
231
+ const builder = new RangeSetBuilder();
232
+ let lastReplaceEnd = -1;
233
+ for (const { from, to, decoration, isReplace } of placed) {
234
+ const isRangeReplace = from !== to && isReplace;
235
+ if (!isRangeReplace || from >= lastReplaceEnd && staysOnItsLine(from, to)) {
236
+ builder.add(from, to, decoration);
237
+ if (isRangeReplace) lastReplaceEnd = to;
238
+ }
239
+ }
240
+ return __foldkitBrandViewResult((builder.finish()), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#liveDecorations");
241
+ };
242
+ const DELIMITER_ROW = /^\s*\|?[\s:|-]+\|?\s*$/u;
243
+ const tableCells = (row) => __foldkitBrandViewResult((row.trim().replaceAll(/^\||\|$/gu, "").split(/(?<!\\)\|/u).map((cell) => __foldkitBrandViewResult((cell.trim().replaceAll(String.raw`\|`, "|")), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~4"))), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#tableCells");
244
+ const tableOf = (source) => {
245
+ const table = document.createElement("table");
246
+ const head = table.createTHead();
247
+ const body = table.createTBody();
248
+ const rows = source.split("\n").filter((row) => __foldkitBrandViewResult((row.trim() !== ""), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~5"));
249
+ table.className = "lmd-table-view";
250
+ for (const [index, row] of rows.entries()) if (index !== 1 || !DELIMITER_ROW.test(row)) {
251
+ const tableRow = document.createElement("tr");
252
+ for (const text of tableCells(row)) {
253
+ const cell = document.createElement(index === 0 ? "th" : "td");
254
+ cell.textContent = text;
255
+ tableRow.append(cell);
256
+ }
257
+ (index === 0 ? head : body).append(tableRow);
258
+ }
259
+ return __foldkitBrandViewResult((table), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#tableOf");
260
+ };
261
+ const tableWidget = (text) => __foldkitBrandViewResult((new DrawnWidget({
262
+ key: `table:${text}`,
263
+ draw: (view) => {
264
+ const dom = document.createElement("div");
265
+ dom.className = "lmd-table-widget";
266
+ dom.title = TABLE_HINT;
267
+ dom.append(tableOf(text));
268
+ dom.addEventListener("mousedown", (event) => {
269
+ event.preventDefault();
270
+ const row = event.target instanceof Element ? event.target.closest("tr") : null;
271
+ const rows = [...dom.querySelectorAll("tr")];
272
+ const rowIndex = row === null ? -1 : rows.indexOf(row);
273
+ const sourceOffset = rowIndex <= 0 ? 0 : rowIndex + 1;
274
+ const first = view.state.doc.lineAt(view.posAtDOM(dom));
275
+ const target = view.state.doc.line(Math.min(first.number + sourceOffset, view.state.doc.lines));
276
+ view.dispatch({ selection: { anchor: Math.min(target.from + 2, target.to) } });
277
+ view.focus();
278
+ });
279
+ return __foldkitBrandViewResult((dom), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#draw~4");
280
+ },
281
+ estimatedHeight: TABLE_ROW_HEIGHT * text.split("\n").length,
282
+ takesOwnEvents: true
283
+ })), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#tableWidget");
284
+ const lastValueOf = (transaction, type, held) => {
285
+ const last = transaction.effects.findLast((effect) => __foldkitBrandViewResult((effect.is(type)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~7"));
286
+ return __foldkitBrandViewResult((last === void 0 ? held : last.value), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#lastValueOf");
287
+ };
288
+ const setFocus = StateEffect.define();
289
+ const setPointerDown = StateEffect.define();
290
+ const pointerDownField = StateField.define({
291
+ create: () => __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#create"),
292
+ update: (isDown, transaction) => __foldkitBrandViewResult((lastValueOf(transaction, setPointerDown, isDown)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#update")
293
+ });
294
+ const POINTER_RELEASE_EVENTS = [
295
+ "mouseup",
296
+ "dragend",
297
+ "contextmenu"
298
+ ];
299
+ const holdRevealUntilPointerRelease = (view) => {
300
+ const { ownerDocument } = view.dom;
301
+ const release = () => {
302
+ for (const name of POINTER_RELEASE_EVENTS) ownerDocument.removeEventListener(name, release);
303
+ setTimeout(() => {
304
+ view.dispatch({ effects: setPointerDown.of(false) });
305
+ }, 0);
306
+ };
307
+ view.dispatch({ effects: setPointerDown.of(true) });
308
+ for (const name of POINTER_RELEASE_EVENTS) ownerDocument.addEventListener(name, release);
309
+ };
310
+ const buildTables = (state, isFocused) => {
311
+ const builder = new RangeSetBuilder();
312
+ syntaxTree(state).iterate({ enter: (node) => {
313
+ if (node.name !== "Table") return __foldkitBrandViewResult((node.name === "Document" || node.name.endsWith("List") || node.name === "ListItem" || node.name === "Blockquote"), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter~2");
314
+ const { from } = state.doc.lineAt(node.from);
315
+ const { to } = state.doc.lineAt(node.to);
316
+ const isInside = state.selection.ranges.some((range) => __foldkitBrandViewResult((range.from <= to && range.to >= from), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~9"));
317
+ if (from === node.from && !(isFocused && isInside)) builder.add(from, to, Decoration.replace({
318
+ widget: tableWidget(state.sliceDoc(from, to)),
319
+ block: true
320
+ }));
321
+ return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#enter~2");
322
+ } });
323
+ return __foldkitBrandViewResult((builder.finish()), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#buildTables");
324
+ };
325
+ const tablesField = StateField.define({
326
+ create: (state) => (__foldkitBrandViewResult(({
327
+ isFocused: false,
328
+ decorations: buildTables(state, false)
329
+ }), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#create~2")),
330
+ update: (value, transaction) => {
331
+ const isFocused = lastValueOf(transaction, setFocus, value.isFocused);
332
+ const wasDown = transaction.startState.field(pointerDownField);
333
+ const isDown = transaction.state.field(pointerDownField);
334
+ if (isDown && !transaction.docChanged) return __foldkitBrandViewResult(({
335
+ isFocused,
336
+ decorations: value.decorations
337
+ }), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#update~2");
338
+ return __foldkitBrandViewResult((isFocused === value.isFocused && wasDown === isDown && !transaction.docChanged && transaction.selection === void 0 && syntaxTree(transaction.startState) === syntaxTree(transaction.state) ? value : {
339
+ isFocused,
340
+ decorations: buildTables(transaction.state, isFocused)
341
+ }), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#update~2");
342
+ },
343
+ provide: (field) => __foldkitBrandViewResult((EditorView.decorations.from(field, (value) => __foldkitBrandViewResult((value.decorations), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~10"))), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#provide")
344
+ });
345
+ const linkHref = (state, node) => {
346
+ const url = node.name === "Link" ? node.getChild("URL") : node;
347
+ if (url === null) return;
348
+ const href = state.sliceDoc(url.from, url.to).replace(ANGLE_BRACKETED, "$<destination>");
349
+ if (/^www\./iu.test(href)) return __foldkitBrandViewResult((`https://${href}`), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#linkHref");
350
+ return __foldkitBrandViewResult((node.name === "URL" && EMAIL.test(href) ? `mailto:${href}` : href), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#linkHref");
351
+ };
352
+ const isLinkNode = (node) => __foldkitBrandViewResult((node.name === "Link" && !isPlainBrackets(node) || isBareUrl(node)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#isLinkNode");
353
+ const toggleCheckbox = (view, box) => {
354
+ const position = view.posAtDOM(box);
355
+ const text = view.state.sliceDoc(position, position + 3);
356
+ if (!/^\[[ xX]\]$/u.test(text)) return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#toggleCheckbox");
357
+ view.dispatch({ changes: {
358
+ from: position + 1,
359
+ to: position + 2,
360
+ insert: text.charAt(1) === " " ? "x" : " "
361
+ } });
362
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#toggleCheckbox");
363
+ };
364
+ const linkAt = (state, position, side) => {
365
+ for (let node = syntaxTree(state).resolveInner(position, side); node !== null; node = node.parent) if (isLinkNode(node)) return __foldkitBrandViewResult((linkHref(state, node)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#linkAt");
366
+ };
367
+ const openLink = (url) => {
368
+ const href = safeHref(url, document.baseURI);
369
+ if (href !== void 0) window.open(href, "_blank", "noopener,noreferrer");
370
+ };
371
+ const openLinkAtCaret = (view) => {
372
+ const { head } = view.state.selection.main;
373
+ const url = linkAt(view.state, head, 1) ?? linkAt(view.state, head, -1);
374
+ if (url === void 0) return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#openLinkAtCaret");
375
+ openLink(url);
376
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#openLinkAtCaret");
377
+ };
378
+ /** Markdown drawn over its own text, as in Obsidian's Live Preview: the document stays
379
+ * Markdown, and its marks show only around the selection. Redrawing a reveal moves text, so
380
+ * nothing redraws under an IME's composition or a pressed mouse button. */
381
+ const liveMarkdown = () => {
382
+ const preview = ViewPlugin.fromClass(class {
383
+ decorations;
384
+ isStale = false;
385
+ constructor(view) {
386
+ this.decorations = liveDecorations(view.state, view.visibleRanges, view.hasFocus);
387
+ }
388
+ update(update) {
389
+ if (update.view.composing || update.state.field(pointerDownField) && !update.docChanged) {
390
+ this.decorations = this.decorations.map(update.changes);
391
+ this.isStale = true;
392
+ } else if (this.isStale || update.docChanged || update.selectionSet || update.focusChanged || update.viewportChanged || syntaxTree(update.startState) !== syntaxTree(update.state)) {
393
+ this.decorations = liveDecorations(update.state, update.view.visibleRanges, update.view.hasFocus);
394
+ this.isStale = false;
395
+ }
396
+ }
397
+ }, {
398
+ decorations: (plugin) => __foldkitBrandViewResult((plugin.decorations), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#decorations"),
399
+ eventObservers: { compositionend: (_, view) => {
400
+ setTimeout(() => {
401
+ view.dispatch({});
402
+ }, 0);
403
+ } },
404
+ eventHandlers: { mousedown: (event, view) => {
405
+ const box = event.target instanceof HTMLInputElement && event.target.classList.contains("lmd-check") ? event.target : void 0;
406
+ if (box !== void 0 && (event.button !== 0 || toggleCheckbox(view, box))) {
407
+ event.preventDefault();
408
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#mousedown");
409
+ }
410
+ if (event.metaKey || event.ctrlKey) {
411
+ const position = view.posAtCoords({
412
+ x: event.clientX,
413
+ y: event.clientY
414
+ });
415
+ const url = position === null ? void 0 : linkAt(view.state, position, 1);
416
+ if (url !== void 0) {
417
+ openLink(url);
418
+ event.preventDefault();
419
+ return __foldkitBrandViewResult((true), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#mousedown");
420
+ }
421
+ }
422
+ if (event.button === 0) holdRevealUntilPointerRelease(view);
423
+ return __foldkitBrandViewResult((false), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#mousedown");
424
+ } }
425
+ });
426
+ return __foldkitBrandViewResult(([
427
+ pointerDownField,
428
+ preview,
429
+ tablesField,
430
+ Prec.high(keymap.of([{
431
+ key: "Mod-Enter",
432
+ run: openLinkAtCaret
433
+ }])),
434
+ EditorView.focusChangeEffect.of((_, isFocusing) => __foldkitBrandViewResult((setFocus.of(isFocusing)), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#anonymous~12")),
435
+ liveMarkdownTheme
436
+ ]), "@gjermundgaraba/pixel-paper/markdown-editor/live/live.js#liveMarkdown");
437
+ };
438
+ export { liveDecorations, liveMarkdown };
@@ -0,0 +1,117 @@
1
+ import { EditorView } from "@codemirror/view";
2
+ // src/markdown-editor/live/theme.ts
3
+ const PIXEL_LABEL = {
4
+ fontFamily: "var(--font-px)",
5
+ fontWeight: "400",
6
+ textTransform: "uppercase",
7
+ letterSpacing: "var(--tracking-label)"
8
+ };
9
+ const CODE = {
10
+ fontFamily: "var(--font-code)",
11
+ fontSize: "var(--type-control)"
12
+ };
13
+ /** The live layer's look, in `tokens.css` values alone, so paper and plum both hold. */
14
+ const liveMarkdownTheme = EditorView.baseTheme({
15
+ "&": {
16
+ color: "var(--ink)",
17
+ background: "var(--paper)"
18
+ },
19
+ "&.cm-focused": { outline: "none" },
20
+ ".cm-scroller": {
21
+ fontFamily: "var(--font-body)",
22
+ fontSize: "var(--type-body)",
23
+ lineHeight: "1.5",
24
+ overflow: "auto"
25
+ },
26
+ ".cm-content": {
27
+ padding: "2px 0",
28
+ caretColor: "var(--ink)"
29
+ },
30
+ ".cm-content ::selection": {
31
+ background: "var(--ink)",
32
+ color: "var(--paper)"
33
+ },
34
+ ".cm-cursor": { borderLeftColor: "var(--ink)" },
35
+ ".cm-line": { padding: "0" },
36
+ ".lmd-h": {
37
+ fontWeight: "var(--read-weight)",
38
+ lineHeight: "1.3"
39
+ },
40
+ ".lmd-h1, .lmd-h2": { fontSize: "var(--read-h2)" },
41
+ ".lmd-h3": { fontSize: "var(--read-h3)" },
42
+ ".lmd-h4": {
43
+ ...PIXEL_LABEL,
44
+ fontSize: "var(--type-control)"
45
+ },
46
+ ".lmd-h5": { fontSize: "var(--type-body)" },
47
+ ".lmd-h6": {
48
+ ...PIXEL_LABEL,
49
+ fontSize: "var(--type-label)",
50
+ color: "var(--muted)"
51
+ },
52
+ ".lmd-strong": { fontWeight: "var(--read-weight)" },
53
+ ".lmd-em": { fontStyle: "italic" },
54
+ ".lmd-strike, .lmd-done": {
55
+ textDecoration: "line-through",
56
+ color: "var(--muted)"
57
+ },
58
+ ".lmd-code": {
59
+ ...CODE,
60
+ background: "var(--head)",
61
+ padding: "1px 5px"
62
+ },
63
+ ".lmd-link": {
64
+ color: "var(--ink)",
65
+ textDecoration: "underline",
66
+ textDecorationThickness: "var(--hair)",
67
+ textUnderlineOffset: "3px"
68
+ },
69
+ ".lmd-codeblock": {
70
+ ...CODE,
71
+ background: "var(--head)",
72
+ padding: "0 var(--space-s) !important"
73
+ },
74
+ ".lmd-table": CODE,
75
+ ".lmd-quote": {
76
+ borderLeft: "var(--hair) solid var(--ink)",
77
+ paddingLeft: "var(--space-m) !important",
78
+ color: "var(--muted)"
79
+ },
80
+ ".lmd-hr": {
81
+ height: "0.8em",
82
+ background: "linear-gradient(var(--rule), var(--rule)) center / 100% var(--hair) no-repeat"
83
+ },
84
+ ".lmd-bullet": {
85
+ color: "var(--muted)",
86
+ display: "inline-block",
87
+ minWidth: "0.7em"
88
+ },
89
+ ".lmd-check": {
90
+ width: "auto",
91
+ margin: "0 var(--space-s) 0 0",
92
+ verticalAlign: "-2px",
93
+ cursor: "pointer",
94
+ accentColor: "var(--ink)"
95
+ },
96
+ ".lmd-img": {
97
+ display: "block",
98
+ maxWidth: "100%",
99
+ maxHeight: "480px",
100
+ margin: "var(--space-s) 0"
101
+ },
102
+ ".lmd-img-note": {
103
+ color: "var(--muted)",
104
+ fontStyle: "italic"
105
+ },
106
+ ".lmd-table-widget": {
107
+ cursor: "text",
108
+ padding: "2px 0"
109
+ },
110
+ ".lmd-table-view": { borderCollapse: "collapse" },
111
+ ".lmd-table-view th, .lmd-table-view td": {
112
+ border: "var(--hair) solid var(--rule)",
113
+ padding: "3px var(--space-s)",
114
+ textAlign: "left"
115
+ }
116
+ });
117
+ export { liveMarkdownTheme };
@@ -0,0 +1,11 @@
1
+ import { ViewConfig as ViewConfig$1 } from "../textarea-field/textarea-field.js";
2
+ import { Schema } from "effect";
3
+ import { Html, HtmlBuilder } from "foldkit/html";
4
+ /** Configuration for rendering a Markdown editor with `view`. */
5
+ export type ViewConfig<Message> = Pick<ViewConfig$1<Message>, "id" | "label" | "value" | "onInput" | "help" | "error" | "rows">;
6
+ /** A labelled Markdown field, edited in place: its marks show only around the caret, and the
7
+ * text it hands back is the Markdown string as typed, except that the first edit turns CRLF
8
+ * line endings into LF. Without `defineMarkdownEditorElement` it is a plain `TextareaField`,
9
+ * which is also what tests see, and so is a field whose editor fails to mount. With it, the
10
+ * element takes the textarea's place and drives `onInput` itself. */
11
+ export declare const view: <Message>(config: ViewConfig<Message>, h: HtmlBuilder<Message>) => Html;
@@ -0,0 +1,22 @@
1
+ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
+ import { view as view$1 } from "../textarea-field/textarea-field.js";
3
+ import { Schema } from "effect";
4
+ import { define } from "foldkit/customElement";
5
+ // src/markdown-editor/markdown-editor.ts
6
+ /** The binding to `<pp-markdown-editor>`. The property is `source`, not `value`, because
7
+ * Foldkit's props module skips a `value` write when the element's `value` already matches. */
8
+ const spec = define({
9
+ tag: "pp-markdown-editor",
10
+ properties: { source: Schema.String },
11
+ events: { "value-change": Schema.Struct({ value: Schema.String }) }
12
+ });
13
+ /** A labelled Markdown field, edited in place: its marks show only around the caret, and the
14
+ * text it hands back is the Markdown string as typed, except that the first edit turns CRLF
15
+ * line endings into LF. Without `defineMarkdownEditorElement` it is a plain `TextareaField`,
16
+ * which is also what tests see, and so is a field whose editor fails to mount. With it, the
17
+ * element takes the textarea's place and drives `onInput` itself. */
18
+ const view = (config, h) => {
19
+ const host = spec.withMessage(h);
20
+ return __foldkitBrandViewResult((host([host.Source(config.value), host.OnValueChange(({ value }) => __foldkitBrandViewResult((config.onInput(value)), "@gjermundgaraba/pixel-paper/markdown-editor/markdown-editor.js#anonymous"))], [view$1(config, h)])), "@gjermundgaraba/pixel-paper/markdown-editor/markdown-editor.js#view");
21
+ };
22
+ export { spec, view };
@@ -0,0 +1,24 @@
1
+ import { brandViewResult as __foldkitBrandViewResult } from 'foldkit/brand'
2
+ // src/markdown-editor/reconcile.ts
3
+ /** CodeMirror's document keeps LF, so a stored CRLF text compares equal to it only after this. */
4
+ const withLfLineEndings = (text) => __foldkitBrandViewResult((text.replaceAll(/\r\n?/gu, "\n")), "@gjermundgaraba/pixel-paper/markdown-editor/reconcile.js#withLfLineEndings");
5
+ /** Remembers a document sent up, so its echo cannot be taken for an outside change. */
6
+ const afterEmission = (pending, doc) => __foldkitBrandViewResult(([...pending, doc]), "@gjermundgaraba/pixel-paper/markdown-editor/reconcile.js#afterEmission");
7
+ /** Sorts a `source` write into an echo of this editor's own typing, which must not rewind a
8
+ * document that has moved on, or an outside change. A render may skip echoes, so a match
9
+ * also retires everything older. Text loss here would be silent, with no history behind it. */
10
+ const afterSource = (pending, source, doc) => {
11
+ const echoIndex = pending.indexOf(source);
12
+ if (echoIndex !== -1) return __foldkitBrandViewResult(({
13
+ pending: pending.slice(echoIndex + 1),
14
+ isExternal: false
15
+ }), "@gjermundgaraba/pixel-paper/markdown-editor/reconcile.js#afterSource");
16
+ return __foldkitBrandViewResult((withLfLineEndings(source) === doc ? {
17
+ pending,
18
+ isExternal: false
19
+ } : {
20
+ pending: [],
21
+ isExternal: true
22
+ }), "@gjermundgaraba/pixel-paper/markdown-editor/reconcile.js#afterSource");
23
+ };
24
+ export { afterEmission, afterSource, withLfLineEndings };
@@ -68,13 +68,13 @@ export declare const update: (model: Model, message: Message) => Readonly<{
68
68
  readonly edge: "First" | "Last";
69
69
  };
70
70
  commands?: Update.Commands<{
71
+ readonly _tag: "CompletedFocus";
72
+ } | {
71
73
  readonly _tag: "ClickedToggle";
72
74
  } | {
73
75
  readonly _tag: "PressedEscape";
74
76
  } | {
75
77
  readonly _tag: "CompletedFocusEdge";
76
- } | {
77
- readonly _tag: "CompletedFocus";
78
78
  } | {
79
79
  readonly _tag: "CompletedPlaceSlip";
80
80
  } | {
@@ -163,13 +163,13 @@ export declare const view: import("foldkit/submodel").View<{
163
163
  readonly isOpen: boolean;
164
164
  readonly edge: "First" | "Last";
165
165
  }, {
166
+ readonly _tag: "CompletedFocus";
167
+ } | {
166
168
  readonly _tag: "ClickedToggle";
167
169
  } | {
168
170
  readonly _tag: "PressedEscape";
169
171
  } | {
170
172
  readonly _tag: "CompletedFocusEdge";
171
- } | {
172
- readonly _tag: "CompletedFocus";
173
173
  } | {
174
174
  readonly _tag: "CompletedPlaceSlip";
175
175
  } | {