68 lines
2.5 KiB
TypeScript
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);
|
|
}
|