- One mounted shell with four kept-alive tabs; pens open/switch tabs, closing only hides the shell so unsaved page-editor state survives until a real reload; admin panels never reload the page, regions are refreshed in place via the shared swapdoc.js helper - SiteEditor split: site settings vs. the new StructureEditor tree tab; BannerEditor split out of the old site editor - Article editor format bar (bold/italic/code/link/table/image) with a table size picker and Ctrl/Cmd-B/I/S bindings; fenced code block on an empty line; icon-only save button disabled (saturate(0)) when clean - Media uploads use icon buttons; favicon upload by clicking the preview tile; placeholders reserved for actual defaults in effect - Banner/site pens and auth buttons grouped in an .editor-pens container; page pen no longer shown to anonymous visitors - While editing, window scroll is locked, the panel exactly fills the available window height, and only #main scrolls; editor scroll drives the article - Banner design inherit label names the design and its true source; backend banner_design_source excludes the node's own setting and the doc payload carries banner_design_inherited
99 lines
3.7 KiB
JavaScript
99 lines
3.7 KiB
JavaScript
// Pagerite editor entry. A single tabbed EditorShell is mounted in a
|
|
// dynamically created host div inside the static document. The shell hosts
|
|
// four tabs: PageEditor (Markdown + preview), BannerEditor (per-page banner
|
|
// HTML/design), SiteEditor (site-wide config), and StructureEditor (the
|
|
// site structure tree).
|
|
//
|
|
// Individual pens are shorthands that open the shell on a particular tab;
|
|
// once the shell is open, pens switch tabs instead of closing/remounting.
|
|
// Closing hides the shell but keeps the Vue app mounted, so editor state —
|
|
// including unsaved page text — survives and editing can continue on the
|
|
// next pen click; it is lost only on a real page reload.
|
|
if (import.meta.env.DEV) {
|
|
// Base styles only; the theme CSS is imported by pagerite.js (which
|
|
// always runs first — the editor opens from public pages).
|
|
import("./assets/pagerite.css");
|
|
}
|
|
|
|
import { createApp } from 'vue'
|
|
import EditorShell from './EditorShell.vue'
|
|
|
|
let host = null
|
|
let app = null
|
|
let savedTitle = null
|
|
let visible = false
|
|
|
|
export function openEditor(path, { mode = 'page' } = {}) {
|
|
if (app) {
|
|
// Shell already mounted: re-show it if hidden, switch to the requested
|
|
// tab and retarget the editors to the current page (the shell survives
|
|
// fetch-navigation).
|
|
if (!visible) showEditor()
|
|
document.body.dataset.editorMode = mode
|
|
dispatchEvent(new CustomEvent('pagerite:switch-editor', { detail: { mode, path } }))
|
|
return
|
|
}
|
|
savedTitle = document.title
|
|
host = document.createElement('div')
|
|
host.className = 'editor-host'
|
|
// Docked inside #content: below the banner, next to the article only.
|
|
document.getElementById('content').prepend(host)
|
|
document.body.classList.add('editing')
|
|
// Which tab is active; pagerite.js uses this to decide whether a pen click
|
|
// closes the panel or switches tabs.
|
|
document.body.dataset.editorMode = mode
|
|
visible = true
|
|
app = createApp(EditorShell, {
|
|
pagePath: path,
|
|
initialMode: mode,
|
|
onClose: closeEditor,
|
|
})
|
|
app.mount(host)
|
|
// The slide-in (editor-slide-in in pagerite.css) is a one-shot open
|
|
// effect; once finished, drop it so that later stylesheet swaps (theme
|
|
// change re-creating @keyframes) cannot restart it.
|
|
const root = host.firstElementChild
|
|
root?.addEventListener('animationend', function done(e) {
|
|
if (e.target !== root) return
|
|
root.removeEventListener('animationend', done)
|
|
root.style.animation = 'none'
|
|
})
|
|
}
|
|
|
|
function showEditor() {
|
|
savedTitle = document.title
|
|
host.style.display = ''
|
|
host.firstElementChild?.classList.remove('closing')
|
|
document.body.classList.add('editing')
|
|
visible = true
|
|
dispatchEvent(new CustomEvent('pagerite:editor-shown'))
|
|
}
|
|
|
|
export function closeEditor() {
|
|
if (!visible) return
|
|
visible = false
|
|
document.body.classList.remove('editing')
|
|
// dataset.editorMode is kept while hidden: the tabs use it to tell whether
|
|
// a pagerite:editor-shown event targets them.
|
|
// Slide the panel out in sync with the page shifting back, then hide it.
|
|
host.firstElementChild?.classList.add('closing')
|
|
const h = host
|
|
setTimeout(() => { h.style.display = 'none' }, 250)
|
|
dispatchEvent(new CustomEvent('pagerite:editor-hidden'))
|
|
// Restore the server-rendered title for the current URL. Re-fetching makes
|
|
// sure a brand change in the site editor or an in-place navigation leaves
|
|
// the correct public title behind.
|
|
const restoreTitle = savedTitle
|
|
savedTitle = null
|
|
fetch(location.pathname)
|
|
.then((r) => r.text())
|
|
.then((html) => {
|
|
if (visible) return // reopened meanwhile; the editor owns the title
|
|
const doc = new DOMParser().parseFromString(html, 'text/html')
|
|
if (doc.title) document.title = doc.title
|
|
})
|
|
.catch(() => {
|
|
if (!visible && restoreTitle != null) document.title = restoreTitle
|
|
})
|
|
}
|