import { LitElement, html, css, type PropertyValues } from "lit"; import { property } from "lit/decorators.js"; import { ref, createRef, type Ref } from "lit/directives/ref.js"; import type { SlDialog, SlDropdown, SlSelectEvent, } from "@shoelace-style/shoelace"; import { wrapCss, IS_APP, VERSION, clickOnSpacebarPress, apiPrefix, replayPrefix, } from "./misc"; import rwpIcon from "~assets/icons/replaywebpage.svg"; import { sourceToId, tsToDate, getPageDateTS, getDateFromTS, } from "./pageutils"; import fasTriangleExclamation from "@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg"; import fasBook from "@fortawesome/fontawesome-free/svgs/solid/book.svg"; import fasDownload from "@fortawesome/fontawesome-free/svgs/solid/download.svg"; import fasFileDownload from "@fortawesome/fontawesome-free/svgs/regular/arrow-alt-circle-down.svg"; import farListAlt from "@fortawesome/fontawesome-free/svgs/regular/list-alt.svg"; import farResources from "@fortawesome/fontawesome-free/svgs/solid/puzzle-piece.svg"; import farPages from "@fortawesome/fontawesome-free/svgs/regular/file-image.svg"; import fasInfoIcon from "@fortawesome/fontawesome-free/svgs/solid/info-circle.svg"; import fasSync from "@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg"; import fasRefresh from "@fortawesome/fontawesome-free/svgs/solid/redo-alt.svg"; import fasFullscreen from "@fortawesome/fontawesome-free/svgs/solid/desktop.svg"; import fasUnfullscreen from "@fortawesome/fontawesome-free/svgs/solid/compress-arrows-alt.svg"; import fasLeft from "@fortawesome/fontawesome-free/svgs/solid/arrow-left.svg"; import fasRight from "@fortawesome/fontawesome-free/svgs/solid/arrow-right.svg"; import fasMenuV from "@fortawesome/fontawesome-free/svgs/solid/ellipsis-v.svg"; import fasAngleLeft from "@fortawesome/fontawesome-free/svgs/solid/angle-left.svg"; import fasAngleRight from "@fortawesome/fontawesome-free/svgs/solid/angle-right.svg"; import fasCaretDown from "@fortawesome/fontawesome-free/svgs/solid/caret-down.svg"; import { RWPEmbedReceipt } from "./embed-receipt"; import Split from "split.js"; import type { FavIconEventDetail, ItemType, URLResource } from "./types"; import type { Replay } from "./replay"; import { ifDefined } from "lit/directives/if-defined.js"; import "./item-info"; import { dateTimeFormatter } from "./utils/dateTimeFormatter"; import type { ReplayLoadingDetail, RwpPageLoadingEvent, RwpUrlChangeEvent, TabNavEvent, } from "./events"; const RWP_SCHEME = "search://"; export type LoadInfo = { extraConfig?: { baseUrlSourcePrefix?: unknown; baseUrl?: unknown; headers?: unknown; recording?: unknown; adblockUrl?: string | null; }; customColl?: string | null; noWebWorker?: boolean; noCache?: boolean; hideOffscreen?: boolean; loadEager?: boolean; sourceUrl?: string; loadUrl?: string; swError?: string; newFullImport?: unknown; name?: string; importCollId?: string; }; export type EmbedOpts = { noMediaDownloadUI?: boolean; }; export type EmbedReplayData = { view?: "story" | "pages" | "resources"; url?: string; ts?: string; title?: string; query?: string; waczhash?: string; }; export type TabData = EmbedReplayData & { currList?: number; urlSearchType?: string; currMime?: string; }; /** * @fires coll-loaded * @fires update-title * @fires about-show */ class Item extends LitElement { @property({ type: Boolean }) inited = false; @property({ type: String }) sourceUrl: string | null = null; @property({ type: String }) downloadUrl: string | null = null; @property({ type: Object, attribute: false }) loadInfo: LoadInfo | null = null; @property({ type: Object }) embedOpts: EmbedOpts = {}; @property({ type: Boolean }) showSidebar: boolean | null = null; @property({ type: Object, attribute: false }) itemInfo: ItemType | Record | null = null; @property({ type: String }) item = ""; @property({ type: Boolean }) hasStory = false; @property({ type: Boolean }) isLoading = false; @property({ type: Object, attribute: false }) tabData: TabData = {}; @property({ type: String }) url = ""; @property({ type: String }) ts = ""; @property({ type: Boolean }) isFullscreen: boolean | null = null; @property({ type: Boolean }) menuActive = false; @property({ type: String }) embed: string | null = null; @property({ type: Boolean }) embedDropdownActive = false; @property({ type: Boolean }) editable = false; @property({ type: Boolean }) browsable = true; @property({ type: Boolean }) clearable = true; @property({ type: Boolean }) isVisible = true; @property({ type: String }) favIconUrl = ""; @property({ type: String }) appName = "ReplayWeb.page"; @property({ type: String }) appVersion = VERSION; @property({ type: Number }) autoUpdateInterval = 10; @property({ type: String }) swName: string | null = null; @property({ type: Boolean }) replayNotFoundError = false; @property({ type: Array }) multiTs?: string[] = []; @property({ type: Boolean }) clickToDownloadMode = false; private splitter: Split.Instance | null = null; private _replaceLoc = false; private _locUpdateNeeded = false; private _locationHash = ""; private _autoUpdater: null | Promise = null; private observer?: IntersectionObserver; private readonly archiveInfoDialog: Ref = createRef(); private readonly tabNames = ["pages", "story", "resources", "info"]; private readonly tabLabels = { pages: "Pages", story: "Story", resources: "URLs", }; private _lastUrlUpdate: EmbedReplayData | null = null; constructor() { super(); this.showSidebar = localStorage.getItem("pages:showSidebar") === "1" && this.browsable; } firstUpdated() { this.inited = true; window.addEventListener("hashchange", () => this.onHashChange()); this.addEventListener("fullscreenchange", () => { this.isFullscreen = !!document.fullscreenElement; }); if (this.embed && this.loadInfo && this.loadInfo.hideOffscreen) { this.observer = new IntersectionObserver((entries /*, observer*/) => { this.isVisible = entries[0].isIntersecting; }); this.observer.observe(this); } } async runUpdateLoop() { try { // only autoupdate if interval is set, and number of pages < 100 to avoid messing up scrolling while ( this.editable && this.autoUpdateInterval && (!this.itemInfo?.pages || this.itemInfo.pages.length < 100) ) { await new Promise((resolve) => setTimeout(resolve, this.autoUpdateInterval * 1000), ); await this.doUpdateInfo(true); } } finally { this._autoUpdater = null; } } async getMultiTimestamps(): Promise { if (!this.tabData.url) return; const resp = await fetch( apiPrefix + "/c/" + this.item + "/ts/?url=" + window.encodeURIComponent(this.tabData.url), ); if (resp.status !== 200) { this.multiTs = []; return; } const json = await resp.json(); this.multiTs = json.timestamps; } willUpdate(changedProperties: Map>) { if (changedProperties.has("tabData")) { // Format tab data from URL query params const tabData: TabData = {}; Object.entries(this.tabData).forEach(([key, value]) => { if (!value) return; // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7053 - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'. tabData[key] = value; }); this.tabData = tabData; const prevTabData = changedProperties.get("tabData"); if (this.tabData.url && this.tabData.url !== prevTabData?.url) { void this.getMultiTimestamps(); } } } updated(changedProperties: PropertyValues) { if (changedProperties.has("sourceUrl")) { void this.doUpdateInfo(); } if (changedProperties.has("editable")) { if (this.editable && this.autoUpdateInterval && !this._autoUpdater) { this._autoUpdater = this.runUpdateLoop(); } } if (changedProperties.has("tabData")) { if (!this.itemInfo?.coll) { return; } const newHash = "#" + new URLSearchParams(this.tabData as Record).toString(); if (!this.tabData.url) { this.url = RWP_SCHEME + decodeURIComponent(this._paramsToString(this.tabData)); } if (newHash !== this._locationHash) { this._locationHash = newHash; if ( this._replaceLoc || Object.keys(changedProperties.get("tabData") || {}).length === 0 ) { const newLoc = new URL(window.location.href); newLoc.hash = this._locationHash; window.history.replaceState({}, "", newLoc.href); this._replaceLoc = false; } else { window.location.hash = this._locationHash; if (!this.showSidebar) { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { replay.focus(); } } } if (this.embed && window.parent !== window) { const { url, ts, view, query, title }: EmbedReplayData = this.tabData; const lastUpdate = this._lastUrlUpdate; const replayNotFoundError = this.replayNotFoundError; if ( !lastUpdate || lastUpdate.url !== url || lastUpdate.ts !== ts || lastUpdate.view !== view || lastUpdate.query !== query || lastUpdate.title !== title ) { const newUpdate: RwpUrlChangeEvent["detail"] = { type: "urlchange", url, ts, view, query, title, replayNotFoundError, }; window.parent.postMessage(newUpdate, "*"); this._lastUrlUpdate = newUpdate; } } } this._locUpdateNeeded = false; } if (changedProperties.has("showSidebar")) { if (!this.embed) { localStorage.setItem("pages:showSidebar", this.showSidebar ? "1" : "0"); } } if ( changedProperties.has("tabData") || changedProperties.has("showSidebar") ) { this.configureSplitter(); } } configureSplitter() { if (this.tabData.url && this.showSidebar) { const contents = this.renderRoot.querySelector("#contents"); const replay = this.renderRoot.querySelector("wr-coll-replay"); if (contents && replay && !this.splitter) { const opts = { sizes: [30, 70], minSize: [300, 300], gutterSize: 4, onDragStart() { replay.setDisablePointer(true); }, onDragEnd() { replay.setDisablePointer(false); }, }; this.splitter = Split([contents, replay], opts); } } else if (this.splitter) { try { this.splitter.destroy(); } catch (e) { // ignore, remove splitter } this.splitter = null; } } async doUpdateInfo(autorefresh = false) { // if auto-refresh, and replay and no sidebar, than skip update if (autorefresh && this.tabData.url && !this.showSidebar) { return; } let item = this.loadInfo?.customColl; // @todo(2023-11-06) update customColl -> customItem if (!item) { const res = await sourceToId(this.sourceUrl!); item = res.item; } this.item = item; const itemApiPrefix = apiPrefix + "/c/" + item; const itemReplayPrefix = replayPrefix + "/" + item; const resp = await fetch(itemApiPrefix + "?all=1"); if (resp.status != 200) { this.itemInfo = {}; return; } const json = await resp.json(); this.itemInfo = { apiPrefix: itemApiPrefix, replayPrefix: itemReplayPrefix, coll: item, ...json, }; if (this.loadInfo?.extraConfig?.headers) { const headers = this.loadInfo.extraConfig.headers; await fetch(`${itemApiPrefix}/updateAuth`, { method: "POST", body: JSON.stringify({ headers }), }); } const { title, name, filename, downloadUrl } = this.itemInfo!; if (!title) { this.itemInfo!.title = name || filename; } if (downloadUrl === null) { this.downloadUrl = null; } else { this.downloadUrl = downloadUrl || this.sourceUrl; } if (this.embed === "replayonly" || this.embed === "replay-with-info") { this.showSidebar = false; } this.hasStory = Boolean( // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition this.itemInfo!.desc || this.itemInfo!.lists?.length, ); this.dispatchEvent( new CustomEvent("coll-loaded", { detail: { collInfo: this.itemInfo, alreadyLoaded: true, }, }), ); this.onHashChange(); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onItemLoaded(event) { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.doUpdateInfo(); this.loadInfo = null; if (event.detail.sourceUrl) { this.sourceUrl = event.detail.sourceUrl; } this.dispatchEvent( new CustomEvent("coll-loaded", { detail: { sourceUrl: this.sourceUrl, collInfo: this.itemInfo, }, }), ); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onItemUpdate(event) { if (!this.editable) { return; } this.itemInfo = { ...this.itemInfo, ...event.detail }; } onHashChange() { const hash = window.location.hash; if (hash && hash !== this._locationHash) { this.tabData = Object.fromEntries( new URLSearchParams(hash.slice(1)).entries(), ); this._locationHash = hash; } if ( this.itemInfo?.coll && (!this.tabData.view || !this.tabNames.includes(this.tabData.view)) ) { const view = this.hasStory ? "story" : // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition this.editable || this.itemInfo.pages?.length ? "pages" : "resources"; this.tabData = { ...this.tabData, view, }; } if (this.tabData.url && this.tabData.url.startsWith("page:")) { const pageIndex = Number(this.tabData.url.slice("page:".length)); if ( this.itemInfo?.pages && !isNaN(pageIndex) && pageIndex < this.itemInfo.pages.length ) { const page = this.itemInfo.pages[pageIndex]; this.tabData.url = page.url; this.tabData.ts = getPageDateTS(page).timestamp; } } if (!this.hasStory && this.tabData.view === "story") { this.tabData.view = "pages"; } if (this.tabData.url && this.tabData.query && this.browsable) { this.showSidebar = true; } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onTabClick(event) { event.preventDefault(); const hash = event.currentTarget.getAttribute("href"); this.tabData = { ...this.tabData, view: hash.slice(1) }; //this.tabData = {view: hash.slice(1)}; return false; } onItemTabNav(event: TabNavEvent) { if (event.detail.reload) { this.onRefresh(null, true); return; } const targetId = (event.target as HTMLElement).id; const { data, replaceLoc, replayNotFoundError } = event.detail; if (replayNotFoundError !== undefined) { this.replayNotFoundError = replayNotFoundError; } if ( targetId === this.tabData.view || (targetId === "replay" && this.tabData.url) || (this.showSidebar && this.tabData.url) ) { this.updateTabData(data, replaceLoc); } } updateTabData(data: TabData, replaceLoc = false) { this.tabData = { ...this.tabData, ...data }; if (this.tabData.url) { this.url = this.tabData.url || ""; } if (this.tabData.ts) { this.ts = this.tabData.ts || ""; } this._replaceLoc = !this._locUpdateNeeded && replaceLoc; this._locUpdateNeeded = true; } static get styles() { return wrapCss(Item.compStyles); } static get compStyles() { return css` :host { display: flex; flex-direction: column; height: 100%; min-width: 0px; } .icon { vertical-align: text-top; } .back fa-icon { width: 1.5em; vertical-align: bottom; line-height: 0.5em; } li.is-active { font-weight: bold; } .tab-label { display: inline; } @media screen and (max-width: ${!IS_APP ? css`1053px` : css`1163px`}) { .tab-label { display: none; } .main.tabs span.icon { margin: 0px; } } .main.tabs { display: flex; flex-direction: row; margin-bottom: 0px; overflow: unset; } .main.tabs ul { position: relative; } .main.tabs li { line-height: 1.5; padding: 6px 0 4px 0; } @media screen and (max-width: 319px) { .main.tabs li a { padding-right: 4px; padding-left: 4px; } } .sidebar.main.tabs li a { padding-right: 6px; padding-left: 6px; } #contents { height: 100%; width: 100%; display: flex; flex-direction: column; min-height: 0px; flex: auto; background-color: white; } #tabContents { height: 100%; width: 100%; display: flex; flex-direction: row; min-height: 0px; flex: auto; } rwp-embed-receipt { flex-direction: column; display: flex; } ${RWPEmbedReceipt.embedStyles} ${Item.replayBarStyles} `; } static get replayBarStyles() { return css` .breadbar { display: flex; align-items: center; height: 35px; width: 100%; background-color: aliceblue; padding: 0.5em; } .replay-bar { padding: 0.5em; max-width: none; border-bottom: solid 0.1rem #97989a; width: 100%; background-color: white; } input#url { border-radius: 4px; } .favicon img { width: 20px; height: 20px; margin: 8px; /*filter: drop-shadow(1px 1px 2px grey);*/ } #datetime { position: absolute; right: 0.5rem; z-index: 10; background: #fff; top: 1px; bottom: 1px; display: flex; align-items: center; line-height: 2; } #click-download-msg { position: absolute; right: 0.5rem; z-index: 10; background: #09c1ff; top: 5px; bottom: 5px; margin-right: -3px; border-radius: 8px; display: flex; align-items: center; line-height: 2; } #hilite-sample { width: 20px; height: 20px; background-color: rgba(0, 0, 255, 0.5); border: solid 4px blue; margin: 2px; display: inline-flex; } /* Gradient to indicate URL clipping */ .loc-overlay:before { content: ""; position: absolute; top: 0; width: 2em; height: 100%; transform: translateX(-100%); background: linear-gradient( 90deg, rgba(255, 255, 255, 0), #fff 50%, #fff ); pointer-events: none; } .timestamp-dropdown-btn { all: unset; cursor: pointer; display: flex; gap: var(--sl-spacing-x-small); align-items: center; transition: background-color var(--sl-transition-fast); color: var(--sl-color-neutral-600); font-variant-numeric: tabular-nums; } .timestamp-dropdown-btn:hover { color: var(--sl-color-neutral-900); } .timestamp-dropdown-btn:hover .timestamp-count-badge { background-color: var(--sl-color-blue-600); } .timestamp-count-badge { display: inline-flex; gap: var(--sl-spacing-2x-small); background-color: var(--sl-color-blue-500); color: var(--sl-color-neutral-0); line-height: 1; padding: var(--sl-spacing-3x-small) var(--sl-spacing-x-small); border-radius: var(--sl-border-radius-small); transition: background-color var(--sl-transition-fast); } .timestamp-count { font-weight: 600; transform: translateY(0.075em); } .timestamp-menu-item { font-variant-numeric: tabular-nums; } .timestamp-menu-item[aria-selected="true"]::part(label) { color: var(--sl-color-blue-600); } .menu-head { font-size: 10px; font-weight: bold; display: block; } .menu-logo { vertical-align: middle; } .menu-version { font-size: 10px; } .dropdown-item.info { font-style: italic; } input:focus + #datetime { display: none; } .grey-disabled { --fa-icon-fill-color: lightgrey; color: lightgrey; } .replay-bar .button:focus { box-shadow: none; } .is-borderless { border: 0px; } .narrow { padding: calc(0.5em - 1px) 0.8em; } form { width: 100%; margin: 0 0 0 0.5em; } .gutter.gutter-horizontal { cursor: col-resize; float: left; background-color: rgb(151, 152, 154); } .gutter.gutter-horizontal:hover { cursor: col-resize; } main, wr-coll-replay { width: 100%; } .is-list { height: fit-content; } #contents.full-pages { width: 100% !important; } .sidebar-nav { position: absolute; vertical-align: middle; } .sidebar-nav a { display: inline-block; border-bottom: 0px; } .sidebar-nav span.nav-hover { font-size: smaller; display: none; } .sidebar-nav:hover span.nav-hover, .sidebar-nav:focus-within span.nav-hover { display: initial; color: rgb(72, 118, 255); } .sidebar-nav fa-icon { vertical-align: bottom; } .sidebar-nav:hover fa-icon { color: rgb(72, 118, 255); } .sidebar-nav.left { left: 8px; } .sidebar-nav.right { right: 8px; } /* Since the replay sometimes programmatically receives keyboard focus, and that is visually unexpected for mouse-users, and since this won't particularly trip up keyboard users, just remove the focus style. */ wr-coll-replay:focus { outline: none; } /* Some keyboard-users may see this replacement style */ wr-coll-replay:focus-visible { outline: 1px solid rgb(72, 118, 255); } `; } render() { if (!this.inited) return html``; const isReplay = !!this.tabData.url; const isSidebar = isReplay && this.showSidebar; // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!isReplay && this.tabData?.view) { const detail = { title: this.tabLabels[this.tabData.view], replayTitle: false, }; this.dispatchEvent( new CustomEvent("update-title", { bubbles: true, composed: true, detail, }), ); } if (this.itemInfo && !this.itemInfo.coll) { return html` `; } else if (this.itemInfo) { return html` ${this.renderLocationBar()} ${this.renderVerifyInfo()} ${this.renderItemInfo()} Close
${this.renderTabHeader(isSidebar)} ${isSidebar || !isReplay ? this.renderItemTabs(isSidebar) : html``}
${isReplay && this.isVisible ? html` ` : ""}
`; } else { return html``; } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'isSidebar' implicitly has an 'any' type. renderTabHeader(isSidebar) { // if (this.tabData.view === "replay") { // return ""; // } return html`
    ${isSidebar ? html`
  • Hide Hide Sidebar
  • ` : ""} ${this.hasStory ? html`
  • Story
  • ` : ""}
  • Pages
  • Resources
  • ${isSidebar ? html`
  • Expand Expand Sidebar to Full View
  • ` : ""}
`; } protected renderToolbarLeft() { const isReplay = !!this.tabData.url; return html` ${this.browsable ? html` ` : ""} ${!this.isLoading ? html` ` : ""} `; } renderLocationBar() { if (this.embed === "replayonly" || this.embed == "replay-with-info") { return ""; } const isReplay = !!this.tabData.url; const showFavIcon = isReplay && this.favIconUrl; return html` Skip replay navigation
${this.renderToolbarLeft()}
${isReplay ? this.clickToDownloadMode ? this.renderClickToDownloadNotify() : this.renderTimestamp() : ""} ${showFavIcon ? html` ` : html``}
${this.renderToolbarRight()}

Skipped

`; } protected renderClickToDownloadNotify() { return html`
Select image or media highlighted with 🟦 (blue box) on hover to download.
`; } protected renderToolbarRight() { const isReplay = !!this.tabData.url; const dateStr = this.ts ? dateTimeFormatter.format(tsToDate(this.ts) as Date) : ""; return html`
Full Screen ${this.browsable ? html` Browse Contents ` : ""} ${this.clearable ? html`
Purge Cache + Full Reload ` : html``} ${isReplay && !this.embedOpts.noMediaDownloadUI ? html`
Select Media to Download ` : html``} ${(!this.editable && this.downloadUrl?.startsWith("http://")) || this.downloadUrl?.startsWith("https://") ? html`
Download Archive ` : html``} ${dateStr ? html`
Capture Date${dateStr}
` : ""} ${!this.editable && (this.downloadUrl === this.sourceUrl || !this.embed) ? html` Archive Info ` : ``}
 About ${this.appName} (${this.appVersion})
`; } private renderTimestamp() { const timestampStrs: { date: string; label: string }[] = []; this.multiTs?.forEach((ts) => { // Filter out invalid dates try { const date = getDateFromTS(+ts); const dateStr = date ? dateTimeFormatter.format(tsToDate(date) as Date) : ""; timestampStrs.push({ date, label: dateStr, }); } catch { /* empty */ } }); const currDateStr = this.ts ? dateTimeFormatter.format(tsToDate(this.ts) as Date) : ""; return html`
${timestampStrs.length > 1 ? html` ${timestampStrs.map(({ date: ts, label }) => { const selected = this.ts === ts; return html` ${label}`; })} ` : currDateStr}
`; } renderVerifyInfo() { if (this.embed !== "replay-with-info") { return ""; } return html` `; } dragStart() { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { replay.setDisablePointer(true); } } dragEnd() { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { replay.setDisablePointer(false); } } clickToDownload() { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { this.clickToDownloadMode = true; replay.setClickToDownload(); } return false; } cancelClickToDownload() { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { this.clickToDownloadMode = false; replay.clearHilite(true); } return false; } renderItemInfo() { if (!this.itemInfo) return html` Archive info is not available
Please reload and try again.
`; return html``; } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'isSidebar' implicitly has an 'any' type. renderItemTabs(isSidebar) { const isStory = this.hasStory && this.tabData.view === "story"; const isPages = this.tabData.view === "pages"; const isResources = this.tabData.view === "resources"; return html` ${isStory ? html` ` : ""} ${isResources ? html` ` : ""} ${isPages ? html` ` : ""} `; } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. skipMenu(event) { // This is a workaround, since this app's routing doesn't permit normal // following of in-page anchors. event.preventDefault(); this.renderRoot.querySelector("#skip-replay-target")?.focus(); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onKeyDown(event) { if (event.key === "Esc" || event.key === "Escape") { event.preventDefault(); event.currentTarget.value = this.url; } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onMenu(event) { event.stopPropagation(); this.menuActive = !this.menuActive; if (this.menuActive) { document.addEventListener( "click", () => { this.menuActive = false; }, { once: true }, ); } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onFullscreenToggle(event) { event.preventDefault(); this.menuActive = false; if (!this.isFullscreen) { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.requestFullscreen(); } else { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises document.exitFullscreen(); } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onGoBack(event) { event.preventDefault(); this.menuActive = false; window.history.back(); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onGoForward(event) { event.preventDefault(); this.menuActive = false; window.history.forward(); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onShowPages(event) { event.preventDefault(); // show sidebar for tablet or wider, or hide sidebar if (this.showSidebar || document.documentElement.clientWidth >= 769) { this.showSidebar = !this.showSidebar; } else { // otherwise, just go to full pages view this.showSidebar = false; this.updateTabData({ url: "", ts: "" }); } if (!this.browsable) { this.showSidebar = false; } } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onFullPageView(event) { event.preventDefault(); this.updateTabData({ url: "", ts: "" }); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onHideSidebar(event) { event.preventDefault(); this.showSidebar = false; } async onReplayLoading( event: CustomEvent<{ loading: boolean; replayNotFoundError?: boolean }>, ) { const { loading, replayNotFoundError } = event.detail; if (this.embed && window.parent !== window && this.isLoading !== loading) { let loadingDetail: ReplayLoadingDetail; if (loading) { loadingDetail = { loading, }; } else { loadingDetail = { loading, replayNotFoundError: replayNotFoundError ?? false, }; } const msg = { ...loadingDetail, type: "page-loading", url: this.tabData.url ?? "", ts: this.tabData.ts ?? "", } satisfies RwpPageLoadingEvent["detail"]; window.parent.postMessage(msg, "*"); } this.isLoading = loading; } async onFavIcons(event: CustomEvent) { if (this.embed && window.parent !== window) { window.parent.postMessage({ type: "favicons", ...event.detail }, "*"); } for (const icon of event.detail.icons) { const resp = await fetch(icon.href); if (resp.status === 200) { const ct = resp.headers.get("Content-Type"); if (ct && !ct.startsWith("text/")) { this.favIconUrl = icon.href; return; } } } // not found this.favIconUrl = ""; } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onPurgeCache(event) { event.preventDefault(); const reload = event.detail && event.detail.reload !== undefined ? event.detail.reload : true; // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises, @typescript-eslint/no-unsafe-argument this.deleteFully(reload); } async deleteFully(reload = false) { const deleteURL = this.itemInfo!.apiPrefix + (reload ? "?reload=1" : ""); const resp = await fetch(deleteURL, { method: "DELETE", }); if (resp.status !== 200) { console.warn("purge failed: " + resp.status); } if (!reload && !this.embed) { window.location.search = ""; return; } //window.location.hash = ""; window.location.reload(); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onSubmit(event) { event.preventDefault(); const input = this.renderRoot.querySelector("input")!; if (input.value) { this.navigateTo(input.value); } else { input.value = this.url; } return false; } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type. onLostFocus(event) { if (!event.currentTarget.value) { event.currentTarget.value = this.url; } } onTimestampDropdownBtnBlur(event: MouseEvent) { const btn = event.currentTarget as HTMLButtonElement; const dropdown = btn.closest("sl-dropdown"); const relatedTarget = event.relatedTarget as HTMLElement; // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (relatedTarget && dropdown?.contains(relatedTarget)) { return; } if (dropdown?.open) { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises dropdown.hide(); } } onSelectTimestamp(event: SlSelectEvent) { const { item } = event.detail; this.updateTabData({ ts: item.value }); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'value' implicitly has an 'any' type. navigateTo(value) { let data: TabData; if (value.startsWith("http://") || value.startsWith("https://")) { data = { url: value }; if (value === this.tabData.url) { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { replay.refresh(); } return; } } else { if (!value.startsWith(RWP_SCHEME)) { data = { query: value, view: "pages" }; } else { data = this._stringToParams(value); } } this.updateTabData(data); } // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'value' implicitly has an 'any' type. _stringToParams(value) { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const q = new URLSearchParams(value.slice(RWP_SCHEME.length)); const data: Partial = {}; data.url = ""; data.ts = ""; for (const param of [ "query", "view", "currList", "currMime", "urlSearchType", ]) { if (q.has(param)) { // @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7053 - Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Partial'. data[param] = q.get(param); } } return data; } _paramsToString(value: Record) { const q = new URLSearchParams(); for (const param of [ "query", "view", "currList", "currMime", "urlSearchType", ]) { if (param in value) { q.set(param, String(value[param])); } } return q.toString(); } onRefresh(event: Event | null, replayOnly = false) { if (event) { event.preventDefault(); } this.menuActive = false; if (this.tabData.url) { const replay = this.renderRoot.querySelector("wr-coll-replay"); if (replay) { replay.refresh(); } } else if (!replayOnly) { window.location.reload(); } } onAbout() { this.dispatchEvent(new CustomEvent("about-show")); } onShowInfoDialog() { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.archiveInfoDialog.value?.show(); } onHideInfoDialog() { // TODO: Fix this the next time the file is edited. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.archiveInfoDialog.value?.hide(); } } customElements.define("wr-item", Item); export { Item };