Tab titles name the record; Guides reach a site and the contract surface
This commit is contained in:
91
frontend/src/composables/pageTitle.js
Normal file
91
frontend/src/composables/pageTitle.js
Normal file
@@ -0,0 +1,91 @@
|
||||
// The browser tab title, including WHICH record is open.
|
||||
//
|
||||
// The router can only name the KIND of page, because it runs before the view
|
||||
// has fetched anything: every application read "West Jefferson ShopDB -
|
||||
// Application", so two tabs on two different machines were indistinguishable,
|
||||
// and a shared link previewed as the section rather than the thing.
|
||||
//
|
||||
// So a detail view names itself once its record arrives:
|
||||
//
|
||||
// setRecordTitle(machine.assetnumber)
|
||||
//
|
||||
// THE ORDERING IS THE WHOLE PROBLEM. The router's afterEach awaits the site
|
||||
// settings before it can build "<facility> ShopDB - <page>", so on a cold load
|
||||
// it finishes AFTER a view that fetched quickly has already set a good title,
|
||||
// and would overwrite it with the generic one. Both writers therefore go
|
||||
// through here, and the generic write is dropped when a record title already
|
||||
// belongs to the route being landed on. The key is the route's fullPath, so
|
||||
// navigating away invalidates the previous record's claim.
|
||||
|
||||
import { getFacilityName } from '@/utils/siteSettings'
|
||||
|
||||
let activeKey = null // fullPath of the route the tab is currently showing
|
||||
let recordTitle = null // record name set by a view for activeKey, if any
|
||||
let sitePrefix = 'ShopDB' // '<facility> ShopDB', resolved once and cached
|
||||
|
||||
function compose(page) {
|
||||
return page && page !== 'Home' ? `${sitePrefix} - ${page}` : sitePrefix
|
||||
}
|
||||
|
||||
async function resolveSitePrefix() {
|
||||
try {
|
||||
const facility = await getFacilityName()
|
||||
if (facility && facility !== 'ShopDB') sitePrefix = `${facility} ShopDB`
|
||||
} catch (settingsError) {
|
||||
// settings unavailable: plain ShopDB is a fine tab title
|
||||
}
|
||||
return sitePrefix
|
||||
}
|
||||
|
||||
// Called by the router on every navigation, BEFORE it awaits anything, so a
|
||||
// view that resolves first is not treated as belonging to the previous route.
|
||||
export function beginNavigation(key) {
|
||||
activeKey = key
|
||||
recordTitle = null
|
||||
}
|
||||
|
||||
// Called by the router once it knows the site prefix. Skipped when a view has
|
||||
// already named the record for this route.
|
||||
export async function applyGenericTitle(key, page) {
|
||||
await resolveSitePrefix()
|
||||
if (key !== activeKey) return // navigated on while we awaited
|
||||
// A view that named the record before the facility name arrived composed its
|
||||
// title against the placeholder prefix, so rewrite it rather than leaving
|
||||
// 'ShopDB - CSF16' on a site called West Jefferson.
|
||||
document.title = compose(recordTitle !== null ? recordTitle : page)
|
||||
}
|
||||
|
||||
// A tab shows very little, and the browser truncates mid-word wherever it runs
|
||||
// out. Cut at a word boundary instead, so what survives is readable. Knowledge
|
||||
// base articles are the case that forces this: their name is a whole sentence
|
||||
// of description, not a short label.
|
||||
const MAXLENGTH = 60
|
||||
|
||||
function shorten(text) {
|
||||
if (text.length <= MAXLENGTH) return text
|
||||
const cut = text.slice(0, MAXLENGTH)
|
||||
const space = cut.lastIndexOf(' ')
|
||||
return `${(space > MAXLENGTH / 2 ? cut.slice(0, space) : cut).trimEnd()}...`
|
||||
}
|
||||
|
||||
// Called by a detail view once its record has loaded. An empty or missing name
|
||||
// leaves the generic title in place rather than showing a bare id: "Machine"
|
||||
// beats "Machine 4192" for somebody scanning a row of tabs, and a record that
|
||||
// failed to load has no name to show anyway.
|
||||
export function setRecordTitle(name) {
|
||||
const text = (name === null || name === undefined) ? '' : String(name).trim()
|
||||
if (!text) return
|
||||
recordTitle = shorten(text)
|
||||
document.title = compose(recordTitle)
|
||||
}
|
||||
|
||||
// Exposed for tests.
|
||||
export function _state() {
|
||||
return { activeKey, recordTitle, sitePrefix }
|
||||
}
|
||||
|
||||
export function _reset() {
|
||||
activeKey = null
|
||||
recordTitle = null
|
||||
sitePrefix = 'ShopDB'
|
||||
}
|
||||
Reference in New Issue
Block a user