Update page title to indicate editing flows
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
// exponential backoff after a failure; unsaved text and pending saves
|
||||
// survive a disconnect. Editor scroll drives the document scroll, keeping the
|
||||
// rendered article at the cursor's position.
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { EditorView, basicSetup } from 'codemirror'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { markdown } from '@codemirror/lang-markdown'
|
||||
@@ -53,6 +53,19 @@ function normPath(p) {
|
||||
return p.trim().replace(/^\/+|\/+$/g, '')
|
||||
}
|
||||
|
||||
function pageLabel() {
|
||||
return title.value.trim() || ('/' + (path.value || ''))
|
||||
}
|
||||
|
||||
// Show the article title (or path for a new page) plus the edit pen in the
|
||||
// window title while editing; the server-rendered public title is restored on
|
||||
// close.
|
||||
function updateWindowTitle() {
|
||||
document.title = `${pageLabel()} 🖊️`
|
||||
}
|
||||
|
||||
watch([title, path], updateWindowTitle)
|
||||
|
||||
function requestRender() {
|
||||
// No debounce: server-side rendering is fast enough per keystroke.
|
||||
if (!view) return
|
||||
@@ -228,6 +241,7 @@ function connect() {
|
||||
|
||||
onMounted(() => {
|
||||
connect()
|
||||
updateWindowTitle()
|
||||
|
||||
view = new EditorView({
|
||||
state: EditorState.create({
|
||||
|
||||
+13
-20
@@ -10,7 +10,7 @@
|
||||
// real — a label with a title and slug, with content (landing page) or
|
||||
// without (category whose URL renders a placeholder page). The front page
|
||||
// is a top-level row with an empty slug, not the parent of the others.
|
||||
import { computed, onMounted, onUnmounted, provide, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
|
||||
import StructureTree from './StructureTree.vue'
|
||||
import { EditorView, basicSetup } from 'codemirror'
|
||||
import { EditorState, Compartment } from '@codemirror/state'
|
||||
@@ -47,19 +47,6 @@ let cssSyncing = false // set while replacing the CSS document programmatically
|
||||
const customCss = ref('')
|
||||
const cssEl = ref(null)
|
||||
|
||||
// path -> node, for quick lookups (current title, delete checks).
|
||||
const flatMap = computed(() => {
|
||||
const map = {}
|
||||
const walk = (nodes) => {
|
||||
for (const n of nodes) {
|
||||
map[n.path] = n
|
||||
walk(n.children)
|
||||
}
|
||||
}
|
||||
walk(tree.value)
|
||||
return map
|
||||
})
|
||||
|
||||
function normPath(p) {
|
||||
return p.trim().replace(/^\/+|\/+$/g, '')
|
||||
}
|
||||
@@ -170,7 +157,11 @@ function swapRegions(doc) {
|
||||
else document.getElementById('pagerite-base')?.after(el) ?? document.head.append(el)
|
||||
anchor = el
|
||||
}
|
||||
document.title = doc.title
|
||||
// The editor keeps its own title while open; only inherit the server title
|
||||
// when navigating outside the editor (e.g. fetch-navigation swaps).
|
||||
if (!document.body.classList.contains('editing')) {
|
||||
document.title = doc.title
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlain(p) {
|
||||
@@ -372,9 +363,10 @@ async function removeFavicon() {
|
||||
}
|
||||
}
|
||||
|
||||
function currentTitle() {
|
||||
return flatMap.value[path.value]?.title
|
||||
|| document.title.replace(/ – [^–]*$/, '')
|
||||
// The site editor’s window title shows the site brand (or a generic label)
|
||||
// plus the edit pen; the current article title is irrelevant here.
|
||||
function updateEditorTitle() {
|
||||
document.title = `${brand.value.trim() || 'Pagerite'} 🖊️`
|
||||
}
|
||||
|
||||
function applyBrand(b) {
|
||||
@@ -387,11 +379,10 @@ function applyBrand(b) {
|
||||
document.getElementById('nav')?.before(el)
|
||||
}
|
||||
el.textContent = b
|
||||
document.title = `${currentTitle()} – ${b}`
|
||||
} else {
|
||||
if (el) el.remove()
|
||||
document.title = currentTitle()
|
||||
}
|
||||
updateEditorTitle()
|
||||
}
|
||||
|
||||
function onBrandInput() {
|
||||
@@ -399,6 +390,8 @@ function onBrandInput() {
|
||||
debounce('brand', saveBrand)
|
||||
}
|
||||
|
||||
watch(brand, updateEditorTitle, { immediate: true })
|
||||
|
||||
async function saveSettings(opts = {}) {
|
||||
const res = await fetch('/_api/settings', {
|
||||
method: 'PUT',
|
||||
|
||||
+27
-3
@@ -15,9 +15,12 @@ import PageEditor from './PageEditor.vue'
|
||||
import SiteEditor from './SiteEditor.vue'
|
||||
|
||||
let host = null
|
||||
let app = null
|
||||
let savedTitle = null
|
||||
|
||||
export function openEditor(path, { mode = 'page' } = {}) {
|
||||
closeEditor()
|
||||
savedTitle = document.title
|
||||
host = document.createElement('div')
|
||||
host.className = 'editor-host'
|
||||
// Docked inside #content: below the banner, next to the article only.
|
||||
@@ -26,10 +29,11 @@ export function openEditor(path, { mode = 'page' } = {}) {
|
||||
// Which kind of editor is open; pagerite.js uses this to decide
|
||||
// whether a pen click closes the panel or swaps in the other editor.
|
||||
document.body.dataset.editorMode = mode
|
||||
createApp(mode === 'site' ? SiteEditor : PageEditor, {
|
||||
app = createApp(mode === 'site' ? SiteEditor : PageEditor, {
|
||||
pagePath: path,
|
||||
onClose: closeEditor,
|
||||
}).mount(host)
|
||||
})
|
||||
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.
|
||||
@@ -48,6 +52,26 @@ export function closeEditor() {
|
||||
// Slide the panel out in sync with the page shifting back.
|
||||
host.firstElementChild?.classList.add('closing')
|
||||
const old = host
|
||||
const oldApp = app
|
||||
const restoreTitle = savedTitle
|
||||
host = null
|
||||
setTimeout(() => old.remove(), 250)
|
||||
app = null
|
||||
savedTitle = null
|
||||
setTimeout(() => {
|
||||
oldApp?.unmount()
|
||||
old.remove()
|
||||
if (host) return // a new editor has opened; its title is authoritative
|
||||
// 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.
|
||||
fetch(location.pathname)
|
||||
.then((r) => r.text())
|
||||
.then((html) => {
|
||||
const doc = new DOMParser().parseFromString(html, 'text/html')
|
||||
if (doc.title) document.title = doc.title
|
||||
})
|
||||
.catch(() => {
|
||||
if (restoreTitle != null) document.title = restoreTitle
|
||||
})
|
||||
}, 250)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user