@threadlabs/looma 0.27.6 → 0.28.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.
@@ -9,6 +9,7 @@ import { gapCursor } from "@tiptap/pm/gapcursor";
9
9
  import { createLowlight } from "lowlight";
10
10
  import { Mapping } from "@tiptap/pm/transform";
11
11
  import MarkdownIt from "markdown-it";
12
+ import { MarkdownSerializer, defaultMarkdownSerializer } from "@tiptap/pm/markdown";
12
13
  import { CellSelection, TableMap, addColumn, addColumnAfter, addColumnBefore, addRow, addRowAfter, addRowBefore, cellAround, columnResizing, deleteCellSelection, deleteColumn, deleteRow, deleteTable, fixTables, goToNextCell, mergeCells, moveTableColumn, moveTableRow, setCellAttr, splitCell, tableEditing, toggleHeader, toggleHeaderCell } from "@tiptap/pm/tables";
13
14
  //#region src/editor/extensions/active-block.ts
14
15
  /**
@@ -127,20 +128,34 @@ var LoomaActiveBlock = Extension.create({
127
128
  });
128
129
  //#endregion
129
130
  //#region src/editor/extensions/callout.ts
130
- /** Canonical tone values that may be persisted in a Looma callout node. */
131
+ /**
132
+ * Canonical tone values that may be persisted in a Looma callout node: the shared block
133
+ * vocabulary of Callout, Markdown `:::` containers and GitHub alerts.
134
+ */
131
135
  var LOOMA_CALLOUT_TONES = [
132
- "info",
133
136
  "note",
134
- "warning"
137
+ "tip",
138
+ "info",
139
+ "important",
140
+ "warning",
141
+ "danger"
135
142
  ];
136
- function normalizeCalloutTone(value) {
137
- return typeof value === "string" && LOOMA_CALLOUT_TONES.includes(value) ? value : "note";
143
+ var TONE_ALIASES = {
144
+ caution: "danger",
145
+ neutral: "note"
146
+ };
147
+ /** Maps a tone name, alias, or unknown value onto a persisted tone. */
148
+ function normalizeLoomaCalloutTone(value) {
149
+ if (typeof value !== "string") return "note";
150
+ const tone = value.toLowerCase();
151
+ return LOOMA_CALLOUT_TONES.includes(tone) ? tone : TONE_ALIASES[tone] ?? "note";
138
152
  }
139
153
  /**
140
- * A durable, themeable block container for informational editor content.
154
+ * A durable block container for informational editor content, saved as Looma's Callout.
141
155
  *
142
- * @invariant Parsed and rendered `data-tone` values always belong to
143
- * `LOOMA_CALLOUT_TONES`; missing or unknown values round-trip as `note`.
156
+ * @invariant Rendered `tone` values always belong to `LOOMA_CALLOUT_TONES`; missing or
157
+ * unknown values round-trip as `note`. Content saved as the older
158
+ * `aside[data-looma-callout][data-tone]` still parses.
144
159
  */
145
160
  var LoomaCallout = Node$1.create({
146
161
  name: "loomaCallout",
@@ -148,25 +163,32 @@ var LoomaCallout = Node$1.create({
148
163
  content: "block+",
149
164
  defining: true,
150
165
  addAttributes() {
151
- return { tone: {
152
- default: "note",
153
- parseHTML: (element) => normalizeCalloutTone(element.getAttribute("data-tone")),
154
- renderHTML: (attributes) => ({ "data-tone": normalizeCalloutTone(attributes.tone) })
155
- } };
166
+ return {
167
+ tone: {
168
+ default: "note",
169
+ parseHTML: (element) => normalizeLoomaCalloutTone(element.getAttribute("tone") ?? element.getAttribute("data-tone")),
170
+ renderHTML: (attributes) => ({ tone: normalizeLoomaCalloutTone(attributes.tone) })
171
+ },
172
+ heading: {
173
+ default: "",
174
+ parseHTML: (element) => element.getAttribute("heading") ?? "",
175
+ renderHTML: (attributes) => attributes.heading ? { heading: String(attributes.heading) } : {}
176
+ }
177
+ };
156
178
  },
157
179
  parseHTML() {
158
- return [{ tag: "aside[data-looma-callout]" }];
180
+ return [{ tag: "ui-callout" }, { tag: "aside[data-looma-callout]" }];
159
181
  },
160
182
  renderHTML({ HTMLAttributes }) {
161
183
  return [
162
- "aside",
163
- mergeAttributes(HTMLAttributes, { "data-looma-callout": "" }),
184
+ "ui-callout",
185
+ HTMLAttributes,
164
186
  0
165
187
  ];
166
188
  },
167
189
  addCommands() {
168
190
  return { setLoomaCallout: (tone = "note") => ({ state, commands }) => {
169
- const normalizedTone = normalizeCalloutTone(tone);
191
+ const normalizedTone = normalizeLoomaCalloutTone(tone);
170
192
  for (let depth = state.selection.$from.depth; depth > 0; depth -= 1) if (state.selection.$from.node(depth).type.name === this.name) return commands.updateAttributes(this.name, { tone: normalizedTone });
171
193
  return commands.wrapIn(this.name, { tone: normalizedTone });
172
194
  } };
@@ -2475,7 +2497,7 @@ function decodeTlds(encoded) {
2475
2497
  /**
2476
2498
  * @type Required<Opts>
2477
2499
  */
2478
- var defaults = {
2500
+ var defaults$1 = {
2479
2501
  defaultProtocol: "http",
2480
2502
  events: null,
2481
2503
  format: noop,
@@ -2502,7 +2524,7 @@ var defaults = {
2502
2524
  * Similar to render option
2503
2525
  */
2504
2526
  function Options(opts, defaultRender = null) {
2505
- let o = Object.assign({}, defaults);
2527
+ let o = Object.assign({}, defaults$1);
2506
2528
  if (opts) o = Object.assign(o, opts instanceof Options ? opts.o : opts);
2507
2529
  const ignoredTags = o.ignoreTags;
2508
2530
  const uppercaseIgnoredTags = [];
@@ -2513,7 +2535,7 @@ function Options(opts, defaultRender = null) {
2513
2535
  this.ignoreTags = uppercaseIgnoredTags;
2514
2536
  }
2515
2537
  Options.prototype = {
2516
- o: defaults,
2538
+ o: defaults$1,
2517
2539
  /**
2518
2540
  * @type string[]
2519
2541
  */
@@ -2550,7 +2572,7 @@ Options.prototype = {
2550
2572
  let option = this.o[key];
2551
2573
  if (!option) return option;
2552
2574
  if (typeof option === "object") {
2553
- option = token.t in option ? option[token.t] : defaults[key];
2575
+ option = token.t in option ? option[token.t] : defaults$1[key];
2554
2576
  if (typeof option === "function" && isCallable) option = option(operator, token);
2555
2577
  } else if (typeof option === "function" && isCallable) option = option(operator, token.t, token);
2556
2578
  return option;
@@ -2668,7 +2690,7 @@ MultiToken.prototype = {
2668
2690
  @method toObject
2669
2691
  @param {string} [protocol] `'http'` by default
2670
2692
  */
2671
- toObject(protocol = defaults.defaultProtocol) {
2693
+ toObject(protocol = defaults$1.defaultProtocol) {
2672
2694
  return {
2673
2695
  type: this.t,
2674
2696
  value: this.toString(),
@@ -2777,7 +2799,7 @@ var Url = createTokenClass("url", {
2777
2799
  @param {string} [scheme] default scheme (e.g., 'https')
2778
2800
  @return {string} the full href
2779
2801
  */
2780
- toHref(scheme = defaults.defaultProtocol) {
2802
+ toHref(scheme = defaults$1.defaultProtocol) {
2781
2803
  return this.hasProtocol() ? this.v : `${scheme}://${this.v}`;
2782
2804
  },
2783
2805
  /**
@@ -9143,15 +9165,213 @@ function createDOMPurify() {
9143
9165
  }
9144
9166
  var purify = createDOMPurify();
9145
9167
  //#endregion
9168
+ //#region src/editor/extensions/markdown.ts
9169
+ var CONTAINER_NAMES = /* @__PURE__ */ new Set([
9170
+ "note",
9171
+ "tip",
9172
+ "info",
9173
+ "important",
9174
+ "warning",
9175
+ "danger",
9176
+ "caution",
9177
+ "details"
9178
+ ]);
9179
+ var CONTAINER_OPEN = /^(:{3,})[ \t]*([A-Za-z]+)(?:[ \t]+(.*?))?[ \t]*$/;
9180
+ var CONTAINER_CLOSE = /^:{3,}[ \t]*$/;
9181
+ var OPEN_FLAG = /[ \t]*\{open\}$/;
9182
+ var ALERT = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\][ \t]*(?:\n|$)/i;
9183
+ function lineText(state, line) {
9184
+ return state.src.slice(state.bMarks[line] + state.tShift[line], state.eMarks[line]);
9185
+ }
9186
+ function container(state, startLine, endLine, silent) {
9187
+ if (state.sCount[startLine] - state.blkIndent >= 4) return false;
9188
+ const match = CONTAINER_OPEN.exec(lineText(state, startLine));
9189
+ if (!match || !CONTAINER_NAMES.has(match[2].toLowerCase())) return false;
9190
+ if (silent) return true;
9191
+ let depth = 1;
9192
+ let next = startLine + 1;
9193
+ for (; next < endLine; next++) {
9194
+ if (state.sCount[next] < state.blkIndent) break;
9195
+ const text = lineText(state, next);
9196
+ if (CONTAINER_CLOSE.test(text)) {
9197
+ if (--depth === 0) break;
9198
+ } else if (CONTAINER_OPEN.test(text) && CONTAINER_NAMES.has(CONTAINER_OPEN.exec(text)[2].toLowerCase())) depth++;
9199
+ }
9200
+ const name = match[2].toLowerCase();
9201
+ let title = (match[3] ?? "").trim();
9202
+ const open = OPEN_FLAG.test(title);
9203
+ title = title.replace(OPEN_FLAG, "");
9204
+ const parentType = state.parentType;
9205
+ const lineMax = state.lineMax;
9206
+ state.parentType = "container";
9207
+ state.lineMax = next;
9208
+ const opening = state.push("looma_container_open", "", 1);
9209
+ Object.assign(opening, {
9210
+ info: name,
9211
+ meta: {
9212
+ title,
9213
+ open
9214
+ },
9215
+ block: true,
9216
+ map: [startLine, next],
9217
+ markup: match[1]
9218
+ });
9219
+ state.md.block.tokenize(state, startLine + 1, next);
9220
+ const closing = state.push("looma_container_close", "", -1);
9221
+ Object.assign(closing, {
9222
+ info: name,
9223
+ block: true,
9224
+ markup: match[1]
9225
+ });
9226
+ state.parentType = parentType;
9227
+ state.lineMax = lineMax;
9228
+ state.line = Math.min(next + 1, endLine);
9229
+ return true;
9230
+ }
9231
+ function alerts(state) {
9232
+ const tokens = state.tokens;
9233
+ for (let index = 0; index < tokens.length; index++) {
9234
+ if (tokens[index].type !== "blockquote_open" || tokens[index + 1]?.type !== "paragraph_open") continue;
9235
+ const inline = tokens[index + 2];
9236
+ const match = inline?.type === "inline" ? ALERT.exec(inline.content) : null;
9237
+ if (!match) continue;
9238
+ inline.content = inline.content.slice(match[0].length);
9239
+ if (!inline.content.trim()) tokens.splice(index + 1, 3);
9240
+ tokens[index].tag = "ui-callout";
9241
+ tokens[index].attrSet("tone", normalizeLoomaCalloutTone(match[1]));
9242
+ for (let depth = 0, end = index; end < tokens.length; end++) if (tokens[end].type === "blockquote_open") depth++;
9243
+ else if (tokens[end].type === "blockquote_close" && --depth === 0) {
9244
+ tokens[end].tag = "ui-callout";
9245
+ break;
9246
+ }
9247
+ }
9248
+ }
9249
+ function withSharedBlocks(md) {
9250
+ const escape = md.utils.escapeHtml;
9251
+ md.block.ruler.before("fence", "looma_container", container, { alt: [
9252
+ "paragraph",
9253
+ "reference",
9254
+ "blockquote",
9255
+ "list"
9256
+ ] });
9257
+ md.core.ruler.before("inline", "looma_alerts", alerts);
9258
+ md.renderer.rules.looma_container_open = (tokens, index) => {
9259
+ const { info, meta } = tokens[index];
9260
+ if (info === "details") return `<details${meta.open ? " open" : ""}><summary>${escape(meta.title || "Details")}</summary>\n`;
9261
+ return `<ui-callout tone="${normalizeLoomaCalloutTone(info)}"${meta.title ? ` heading="${escape(meta.title)}"` : ""}>\n`;
9262
+ };
9263
+ md.renderer.rules.looma_container_close = (tokens, index) => tokens[index].info === "details" ? "</details>\n" : "</ui-callout>\n";
9264
+ return md;
9265
+ }
9266
+ /** The markdown-it instance LoomaEditor imports Markdown with, shared block vocabulary included. */
9267
+ var loomaMarkdown = withSharedBlocks(new MarkdownIt({
9268
+ html: true,
9269
+ linkify: false,
9270
+ typographer: false
9271
+ }));
9272
+ /**
9273
+ * Renders Markdown, including the shared block vocabulary, as HTML the editor parses.
9274
+ * @contract The result is not sanitized; pass it to the editor's `setContent`, whose schema
9275
+ * keeps only content it knows.
9276
+ */
9277
+ function loomaMarkdownToHtml(source) {
9278
+ return loomaMarkdown.render(source).replace(/\n<\/code><\/pre>/g, "</code></pre>");
9279
+ }
9280
+ var containerDepth = (node) => {
9281
+ let depth = 0;
9282
+ node.descendants((child) => {
9283
+ if (child.type.name === "loomaCallout" || child.type.name === "loomaExpand") depth = Math.max(depth, 1 + containerDepth(child));
9284
+ return false;
9285
+ });
9286
+ return depth;
9287
+ };
9288
+ function writeContainer(state, node, opening) {
9289
+ const marker = ":".repeat(3 + containerDepth(node));
9290
+ state.write(`${marker} ${opening}`.trimEnd());
9291
+ state.ensureNewLine();
9292
+ state.renderContent(node);
9293
+ state.write(marker);
9294
+ state.closeBlock(node);
9295
+ }
9296
+ var defaults = defaultMarkdownSerializer.nodes;
9297
+ var NODES = {
9298
+ paragraph: defaults.paragraph,
9299
+ text: defaults.text,
9300
+ blockquote: defaults.blockquote,
9301
+ heading: defaults.heading,
9302
+ horizontalRule: defaults.horizontal_rule,
9303
+ bulletList: defaults.bullet_list,
9304
+ orderedList: (state, node) => {
9305
+ const start = Number(node.attrs.start ?? 1);
9306
+ state.renderList(node, " ", (index) => `${start + index}. `);
9307
+ },
9308
+ listItem: defaults.list_item,
9309
+ hardBreak: defaults.hard_break,
9310
+ codeBlock: (state, node) => {
9311
+ const longest = Math.max(0, ...(node.textContent.match(/`+/g) ?? []).map((run) => run.length));
9312
+ const fence = "`".repeat(Math.max(3, longest + 1));
9313
+ state.write(`${fence}${node.attrs.language ?? ""}\n`);
9314
+ state.text(node.textContent, false);
9315
+ state.ensureNewLine();
9316
+ state.write(fence);
9317
+ state.closeBlock(node);
9318
+ },
9319
+ loomaCallout: (state, node) => writeContainer(state, node, `${node.attrs.tone} ${node.attrs.heading ?? ""}`),
9320
+ loomaExpand: (state, node) => writeContainer(state, node, `details ${node.attrs.summary}${node.attrs.open ? " {open}" : ""}`)
9321
+ };
9322
+ var MARKS = {
9323
+ bold: defaultMarkdownSerializer.marks.strong,
9324
+ italic: defaultMarkdownSerializer.marks.em,
9325
+ code: defaultMarkdownSerializer.marks.code,
9326
+ link: defaultMarkdownSerializer.marks.link,
9327
+ strike: {
9328
+ open: "~~",
9329
+ close: "~~",
9330
+ mixable: true,
9331
+ expelEnclosingWhitespace: true
9332
+ },
9333
+ underline: {
9334
+ open: "<u>",
9335
+ close: "</u>",
9336
+ mixable: true
9337
+ },
9338
+ highlight: {
9339
+ open: "<mark>",
9340
+ close: "</mark>",
9341
+ mixable: true
9342
+ }
9343
+ };
9344
+ /**
9345
+ * Writes an editor document as Markdown, with callouts and expand sections in the shared
9346
+ * `:::` syntax that `loomaMarkdownToHtml` and Markupress read.
9347
+ * @contract Content Markdown has no syntax for (tables, task lists, images, chips, mentions,
9348
+ * the table of contents) is written as its saved HTML, which Markdown carries unchanged.
9349
+ * Marks without a Markdown form other than underline and highlight are dropped.
9350
+ */
9351
+ function loomaMarkdownFromDoc(doc) {
9352
+ const dom = DOMSerializer.fromSchema(doc.type.schema);
9353
+ const html = (node) => {
9354
+ const holder = document.createElement("div");
9355
+ holder.append(dom.serializeNode(node));
9356
+ return holder.innerHTML;
9357
+ };
9358
+ const nodes = {};
9359
+ for (const name of Object.keys(doc.type.schema.nodes)) nodes[name] = NODES[name] ?? ((state, node) => {
9360
+ state.write(html(node));
9361
+ if (node.isBlock) state.closeBlock(node);
9362
+ });
9363
+ const marks = {};
9364
+ for (const name of Object.keys(doc.type.schema.marks)) marks[name] = MARKS[name] ?? {
9365
+ open: "",
9366
+ close: ""
9367
+ };
9368
+ return new MarkdownSerializer(nodes, marks).serialize(doc, { tightLists: true });
9369
+ }
9370
+ //#endregion
9146
9371
  //#region src/editor/extensions/smart-paste.ts
9147
9372
  var HTML_DOCUMENT_PATTERN = /<!doctype\s+html\b|<(?:html|body)\b[^>]*>/i;
9148
9373
  var HTML_PAIRED_TAG_PATTERN = /<(h[1-6]|p|ul|ol|li|blockquote|table|thead|tbody|tfoot|tr|th|td|figure|figcaption|article|section|header|footer|main|div|span|strong|em|b|i|a|code|pre)\b[^>]*>[\s\S]*?<\/\1\s*>/i;
9149
9374
  var HTML_VOID_TAG_PATTERN = /<(?:img|hr|br)\b[^>]*\/?\s*>/i;
9150
- var markdown = new MarkdownIt({
9151
- html: true,
9152
- linkify: false,
9153
- typographer: false
9154
- });
9155
9375
  function selectionIsInsideCodeBlock(parentTypeName) {
9156
9376
  return parentTypeName === "codeBlock";
9157
9377
  }
@@ -9162,7 +9382,7 @@ function looksLikeDocumentHtml(value) {
9162
9382
  return !/[={};`"']|=>|\b(?:const|let|var|return|function|class|import|export)\b/.test(prefix);
9163
9383
  }
9164
9384
  function looksLikeMarkdown(value) {
9165
- const tokens = markdown.parse(value, {});
9385
+ const tokens = loomaMarkdown.parse(value, {});
9166
9386
  return tokens.some((token, index) => {
9167
9387
  if (token.type === "heading_open") return Boolean(tokens[index + 1]?.content.trim());
9168
9388
  if ([
@@ -9170,7 +9390,8 @@ function looksLikeMarkdown(value) {
9170
9390
  "ordered_list_open",
9171
9391
  "blockquote_open",
9172
9392
  "fence",
9173
- "table_open"
9393
+ "table_open",
9394
+ "looma_container_open"
9174
9395
  ].includes(token.type)) return true;
9175
9396
  return token.type === "inline" && Boolean(token.children?.some((child) => [
9176
9397
  "link_open",
@@ -9218,6 +9439,8 @@ function parseHtmlSlice(schema, value) {
9218
9439
  "template"
9219
9440
  ],
9220
9441
  FORBID_ATTR: ["style"],
9442
+ ADD_TAGS: ["ui-callout"],
9443
+ ADD_ATTR: ["tone", "heading"],
9221
9444
  RETURN_DOM_FRAGMENT: true
9222
9445
  }));
9223
9446
  if (!container.textContent?.trim() && !container.querySelector("img, hr")) return Slice.empty;
@@ -9236,7 +9459,7 @@ function escapeHtmlText(value) {
9236
9459
  }
9237
9460
  function documentSlice(schema, plainText) {
9238
9461
  const frontmatter = markdownFrontmatter(plainText);
9239
- if (frontmatter || looksLikeMarkdown(plainText)) return parseHtmlSlice(schema, frontmatter ? `<pre><code class="language-yaml">${escapeHtmlText(frontmatter.source)}</code></pre>${markdown.render(frontmatter.body)}` : markdown.render(plainText));
9462
+ if (frontmatter || looksLikeMarkdown(plainText)) return parseHtmlSlice(schema, frontmatter ? `<pre><code class="language-yaml">${escapeHtmlText(frontmatter.source)}</code></pre>${loomaMarkdown.render(frontmatter.body)}` : loomaMarkdown.render(plainText));
9240
9463
  if (looksLikeDocumentHtml(plainText)) return parseHtmlSlice(schema, plainText);
9241
9464
  return null;
9242
9465
  }
@@ -9838,7 +10061,7 @@ function getLoomaTableExtensions() {
9838
10061
  * the code and explicit language stay in the document schema.
9839
10062
  */
9840
10063
  function getDefaultEditorExtensions(options = {}) {
9841
- const { placeholder = "Type “/” for commands, or start writing…", linkOpenOnClick = false, linkBaseUrl, imageInline = false, imageNodeView, dividerNodeView, mention = createLoomaMentionExtension(), disableHighlight = false, codeLanguages, codeLowlight, codeBlockNodeView, tableOfContentsNodeView, expandNodeView } = options;
10064
+ const { placeholder = "Type “/” for commands, or start writing…", linkOpenOnClick = false, linkBaseUrl, imageInline = false, imageNodeView, dividerNodeView, mention = createLoomaMentionExtension(), disableHighlight = false, codeLanguages, codeLowlight, codeBlockNodeView, tableOfContentsNodeView, expandNodeView, calloutNodeView } = options;
9842
10065
  const image = LoomaImage.configure({ inline: imageInline });
9843
10066
  const codeBlock = LoomaCodeBlock.configure({ lowlight: codeLowlight ?? createLowlight(codeLanguages) });
9844
10067
  return [
@@ -9860,7 +10083,7 @@ function getDefaultEditorExtensions(options = {}) {
9860
10083
  TaskList,
9861
10084
  TaskItem.configure({ nested: false }),
9862
10085
  Blockquote,
9863
- LoomaCallout,
10086
+ calloutNodeView ? LoomaCallout.extend({ addNodeView: () => calloutNodeView }) : LoomaCallout,
9864
10087
  LoomaChip,
9865
10088
  LoomaExpand.configure({ nodeView: expandNodeView ?? null }),
9866
10089
  LoomaTableOfContents.configure({ nodeView: tableOfContentsNodeView ?? null }),
@@ -10307,27 +10530,65 @@ var CALLOUT_COMMANDS = [
10307
10530
  },
10308
10531
  {
10309
10532
  title: "Note",
10310
- description: "Highlighted note",
10533
+ description: "Muted aside",
10311
10534
  tone: "note",
10312
10535
  icon: "notebook-pen",
10313
10536
  keywords: [
10314
10537
  "note",
10538
+ "aside",
10539
+ "callout",
10540
+ "panel"
10541
+ ]
10542
+ },
10543
+ {
10544
+ title: "Tip",
10545
+ description: "Helpful advice",
10546
+ tone: "tip",
10547
+ icon: "lightbulb",
10548
+ keywords: [
10549
+ "tip",
10550
+ "hint",
10551
+ "advice",
10552
+ "callout",
10553
+ "panel"
10554
+ ]
10555
+ },
10556
+ {
10557
+ title: "Important",
10558
+ description: "Must-read information",
10559
+ tone: "important",
10560
+ icon: "message-square-warning",
10561
+ keywords: [
10562
+ "important",
10563
+ "key",
10315
10564
  "callout",
10316
10565
  "panel"
10317
10566
  ]
10318
10567
  },
10319
10568
  {
10320
10569
  title: "Warning",
10321
- description: "Important warning",
10570
+ description: "Proceed with care",
10322
10571
  tone: "warning",
10323
10572
  icon: "triangle-alert",
10324
10573
  keywords: [
10325
10574
  "warning",
10326
- "caution",
10327
10575
  "alert",
10328
10576
  "callout",
10329
10577
  "panel"
10330
10578
  ]
10579
+ },
10580
+ {
10581
+ title: "Danger",
10582
+ description: "Harmful consequence",
10583
+ tone: "danger",
10584
+ icon: "circle-x",
10585
+ keywords: [
10586
+ "danger",
10587
+ "caution",
10588
+ "error",
10589
+ "callout",
10590
+ "panel"
10591
+ ]
10331
10592
  }
10332
10593
  ];
10333
10594
  /**
@@ -10589,6 +10850,8 @@ function getDefaultSlashCommands(onOpenImagePicker, onOpenChipEditor, onOpenLink
10589
10850
  }
10590
10851
  function canRunDefaultCommand(title, { editor, range }) {
10591
10852
  const chain = editor.can().chain().deleteRange(range);
10853
+ const callout = CALLOUT_COMMANDS.find((command) => command.title === title);
10854
+ if (callout) return chain.setLoomaCallout(callout.tone).run();
10592
10855
  switch (title) {
10593
10856
  case "Text": return editor.isActive("paragraph") || chain.setParagraph().run();
10594
10857
  case "Heading 1": return editor.isActive("heading", { level: 1 }) || chain.setHeading({ level: 1 }).run();
@@ -10598,9 +10861,6 @@ function canRunDefaultCommand(title, { editor, range }) {
10598
10861
  case "Numbered list": return chain.toggleOrderedList().run();
10599
10862
  case "Checklist": return chain.toggleTaskList().run();
10600
10863
  case "Blockquote": return chain.toggleBlockquote().run();
10601
- case "Info": return chain.setLoomaCallout("info").run();
10602
- case "Note": return chain.setLoomaCallout("note").run();
10603
- case "Warning": return chain.setLoomaCallout("warning").run();
10604
10864
  case "Chip": return chain.insertLoomaChip({ label: "" }).run();
10605
10865
  case "Inline code": return chain.toggleCode().run();
10606
10866
  case "Code block": return chain.toggleCodeBlock().run();
@@ -10816,4 +11076,4 @@ function createLoomaSlashCommandExtension(options = {}) {
10816
11076
  });
10817
11077
  }
10818
11078
  //#endregion
10819
- export { LoomaImage as A, normalizeLoomaChipColor as B, siteRelativeHref as C, MAX_MENTION_RESULT_LIMIT as D, DEFAULT_MENTION_RESULT_LIMIT as E, normalizeTableOfContentsConfig as F, LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS as G, LoomaCallout as H, LoomaExpand as I, LoomaActiveBlock as K, revealLoomaExpandAt as L, normalizeImagePlacement as M, LoomaTableOfContents as N, filterLoomaMentionItems as O, getTableOfContentsEntries as P, LOOMA_CHIP_COLORS as R, createSiteRelativeLinks as S, createLoomaMentionExtension as T, LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS as U, LOOMA_CALLOUT_TONES as V, LOOMA_ACTIVE_BLOCK_CLASS as W, getActiveTableCellAlignment as _, getActiveTableUiState as a, setActiveTableCellBackground as b, insertTableAtRange as c, LoomaTableKit as d, getDefaultEditorExtensions as f, LoomaTableHeader as g, LoomaTableCell as h, getDefaultSlashCommands as i, normalizeImageDimension as j, normalizeMentionResultLimit as k, normalizeActiveTableColumnWidths as l, LoomaTable as m, createLoomaSlashCommandExtension as n, handleTableAction as o, getLoomaTableExtensions as p, LoomaActiveBlockPluginKey as q, filterLoomaSlashCommands as r, handleTableOverlayAction as s, LoomaSlashCommand as t, shouldShowTextFormattingToolbar as u, getActiveTableCellBackground as v, LoomaMentionSuggestionPluginKey as w, LoomaSmartPaste as x, setActiveTableCellAlignment as y, LoomaChip as z };
11079
+ export { filterLoomaMentionItems as A, LOOMA_CHIP_COLORS as B, loomaMarkdownToHtml as C, createLoomaMentionExtension as D, LoomaMentionSuggestionPluginKey as E, LoomaTableOfContents as F, LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS as G, normalizeLoomaChipColor as H, getTableOfContentsEntries as I, LoomaActiveBlock as J, LOOMA_ACTIVE_BLOCK_CLASS as K, normalizeTableOfContentsConfig as L, LoomaImage as M, normalizeImageDimension as N, DEFAULT_MENTION_RESULT_LIMIT as O, normalizeImagePlacement as P, LoomaExpand as R, loomaMarkdownFromDoc as S, siteRelativeHref as T, LOOMA_CALLOUT_TONES as U, LoomaChip as V, LoomaCallout as W, LoomaActiveBlockPluginKey as Y, getActiveTableCellAlignment as _, getActiveTableUiState as a, setActiveTableCellBackground as b, insertTableAtRange as c, LoomaTableKit as d, getDefaultEditorExtensions as f, LoomaTableHeader as g, LoomaTableCell as h, getDefaultSlashCommands as i, normalizeMentionResultLimit as j, MAX_MENTION_RESULT_LIMIT as k, normalizeActiveTableColumnWidths as l, LoomaTable as m, createLoomaSlashCommandExtension as n, handleTableAction as o, getLoomaTableExtensions as p, LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS as q, filterLoomaSlashCommands as r, handleTableOverlayAction as s, LoomaSlashCommand as t, shouldShowTextFormattingToolbar as u, getActiveTableCellBackground as v, createSiteRelativeLinks as w, LoomaSmartPaste as x, setActiveTableCellAlignment as y, revealLoomaExpandAt as z };
@@ -0,0 +1,25 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import { createLowlight } from "lowlight";
3
+ import type { LoomaCodeLanguages } from "./extensions/preset.js";
4
+ /** Code-block highlighting for one editor: its lowlight registry, picker languages, and grammar loading. */
5
+ export interface LoomaCodeHighlighting {
6
+ /** The registry the code-block extension highlights with. */
7
+ readonly lowlight: ReturnType<typeof createLowlight>;
8
+ /** Languages the picker offers, in display order. */
9
+ readonly languageNames: readonly string[];
10
+ /**
11
+ * Loads the grammar a code block needs, then re-highlights the document.
12
+ *
13
+ * @contract Does nothing when the host supplied its own languages; each grammar loads once.
14
+ * @failure A failed import is forgotten so a later call retries it.
15
+ */
16
+ loadForCodeBlock(editor: Editor, language: unknown, value: string): void;
17
+ }
18
+ /**
19
+ * Highlighting from the host's `codeLanguages`, or, when that is null, from the built-in catalog,
20
+ * whose grammars load on demand and whose automatic detection uses a common subset.
21
+ *
22
+ * @lifecycle `onGrammarsLoaded` runs after each lazy load, before the document is re-highlighted,
23
+ * because the lowlight registry is mutable rather than reactive.
24
+ */
25
+ export declare function createCodeHighlighting(codeLanguages: LoomaCodeLanguages | null, onGrammarsLoaded: () => void): LoomaCodeHighlighting;
@@ -0,0 +1,10 @@
1
+ import type { JSONContent } from "@tiptap/core";
2
+ /** Whether a controlled document already matches the editor, so applying it would only reset the selection. */
3
+ export declare function sameDocument(left: JSONContent, right: JSONContent): boolean;
4
+ /**
5
+ * A link destination the editor may store: a site-relative path, fragment, or query, or an
6
+ * absolute http, https, mailto, or tel URL.
7
+ *
8
+ * @failure Returns null for empty input, control characters, other schemes, and unparseable URLs.
9
+ */
10
+ export declare function validLinkHref(value: string): string | null;
@@ -1,6 +1,9 @@
1
1
  import { Node } from "@tiptap/core";
2
- /** Canonical tone values that may be persisted in a Looma callout node. */
3
- export declare const LOOMA_CALLOUT_TONES: readonly ["info", "note", "warning"];
2
+ /**
3
+ * Canonical tone values that may be persisted in a Looma callout node: the shared block
4
+ * vocabulary of Callout, Markdown `:::` containers and GitHub alerts.
5
+ */
6
+ export declare const LOOMA_CALLOUT_TONES: readonly ["note", "tip", "info", "important", "warning", "danger"];
4
7
  /** A serialized callout tone; unsupported input is normalized to `note`. */
5
8
  export type LoomaCalloutTone = (typeof LOOMA_CALLOUT_TONES)[number];
6
9
  declare module "@tiptap/core" {
@@ -11,10 +14,13 @@ declare module "@tiptap/core" {
11
14
  };
12
15
  }
13
16
  }
17
+ /** Maps a tone name, alias, or unknown value onto a persisted tone. */
18
+ export declare function normalizeLoomaCalloutTone(value: unknown): LoomaCalloutTone;
14
19
  /**
15
- * A durable, themeable block container for informational editor content.
20
+ * A durable block container for informational editor content, saved as Looma's Callout.
16
21
  *
17
- * @invariant Parsed and rendered `data-tone` values always belong to
18
- * `LOOMA_CALLOUT_TONES`; missing or unknown values round-trip as `note`.
22
+ * @invariant Rendered `tone` values always belong to `LOOMA_CALLOUT_TONES`; missing or
23
+ * unknown values round-trip as `note`. Content saved as the older
24
+ * `aside[data-looma-callout][data-tone]` still parses.
19
25
  */
20
26
  export declare const LoomaCallout: Node<any, any>;
@@ -13,6 +13,7 @@ export { LoomaExpand, revealLoomaExpandAt, type LoomaExpandAttributes } from "./
13
13
  export { LoomaTableOfContents, getTableOfContentsEntries, normalizeTableOfContentsConfig, type LoomaTableOfContentsConfig, type LoomaTableOfContentsEntry, type LoomaTableOfContentsFormat, } from "./table-of-contents.js";
14
14
  export { getDefaultEditorExtensions, getLoomaTableExtensions, LoomaTableKit, type DefaultEditorExtensionsOptions, type LoomaCodeLanguages, } from "./preset.js";
15
15
  export { LoomaSmartPaste } from "./smart-paste.js";
16
+ export { loomaMarkdownFromDoc, loomaMarkdownToHtml } from "./markdown.js";
16
17
  export { getActiveTableUiState, handleTableAction, handleTableOverlayAction, insertTableAtRange, normalizeActiveTableColumnWidths, shouldShowTextFormattingToolbar, type ActiveTableUiState, type InsertTableAtRangeOptions, type TableActionCapabilities, } from "./table-commands.js";
17
18
  export { LoomaTable, getActiveTableCellAlignment, getActiveTableCellBackground, LoomaTableCell, LoomaTableHeader, setActiveTableCellAlignment, setActiveTableCellBackground, TABLE_CELL_BACKGROUND_PRESETS, type TableCellAlignment, type TableCellBackground, } from "./table-formatting.js";
18
19
  export { createLoomaMentionExtension, DEFAULT_MENTION_RESULT_LIMIT, filterLoomaMentionItems, LoomaMentionSuggestionPluginKey, MAX_MENTION_RESULT_LIMIT, normalizeMentionResultLimit, type LoomaMentionItem, type LoomaMentionMenuSnapshot, type LoomaMentionOptions, type LoomaMentionProvider, type LoomaMentionProviderContext, } from "./mention.js";
@@ -1,3 +1,3 @@
1
1
  import { n as TABLE_CELL_BACKGROUND_PRESETS } from "../chunks/table-backgrounds.js";
2
- import { A as LoomaImage, B as normalizeLoomaChipColor, C as siteRelativeHref, D as MAX_MENTION_RESULT_LIMIT, E as DEFAULT_MENTION_RESULT_LIMIT, F as normalizeTableOfContentsConfig, G as LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS, H as LoomaCallout, I as LoomaExpand, K as LoomaActiveBlock, L as revealLoomaExpandAt, M as normalizeImagePlacement, N as LoomaTableOfContents, O as filterLoomaMentionItems, P as getTableOfContentsEntries, R as LOOMA_CHIP_COLORS, S as createSiteRelativeLinks, T as createLoomaMentionExtension, U as LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS, V as LOOMA_CALLOUT_TONES, W as LOOMA_ACTIVE_BLOCK_CLASS, _ as getActiveTableCellAlignment, a as getActiveTableUiState, b as setActiveTableCellBackground, c as insertTableAtRange, d as LoomaTableKit, f as getDefaultEditorExtensions, g as LoomaTableHeader, h as LoomaTableCell, i as getDefaultSlashCommands, j as normalizeImageDimension, k as normalizeMentionResultLimit, l as normalizeActiveTableColumnWidths, m as LoomaTable, n as createLoomaSlashCommandExtension, o as handleTableAction, p as getLoomaTableExtensions, q as LoomaActiveBlockPluginKey, r as filterLoomaSlashCommands, s as handleTableOverlayAction, t as LoomaSlashCommand, u as shouldShowTextFormattingToolbar, v as getActiveTableCellBackground, w as LoomaMentionSuggestionPluginKey, x as LoomaSmartPaste, y as setActiveTableCellAlignment, z as LoomaChip } from "../chunks/extensions.js";
3
- export { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS, LOOMA_ACTIVE_BLOCK_CLASS, LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS, LOOMA_CALLOUT_TONES, LOOMA_CHIP_COLORS, LoomaActiveBlock, LoomaActiveBlockPluginKey, LoomaCallout, LoomaChip, LoomaExpand, LoomaImage, LoomaMentionSuggestionPluginKey, LoomaSlashCommand, LoomaSmartPaste, LoomaTable, LoomaTableCell, LoomaTableHeader, LoomaTableKit, LoomaTableOfContents, MAX_MENTION_RESULT_LIMIT, TABLE_CELL_BACKGROUND_PRESETS, createLoomaMentionExtension, createLoomaSlashCommandExtension, createSiteRelativeLinks, filterLoomaMentionItems, filterLoomaSlashCommands, getActiveTableCellAlignment, getActiveTableCellBackground, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getLoomaTableExtensions, getTableOfContentsEntries, handleTableAction, handleTableOverlayAction, insertTableAtRange, normalizeActiveTableColumnWidths, normalizeImageDimension, normalizeImagePlacement, normalizeLoomaChipColor, normalizeMentionResultLimit, normalizeTableOfContentsConfig, revealLoomaExpandAt, setActiveTableCellAlignment, setActiveTableCellBackground, shouldShowTextFormattingToolbar, siteRelativeHref };
2
+ import { A as filterLoomaMentionItems, B as LOOMA_CHIP_COLORS, C as loomaMarkdownToHtml, D as createLoomaMentionExtension, E as LoomaMentionSuggestionPluginKey, F as LoomaTableOfContents, G as LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS, H as normalizeLoomaChipColor, I as getTableOfContentsEntries, J as LoomaActiveBlock, K as LOOMA_ACTIVE_BLOCK_CLASS, L as normalizeTableOfContentsConfig, M as LoomaImage, N as normalizeImageDimension, O as DEFAULT_MENTION_RESULT_LIMIT, P as normalizeImagePlacement, R as LoomaExpand, S as loomaMarkdownFromDoc, T as siteRelativeHref, U as LOOMA_CALLOUT_TONES, V as LoomaChip, W as LoomaCallout, Y as LoomaActiveBlockPluginKey, _ as getActiveTableCellAlignment, a as getActiveTableUiState, b as setActiveTableCellBackground, c as insertTableAtRange, d as LoomaTableKit, f as getDefaultEditorExtensions, g as LoomaTableHeader, h as LoomaTableCell, i as getDefaultSlashCommands, j as normalizeMentionResultLimit, k as MAX_MENTION_RESULT_LIMIT, l as normalizeActiveTableColumnWidths, m as LoomaTable, n as createLoomaSlashCommandExtension, o as handleTableAction, p as getLoomaTableExtensions, q as LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS, r as filterLoomaSlashCommands, s as handleTableOverlayAction, t as LoomaSlashCommand, u as shouldShowTextFormattingToolbar, v as getActiveTableCellBackground, w as createSiteRelativeLinks, x as LoomaSmartPaste, y as setActiveTableCellAlignment, z as revealLoomaExpandAt } from "../chunks/extensions.js";
3
+ export { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_ACTIVE_BLOCK_BLUR_GRACE_MS, LOOMA_ACTIVE_BLOCK_CLASS, LOOMA_ACTIVE_BLOCK_TYPING_IDLE_MS, LOOMA_CALLOUT_TONES, LOOMA_CHIP_COLORS, LoomaActiveBlock, LoomaActiveBlockPluginKey, LoomaCallout, LoomaChip, LoomaExpand, LoomaImage, LoomaMentionSuggestionPluginKey, LoomaSlashCommand, LoomaSmartPaste, LoomaTable, LoomaTableCell, LoomaTableHeader, LoomaTableKit, LoomaTableOfContents, MAX_MENTION_RESULT_LIMIT, TABLE_CELL_BACKGROUND_PRESETS, createLoomaMentionExtension, createLoomaSlashCommandExtension, createSiteRelativeLinks, filterLoomaMentionItems, filterLoomaSlashCommands, getActiveTableCellAlignment, getActiveTableCellBackground, getActiveTableUiState, getDefaultEditorExtensions, getDefaultSlashCommands, getLoomaTableExtensions, getTableOfContentsEntries, handleTableAction, handleTableOverlayAction, insertTableAtRange, loomaMarkdownFromDoc, loomaMarkdownToHtml, normalizeActiveTableColumnWidths, normalizeImageDimension, normalizeImagePlacement, normalizeLoomaChipColor, normalizeMentionResultLimit, normalizeTableOfContentsConfig, revealLoomaExpandAt, setActiveTableCellAlignment, setActiveTableCellBackground, shouldShowTextFormattingToolbar, siteRelativeHref };
@@ -0,0 +1,18 @@
1
+ import MarkdownIt from "markdown-it";
2
+ import { type Node as ProseMirrorNode } from "@tiptap/pm/model";
3
+ /** The markdown-it instance LoomaEditor imports Markdown with, shared block vocabulary included. */
4
+ export declare const loomaMarkdown: MarkdownIt;
5
+ /**
6
+ * Renders Markdown, including the shared block vocabulary, as HTML the editor parses.
7
+ * @contract The result is not sanitized; pass it to the editor's `setContent`, whose schema
8
+ * keeps only content it knows.
9
+ */
10
+ export declare function loomaMarkdownToHtml(source: string): string;
11
+ /**
12
+ * Writes an editor document as Markdown, with callouts and expand sections in the shared
13
+ * `:::` syntax that `loomaMarkdownToHtml` and Markupress read.
14
+ * @contract Content Markdown has no syntax for (tables, task lists, images, chips, mentions,
15
+ * the table of contents) is written as its saved HTML, which Markdown carries unchanged.
16
+ * Marks without a Markdown form other than underline and highlight are dropped.
17
+ */
18
+ export declare function loomaMarkdownFromDoc(doc: ProseMirrorNode): string;
@@ -43,6 +43,8 @@ export interface DefaultEditorExtensionsOptions {
43
43
  tableOfContentsNodeView?: NodeViewRenderer;
44
44
  /** Optional section settings; the shared schema owns summary and body content. */
45
45
  expandNodeView?: NodeViewRenderer;
46
+ /** Renders callouts while editing; saved HTML stays `<ui-callout>` either way. */
47
+ calloutNodeView?: NodeViewRenderer;
46
48
  }
47
49
  /**
48
50
  * Complete Looma table schema as one Tiptap extension.
package/editor/icons.d.ts CHANGED
@@ -45,8 +45,10 @@ export declare const LOOMA_ICONS: {
45
45
  list: IconNode;
46
46
  'list-ordered': IconNode;
47
47
  'list-todo': IconNode;
48
+ lightbulb: IconNode;
48
49
  loader: IconNode;
49
50
  merge: IconNode;
51
+ 'message-square-warning': IconNode;
50
52
  'notebook-pen': IconNode;
51
53
  minus: IconNode;
52
54
  'paint-bucket': IconNode;
@@ -1,5 +1,5 @@
1
1
  import { Extension, type Editor, type JSONContent } from "@tiptap/core";
2
- export { normalizeImageDimension } from "../../editor/extensions/image.js";
2
+ export { normalizeImageDimension } from "./extensions/image.js";
3
3
  /** What caused an overlay to open or close. */
4
4
  type OverlayTrigger = "keyboard" | "pointer" | "programmatic";
5
5
  /**