rspace-online/shared/components/rstack-markwhen-view.ts

68 lines
2.5 KiB
TypeScript

/**
* <rstack-markwhen-view> — iframe-based markwhen viewer.
*
* Two modes:
* - `src` attribute: fetch a server-rendered `/render` endpoint (preferred)
* - `text` property: render an ad-hoc .mw string via the server-side
* render pipeline, delivered via srcdoc for isolation.
*
* The upstream @markwhen/{timeline,calendar} packages are pre-built Vue
* apps, not importable libraries — so the iframe route is the sane embed.
*/
export type MarkwhenViewMode = 'timeline' | 'calendar';
export class RstackMarkwhenView extends HTMLElement {
static get observedAttributes() { return ['src', 'view']; }
#src = '';
#view: MarkwhenViewMode = 'timeline';
#shadow: ShadowRoot;
#iframe: HTMLIFrameElement;
constructor() {
super();
this.#shadow = this.attachShadow({ mode: 'open' });
this.#shadow.innerHTML = `
<style>
:host { display: block; width: 100%; height: 100%; min-height: 320px;
background: #0b1221; border-radius: 8px; overflow: hidden; }
iframe { width: 100%; height: 100%; border: 0; background: #0b1221; }
.empty { display: grid; place-items: center; height: 100%;
font: 13px/1.4 system-ui; color: #94a3b8; padding: 24px; text-align: center; }
</style>
<iframe sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation" title="Timeline"></iframe>
`;
this.#iframe = this.#shadow.querySelector('iframe')!;
}
set src(v: string) { this.#src = v; this.#render(); }
get src() { return this.#src; }
set view(v: MarkwhenViewMode) { this.#view = v; this.#render(); }
get view() { return this.#view; }
attributeChangedCallback(name: string, _old: string | null, next: string | null) {
if (name === 'src') this.src = next ?? '';
if (name === 'view') this.view = (next === 'calendar' ? 'calendar' : 'timeline');
}
connectedCallback() { this.#render(); }
#render() {
if (!this.#src) {
this.#iframe.removeAttribute('src');
this.#iframe.srcdoc = `<body style="display:grid;place-items:center;height:100vh;margin:0;font:13px system-ui;color:#94a3b8;background:#0b1221">No timeline source yet.</body>`;
return;
}
// Append view param if caller used a bare endpoint URL.
const url = new URL(this.#src, window.location.origin);
if (!url.searchParams.has('view')) url.searchParams.set('view', this.#view);
this.#iframe.removeAttribute('srcdoc');
this.#iframe.src = url.pathname + url.search;
}
}
if (!customElements.get('rstack-markwhen-view')) {
customElements.define('rstack-markwhen-view', RstackMarkwhenView);
}