feat: finalisation éditeur Forge + mode hors-ligne PWA + consolidation roadmap
- 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:
+49
-29
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user