fix: regenerate corrupted ui.js, search.js, viewer.js from app.js
This commit is contained in:
+217
-86
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user