v2.5.0 (#527)
Closes https://github.com/webrecorder/replayweb.page/issues/523, closes https://github.com/webrecorder/replayweb.page/issues/396, closes https://github.com/webrecorder/replayweb.page/issues/470, closes https://github.com/webrecorder/replayweb.page/issues/237 ## Changes https://github.com/webrecorder/replayweb.page/pull/521 - Adds public `navigateReplayTo` method in `<replay-app-main>` that allows embeds to control replay navigation directly without reloading entire UI. - Enables reloading `<wr-coll-replay>` iframe even if source URL hasn't changed. - Refactors lifecycle events to prevent unnecessary re-renders. https://github.com/webrecorder/replayweb.page/pull/524 - Fixes incorrect page count shown - Enables non-seed pages by default - Improves UX and accessibility of scrolling to load more by displaying help text https://github.com/webrecorder/replayweb.page/pull/525 - Updates general styles and icons throughout to match Browsertrix styles - Allows deep customization of components with `part` and `exportparts` https://github.com/webrecorder/replayweb.page/pull/526 - Enables importing Shoelace (ie Bootstrap) icons as inline SVG - Updates location bar buttons to use Bootstrap icons - Replaces deprecated raw-loader with Webpack-native asset rule https://github.com/webrecorder/replayweb.page/pull/529 - Allows hiding collection metadata in embed. - Moves non-seed toggle next to search bar to match resources controls and enable hiding entire column when metadata is hidden. https://github.com/webrecorder/replayweb.page/pull/533 - Fix non-seed pages toggle https://github.com/webrecorder/replayweb.page/pull/535, https://github.com/webrecorder/replayweb.page/pull/534 - Upgrade to Node 22 and Yarn 4 --------- Co-authored-by: Ilya Kreymer <ikreymer@gmail.com> Co-authored-by: Misty De Méo <hi@mistydemeo.com>
This commit is contained in:
co-authored by
Ilya Kreymer
Misty De Méo
parent
830468e03a
commit
141d35d687
+162
-84
@@ -6,7 +6,7 @@ import {
|
||||
type TemplateResult,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import {
|
||||
wrapCss,
|
||||
@@ -26,7 +26,13 @@ import { SWManager } from "./swmanager";
|
||||
import "./item";
|
||||
import "./item-index";
|
||||
import "./chooser";
|
||||
import { type EmbedOpts, type LoadInfo } from "./item";
|
||||
import {
|
||||
type Item,
|
||||
type EmbedOpts,
|
||||
type LoadInfo,
|
||||
RWP_SCHEME,
|
||||
EmbedReplayDataView,
|
||||
} from "./item";
|
||||
import type { FavIconEventDetail } from "./types";
|
||||
|
||||
// ===========================================================================
|
||||
@@ -47,6 +53,9 @@ export class ReplayWebApp extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
showAbout = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showAllPages = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showFileDropOverlay = false;
|
||||
|
||||
@@ -80,7 +89,40 @@ export class ReplayWebApp extends LitElement {
|
||||
@property({ type: String })
|
||||
swErrorMsg: TemplateResult<1> | string | null = null;
|
||||
|
||||
protected swName?: string;
|
||||
@query("wr-item")
|
||||
private readonly item?: Item | null;
|
||||
|
||||
/**
|
||||
* Navigate to a view in replay.
|
||||
*
|
||||
* @param {string} urlOrView - The page URL or view to navigate to.
|
||||
* @param {Object|undefined} data - Optional view data.
|
||||
*/
|
||||
public navigateReplayTo(
|
||||
urlOrView: EmbedReplayDataView | string,
|
||||
data?: { ts: string },
|
||||
) {
|
||||
if (!this.item) {
|
||||
console.debug("missing `this.item`");
|
||||
return;
|
||||
}
|
||||
|
||||
let value = urlOrView;
|
||||
|
||||
if (
|
||||
Object.values(EmbedReplayDataView).some(
|
||||
(view) => (view as string) === urlOrView,
|
||||
)
|
||||
) {
|
||||
value = `${RWP_SCHEME}view=${value}${
|
||||
data ? `?${new URLSearchParams(data).toString()}` : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
this.item.navigateTo(value, data);
|
||||
}
|
||||
|
||||
swName?: string;
|
||||
protected swmanager: SWManager | null;
|
||||
private useRuffle = false;
|
||||
|
||||
@@ -219,6 +261,24 @@ export class ReplayWebApp extends LitElement {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.about-dialog-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.about-dialog h3,
|
||||
h4 {
|
||||
font-weight: var(--sl-font-weight-bold);
|
||||
margin-bottom: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.about-dialog p + p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.about-dialog details p {
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 840px) {
|
||||
.menu-only {
|
||||
display: none;
|
||||
@@ -404,11 +464,21 @@ export class ReplayWebApp extends LitElement {
|
||||
.embedOpts="${this.embedOpts}"
|
||||
appName="${this.appName}"
|
||||
swName="${ifDefined(this.swName)}"
|
||||
?showAllPages=${this.showAllPages}
|
||||
@replay-favicons=${this.onFavIcons}
|
||||
@update-title=${this.onTitle}
|
||||
@coll-loaded=${this.onCollLoaded}
|
||||
@coll-load-cancel=${this.onCollLoadCancel}
|
||||
@about-show=${() => (this.showAbout = true)}
|
||||
exportparts="
|
||||
replay-bar:wr-item__replay-bar,
|
||||
replay-bar-container:wr-item__replay-bar-container,
|
||||
replay-bar-input:wr-item__replay-bar-input,
|
||||
replay-content:wr-item__replay-content,
|
||||
replay-tabs-nav:wr-item__replay-tabs-nav,
|
||||
replay-tabs-panel:wr-item__replay-tabs-panel,
|
||||
replay-main:wr-item__replay-main,
|
||||
"
|
||||
></wr-item>`;
|
||||
}
|
||||
|
||||
@@ -450,7 +520,7 @@ export class ReplayWebApp extends LitElement {
|
||||
return html`
|
||||
${!this.embed || this.embed === "full" ? this.renderNavBar() : ""}
|
||||
${this.sourceUrl ? this.renderColl() : this.renderHomeIndex()}
|
||||
${this.showAbout ? this.renderAbout() : ""}
|
||||
${this.renderAbout()}
|
||||
${this.showFileDropOverlay ? this.renderDropFileOverlay() : ""}
|
||||
`;
|
||||
}
|
||||
@@ -595,6 +665,10 @@ export class ReplayWebApp extends LitElement {
|
||||
if (this.pageParams.has("noMediaDownload")) {
|
||||
this.embedOpts = { ...this.embedOpts, noMediaDownloadUI: true };
|
||||
}
|
||||
|
||||
if (this.pageParams.has("hideMetadataSidebar")) {
|
||||
this.embedOpts = { ...this.embedOpts, hideMetadataSidebar: true };
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pageParams.get("config")) {
|
||||
@@ -733,91 +807,95 @@ export class ReplayWebApp extends LitElement {
|
||||
}
|
||||
|
||||
renderAbout() {
|
||||
return html`
|
||||
<div class="modal is-active">
|
||||
<div class="modal-background" @click="${this.onAboutClose}"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">About ReplayWeb.page ${
|
||||
IS_APP ? "App" : ""
|
||||
}</p>
|
||||
<button class="delete" aria-label="close" @click="${
|
||||
this.onAboutClose
|
||||
}"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<div style="display: flex">
|
||||
<div class="has-text-centered" style="width: 220px">
|
||||
<fa-icon
|
||||
size="3rem"
|
||||
.svg=${rwpLogo}
|
||||
aria-label="ReplayWeb.page Logo"
|
||||
role="img"
|
||||
></fa-icon>
|
||||
<div style="font-size: smaller; margin-bottom: 1em">${
|
||||
IS_APP ? "App" : ""
|
||||
} v${VERSION}</div>
|
||||
</div>
|
||||
|
||||
${
|
||||
IS_APP
|
||||
? html`
|
||||
<p>
|
||||
ReplayWeb.page App is a standalone app for Mac,
|
||||
Windows and Linux that loads web archive files
|
||||
provided by the user and renders them for replay.
|
||||
</p>
|
||||
`
|
||||
: html` <p>
|
||||
<a href="https://replayweb.page" target="_blank"
|
||||
>ReplayWeb.page</a
|
||||
>
|
||||
is a browser-based viewer that loads web archive
|
||||
files provided by the user and renders them for
|
||||
replay in the browser.
|
||||
</p>`
|
||||
}
|
||||
</div>
|
||||
|
||||
<p>Full source code is available
|
||||
<a href="https://github.com/webrecorder/replayweb.page" target="_blank">on GitHub</a>.
|
||||
</p>
|
||||
|
||||
<p>See the <a target="_blank" href="./docs">documentation</a> for more info on how it works.</p>
|
||||
|
||||
<p>ReplayWeb.page is developed by <a href="https://webrecorder.net/" target="_blank">Webrecorder</a>.</p>
|
||||
|
||||
<h3>Privacy</h3>
|
||||
<p><b>No data is uploaded anywhere and no information is collected.</b></p>
|
||||
<p>All content rendered stays directly in your browser.<br/>When loading an archive from Google Drive,
|
||||
the site may ask for account authorization to download the specified file only.</p>
|
||||
|
||||
<h4>Disclaimer of Warranties</h4>
|
||||
<p>The application is provided "as is" without any guarantees.</p>
|
||||
<details>
|
||||
<summary>Legalese:</summary>
|
||||
<p style="font-size: 0.8rem">DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS SOFTWARE TO YOU "AS AVAILABLE"
|
||||
AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
|
||||
INCLUDING WITHOUT LIMITATION ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.</p>
|
||||
</details>
|
||||
<div class="has-text-centered">
|
||||
<a class="button is-warning" href="#" @click="${
|
||||
this.onAboutClose
|
||||
}">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
return html` <sl-dialog
|
||||
class="about-dialog"
|
||||
label=${`About ReplayWeb.page ${IS_APP ? "App" : ""}`}
|
||||
?open=${this.showAbout}
|
||||
@sl-after-hide=${this.onAboutClose}
|
||||
>
|
||||
<div class="about-dialog-header">
|
||||
<div class="has-text-centered" style="width: 220px">
|
||||
<fa-icon
|
||||
size="3rem"
|
||||
.svg=${rwpLogo}
|
||||
aria-label="ReplayWeb.page Logo"
|
||||
role="img"
|
||||
></fa-icon>
|
||||
<div style="font-size: smaller; margin-bottom: 1em">
|
||||
${IS_APP ? "App" : ""} v${VERSION}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
${IS_APP
|
||||
? html`
|
||||
<p>
|
||||
ReplayWeb.page App is a standalone app for Mac, Windows and
|
||||
Linux that loads web archive files provided by the user and
|
||||
renders them for replay.
|
||||
</p>
|
||||
`
|
||||
: html` <p>
|
||||
<a href="https://replayweb.page" target="_blank"
|
||||
>ReplayWeb.page</a
|
||||
>
|
||||
is a browser-based viewer that loads web archive files provided by
|
||||
the user and renders them for replay in the browser.
|
||||
</p>`}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Full source code is available
|
||||
<a href="https://github.com/webrecorder/replayweb.page" target="_blank"
|
||||
>on GitHub</a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See the <a target="_blank" href="./docs">documentation</a> for more info
|
||||
on how it works.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
ReplayWeb.page is developed by
|
||||
<a href="https://webrecorder.net/" target="_blank">Webrecorder</a>.
|
||||
</p>
|
||||
|
||||
<sl-divider></sl-divider>
|
||||
<h3>Privacy</h3>
|
||||
<p>No data is uploaded anywhere and no information is collected.</p>
|
||||
<details>
|
||||
<summary>More info</summary>
|
||||
<p>
|
||||
All content rendered stays directly in your browser.<br />When loading
|
||||
an archive from Google Drive, the site may ask for account
|
||||
authorization to download the specified file only.
|
||||
</p>
|
||||
</details>
|
||||
<sl-divider></sl-divider>
|
||||
<h4>Disclaimer of Warranties</h4>
|
||||
<p>The application is provided "as is" without any guarantees.</p>
|
||||
<details>
|
||||
<summary>Legal</summary>
|
||||
<p>
|
||||
DISCLAIMER OF SOFTWARE WARRANTY. WEBRECORDER SOFTWARE PROVIDES THIS
|
||||
SOFTWARE TO YOU "AS AVAILABLE" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
</p>
|
||||
</details>
|
||||
<sl-button
|
||||
slot="footer"
|
||||
variant="primary"
|
||||
size="small"
|
||||
@click="${this.onAboutClose}"
|
||||
>Close</sl-button
|
||||
>
|
||||
</sl-dialog>`;
|
||||
}
|
||||
|
||||
onAboutClose() {
|
||||
readonly onAboutClose = () => {
|
||||
this.showAbout = false;
|
||||
}
|
||||
};
|
||||
|
||||
renderDropFileOverlay() {
|
||||
return html`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@charset "utf-8";
|
||||
|
||||
@import "@shoelace-style/shoelace/dist/themes/light.css";
|
||||
@import "./shoelace.scss";
|
||||
|
||||
/*$wr-green: #64e986;*/
|
||||
$wr-green: #55be6f;
|
||||
@@ -151,3 +152,20 @@ a:focus {
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
// Update size of small icons in controls
|
||||
.control.has-icons-left .icon.is-small, .control.has-icons-right .icon.is-small {
|
||||
margin-top: 1px;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: var(--sl-font-size-medium);
|
||||
}
|
||||
|
||||
.control.has-icons-left .icon.is-small {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.control.has-icons-right .icon.is-small {
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
sl-dialog {
|
||||
--header-spacing: var(--sl-spacing-small);
|
||||
--body-spacing: var(--sl-spacing-small);
|
||||
--footer-spacing: var(--sl-spacing-small);
|
||||
--width: 40rem;
|
||||
}
|
||||
|
||||
sl-dialog::part(header) {
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
sl-dialog::part(title) {
|
||||
font-size: var(--sl-font-size-medium);
|
||||
font-weight: var(--sl-font-weight-bold);
|
||||
}
|
||||
|
||||
sl-dialog::part(body) {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
sl-dialog::part(footer) {
|
||||
border-top: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { css, LitElement } from "lit";
|
||||
import { unsafeSVG } from "lit/directives/unsafe-svg.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
/**
|
||||
* Based on `<sl-icon>` but uses inline SVG.
|
||||
*/
|
||||
@customElement("wr-icon")
|
||||
export class Icon extends LitElement {
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Raw SVG string
|
||||
*/
|
||||
@property({ type: String })
|
||||
svg?: string;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
ariaHidden = "true";
|
||||
|
||||
render() {
|
||||
if (!this.svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
return unsafeSVG(this.svg);
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,6 @@ import faClone from "@fortawesome/fontawesome-free/svgs/regular/clone.svg";
|
||||
import faCheck from "@fortawesome/fontawesome-free/svgs/solid/check.svg";
|
||||
import faX from "@fortawesome/fontawesome-free/svgs/solid/times.svg";
|
||||
|
||||
import { registerIconLibrary } from "@shoelace-style/shoelace/dist/utilities/icon-library.js";
|
||||
|
||||
import systemLibrary from "@shoelace-style/shoelace/dist/components/icon/library.system.js";
|
||||
|
||||
// disable system library to prevent loading of unused data: URLs
|
||||
// allow only "x-lg" as it is needed for sl-dialog
|
||||
registerIconLibrary("system", {
|
||||
resolver: (name) => {
|
||||
if (name === "x-lg") {
|
||||
return systemLibrary.resolver(name);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
});
|
||||
|
||||
@customElement("wr-labeled-field")
|
||||
class LabeledField extends LitElement {
|
||||
@property({ type: String })
|
||||
|
||||
@@ -3,7 +3,8 @@ import webrecorderLockupColor from "~assets/brand/webrecorder-lockup-color.svg";
|
||||
import btAngleDoubleDown from "~assets/icons/chevron-double-down.svg";
|
||||
import btAngleDoubleUp from "~assets/icons/chevron-double-up.svg";
|
||||
import fabGithub from "@fortawesome/fontawesome-free/svgs/brands/github.svg";
|
||||
import fasDownload from "@fortawesome/fontawesome-free/svgs/solid/download.svg";
|
||||
|
||||
import iconDownload from "~icons/download.svg";
|
||||
|
||||
import { clickOnSpacebarPress } from "./misc";
|
||||
|
||||
@@ -246,11 +247,7 @@ export class RWPEmbedReceipt extends LitElement {
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<fa-icon
|
||||
size="1.0em"
|
||||
aria-hidden="true"
|
||||
.svg="${fasDownload}"
|
||||
></fa-icon>
|
||||
<wr-icon .svg="${iconDownload}"></wr-icon>
|
||||
</span>
|
||||
<span>Download Archive</span>
|
||||
</a>
|
||||
|
||||
+35
-20
@@ -13,6 +13,7 @@ import { property, query } from "lit/decorators.js";
|
||||
import type { FavIconEventDetail } from "./types";
|
||||
import type { EmbedReplayData } from "./item";
|
||||
import type { RwpPageLoadingEvent, RwpUrlChangeEvent } from "./events";
|
||||
import type { ReplayWebApp } from "./appmain";
|
||||
|
||||
type IframeMessage = MessageEvent<
|
||||
| RwpUrlChangeEvent["detail"]
|
||||
@@ -67,6 +68,7 @@ class Embed extends LitElement {
|
||||
@property({ type: Boolean }) hideOffscreen: boolean | undefined;
|
||||
|
||||
@property({ type: Boolean }) noMediaDownload = false;
|
||||
@property({ type: Boolean }) hideMetadataSidebar = false;
|
||||
|
||||
@property({ type: Boolean }) useAdblock = false;
|
||||
@property({ type: String }) adblockRulesUrl = DEFAULT_ADBLOCK_FILE;
|
||||
@@ -88,6 +90,8 @@ class Embed extends LitElement {
|
||||
@query("iframe")
|
||||
private readonly iframe?: HTMLIFrameElement | null;
|
||||
|
||||
mainElement?: ReplayWebApp | null;
|
||||
|
||||
replayfile = defaultReplayFile;
|
||||
mainElementName = "replay-app-main";
|
||||
appName = "ReplayWeb.page";
|
||||
@@ -267,20 +271,16 @@ class Embed extends LitElement {
|
||||
const qs = new URLSearchParams(window.location.hash.slice(1));
|
||||
|
||||
if (qs.has("url")) {
|
||||
// @ts-expect-error - TS2339 - Property 'url' does not exist on type 'Embed'.
|
||||
this.url = qs.get("url");
|
||||
this.url = qs.get("url") ?? "";
|
||||
}
|
||||
if (qs.has("ts")) {
|
||||
// @ts-expect-error - TS2339 - Property 'ts' does not exist on type 'Embed'.
|
||||
this.ts = qs.get("ts");
|
||||
this.ts = qs.get("ts") ?? "";
|
||||
}
|
||||
if (qs.has("query")) {
|
||||
// @ts-expect-error - TS2339 - Property 'query' does not exist on type 'Embed'.
|
||||
this.query = qs.get("query");
|
||||
this.query = qs.get("query") ?? "";
|
||||
}
|
||||
if (qs.has("view")) {
|
||||
// @ts-expect-error - TS2339 - Property 'view' does not exist on type 'Embed'.
|
||||
this.view = qs.get("view");
|
||||
this.view = qs.get("view") ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +333,7 @@ class Embed extends LitElement {
|
||||
ruffle?: "1";
|
||||
adblockUrl?: string;
|
||||
noMediaDownload?: string;
|
||||
hideMetadataSidebar?: string;
|
||||
} = {
|
||||
source,
|
||||
customColl: this.coll,
|
||||
@@ -378,6 +379,10 @@ class Embed extends LitElement {
|
||||
params.noMediaDownload = "1";
|
||||
}
|
||||
|
||||
if (this.hideMetadataSidebar) {
|
||||
params.hideMetadataSidebar = "1";
|
||||
}
|
||||
|
||||
this.paramString = new URLSearchParams(
|
||||
// Converting to unknown here so that we can ignore the URL -> string conversion necessary for the types to work out
|
||||
params as unknown as Record<string, string>,
|
||||
@@ -452,30 +457,40 @@ class Embed extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
|
||||
onLoad(event) {
|
||||
onLoad(event: Event) {
|
||||
if (this.isCrossOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const win = event.target.contentWindow;
|
||||
const doc = event.target.contentDocument;
|
||||
const target = event.target as HTMLIFrameElement;
|
||||
const win = target.contentWindow;
|
||||
const doc = target.contentDocument;
|
||||
|
||||
if (
|
||||
win.navigator.serviceWorker &&
|
||||
!win.navigator.serviceWorker.controller &&
|
||||
this.reloadCount <= 2
|
||||
) {
|
||||
if (!win) {
|
||||
console.debug("missing `event.target.contentWindow`");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!win.navigator.serviceWorker.controller && this.reloadCount <= 2) {
|
||||
this.reloadCount++;
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
setTimeout(() => win.location.reload(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.reloadCount = 0;
|
||||
|
||||
if (win.customElements.get(this.mainElementName)) {
|
||||
if (!doc) {
|
||||
console.debug("missing `event.target.contentDocument`");
|
||||
return;
|
||||
}
|
||||
|
||||
if (win?.customElements.get(this.mainElementName)) {
|
||||
this.mainElement = doc.querySelector(this.mainElementName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!scriptSrc) {
|
||||
console.debug("missing `scriptSrc`");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { URLResources } from "./url-resources";
|
||||
import { Embed } from "./embed";
|
||||
import "./coll-description";
|
||||
import "./shoelace";
|
||||
import "./components/icon";
|
||||
|
||||
import rwpIcon from "~assets/icons/replaywebpage.svg";
|
||||
import rwpLogoAnimated from "~assets/brand/replaywebpage-icon-color-animated.svg";
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import { wrapCss, apiPrefix } from "./misc";
|
||||
import fasArrowUp from "@fortawesome/fontawesome-free/svgs/solid/angle-double-up.svg";
|
||||
import fasArrowDown from "@fortawesome/fontawesome-free/svgs/solid/angle-double-down.svg";
|
||||
|
||||
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
||||
import iconSearch from "~icons/search.svg";
|
||||
|
||||
import type { ItemType } from "./types";
|
||||
|
||||
@@ -306,7 +306,7 @@ class ItemIndex extends LitElement {
|
||||
placeholder="Search by Archive Title or Source"
|
||||
/>
|
||||
<span class="icon is-left is-small">
|
||||
<fa-icon .svg="${fasSearch}"></fa-icon>
|
||||
<wr-icon .svg="${iconSearch}"></wr-icon>
|
||||
</span>
|
||||
</div>
|
||||
<wr-sorter
|
||||
|
||||
+452
-408
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,9 @@ export function updateFaviconLinks(data: FavIconEventDetail) {
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
/**
|
||||
* @deprecated Use `<wr-icon>`
|
||||
*/
|
||||
class FaIcon extends LitElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
+130
-44
@@ -1,10 +1,19 @@
|
||||
import prettyBytes from "pretty-bytes";
|
||||
|
||||
import { LitElement, html, css, unsafeCSS, type PropertyValues } from "lit";
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
unsafeCSS,
|
||||
type PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
import "keyword-mark-element/lib/keyword-mark.js";
|
||||
|
||||
import iconImageAlt from "~icons/image-alt.svg";
|
||||
|
||||
import { getPageDateTS, getReplayLink } from "./pageutils";
|
||||
|
||||
import { wrapCss } from "./misc";
|
||||
@@ -15,6 +24,7 @@ import {
|
||||
timeFormatter,
|
||||
} from "./utils/dateTimeFormatter";
|
||||
import type { TabNavEvent } from "./events";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
// ===========================================================================
|
||||
class PageEntry extends LitElement {
|
||||
@@ -48,6 +58,9 @@ class PageEntry extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
isSidebar = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
useFaviconFallback = false;
|
||||
|
||||
@state()
|
||||
thumbnailValid = true;
|
||||
|
||||
@@ -70,6 +83,26 @@ class PageEntry extends LitElement {
|
||||
width: unset !important;
|
||||
}
|
||||
|
||||
:host(.sidebar) .page-title {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
:host(.sidebar) .page-title,
|
||||
:host(.sidebar) .page-url {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host(.sidebar) .page-link:not(.current):hover {
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
}
|
||||
|
||||
:host(.sidebar) .current {
|
||||
background-color: var(--sl-color-blue-100);
|
||||
}
|
||||
|
||||
:host(.sidebar) {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -92,23 +125,62 @@ class PageEntry extends LitElement {
|
||||
margin-bottom: calc(-0.75rem + 2px);
|
||||
}
|
||||
|
||||
.favicon {
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
.thumbnail {
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
border: 1px solid var(--sl-color-neutral-200);
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
}
|
||||
|
||||
.thumbnail-placeholder {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.thumbnail-placeholder-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: var(--sl-spacing-2x-small);
|
||||
}
|
||||
|
||||
.thumbnail-placeholder-content .icon {
|
||||
margin: var(--sl-spacing-2x-small) 0;
|
||||
color: var(--sl-color-neutral-300);
|
||||
}
|
||||
|
||||
.thumbnail-placeholder-label {
|
||||
font-size: var(--sl-font-size-2x-small);
|
||||
color: var(--sl-color-neutral-500);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.page-link {
|
||||
transition: background-color var(--sl-transition-x-fast);
|
||||
}
|
||||
|
||||
.page-link:hover .page-title {
|
||||
color: var(--sl-color-blue-700);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-weight: var(--sl-font-weight-bold);
|
||||
color: var(--sl-color-blue-600);
|
||||
transition: color var(--sl-transition-x-fast);
|
||||
}
|
||||
|
||||
.media-left .favicon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
.media-left img.favicon {
|
||||
filter: drop-shadow(1px 1px 2px grey);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.media-content .favicon {
|
||||
width: 1.15rem;
|
||||
height: 1.15rem;
|
||||
margin: 0 0.25rem;
|
||||
display: inline-block;
|
||||
vertical-align: -0.25rem;
|
||||
}
|
||||
|
||||
.media-left {
|
||||
@@ -141,18 +213,6 @@ class PageEntry extends LitElement {
|
||||
|
||||
${PageEntry.sidebarStyles(unsafeCSS`:host(.sidebar)`)}
|
||||
|
||||
.current a {
|
||||
background-color: rgb(207, 243, 255);
|
||||
font-style: italic;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.current .curr-page {
|
||||
font-style: italic;
|
||||
font-size: 9px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.is-inline-date {
|
||||
display: none;
|
||||
}
|
||||
@@ -164,6 +224,16 @@ class PageEntry extends LitElement {
|
||||
.col-date {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.date-time,
|
||||
.is-inline-date {
|
||||
font-size: var(--sl-font-size-small);
|
||||
color: var(--sl-color-neutral-700);
|
||||
}
|
||||
|
||||
.page-url {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -185,10 +255,6 @@ class PageEntry extends LitElement {
|
||||
}
|
||||
${prefix} .is-inline-date {
|
||||
display: initial !important;
|
||||
font-style: italic;
|
||||
}
|
||||
${prefix} .media-left {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -229,12 +295,15 @@ class PageEntry extends LitElement {
|
||||
: ""}
|
||||
<div class="column col-date is-2">
|
||||
<div>${date ? dateFormatter.format(date) : ""}</div>
|
||||
<div>${date ? timeFormatter.format(date) : ""}</div>
|
||||
<div class="date-time">${date ? timeFormatter.format(date) : ""}</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div
|
||||
class="page-link column ${this.isCurrent ? "current" : ""}"
|
||||
aria-current=${ifDefined(this.isCurrent ? "location" : undefined)}
|
||||
>
|
||||
<div class="media">
|
||||
<figure class="media-left">${this.renderPageIcon()}</figure>
|
||||
<div class="media-content ${this.isCurrent ? "current" : ""}">
|
||||
<div class="media-content">
|
||||
<div role="heading" aria-level="${this.isSidebar ? "4" : "3"}">
|
||||
<a
|
||||
@dblclick="${this.onReload}"
|
||||
@@ -246,17 +315,20 @@ class PageEntry extends LitElement {
|
||||
this.page!.waczhash,
|
||||
)}"
|
||||
>
|
||||
<p class="is-size-6 has-text-weight-bold has-text-link text">
|
||||
<keyword-mark keywords="${this.query}"
|
||||
<p class="page-title text">
|
||||
${this.thumbnailValid ? this.renderFavicon() : nothing}
|
||||
<keyword-mark
|
||||
keywords="${this.query}"
|
||||
title="${p.title || p.url}"
|
||||
>${p.title || p.url}</keyword-mark
|
||||
>
|
||||
</p>
|
||||
<p class="has-text-dark text">
|
||||
<keyword-mark keywords="${this.query}"
|
||||
<p class="page-url has-text-dark text">
|
||||
<keyword-mark keywords="${this.query}" title="${p.url}"
|
||||
>${p.url}</keyword-mark
|
||||
>${this.thumbnailValid ? this.renderFavicon() : ""}
|
||||
>
|
||||
</p>
|
||||
<p class="has-text-grey-dark text is-inline-date">
|
||||
<p class="text is-inline-date">
|
||||
${date ? dateTimeFormatter.format(date) : ""}
|
||||
</p>
|
||||
</a>
|
||||
@@ -269,9 +341,7 @@ class PageEntry extends LitElement {
|
||||
: html``}
|
||||
</div>
|
||||
${hasSize
|
||||
? html` <div class="media-right" style="margin-right: 2em">
|
||||
${prettyBytes(p.size)}
|
||||
</div>`
|
||||
? html` <div class="text">${prettyBytes(p.size)}</div>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
@@ -301,14 +371,30 @@ class PageEntry extends LitElement {
|
||||
|
||||
private renderPageIcon() {
|
||||
if (!this.thumbnailValid) {
|
||||
return this.renderFavicon();
|
||||
return html`<div
|
||||
class="thumbnail-placeholder image is-16by9 ${this.useFaviconFallback
|
||||
? "favicon-fallback"
|
||||
: "thumbnail"}"
|
||||
>
|
||||
<div class="thumbnail-placeholder-content">
|
||||
${this.useFaviconFallback
|
||||
? this.renderFavicon()
|
||||
: html`<span class="icon is-small">
|
||||
<wr-icon .svg="${iconImageAlt}" aria-hidden="true"></wr-icon>
|
||||
</span>
|
||||
<span class="thumbnail-placeholder-label">No image</span>`}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
return html`<img
|
||||
class="thumbnail"
|
||||
@error=${() => (this.thumbnailValid = false)}
|
||||
src=${`${this.getReplayPrefix()}/urn:thumbnail:${this.page!.url}`}
|
||||
loading="lazy"
|
||||
/>`;
|
||||
|
||||
return html`<div class="image is-16by9">
|
||||
<img
|
||||
class="thumbnail"
|
||||
@error=${() => (this.thumbnailValid = false)}
|
||||
src=${`${this.getReplayPrefix()}/urn:thumbnail:${this.page!.url}`}
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private renderFavicon() {
|
||||
|
||||
+201
-106
@@ -1,7 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
import { LitElement, html, css, unsafeCSS, type PropertyValues } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
unsafeCSS,
|
||||
type PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { wrapCss, clickOnSpacebarPress } from "./misc";
|
||||
import ndjson from "fetch-ndjson";
|
||||
@@ -11,9 +18,10 @@ import prettyBytes from "pretty-bytes";
|
||||
|
||||
import { getTS, getPageDateTS } from "./pageutils";
|
||||
|
||||
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
||||
import fasAngleDown from "@fortawesome/fontawesome-free/svgs/solid/angle-down.svg";
|
||||
import fasEdit from "@fortawesome/fontawesome-free/svgs/solid/edit.svg";
|
||||
import iconArrowDown from "~icons/arrow-down.svg";
|
||||
import iconChevronDown from "~icons/chevron-down.svg";
|
||||
import iconPencil from "~icons/pencil.svg";
|
||||
import iconSearch from "~icons/search.svg";
|
||||
|
||||
import type { Sorter } from "./sorter";
|
||||
import type { PageEntry } from "./pageentry";
|
||||
@@ -100,6 +108,9 @@ class Pages extends LitElement {
|
||||
@property({ type: Object })
|
||||
collInfo: ItemType | Record<string, never> | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
hideMetadataSidebar = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
allSelected = false;
|
||||
|
||||
@@ -109,15 +120,15 @@ class Pages extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
dynamicPagesQuery = false;
|
||||
|
||||
@property({ type: Number })
|
||||
totalPages = 0;
|
||||
|
||||
@property({ type: Number })
|
||||
dynamicPageCount = 1;
|
||||
|
||||
@property({ type: Boolean })
|
||||
skipScrollMore = false;
|
||||
|
||||
@state()
|
||||
private filteredPagesTotal = 0;
|
||||
|
||||
private _ival: number | undefined;
|
||||
|
||||
static get sortKeys() {
|
||||
@@ -222,17 +233,20 @@ class Pages extends LitElement {
|
||||
}
|
||||
this.loading = true;
|
||||
if (this.flex && this.query && this.textPages) {
|
||||
const results = await this.flex.searchAsync(this.query, 25);
|
||||
const results = await this.flex.searchAsync(
|
||||
this.query,
|
||||
DYNAMIC_PAGE_SIZE,
|
||||
);
|
||||
this.filteredPages = results.map(
|
||||
(inx: Id) => this.textPages![inx as number],
|
||||
);
|
||||
} else if (this.showAllPages && this.hasExtraPages) {
|
||||
this.filteredPages = [...this.textPages!];
|
||||
this.filteredPages = [...this.textPages!, ...this.collInfo!.pages];
|
||||
} else if (!this.dynamicPagesQuery) {
|
||||
this.filteredPages = [...this.collInfo!.pages];
|
||||
}
|
||||
|
||||
this.totalPages = this.filteredPages.length;
|
||||
this.filteredPagesTotal = this.filteredPages.length;
|
||||
|
||||
if (this.dynamicPagesQuery) {
|
||||
if (!this.query) {
|
||||
@@ -347,7 +361,7 @@ class Pages extends LitElement {
|
||||
this.filteredPages = [...this.filteredPages, ...newPages];
|
||||
}
|
||||
|
||||
this.totalPages = this.filteredPages.length;
|
||||
this.filteredPagesTotal = this.filteredPages.length;
|
||||
}
|
||||
|
||||
async filterCurated() {
|
||||
@@ -369,10 +383,7 @@ class Pages extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'pages' implicitly has an 'any' type.
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
addPages(pages) {
|
||||
async addPages(pages: Page[]) {
|
||||
const flex = new FlexIndex();
|
||||
|
||||
this.flex = flex;
|
||||
@@ -380,8 +391,7 @@ class Pages extends LitElement {
|
||||
|
||||
if (!this.dynamicPagesQuery) {
|
||||
this.hasExtraPages = Boolean(
|
||||
this.textPages &&
|
||||
this.collInfo?.pages &&
|
||||
this.collInfo?.pages &&
|
||||
this.textPages.length > this.collInfo.pages.length,
|
||||
);
|
||||
}
|
||||
@@ -391,10 +401,7 @@ class Pages extends LitElement {
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'page' implicitly has an 'any' type. | TS7006 - Parameter 'index' implicitly has an 'any' type.
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
pages.map((page, index) => {
|
||||
pages.map(async (page, index) => {
|
||||
let text = page.url;
|
||||
if (page.title) {
|
||||
text += " " + page.title;
|
||||
@@ -402,8 +409,6 @@ class Pages extends LitElement {
|
||||
if (page.text) {
|
||||
text += " " + page.text;
|
||||
}
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
return flex.addAsync(index, text);
|
||||
}),
|
||||
);
|
||||
@@ -433,7 +438,7 @@ class Pages extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
const lines: unknown[] = [];
|
||||
const lines: Page[] = [];
|
||||
|
||||
for await (const line of ndjson(resp.body!.getReader())) {
|
||||
if (!line.url) {
|
||||
@@ -445,7 +450,7 @@ class Pages extends LitElement {
|
||||
if (typeof line.ts === "string") {
|
||||
line.ts = new Date(line.ts as string).getTime();
|
||||
}
|
||||
lines.push(line);
|
||||
lines.push(line as unknown as Page);
|
||||
}
|
||||
|
||||
await this.addPages(lines);
|
||||
@@ -489,6 +494,7 @@ class Pages extends LitElement {
|
||||
.header.columns {
|
||||
width: 100%;
|
||||
margin-bottom: 0px;
|
||||
padding: var(--sl-spacing-x-small) var(--sl-spacing-small);
|
||||
}
|
||||
.header a {
|
||||
color: black;
|
||||
@@ -503,8 +509,6 @@ class Pages extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0px;
|
||||
margin-top: 0.5em;
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
@@ -516,10 +520,10 @@ class Pages extends LitElement {
|
||||
.index-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 3px solid rgb(237, 237, 237);
|
||||
background-color: whitesmoke;
|
||||
padding-right: 0px;
|
||||
border-right: 1px solid var(--sl-panel-border-color);
|
||||
background-color: var(--sl-color-neutral-50);
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.index-bar-label {
|
||||
@@ -527,17 +531,27 @@ class Pages extends LitElement {
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
color: var(--sl-color-neutral-500);
|
||||
margin-bottom: var(--sl-spacing-2x-small);
|
||||
margin-bottom: var(--sl-spacing-x-small);
|
||||
padding: 0 var(--sl-spacing-small);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.index-bar-label:first-child {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.index-bar-title {
|
||||
font-size: var(--sl-font-size-large);
|
||||
font-size: var(--sl-font-size-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
margin-bottom: var(--sl-spacing-large);
|
||||
margin-bottom: var(--sl-spacing-small);
|
||||
padding: 0 var(--sl-spacing-small);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.index-bar-title + .index-bar-label {
|
||||
margin-top: var(--sl-spacing-medium);
|
||||
}
|
||||
|
||||
.index-bar-title:hover + .editIcon {
|
||||
display: block;
|
||||
}
|
||||
@@ -565,9 +579,59 @@ class Pages extends LitElement {
|
||||
}
|
||||
|
||||
.num-results {
|
||||
font-style: italic;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sl-spacing-small);
|
||||
justify-content: space-between;
|
||||
font-weight: normal;
|
||||
line-height: 2.5;
|
||||
font-size: var(--sl-font-size-small);
|
||||
color: var(--sl-color-neutral-700);
|
||||
line-height: 1;
|
||||
border-top: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
.main-num-results {
|
||||
padding: var(--sl-spacing-x-small) var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.sidebar-num-results {
|
||||
padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.seed-pages-filter {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
.seed-pages-filter .checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-x-small);
|
||||
line-height: 1.5;
|
||||
padding-inline-end: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.seed-pages-filter .checkbox span {
|
||||
margin-top: -0.0875rem;
|
||||
}
|
||||
|
||||
.sorter {
|
||||
gap: var(--sl-spacing-x-small);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sorter wr-sorter {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sort-control-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-2x-small);
|
||||
font-size: var(--sl-font-size-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
color: var(--sl-color-neutral-600);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.asc:after {
|
||||
@@ -613,19 +677,31 @@ class Pages extends LitElement {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
:host(.sidebar) .search-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
|
||||
padding-bottom: 1em;
|
||||
padding: var(--sl-spacing-small);
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.scroller-help-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-2x-small);
|
||||
color: var(--sl-color-neutral-500);
|
||||
}
|
||||
|
||||
.page-entry {
|
||||
padding-bottom: 1.5rem;
|
||||
margin-right: -1.5rem;
|
||||
}
|
||||
|
||||
.selected {
|
||||
@@ -643,19 +719,22 @@ class Pages extends LitElement {
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
min-height: fit-content;
|
||||
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 3px solid rgb(237, 237, 237);
|
||||
}
|
||||
|
||||
.check-select {
|
||||
padding: 0 1em 0 0.5em;
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
width: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-small);
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
font-size: var(--sl-font-size-small);
|
||||
padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.search-bar .input {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
@@ -663,11 +742,11 @@ class Pages extends LitElement {
|
||||
}
|
||||
|
||||
.index-bar-description {
|
||||
margin-bottom: var(--sl-spacing-x-small);
|
||||
font-size: var(--sl-font-size-small);
|
||||
line-height: var(--sl-line-height-normal);
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
padding: 0 var(--sl-spacing-small);
|
||||
}
|
||||
`);
|
||||
}
|
||||
@@ -698,8 +777,7 @@ class Pages extends LitElement {
|
||||
}
|
||||
|
||||
${prefix} .mobile-header {
|
||||
margin: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
margin: var(--sl-spacing-x-small);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -728,6 +806,9 @@ class Pages extends LitElement {
|
||||
|
||||
render() {
|
||||
const currList = this.currList;
|
||||
const collList = this.collInfo?.lists.length;
|
||||
const hideSideCol =
|
||||
this.hideMetadataSidebar && !this.editable && !this.editing && !collList;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -737,11 +818,11 @@ class Pages extends LitElement {
|
||||
>
|
||||
Pages in ${this.collInfo!.title}
|
||||
</div>
|
||||
<div class="search-bar notification is-marginless">
|
||||
<div class="search-bar is-marginless">
|
||||
${this.isSidebar
|
||||
? html`<h3 class="is-sr-only">Search and Filter Pages</h3>`
|
||||
: ""}
|
||||
<div class="field flex-auto">
|
||||
<div class="flex-auto">
|
||||
<div
|
||||
class="control has-icons-left ${this.loading ? "is-loading" : ""}"
|
||||
>
|
||||
@@ -752,16 +833,19 @@ class Pages extends LitElement {
|
||||
type="text"
|
||||
placeholder="Search by Page URL, Title, or Text"
|
||||
/>
|
||||
<span class="icon is-left"
|
||||
><fa-icon .svg="${fasSearch}" aria-hidden="true"></fa-icon
|
||||
<span class="icon is-left is-small"
|
||||
><wr-icon .svg="${iconSearch}"></wr-icon
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
${this.renderSeedPagesFilter()}
|
||||
</div>
|
||||
<div class="main columns">
|
||||
<div
|
||||
class="column index-bar is-one-fifth ${this.isSidebar
|
||||
? "is-hidden-mobile"
|
||||
: hideSideCol
|
||||
? "is-hidden"
|
||||
: ""}"
|
||||
>
|
||||
${this.editable && this.editing
|
||||
@@ -775,11 +859,9 @@ class Pages extends LitElement {
|
||||
/>
|
||||
</form>
|
||||
`
|
||||
: html` <div
|
||||
class="index-bar-label ${this.collInfo!.description
|
||||
? "is-hidden-mobile"
|
||||
: "is-sr-only"}"
|
||||
>
|
||||
: this.hideMetadataSidebar
|
||||
? nothing
|
||||
: html`<div class="index-bar-label is-hidden-mobile">
|
||||
Collection Name
|
||||
</div>
|
||||
<div
|
||||
@@ -802,36 +884,14 @@ class Pages extends LitElement {
|
||||
</div>`
|
||||
: html``}`}
|
||||
${this.editable
|
||||
? html`<fa-icon class="editIcon" .svg="${fasEdit}"></fa-icon>`
|
||||
? html`<wr-icon class="editIcon" .svg="${iconPencil}"></wr-icon>`
|
||||
: html``}
|
||||
${this.hasExtraPages
|
||||
? html` <span class="check-select">
|
||||
<label class="checkbox">
|
||||
<input
|
||||
@change=${(e: Event) =>
|
||||
(this.showAllPages = (
|
||||
e.currentTarget as HTMLInputElement
|
||||
).checked)}
|
||||
type="checkbox"
|
||||
.checked="${this.showAllPages}"
|
||||
/>
|
||||
Show Non-Seed Pages
|
||||
</label>
|
||||
</span>`
|
||||
: ""}
|
||||
|
||||
<span
|
||||
class="num-results is-hidden-mobile"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>${this.formatResults()}</span
|
||||
>
|
||||
${this.editable
|
||||
? html` <div class="index-bar-actions">
|
||||
${this.renderDownloadMenu()}
|
||||
</div>`
|
||||
: ""}
|
||||
${this.collInfo!.lists.length
|
||||
${collList
|
||||
? html`
|
||||
<p id="filter-label" class="menu-label">Filter By List:</p>
|
||||
<div class="index-bar-menu menu">
|
||||
@@ -889,7 +949,7 @@ class Pages extends LitElement {
|
||||
>
|
||||
<span>Download</span>
|
||||
<span class="icon is-small">
|
||||
<fa-icon .svg="${fasAngleDown}" aria-hidden="true"></fa-icon>
|
||||
<wr-icon .svg="${iconChevronDown}"></wr-icon>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -967,7 +1027,7 @@ class Pages extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key=""
|
||||
class="column is-1 ${this.sortKey === ""
|
||||
class="sort-control-label column is-1 ${this.sortKey === ""
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -982,7 +1042,7 @@ class Pages extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="date"
|
||||
class="column is-2 ${this.sortKey === "date"
|
||||
class="sort-control-label column is-2 ${this.sortKey === "date"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -998,8 +1058,9 @@ class Pages extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="title"
|
||||
class="column is-6 pagetitle ${this.query ? "is-5" : "is-6"} ${this
|
||||
.sortKey === "title"
|
||||
class="sort-control-label column is-6 pagetitle ${this.query
|
||||
? "is-5"
|
||||
: "is-6"} ${this.sortKey === "title"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -1008,17 +1069,16 @@ class Pages extends LitElement {
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="is-hidden-tablet mobile-header">
|
||||
<div class="num-results" aria-live="polite" aria-atomic="true">
|
||||
${this.formatResults()}
|
||||
</div>
|
||||
<div class="sorter is-hidden-tablet mobile-header">
|
||||
<wr-sorter
|
||||
id="pages"
|
||||
.sortKey="${this.sortKey}"
|
||||
.sortDesc="${this.sortDesc}"
|
||||
.sortKeys="${Pages.sortKeys}"
|
||||
.data="${this.filteredPages}"
|
||||
.pageResults="${this.dynamicPagesQuery ? this.totalPages : 100}"
|
||||
.pageResults="${this.dynamicPagesQuery
|
||||
? this.filteredPagesTotal
|
||||
: 100}"
|
||||
@sort-changed="${this.onSortChanged}"
|
||||
class="${this.filteredPages.length ? "" : "is-hidden"}"
|
||||
>
|
||||
@@ -1092,9 +1152,7 @@ class Pages extends LitElement {
|
||||
renderPages() {
|
||||
//const name = this.currList === 0 ? "All Pages" : this.collInfo.lists[this.currList - 1].title;
|
||||
return html`
|
||||
<div class="page-header has-text-weight-bold">
|
||||
${this.renderPageHeader()}
|
||||
</div>
|
||||
<div class="page-header">${this.renderPageHeader()}</div>
|
||||
<ul class="scroller" @scroll="${this.onScroll}">
|
||||
${this.sortedPages.length
|
||||
? html` ${this.sortedPages.map((p, i) => {
|
||||
@@ -1107,6 +1165,7 @@ class Pages extends LitElement {
|
||||
?selected="${selected}"
|
||||
?isCurrent="${this.isCurrPage(p)}"
|
||||
?isSidebar="${this.isSidebar}"
|
||||
useFaviconFallback
|
||||
.page="${p}"
|
||||
pid="${p.id}"
|
||||
@sel-page="${this.onSelectToggle}"
|
||||
@@ -1120,6 +1179,15 @@ class Pages extends LitElement {
|
||||
})}`
|
||||
: html`<p class="mobile-header">${this.getNoResultsMessage()}</p>`}
|
||||
</ul>
|
||||
<div
|
||||
class="num-results ${this.isSidebar
|
||||
? "sidebar-num-results"
|
||||
: "main-num-results"}"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
${this.renderPaginationResults()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1310,13 +1378,41 @@ class Pages extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
formatResults() {
|
||||
const length = this.totalPages;
|
||||
if (length === this.sortedPages.length) {
|
||||
return `${length} Page${length !== 1 ? "s" : ""}`;
|
||||
} else {
|
||||
return `${this.sortedPages.length} of ${length} Pages Shown`;
|
||||
}
|
||||
renderPaginationResults() {
|
||||
const total = this.filteredPagesTotal;
|
||||
const shown = this.sortedPages.length;
|
||||
|
||||
const totalStr = `${total.toLocaleString()} ${
|
||||
total === 1 ? "page" : "pages"
|
||||
}`;
|
||||
const allShown = total === shown;
|
||||
const countMsg = allShown
|
||||
? totalStr
|
||||
: `${shown.toLocaleString()} of ${totalStr}`;
|
||||
|
||||
return html`<span>${countMsg}</span>
|
||||
${allShown
|
||||
? nothing
|
||||
: html`<span class="scroller-help-text">
|
||||
Scroll for more
|
||||
<wr-icon .svg="${iconArrowDown}"></wr-icon>
|
||||
</span>`} `;
|
||||
}
|
||||
|
||||
renderSeedPagesFilter() {
|
||||
if (!this.hasExtraPages) return;
|
||||
|
||||
return html`<div class="seed-pages-filter check-select">
|
||||
<label class="checkbox">
|
||||
<input
|
||||
@change=${(e: Event) =>
|
||||
(this.showAllPages = (e.currentTarget as HTMLInputElement).checked)}
|
||||
type="checkbox"
|
||||
.checked="${this.showAllPages}"
|
||||
/>
|
||||
<span>Include Non-Seed Pages</span>
|
||||
</label>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
getNoResultsMessage() {
|
||||
@@ -1345,9 +1441,8 @@ class Pages extends LitElement {
|
||||
return "No Pages Found";
|
||||
}
|
||||
|
||||
// @ts-expect-error [// TODO: Fix this the next time the file is edited.] - TS7006 - Parameter 'event' implicitly has an 'any' type.
|
||||
async onScroll(event) {
|
||||
const element = event.currentTarget;
|
||||
async onScroll(event: Event) {
|
||||
const element = event.currentTarget as HTMLUListElement;
|
||||
const diff =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
if (diff < 40 && !this.skipScrollMore) {
|
||||
|
||||
+51
-15
@@ -1,13 +1,17 @@
|
||||
import { LitElement, html, css, type PropertyValues } from "lit";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { property, query, state } from "lit/decorators.js";
|
||||
|
||||
import { wrapCss } from "./misc";
|
||||
import rwpLogo from "~assets/brand/replaywebpage-icon-color.svg";
|
||||
import type { ItemType } from "./types";
|
||||
import type { ReplayLoadingDetail, TabNavEvent } from "./events";
|
||||
import { keyed } from "lit/directives/keyed.js";
|
||||
|
||||
/**
|
||||
* @cssPart iframe
|
||||
* @cssPart iframe-container
|
||||
* @cssPart iframe-page-not-found
|
||||
* @fires update-title
|
||||
* @fires coll-tab-nav
|
||||
* @fires update-title
|
||||
@@ -62,12 +66,31 @@ class Replay extends LitElement {
|
||||
@property({ type: String })
|
||||
downloadResUrl = "";
|
||||
|
||||
@query("iframe")
|
||||
readonly iframe?: HTMLIFrameElement | null;
|
||||
|
||||
/**
|
||||
* Use key to force iframe to update even if `src` hasn't changed,
|
||||
* for example, if the iframe window has navigated to a linked page
|
||||
* and is being externally reset to a seed URL.
|
||||
*/
|
||||
@state()
|
||||
private iframeUrlUpdateKey?: number;
|
||||
|
||||
private reauthWait: null | Promise<void> = null;
|
||||
|
||||
private _loadPoll: null | number = null;
|
||||
|
||||
private hiliter: HoverHiliter | null = null;
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
if (this._loadPoll) {
|
||||
window.clearInterval(this._loadPoll);
|
||||
}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
window.addEventListener("message", (event) => this.onReplayMessage(event));
|
||||
// TODO: Fix this the next time the file is edited.
|
||||
@@ -121,7 +144,7 @@ class Replay extends LitElement {
|
||||
: "";
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (
|
||||
changedProperties.has("sourceUrl") ||
|
||||
changedProperties.has("collInfo")
|
||||
@@ -138,9 +161,17 @@ class Replay extends LitElement {
|
||||
this.replayTS = this.ts;
|
||||
this.showAuth = false;
|
||||
this.reauthWait = null;
|
||||
this.doSetIframeUrl();
|
||||
}
|
||||
|
||||
const prevIframe = this.iframeUrl;
|
||||
this.doSetIframeUrl();
|
||||
|
||||
if (prevIframe === this.iframeUrl) {
|
||||
this.iframeUrlUpdateKey = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (this.iframeUrl && changedProperties.has("iframeUrl")) {
|
||||
this.waitForLoad();
|
||||
|
||||
@@ -434,17 +465,22 @@ class Replay extends LitElement {
|
||||
: undefined,
|
||||
)}"
|
||||
></a>
|
||||
<div class="iframe-container">
|
||||
<iframe
|
||||
class="iframe-main"
|
||||
name="___wb_replay_top_frame"
|
||||
@message="${this.onReplayMessage}"
|
||||
allow="autoplay 'self'; fullscreen"
|
||||
allowfullscreen
|
||||
src="${this.iframeUrl}"
|
||||
title="${title}"
|
||||
></iframe>
|
||||
|
||||
<div part="iframe-container" class="iframe-container">
|
||||
${keyed(
|
||||
this.iframeUrlUpdateKey,
|
||||
html`<iframe
|
||||
part="iframe ${this.replayNotFoundError
|
||||
? "iframe-page-not-found"
|
||||
: ""}"
|
||||
class="iframe-main"
|
||||
name="___wb_replay_top_frame"
|
||||
@message="${this.onReplayMessage}"
|
||||
allow="autoplay 'self'; fullscreen"
|
||||
allowfullscreen
|
||||
src="${this.iframeUrl}"
|
||||
title="${title}"
|
||||
></iframe>`,
|
||||
)}
|
||||
${this.showAuth
|
||||
? html`
|
||||
<div class="iframe-main modal-bg">
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { registerIconLibrary } from "@shoelace-style/shoelace/dist/utilities/icon-library.js";
|
||||
import systemLibrary from "@shoelace-style/shoelace/dist/components/icon/library.system.js";
|
||||
|
||||
// Cherry-picked Shoelace components
|
||||
import "@shoelace-style/shoelace/dist/components/alert/alert.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/copy-button/copy-button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/dialog/dialog.js";
|
||||
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
|
||||
import "@shoelace-style/shoelace/dist/components/dropdown/dropdown.js";
|
||||
import "@shoelace-style/shoelace/dist/components/menu/menu.js";
|
||||
import "@shoelace-style/shoelace/dist/components/menu-item/menu-item.js";
|
||||
import "@shoelace-style/shoelace/dist/components/menu-label/menu-label.js";
|
||||
|
||||
// disable system library to prevent loading of unused data: URLs
|
||||
// allow only "x-lg" as it is needed for sl-dialog
|
||||
registerIconLibrary("system", {
|
||||
resolver: (name) => {
|
||||
if (name === "x-lg") {
|
||||
return systemLibrary.resolver(name);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
});
|
||||
|
||||
+36
-26
@@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { wrapCss } from "./misc";
|
||||
|
||||
import fasSortDown from "@fortawesome/fontawesome-free/svgs/solid/sort-down.svg";
|
||||
import fasSortUp from "@fortawesome/fontawesome-free/svgs/solid/sort-up.svg";
|
||||
import iconSortDown from "~icons/sort-down.svg";
|
||||
import iconSortUp from "~icons/sort-up.svg";
|
||||
|
||||
// ===========================================================================
|
||||
@customElement("wr-sorter")
|
||||
@@ -118,34 +118,44 @@ class Sorter<T = unknown> extends LitElement {
|
||||
button.button.is-small {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.is-small {
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="select is-small">
|
||||
<select id="sort-select" @change=${(e: Event) =>
|
||||
(this.sortKey = (e.currentTarget as HTMLSelectElement).value)}>
|
||||
|
||||
${this.sortKeys?.map(
|
||||
(sort) => html`
|
||||
<option value="${sort.key}" ?selected="${sort.key === this.sortKey}">
|
||||
Sort By: ${sort.name}
|
||||
</option>
|
||||
`,
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<button @click=${() =>
|
||||
(this.sortDesc = !this.sortDesc)} class="button is-small">
|
||||
<span>Order:</span>
|
||||
<span class="is-sr-only">${
|
||||
this.sortDesc ? "Ascending" : "Descending"
|
||||
}</span>
|
||||
<span class="icon"><fa-icon aria-hidden="true" .svg=${
|
||||
this.sortDesc ? fasSortUp : fasSortDown
|
||||
}></span>
|
||||
</button>`;
|
||||
return html` <div class="select is-small">
|
||||
<select
|
||||
id="sort-select"
|
||||
@change=${(e: Event) =>
|
||||
(this.sortKey = (e.currentTarget as HTMLSelectElement).value)}
|
||||
>
|
||||
${this.sortKeys?.map(
|
||||
(sort) => html`
|
||||
<option
|
||||
value="${sort.key}"
|
||||
?selected="${sort.key === this.sortKey}"
|
||||
>
|
||||
Sort By: ${sort.name}
|
||||
</option>
|
||||
`,
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
@click=${() => (this.sortDesc = !this.sortDesc)}
|
||||
class="button is-small"
|
||||
>
|
||||
<span>Order:</span>
|
||||
<span class="is-sr-only"
|
||||
>${this.sortDesc ? "Ascending" : "Descending"}</span
|
||||
>
|
||||
<span class="icon"
|
||||
><wr-icon .svg=${this.sortDesc ? iconSortDown : iconSortUp}></wr-icon
|
||||
></span>
|
||||
</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ export type ItemType = {
|
||||
totalSize?: unknown;
|
||||
size?: number | string;
|
||||
canQueryPages?: boolean;
|
||||
pageCount?: number;
|
||||
};
|
||||
|
||||
export type FavIconEventDetail = {
|
||||
|
||||
+110
-45
@@ -3,8 +3,8 @@ import { wrapCss, clickOnSpacebarPress } from "./misc";
|
||||
|
||||
import { getReplayLink, getDownloadLink } from "./pageutils";
|
||||
|
||||
import fasSearch from "@fortawesome/fontawesome-free/svgs/solid/search.svg";
|
||||
import fasDownload from "@fortawesome/fontawesome-free/svgs/solid/download.svg";
|
||||
import iconDownload from "~icons/download.svg";
|
||||
import iconSearch from "~icons/search.svg";
|
||||
|
||||
import "keyword-mark-element/lib/keyword-mark.js";
|
||||
import { type ItemType } from "./types";
|
||||
@@ -268,6 +268,10 @@ class URLResources extends LitElement {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
:host(.sidebar) .level-left {
|
||||
margin-bottom: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
:host(.sidebar) .columns {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
@@ -277,17 +281,33 @@ class URLResources extends LitElement {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.notification {
|
||||
.search-bar {
|
||||
gap: var(--sl-spacing-small);
|
||||
width: 100%;
|
||||
background-color: var(--sl-color-neutral-100);
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
font-size: var(--sl-font-size-small);
|
||||
padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.search-bar .level-item {
|
||||
gap: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.search-bar .input,
|
||||
.search-bar .radio {
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
.all-results {
|
||||
margin: 0 0 0 0.5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
.main-scroll {
|
||||
flex-grow: 1;
|
||||
flex: 1 1 0;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
.minihead {
|
||||
font-size: 10px;
|
||||
@@ -296,32 +316,58 @@ class URLResources extends LitElement {
|
||||
.columns {
|
||||
margin: 0px;
|
||||
}
|
||||
thead {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
table th:not([align]) {
|
||||
text-align: left;
|
||||
}
|
||||
.result {
|
||||
border-bottom: 1px #dbdbdb solid;
|
||||
min-height: fit-content;
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
|
||||
.result:first-child {
|
||||
margin-top: var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.result:not(:last-child) {
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
}
|
||||
|
||||
.results-head {
|
||||
border-bottom: 2px #dbdbdb solid;
|
||||
margin-right: 16px;
|
||||
border-bottom: 1px solid var(--sl-panel-border-color);
|
||||
min-height: fit-content;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-right: 15px; // For scrollbar gutter
|
||||
}
|
||||
.results-head a {
|
||||
color: black;
|
||||
|
||||
.results-head .column {
|
||||
background-color: var(--sl-panel-background-color);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
table .column {
|
||||
padding: var(--sl-spacing-x-small);
|
||||
}
|
||||
|
||||
.all-results .column {
|
||||
word-break: break-word;
|
||||
}
|
||||
.dl-button {
|
||||
padding: 4px;
|
||||
|
||||
tbody .col-url a {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.dl-button {
|
||||
display: inline-block;
|
||||
font-size: 1.125rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: var(--sl-spacing-3x-small);
|
||||
}
|
||||
|
||||
div.sort-header {
|
||||
padding: 10px;
|
||||
margin-bottom: 0px !important;
|
||||
@@ -330,6 +376,17 @@ class URLResources extends LitElement {
|
||||
.flex-auto {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.sort-control-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sl-spacing-2x-small);
|
||||
font-size: var(--sl-font-size-small);
|
||||
font-weight: var(--sl-font-weight-semibold);
|
||||
color: var(--sl-color-neutral-600);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.asc:after {
|
||||
content: "▼";
|
||||
font-size: 0.75em;
|
||||
@@ -338,9 +395,12 @@ class URLResources extends LitElement {
|
||||
content: "▲";
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.num-results {
|
||||
margin-left: 1em;
|
||||
font-style: italic;
|
||||
font-size: var(--sl-font-size-small);
|
||||
color: var(--sl-color-neutral-500);
|
||||
line-height: 1;
|
||||
margin-left: var(--sl-spacing-small);
|
||||
}
|
||||
`);
|
||||
}
|
||||
@@ -362,10 +422,10 @@ class URLResources extends LitElement {
|
||||
>
|
||||
Search and Filter
|
||||
</div>
|
||||
<div class="notification level is-marginless">
|
||||
<div class="search-bar level is-marginless">
|
||||
<div class="level-left flex-auto">
|
||||
<div class="level-item flex-auto">
|
||||
<span class="is-hidden-mobile">Search: </span>
|
||||
<span class="is-hidden-mobile">Search:</span>
|
||||
<div class="select">
|
||||
<select @change="${this.onChangeTypeSearch}">
|
||||
${URLResources.filters.map(
|
||||
@@ -393,15 +453,15 @@ class URLResources extends LitElement {
|
||||
.value="${this.query}"
|
||||
placeholder="Enter URL to Search"
|
||||
/>
|
||||
<span class="icon is-left"
|
||||
><fa-icon .svg="${fasSearch}"></fa-icon
|
||||
<span class="icon is-left is-small"
|
||||
><wr-icon .svg="${iconSearch}"></wr-icon
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control level-right">
|
||||
<div style="margin-left: 1em" class="control">
|
||||
<div class="control">
|
||||
<label class="radio has-text-left"
|
||||
><input
|
||||
type="radio"
|
||||
@@ -435,7 +495,9 @@ class URLResources extends LitElement {
|
||||
is-pulled-right
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>${this.filteredResults.length} Result(s)</span
|
||||
>${this.filteredResults.length.toLocaleString()}
|
||||
${this.filteredResults.length === 1 ? "result" : "results"}
|
||||
shown</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -463,7 +525,7 @@ class URLResources extends LitElement {
|
||||
</div>
|
||||
|
||||
<table class="all-results" aria-labelledby="results-heading num-results">
|
||||
<thead>
|
||||
<thead class="all-results-header">
|
||||
<tr class="columns results-head has-text-weight-bold">
|
||||
<th scope="col" class="column col-url is-6 is-hidden-mobile">
|
||||
<a
|
||||
@@ -472,7 +534,7 @@ class URLResources extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="url"
|
||||
class="${this.sortKey === "url"
|
||||
class="sort-control-label ${this.sortKey === "url"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -487,7 +549,7 @@ class URLResources extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="ts"
|
||||
class="${this.sortKey === "ts"
|
||||
class="sort-control-label ${this.sortKey === "ts"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -495,14 +557,14 @@ class URLResources extends LitElement {
|
||||
>Date</a
|
||||
>
|
||||
</th>
|
||||
<th scope="col" class="column col-mime is-3 is-hidden-mobile">
|
||||
<th scope="col" class="column col-mime is-2 is-hidden-mobile">
|
||||
<a
|
||||
role="button"
|
||||
href="#"
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="mime"
|
||||
class="${this.sortKey === "mime"
|
||||
class="sort-control-label ${this.sortKey === "mime"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -517,7 +579,7 @@ class URLResources extends LitElement {
|
||||
@click="${this.onSort}"
|
||||
@keyup="${clickOnSpacebarPress}"
|
||||
data-key="status"
|
||||
class="${this.sortKey === "status"
|
||||
class="sort-control-label ${this.sortKey === "status"
|
||||
? this.sortDesc
|
||||
? "desc"
|
||||
: "asc"
|
||||
@@ -525,6 +587,9 @@ class URLResources extends LitElement {
|
||||
>Status</a
|
||||
>
|
||||
</th>
|
||||
<th scope="col" class="column col-actions is-1 is-hidden-mobile">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -535,21 +600,6 @@ class URLResources extends LitElement {
|
||||
<tr class="columns result">
|
||||
<td class="column col-url is-6">
|
||||
<p class="minihead is-hidden-tablet">URL</p>
|
||||
<a
|
||||
class="dl-button"
|
||||
href="${getDownloadLink(
|
||||
this.collInfo!.replayPrefix,
|
||||
result.url,
|
||||
result.ts,
|
||||
)}"
|
||||
><fa-icon
|
||||
size="1.0em"
|
||||
class="has-text-black"
|
||||
aria-hidden="true"
|
||||
title="Download Resource"
|
||||
.svg="${fasDownload}"
|
||||
></fa-icon>
|
||||
</a>
|
||||
<a
|
||||
@click="${this.onReplay}"
|
||||
data-url="${result.url}"
|
||||
@@ -569,7 +619,7 @@ class URLResources extends LitElement {
|
||||
<p class="minihead is-hidden-tablet">Date</p>
|
||||
${dateTimeFormatter.format(new Date(result.date))}
|
||||
</td>
|
||||
<td class="column col-mime is-3">
|
||||
<td class="column col-mime is-2">
|
||||
<p class="minihead is-hidden-tablet">Media Type</p>
|
||||
${result.mime}
|
||||
</td>
|
||||
@@ -577,6 +627,21 @@ class URLResources extends LitElement {
|
||||
<p class="minihead is-hidden-tablet">Status</p>
|
||||
${result.status}
|
||||
</td>
|
||||
<td class="column col-actions is-1">
|
||||
<a
|
||||
class="dl-button"
|
||||
href="${getDownloadLink(
|
||||
this.collInfo!.replayPrefix,
|
||||
result.url,
|
||||
result.ts,
|
||||
)}"
|
||||
download
|
||||
><wr-icon
|
||||
title="Download Resource"
|
||||
.svg="${iconDownload}"
|
||||
></wr-icon>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user