Files
ObsiGate/frontend/js/editor-inline.js
T
bruno 788d84a2bf
CI / lint (push) Successful in 1m27s
CI / security (push) Successful in 57s
CI / test (push) Successful in 2m52s
CI / build (push) Successful in 53s
CI / e2e (push) Successful in 10m45s
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.
2026-09-16 11:43:41 -04:00

127 lines
4.7 KiB
JavaScript

/**
* ObsiGate — Inline editor host (ROADMAP #93)
*
* The edition surface (`#editor-container`, containing the CodeMirror editor or
* the Forge iframe) used to live inside the full-screen `#editor-modal` overlay,
* which covered both the document read view and the AI assistant sidebar.
*
* This module moves that container **into the content area of the document
* being read** so editing replaces the read view instead of overlaying it:
*
* `#content-area` (or the active pane `.pane-content` in split view)
* └── .editor-container.editor-inline ← the editor, replacing the document
*
* Keeping the same DOM nodes (same ids: `#editor-body`, `#editor-save`, …) means
* CodeMirror, autosave, Yjs collaboration, the AI toolbar and the Forge iframe
* keep working untouched — only their host moves.
*
* Classes:
* `.editor-inline` — set on the container while it hosts an inline session
* `.editor-inline-host` — set on the content area it replaces
* `#editor-modal.editor-inline-mode` — the overlay is neutralised (transparent,
* zero-size, no scroll) but keeps `active`, so the mobile
* ribbon anchored to it (`.me-ribbon`) still works.
*/
export const INLINE_CONTAINER_CLASS = 'editor-inline';
export const INLINE_HOST_CLASS = 'editor-inline-host';
export const INLINE_MODAL_CLASS = 'editor-inline-mode';
/** The editor overlay element (`#editor-modal` when present). */
export function getEditorModal() {
return document.getElementById('editor-modal');
}
/**
* The editor container, wherever it currently lives (modal or content area).
* Falls back to a class lookup so the helper also works on a DOM where the id
* was not set (tests, older markup).
*/
export function getEditorContainer() {
return (
document.getElementById('editor-container') ||
document.querySelector('.editor-container.' + INLINE_CONTAINER_CLASS)
);
}
/**
* Content area that should host the inline editor: the pane currently being
* read. Mirrors viewer.js `getContentArea()` (multi-pane aware).
*/
export function getInlineHostArea() {
if (typeof window !== 'undefined') {
if (window._activePaneContentArea) return window._activePaneContentArea;
if (
window.PaneManager &&
typeof window.PaneManager.isSplit === 'function' &&
window.PaneManager.isSplit() &&
typeof window.getActiveContentArea === 'function'
) {
return window.getActiveContentArea();
}
}
return document.getElementById('content-area');
}
/** True when an inline edition session currently owns a content area. */
export function isInlineEditorActive() {
const container = getEditorContainer();
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.
*/
export function mountEditorInline(area) {
const container = getEditorContainer();
const modal = getEditorModal();
if (!container || !modal || !area) return false;
// Already hosted by this very area — nothing to move.
if (container.parentElement === area) {
container.classList.add(INLINE_CONTAINER_CLASS);
area.classList.add(INLINE_HOST_CLASS);
modal.classList.add(INLINE_MODAL_CLASS);
return true;
}
area.classList.add(INLINE_HOST_CLASS);
area.replaceChildren(container);
container.classList.add(INLINE_CONTAINER_CLASS);
modal.classList.add(INLINE_MODAL_CLASS);
return true;
}
/**
* Move the editor container back into the modal and clear the inline classes.
* The host area is emptied (its read view is gone) and returned so the caller
* can re-render the document.
* @returns {HTMLElement|null} the content area that must be re-rendered.
*/
export function unmountEditorInline() {
const container = getEditorContainer();
const modal = getEditorModal();
if (!container || !modal) return null;
const parent = container.parentElement;
const host = parent && parent !== modal ? parent : null;
container.classList.remove(INLINE_CONTAINER_CLASS);
if (host) {
host.classList.remove(INLINE_HOST_CLASS);
if (typeof host.replaceChildren === 'function') host.replaceChildren();
else host.innerHTML = '';
}
modal.classList.remove(INLINE_MODAL_CLASS);
modal.appendChild(container);
return host;
}