living-ai-documentation 1.6.0 → 1.8.0

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.
@@ -314,6 +314,24 @@
314
314
  "snippet.inline_delete_detail": "This action will remove the selected Markdown block.",
315
315
  "snippet.inline_delete_confirm_btn": "Delete",
316
316
  "snippet.inline_delete_failed": "Inline block deletion failed: ",
317
+ "snippet.inline_insert_btn": "Insert a snippet here",
318
+ "snippet.inline_insert_modal_title": "Insert a snippet",
319
+ "snippet.inline_insert_failed": "Inline insertion failed: ",
320
+ "snippet.inline_edit_btn_table": "Edit table",
321
+ "snippet.inline_edit_btn_code_block": "Edit code block",
322
+ "snippet.inline_edit_btn_blockquote": "Edit blockquote",
323
+ "snippet.inline_edit_btn_ordered_list": "Edit numbered list",
324
+ "snippet.inline_edit_btn_unordered_list": "Edit bullet list",
325
+ "snippet.inline_edit_btn_tree": "Edit tree",
326
+ "snippet.inline_edit_btn_colored_section": "Edit colored section",
327
+ "snippet.inline_edit_btn_colored_text": "Edit colored text",
328
+ "snippet.inline_edit_btn_collapsible": "Edit collapsible block",
329
+ "snippet.inline_edit_btn_link": "Edit link",
330
+ "snippet.inline_edit_btn_doc_link": "Edit document link",
331
+ "snippet.inline_edit_btn_anchor_link": "Edit anchor link",
332
+ "snippet.inline_edit_btn_anchor_doc_link": "Edit document + anchor link",
333
+ "snippet.inline_edit_btn_image": "Edit image",
334
+ "snippet.inline_edit_btn_separator": "Edit separator",
317
335
  "snippet.diagram_existing": "Existing diagram",
318
336
  "snippet.diagram_new": "New diagram",
319
337
  "snippet.diagram_select_label": "Select diagram",
@@ -314,6 +314,24 @@
314
314
  "snippet.inline_delete_detail": "Cette action retirera le bloc Markdown sélectionné.",
315
315
  "snippet.inline_delete_confirm_btn": "Supprimer",
316
316
  "snippet.inline_delete_failed": "Échec de la suppression inline : ",
317
+ "snippet.inline_insert_btn": "Insérer un snippet ici",
318
+ "snippet.inline_insert_modal_title": "Insérer un snippet",
319
+ "snippet.inline_insert_failed": "Échec de l'insertion inline : ",
320
+ "snippet.inline_edit_btn_table": "Éditer le tableau",
321
+ "snippet.inline_edit_btn_code_block": "Éditer le bloc de code",
322
+ "snippet.inline_edit_btn_blockquote": "Éditer la citation",
323
+ "snippet.inline_edit_btn_ordered_list": "Éditer la liste numérotée",
324
+ "snippet.inline_edit_btn_unordered_list": "Éditer la liste à puces",
325
+ "snippet.inline_edit_btn_tree": "Éditer l'arborescence",
326
+ "snippet.inline_edit_btn_colored_section": "Éditer la section colorée",
327
+ "snippet.inline_edit_btn_colored_text": "Éditer le texte coloré",
328
+ "snippet.inline_edit_btn_collapsible": "Éditer le bloc repliable",
329
+ "snippet.inline_edit_btn_link": "Éditer le lien",
330
+ "snippet.inline_edit_btn_doc_link": "Éditer le lien vers un document",
331
+ "snippet.inline_edit_btn_anchor_link": "Éditer le lien d'ancre",
332
+ "snippet.inline_edit_btn_anchor_doc_link": "Éditer le lien document + ancre",
333
+ "snippet.inline_edit_btn_image": "Éditer l'image",
334
+ "snippet.inline_edit_btn_separator": "Éditer le séparateur",
317
335
  "snippet.diagram_existing": "Diagramme existant",
318
336
  "snippet.diagram_new": "Nouveau diagramme",
319
337
  "snippet.diagram_select_label": "Sélectionner un diagramme",
@@ -21,6 +21,30 @@ const _INLINE_SNIPPET_TYPES = new Set([
21
21
  "unordered-list",
22
22
  ]);
23
23
 
24
+ const _INLINE_EDIT_AFFORDANCE_BY_TYPE = {
25
+ table: { labelKey: "snippet.inline_edit_btn_table", iconClass: "fa-solid fa-table-cells" },
26
+ "code-block": { labelKey: "snippet.inline_edit_btn_code_block", iconClass: "fa-solid fa-code" },
27
+ blockquote: { labelKey: "snippet.inline_edit_btn_blockquote", iconClass: "fa-solid fa-quote-right" },
28
+ "ordered-list": { labelKey: "snippet.inline_edit_btn_ordered_list", iconClass: "fa-solid fa-list-ol" },
29
+ "unordered-list": { labelKey: "snippet.inline_edit_btn_unordered_list", iconClass: "fa-solid fa-list-ul" },
30
+ tree: { labelKey: "snippet.inline_edit_btn_tree", iconClass: "fa-solid fa-folder-tree" },
31
+ "colored-section": { labelKey: "snippet.inline_edit_btn_colored_section", iconClass: "fa-solid fa-fill-drip" },
32
+ "colored-text": { labelKey: "snippet.inline_edit_btn_colored_text", iconClass: "fa-solid fa-highlighter" },
33
+ collapsible: { labelKey: "snippet.inline_edit_btn_collapsible", iconClass: "fa-solid fa-caret-right" },
34
+ link: { labelKey: "snippet.inline_edit_btn_link", iconClass: "fa-solid fa-link" },
35
+ "doc-link": { labelKey: "snippet.inline_edit_btn_doc_link", iconClass: "fa-solid fa-file-lines" },
36
+ "anchor-link": { labelKey: "snippet.inline_edit_btn_anchor_link", iconClass: "fa-solid fa-anchor" },
37
+ "anchor-doc-link": { labelKey: "snippet.inline_edit_btn_anchor_doc_link", iconClass: "fa-solid fa-anchor" },
38
+ image: { labelKey: "snippet.inline_edit_btn_image", iconClass: "fa-solid fa-image" },
39
+ separator: { labelKey: "snippet.inline_edit_btn_separator", iconClass: "fa-solid fa-minus" },
40
+ };
41
+
42
+ function _inlineEditAffordance(type) {
43
+ const known = _INLINE_EDIT_AFFORDANCE_BY_TYPE[type];
44
+ if (known) return known;
45
+ return { labelKey: "snippet.inline_edit_btn", iconClass: "fa-solid fa-pen-to-square" };
46
+ }
47
+
24
48
  const _INLINE_TYPE_SELECTORS = [
25
49
  { types: ["collapsible"], selector: "details" },
26
50
  { types: ["colored-section"], selector: 'div[style*="border-left"]' },
@@ -67,10 +91,39 @@ function _inlineAddRegexRanges(ranges, content, regex, groupIndex = 0) {
67
91
  }
68
92
  }
69
93
 
94
+ function _inlineLineIndentBefore(content, idx) {
95
+ let i = idx;
96
+ while (i > 0 && content[i - 1] !== "\n") i -= 1;
97
+ const prefix = content.slice(i, idx);
98
+ return /^[ \t]+$/.test(prefix) ? prefix : "";
99
+ }
100
+
101
+ function _inlineAddCodeBlockRanges(ranges, content) {
102
+ const regex = /```[\s\S]*?```/g;
103
+ let match;
104
+ while ((match = regex.exec(content))) {
105
+ const raw = match[0];
106
+ if (!raw.trim()) {
107
+ if (raw.length === 0) regex.lastIndex += 1;
108
+ continue;
109
+ }
110
+ const type = detectSnippetType(raw);
111
+ if (!type || !_INLINE_SNIPPET_TYPES.has(type)) continue;
112
+ const indent = _inlineLineIndentBefore(content, match.index);
113
+ const start = match.index - indent.length;
114
+ const end = match.index + raw.length;
115
+ const candidate = { start, end, type, indent };
116
+ if (ranges.some((existing) => _inlineRangesOverlap(existing, candidate))) {
117
+ continue;
118
+ }
119
+ ranges.push(candidate);
120
+ }
121
+ }
122
+
70
123
  function _inlineCollectSnippetRanges(content) {
71
124
  const ranges = [];
72
125
 
73
- _inlineAddRegexRanges(ranges, content, /```[\s\S]*?```/g);
126
+ _inlineAddCodeBlockRanges(ranges, content);
74
127
  _inlineAddRegexRanges(ranges, content, /<details[\s\S]*?<\/details>/gi);
75
128
  _inlineAddRegexRanges(
76
129
  ranges,
@@ -144,10 +197,12 @@ function _inlineClosePopup() {
144
197
  }
145
198
  }
146
199
 
147
- function _inlineShowPopup(event, range) {
200
+ function _inlineShowPopup(event, { iconClass, labelKey, onActivate, dataAction, dataType }) {
148
201
  _inlineClosePopup();
149
202
  const popup = document.createElement("div");
150
203
  popup.id = "inline-snippet-popup";
204
+ if (dataAction) popup.dataset.action = dataAction;
205
+ if (dataType) popup.dataset.snippetType = dataType;
151
206
  popup.className =
152
207
  "fixed z-50 rounded-lg border border-blue-200 dark:border-blue-800 bg-white dark:bg-gray-900 shadow-lg p-1";
153
208
  const btn = document.createElement("button");
@@ -155,16 +210,16 @@ function _inlineShowPopup(event, range) {
155
210
  btn.className =
156
211
  "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-semibold text-blue-700 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/30";
157
212
  const icon = document.createElement("i");
158
- icon.className = "fa-solid fa-pen-to-square";
213
+ icon.className = iconClass;
159
214
  icon.setAttribute("aria-hidden", "true");
160
215
  const label = document.createElement("span");
161
- label.textContent = window.t("snippet.inline_edit_btn");
216
+ label.textContent = window.t(labelKey);
162
217
  btn.append(icon, label);
163
218
  btn.addEventListener("click", (e) => {
164
219
  e.preventDefault();
165
220
  e.stopPropagation();
166
221
  _inlineClosePopup();
167
- openSnippetsModalForInlineEdit(range);
222
+ onActivate();
168
223
  });
169
224
  popup.appendChild(btn);
170
225
  document.body.appendChild(popup);
@@ -181,6 +236,124 @@ function _inlineShowPopup(event, range) {
181
236
  _inlineSnippetPopup = popup;
182
237
  }
183
238
 
239
+ function _inlineTopLevelBlock(contentEl, target) {
240
+ let block = target instanceof Element ? target : null;
241
+ while (block && block.parentElement && block.parentElement !== contentEl) {
242
+ block = block.parentElement;
243
+ }
244
+ if (!block || block === contentEl || block.parentElement !== contentEl) {
245
+ return null;
246
+ }
247
+ return block;
248
+ }
249
+
250
+ function _inlineNearestBlockByY(contentEl, clientY) {
251
+ if (typeof clientY !== "number") return null;
252
+ const children = Array.from(contentEl.children);
253
+ if (!children.length) return null;
254
+ let above = null;
255
+ let below = null;
256
+ for (const child of children) {
257
+ const rect = child.getBoundingClientRect();
258
+ if (rect.bottom < clientY) above = child;
259
+ else if (rect.top > clientY) {
260
+ if (!below) below = child;
261
+ } else return child;
262
+ }
263
+ return above || below;
264
+ }
265
+
266
+ function _inlineResolveBlockEnd(block, candidates) {
267
+ if (block.matches("[data-inline-snippet-index]")) {
268
+ const range = candidates[Number(block.dataset.inlineSnippetIndex)];
269
+ if (range) return range.end;
270
+ }
271
+ let anchorIdx = null;
272
+ const desc = Array.from(
273
+ block.querySelectorAll("[data-inline-snippet-index]"),
274
+ );
275
+ if (desc.length > 0) {
276
+ let maxEnd = -1;
277
+ for (const d of desc) {
278
+ const range = candidates[Number(d.dataset.inlineSnippetIndex)];
279
+ if (range && range.end > maxEnd) maxEnd = range.end;
280
+ }
281
+ if (maxEnd >= 0) anchorIdx = maxEnd;
282
+ } else {
283
+ const text = (block.textContent || "").trim();
284
+ if (text) {
285
+ const firstLine = text.split("\n")[0].trim().slice(0, 60);
286
+ if (firstLine) {
287
+ const idx = currentDocContent.indexOf(firstLine);
288
+ if (idx >= 0) anchorIdx = idx;
289
+ }
290
+ }
291
+ }
292
+ if (anchorIdx === null) return null;
293
+ const blankIdx = currentDocContent.indexOf("\n\n", anchorIdx);
294
+ return blankIdx >= 0 ? blankIdx : currentDocContent.length;
295
+ }
296
+
297
+ function _inlineResolveBlockStart(block, candidates) {
298
+ if (block.matches("[data-inline-snippet-index]")) {
299
+ const range = candidates[Number(block.dataset.inlineSnippetIndex)];
300
+ if (range) return range.start;
301
+ }
302
+ const desc = Array.from(
303
+ block.querySelectorAll("[data-inline-snippet-index]"),
304
+ );
305
+ if (desc.length > 0) {
306
+ let minStart = Infinity;
307
+ for (const d of desc) {
308
+ const range = candidates[Number(d.dataset.inlineSnippetIndex)];
309
+ if (range && range.start < minStart) minStart = range.start;
310
+ }
311
+ if (minStart < Infinity) return minStart;
312
+ }
313
+ const text = (block.textContent || "").trim();
314
+ if (text) {
315
+ const firstLine = text.split("\n")[0].trim().slice(0, 60);
316
+ if (firstLine) {
317
+ const idx = currentDocContent.indexOf(firstLine);
318
+ if (idx >= 0) return idx;
319
+ }
320
+ }
321
+ return null;
322
+ }
323
+
324
+ function _inlineFindInsertPosition(contentEl, target, candidates, clientY) {
325
+ if (typeof currentDocContent !== "string") return null;
326
+ const topBlock =
327
+ _inlineTopLevelBlock(contentEl, target) ||
328
+ _inlineNearestBlockByY(contentEl, clientY);
329
+ if (!topBlock) return currentDocContent.length;
330
+
331
+ const directEnd = _inlineResolveBlockEnd(topBlock, candidates);
332
+ if (directEnd !== null) return directEnd;
333
+
334
+ let prev = topBlock.previousElementSibling;
335
+ while (prev) {
336
+ const end = _inlineResolveBlockEnd(prev, candidates);
337
+ if (end !== null) {
338
+ const next = currentDocContent.indexOf("\n\n", end + 1);
339
+ return next >= 0 ? next : currentDocContent.length;
340
+ }
341
+ prev = prev.previousElementSibling;
342
+ }
343
+
344
+ let nxt = topBlock.nextElementSibling;
345
+ while (nxt) {
346
+ const start = _inlineResolveBlockStart(nxt, candidates);
347
+ if (start !== null) {
348
+ const prevBlank = currentDocContent.lastIndexOf("\n\n", start);
349
+ return prevBlank >= 0 ? prevBlank : 0;
350
+ }
351
+ nxt = nxt.nextElementSibling;
352
+ }
353
+
354
+ return currentDocContent.length;
355
+ }
356
+
184
357
  function initInlineSnippetEditing(contentEl) {
185
358
  if (!contentEl || typeof currentDocContent !== "string") return;
186
359
  const candidates = _inlineCollectSnippetRanges(currentDocContent);
@@ -194,11 +367,35 @@ function initInlineSnippetEditing(contentEl) {
194
367
  }
195
368
  contentEl._inlineSnippetContextHandler = (event) => {
196
369
  const target = event.target.closest("[data-inline-snippet-index]");
197
- if (!target || !contentEl.contains(target)) return;
198
- const range = candidates[Number(target.dataset.inlineSnippetIndex)];
199
- if (!range) return;
370
+ if (target && contentEl.contains(target)) {
371
+ const range = candidates[Number(target.dataset.inlineSnippetIndex)];
372
+ if (!range) return;
373
+ event.preventDefault();
374
+ const affordance = _inlineEditAffordance(range.type);
375
+ _inlineShowPopup(event, {
376
+ iconClass: affordance.iconClass,
377
+ labelKey: affordance.labelKey,
378
+ dataAction: "edit",
379
+ dataType: range.type,
380
+ onActivate: () => openSnippetsModalForInlineEdit(range),
381
+ });
382
+ return;
383
+ }
384
+ if (!contentEl.contains(event.target)) return;
385
+ const insertPos = _inlineFindInsertPosition(
386
+ contentEl,
387
+ event.target,
388
+ candidates,
389
+ event.clientY,
390
+ );
391
+ if (insertPos === null) return;
200
392
  event.preventDefault();
201
- _inlineShowPopup(event, range);
393
+ _inlineShowPopup(event, {
394
+ iconClass: "fa-solid fa-plus",
395
+ labelKey: "snippet.inline_insert_btn",
396
+ dataAction: "insert",
397
+ onActivate: () => openSnippetsModalForInlineInsert(insertPos),
398
+ });
202
399
  };
203
400
  contentEl.addEventListener(
204
401
  "contextmenu",
@@ -7,6 +7,8 @@
7
7
  let _snippetSelStart = 0;
8
8
  let _snippetSelEnd = 0;
9
9
  let _snippetInlineEdit = false;
10
+ let _snippetInlineInsert = false;
11
+ let _snippetInlineIndent = "";
10
12
  const _SNIPPET_PANELS = [
11
13
  "collapsible",
12
14
  "link",
@@ -630,34 +632,40 @@ async function snippetAnchorDocChanged() {
630
632
  snippetUpdatePreview();
631
633
  }
632
634
 
633
- function _setSnippetModalMode(isInlineEdit) {
634
- _snippetInlineEdit = !!isInlineEdit;
635
+ function _setSnippetModalMode(mode) {
636
+ const isInlineEdit = mode === "inline-edit";
637
+ const isInlineInsert = mode === "inline-insert";
638
+ const isInline = isInlineEdit || isInlineInsert;
639
+ _snippetInlineEdit = isInlineEdit;
640
+ _snippetInlineInsert = isInlineInsert;
641
+ if (!isInlineEdit) _snippetInlineIndent = "";
635
642
  const title = document.getElementById("snippet-modal-title");
636
643
  if (title) {
637
- title.textContent = window.t(
638
- _snippetInlineEdit ? "snippet.inline_modal_title" : "snippet.modal_title",
639
- );
644
+ let key = "snippet.modal_title";
645
+ if (isInlineEdit) key = "snippet.inline_modal_title";
646
+ else if (isInlineInsert) key = "snippet.inline_insert_modal_title";
647
+ title.textContent = window.t(key);
640
648
  }
641
649
  const submit = document.getElementById("snippet-submit-btn");
642
650
  if (submit) {
643
651
  submit.textContent = window.t(
644
- _snippetInlineEdit ? "snippet.inline_save_btn" : "snippet.insert_btn",
652
+ isInlineEdit ? "snippet.inline_save_btn" : "snippet.insert_btn",
645
653
  );
646
654
  }
647
655
  const typeSelect = document.getElementById("snippet-type");
648
656
  if (typeSelect) {
649
- typeSelect.disabled = _snippetInlineEdit;
650
- typeSelect.classList.toggle("cursor-not-allowed", _snippetInlineEdit);
651
- typeSelect.classList.toggle("opacity-70", _snippetInlineEdit);
657
+ typeSelect.disabled = isInlineEdit;
658
+ typeSelect.classList.toggle("cursor-not-allowed", isInlineEdit);
659
+ typeSelect.classList.toggle("opacity-70", isInlineEdit);
652
660
  }
653
661
  const deleteBtn = document.getElementById("snippet-delete-btn");
654
662
  if (deleteBtn) {
655
- deleteBtn.classList.toggle("hidden", !_snippetInlineEdit);
663
+ deleteBtn.classList.toggle("hidden", !isInlineEdit);
656
664
  }
657
665
  const card = document.getElementById("snippet-modal-card");
658
666
  if (card) {
659
- card.classList.toggle("max-w-lg", !_snippetInlineEdit);
660
- card.classList.toggle("max-w-5xl", _snippetInlineEdit);
667
+ card.classList.toggle("max-w-lg", !isInline);
668
+ card.classList.toggle("max-w-5xl", isInline);
661
669
  }
662
670
  }
663
671
 
@@ -719,7 +727,7 @@ function openSnippetsModal() {
719
727
  const editor = document.getElementById("doc-editor");
720
728
  _snippetSelStart = editor.selectionStart;
721
729
  _snippetSelEnd = editor.selectionEnd;
722
- _setSnippetModalMode(false);
730
+ _setSnippetModalMode("insert");
723
731
  _openSnippetsModalForText(editor.value.slice(_snippetSelStart, _snippetSelEnd));
724
732
  }
725
733
 
@@ -727,14 +735,27 @@ function openSnippetsModalForInlineEdit(range) {
727
735
  if (!range || typeof currentDocContent !== "string") return;
728
736
  _snippetSelStart = range.start;
729
737
  _snippetSelEnd = range.end;
730
- _setSnippetModalMode(true);
738
+ _setSnippetModalMode("inline-edit");
739
+ _snippetInlineIndent = range.indent || "";
731
740
  const selectedText = currentDocContent.slice(_snippetSelStart, _snippetSelEnd);
732
741
  _openSnippetsModalForText(selectedText, range.type || null);
733
742
  }
734
743
 
744
+ function openSnippetsModalForInlineInsert(insertPos) {
745
+ if (typeof currentDocContent !== "string") return;
746
+ const pos = Math.max(
747
+ 0,
748
+ Math.min(currentDocContent.length, Number(insertPos) || 0),
749
+ );
750
+ _snippetSelStart = pos;
751
+ _snippetSelEnd = pos;
752
+ _setSnippetModalMode("inline-insert");
753
+ _openSnippetsModalForText("");
754
+ }
755
+
735
756
  function closeSnippetsModal() {
736
757
  document.getElementById("snippets-modal").classList.add("hidden");
737
- _setSnippetModalMode(false);
758
+ _setSnippetModalMode("insert");
738
759
  }
739
760
 
740
761
  function snippetTypeChanged() {
@@ -932,7 +953,14 @@ function buildSnippetMarkdown() {
932
953
  const lang = document.getElementById("snip-code-lang").value || "";
933
954
  const code =
934
955
  document.getElementById("snip-code-content").value || "// code ici";
935
- return `\`\`\`${lang}\n${code}\n\`\`\``;
956
+ const block = `\`\`\`${lang}\n${code}\n\`\`\``;
957
+ if (_snippetInlineEdit && _snippetInlineIndent) {
958
+ return block
959
+ .split("\n")
960
+ .map((line) => _snippetInlineIndent + line)
961
+ .join("\n");
962
+ }
963
+ return block;
936
964
  }
937
965
  case "blockquote": {
938
966
  const content =
@@ -1001,7 +1029,10 @@ function snippetUpdatePreview() {
1001
1029
 
1002
1030
  async function insertSnippet() {
1003
1031
  const type = document.getElementById("snippet-type").value;
1004
- if (_snippetInlineEdit && (type === "diagram" || type === "attachment")) {
1032
+ if (
1033
+ (_snippetInlineEdit || _snippetInlineInsert) &&
1034
+ (type === "diagram" || type === "attachment")
1035
+ ) {
1005
1036
  return;
1006
1037
  }
1007
1038
  if (type === "diagram") {
@@ -1015,6 +1046,7 @@ async function insertSnippet() {
1015
1046
  }
1016
1047
  const text = buildSnippetMarkdown();
1017
1048
  const wasInlineEdit = _snippetInlineEdit;
1049
+ const wasInlineInsert = _snippetInlineInsert;
1018
1050
  closeSnippetsModal();
1019
1051
  if (wasInlineEdit) {
1020
1052
  const before = currentDocContent.slice(0, _snippetSelStart);
@@ -1029,6 +1061,32 @@ async function insertSnippet() {
1029
1061
  }
1030
1062
  return;
1031
1063
  }
1064
+ if (wasInlineInsert) {
1065
+ const before = currentDocContent.slice(0, _snippetSelStart);
1066
+ const after = currentDocContent.slice(_snippetSelStart);
1067
+ const leadingBlank =
1068
+ before.length === 0 || /\n\n$/.test(before)
1069
+ ? ""
1070
+ : before.endsWith("\n")
1071
+ ? "\n"
1072
+ : "\n\n";
1073
+ const trailingBlank =
1074
+ after.length === 0 || /^\n\n/.test(after)
1075
+ ? ""
1076
+ : after.startsWith("\n")
1077
+ ? "\n"
1078
+ : "\n\n";
1079
+ const payload = leadingBlank + text + trailingBlank;
1080
+ try {
1081
+ await saveCurrentDocumentContent(before + payload + after);
1082
+ } catch (err) {
1083
+ alert(
1084
+ window.t("snippet.inline_insert_failed") +
1085
+ (err && err.message ? err.message : String(err)),
1086
+ );
1087
+ }
1088
+ return;
1089
+ }
1032
1090
  const editor = document.getElementById("doc-editor");
1033
1091
  const before = editor.value.slice(0, _snippetSelStart);
1034
1092
  const after = editor.value.slice(_snippetSelEnd);
@@ -1237,9 +1295,20 @@ function parseAndFillSnippet(text, type) {
1237
1295
  break;
1238
1296
  }
1239
1297
  case "code-block": {
1240
- const m = t.match(/^```\s*([^\n]*)\n([\s\S]*?)\n```$/);
1298
+ const m = t.match(/^```[ \t]*([^\n]*)\n([\s\S]*?)\n[ \t]*```$/);
1241
1299
  document.getElementById("snip-code-lang").value = m ? m[1].trim() : "";
1242
- document.getElementById("snip-code-content").value = m ? m[2] : "";
1300
+ let codeContent = m ? m[2] : "";
1301
+ if (m && _snippetInlineIndent) {
1302
+ const escapedIndent = _snippetInlineIndent.replace(
1303
+ /[.*+?^${}()|[\]\\]/g,
1304
+ "\\$&",
1305
+ );
1306
+ codeContent = codeContent.replace(
1307
+ new RegExp("^" + escapedIndent, "gm"),
1308
+ "",
1309
+ );
1310
+ }
1311
+ document.getElementById("snip-code-content").value = codeContent;
1243
1312
  break;
1244
1313
  }
1245
1314
  case "blockquote": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "living-ai-documentation",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Local Markdown documentation hub with a built-in MCP server — coding agents create ADRs, draw diagrams and detect drift while you code.",
5
5
  "main": "dist/src/server.js",
6
6
  "bin": {