fix(editeur): #93 garde-fous de session d'edition inline (onglets, ecriture externe) + hooks de versionnage documentes
Une session d'edition inline (Editer/Forge) qui possede la zone de contenu etait detruite par deux chemins qui vident cette zone : activation d'onglet (TabManager.activate, PaneTabManager.activate) -> detachInlineEditor() avant le placeholder ; evenement SSE index_updated -> reloadExternalWrite() recharge le tampon de l'editeur au lieu de re-rendre la vue lecture. Nouveau helper queryEditor() ; closeEditor() remet le conteneur dans la modale avant de restaurer en-tete/pied/marque. docs/CONTRIBUTING.md documente scripts/install-hooks.sh.
This commit is contained in:
@@ -69,6 +69,17 @@ export function isInlineEditorActive() {
|
||||
return !!(container && container.classList.contains(INLINE_CONTAINER_CLASS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Query an element inside the editor container, wherever it currently lives
|
||||
* (modal or content area). Use it instead of `modal.querySelector(...)` for the
|
||||
* header/footer/brand, which move with the container in inline mode (#93).
|
||||
*/
|
||||
export function queryEditor(selector) {
|
||||
const container = getEditorContainer();
|
||||
const scope = container || getEditorModal();
|
||||
return scope ? scope.querySelector(selector) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the editor container into `area`, replacing the rendered document.
|
||||
* @returns {boolean} true when the container was mounted inline.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
import { state } from './state.js';
|
||||
import { api } from './auth.js';
|
||||
import { escapeHtml, getFileIcon, safeCreateIcons } from './utils.js';
|
||||
import { escapeHtml, getFileIcon, safeCreateIcons, detachInlineEditor } from './utils.js';
|
||||
|
||||
const PANE_MAX = 4;
|
||||
|
||||
@@ -154,6 +154,9 @@ function createPaneTabManager(paneId) {
|
||||
|
||||
async activate(tabId) {
|
||||
if (this._activeTabId === tabId && this._tabs.length > 0) return;
|
||||
// #93 — an inline edition session owns this pane's content area: release
|
||||
// it before the load placeholder / read view wipes it off the DOM.
|
||||
detachInlineEditor();
|
||||
if (this._activeTabId && this._tabCache[this._activeTabId])
|
||||
this._saveCurrentTabState();
|
||||
|
||||
|
||||
+6
-2
@@ -6,7 +6,7 @@ import { api, AuthManager } from './auth.js';
|
||||
import { loadVaults, loadTags, refreshTagsForContext, refreshSidebarTreePreservingState } from './sidebar.js';
|
||||
import { loadRecentFiles, initAboutModal } from './config.js';
|
||||
import { openFile } from './viewer.js';
|
||||
import { closeEditor } from './utils.js';
|
||||
import { closeEditor, reloadExternalWrite } from './utils.js';
|
||||
import { initOffline, syncFileIndexFromServer } from './offline.js';
|
||||
import { initThemes } from './themes.js';
|
||||
|
||||
@@ -166,7 +166,11 @@ export const IndexUpdateManager = (() => {
|
||||
if (state.currentVault && state.currentPath) {
|
||||
const changed = (data.changes || []).some((c) => c.vault === state.currentVault && c.path === state.currentPath);
|
||||
if (changed) {
|
||||
openFile(state.currentVault, state.currentPath);
|
||||
// #93 — an inline edition session (or Forge) owns the content area:
|
||||
// re-render the read view below it would destroy the editor mid-
|
||||
// session. Reload the editor buffer from disk instead (same path as
|
||||
// an external AI write).
|
||||
reloadExternalWrite(state.currentVault, state.currentPath);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
+6
-2
@@ -2,7 +2,7 @@
|
||||
import { api, AuthManager, initLoginForm } from './auth.js';
|
||||
import { state } from './state.js';
|
||||
import { openFile, showWelcome, renderFile, el } from './viewer.js';
|
||||
import { safeCreateIcons, getFileIcon, escapeHtml } from './utils.js';
|
||||
import { safeCreateIcons, getFileIcon, escapeHtml, detachInlineEditor } from './utils.js';
|
||||
import { syncActiveFileTreeItem, refreshSidebarTreePreservingState } from './sidebar.js';
|
||||
import { GraphViewManager } from './graph.js';
|
||||
import { DashboardConflictsWidget } from './dashboard.js';
|
||||
@@ -2014,7 +2014,11 @@ export const TabManager = {
|
||||
/** Activate a specific tab */
|
||||
async activate(tabId) {
|
||||
if (this._activeTabId === tabId && this._tabs.length > 0) return;
|
||||
|
||||
|
||||
// #93 — an inline edition session owns this content area: release it before
|
||||
// the load placeholder / read view wipes it off the DOM.
|
||||
detachInlineEditor();
|
||||
|
||||
// Save current tab state
|
||||
if (this._activeTabId && this._tabCache[this._activeTabId]) {
|
||||
this._saveCurrentTabState();
|
||||
|
||||
+14
-13
@@ -6,9 +6,11 @@ import { createAIToolbar } from './ai.js';
|
||||
import { startCollab, stopCollab } from './collab.js';
|
||||
import {
|
||||
getInlineHostArea,
|
||||
getEditorContainer,
|
||||
mountEditorInline,
|
||||
unmountEditorInline,
|
||||
isInlineEditorActive,
|
||||
queryEditor,
|
||||
} from './editor-inline.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -372,9 +374,9 @@ async function openEditor(vaultName, filePath) {
|
||||
const bodyEl = document.getElementById("editor-body");
|
||||
|
||||
// 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");
|
||||
var headerEl = queryEditor(".editor-header");
|
||||
var footerEl = queryEditor(".editor-footer");
|
||||
var container = getEditorContainer();
|
||||
if (headerEl) headerEl.style.display = "";
|
||||
if (footerEl) footerEl.style.display = "";
|
||||
if (container) container.style.maxHeight = "";
|
||||
@@ -580,14 +582,6 @@ function closeEditor() {
|
||||
const modal = document.getElementById("editor-modal");
|
||||
if (!modal) return;
|
||||
modal.classList.remove("active");
|
||||
// Restore brand text
|
||||
var brand = modal.querySelector('.editor-brand');
|
||||
if (brand) brand.textContent = 'ObsiGate';
|
||||
// Restore header/footer (may be hidden by Forge)
|
||||
var headerEl = modal.querySelector(".editor-header");
|
||||
var footerEl = modal.querySelector(".editor-footer");
|
||||
if (headerEl) headerEl.style.display = "";
|
||||
if (footerEl) footerEl.style.display = "";
|
||||
stopCollab();
|
||||
if (state.editorView) {
|
||||
state.editorView.destroy();
|
||||
@@ -598,9 +592,16 @@ function closeEditor() {
|
||||
// Remove forge iframe if present
|
||||
var forgeFrame = document.getElementById("forge-iframe");
|
||||
if (forgeFrame) forgeFrame.remove();
|
||||
// #93 — give the editor container back to the modal; an inline session also
|
||||
// releases the content area, whose read view must be re-rendered.
|
||||
// #93 — hand the editor container back to the modal BEFORE restoring the
|
||||
// header/footer/brand: they live inside the container and therefore travel
|
||||
// with it in inline mode (a `modal.querySelector` would miss them).
|
||||
const host = unmountEditorInline();
|
||||
var brand = queryEditor('.editor-brand');
|
||||
if (brand) brand.textContent = 'ObsiGate';
|
||||
var headerEl = queryEditor(".editor-header");
|
||||
var footerEl = queryEditor(".editor-footer");
|
||||
if (headerEl) headerEl.style.display = "";
|
||||
if (footerEl) footerEl.style.display = "";
|
||||
_invalidateActiveTabCache(vault, path);
|
||||
state.editorVault = null;
|
||||
state.editorPath = null;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { api, AuthManager } from './auth.js';
|
||||
import { state } from './state.js';
|
||||
import { escapeHtml, safeCreateIcons, safeHighlight, getFileIcon, openEditor, copyToClipboard, activateInlineEditor, detachInlineEditor } from './utils.js';
|
||||
import { isInlineEditorActive } from './editor-inline.js';
|
||||
import { isInlineEditorActive, queryEditor } from './editor-inline.js';
|
||||
import { TabManager, closeMobileSidebar, ContextMenuManager, RightSidebarManager, showToast, buildFrontmatterCard } from './ui.js';
|
||||
import { syncActiveFileTreeItem, searchByTag, TagFilterService, refreshSidebarTreePreservingState, focusPathInSidebar } from './sidebar.js';
|
||||
import { AutocompleteDropdown, performAdvancedSearch } from './search.js';
|
||||
@@ -768,8 +768,9 @@ export function renderFile(data) {
|
||||
state.editorVault = data.vault;
|
||||
state.editorPath = data.path;
|
||||
}
|
||||
// Update header: show "Forge" brand + filename
|
||||
var brand = modal.querySelector(".editor-brand");
|
||||
// Update header: show "Forge" brand + filename (the header travels with the
|
||||
// container in inline mode — query it wherever it lives, #93)
|
||||
var brand = queryEditor(".editor-brand");
|
||||
var titleInp = document.getElementById("editor-title-input");
|
||||
var fileInp = document.getElementById("editor-filename-input");
|
||||
if (brand) brand.textContent = t("viewer.forge_brand");
|
||||
|
||||
Reference in New Issue
Block a user