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.
127 lines
4.7 KiB
JavaScript
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;
|
|
}
|