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
|
-
|
|
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,
|
|
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 =
|
|
213
|
+
icon.className = iconClass;
|
|
159
214
|
icon.setAttribute("aria-hidden", "true");
|
|
160
215
|
const label = document.createElement("span");
|
|
161
|
-
label.textContent = window.t(
|
|
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
|
-
|
|
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 (
|
|
198
|
-
|
|
199
|
-
|
|
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,
|
|
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(
|
|
634
|
-
|
|
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
|
-
|
|
638
|
-
|
|
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
|
-
|
|
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 =
|
|
650
|
-
typeSelect.classList.toggle("cursor-not-allowed",
|
|
651
|
-
typeSelect.classList.toggle("opacity-70",
|
|
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", !
|
|
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", !
|
|
660
|
-
card.classList.toggle("max-w-5xl",
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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": {
|