fix(search,excalidraw,e2e): recherche cassee + corruption .excalidraw.md + tests E2E #78
CI / lint (push) Successful in 54s
CI / security (push) Successful in 38s
CI / test (push) Successful in 1m7s
CI / build (push) Successful in 33s
CI / e2e (push) Failing after 10m52s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
CI / lint (push) Successful in 54s
CI / security (push) Successful in 38s
CI / test (push) Successful in 1m7s
CI / build (push) Successful in 33s
CI / e2e (push) Failing after 10m52s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- frontend/js/search.js : onSearchFilter appelee avec (vault, query, results) — renvoyait undefined, cassait le rendu et basculait sur la recherche offline (0 resultat). Regression plugins depuis 30f2df3.
- frontend/excalidraw-editor.html + js/excalidraw-viewer.js : ignorer onChange au montage (plus d'auto-save spurieux qui reecrivait .excalidraw.md en JSON brut) + preservation du format Obsidian frontmatter + compressed-json a la sauvegarde.
- frontend/js/app.js : __OBSIGATE_BOOTED pose apres init() (splash + E2E attendent les handlers lies).
- tests/e2e : goHome/login attendent le boot ; excalidraw.spec.js reecrit avec les selecteurs reels (modal, context menu, canvas.first()).
- ROADMAP : C8 (creation via menu contextuel) marque fait.
- Verifie : 86/86 E2E chromium-desktop, 599 backend, ruff, validate-imports, JSDOM.
This commit is contained in:
@@ -102,7 +102,7 @@
|
||||
<script type="module">
|
||||
import React from "https://esm.sh/react@^19.2.0?target=es2022";
|
||||
import { createRoot } from "https://esm.sh/react-dom@^19.2.0/client?target=es2022";
|
||||
import { decompressFromBase64 } from "https://esm.sh/[email protected]";
|
||||
import { decompressFromBase64, compressToBase64 } from "https://esm.sh/[email protected]";
|
||||
import * as ExcalidrawLib from "https://esm.sh/@excalidraw/[email protected]";
|
||||
|
||||
window.React = React;
|
||||
@@ -116,6 +116,12 @@
|
||||
let isDirty = false;
|
||||
let currentTheme = "light";
|
||||
let saveTimeout = null;
|
||||
// Excalidraw fires onChange on initial mount; ignore those so simply
|
||||
// opening a diagram does not trigger a spurious autosave (which would
|
||||
// rewrite a .excalidraw.md file as raw JSON and destroy its format).
|
||||
let ignoreChanges = true;
|
||||
// Source format: "md" for the Obsidian .excalidraw.md (compressed-json).
|
||||
let sourceIsMd = false;
|
||||
|
||||
// --- DOM refs ---
|
||||
const loadingEl = document.getElementById("loading");
|
||||
@@ -153,16 +159,23 @@
|
||||
const elements = excalidrawAPI.getSceneElements();
|
||||
const appState = excalidrawAPI.getAppState();
|
||||
const files = excalidrawAPI.getFiles();
|
||||
sendToParent({
|
||||
type: "save",
|
||||
data: {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
elements: elements,
|
||||
appState: appState,
|
||||
files: files,
|
||||
},
|
||||
});
|
||||
const data = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
elements: elements,
|
||||
appState: appState,
|
||||
files: files,
|
||||
};
|
||||
// Preserve the Obsidian .excalidraw.md format: ship the compressed
|
||||
// scene so the parent can rewrite the frontmatter + compressed-json block.
|
||||
if (sourceIsMd) {
|
||||
try {
|
||||
data.compressed = compressToBase64(JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error("Failed to compress Excalidraw scene:", e);
|
||||
}
|
||||
}
|
||||
sendToParent({ type: "save", data });
|
||||
markClean();
|
||||
btnSave.textContent = "💾 Saved!";
|
||||
setTimeout(() => { btnSave.textContent = "💾 Save"; }, 1200);
|
||||
@@ -191,10 +204,13 @@
|
||||
if (theme === "dark") {
|
||||
api.updateScene({ appState: { theme: "dark" } });
|
||||
}
|
||||
// Stop ignoring changes once the initial mount settles.
|
||||
setTimeout(() => { ignoreChanges = false; }, 800);
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
const onChange = React.useCallback((elements, state, files) => {
|
||||
if (ignoreChanges) return;
|
||||
markDirty();
|
||||
}, []);
|
||||
|
||||
@@ -225,6 +241,7 @@
|
||||
let elements, appState, files;
|
||||
// Handle compressed .excalidraw.md format
|
||||
if (msg.data.compressed) {
|
||||
sourceIsMd = true;
|
||||
try {
|
||||
const decompressed = decompressFromBase64(msg.data.compressed);
|
||||
const parsed = JSON.parse(decompressed);
|
||||
|
||||
+3
-2
@@ -113,13 +113,14 @@ async function init() {
|
||||
initAIFab();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
try {
|
||||
init();
|
||||
await init();
|
||||
Sync.init();
|
||||
} finally {
|
||||
// Signale au splash de démarrage (index.html #boot-splash) que l'app est
|
||||
// initialisée : le splash est retiré et le squelette stylé révélé.
|
||||
// Set AFTER init() resolves so every handler (search, tabs, …) is bound.
|
||||
window.__OBSIGATE_BOOTED = true;
|
||||
if (typeof window.__obsigateBootReady === "function") window.__obsigateBootReady();
|
||||
}
|
||||
|
||||
@@ -114,11 +114,22 @@ export function renderExcalidraw(container, data, vaultName, filePath, opts = {}
|
||||
*/
|
||||
async function _saveExcalidrawFile(state, excalidrawData) {
|
||||
try {
|
||||
const jsonStr = JSON.stringify(excalidrawData, null, 2);
|
||||
const isMd = (state.path || '').toLowerCase().endsWith('.excalidraw.md');
|
||||
let content;
|
||||
if (isMd && excalidrawData && excalidrawData.compressed) {
|
||||
// Preserve the Obsidian Excalidraw plugin format: frontmatter +
|
||||
// compressed-json block. Writing raw JSON here would destroy the
|
||||
// .excalidraw.md file (the plugin would no longer recognize it).
|
||||
content = `---\nexcalidraw-plugin: parsed\n---\n\n\`\`\`compressed-json\n${excalidrawData.compressed}\n\`\`\`\n`;
|
||||
} else {
|
||||
// Transport-only field must not leak into the pure JSON format.
|
||||
const { compressed, ...scene } = excalidrawData || {};
|
||||
content = JSON.stringify(scene, null, 2);
|
||||
}
|
||||
await api(`/api/file/${encodeURIComponent(state.vault)}/save?path=${encodeURIComponent(state.path)}&backup=false`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: jsonStr }),
|
||||
body: JSON.stringify({ content }),
|
||||
});
|
||||
// Toast is handled by the caller or we show a quick one
|
||||
showToast('Excalidraw saved', 'success');
|
||||
|
||||
@@ -804,7 +804,7 @@ export async function performAdvancedSearch(query, vaultFilter, tagFilter, offse
|
||||
state.advancedSearchTotal = data.total;
|
||||
state.advancedSearchOffset = ofs;
|
||||
// Plugin hook: filter search results
|
||||
data.results = await onSearchFilter(state.vault || "default", data.results);
|
||||
data.results = await onSearchFilter(state.vault || "default", query, data.results);
|
||||
renderAdvancedSearchResults(data, query, tagFilter);
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
Reference in New Issue
Block a user