living-ai-documentation 1.8.0 → 1.9.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.
@@ -269,6 +269,18 @@
269
269
  "snippet.collapsible_summary_label": "Summary title",
270
270
  "snippet.collapsible_details_placeholder": "Details",
271
271
  "snippet.collapsible_summary_value": "Details",
272
+ "snippet.collapsible_body_label": "Body Markdown",
273
+ "snippet.collapsible_body_placeholder": "## Title\n\nText",
274
+ "snippet.heading_1": "🇭1 Heading level 1",
275
+ "snippet.heading_2": "🇭2 Heading level 2",
276
+ "snippet.heading_3": "🇭3 Heading level 3",
277
+ "snippet.heading_4": "🇭4 Heading level 4",
278
+ "snippet.heading_text_label": "Heading text",
279
+ "snippet.heading_text_placeholder": "My heading",
280
+ "snippet.inline_edit_btn_heading_1": "Edit heading level 1",
281
+ "snippet.inline_edit_btn_heading_2": "Edit heading level 2",
282
+ "snippet.inline_edit_btn_heading_3": "Edit heading level 3",
283
+ "snippet.inline_edit_btn_heading_4": "Edit heading level 4",
272
284
  "snippet.link_text_label": "Link text",
273
285
  "snippet.link_text_placeholder": "My link",
274
286
  "snippet.link_url_label": "URL",
@@ -269,6 +269,18 @@
269
269
  "snippet.collapsible_summary_label": "Titre du résumé",
270
270
  "snippet.collapsible_details_placeholder": "Détails",
271
271
  "snippet.collapsible_summary_value": "Détails",
272
+ "snippet.collapsible_body_label": "Contenu du bloc (Markdown)",
273
+ "snippet.collapsible_body_placeholder": "## Titre\n\nTexte",
274
+ "snippet.heading_1": "🇭1 Titre niveau 1",
275
+ "snippet.heading_2": "🇭2 Titre niveau 2",
276
+ "snippet.heading_3": "🇭3 Titre niveau 3",
277
+ "snippet.heading_4": "🇭4 Titre niveau 4",
278
+ "snippet.heading_text_label": "Texte du titre",
279
+ "snippet.heading_text_placeholder": "Mon titre",
280
+ "snippet.inline_edit_btn_heading_1": "Éditer le titre niveau 1",
281
+ "snippet.inline_edit_btn_heading_2": "Éditer le titre niveau 2",
282
+ "snippet.inline_edit_btn_heading_3": "Éditer le titre niveau 3",
283
+ "snippet.inline_edit_btn_heading_4": "Éditer le titre niveau 4",
272
284
  "snippet.link_text_label": "Texte du lien",
273
285
  "snippet.link_text_placeholder": "Mon lien",
274
286
  "snippet.link_url_label": "URL",
@@ -1453,6 +1453,10 @@
1453
1453
  class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1454
1454
  >
1455
1455
  <option data-i18n="snippet.diagram" value="diagram" selected>Diagram</option>
1456
+ <option data-i18n="snippet.heading_1" value="heading-1">Title level 1</option>
1457
+ <option data-i18n="snippet.heading_2" value="heading-2">Title level 2</option>
1458
+ <option data-i18n="snippet.heading_3" value="heading-3">Title level 3</option>
1459
+ <option data-i18n="snippet.heading_4" value="heading-4">Title level 4</option>
1456
1460
  <option data-i18n="snippet.collapsible" value="collapsible">Collapsible block (details)</option>
1457
1461
  <option data-i18n="snippet.link" value="link">Link</option>
1458
1462
  <option data-i18n="snippet.link_doc" value="doc-link">Link to document</option>
@@ -1476,6 +1480,26 @@
1476
1480
  </select>
1477
1481
  </div>
1478
1482
 
1483
+ <!-- Panel: heading (shared by heading-1..heading-4) -->
1484
+ <div id="snip-panel-heading" class="hidden space-y-3">
1485
+ <div class="space-y-1.5">
1486
+ <label
1487
+ data-i18n="snippet.heading_text_label"
1488
+ for="snip-heading-content"
1489
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1490
+ >Title text</label
1491
+ >
1492
+ <input
1493
+ id="snip-heading-content"
1494
+ type="text"
1495
+ oninput="snippetUpdatePreview()"
1496
+ data-i18n-placeholder="snippet.heading_text_placeholder"
1497
+ placeholder="My title"
1498
+ class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1499
+ />
1500
+ </div>
1501
+ </div>
1502
+
1479
1503
  <!-- Panel: collapsible -->
1480
1504
  <div id="snip-panel-collapsible" class="space-y-3">
1481
1505
  <div class="space-y-1.5">
@@ -1492,6 +1516,22 @@
1492
1516
  class="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1493
1517
  />
1494
1518
  </div>
1519
+ <div class="space-y-1.5">
1520
+ <label
1521
+ data-i18n="snippet.collapsible_body_label"
1522
+ for="snip-collapsible-body"
1523
+ class="block text-xs font-medium text-gray-500 dark:text-gray-400"
1524
+ >Body Markdown</label
1525
+ >
1526
+ <textarea
1527
+ id="snip-collapsible-body"
1528
+ rows="6"
1529
+ oninput="snippetUpdatePreview()"
1530
+ data-i18n-placeholder="snippet.collapsible_body_placeholder"
1531
+ placeholder="## Titre&#10;&#10;Texte"
1532
+ class="w-full px-3 py-2 text-sm font-mono rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
1533
+ ></textarea>
1534
+ </div>
1495
1535
  </div>
1496
1536
 
1497
1537
  <!-- Panel: link -->
@@ -19,6 +19,10 @@ const _INLINE_SNIPPET_TYPES = new Set([
19
19
  "separator",
20
20
  "ordered-list",
21
21
  "unordered-list",
22
+ "heading-1",
23
+ "heading-2",
24
+ "heading-3",
25
+ "heading-4",
22
26
  ]);
23
27
 
24
28
  const _INLINE_EDIT_AFFORDANCE_BY_TYPE = {
@@ -37,6 +41,10 @@ const _INLINE_EDIT_AFFORDANCE_BY_TYPE = {
37
41
  "anchor-doc-link": { labelKey: "snippet.inline_edit_btn_anchor_doc_link", iconClass: "fa-solid fa-anchor" },
38
42
  image: { labelKey: "snippet.inline_edit_btn_image", iconClass: "fa-solid fa-image" },
39
43
  separator: { labelKey: "snippet.inline_edit_btn_separator", iconClass: "fa-solid fa-minus" },
44
+ "heading-1": { labelKey: "snippet.inline_edit_btn_heading_1", iconClass: "fa-solid fa-heading" },
45
+ "heading-2": { labelKey: "snippet.inline_edit_btn_heading_2", iconClass: "fa-solid fa-heading" },
46
+ "heading-3": { labelKey: "snippet.inline_edit_btn_heading_3", iconClass: "fa-solid fa-heading" },
47
+ "heading-4": { labelKey: "snippet.inline_edit_btn_heading_4", iconClass: "fa-solid fa-heading" },
40
48
  };
41
49
 
42
50
  function _inlineEditAffordance(type) {
@@ -55,6 +63,10 @@ const _INLINE_TYPE_SELECTORS = [
55
63
  { types: ["separator"], selector: "hr" },
56
64
  { types: ["ordered-list"], selector: "ol" },
57
65
  { types: ["unordered-list"], selector: "ul" },
66
+ { types: ["heading-1"], selector: "h1" },
67
+ { types: ["heading-2"], selector: "h2" },
68
+ { types: ["heading-3"], selector: "h3" },
69
+ { types: ["heading-4"], selector: "h4" },
58
70
  { types: ["image"], selector: "img" },
59
71
  {
60
72
  types: ["anchor-doc-link", "doc-link", "anchor-link", "link"],
@@ -65,6 +77,12 @@ const _INLINE_TYPE_SELECTORS = [
65
77
  let _inlineSnippetPopup = null;
66
78
 
67
79
  function _inlineRangesOverlap(a, b) {
80
+ // Reject only same-position duplicates (different regex matched the same span)
81
+ // and partial overlaps. Allow strict nesting so a container can coexist with
82
+ // its inner snippets (the deepest-mapped DOM ancestor wins at click time).
83
+ if (a.start === b.start && a.end === b.end) return true;
84
+ if (a.start >= b.start && a.end <= b.end) return false;
85
+ if (b.start >= a.start && b.end <= a.end) return false;
68
86
  return a.start < b.end && b.start < a.end;
69
87
  }
70
88
 
@@ -123,13 +141,21 @@ function _inlineAddCodeBlockRanges(ranges, content) {
123
141
  function _inlineCollectSnippetRanges(content) {
124
142
  const ranges = [];
125
143
 
126
- _inlineAddCodeBlockRanges(ranges, content);
144
+ // Container ranges first so inner snippets (code, lists, links…) that overlap
145
+ // with a container get skipped — the user edits the container as a whole.
127
146
  _inlineAddRegexRanges(ranges, content, /<details[\s\S]*?<\/details>/gi);
128
147
  _inlineAddRegexRanges(
129
148
  ranges,
130
149
  content,
131
150
  /<div\b[^>]*border-left[^>]*>[\s\S]*?<\/div>/gi,
132
151
  );
152
+ _inlineAddCodeBlockRanges(ranges, content);
153
+ _inlineAddRegexRanges(
154
+ ranges,
155
+ content,
156
+ /(?:^|\n\n)(#{1,4} [^\n]+)/g,
157
+ 1,
158
+ );
133
159
  _inlineAddRegexRanges(
134
160
  ranges,
135
161
  content,
@@ -18,6 +18,8 @@ function detectSnippetType(text) {
18
18
  if (/^```/.test(t)) return "code-block";
19
19
  if (/^(\| *.*? *\|)+\n(\| *-+.*\|)+/.test(t)) return "table";
20
20
  if (/^> /.test(t)) return "blockquote";
21
+ const headingMatch = /^(#{1,4}) [^\n]+$/.exec(t);
22
+ if (headingMatch) return `heading-${headingMatch[1].length}`;
21
23
  if (/^(---|\n---\n)$/.test(t)) return "separator";
22
24
  if (/^1\. /.test(t)) return "ordered-list";
23
25
  if (/^- /.test(t)) return "unordered-list";
@@ -10,6 +10,7 @@ let _snippetInlineEdit = false;
10
10
  let _snippetInlineInsert = false;
11
11
  let _snippetInlineIndent = "";
12
12
  const _SNIPPET_PANELS = [
13
+ "heading",
13
14
  "collapsible",
14
15
  "link",
15
16
  "doc-link",
@@ -29,6 +30,17 @@ const _SNIPPET_PANELS = [
29
30
  "attachment",
30
31
  ];
31
32
 
33
+ const _SNIPPET_TYPE_TO_PANEL = {
34
+ "heading-1": "heading",
35
+ "heading-2": "heading",
36
+ "heading-3": "heading",
37
+ "heading-4": "heading",
38
+ };
39
+
40
+ function _snippetPanelForType(type) {
41
+ return _SNIPPET_TYPE_TO_PANEL[type] || type;
42
+ }
43
+
32
44
  // Each emoji has search tags (bilingual FR/EN, space-separated, lowercase).
33
45
  // Filter matches a 2+ char query against any tag prefix or substring.
34
46
  const _EMOJI_CATEGORIES = [
@@ -700,6 +712,10 @@ function _openSnippetsModalForText(selectedText, detectedOverride = null) {
700
712
  image: window.t('snippet.image'),
701
713
  table: window.t('snippet.table'),
702
714
  tree: window.t('snippet.tree'),
715
+ "heading-1": window.t('snippet.heading_1'),
716
+ "heading-2": window.t('snippet.heading_2'),
717
+ "heading-3": window.t('snippet.heading_3'),
718
+ "heading-4": window.t('snippet.heading_4'),
703
719
  "colored-section": window.t('snippet.colored_section'),
704
720
  "colored-text": window.t('snippet.colored_text'),
705
721
  };
@@ -760,9 +776,10 @@ function closeSnippetsModal() {
760
776
 
761
777
  function snippetTypeChanged() {
762
778
  const type = document.getElementById("snippet-type").value;
779
+ const activePanel = _snippetPanelForType(type);
763
780
  _SNIPPET_PANELS.forEach((p) => {
764
781
  const panel = document.getElementById("snip-panel-" + p);
765
- if (panel) panel.classList.toggle("hidden", p !== type);
782
+ if (panel) panel.classList.toggle("hidden", p !== activePanel);
766
783
  });
767
784
  const previewWrap = document.getElementById("snippet-preview-wrap");
768
785
  if (previewWrap) {
@@ -776,7 +793,9 @@ function snippetTypeChanged() {
776
793
  type === "unordered-list" ||
777
794
  type === "colored-section" ||
778
795
  type === "colored-text" ||
779
- type === "tree",
796
+ type === "tree" ||
797
+ type === "collapsible" ||
798
+ type.startsWith("heading-"),
780
799
  );
781
800
  }
782
801
 
@@ -861,7 +880,11 @@ function buildSnippetMarkdown() {
861
880
  const summary =
862
881
  document.getElementById("snip-collapsible-summary").value ||
863
882
  window.t('snippet.collapsible_summary_value');
864
- return `<details>\n<summary>${summary}</summary>\n\n## Titre\n\nTexte\n\n</details>`;
883
+ const bodyEl = document.getElementById("snip-collapsible-body");
884
+ const body = bodyEl && bodyEl.value.trim() !== ""
885
+ ? bodyEl.value
886
+ : "## Titre\n\nTexte";
887
+ return `<details>\n<summary>${summary}</summary>\n\n${body}\n\n</details>`;
865
888
  }
866
889
  case "link": {
867
890
  const text =
@@ -973,6 +996,17 @@ function buildSnippetMarkdown() {
973
996
  }
974
997
  case "separator":
975
998
  return `\n---\n`;
999
+ case "heading-1":
1000
+ case "heading-2":
1001
+ case "heading-3":
1002
+ case "heading-4": {
1003
+ const level = Number(type.slice(-1));
1004
+ const text =
1005
+ document.getElementById("snip-heading-content").value.trim() ||
1006
+ (window.t && window.t("snippet.heading_text_placeholder")) ||
1007
+ "Titre";
1008
+ return `${"#".repeat(level)} ${text}`;
1009
+ }
976
1010
  case "image": {
977
1011
  const alt =
978
1012
  document.getElementById("snip-image-alt").value || "image";
@@ -1198,10 +1232,16 @@ function parseAndFillSnippet(text, type) {
1198
1232
  const t = text.trim();
1199
1233
  switch (type) {
1200
1234
  case "collapsible": {
1201
- const m = t.match(/<summary>([\s\S]*?)<\/summary>/i);
1202
- if (m)
1235
+ const summaryMatch = t.match(/<summary>([\s\S]*?)<\/summary>/i);
1236
+ if (summaryMatch) {
1203
1237
  document.getElementById("snip-collapsible-summary").value =
1204
- m[1].trim();
1238
+ summaryMatch[1].trim();
1239
+ }
1240
+ const bodyMatch = t.match(
1241
+ /<details\b[^>]*>[\s\S]*?<\/summary>\s*\n?([\s\S]*?)\s*<\/details>\s*$/i,
1242
+ );
1243
+ const bodyEl = document.getElementById("snip-collapsible-body");
1244
+ if (bodyEl) bodyEl.value = bodyMatch ? bodyMatch[1].trim() : "";
1205
1245
  break;
1206
1246
  }
1207
1247
  case "link": {
@@ -1326,6 +1366,17 @@ function parseAndFillSnippet(text, type) {
1326
1366
  }
1327
1367
  break;
1328
1368
  }
1369
+ case "heading-1":
1370
+ case "heading-2":
1371
+ case "heading-3":
1372
+ case "heading-4": {
1373
+ const level = Number(type.slice(-1));
1374
+ const re = new RegExp(`^#{${level}}\\s+(.+)$`);
1375
+ const m = t.match(re);
1376
+ const headingEl = document.getElementById("snip-heading-content");
1377
+ if (headingEl) headingEl.value = m ? m[1].trim() : "";
1378
+ break;
1379
+ }
1329
1380
  case "table": {
1330
1381
  const allLines = t
1331
1382
  .split("\n")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "living-ai-documentation",
3
- "version": "1.8.0",
3
+ "version": "1.9.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": {