Something went wrong. Try again.
Rewild Your Web
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137// SPDX-License-Identifier: AGPL-3.0-or-later
/** * A `View` is the per-tab/per-page state object that every shell wraps a * `<web-view>` element in. The three shells used to define this shape * implicitly and inconsistently (desktop's `view` literals, mobile's * carousel entries, mediacenter's `pages`). This class is the single * normalized shape they all converge on. * * Fields are intentionally a flat record so shell-specific state can be * tacked on the instance: `view.floating` for the desktop, `view.stacked`, * `view.sourceId` for mediacenter, etc. without forcing every shell * through an inheritance chain. * * The methods are the small set of mutations that EVERY shell needed to * do in its `webview-title-change` and `webview-url-change` event * handlers. Centralising them here means one place to fix when the parse * rule changes (e.g., handling about: / data: URLs). */export class View { /** * @param {object} init * @param {string} init.id Unique webview id (matches webView.webviewId). * @param {object} [init.webView] The `<web-view>` custom element. Optional, * `null` for lazy-restored views that haven't * been hydrated yet (the desktop pattern). * @param {string} [init.name] Display title. Defaults to "Loading…". * @param {string} [init.host] Hostname segment (e.g. "example.com"). * @param {string} [init.path] Path + query + fragment (e.g. "/foo?bar"). * @param {string} [init.fav] Favicon color OR url. Initialized from a * sensible default (parent context) by the shell. * @param {string} [init.themeColor] CSS color from the page's <meta theme-color>. * @param {string} [init.appId] If this view is an app launcher, the app id. * @param {string} [init.thumb] Overview thumbnail (CSS background value). */ constructor({ id, webView = null, name = "Loading…", host = "", path = "", fav = "", themeColor = "transparent", appId = "", thumb = "", } = {}) { if (!id) throw new Error("View requires an id"); /** @type {string} */ this.id = id; /** @type {object|null} The `<web-view>` element, or null if not yet hydrated. */ this.webView = webView; /** @type {string} */ this.name = name; /** @type {string} */ this.host = host; /** @type {string} */ this.path = path; /** @type {string} */ this.fav = fav; /** @type {string} */ this.themeColor = themeColor; /** @type {string} */ this.appId = appId; /** @type {string} */ this.thumb = thumb; }
/** * Apply a `webview-title-change` event detail to this view. The shell * caller still needs to refresh any chrome that displays the name. * * @param {{title?: string|null}} detail * @returns {boolean} true if `name` changed */ setFromTitleEvent(detail) { const next = detail?.title || this.name; if (next === this.name) return false; this.name = next; return true; }
/** * Apply a `webview-url-change` event detail to this view. Updates * `host` and `path` from the URL. Bare domains (path `/`) end up with * `path = ""` so the chrome can elide it cleanly. * * @param {{url?: string|null}} detail * @returns {boolean} true if `host` or `path` changed */ setFromUrlEvent(detail) { const url = detail?.url; if (!url) return false; let nextHost; let nextPath; try { const u = new URL(url); // Hierarchical URLs (http, https, file, beaver://) have a host; // opaque ones (about:blank, data:…, javascript:…) don't, so we // fall back to the raw URL for display. nextHost = u.host || url; nextPath = u.host ? u.pathname + u.search + u.hash : ""; if (nextPath === "/") nextPath = ""; } catch { nextHost = url; nextPath = ""; } if (nextHost === this.host && nextPath === this.path) return false; this.host = nextHost; this.path = nextPath; return true; }
/** * Human-friendly host+path used in the topbar / overview cards. * Returns "" when there's no host (e.g. about:blank views). */ displayHost() { if (!this.host) return ""; if (this.path) return `${this.host}${this.path}`; return this.host; }
/** * Short hostname used in compact chips: strips scheme + trims to the * label before the first dot. "example.com" → "example", * "news.bbc.co.uk" → "news". * * @param {string} host */ static shortHost(host) { if (!host) return ""; const stripped = host.replace(/^[a-z]+:\/\//, ""); const dot = stripped.indexOf("."); return dot > 0 ? stripped.slice(0, dot) : stripped; }}