@realiizlabs/admin 0.8.0 → 0.8.2
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/auth-ui/index.cjs +56 -0
- package/dist/auth-ui/index.cjs.map +1 -1
- package/dist/auth-ui/index.js +1 -1
- package/dist/chunk-CE2DRQBD.js +98 -0
- package/dist/chunk-CE2DRQBD.js.map +1 -0
- package/dist/{chunk-FGKT5FD4.js → chunk-J6FI73PF.js} +31 -4
- package/dist/chunk-J6FI73PF.js.map +1 -0
- package/dist/forms/index.cjs +29 -2
- package/dist/forms/index.cjs.map +1 -1
- package/dist/forms/index.d.cts +2 -2
- package/dist/forms/index.d.ts +2 -2
- package/dist/forms/index.js +1 -1
- package/dist/forms-ui/index.cjs +770 -39
- package/dist/forms-ui/index.cjs.map +1 -1
- package/dist/forms-ui/index.d.cts +123 -12
- package/dist/forms-ui/index.d.ts +123 -12
- package/dist/forms-ui/index.js +691 -41
- package/dist/forms-ui/index.js.map +1 -1
- package/dist/git/index.cjs +32 -4
- package/dist/git/index.cjs.map +1 -1
- package/dist/git/index.d.cts +25 -2
- package/dist/git/index.d.ts +25 -2
- package/dist/git/index.js +31 -5
- package/dist/git/index.js.map +1 -1
- package/dist/shell/index.cjs +248 -77
- package/dist/shell/index.cjs.map +1 -1
- package/dist/shell/index.d.cts +29 -5
- package/dist/shell/index.d.ts +29 -5
- package/dist/shell/index.js +247 -79
- package/dist/shell/index.js.map +1 -1
- package/dist/{types-DsSMscTh.d.cts → types-DyryuIX1.d.cts} +24 -1
- package/dist/{types-DsSMscTh.d.ts → types-DyryuIX1.d.ts} +24 -1
- package/package.json +1 -1
- package/dist/chunk-4OCBMOEP.js +0 -42
- package/dist/chunk-4OCBMOEP.js.map +0 -1
- package/dist/chunk-FGKT5FD4.js.map +0 -1
package/dist/forms-ui/index.cjs
CHANGED
|
@@ -143,12 +143,26 @@ function readMeta(schema) {
|
|
|
143
143
|
const s = schema;
|
|
144
144
|
const raw = typeof s.meta === "function" ? s.meta() ?? {} : {};
|
|
145
145
|
const pick = (k) => typeof raw[k] === "string" ? raw[k] : void 0;
|
|
146
|
+
const rec = raw.recommended;
|
|
147
|
+
const recommended = Array.isArray(rec) && rec.length === 2 && rec.every((n) => typeof n === "number") ? [rec[0], rec[1]] : void 0;
|
|
148
|
+
const INPUTS = ["text", "textarea", "date", "number", "boolean", "select", "tags", "image", "url"];
|
|
149
|
+
const input = typeof raw.input === "string" && INPUTS.includes(raw.input) ? raw.input : void 0;
|
|
146
150
|
return {
|
|
147
151
|
label: pick("label"),
|
|
148
152
|
placeholder: pick("placeholder"),
|
|
149
|
-
description: pick("description") ?? (typeof s.description === "string" ? s.description : void 0)
|
|
153
|
+
description: pick("description") ?? (typeof s.description === "string" ? s.description : void 0),
|
|
154
|
+
recommended,
|
|
155
|
+
suffix: pick("suffix"),
|
|
156
|
+
help: pick("help"),
|
|
157
|
+
input
|
|
150
158
|
};
|
|
151
159
|
}
|
|
160
|
+
function inputOverrideFor(name, schema, entry) {
|
|
161
|
+
return entry?.inputs?.[name] ?? readMeta(schema).input;
|
|
162
|
+
}
|
|
163
|
+
function recommendedFor(name, schema, entry) {
|
|
164
|
+
return entry?.recommended?.[name] ?? readMeta(schema).recommended;
|
|
165
|
+
}
|
|
152
166
|
function titlecase(name) {
|
|
153
167
|
const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
154
168
|
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
@@ -162,6 +176,9 @@ function labelFor(name, schema, entry) {
|
|
|
162
176
|
function placeholderFor(name, schema, entry) {
|
|
163
177
|
return entry?.placeholders?.[name] ?? readMeta(schema).placeholder;
|
|
164
178
|
}
|
|
179
|
+
function helpFor(name, schema, entry) {
|
|
180
|
+
return entry?.help?.[name] ?? readMeta(schema).help;
|
|
181
|
+
}
|
|
165
182
|
|
|
166
183
|
// src/forms/walk.ts
|
|
167
184
|
function walkSchema(schema, entry = {}) {
|
|
@@ -176,11 +193,14 @@ function walkSchema(schema, entry = {}) {
|
|
|
176
193
|
return {
|
|
177
194
|
name,
|
|
178
195
|
label: labelFor(name, raw, entry),
|
|
179
|
-
input: role === "image" ? "image" : info.input,
|
|
196
|
+
input: role === "image" ? "image" : inputOverrideFor(name, raw, entry) ?? info.input,
|
|
180
197
|
required,
|
|
181
198
|
constraints: info.constraints,
|
|
182
199
|
messages: info.messages,
|
|
183
200
|
placeholder: placeholderFor(name, raw, entry),
|
|
201
|
+
recommended: recommendedFor(name, raw, entry),
|
|
202
|
+
suffix: readMeta(raw).suffix,
|
|
203
|
+
help: helpFor(name, raw, entry),
|
|
184
204
|
order: 0,
|
|
185
205
|
role,
|
|
186
206
|
options: entry.options?.[name] ?? info.options
|
|
@@ -212,6 +232,50 @@ function applyOrder(specs, order) {
|
|
|
212
232
|
const rest = specs.filter((s) => !rank.has(s.name));
|
|
213
233
|
return [...named, ...rest].map((s, i) => ({ ...s, order: i }));
|
|
214
234
|
}
|
|
235
|
+
function ChipsInput({ id, name, value, onChange, onBlur, placeholder, invalid, max }) {
|
|
236
|
+
const [draft, setDraft] = react.useState("");
|
|
237
|
+
const commit = () => {
|
|
238
|
+
const parts = draft.split(",").map((s) => s.trim()).filter(Boolean);
|
|
239
|
+
if (parts.length) onChange([...value, ...parts.filter((p) => !value.includes(p))]);
|
|
240
|
+
setDraft("");
|
|
241
|
+
};
|
|
242
|
+
const onKey = (e) => {
|
|
243
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
commit();
|
|
246
|
+
} else if (e.key === "Backspace" && draft === "" && value.length) {
|
|
247
|
+
onChange(value.slice(0, -1));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["realiiz-chips", invalid && "realiiz-form__control--invalid"].filter(Boolean).join(" "), "data-name": name, children: [
|
|
251
|
+
value.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-chip", children: [
|
|
252
|
+
tag,
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": `Remove ${tag}`, onClick: () => onChange(value.filter((t) => t !== tag)), children: "\xD7" })
|
|
254
|
+
] }, tag)),
|
|
255
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
256
|
+
"input",
|
|
257
|
+
{
|
|
258
|
+
id,
|
|
259
|
+
type: "text",
|
|
260
|
+
className: "realiiz-chips__input",
|
|
261
|
+
value: draft,
|
|
262
|
+
placeholder: value.length ? "" : placeholder,
|
|
263
|
+
onChange: (e) => setDraft(e.target.value),
|
|
264
|
+
onKeyDown: onKey,
|
|
265
|
+
onBlur: () => {
|
|
266
|
+
commit();
|
|
267
|
+
onBlur();
|
|
268
|
+
},
|
|
269
|
+
"aria-invalid": invalid || void 0
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
typeof max === "number" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-chips__count", children: [
|
|
273
|
+
value.length,
|
|
274
|
+
" of ",
|
|
275
|
+
max
|
|
276
|
+
] })
|
|
277
|
+
] });
|
|
278
|
+
}
|
|
215
279
|
function Control(p) {
|
|
216
280
|
const cls = ["realiiz-form__control", p.invalid && "realiiz-form__control--invalid"].filter(Boolean).join(" ");
|
|
217
281
|
const common = {
|
|
@@ -286,18 +350,6 @@ function Control(p) {
|
|
|
286
350
|
onChange: (e) => p.onChange(e.target.value)
|
|
287
351
|
}
|
|
288
352
|
);
|
|
289
|
-
case "tags":
|
|
290
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
291
|
-
"input",
|
|
292
|
-
{
|
|
293
|
-
...common,
|
|
294
|
-
type: "text",
|
|
295
|
-
className: cls,
|
|
296
|
-
value: str,
|
|
297
|
-
placeholder: p.spec.placeholder ?? "one, two, three",
|
|
298
|
-
onChange: (e) => p.onChange(e.target.value)
|
|
299
|
-
}
|
|
300
|
-
);
|
|
301
353
|
default:
|
|
302
354
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
303
355
|
"input",
|
|
@@ -313,24 +365,61 @@ function Control(p) {
|
|
|
313
365
|
);
|
|
314
366
|
}
|
|
315
367
|
}
|
|
368
|
+
var defaultLabel = (label, help) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__help", title: help, children: label });
|
|
369
|
+
function LengthGauge({ length: raw, range, suffix }) {
|
|
370
|
+
const [min, max] = range;
|
|
371
|
+
const length = raw === 0 ? 0 : raw + (suffix?.length ?? 0);
|
|
372
|
+
const state = length === 0 ? "empty" : length < min ? "short" : length <= max ? "ok" : "over";
|
|
373
|
+
const scale = Math.round(max * 1.3);
|
|
374
|
+
const pct = (n) => `${Math.min(100, n / scale * 100)}%`;
|
|
375
|
+
const verdict = state === "ok" ? "good length" : state === "over" ? `over ${max} \u2014 may be cut off` : `aim for ${min}\u2013${max}`;
|
|
376
|
+
const incl = suffix && length > 0 ? ` incl. \u201C${suffix.trim()}\u201D` : "";
|
|
377
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `realiiz-gauge realiiz-gauge--${state}`, title: `Recommended ${min}\u2013${max} characters${suffix ? ` including \u201C${suffix.trim()}\u201D` : ""}`, children: [
|
|
378
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-gauge__bar", "aria-hidden": "true", children: [
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-gauge__zone", style: { left: pct(min), width: `calc(${pct(max)} - ${pct(min)})` } }),
|
|
380
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-gauge__fill", style: { width: pct(length) } })
|
|
381
|
+
] }),
|
|
382
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-gauge__text", children: [
|
|
383
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: length }),
|
|
384
|
+
" characters",
|
|
385
|
+
incl,
|
|
386
|
+
" \xB7 ",
|
|
387
|
+
verdict
|
|
388
|
+
] })
|
|
389
|
+
] });
|
|
390
|
+
}
|
|
316
391
|
function Field(p) {
|
|
317
392
|
const id = `${p.formId}-${p.spec.name}`;
|
|
318
393
|
const invalid = p.issues.length > 0;
|
|
319
394
|
const max = p.spec.constraints.max;
|
|
320
|
-
const
|
|
395
|
+
const isText = p.spec.input === "text" || p.spec.input === "textarea";
|
|
321
396
|
const len = typeof p.value === "string" ? p.value.length : 0;
|
|
322
397
|
const isCheck = p.spec.input === "boolean";
|
|
323
|
-
const
|
|
398
|
+
const isTags = p.spec.input === "tags";
|
|
399
|
+
const labelText = p.spec.help ? (p.renderLabel ?? defaultLabel)(p.spec.label, p.spec.help) : p.spec.label;
|
|
400
|
+
const control = isTags ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
401
|
+
ChipsInput,
|
|
402
|
+
{
|
|
403
|
+
id,
|
|
404
|
+
name: p.spec.name,
|
|
405
|
+
value: Array.isArray(p.value) ? p.value : [],
|
|
406
|
+
onChange: (v) => p.onChange(v),
|
|
407
|
+
onBlur: p.onBlur,
|
|
408
|
+
placeholder: p.spec.placeholder,
|
|
409
|
+
invalid,
|
|
410
|
+
max: p.spec.constraints.exact ?? p.spec.constraints.max
|
|
411
|
+
}
|
|
412
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(Control, { id, spec: p.spec, value: p.value, invalid, options: p.options, onChange: p.onChange, onBlur: p.onBlur });
|
|
324
413
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `realiiz-form__field realiiz-form__field--${p.spec.input}`, "data-field": p.spec.name, children: [
|
|
325
414
|
isCheck ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "realiiz-form__check", htmlFor: id, children: [
|
|
326
415
|
control,
|
|
327
416
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
328
|
-
|
|
417
|
+
labelText,
|
|
329
418
|
p.spec.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
|
|
330
419
|
] })
|
|
331
420
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
332
421
|
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "realiiz-form__label", htmlFor: id, children: [
|
|
333
|
-
|
|
422
|
+
labelText,
|
|
334
423
|
p.spec.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
|
|
335
424
|
] }),
|
|
336
425
|
control
|
|
@@ -339,15 +428,529 @@ function Field(p) {
|
|
|
339
428
|
"Current: ",
|
|
340
429
|
p.current
|
|
341
430
|
] }),
|
|
342
|
-
|
|
343
|
-
len,
|
|
344
|
-
"
|
|
345
|
-
|
|
346
|
-
|
|
431
|
+
isText && (p.spec.recommended || typeof max === "number") && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__helper", children: [
|
|
432
|
+
p.spec.recommended ? /* @__PURE__ */ jsxRuntime.jsx(LengthGauge, { length: len, range: p.spec.recommended, suffix: p.spec.suffix }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
433
|
+
typeof max === "number" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-form__count", children: [
|
|
434
|
+
len,
|
|
435
|
+
" / ",
|
|
436
|
+
max
|
|
437
|
+
] })
|
|
438
|
+
] }),
|
|
347
439
|
invalid && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__error", id: `${id}-error`, children: p.issues.map((i, n) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: i.message }, n)) })
|
|
348
440
|
] });
|
|
349
441
|
}
|
|
350
442
|
|
|
443
|
+
// src/forms-ui/md/table.ts
|
|
444
|
+
var ROW = /^\s*\|.*\|\s*$/;
|
|
445
|
+
var SEP = /^\s*\|[\s:|-]+\|\s*$/;
|
|
446
|
+
function tableCells(line) {
|
|
447
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim());
|
|
448
|
+
}
|
|
449
|
+
function tableAt(v, pos) {
|
|
450
|
+
const lines = v.split("\n");
|
|
451
|
+
let i = 0, off = 0;
|
|
452
|
+
while (i < lines.length && pos > off + lines[i].length) {
|
|
453
|
+
off += lines[i].length + 1;
|
|
454
|
+
i++;
|
|
455
|
+
}
|
|
456
|
+
if (i >= lines.length || !ROW.test(lines[i])) return null;
|
|
457
|
+
let s = i, e = i;
|
|
458
|
+
while (s > 0 && ROW.test(lines[s - 1])) s--;
|
|
459
|
+
while (e < lines.length - 1 && ROW.test(lines[e + 1])) e++;
|
|
460
|
+
if (e - s < 1 || !SEP.test(lines[s + 1])) return null;
|
|
461
|
+
const start = lines.slice(0, s).reduce((n, l) => n + l.length + 1, 0);
|
|
462
|
+
const end = start + lines.slice(s, e + 1).join("\n").length;
|
|
463
|
+
const align = tableCells(lines[s + 1]).map((c) => c.startsWith(":") && c.endsWith(":") ? "c" : c.endsWith(":") ? "r" : c.startsWith(":") ? "L" : "l");
|
|
464
|
+
const rows = lines.slice(s, e + 1).filter((_, n) => n !== 1).map(tableCells);
|
|
465
|
+
const col = Math.max(0, (lines[i].slice(0, pos - off).match(/(?<!\\)\|/g) || []).length - 1);
|
|
466
|
+
const row = i - s <= 1 ? 0 : i - s - 1;
|
|
467
|
+
return { start, end, rows, align, row, col };
|
|
468
|
+
}
|
|
469
|
+
function renderTable(rows, align) {
|
|
470
|
+
const cols = Math.max(...rows.map((r) => r.length));
|
|
471
|
+
const w = [];
|
|
472
|
+
for (let c = 0; c < cols; c++) w[c] = Math.max(3, ...rows.map((r) => (r[c] || "").length));
|
|
473
|
+
const pad = (t, c) => (t || "").padEnd(w[c]);
|
|
474
|
+
const line = (r) => "| " + Array.from({ length: cols }, (_, c) => pad(r[c], c)).join(" | ") + " |";
|
|
475
|
+
const sep = "|" + Array.from({ length: cols }, (_, c) => align[c] === "c" ? ":" + "-".repeat(w[c]) + ":" : align[c] === "r" ? " " + "-".repeat(w[c] - 1) + ": " : align[c] === "L" ? ":" + "-".repeat(w[c] - 1) + " " : " " + "-".repeat(w[c]) + " ").join("|") + "|";
|
|
476
|
+
return [line(rows[0]), sep, ...rows.slice(1).map(line)].join("\n");
|
|
477
|
+
}
|
|
478
|
+
function newTable(rows, cols) {
|
|
479
|
+
const head = Array.from({ length: cols }, (_, i) => `Column ${i + 1}`);
|
|
480
|
+
const grid = [head, ...Array.from({ length: rows }, () => Array(cols).fill(""))];
|
|
481
|
+
return { text: renderTable(grid, Array(cols).fill("l")), firstHeader: head[0] };
|
|
482
|
+
}
|
|
483
|
+
function applyTableOp(v, pos, op) {
|
|
484
|
+
const t = tableAt(v, pos);
|
|
485
|
+
if (!t) return null;
|
|
486
|
+
const rows = t.rows.map((r) => r.slice());
|
|
487
|
+
const cols = Math.max(...rows.map((r) => r.length));
|
|
488
|
+
const align = t.align.slice();
|
|
489
|
+
if (op === "rowAfter") {
|
|
490
|
+
rows.splice(Math.max(1, t.row + 1), 0, Array(cols).fill(""));
|
|
491
|
+
} else if (op === "rowDel") {
|
|
492
|
+
if (t.row === 0 || rows.length <= 2) return { error: "A table needs its header row and at least one row below it." };
|
|
493
|
+
rows.splice(t.row, 1);
|
|
494
|
+
} else if (op === "colAfter") {
|
|
495
|
+
rows.forEach((r) => {
|
|
496
|
+
while (r.length < cols) r.push("");
|
|
497
|
+
r.splice(t.col + 1, 0, "");
|
|
498
|
+
});
|
|
499
|
+
align.splice(t.col + 1, 0, "l");
|
|
500
|
+
} else if (op === "colDel") {
|
|
501
|
+
if (cols <= 1) return { error: "That's the last column \u2014 delete the table instead." };
|
|
502
|
+
rows.forEach((r) => r.splice(t.col, 1));
|
|
503
|
+
align.splice(t.col, 1);
|
|
504
|
+
}
|
|
505
|
+
const out = renderTable(rows, align);
|
|
506
|
+
return { text: v.slice(0, t.start) + out + v.slice(t.end), a: t.start, b: t.start + out.length };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/forms-ui/md/edits.ts
|
|
510
|
+
var toggleWrap = (mark, placeholder = "text") => (s, a, b) => {
|
|
511
|
+
const sel = s.slice(a, b);
|
|
512
|
+
const n = mark.length;
|
|
513
|
+
if (sel.length >= 2 * n && sel.startsWith(mark) && sel.endsWith(mark)) {
|
|
514
|
+
const inner = sel.slice(n, sel.length - n);
|
|
515
|
+
return { text: s.slice(0, a) + inner + s.slice(b), a, b: a + inner.length };
|
|
516
|
+
}
|
|
517
|
+
if (s.slice(a - n, a) === mark && s.slice(b, b + n) === mark) {
|
|
518
|
+
return { text: s.slice(0, a - n) + sel + s.slice(b + n), a: a - n, b: a - n + sel.length };
|
|
519
|
+
}
|
|
520
|
+
const body = sel || placeholder;
|
|
521
|
+
return { text: s.slice(0, a) + mark + body + mark + s.slice(b), a: a + n, b: a + n + body.length };
|
|
522
|
+
};
|
|
523
|
+
var PREFIX_RE = /^(#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s?)/;
|
|
524
|
+
var lineSpan = (s, a, b) => {
|
|
525
|
+
const start = s.lastIndexOf("\n", a - 1) + 1;
|
|
526
|
+
const endIdx = s.indexOf("\n", Math.max(b, a));
|
|
527
|
+
return { start, end: endIdx === -1 ? s.length : endIdx };
|
|
528
|
+
};
|
|
529
|
+
var toggleLines = (prefix, matches) => (s, a, b) => {
|
|
530
|
+
const { start, end } = lineSpan(s, a, b);
|
|
531
|
+
const lines = s.slice(start, end).split("\n");
|
|
532
|
+
const allOn = lines.every((l) => l.trim() === "" || matches(l));
|
|
533
|
+
const out = lines.map((l, i) => {
|
|
534
|
+
const bare = l.replace(PREFIX_RE, "");
|
|
535
|
+
return allOn ? bare : (typeof prefix === "string" ? prefix : prefix(i)) + bare;
|
|
536
|
+
});
|
|
537
|
+
const replaced = out.join("\n");
|
|
538
|
+
return { text: s.slice(0, start) + replaced + s.slice(end), a: start, b: start + replaced.length };
|
|
539
|
+
};
|
|
540
|
+
var LINK_RE = /\[([^\]\n]*)\]\(([^)\n]*)\)/g;
|
|
541
|
+
function linkAt(s, a, b) {
|
|
542
|
+
for (const m of s.matchAll(LINK_RE)) {
|
|
543
|
+
if (s[m.index - 1] === "!") continue;
|
|
544
|
+
const from = m.index, to = from + m[0].length;
|
|
545
|
+
if (a >= from && b <= to) return { from, to, label: m[1] };
|
|
546
|
+
if (from > b) break;
|
|
547
|
+
}
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
var toggleLink = (s, a, b) => {
|
|
551
|
+
const hit = linkAt(s, a, b);
|
|
552
|
+
if (hit) return { text: s.slice(0, hit.from) + hit.label + s.slice(hit.to), a: hit.from, b: hit.from + hit.label.length };
|
|
553
|
+
const label = s.slice(a, b) || "link text";
|
|
554
|
+
const urlStart = a + label.length + 3;
|
|
555
|
+
return { text: s.slice(0, a) + `[${label}](https://)` + s.slice(b), a: urlStart, b: urlStart + 8 };
|
|
556
|
+
};
|
|
557
|
+
var insertImage = (s, a, b) => {
|
|
558
|
+
const alt = s.slice(a, b) || "description";
|
|
559
|
+
const urlStart = a + alt.length + 4;
|
|
560
|
+
return { text: s.slice(0, a) + `` + s.slice(b), a: urlStart, b: urlStart + 8 };
|
|
561
|
+
};
|
|
562
|
+
var insertDot = (s, a, b) => {
|
|
563
|
+
const t = " \xB7 ";
|
|
564
|
+
return { text: s.slice(0, a) + t + s.slice(b), a: a + t.length, b: a + t.length };
|
|
565
|
+
};
|
|
566
|
+
var blockBefore = (s, a) => a === 0 || s.slice(0, a).endsWith("\n\n") ? "" : s.slice(0, a).endsWith("\n") ? "\n" : "\n\n";
|
|
567
|
+
var insertRule = (s, a, b) => {
|
|
568
|
+
const before = blockBefore(s, a);
|
|
569
|
+
const t = before + "---\n\n";
|
|
570
|
+
return { text: s.slice(0, a) + t + s.slice(b), a: a + t.length, b: a + t.length };
|
|
571
|
+
};
|
|
572
|
+
var insertTable = (rows, cols) => (s, a, b) => {
|
|
573
|
+
const { text, firstHeader } = newTable(rows, cols);
|
|
574
|
+
const before = blockBefore(s, a);
|
|
575
|
+
const t = before + text + "\n";
|
|
576
|
+
const at = a + before.length + text.indexOf(firstHeader);
|
|
577
|
+
return { text: s.slice(0, a) + t + s.slice(b), a: at, b: at + firstHeader.length };
|
|
578
|
+
};
|
|
579
|
+
var toggleTerm = (component = "Term", attr = "definition") => (s, a, b) => {
|
|
580
|
+
const re = new RegExp(`<${component}\\s+${attr}="([^"]*)"\\s*>([\\s\\S]*?)</${component}>`, "g");
|
|
581
|
+
for (const m of s.matchAll(re)) {
|
|
582
|
+
const from = m.index, to = from + m[0].length;
|
|
583
|
+
if (a >= from && b <= to) return { text: s.slice(0, from) + m[2] + s.slice(to), a: from, b: from + m[2].length };
|
|
584
|
+
if (from > b) break;
|
|
585
|
+
}
|
|
586
|
+
const word = s.slice(a, b) || "term";
|
|
587
|
+
const open = `<${component} ${attr}="`;
|
|
588
|
+
const text = s.slice(0, a) + open + `">${word}</${component}>` + s.slice(b);
|
|
589
|
+
return { text, a: a + open.length, b: a + open.length };
|
|
590
|
+
};
|
|
591
|
+
var TOOLS = [
|
|
592
|
+
{ id: "bold", title: "Bold \u2014 \u2318B", apply: toggleWrap("**") },
|
|
593
|
+
{ id: "italic", title: "Italic \u2014 \u2318I", apply: toggleWrap("_") },
|
|
594
|
+
{ id: "h2", title: "Section heading", apply: toggleLines("## ", (l) => /^##\s/.test(l)) },
|
|
595
|
+
{ id: "h3", title: "Sub-heading", apply: toggleLines("### ", (l) => /^###\s/.test(l)) },
|
|
596
|
+
{ id: "ul", title: "Bulleted list", apply: toggleLines("- ", (l) => /^[-*+]\s/.test(l)) },
|
|
597
|
+
{ id: "ol", title: "Numbered list", apply: toggleLines((i) => `${i + 1}. `, (l) => /^\d+[.)]\s/.test(l)) },
|
|
598
|
+
{ id: "quote", title: "Quote", apply: toggleLines("> ", (l) => /^>/.test(l)) },
|
|
599
|
+
{ id: "link", title: "Link", apply: toggleLink },
|
|
600
|
+
{ id: "code", title: "Inline code", apply: toggleWrap("`", "code") },
|
|
601
|
+
{ id: "image", title: "Image", apply: insertImage },
|
|
602
|
+
{ id: "dot", title: "Separator between details ( \xB7 )", apply: insertDot },
|
|
603
|
+
{ id: "rule", title: "Divider line across the page", apply: insertRule }
|
|
604
|
+
];
|
|
605
|
+
var toolById = (id) => TOOLS.find((t) => t.id === id);
|
|
606
|
+
function activeTools(s, a, b) {
|
|
607
|
+
const on = /* @__PURE__ */ new Set();
|
|
608
|
+
const sel = s.slice(a, b);
|
|
609
|
+
const wrapped = (m) => s.slice(a - m.length, a) === m && s.slice(b, b + m.length) === m || sel.length >= 2 * m.length && sel.startsWith(m) && sel.endsWith(m);
|
|
610
|
+
if (wrapped("**") || spanAt(s, a, b, /\*\*[^*\n]+\*\*/g, 2)) on.add("bold");
|
|
611
|
+
if (wrapped("_") || spanAt(s, a, b, /(?<![\w_])_[^_\n]+_(?![\w_])/g, 1)) on.add("italic");
|
|
612
|
+
if (wrapped("`") || spanAt(s, a, b, /`[^`\n]+`/g, 1)) on.add("code");
|
|
613
|
+
if (linkAt(s, a, b)) on.add("link");
|
|
614
|
+
if (spanAt(s, a, b, /<([A-Z][\w]*)\s[^>]*>[\s\S]*?<\/\1>/g, 0)) on.add("term");
|
|
615
|
+
const { start, end } = lineSpan(s, a, a);
|
|
616
|
+
const line = s.slice(start, end);
|
|
617
|
+
if (/^##\s/.test(line)) on.add("h2");
|
|
618
|
+
else if (/^###\s/.test(line)) on.add("h3");
|
|
619
|
+
else if (/^[-*+]\s/.test(line)) on.add("ul");
|
|
620
|
+
else if (/^\d+[.)]\s/.test(line)) on.add("ol");
|
|
621
|
+
else if (/^>/.test(line)) on.add("quote");
|
|
622
|
+
return on;
|
|
623
|
+
}
|
|
624
|
+
function spanAt(s, a, b, re, pad) {
|
|
625
|
+
for (const m of s.matchAll(re)) {
|
|
626
|
+
const from = m.index, to = from + m[0].length;
|
|
627
|
+
if (a >= from + pad && b <= to - pad) return true;
|
|
628
|
+
if (from > b) break;
|
|
629
|
+
}
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
var Svg = ({ children, fill = "none" }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "realiiz-md__ico", viewBox: "0 0 24 24", fill, stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
|
|
633
|
+
var MD_ICONS = {
|
|
634
|
+
bold: /* @__PURE__ */ jsxRuntime.jsx("b", { children: "B" }),
|
|
635
|
+
italic: /* @__PURE__ */ jsxRuntime.jsx("i", { children: "I" }),
|
|
636
|
+
h2: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
637
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 5v14" }),
|
|
638
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 5v14" }),
|
|
639
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 12h8" }),
|
|
640
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.5 19v-6.5l-1.6 1.2" })
|
|
641
|
+
] }),
|
|
642
|
+
h3: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
643
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 5v14" }),
|
|
644
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14" }),
|
|
645
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h7" }),
|
|
646
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 12.5h3.5l-2 2.2a1.9 1.9 0 1 1-1.5 3.2" })
|
|
647
|
+
] }),
|
|
648
|
+
ul: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
649
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "6.5", x2: "20", y2: "6.5" }),
|
|
650
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "12", x2: "20", y2: "12" }),
|
|
651
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "17.5", x2: "20", y2: "17.5" }),
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.6", cy: "6.5", r: "1.3", fill: "currentColor", stroke: "none" }),
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.6", cy: "12", r: "1.3", fill: "currentColor", stroke: "none" }),
|
|
654
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.6", cy: "17.5", r: "1.3", fill: "currentColor", stroke: "none" })
|
|
655
|
+
] }),
|
|
656
|
+
ol: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
657
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", y1: "6.5", x2: "20", y2: "6.5" }),
|
|
658
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", y1: "12", x2: "20", y2: "12" }),
|
|
659
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", y1: "17.5", x2: "20", y2: "17.5" }),
|
|
660
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 5.6 5.2 5v3.4" }),
|
|
661
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.6 11.2h1.8L3.6 13.7h1.9" }),
|
|
662
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.7 16.3h1.7v1.2H4.2h1.2v1.2H3.7" })
|
|
663
|
+
] }),
|
|
664
|
+
quote: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 7c-2.4 0-4 1.7-4 4 0 1.9 1.3 3.2 3 3.2.4 0 .8-.1 1-.2-.3 1.4-1.5 2.6-3 3l.7 1.5c2.8-.8 4.8-3.3 4.8-6.6C11.5 8.7 10.5 7 9 7z", fill: "currentColor", stroke: "none" }),
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 7c-2.4 0-4 1.7-4 4 0 1.9 1.3 3.2 3 3.2.4 0 .8-.1 1-.2-.3 1.4-1.5 2.6-3 3l.7 1.5c2.8-.8 4.8-3.3 4.8-6.6C20.5 8.7 19.5 7 18 7z", fill: "currentColor", stroke: "none" })
|
|
667
|
+
] }),
|
|
668
|
+
link: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
669
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 13a5 5 0 0 0 7.5.5l2.5-2.5a5 5 0 0 0-7-7l-1.5 1.4" }),
|
|
670
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 11a5 5 0 0 0-7.5-.5L4 13a5 5 0 0 0 7 7l1.4-1.4" })
|
|
671
|
+
] }),
|
|
672
|
+
code: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
673
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m8 7-5 5 5 5" }),
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m16 7 5 5-5 5" }),
|
|
675
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m14 4-4 16" })
|
|
676
|
+
] }),
|
|
677
|
+
// A tooltip: a tall speech bubble with an "i" — what the reader will actually see on the page.
|
|
678
|
+
term: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
679
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 2.5h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-4.5L12 21.5 9.5 17.5H5a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2z" }),
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4.5" }),
|
|
681
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "6.2", r: ".9", fill: "currentColor", stroke: "none" })
|
|
682
|
+
] }),
|
|
683
|
+
image: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
684
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
|
|
685
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.6" }),
|
|
686
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21 15-5-5L5 21" })
|
|
687
|
+
] }),
|
|
688
|
+
dot: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__dot", children: "\xB7" }),
|
|
689
|
+
rule: /* @__PURE__ */ jsxRuntime.jsx(Svg, { children: /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12" }) }),
|
|
690
|
+
table: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
691
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
|
|
692
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9.5", x2: "21", y2: "9.5" }),
|
|
693
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" }),
|
|
694
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "9.5", x2: "12", y2: "20" })
|
|
695
|
+
] }),
|
|
696
|
+
expand: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
697
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 3H3v6" }),
|
|
698
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 21h6v-6" }),
|
|
699
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3l7 7" }),
|
|
700
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 21l-7-7" })
|
|
701
|
+
] }),
|
|
702
|
+
contract: /* @__PURE__ */ jsxRuntime.jsxs(Svg, { children: [
|
|
703
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 10h6V4" }),
|
|
704
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 14h-6v6" }),
|
|
705
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 3l7 7" }),
|
|
706
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 21l-7-7" })
|
|
707
|
+
] })
|
|
708
|
+
};
|
|
709
|
+
var MAX = 6;
|
|
710
|
+
function TableGrid({ onPick }) {
|
|
711
|
+
const [open, setOpen] = react.useState(false);
|
|
712
|
+
const [hot, setHot] = react.useState([0, 0]);
|
|
713
|
+
const wrap = react.useRef(null);
|
|
714
|
+
react.useEffect(() => {
|
|
715
|
+
if (!open) return;
|
|
716
|
+
const away = (e) => {
|
|
717
|
+
if (!wrap.current?.contains(e.target)) setOpen(false);
|
|
718
|
+
};
|
|
719
|
+
const esc = (e) => {
|
|
720
|
+
if (e.key === "Escape") setOpen(false);
|
|
721
|
+
};
|
|
722
|
+
document.addEventListener("mousedown", away);
|
|
723
|
+
document.addEventListener("keydown", esc);
|
|
724
|
+
return () => {
|
|
725
|
+
document.removeEventListener("mousedown", away);
|
|
726
|
+
document.removeEventListener("keydown", esc);
|
|
727
|
+
};
|
|
728
|
+
}, [open]);
|
|
729
|
+
const [r, c] = hot;
|
|
730
|
+
const label = r && c ? `${c} column${c === 1 ? "" : "s"} \xD7 ${r} row${r === 1 ? "" : "s"}` : "Pick a size";
|
|
731
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: ["realiiz-md__tgwrap", open && "is-open"].filter(Boolean).join(" "), ref: wrap, children: [
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-tool": "table", "data-tip": "New table \u2014 pick a size", "aria-label": "Insert a table", "aria-expanded": open, onClick: () => setOpen((o) => !o), children: MD_ICONS.table }),
|
|
733
|
+
open && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-md__tgrid", role: "dialog", "aria-label": "Table size", onMouseLeave: () => setHot([0, 0]), children: [
|
|
734
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__tgcells", children: Array.from({ length: MAX * MAX }, (_, n) => {
|
|
735
|
+
const rr = Math.floor(n / MAX) + 1, cc = n % MAX + 1;
|
|
736
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
737
|
+
"i",
|
|
738
|
+
{
|
|
739
|
+
className: rr <= r && cc <= c ? "hot" : void 0,
|
|
740
|
+
onMouseEnter: () => setHot([rr, cc]),
|
|
741
|
+
onClick: () => {
|
|
742
|
+
setOpen(false);
|
|
743
|
+
onPick(rr, cc);
|
|
744
|
+
},
|
|
745
|
+
"aria-label": `${cc} \xD7 ${rr}`,
|
|
746
|
+
role: "button"
|
|
747
|
+
},
|
|
748
|
+
n
|
|
749
|
+
);
|
|
750
|
+
}) }),
|
|
751
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__tglab", children: label })
|
|
752
|
+
] })
|
|
753
|
+
] });
|
|
754
|
+
}
|
|
755
|
+
var FORMAT = ["bold", "italic"];
|
|
756
|
+
var BLOCK = ["h2", "h3", "ul", "ol", "quote"];
|
|
757
|
+
var INLINE = ["link", "code"];
|
|
758
|
+
var INSERT = ["image", "dot", "rule"];
|
|
759
|
+
var TABLE_OPS = [
|
|
760
|
+
{ op: "rowAfter", label: "+ Row", tip: "Insert a row below this one" },
|
|
761
|
+
{ op: "rowDel", label: "\u2212 Row", tip: "Delete this row" },
|
|
762
|
+
{ op: "colAfter", label: "+ Col", tip: "Insert a column to the right" },
|
|
763
|
+
{ op: "colDel", label: "\u2212 Col", tip: "Delete this column" }
|
|
764
|
+
];
|
|
765
|
+
function MarkdownEditor({ id, name = "body", value, onChange, placeholder, renderPreview, previewClassName, minHeight = 320, termComponent }) {
|
|
766
|
+
const [mode, setMode] = react.useState(() => renderPreview && value.trim() ? "preview" : "edit");
|
|
767
|
+
const [preview, setPreview] = react.useState(null);
|
|
768
|
+
const [loading, setLoading] = react.useState(false);
|
|
769
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
770
|
+
const [on, setOn] = react.useState(() => /* @__PURE__ */ new Set());
|
|
771
|
+
const [inTable, setInTable] = react.useState(false);
|
|
772
|
+
const [note, setNote] = react.useState(null);
|
|
773
|
+
const ta = react.useRef(null);
|
|
774
|
+
const sync = react.useCallback(() => {
|
|
775
|
+
const el = ta.current;
|
|
776
|
+
if (!el) return;
|
|
777
|
+
setOn(activeTools(el.value, el.selectionStart, el.selectionEnd));
|
|
778
|
+
setInTable(Boolean(tableAt(el.value, el.selectionStart)));
|
|
779
|
+
}, []);
|
|
780
|
+
const commit = (edit) => {
|
|
781
|
+
const el = ta.current;
|
|
782
|
+
if (!el) return;
|
|
783
|
+
const { text, a, b } = edit;
|
|
784
|
+
const from = commonPrefix(el.value, text);
|
|
785
|
+
const oldTail = el.value.length - from, newTail = text.length - from;
|
|
786
|
+
const suffix = commonSuffix(el.value.slice(from), text.slice(from));
|
|
787
|
+
const oldEnd = from + oldTail - suffix, newEnd = from + newTail - suffix;
|
|
788
|
+
let ok = false;
|
|
789
|
+
el.focus();
|
|
790
|
+
el.setSelectionRange(from, oldEnd);
|
|
791
|
+
try {
|
|
792
|
+
ok = document.execCommand("insertText", false, text.slice(from, newEnd));
|
|
793
|
+
} catch {
|
|
794
|
+
ok = false;
|
|
795
|
+
}
|
|
796
|
+
if (!ok || el.value !== text) onChange(text);
|
|
797
|
+
requestAnimationFrame(() => {
|
|
798
|
+
el.setSelectionRange(a, b);
|
|
799
|
+
sync();
|
|
800
|
+
});
|
|
801
|
+
};
|
|
802
|
+
const run = (apply) => {
|
|
803
|
+
const el = ta.current;
|
|
804
|
+
if (!el) return;
|
|
805
|
+
commit(apply(value, el.selectionStart, el.selectionEnd));
|
|
806
|
+
};
|
|
807
|
+
const runTable = (op) => {
|
|
808
|
+
const el = ta.current;
|
|
809
|
+
if (!el) return;
|
|
810
|
+
const r = applyTableOp(value, el.selectionStart, op);
|
|
811
|
+
if (!r) return;
|
|
812
|
+
if ("error" in r) {
|
|
813
|
+
setNote(r.error);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
setNote(null);
|
|
817
|
+
commit(r);
|
|
818
|
+
};
|
|
819
|
+
const onKey = (e) => {
|
|
820
|
+
if (e.key === "Escape" && expanded) {
|
|
821
|
+
e.preventDefault();
|
|
822
|
+
setExpanded(false);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
826
|
+
if (e.key === "b") {
|
|
827
|
+
e.preventDefault();
|
|
828
|
+
run(toolById("bold").apply);
|
|
829
|
+
}
|
|
830
|
+
if (e.key === "i") {
|
|
831
|
+
e.preventDefault();
|
|
832
|
+
run(toolById("italic").apply);
|
|
833
|
+
}
|
|
834
|
+
if (e.key === "k") {
|
|
835
|
+
e.preventDefault();
|
|
836
|
+
run(toolById("link").apply);
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
react.useEffect(() => {
|
|
840
|
+
if (!expanded) return;
|
|
841
|
+
const prev = document.body.style.overflow;
|
|
842
|
+
document.body.style.overflow = "hidden";
|
|
843
|
+
const esc = (e) => {
|
|
844
|
+
if (e.key === "Escape") setExpanded(false);
|
|
845
|
+
};
|
|
846
|
+
document.addEventListener("keydown", esc);
|
|
847
|
+
return () => {
|
|
848
|
+
document.body.style.overflow = prev;
|
|
849
|
+
document.removeEventListener("keydown", esc);
|
|
850
|
+
};
|
|
851
|
+
}, [expanded]);
|
|
852
|
+
react.useEffect(() => {
|
|
853
|
+
if (mode !== "preview" || !renderPreview) return;
|
|
854
|
+
let live = true;
|
|
855
|
+
setLoading(true);
|
|
856
|
+
renderPreview(value).then((r) => {
|
|
857
|
+
if (live) setPreview(r);
|
|
858
|
+
}).catch((err) => {
|
|
859
|
+
if (live) setPreview({ error: err instanceof Error ? err.message : String(err) });
|
|
860
|
+
}).finally(() => {
|
|
861
|
+
if (live) setLoading(false);
|
|
862
|
+
});
|
|
863
|
+
return () => {
|
|
864
|
+
live = false;
|
|
865
|
+
};
|
|
866
|
+
}, [mode, value, renderPreview]);
|
|
867
|
+
const termTool = termComponent ? { id: "term", title: `Tooltip \u2014 wraps the selection in <${termComponent}>`, apply: toggleTerm(termComponent) } : null;
|
|
868
|
+
const btn = (toolId) => {
|
|
869
|
+
const t = toolId === "term" && termTool ? termTool : toolById(toolId);
|
|
870
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-tool": t.id, "data-tip": t.title, "aria-label": t.title, "aria-pressed": on.has(t.id) || void 0, className: on.has(t.id) ? "on" : void 0, onMouseDown: (e) => e.preventDefault(), onClick: () => run(t.apply), children: MD_ICONS[t.id] }, t.id);
|
|
871
|
+
};
|
|
872
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ["realiiz-md", expanded && "realiiz-md--fs"].filter(Boolean).join(" "), children: [
|
|
873
|
+
renderPreview && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-md__mode", role: "tablist", "aria-label": "Editor mode", children: [
|
|
874
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", role: "tab", "aria-selected": mode === "edit", onClick: () => setMode("edit"), children: "Edit Markdown" }),
|
|
875
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", role: "tab", "aria-selected": mode === "preview", onClick: () => setMode("preview"), children: "Preview" })
|
|
876
|
+
] }),
|
|
877
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-md__panel", children: [
|
|
878
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-md__head", children: [
|
|
879
|
+
mode === "edit" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-md__tools", role: "toolbar", "aria-label": "Formatting", children: [
|
|
880
|
+
FORMAT.map(btn),
|
|
881
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__sep" }),
|
|
882
|
+
BLOCK.map(btn),
|
|
883
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__sep" }),
|
|
884
|
+
INLINE.map(btn),
|
|
885
|
+
termTool && btn("term"),
|
|
886
|
+
inTable && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
887
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__sep" }),
|
|
888
|
+
TABLE_OPS.map((t) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "data-tbl-op": t.op, "data-tip": t.tip, "aria-label": t.tip, onMouseDown: (e) => e.preventDefault(), onClick: () => runTable(t.op), children: t.label }, t.op))
|
|
889
|
+
] }),
|
|
890
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-md__insert", children: [
|
|
891
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__lab", children: "Insert" }),
|
|
892
|
+
INSERT.map(btn),
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx(TableGrid, { onPick: (r, c) => run(insertTable(r, c)) })
|
|
894
|
+
] })
|
|
895
|
+
] }),
|
|
896
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-md__spacer" }),
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
898
|
+
"button",
|
|
899
|
+
{
|
|
900
|
+
type: "button",
|
|
901
|
+
className: "realiiz-md__expand",
|
|
902
|
+
"data-tip": expanded ? "Back to the normal layout \u2014 or press Esc" : "Fill the window \u2014 Esc to come back",
|
|
903
|
+
"aria-label": expanded ? "Exit" : "Expand",
|
|
904
|
+
onClick: () => setExpanded((x) => !x),
|
|
905
|
+
children: expanded ? MD_ICONS.contract : MD_ICONS.expand
|
|
906
|
+
}
|
|
907
|
+
)
|
|
908
|
+
] }),
|
|
909
|
+
mode === "edit" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
910
|
+
"textarea",
|
|
911
|
+
{
|
|
912
|
+
ref: ta,
|
|
913
|
+
id,
|
|
914
|
+
name,
|
|
915
|
+
className: "realiiz-form__control realiiz-form__control--body realiiz-md__ta",
|
|
916
|
+
style: expanded ? void 0 : { minHeight },
|
|
917
|
+
value,
|
|
918
|
+
placeholder,
|
|
919
|
+
onChange: (e) => {
|
|
920
|
+
onChange(e.target.value);
|
|
921
|
+
sync();
|
|
922
|
+
},
|
|
923
|
+
onSelect: sync,
|
|
924
|
+
onKeyUp: sync,
|
|
925
|
+
onClick: sync,
|
|
926
|
+
onKeyDown: onKey,
|
|
927
|
+
spellCheck: true
|
|
928
|
+
}
|
|
929
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-md__preview", style: expanded ? void 0 : { minHeight }, "aria-live": "polite", "aria-busy": loading, children: [
|
|
930
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-md__loading", children: "Rendering\u2026" }),
|
|
931
|
+
!loading && preview && "error" in preview && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__alert", role: "alert", children: [
|
|
932
|
+
"Couldn't render: ",
|
|
933
|
+
preview.error
|
|
934
|
+
] }),
|
|
935
|
+
!loading && preview && "html" in preview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: ["realiiz-prose", previewClassName].filter(Boolean).join(" "), dangerouslySetInnerHTML: { __html: preview.html } })
|
|
936
|
+
] })
|
|
937
|
+
] }),
|
|
938
|
+
note && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__helper realiiz-md__note", role: "status", children: note })
|
|
939
|
+
] });
|
|
940
|
+
}
|
|
941
|
+
function commonPrefix(a, b) {
|
|
942
|
+
const n = Math.min(a.length, b.length);
|
|
943
|
+
let i = 0;
|
|
944
|
+
while (i < n && a[i] === b[i]) i++;
|
|
945
|
+
return i;
|
|
946
|
+
}
|
|
947
|
+
function commonSuffix(a, b) {
|
|
948
|
+
const n = Math.min(a.length, b.length);
|
|
949
|
+
let i = 0;
|
|
950
|
+
while (i < n && a[a.length - 1 - i] === b[b.length - 1 - i]) i++;
|
|
951
|
+
return i;
|
|
952
|
+
}
|
|
953
|
+
|
|
351
954
|
// src/forms-ui/issues.ts
|
|
352
955
|
function mapIssues(issues, specs) {
|
|
353
956
|
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
@@ -424,9 +1027,65 @@ var FORM_CSS = `
|
|
|
424
1027
|
.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
|
|
425
1028
|
.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
|
|
426
1029
|
.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
|
|
1030
|
+
.realiiz-form__help{text-decoration:underline dotted;text-decoration-color:color-mix(in srgb,currentColor 45%,transparent);text-underline-offset:4px;cursor:help}
|
|
1031
|
+
.realiiz-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
1032
|
+
.realiiz-form__footer{max-width:920px}
|
|
1033
|
+
.realiiz-form__bar{display:flex;align-items:center;gap:10px;margin-top:16px;padding:12px 0;border-top:1px solid var(--_border);background:#fff}
|
|
1034
|
+
.realiiz-form__status{margin-right:auto;font-size:.85rem;color:var(--_muted)}
|
|
1035
|
+
.realiiz-form__bar[data-dirty] .realiiz-form__status{color:var(--_fg)}
|
|
427
1036
|
.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}
|
|
428
1037
|
.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}
|
|
429
1038
|
.realiiz-form__button:disabled{opacity:.6;cursor:default}
|
|
1039
|
+
.realiiz-md{--_md-head:#fafbfd;--_md-hover:#f4f8fd;--_md-hover-line:#bcd0ea;--_md-on:#eef4fb;--_md-tip:#1f2933;position:relative}
|
|
1040
|
+
.realiiz-md__mode{display:inline-flex;align-self:flex-start;width:max-content;gap:2px;padding:3px;background:color-mix(in srgb,var(--_border) 40%,transparent);border-radius:8px;margin-bottom:8px}
|
|
1041
|
+
.realiiz-md__mode button{font:inherit;font-size:.8rem;padding:5px 13px;border:0;border-radius:6px;background:transparent;color:var(--_muted);cursor:pointer}
|
|
1042
|
+
.realiiz-md__mode button[aria-selected="true"]{background:#fff;color:var(--_fg);box-shadow:0 1px 2px rgba(0,0,0,.12)}
|
|
1043
|
+
.realiiz-md__panel{position:relative;z-index:0;display:flex;flex-direction:column;border:1px solid var(--_border);border-radius:10px;background:#fff;overflow:hidden}
|
|
1044
|
+
.realiiz-md__panel:focus-within{border-color:var(--_md-hover-line)}
|
|
1045
|
+
.realiiz-md__head{position:relative;z-index:5;display:flex;align-items:center;gap:2px;flex-wrap:wrap;padding:5px 7px;background:var(--_md-head);border-bottom:1px solid var(--_border);flex-shrink:0}
|
|
1046
|
+
.realiiz-md__tools{display:flex;align-items:center;gap:3px;flex-wrap:wrap}
|
|
1047
|
+
.realiiz-md__spacer{flex:1}
|
|
1048
|
+
.realiiz-md__head button{position:relative;min-width:31px;height:28px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;background:#fff;border:1px solid var(--_border);border-radius:6px;font-family:inherit;font-size:.82rem;color:var(--_fg);cursor:pointer;line-height:1}
|
|
1049
|
+
.realiiz-md__head button:hover:not(:disabled){border-color:var(--_md-hover-line);background:var(--_md-hover)}
|
|
1050
|
+
.realiiz-md__head button:active{background:var(--_md-on)}
|
|
1051
|
+
.realiiz-md__head button.on{background:var(--_md-on);border-color:var(--_accent);color:var(--_accent);font-weight:600}
|
|
1052
|
+
.realiiz-md__ico{width:15px;height:15px;display:block}
|
|
1053
|
+
.realiiz-md__dot{font-size:1.05rem;line-height:1}
|
|
1054
|
+
.realiiz-md__sep{width:1px;height:17px;background:var(--_border);margin:0 5px}
|
|
1055
|
+
.realiiz-md__insert{display:inline-flex;align-items:center;gap:3px;border-left:1px solid var(--_border);padding-left:8px;margin-left:6px}
|
|
1056
|
+
.realiiz-md__insert button{background:#f6f8fa;color:var(--_muted);padding:0;width:31px}
|
|
1057
|
+
.realiiz-md__insert button:hover{color:var(--_accent)}
|
|
1058
|
+
.realiiz-md__lab{font-size:.74rem;color:var(--_muted);margin-right:3px}
|
|
1059
|
+
.realiiz-md__head button[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:0;background:var(--_md-tip);color:#fff;font-size:.72rem;padding:4px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .12s;z-index:20}
|
|
1060
|
+
.realiiz-md__head button[data-tip]:hover::after,.realiiz-md__head button[data-tip]:focus-visible::after{opacity:1}
|
|
1061
|
+
.realiiz-md__expand[data-tip]::after{left:auto;right:0}
|
|
1062
|
+
.realiiz-md__tgwrap{position:relative;display:inline-flex}
|
|
1063
|
+
.realiiz-md__tgrid{position:absolute;top:calc(100% + 6px);left:0;background:#fff;border:1px solid var(--_border);border-radius:9px;padding:9px;box-shadow:0 10px 28px rgba(0,0,0,.14);z-index:7;display:block}
|
|
1064
|
+
.realiiz-md__tgcells{display:grid;grid-template-columns:repeat(6,17px);gap:3px}
|
|
1065
|
+
.realiiz-md__tgcells i{width:17px;height:15px;border:1px solid var(--_border);border-radius:2px;background:#fff;cursor:pointer;display:block}
|
|
1066
|
+
.realiiz-md__tgcells i.hot{background:#dbe9fb;border-color:var(--_accent)}
|
|
1067
|
+
.realiiz-md__tglab{display:block;margin-top:8px;font-size:.74rem;color:var(--_muted);text-align:center;white-space:nowrap}
|
|
1068
|
+
.realiiz-md .realiiz-md__ta{flex:1 1 auto;border:0;border-radius:0;resize:vertical;background:#fff;max-width:none;padding:16px 18px;line-height:1.65}
|
|
1069
|
+
.realiiz-md .realiiz-md__ta:focus{outline:none;box-shadow:none}
|
|
1070
|
+
.realiiz-md__preview{padding:20px 24px;background:#fff}
|
|
1071
|
+
.realiiz-md__note{padding:4px 2px}
|
|
1072
|
+
.realiiz-md--fs{position:fixed;inset:0;z-index:60;background:#fff;padding:14px 22px 22px;overflow:auto;display:flex;flex-direction:column}
|
|
1073
|
+
.realiiz-md--fs .realiiz-md__panel{flex:1;min-height:0}
|
|
1074
|
+
.realiiz-md--fs .realiiz-md__ta,.realiiz-md--fs .realiiz-md__preview{flex:1;min-height:0;overflow:auto;resize:none}
|
|
1075
|
+
.realiiz-prose{max-width:68ch;line-height:1.65}
|
|
1076
|
+
.realiiz-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;min-height:40px;padding:5px 8px;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius)}
|
|
1077
|
+
.realiiz-chips:focus-within{border-color:var(--_accent);box-shadow:var(--_ring)}
|
|
1078
|
+
.realiiz-chip{display:inline-flex;align-items:center;gap:4px;padding:2px 4px 2px 9px;border:1px solid var(--_border);border-radius:999px;font-size:.8rem}
|
|
1079
|
+
.realiiz-chip button{font:inherit;line-height:1;width:18px;height:18px;border-radius:50%;border:0;background:transparent;color:var(--_muted);cursor:pointer}
|
|
1080
|
+
.realiiz-chips__input{flex:1;min-width:120px;border:0;background:transparent;font:inherit;color:inherit;outline:none;padding:4px}
|
|
1081
|
+
.realiiz-chips__count{margin-left:auto;color:var(--_muted);font-size:.75rem}
|
|
1082
|
+
.realiiz-gauge{display:inline-flex;align-items:center;gap:10px;font-size:.75rem;color:var(--_muted)}
|
|
1083
|
+
.realiiz-gauge__bar{position:relative;width:140px;height:6px;border-radius:3px;background:var(--_border);overflow:hidden}
|
|
1084
|
+
.realiiz-gauge__zone{position:absolute;top:0;bottom:0;background:rgba(21,128,61,.28)}
|
|
1085
|
+
.realiiz-gauge__fill{position:absolute;top:0;bottom:0;left:0;background:var(--_muted);border-radius:3px;transition:width .12s}
|
|
1086
|
+
.realiiz-gauge__text b{font-weight:600;color:inherit}
|
|
1087
|
+
.realiiz-gauge--ok{color:#15803d}.realiiz-gauge--ok .realiiz-gauge__fill{background:#15803d}
|
|
1088
|
+
.realiiz-gauge--over{color:var(--_danger)}.realiiz-gauge--over .realiiz-gauge__fill{background:var(--_danger)}
|
|
430
1089
|
`;
|
|
431
1090
|
|
|
432
1091
|
// src/forms-ui/values.ts
|
|
@@ -436,10 +1095,13 @@ function toControl(spec, raw) {
|
|
|
436
1095
|
switch (spec.input) {
|
|
437
1096
|
case "boolean":
|
|
438
1097
|
return Boolean(raw);
|
|
439
|
-
case "date":
|
|
440
|
-
|
|
1098
|
+
case "date": {
|
|
1099
|
+
if (raw instanceof Date) return raw.toISOString().slice(0, 10);
|
|
1100
|
+
const s = String(raw);
|
|
1101
|
+
return /^\d{4}-\d{2}-\d{2}/.test(s) ? s.slice(0, 10) : s;
|
|
1102
|
+
}
|
|
441
1103
|
case "tags":
|
|
442
|
-
return Array.isArray(raw) ? raw.
|
|
1104
|
+
return Array.isArray(raw) ? raw.map(String) : String(raw).split(",").map((t) => t.trim()).filter(Boolean);
|
|
443
1105
|
default:
|
|
444
1106
|
return String(raw);
|
|
445
1107
|
}
|
|
@@ -460,6 +1122,10 @@ function toFrontmatter(specs, state, hiddenValues) {
|
|
|
460
1122
|
frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;
|
|
461
1123
|
continue;
|
|
462
1124
|
}
|
|
1125
|
+
if (Array.isArray(v)) {
|
|
1126
|
+
if (v.length || spec.required) frontmatter[spec.name] = v;
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
463
1129
|
const s = typeof v === "string" ? v.trim() : "";
|
|
464
1130
|
if (s === "") {
|
|
465
1131
|
if (spec.required) frontmatter[spec.name] = spec.input === "tags" ? [] : "";
|
|
@@ -480,13 +1146,39 @@ function toFrontmatter(specs, state, hiddenValues) {
|
|
|
480
1146
|
}
|
|
481
1147
|
return { frontmatter, files };
|
|
482
1148
|
}
|
|
483
|
-
|
|
1149
|
+
var LEAVE_MESSAGE = "You have unsaved changes. Leave this page and lose them?";
|
|
1150
|
+
function slugify(s) {
|
|
1151
|
+
return s.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1152
|
+
}
|
|
1153
|
+
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content", renderPreview, previewClassName, termComponent, stickyBar = false, status, barExtra, footer, renderLabel, derive, guardUnsaved = true }) {
|
|
484
1154
|
const formId = react.useId();
|
|
485
1155
|
const specs = react.useMemo(() => walkSchema(entry.schema, entry), [entry]);
|
|
486
1156
|
const visible = react.useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
|
|
487
1157
|
const bodySpec = specs.find((s) => s.role === "body");
|
|
488
|
-
const
|
|
1158
|
+
const sections = react.useMemo(() => {
|
|
1159
|
+
const groups = entry.groups ?? [];
|
|
1160
|
+
const grouped = new Set(groups.flatMap((g) => g.fields));
|
|
1161
|
+
const byName = new Map(visible.map((s) => [s.name, s]));
|
|
1162
|
+
const main = { label: entry.singular ?? entry.label, fields: visible.filter((s) => !grouped.has(s.name)) };
|
|
1163
|
+
const rest = groups.map((g) => ({ label: g.label, fields: g.fields.map((n) => byName.get(n)).filter((s) => Boolean(s)) })).filter((g) => g.fields.length > 0);
|
|
1164
|
+
return [main, ...rest];
|
|
1165
|
+
}, [visible, entry.groups, entry.label, entry.singular]);
|
|
1166
|
+
const initialState = react.useMemo(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])), [specs, initialValues]);
|
|
1167
|
+
const [state, setState] = react.useState(initialState);
|
|
1168
|
+
const [deriving, setDeriving] = react.useState(() => Boolean(derive && !initialState[derive.target]));
|
|
1169
|
+
const update = (name, v) => setState((st) => {
|
|
1170
|
+
const next = { ...st, [name]: v };
|
|
1171
|
+
if (derive && deriving && name === derive.source && typeof v === "string") {
|
|
1172
|
+
next[derive.target] = (derive.transform ?? slugify)(v);
|
|
1173
|
+
}
|
|
1174
|
+
return next;
|
|
1175
|
+
});
|
|
1176
|
+
const onFieldChange = (name, v) => {
|
|
1177
|
+
if (derive && deriving && name === derive.target) setDeriving(false);
|
|
1178
|
+
update(name, v);
|
|
1179
|
+
};
|
|
489
1180
|
const [body, setBody] = react.useState(initialBody);
|
|
1181
|
+
const dirty = body !== initialBody || specs.some((s) => !sameValue(state[s.name], initialState[s.name]));
|
|
490
1182
|
const [issues, setIssues] = react.useState([]);
|
|
491
1183
|
const [attempted, setAttempted] = react.useState(false);
|
|
492
1184
|
const [submitting, setSubmitting] = react.useState(false);
|
|
@@ -509,6 +1201,31 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
509
1201
|
live = false;
|
|
510
1202
|
};
|
|
511
1203
|
}, [visible]);
|
|
1204
|
+
const leaving = react.useRef(false);
|
|
1205
|
+
react.useEffect(() => {
|
|
1206
|
+
if (!guardUnsaved || !dirty || submitting) return;
|
|
1207
|
+
const onUnload = (e) => {
|
|
1208
|
+
if (!leaving.current) {
|
|
1209
|
+
e.preventDefault();
|
|
1210
|
+
e.returnValue = LEAVE_MESSAGE;
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
const onClick = (e) => {
|
|
1214
|
+
const a = e.target?.closest?.("a[href]");
|
|
1215
|
+
if (!a || a.target === "_blank" || e.defaultPrevented || e.metaKey || e.ctrlKey) return;
|
|
1216
|
+
if (a.origin !== window.location.origin) return;
|
|
1217
|
+
if (!window.confirm(LEAVE_MESSAGE)) {
|
|
1218
|
+
e.preventDefault();
|
|
1219
|
+
e.stopPropagation();
|
|
1220
|
+
} else leaving.current = true;
|
|
1221
|
+
};
|
|
1222
|
+
window.addEventListener("beforeunload", onUnload);
|
|
1223
|
+
document.addEventListener("click", onClick, true);
|
|
1224
|
+
return () => {
|
|
1225
|
+
window.removeEventListener("beforeunload", onUnload);
|
|
1226
|
+
document.removeEventListener("click", onClick, true);
|
|
1227
|
+
};
|
|
1228
|
+
}, [guardUnsaved, dirty, submitting]);
|
|
512
1229
|
const validate = react.useCallback(() => {
|
|
513
1230
|
const { frontmatter } = toFrontmatter(specs, state, hiddenValues);
|
|
514
1231
|
const result = entry.schema.safeParse(frontmatter);
|
|
@@ -537,9 +1254,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
537
1254
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: "This can't be saved yet." }),
|
|
538
1255
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { children: formLevel.map((i, n) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: i.message }, n)) })
|
|
539
1256
|
] }),
|
|
540
|
-
/* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
541
|
-
/* @__PURE__ */ jsxRuntime.jsx("legend", { children:
|
|
542
|
-
|
|
1257
|
+
sections.map((sec) => /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
1258
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: sec.label }),
|
|
1259
|
+
sec.fields.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
543
1260
|
Field,
|
|
544
1261
|
{
|
|
545
1262
|
formId,
|
|
@@ -548,37 +1265,51 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
|
|
|
548
1265
|
issues: byField.get(s.name) ?? [],
|
|
549
1266
|
current: typeof initialValues[s.name] === "string" ? initialValues[s.name] : void 0,
|
|
550
1267
|
options: optionsFor(s),
|
|
551
|
-
|
|
1268
|
+
renderLabel,
|
|
1269
|
+
onChange: (v) => onFieldChange(s.name, v),
|
|
552
1270
|
onBlur: () => {
|
|
553
1271
|
if (attempted) validate();
|
|
554
1272
|
}
|
|
555
1273
|
},
|
|
556
1274
|
s.name
|
|
557
1275
|
))
|
|
558
|
-
] }),
|
|
1276
|
+
] }, sec.label)),
|
|
559
1277
|
(showBody || bodySpec) && /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
560
1278
|
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: bodySpec?.label ?? bodyLabel }),
|
|
561
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
|
|
1279
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "realiiz-form__label realiiz-sr-only", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
|
|
562
1280
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
563
|
-
|
|
1281
|
+
MarkdownEditor,
|
|
564
1282
|
{
|
|
565
1283
|
id: `${formId}-body`,
|
|
566
|
-
name: "body",
|
|
567
|
-
className: "realiiz-form__control realiiz-form__control--body",
|
|
568
1284
|
value: body,
|
|
1285
|
+
onChange: setBody,
|
|
569
1286
|
placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
|
|
570
|
-
|
|
1287
|
+
renderPreview,
|
|
1288
|
+
previewClassName,
|
|
1289
|
+
termComponent
|
|
571
1290
|
}
|
|
572
1291
|
)
|
|
573
1292
|
] }),
|
|
574
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1293
|
+
footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__footer", children: footer }),
|
|
1294
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: stickyBar ? "realiiz-form__bar" : "realiiz-form__actions", "data-dirty": dirty || void 0, children: [
|
|
1295
|
+
stickyBar && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__status", role: "status", children: status ?? (dirty ? "Unsaved changes" : "No changes yet") }),
|
|
1296
|
+
stickyBar && barExtra,
|
|
575
1297
|
onCancel && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "realiiz-form__button", onClick: onCancel, children: "Cancel" }),
|
|
576
1298
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "realiiz-form__button realiiz-form__button--primary", disabled: submitting, children: submitting ? `${submitLabel}\u2026` : submitLabel })
|
|
577
1299
|
] })
|
|
578
1300
|
] });
|
|
579
1301
|
}
|
|
1302
|
+
function sameValue(a, b) {
|
|
1303
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
1304
|
+
return a === b;
|
|
1305
|
+
}
|
|
580
1306
|
|
|
1307
|
+
exports.ChipsInput = ChipsInput;
|
|
581
1308
|
exports.ContentForm = ContentForm;
|
|
1309
|
+
exports.LengthGauge = LengthGauge;
|
|
1310
|
+
exports.MarkdownEditor = MarkdownEditor;
|
|
582
1311
|
exports.PENDING_UPLOAD_PREFIX = PENDING_UPLOAD_PREFIX;
|
|
1312
|
+
exports.TOOLS = TOOLS;
|
|
1313
|
+
exports.slugify = slugify;
|
|
583
1314
|
//# sourceMappingURL=index.cjs.map
|
|
584
1315
|
//# sourceMappingURL=index.cjs.map
|