diff --git a/.gitignore b/.gitignore index 4fc3fa5..a429f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ **/node_modules .DS_Store dist +.export-include \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a0d7b8f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "useTabs": false, + "tabWidth": 2, + "semi": true, + "singleQuote": false, + "printWidth": 120 +} diff --git a/src/argo-archive-list.ts b/src/argo-archive-list.ts new file mode 100644 index 0000000..c36101e --- /dev/null +++ b/src/argo-archive-list.ts @@ -0,0 +1,223 @@ +import { LitElement, html, css, CSSResultGroup } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { styles as typescaleStyles } from "@material/web/typography/md-typescale-styles.js"; + +import "@material/web/list/list.js"; +import "@material/web/list/list-item.js"; +import "@material/web/checkbox/checkbox.js"; +import "@material/web/icon/icon.js"; +import "@material/web/labs/card/elevated-card.js"; + +import { getLocalOption } from "./localstorage"; + +@customElement("argo-archive-list") +export class ArgoArchiveList extends LitElement { + static styles: CSSResultGroup = [ + typescaleStyles as unknown as CSSResultGroup, + css` + md-elevated-card { + display: block; + margin: 1rem 0; + padding: 0; + overflow: visible; + } + + md-elevated-card > details { + border-radius: inherit; + overflow: hidden; + margin: 0; + background: transparent; + } + + md-elevated-card > details summary { + background: transparent !important; + padding: 0.75rem 1rem; + } + + md-elevated-card > details md-list { + background: transparent; + padding: 0 0rem 0rem; + } + + md-list-item { + --md-list-item-top-space: 0px; + --md-list-item-bottom-space: 0px; + + --md-list-item-leading-space: 0px; + --md-list-item-trailing-space: 12px; + + --md-list-item-one-line-container-height: 0px; + } + + .leading-group { + display: flex; + gap: 0px; + align-items: center; + height: 100%; + } + + .card-container { + padding: 0 1rem; + } + + img.favicon { + width: 20px !important; + height: 20px !important; + flex: 0 0 auto; + object-fit: cover; + border-radius: 4px; + filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.6)); + } + + summary { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + cursor: pointer; + user-select: none; + } + summary::-webkit-details-marker { + display: none; + } + + summary md-icon.arrow-right, + summary md-icon.arrow-down { + display: none; + } + details:not([open]) summary md-icon.arrow-right { + display: block; + } + details[open] summary md-icon.arrow-down { + display: block; + } + + .title-url { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + overflow: hidden; + white-space: nowrap; + } + .title-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .base-url { + flex-shrink: 0; + text-decoration: none; + } + `, + ]; + + @state() private pages: Array<{ ts: string; url: string; title?: string; favIconUrl?: string }> = []; + @state() private collId = ""; + + async connectedCallback() { + super.connectedCallback(); + this.collId = (await getLocalOption("defaultCollId")) || ""; + const port = chrome.runtime.connect({ name: "sidepanel-port" }); + // @ts-expect-error - TS7006 - Parameter 'msg' implicitly has an 'any' type. + port.onMessage.addListener((msg) => { + if (msg.type === "pages") this.pages = msg.pages || []; + }); + port.postMessage({ type: "getPages" }); + } + + render() { + if (!this.pages.length) { + return html`

No archives yet.

`; + } + + const groups = this.pages.reduce( + (acc, page) => { + const key = this._formatDate(new Date(Number(page.ts))); + (acc[key] ||= []).push(page); + return acc; + }, + {} as Record, + ); + + return html` +
+ ${Object.entries(groups) + .sort(([a], [b]) => new Date(b).getTime() - new Date(a).getTime()) + .map( + ([dateLabel, pages]) => html` + +
+ + chevron_right + expand_more + ${dateLabel} + + + ${pages.map((page) => { + const u = new URL(page.url); + return html` + this._openPage(page)}> +
+ e.stopPropagation()} + > + + ${page.favIconUrl + ? html` + favicon of ${u.hostname} + ` + : html`article`} +
+
+ ${page.title || page.url} + ${u.hostname} +
+
+ `; + })} +
+
+
+ `, + )} +
+ `; + } + + private _formatDate(date: Date): string { + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(today.getDate() - 1); + const opts: Intl.DateTimeFormatOptions = { weekday: "long", month: "long", day: "numeric", year: "numeric" }; + const label = date.toLocaleDateString("en-US", opts); + if (date.toDateString() === today.toDateString()) return `Today — ${label}`; + if (date.toDateString() === yesterday.toDateString()) return `Yesterday — ${label}`; + return label; + } + + private _openPage(page: { ts: string; url: string }) { + const tsParam = new Date(Number(page.ts)).toISOString().replace(/[-:TZ.]/g, ""); + const urlEnc = encodeURIComponent(page.url); + const fullUrl = + `${chrome.runtime.getURL("index.html")}?source=local://${this.collId}&url=${urlEnc}` + + `#view=pages&url=${urlEnc}&ts=${tsParam}`; + chrome.tabs.create({ url: fullUrl }); + } +} diff --git a/src/ext/bg.ts b/src/ext/bg.ts index 012e6d7..ab258c5 100644 --- a/src/ext/bg.ts +++ b/src/ext/bg.ts @@ -4,11 +4,7 @@ import { CollectionLoader } from "@webrecorder/wabac/swlib"; import { listAllMsg } from "../utils"; -import { - getLocalOption, - removeLocalOption, - setLocalOption, -} from "../localstorage"; +import { getLocalOption, removeLocalOption, setLocalOption } from "../localstorage"; // =========================================================================== self.recorders = {}; @@ -46,11 +42,13 @@ function main() { }); } // Side panel -chrome.sidePanel.setPanelBehavior({ - openPanelOnActionClick: true -}).catch((err: Error) => { - console.error(err); -}); +chrome.sidePanel + .setPanelBehavior({ + openPanelOnActionClick: true, + }) + .catch((err: Error) => { + console.error(err); + }); // @ts-expect-error - TS7006 - Parameter 'port' implicitly has an 'any' type. chrome.runtime.onConnect.addListener((port) => { @@ -143,6 +141,23 @@ function sidepanelHandler(port) { port.postMessage(await listAllMsg(collLoader)); break; + case "getPages": { + const defaultCollId = await getLocalOption("defaultCollId"); + if (!defaultCollId) { + port.postMessage({ type: "pages", pages: [] }); + return; + } + + const coll = await collLoader.loadColl(defaultCollId); + if (coll?.store?.getAllPages) { + const pages = await coll.store.getAllPages(); + port.postMessage({ type: "pages", pages }); + } else { + port.postMessage({ type: "pages", pages: [] }); + } + break; + } + case "startRecording": { const { collId, autorun } = message; // @ts-expect-error - TS2554 - Expected 2 arguments, but got 3. @@ -362,12 +377,7 @@ function isRecording(tabId) { // =========================================================================== // @ts-expect-error - TS7006 - Parameter 'url' implicitly has an 'any' type. function isValidUrl(url) { - return ( - url && - (url === "about:blank" || - url.startsWith("https:") || - url.startsWith("http:")) - ); + return url && (url === "about:blank" || url.startsWith("https:") || url.startsWith("http:")); } // =========================================================================== diff --git a/src/sidepanel.ts b/src/sidepanel.ts index 0e3421b..f2e3ca3 100644 --- a/src/sidepanel.ts +++ b/src/sidepanel.ts @@ -1,12 +1,10 @@ - -import '@material/web/all.js'; -import { styles as typescaleStyles } from '@material/web/typography/md-typescale-styles.js'; +import "@material/web/all.js"; +import { styles as typescaleStyles } from "@material/web/typography/md-typescale-styles.js"; import { LitElement, html } from "lit"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; - -import fasHome from "@fortawesome/fontawesome-free/svgs/solid/home.svg"; -import fasBox from "@fortawesome/fontawesome-free/svgs/solid/square.svg"; -import wrRec from "./assets/icons/recLogo.svg"; +import "./argo-archive-list"; +import "@material/web/textfield/outlined-text-field.js"; +import "@material/web/icon/icon.js"; import { getLocalOption, @@ -21,8 +19,11 @@ import { // BEHAVIOR_DONE, } from "./consts"; -document.adoptedStyleSheets.push(typescaleStyles.styleSheet!); +import "@material/web/button/filled-button.js"; +import "@material/web/button/outlined-button.js"; +import "@material/web/divider/divider.js"; +document.adoptedStyleSheets.push(typescaleStyles.styleSheet!); class ArgoViewer extends LitElement { constructor() { @@ -101,7 +102,6 @@ class ArgoViewer extends LitElement { } firstUpdated() { - this.registerMessages(); } @@ -126,11 +126,12 @@ class ArgoViewer extends LitElement { } }); + // this.sendMessage({ type: "getPages" }); + // @ts-expect-error - TS2339 - Property 'port' does not exist on type 'RecPopup'. this.port.onMessage.addListener((message) => { this.onMessage(message); }); - } // @ts-expect-error - TS7006 - Parameter 'message' implicitly has an 'any' type. @@ -212,8 +213,8 @@ class ArgoViewer extends LitElement { break; } } - get actionButtonDisabled() { + get actionButtonDisabled() { // @ts-expect-error - TS2339 - Property 'recording' does not exist on type 'RecPopup'. | TS2339 - Property 'waitingForStart' does not exist on type 'RecPopup'. | TS2339 - Property 'waitingForStop' does not exist on type 'RecPopup'. return !this.recording ? this.waitingForStart : this.waitingForStop; } @@ -244,10 +245,7 @@ class ArgoViewer extends LitElement { this.replayUrl = this.getCollPage() + "#" + params.toString(); } - if ( - changedProperties.has("pageUrl") || - changedProperties.has("failureMsg") - ) { + if (changedProperties.has("pageUrl") || changedProperties.has("failureMsg")) { // @ts-expect-error - TS2339 - Property 'canRecord' does not exist on type 'RecPopup'. this.canRecord = // @ts-expect-error - TS2339 - Property 'pageUrl' does not exist on type 'RecPopup'. @@ -299,54 +297,50 @@ class ArgoViewer extends LitElement { this.waitingForStop = true; } render() { - return html`
- - - - Home - All Archives - - - ${ - // @ts-expect-error - TS2339 - Property 'canRecord' does not exist on type 'RecPopup'. - this.canRecord - ? html` - - ` - : "" - } -
`; + !this.recording + ? html` + + public + Resume Archiving + + ` + : html` + + pause + Pause Archiving + + ` + } + ` + : html`` + } + + + settings + + + `; } } @@ -368,18 +362,18 @@ class WrIcon extends LitElement { return html` ${ - // @ts-expect-error - TS2339 - Property 'src' does not exist on type 'WrIcon'. - unsafeSVG(this.src) - } + // @ts-expect-error - TS2339 - Property 'src' does not exist on type 'WrIcon'. + unsafeSVG(this.src) + } `; diff --git a/static/sidepanel.html b/static/sidepanel.html index f4c2625..ed57b29 100644 --- a/static/sidepanel.html +++ b/static/sidepanel.html @@ -1,25 +1,99 @@ + My Sidepanel + + - -

Searchbar here

- - - My Archives - My Shared Archives + +
+ + search + +
+ + + + + My Archives + My Shared Archives + +
+
+ +
+
+ +
+
+ + \ No newline at end of file