fix: regenerate corrupted ui.js, search.js, viewer.js from app.js
CI / lint (push) Successful in 12s
CI / security (push) Successful in 8s
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-05-28 16:49:29 -04:00
parent a2ff9297ce
commit 6d36b53b3a
3 changed files with 870 additions and 530 deletions
+217 -86
View File
@@ -1,9 +1,5 @@
/* ObsiGate — Viewer module: Outline, ScrollSpy, ReadingProgress, file viewer, frontmatter card, editor init */
/* ObsiGate — Auto-extracted module */
import { state } from './state.js';
import { escapeHtml, safeCreateIcons, safeHighlight, getFileIcon } from "./utils.js";
// initEditor is defined in utils.js — re-exported below.
// ---------------------------------------------------------------------------
// Outline/TOC Manager
// ---------------------------------------------------------------------------
@@ -118,7 +114,7 @@ const OutlineManager = {
outlineList.appendChild(item);
});
state.headingsCache = headings;
headingsCache = headings;
},
/**
@@ -148,9 +144,9 @@ const OutlineManager = {
* Set active heading in outline
*/
setActiveHeading(headingId) {
if (state.activeHeadingId === headingId) return;
if (activeHeadingId === headingId) return;
state.activeHeadingId = headingId;
activeHeadingId = headingId;
const items = document.querySelectorAll(".outline-item");
items.forEach((item) => {
@@ -182,12 +178,11 @@ const OutlineManager = {
destroy() {
ScrollSpyManager.destroy();
ReadingProgressManager.destroy();
state.headingsCache = [];
state.activeHeadingId = null;
headingsCache = [];
activeHeadingId = null;
},
};
// ---------------------------------------------------------------------------
// Scroll Spy Manager
// ---------------------------------------------------------------------------
@@ -245,7 +240,6 @@ const ScrollSpyManager = {
},
};
// ---------------------------------------------------------------------------
// Reading Progress Manager
// ---------------------------------------------------------------------------
@@ -311,15 +305,14 @@ const ReadingProgressManager = {
},
};
// ---------------------------------------------------------------------------
// File viewer
// ---------------------------------------------------------------------------
async function openFile(vaultName, filePath) {
state.currentVault = vaultName;
state.currentPath = filePath;
state.showingSource = false;
state.cachedRawSource = null;
currentVault = vaultName;
currentPath = filePath;
showingSource = false;
cachedRawSource = null;
// Highlight active
syncActiveFileTreeItem(vaultName, filePath);
@@ -442,12 +435,12 @@ function renderFile(data) {
copyBtn.addEventListener("click", async () => {
try {
// Fetch raw content if not already cached
if (!state.cachedRawSource) {
if (!cachedRawSource) {
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
const rawData = await api(rawUrl);
state.cachedRawSource = rawData.raw;
cachedRawSource = rawData.raw;
}
await navigator.clipboard.writeText(state.cachedRawSource);
await navigator.clipboard.writeText(cachedRawSource);
copyBtn.lastChild.textContent = "Copié !";
setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500);
} catch (err) {
@@ -505,10 +498,194 @@ function renderFile(data) {
}
} catch (e) { /* ignore */ }
})();
shareBtn.addEventListener("click",
shareBtn.addEventListener("click", () => openShareDialog(data.vault, data.path));
... [OUTPUT TRUNCATED - 13907 chars omitted out of 63907 total] ...
// Bookmark button — check if already bookmarked
const bookmarkBtn = el("button", { class: "btn-action btn-bookmark", title: "Ajouter/Retirer des bookmarks" }, [icon("bookmark-plus", 14), document.createTextNode("Bookmark")]);
// Check bookmark status and color the button
(async () => {
try {
const bms = await api("/api/bookmarks");
if (Array.isArray(bms) && bms.some(b => b.vault === data.vault && b.path === data.path)) {
bookmarkBtn.classList.add("active");
bookmarkBtn.title = "Retirer des bookmarks";
}
} catch (e) { /* ignore */ }
})();
bookmarkBtn.addEventListener("click", async () => {
try {
const res = await api("/api/bookmarks/toggle", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ vault: data.vault, path: data.path, title: data.title }) });
bookmarkBtn.classList.toggle("active", res.bookmarked);
bookmarkBtn.title = res.bookmarked ? "Retirer des bookmarks" : "Ajouter aux bookmarks";
showToast(res.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success");
if (typeof DashboardBookmarkWidget !== "undefined") DashboardBookmarkWidget.load();
} catch (err) { showToast("Erreur: " + err.message, "error"); }
});
// Frontmatter — Accent Card
let fmSection = null;
if (data.frontmatter && Object.keys(data.frontmatter).length > 0) {
fmSection = buildFrontmatterCard(data.frontmatter);
}
// Content container (rendered HTML)
const mdDiv = el("div", { class: "md-content", id: "file-rendered-content" });
mdDiv.innerHTML = data.html;
// Raw source container (hidden initially)
const rawDiv = el("div", { class: "raw-source-view", id: "file-raw-content", style: "display:none" });
// Source button toggle logic
sourceBtn.addEventListener("click", async () => {
const rendered = document.getElementById("file-rendered-content");
const raw = document.getElementById("file-raw-content");
if (!rendered || !raw) return;
showingSource = !showingSource;
if (showingSource) {
sourceBtn.classList.add("active");
if (!cachedRawSource) {
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
const rawData = await api(rawUrl);
cachedRawSource = rawData.raw;
}
raw.textContent = cachedRawSource;
rendered.style.display = "none";
raw.style.display = "block";
} else {
sourceBtn.classList.remove("active");
rendered.style.display = "block";
raw.style.display = "none";
}
});
// Assemble
area.innerHTML = "";
area.appendChild(breadcrumb);
area.appendChild(el("div", { class: "file-header" }, [el("div", { class: "file-title" }, [document.createTextNode(data.title)]), tagsDiv, el("div", { class: "file-actions" }, [copyBtn, sourceBtn, mdBtn, pdfBtn, editBtn, openNewWindowBtn, tocBtn, shareBtn, bookmarkBtn])]));
if (fmSection) area.appendChild(fmSection);
area.appendChild(mdDiv);
area.appendChild(rawDiv);
// Backlinks panel
if (data.is_markdown) {
renderBacklinksPanel(data.vault, data.path, area);
}
// Highlight code blocks
area.querySelectorAll("pre code").forEach((block) => {
safeHighlight(block);
});
// Wire up wikilinks
area.querySelectorAll(".wikilink").forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
const v = link.getAttribute("data-vault");
const p = link.getAttribute("data-path");
if (v && p) openFile(v, p);
});
});
safeCreateIcons();
area.scrollTop = 0;
// Initialize outline/TOC for this document
OutlineManager.init();
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function escapeHtml(str) {
if (!str) return "";
return String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function el(tag, attrs, children) {
const e = document.createElement(tag);
if (attrs) {
Object.entries(attrs).forEach(([k, v]) => {
// Skip boolean false for standard HTML boolean attributes to avoid setAttribute("checked", "false") bug
if (v === false && (k === "checked" || k === "disabled" || k === "hidden" || k === "required" || k === "readonly")) {
return;
}
e.setAttribute(k, v);
});
}
if (children) {
children.forEach((c) => {
if (c) e.appendChild(c);
});
}
return e;
}
function icon(name, size) {
const i = document.createElement("i");
i.setAttribute("data-lucide", name);
i.style.width = size + "px";
i.style.height = size + "px";
i.classList.add("icon");
return i;
}
function smallBadge(count) {
const s = document.createElement("span");
s.className = "badge-small";
s.style.cssText = "font-size:0.68rem;color:var(--text-muted);margin-left:4px";
s.textContent = `(${count})`;
return s;
}
function getContextMenuPositionFromElement(target) {
const rect = target.getBoundingClientRect();
return {
x: Math.min(rect.right - 8, window.innerWidth - 16),
y: Math.min(rect.top + rect.height / 2, window.innerHeight - 16),
};
}
function attachTreeItemActionButton(itemEl, vault, path, type, isReadonly) {
const button = document.createElement("button");
button.type = "button";
button.className = "tree-item-action-btn";
button.setAttribute("aria-label", "Afficher le menu d’actions");
button.setAttribute("title", "Actions");
const iconEl = icon("more-vertical", 16);
button.appendChild(iconEl);
button.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const pos = getContextMenuPositionFromElement(button);
ContextMenuManager.show(pos.x, pos.y, vault, path, type, isReadonly);
});
itemEl.appendChild(button);
// Ensure Lucide icons are rendered for the button
setTimeout(() => {
safeCreateIcons();
}, 0);
}
function attachTreeItemLongPress(itemEl, getMenuData) {
let pressTimer = null;
let pressHandled = false;
let startX = 0;
let startY = 0;
const longPressDelay = 550;
const moveThreshold = 10;
const clearPressTimer = () => {
if (pressTimer) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
itemEl.addEventListener("touchstart", (e) => {
if (!e.touches || e.touches.length !== 1) return;
pressHandled = false;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
clearPressTimer();
pressTimer = setTimeout(() => {
@@ -548,7 +725,7 @@ function renderFile(data) {
}
function getVaultIcon(vaultName, size = 16) {
const v = state.allVaults.find((val) => val.name === vaultName);
const v = allVaults.find((val) => val.name === vaultName);
const type = v ? v.type : "VAULT";
if (type === "DIR") {
@@ -771,10 +948,10 @@ function showWelcome() {
DashboardConflictsWidget.load();
}
if (typeof DashboardRecentWidget !== "undefined") {
DashboardRecentWidget.load(state.selectedContextVault);
DashboardRecentWidget.load(selectedContextVault);
}
if (typeof DashboardBookmarkWidget !== "undefined") {
DashboardBookmarkWidget.load(state.selectedContextVault);
DashboardBookmarkWidget.load(selectedContextVault);
}
if (typeof DashboardSharedWidget !== "undefined") {
DashboardSharedWidget.load();
@@ -825,9 +1002,9 @@ async function loadSavedSearches() {
// Apply the saved search
const input = document.getElementById("search-input");
if (input) input.value = s.query;
state.searchCaseSensitive = s.case_sensitive || false;
state.searchWholeWord = s.whole_word || false;
state.searchRegex = s.regex || false;
searchCaseSensitive = s.case_sensitive || false;
searchWholeWord = s.whole_word || false;
searchRegex = s.regex || false;
if (typeof _updateToggleUI === "function") _updateToggleUI();
if (s.include_paths) {
const incl = document.getElementById("search-include-input");
@@ -842,8 +1019,8 @@ async function loadSavedSearches() {
AutocompleteDropdown._suppressNext = true;
const vault = s.vault || "all";
if (input) { input.dispatchEvent(new Event("input")); }
clearTimeout(state.searchTimeout);
state.advancedSearchOffset = 0;
clearTimeout(searchTimeout);
advancedSearchOffset = 0;
performAdvancedSearch(s.query, vault, null);
});
});
@@ -882,58 +1059,15 @@ function goHome() {
document.querySelectorAll(".tree-item.active").forEach((el) => el.classList.remove("active"));
state.currentVault = null;
state.currentPath = null;
state.showingSource = false;
state.cachedRawSource = null;
currentVault = null;
currentPath = null;
showingSource = false;
cachedRawSource = null;
closeMobileSidebar();
showWelcome();
}
// initEditor wires up the editor modal — editor functions (openEditor, closeEditor, saveFile, deleteFile) are in utils.js
function initEditor() {
const cancelBtn = document.getElementById("editor-cancel");
const deleteBtn = document.getElementById("editor-delete");
const saveBtn = document.getElementById("editor-save");
const modal = document.getElementById("editor-modal");
cancelBtn.addEventListener("click", closeEditor);
deleteBtn.addEventListener("click", deleteFile);
saveBtn.addEventListener("click", saveFile);
// Close on overlay click
modal.addEventListener("click", (e) => {
if (e.target === modal) {
closeEditor();
}
});
// ESC to close
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && modal.classList.contains("active")) {
closeEditor();
}
});
// Fix mouse wheel scrolling in editor
modal.addEventListener(
"wheel",
(e) => {
const editorBody = document.getElementById("editor-body");
if (editorBody && editorBody.contains(e.target)) {
// Let the editor handle the scroll
return;
}
// Prevent modal from scrolling if not in editor area
e.preventDefault();
},
{ passive: false },
);
}
// ---------------------------------------------------------------------------
// SSE Client — IndexUpdateManager
// ---------------------------------------------------------------------------
@@ -1082,15 +1216,15 @@ const IndexUpdateManager = (() => {
// Toast removed: silent auto-indexing — no notification needed
// Refresh sidebar and tags if affected vault matches current context
const affectsCurrentVault = state.selectedContextVault === "all" || (data.vaults || []).includes(state.selectedContextVault);
const affectsCurrentVault = selectedContextVault === "all" || (data.vaults || []).includes(selectedContextVault);
if (affectsCurrentVault) {
try {
await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]);
// Refresh current file if it was updated
if (currentVault && state.currentPath) {
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === state.currentPath);
if (currentVault && currentPath) {
const changed = (data.changes || []).some((c) => c.vault === currentVault && c.path === currentPath);
if (changed) {
openFile(state.currentVault, state.currentPath);
openFile(currentVault, currentPath);
}
}
} catch (err) {
@@ -1099,7 +1233,7 @@ const IndexUpdateManager = (() => {
}
// Refresh recent tab if it is active
if (state.activeSidebarTab === "recent") {
if (activeSidebarTab === "recent") {
const vaultFilter = document.getElementById("recent-vault-filter");
loadRecentFiles(vaultFilter ? vaultFilter.value || null : null);
}
@@ -1249,7 +1383,4 @@ function _renderSyncPanel(panel) {
}
panel.innerHTML = html;
}
export { OutlineManager, ScrollSpyManager, ReadingProgressManager, openFile, buildFrontmatterCard, initEditor };
}