living-ai-documentation 1.6.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.
- package/dist/src/frontend/i18n/en.json +30 -0
- package/dist/src/frontend/i18n/fr.json +30 -0
- package/dist/src/frontend/index.html +40 -0
- package/dist/src/frontend/inline-snippet-edit.js +232 -9
- package/dist/src/frontend/snippet-detect.js +2 -0
- package/dist/src/frontend/snippets.js +145 -25
- package/package.json +1 -1
|
@@ -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",
|
|
@@ -314,6 +326,24 @@
|
|
|
314
326
|
"snippet.inline_delete_detail": "This action will remove the selected Markdown block.",
|
|
315
327
|
"snippet.inline_delete_confirm_btn": "Delete",
|
|
316
328
|
"snippet.inline_delete_failed": "Inline block deletion failed: ",
|
|
329
|
+
"snippet.inline_insert_btn": "Insert a snippet here",
|
|
330
|
+
"snippet.inline_insert_modal_title": "Insert a snippet",
|
|
331
|
+
"snippet.inline_insert_failed": "Inline insertion failed: ",
|
|
332
|
+
"snippet.inline_edit_btn_table": "Edit table",
|
|
333
|
+
"snippet.inline_edit_btn_code_block": "Edit code block",
|
|
334
|
+
"snippet.inline_edit_btn_blockquote": "Edit blockquote",
|
|
335
|
+
"snippet.inline_edit_btn_ordered_list": "Edit numbered list",
|
|
336
|
+
"snippet.inline_edit_btn_unordered_list": "Edit bullet list",
|
|
337
|
+
"snippet.inline_edit_btn_tree": "Edit tree",
|
|
338
|
+
"snippet.inline_edit_btn_colored_section": "Edit colored section",
|
|
339
|
+
"snippet.inline_edit_btn_colored_text": "Edit colored text",
|
|
340
|
+
"snippet.inline_edit_btn_collapsible": "Edit collapsible block",
|
|
341
|
+
"snippet.inline_edit_btn_link": "Edit link",
|
|
342
|
+
"snippet.inline_edit_btn_doc_link": "Edit document link",
|
|
343
|
+
"snippet.inline_edit_btn_anchor_link": "Edit anchor link",
|
|
344
|
+
"snippet.inline_edit_btn_anchor_doc_link": "Edit document + anchor link",
|
|
345
|
+
"snippet.inline_edit_btn_image": "Edit image",
|
|
346
|
+
"snippet.inline_edit_btn_separator": "Edit separator",
|
|
317
347
|
"snippet.diagram_existing": "Existing diagram",
|
|
318
348
|
"snippet.diagram_new": "New diagram",
|
|
319
349
|
"snippet.diagram_select_label": "Select diagram",
|
|
@@ -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",
|
|
@@ -314,6 +326,24 @@
|
|
|
314
326
|
"snippet.inline_delete_detail": "Cette action retirera le bloc Markdown sélectionné.",
|
|
315
327
|
"snippet.inline_delete_confirm_btn": "Supprimer",
|
|
316
328
|
"snippet.inline_delete_failed": "Échec de la suppression inline : ",
|
|
329
|
+
"snippet.inline_insert_btn": "Insérer un snippet ici",
|
|
330
|
+
"snippet.inline_insert_modal_title": "Insérer un snippet",
|
|
331
|
+
"snippet.inline_insert_failed": "Échec de l'insertion inline : ",
|
|
332
|
+
"snippet.inline_edit_btn_table": "Éditer le tableau",
|
|
333
|
+
"snippet.inline_edit_btn_code_block": "Éditer le bloc de code",
|
|
334
|
+
"snippet.inline_edit_btn_blockquote": "Éditer la citation",
|
|
335
|
+
"snippet.inline_edit_btn_ordered_list": "Éditer la liste numérotée",
|
|
336
|
+
"snippet.inline_edit_btn_unordered_list": "Éditer la liste à puces",
|
|
337
|
+
"snippet.inline_edit_btn_tree": "Éditer l'arborescence",
|
|
338
|
+
"snippet.inline_edit_btn_colored_section": "Éditer la section colorée",
|
|
339
|
+
"snippet.inline_edit_btn_colored_text": "Éditer le texte coloré",
|
|
340
|
+
"snippet.inline_edit_btn_collapsible": "Éditer le bloc repliable",
|
|
341
|
+
"snippet.inline_edit_btn_link": "Éditer le lien",
|
|
342
|
+
"snippet.inline_edit_btn_doc_link": "Éditer le lien vers un document",
|
|
343
|
+
"snippet.inline_edit_btn_anchor_link": "Éditer le lien d'ancre",
|
|
344
|
+
"snippet.inline_edit_btn_anchor_doc_link": "Éditer le lien document + ancre",
|
|
345
|
+
"snippet.inline_edit_btn_image": "Éditer l'image",
|
|
346
|
+
"snippet.inline_edit_btn_separator": "Éditer le séparateur",
|
|
317
347
|
"snippet.diagram_existing": "Diagramme existant",
|
|
318
348
|
"snippet.diagram_new": "Nouveau diagramme",
|
|
319
349
|
"snippet.diagram_select_label": "Sélectionner un diagramme",
|
|
@@ -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 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,8 +19,40 @@ 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
|
|
|
28
|
+
const _INLINE_EDIT_AFFORDANCE_BY_TYPE = {
|
|
29
|
+
table: { labelKey: "snippet.inline_edit_btn_table", iconClass: "fa-solid fa-table-cells" },
|
|
30
|
+
"code-block": { labelKey: "snippet.inline_edit_btn_code_block", iconClass: "fa-solid fa-code" },
|
|
31
|
+
blockquote: { labelKey: "snippet.inline_edit_btn_blockquote", iconClass: "fa-solid fa-quote-right" },
|
|
32
|
+
"ordered-list": { labelKey: "snippet.inline_edit_btn_ordered_list", iconClass: "fa-solid fa-list-ol" },
|
|
33
|
+
"unordered-list": { labelKey: "snippet.inline_edit_btn_unordered_list", iconClass: "fa-solid fa-list-ul" },
|
|
34
|
+
tree: { labelKey: "snippet.inline_edit_btn_tree", iconClass: "fa-solid fa-folder-tree" },
|
|
35
|
+
"colored-section": { labelKey: "snippet.inline_edit_btn_colored_section", iconClass: "fa-solid fa-fill-drip" },
|
|
36
|
+
"colored-text": { labelKey: "snippet.inline_edit_btn_colored_text", iconClass: "fa-solid fa-highlighter" },
|
|
37
|
+
collapsible: { labelKey: "snippet.inline_edit_btn_collapsible", iconClass: "fa-solid fa-caret-right" },
|
|
38
|
+
link: { labelKey: "snippet.inline_edit_btn_link", iconClass: "fa-solid fa-link" },
|
|
39
|
+
"doc-link": { labelKey: "snippet.inline_edit_btn_doc_link", iconClass: "fa-solid fa-file-lines" },
|
|
40
|
+
"anchor-link": { labelKey: "snippet.inline_edit_btn_anchor_link", iconClass: "fa-solid fa-anchor" },
|
|
41
|
+
"anchor-doc-link": { labelKey: "snippet.inline_edit_btn_anchor_doc_link", iconClass: "fa-solid fa-anchor" },
|
|
42
|
+
image: { labelKey: "snippet.inline_edit_btn_image", iconClass: "fa-solid fa-image" },
|
|
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" },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function _inlineEditAffordance(type) {
|
|
51
|
+
const known = _INLINE_EDIT_AFFORDANCE_BY_TYPE[type];
|
|
52
|
+
if (known) return known;
|
|
53
|
+
return { labelKey: "snippet.inline_edit_btn", iconClass: "fa-solid fa-pen-to-square" };
|
|
54
|
+
}
|
|
55
|
+
|
|
24
56
|
const _INLINE_TYPE_SELECTORS = [
|
|
25
57
|
{ types: ["collapsible"], selector: "details" },
|
|
26
58
|
{ types: ["colored-section"], selector: 'div[style*="border-left"]' },
|
|
@@ -31,6 +63,10 @@ const _INLINE_TYPE_SELECTORS = [
|
|
|
31
63
|
{ types: ["separator"], selector: "hr" },
|
|
32
64
|
{ types: ["ordered-list"], selector: "ol" },
|
|
33
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" },
|
|
34
70
|
{ types: ["image"], selector: "img" },
|
|
35
71
|
{
|
|
36
72
|
types: ["anchor-doc-link", "doc-link", "anchor-link", "link"],
|
|
@@ -41,6 +77,12 @@ const _INLINE_TYPE_SELECTORS = [
|
|
|
41
77
|
let _inlineSnippetPopup = null;
|
|
42
78
|
|
|
43
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;
|
|
44
86
|
return a.start < b.end && b.start < a.end;
|
|
45
87
|
}
|
|
46
88
|
|
|
@@ -67,16 +109,53 @@ function _inlineAddRegexRanges(ranges, content, regex, groupIndex = 0) {
|
|
|
67
109
|
}
|
|
68
110
|
}
|
|
69
111
|
|
|
112
|
+
function _inlineLineIndentBefore(content, idx) {
|
|
113
|
+
let i = idx;
|
|
114
|
+
while (i > 0 && content[i - 1] !== "\n") i -= 1;
|
|
115
|
+
const prefix = content.slice(i, idx);
|
|
116
|
+
return /^[ \t]+$/.test(prefix) ? prefix : "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function _inlineAddCodeBlockRanges(ranges, content) {
|
|
120
|
+
const regex = /```[\s\S]*?```/g;
|
|
121
|
+
let match;
|
|
122
|
+
while ((match = regex.exec(content))) {
|
|
123
|
+
const raw = match[0];
|
|
124
|
+
if (!raw.trim()) {
|
|
125
|
+
if (raw.length === 0) regex.lastIndex += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const type = detectSnippetType(raw);
|
|
129
|
+
if (!type || !_INLINE_SNIPPET_TYPES.has(type)) continue;
|
|
130
|
+
const indent = _inlineLineIndentBefore(content, match.index);
|
|
131
|
+
const start = match.index - indent.length;
|
|
132
|
+
const end = match.index + raw.length;
|
|
133
|
+
const candidate = { start, end, type, indent };
|
|
134
|
+
if (ranges.some((existing) => _inlineRangesOverlap(existing, candidate))) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
ranges.push(candidate);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
70
141
|
function _inlineCollectSnippetRanges(content) {
|
|
71
142
|
const ranges = [];
|
|
72
143
|
|
|
73
|
-
|
|
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.
|
|
74
146
|
_inlineAddRegexRanges(ranges, content, /<details[\s\S]*?<\/details>/gi);
|
|
75
147
|
_inlineAddRegexRanges(
|
|
76
148
|
ranges,
|
|
77
149
|
content,
|
|
78
150
|
/<div\b[^>]*border-left[^>]*>[\s\S]*?<\/div>/gi,
|
|
79
151
|
);
|
|
152
|
+
_inlineAddCodeBlockRanges(ranges, content);
|
|
153
|
+
_inlineAddRegexRanges(
|
|
154
|
+
ranges,
|
|
155
|
+
content,
|
|
156
|
+
/(?:^|\n\n)(#{1,4} [^\n]+)/g,
|
|
157
|
+
1,
|
|
158
|
+
);
|
|
80
159
|
_inlineAddRegexRanges(
|
|
81
160
|
ranges,
|
|
82
161
|
content,
|
|
@@ -144,10 +223,12 @@ function _inlineClosePopup() {
|
|
|
144
223
|
}
|
|
145
224
|
}
|
|
146
225
|
|
|
147
|
-
function _inlineShowPopup(event,
|
|
226
|
+
function _inlineShowPopup(event, { iconClass, labelKey, onActivate, dataAction, dataType }) {
|
|
148
227
|
_inlineClosePopup();
|
|
149
228
|
const popup = document.createElement("div");
|
|
150
229
|
popup.id = "inline-snippet-popup";
|
|
230
|
+
if (dataAction) popup.dataset.action = dataAction;
|
|
231
|
+
if (dataType) popup.dataset.snippetType = dataType;
|
|
151
232
|
popup.className =
|
|
152
233
|
"fixed z-50 rounded-lg border border-blue-200 dark:border-blue-800 bg-white dark:bg-gray-900 shadow-lg p-1";
|
|
153
234
|
const btn = document.createElement("button");
|
|
@@ -155,16 +236,16 @@ function _inlineShowPopup(event, range) {
|
|
|
155
236
|
btn.className =
|
|
156
237
|
"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
238
|
const icon = document.createElement("i");
|
|
158
|
-
icon.className =
|
|
239
|
+
icon.className = iconClass;
|
|
159
240
|
icon.setAttribute("aria-hidden", "true");
|
|
160
241
|
const label = document.createElement("span");
|
|
161
|
-
label.textContent = window.t(
|
|
242
|
+
label.textContent = window.t(labelKey);
|
|
162
243
|
btn.append(icon, label);
|
|
163
244
|
btn.addEventListener("click", (e) => {
|
|
164
245
|
e.preventDefault();
|
|
165
246
|
e.stopPropagation();
|
|
166
247
|
_inlineClosePopup();
|
|
167
|
-
|
|
248
|
+
onActivate();
|
|
168
249
|
});
|
|
169
250
|
popup.appendChild(btn);
|
|
170
251
|
document.body.appendChild(popup);
|
|
@@ -181,6 +262,124 @@ function _inlineShowPopup(event, range) {
|
|
|
181
262
|
_inlineSnippetPopup = popup;
|
|
182
263
|
}
|
|
183
264
|
|
|
265
|
+
function _inlineTopLevelBlock(contentEl, target) {
|
|
266
|
+
let block = target instanceof Element ? target : null;
|
|
267
|
+
while (block && block.parentElement && block.parentElement !== contentEl) {
|
|
268
|
+
block = block.parentElement;
|
|
269
|
+
}
|
|
270
|
+
if (!block || block === contentEl || block.parentElement !== contentEl) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return block;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function _inlineNearestBlockByY(contentEl, clientY) {
|
|
277
|
+
if (typeof clientY !== "number") return null;
|
|
278
|
+
const children = Array.from(contentEl.children);
|
|
279
|
+
if (!children.length) return null;
|
|
280
|
+
let above = null;
|
|
281
|
+
let below = null;
|
|
282
|
+
for (const child of children) {
|
|
283
|
+
const rect = child.getBoundingClientRect();
|
|
284
|
+
if (rect.bottom < clientY) above = child;
|
|
285
|
+
else if (rect.top > clientY) {
|
|
286
|
+
if (!below) below = child;
|
|
287
|
+
} else return child;
|
|
288
|
+
}
|
|
289
|
+
return above || below;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function _inlineResolveBlockEnd(block, candidates) {
|
|
293
|
+
if (block.matches("[data-inline-snippet-index]")) {
|
|
294
|
+
const range = candidates[Number(block.dataset.inlineSnippetIndex)];
|
|
295
|
+
if (range) return range.end;
|
|
296
|
+
}
|
|
297
|
+
let anchorIdx = null;
|
|
298
|
+
const desc = Array.from(
|
|
299
|
+
block.querySelectorAll("[data-inline-snippet-index]"),
|
|
300
|
+
);
|
|
301
|
+
if (desc.length > 0) {
|
|
302
|
+
let maxEnd = -1;
|
|
303
|
+
for (const d of desc) {
|
|
304
|
+
const range = candidates[Number(d.dataset.inlineSnippetIndex)];
|
|
305
|
+
if (range && range.end > maxEnd) maxEnd = range.end;
|
|
306
|
+
}
|
|
307
|
+
if (maxEnd >= 0) anchorIdx = maxEnd;
|
|
308
|
+
} else {
|
|
309
|
+
const text = (block.textContent || "").trim();
|
|
310
|
+
if (text) {
|
|
311
|
+
const firstLine = text.split("\n")[0].trim().slice(0, 60);
|
|
312
|
+
if (firstLine) {
|
|
313
|
+
const idx = currentDocContent.indexOf(firstLine);
|
|
314
|
+
if (idx >= 0) anchorIdx = idx;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (anchorIdx === null) return null;
|
|
319
|
+
const blankIdx = currentDocContent.indexOf("\n\n", anchorIdx);
|
|
320
|
+
return blankIdx >= 0 ? blankIdx : currentDocContent.length;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function _inlineResolveBlockStart(block, candidates) {
|
|
324
|
+
if (block.matches("[data-inline-snippet-index]")) {
|
|
325
|
+
const range = candidates[Number(block.dataset.inlineSnippetIndex)];
|
|
326
|
+
if (range) return range.start;
|
|
327
|
+
}
|
|
328
|
+
const desc = Array.from(
|
|
329
|
+
block.querySelectorAll("[data-inline-snippet-index]"),
|
|
330
|
+
);
|
|
331
|
+
if (desc.length > 0) {
|
|
332
|
+
let minStart = Infinity;
|
|
333
|
+
for (const d of desc) {
|
|
334
|
+
const range = candidates[Number(d.dataset.inlineSnippetIndex)];
|
|
335
|
+
if (range && range.start < minStart) minStart = range.start;
|
|
336
|
+
}
|
|
337
|
+
if (minStart < Infinity) return minStart;
|
|
338
|
+
}
|
|
339
|
+
const text = (block.textContent || "").trim();
|
|
340
|
+
if (text) {
|
|
341
|
+
const firstLine = text.split("\n")[0].trim().slice(0, 60);
|
|
342
|
+
if (firstLine) {
|
|
343
|
+
const idx = currentDocContent.indexOf(firstLine);
|
|
344
|
+
if (idx >= 0) return idx;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function _inlineFindInsertPosition(contentEl, target, candidates, clientY) {
|
|
351
|
+
if (typeof currentDocContent !== "string") return null;
|
|
352
|
+
const topBlock =
|
|
353
|
+
_inlineTopLevelBlock(contentEl, target) ||
|
|
354
|
+
_inlineNearestBlockByY(contentEl, clientY);
|
|
355
|
+
if (!topBlock) return currentDocContent.length;
|
|
356
|
+
|
|
357
|
+
const directEnd = _inlineResolveBlockEnd(topBlock, candidates);
|
|
358
|
+
if (directEnd !== null) return directEnd;
|
|
359
|
+
|
|
360
|
+
let prev = topBlock.previousElementSibling;
|
|
361
|
+
while (prev) {
|
|
362
|
+
const end = _inlineResolveBlockEnd(prev, candidates);
|
|
363
|
+
if (end !== null) {
|
|
364
|
+
const next = currentDocContent.indexOf("\n\n", end + 1);
|
|
365
|
+
return next >= 0 ? next : currentDocContent.length;
|
|
366
|
+
}
|
|
367
|
+
prev = prev.previousElementSibling;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let nxt = topBlock.nextElementSibling;
|
|
371
|
+
while (nxt) {
|
|
372
|
+
const start = _inlineResolveBlockStart(nxt, candidates);
|
|
373
|
+
if (start !== null) {
|
|
374
|
+
const prevBlank = currentDocContent.lastIndexOf("\n\n", start);
|
|
375
|
+
return prevBlank >= 0 ? prevBlank : 0;
|
|
376
|
+
}
|
|
377
|
+
nxt = nxt.nextElementSibling;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return currentDocContent.length;
|
|
381
|
+
}
|
|
382
|
+
|
|
184
383
|
function initInlineSnippetEditing(contentEl) {
|
|
185
384
|
if (!contentEl || typeof currentDocContent !== "string") return;
|
|
186
385
|
const candidates = _inlineCollectSnippetRanges(currentDocContent);
|
|
@@ -194,11 +393,35 @@ function initInlineSnippetEditing(contentEl) {
|
|
|
194
393
|
}
|
|
195
394
|
contentEl._inlineSnippetContextHandler = (event) => {
|
|
196
395
|
const target = event.target.closest("[data-inline-snippet-index]");
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
396
|
+
if (target && contentEl.contains(target)) {
|
|
397
|
+
const range = candidates[Number(target.dataset.inlineSnippetIndex)];
|
|
398
|
+
if (!range) return;
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
const affordance = _inlineEditAffordance(range.type);
|
|
401
|
+
_inlineShowPopup(event, {
|
|
402
|
+
iconClass: affordance.iconClass,
|
|
403
|
+
labelKey: affordance.labelKey,
|
|
404
|
+
dataAction: "edit",
|
|
405
|
+
dataType: range.type,
|
|
406
|
+
onActivate: () => openSnippetsModalForInlineEdit(range),
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (!contentEl.contains(event.target)) return;
|
|
411
|
+
const insertPos = _inlineFindInsertPosition(
|
|
412
|
+
contentEl,
|
|
413
|
+
event.target,
|
|
414
|
+
candidates,
|
|
415
|
+
event.clientY,
|
|
416
|
+
);
|
|
417
|
+
if (insertPos === null) return;
|
|
200
418
|
event.preventDefault();
|
|
201
|
-
_inlineShowPopup(event,
|
|
419
|
+
_inlineShowPopup(event, {
|
|
420
|
+
iconClass: "fa-solid fa-plus",
|
|
421
|
+
labelKey: "snippet.inline_insert_btn",
|
|
422
|
+
dataAction: "insert",
|
|
423
|
+
onActivate: () => openSnippetsModalForInlineInsert(insertPos),
|
|
424
|
+
});
|
|
202
425
|
};
|
|
203
426
|
contentEl.addEventListener(
|
|
204
427
|
"contextmenu",
|
|
@@ -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";
|
|
@@ -7,7 +7,10 @@
|
|
|
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 = [
|
|
13
|
+
"heading",
|
|
11
14
|
"collapsible",
|
|
12
15
|
"link",
|
|
13
16
|
"doc-link",
|
|
@@ -27,6 +30,17 @@ const _SNIPPET_PANELS = [
|
|
|
27
30
|
"attachment",
|
|
28
31
|
];
|
|
29
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
|
+
|
|
30
44
|
// Each emoji has search tags (bilingual FR/EN, space-separated, lowercase).
|
|
31
45
|
// Filter matches a 2+ char query against any tag prefix or substring.
|
|
32
46
|
const _EMOJI_CATEGORIES = [
|
|
@@ -630,34 +644,40 @@ async function snippetAnchorDocChanged() {
|
|
|
630
644
|
snippetUpdatePreview();
|
|
631
645
|
}
|
|
632
646
|
|
|
633
|
-
function _setSnippetModalMode(
|
|
634
|
-
|
|
647
|
+
function _setSnippetModalMode(mode) {
|
|
648
|
+
const isInlineEdit = mode === "inline-edit";
|
|
649
|
+
const isInlineInsert = mode === "inline-insert";
|
|
650
|
+
const isInline = isInlineEdit || isInlineInsert;
|
|
651
|
+
_snippetInlineEdit = isInlineEdit;
|
|
652
|
+
_snippetInlineInsert = isInlineInsert;
|
|
653
|
+
if (!isInlineEdit) _snippetInlineIndent = "";
|
|
635
654
|
const title = document.getElementById("snippet-modal-title");
|
|
636
655
|
if (title) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
);
|
|
656
|
+
let key = "snippet.modal_title";
|
|
657
|
+
if (isInlineEdit) key = "snippet.inline_modal_title";
|
|
658
|
+
else if (isInlineInsert) key = "snippet.inline_insert_modal_title";
|
|
659
|
+
title.textContent = window.t(key);
|
|
640
660
|
}
|
|
641
661
|
const submit = document.getElementById("snippet-submit-btn");
|
|
642
662
|
if (submit) {
|
|
643
663
|
submit.textContent = window.t(
|
|
644
|
-
|
|
664
|
+
isInlineEdit ? "snippet.inline_save_btn" : "snippet.insert_btn",
|
|
645
665
|
);
|
|
646
666
|
}
|
|
647
667
|
const typeSelect = document.getElementById("snippet-type");
|
|
648
668
|
if (typeSelect) {
|
|
649
|
-
typeSelect.disabled =
|
|
650
|
-
typeSelect.classList.toggle("cursor-not-allowed",
|
|
651
|
-
typeSelect.classList.toggle("opacity-70",
|
|
669
|
+
typeSelect.disabled = isInlineEdit;
|
|
670
|
+
typeSelect.classList.toggle("cursor-not-allowed", isInlineEdit);
|
|
671
|
+
typeSelect.classList.toggle("opacity-70", isInlineEdit);
|
|
652
672
|
}
|
|
653
673
|
const deleteBtn = document.getElementById("snippet-delete-btn");
|
|
654
674
|
if (deleteBtn) {
|
|
655
|
-
deleteBtn.classList.toggle("hidden", !
|
|
675
|
+
deleteBtn.classList.toggle("hidden", !isInlineEdit);
|
|
656
676
|
}
|
|
657
677
|
const card = document.getElementById("snippet-modal-card");
|
|
658
678
|
if (card) {
|
|
659
|
-
card.classList.toggle("max-w-lg", !
|
|
660
|
-
card.classList.toggle("max-w-5xl",
|
|
679
|
+
card.classList.toggle("max-w-lg", !isInline);
|
|
680
|
+
card.classList.toggle("max-w-5xl", isInline);
|
|
661
681
|
}
|
|
662
682
|
}
|
|
663
683
|
|
|
@@ -692,6 +712,10 @@ function _openSnippetsModalForText(selectedText, detectedOverride = null) {
|
|
|
692
712
|
image: window.t('snippet.image'),
|
|
693
713
|
table: window.t('snippet.table'),
|
|
694
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'),
|
|
695
719
|
"colored-section": window.t('snippet.colored_section'),
|
|
696
720
|
"colored-text": window.t('snippet.colored_text'),
|
|
697
721
|
};
|
|
@@ -719,7 +743,7 @@ function openSnippetsModal() {
|
|
|
719
743
|
const editor = document.getElementById("doc-editor");
|
|
720
744
|
_snippetSelStart = editor.selectionStart;
|
|
721
745
|
_snippetSelEnd = editor.selectionEnd;
|
|
722
|
-
_setSnippetModalMode(
|
|
746
|
+
_setSnippetModalMode("insert");
|
|
723
747
|
_openSnippetsModalForText(editor.value.slice(_snippetSelStart, _snippetSelEnd));
|
|
724
748
|
}
|
|
725
749
|
|
|
@@ -727,21 +751,35 @@ function openSnippetsModalForInlineEdit(range) {
|
|
|
727
751
|
if (!range || typeof currentDocContent !== "string") return;
|
|
728
752
|
_snippetSelStart = range.start;
|
|
729
753
|
_snippetSelEnd = range.end;
|
|
730
|
-
_setSnippetModalMode(
|
|
754
|
+
_setSnippetModalMode("inline-edit");
|
|
755
|
+
_snippetInlineIndent = range.indent || "";
|
|
731
756
|
const selectedText = currentDocContent.slice(_snippetSelStart, _snippetSelEnd);
|
|
732
757
|
_openSnippetsModalForText(selectedText, range.type || null);
|
|
733
758
|
}
|
|
734
759
|
|
|
760
|
+
function openSnippetsModalForInlineInsert(insertPos) {
|
|
761
|
+
if (typeof currentDocContent !== "string") return;
|
|
762
|
+
const pos = Math.max(
|
|
763
|
+
0,
|
|
764
|
+
Math.min(currentDocContent.length, Number(insertPos) || 0),
|
|
765
|
+
);
|
|
766
|
+
_snippetSelStart = pos;
|
|
767
|
+
_snippetSelEnd = pos;
|
|
768
|
+
_setSnippetModalMode("inline-insert");
|
|
769
|
+
_openSnippetsModalForText("");
|
|
770
|
+
}
|
|
771
|
+
|
|
735
772
|
function closeSnippetsModal() {
|
|
736
773
|
document.getElementById("snippets-modal").classList.add("hidden");
|
|
737
|
-
_setSnippetModalMode(
|
|
774
|
+
_setSnippetModalMode("insert");
|
|
738
775
|
}
|
|
739
776
|
|
|
740
777
|
function snippetTypeChanged() {
|
|
741
778
|
const type = document.getElementById("snippet-type").value;
|
|
779
|
+
const activePanel = _snippetPanelForType(type);
|
|
742
780
|
_SNIPPET_PANELS.forEach((p) => {
|
|
743
781
|
const panel = document.getElementById("snip-panel-" + p);
|
|
744
|
-
if (panel) panel.classList.toggle("hidden", p !==
|
|
782
|
+
if (panel) panel.classList.toggle("hidden", p !== activePanel);
|
|
745
783
|
});
|
|
746
784
|
const previewWrap = document.getElementById("snippet-preview-wrap");
|
|
747
785
|
if (previewWrap) {
|
|
@@ -755,7 +793,9 @@ function snippetTypeChanged() {
|
|
|
755
793
|
type === "unordered-list" ||
|
|
756
794
|
type === "colored-section" ||
|
|
757
795
|
type === "colored-text" ||
|
|
758
|
-
type === "tree"
|
|
796
|
+
type === "tree" ||
|
|
797
|
+
type === "collapsible" ||
|
|
798
|
+
type.startsWith("heading-"),
|
|
759
799
|
);
|
|
760
800
|
}
|
|
761
801
|
|
|
@@ -840,7 +880,11 @@ function buildSnippetMarkdown() {
|
|
|
840
880
|
const summary =
|
|
841
881
|
document.getElementById("snip-collapsible-summary").value ||
|
|
842
882
|
window.t('snippet.collapsible_summary_value');
|
|
843
|
-
|
|
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>`;
|
|
844
888
|
}
|
|
845
889
|
case "link": {
|
|
846
890
|
const text =
|
|
@@ -932,7 +976,14 @@ function buildSnippetMarkdown() {
|
|
|
932
976
|
const lang = document.getElementById("snip-code-lang").value || "";
|
|
933
977
|
const code =
|
|
934
978
|
document.getElementById("snip-code-content").value || "// code ici";
|
|
935
|
-
|
|
979
|
+
const block = `\`\`\`${lang}\n${code}\n\`\`\``;
|
|
980
|
+
if (_snippetInlineEdit && _snippetInlineIndent) {
|
|
981
|
+
return block
|
|
982
|
+
.split("\n")
|
|
983
|
+
.map((line) => _snippetInlineIndent + line)
|
|
984
|
+
.join("\n");
|
|
985
|
+
}
|
|
986
|
+
return block;
|
|
936
987
|
}
|
|
937
988
|
case "blockquote": {
|
|
938
989
|
const content =
|
|
@@ -945,6 +996,17 @@ function buildSnippetMarkdown() {
|
|
|
945
996
|
}
|
|
946
997
|
case "separator":
|
|
947
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
|
+
}
|
|
948
1010
|
case "image": {
|
|
949
1011
|
const alt =
|
|
950
1012
|
document.getElementById("snip-image-alt").value || "image";
|
|
@@ -1001,7 +1063,10 @@ function snippetUpdatePreview() {
|
|
|
1001
1063
|
|
|
1002
1064
|
async function insertSnippet() {
|
|
1003
1065
|
const type = document.getElementById("snippet-type").value;
|
|
1004
|
-
if (
|
|
1066
|
+
if (
|
|
1067
|
+
(_snippetInlineEdit || _snippetInlineInsert) &&
|
|
1068
|
+
(type === "diagram" || type === "attachment")
|
|
1069
|
+
) {
|
|
1005
1070
|
return;
|
|
1006
1071
|
}
|
|
1007
1072
|
if (type === "diagram") {
|
|
@@ -1015,6 +1080,7 @@ async function insertSnippet() {
|
|
|
1015
1080
|
}
|
|
1016
1081
|
const text = buildSnippetMarkdown();
|
|
1017
1082
|
const wasInlineEdit = _snippetInlineEdit;
|
|
1083
|
+
const wasInlineInsert = _snippetInlineInsert;
|
|
1018
1084
|
closeSnippetsModal();
|
|
1019
1085
|
if (wasInlineEdit) {
|
|
1020
1086
|
const before = currentDocContent.slice(0, _snippetSelStart);
|
|
@@ -1029,6 +1095,32 @@ async function insertSnippet() {
|
|
|
1029
1095
|
}
|
|
1030
1096
|
return;
|
|
1031
1097
|
}
|
|
1098
|
+
if (wasInlineInsert) {
|
|
1099
|
+
const before = currentDocContent.slice(0, _snippetSelStart);
|
|
1100
|
+
const after = currentDocContent.slice(_snippetSelStart);
|
|
1101
|
+
const leadingBlank =
|
|
1102
|
+
before.length === 0 || /\n\n$/.test(before)
|
|
1103
|
+
? ""
|
|
1104
|
+
: before.endsWith("\n")
|
|
1105
|
+
? "\n"
|
|
1106
|
+
: "\n\n";
|
|
1107
|
+
const trailingBlank =
|
|
1108
|
+
after.length === 0 || /^\n\n/.test(after)
|
|
1109
|
+
? ""
|
|
1110
|
+
: after.startsWith("\n")
|
|
1111
|
+
? "\n"
|
|
1112
|
+
: "\n\n";
|
|
1113
|
+
const payload = leadingBlank + text + trailingBlank;
|
|
1114
|
+
try {
|
|
1115
|
+
await saveCurrentDocumentContent(before + payload + after);
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
alert(
|
|
1118
|
+
window.t("snippet.inline_insert_failed") +
|
|
1119
|
+
(err && err.message ? err.message : String(err)),
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1032
1124
|
const editor = document.getElementById("doc-editor");
|
|
1033
1125
|
const before = editor.value.slice(0, _snippetSelStart);
|
|
1034
1126
|
const after = editor.value.slice(_snippetSelEnd);
|
|
@@ -1140,10 +1232,16 @@ function parseAndFillSnippet(text, type) {
|
|
|
1140
1232
|
const t = text.trim();
|
|
1141
1233
|
switch (type) {
|
|
1142
1234
|
case "collapsible": {
|
|
1143
|
-
const
|
|
1144
|
-
if (
|
|
1235
|
+
const summaryMatch = t.match(/<summary>([\s\S]*?)<\/summary>/i);
|
|
1236
|
+
if (summaryMatch) {
|
|
1145
1237
|
document.getElementById("snip-collapsible-summary").value =
|
|
1146
|
-
|
|
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() : "";
|
|
1147
1245
|
break;
|
|
1148
1246
|
}
|
|
1149
1247
|
case "link": {
|
|
@@ -1237,9 +1335,20 @@ function parseAndFillSnippet(text, type) {
|
|
|
1237
1335
|
break;
|
|
1238
1336
|
}
|
|
1239
1337
|
case "code-block": {
|
|
1240
|
-
const m = t.match(
|
|
1338
|
+
const m = t.match(/^```[ \t]*([^\n]*)\n([\s\S]*?)\n[ \t]*```$/);
|
|
1241
1339
|
document.getElementById("snip-code-lang").value = m ? m[1].trim() : "";
|
|
1242
|
-
|
|
1340
|
+
let codeContent = m ? m[2] : "";
|
|
1341
|
+
if (m && _snippetInlineIndent) {
|
|
1342
|
+
const escapedIndent = _snippetInlineIndent.replace(
|
|
1343
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
1344
|
+
"\\$&",
|
|
1345
|
+
);
|
|
1346
|
+
codeContent = codeContent.replace(
|
|
1347
|
+
new RegExp("^" + escapedIndent, "gm"),
|
|
1348
|
+
"",
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
document.getElementById("snip-code-content").value = codeContent;
|
|
1243
1352
|
break;
|
|
1244
1353
|
}
|
|
1245
1354
|
case "blockquote": {
|
|
@@ -1257,6 +1366,17 @@ function parseAndFillSnippet(text, type) {
|
|
|
1257
1366
|
}
|
|
1258
1367
|
break;
|
|
1259
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
|
+
}
|
|
1260
1380
|
case "table": {
|
|
1261
1381
|
const allLines = t
|
|
1262
1382
|
.split("\n")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "living-ai-documentation",
|
|
3
|
-
"version": "1.
|
|
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": {
|