feat: finalisation éditeur Forge + mode hors-ligne PWA + consolidation roadmap
CI / lint (push) Failing after 7s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / security (push) Failing after -7s

- Forge: Tab indent, Ctrl+K, auto-focus, AI status, messages erreur FR
- Forge: slash commands clean, cache-bust iframe, preview serveur
- Forge: iframe 82vh, hauteur auto, table inline, sélecteur langage
- PWA hors-ligne: IndexedDB (offline-db.js + offline.js)
- Mermaid: thème dark complet, highlight.js preview
- Backend: màj CSP, rename chain, bookmarks/history/shares
- Roadmap: consolidation dans docs/ROADMAP.md (75 items)
- Infra: docker-compose.podman.yml, suppression test-vault
- .gitignore: exclusion odysseus/
This commit is contained in:
2026-06-14 20:27:37 -04:00
parent 0cdebc5b40
commit feeb46b873
31 changed files with 1769 additions and 981 deletions
+49 -29
View File
@@ -275,10 +275,22 @@ async function openEditor(vaultName, filePath) {
state.editorPath = filePath;
const modal = document.getElementById("editor-modal");
const titleEl = document.getElementById("editor-title");
const titleInput = document.getElementById("editor-title-input");
const filenameInput = document.getElementById("editor-filename-input");
const statFile = document.getElementById("editor-stat-file");
const statWords = document.getElementById("editor-stat-words");
const bodyEl = document.getElementById("editor-body");
titleEl.textContent = `Édition: ${filePath.split("/").pop()}`;
// Restore header/footer (may be hidden by Forge)
var headerEl = modal.querySelector(".editor-header");
var footerEl = modal.querySelector(".editor-footer");
var container = modal.querySelector(".editor-container");
if (headerEl) headerEl.style.display = "";
if (footerEl) footerEl.style.display = "";
if (container) container.style.maxHeight = "";
filenameInput.value = filePath.split("/").pop();
statFile.textContent = vaultName + "/" + filePath;
// Fetch raw content
const rawUrl = `/api/file/${encodeURIComponent(vaultName)}/raw?path=${encodeURIComponent(filePath)}`;
@@ -322,6 +334,11 @@ async function openEditor(vaultName, filePath) {
if (update.docChanged) {
clearTimeout(window._obsigateAutoSaveTimer);
window._obsigateAutoSaveTimer = setTimeout(() => saveFile(true), 2000);
// Mark dirty
var sd = document.getElementById("editor-save-dot");
var sl = document.getElementById("editor-save-label");
if (sd) sd.className = "editor-save-dot dirty";
if (sl) sl.textContent = "Unsaved";
}
}),
];
@@ -478,9 +495,13 @@ async function saveFile(silent = false) {
const content = state.editorView ? state.editorView.state.doc.toString() : state.fallbackEditorEl.value;
const saveBtn = document.getElementById("editor-save");
const originalHTML = saveBtn ? saveBtn.innerHTML : '';
const saveDot = document.getElementById("editor-save-dot");
const saveLabel = document.getElementById("editor-save-label");
try {
// Update save dot to saving state
if (saveDot) { saveDot.className = 'editor-save-dot saving'; }
if (saveLabel) saveLabel.textContent = 'Saving...';
if (saveBtn && !silent) {
saveBtn.disabled = true;
saveBtn.innerHTML = '<i data-lucide="loader" style="width:16px;height:16px"></i>';
@@ -499,35 +520,41 @@ async function saveFile(silent = false) {
throw new Error(error.detail || "Erreur de sauvegarde");
}
// Update save dot to saved state
if (saveDot) { saveDot.className = 'editor-save-dot ok'; }
if (saveLabel) saveLabel.textContent = 'Saved';
if (silent) {
// Auto-save: brief green flash on save button
if (saveBtn) {
saveBtn.style.background = '#22c55e';
saveBtn.style.color = '#fff';
saveBtn.style.borderColor = '#22c55e';
saveBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px"></i>';
safeCreateIcons();
setTimeout(() => {
if (saveBtn) {
saveBtn.style.background = '';
saveBtn.style.color = '';
saveBtn.style.borderColor = '';
saveBtn.innerHTML = originalHTML;
}
}, 1500);
}
setTimeout(() => {
if (saveBtn) {
saveBtn.style.background = '';
saveBtn.style.color = '';
saveBtn.style.borderColor = '';
}
}, 1500);
} else {
saveBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px"></i>';
safeCreateIcons();
setTimeout(() => closeEditor(), 800);
closeEditor();
}
} catch (err) {
console.error("Save error:", err);
alert(`Erreur: ${err.message}`);
saveBtn.innerHTML = originalHTML;
saveBtn.disabled = false;
safeCreateIcons();
if (saveDot) { saveDot.className = 'editor-save-dot err'; }
if (saveLabel) saveLabel.textContent = 'Erreur';
// If offline, queue the save for later sync
if (!navigator.onLine) {
try {
const { queueOfflineSave } = await import('./offline.js');
await queueOfflineSave(state.editorVault, state.editorPath, content);
alert('Vous etes hors-ligne. La modification a ete enregistree localement.');
} catch (e) { alert('Erreur: ' + err.message); }
} else {
alert('Erreur: ' + err.message);
}
}
}
@@ -573,14 +600,7 @@ function initEditor() {
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();
}
});
saveBtn.addEventListener("click", () => saveFile());
// ESC to close
document.addEventListener("keydown", (e) => {