From 8cd1b0d96654c969f0d5745b5d3d5d034569ac83 Mon Sep 17 00:00:00 2001 From: nikitalokhmachev-ai Date: Mon, 5 May 2025 17:41:02 -0400 Subject: [PATCH 1/3] feat: implement basic archive links --- .gitignore | 1 + .prettierrc | 7 ++++ src/argo-archive-list.ts | 79 ++++++++++++++++++++++++++++++++++++++++ src/ext/bg.ts | 42 +++++++++++++-------- src/sidepanel.ts | 73 +++++++++++++++++++++++++++++-------- static/sidepanel.html | 8 ++++ 6 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 .prettierrc create mode 100644 src/argo-archive-list.ts 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..18d8cdf --- /dev/null +++ b/src/argo-archive-list.ts @@ -0,0 +1,79 @@ +import { LitElement, html, css } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { getLocalOption } from "./localstorage"; + +@customElement("argo-archive-list") +export class ArgoArchiveList extends LitElement { + @state() + private pages: any[] = []; + + @property({ type: String }) + collId = ""; + + static styles = css` + ul { + list-style: none; + padding-left: 0; + } + li { + cursor: pointer; + margin: 0.3em 0; + color: #0066cc; + text-decoration: underline; + } + `; + + async connectedCallback() { + super.connectedCallback(); + + this.collId = (await getLocalOption("defaultCollId")) || ""; + + const port = chrome.runtime.connect({ name: "sidepanel-port" }); + + // @ts-expect-error - TS7006 - Parameter 'message' implicitly has an 'any' type. + port.onMessage.addListener((message) => { + if (message.type === "pages") { + this.pages = message.pages || []; + } + }); + + port.postMessage({ type: "getPages" }); + } + + render() { + if (!this.pages.length) { + return html`

No archives found yet.

`; + } + + return html` + + `; + } +} 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..8bb072f 100644 --- a/src/sidepanel.ts +++ b/src/sidepanel.ts @@ -1,12 +1,12 @@ - -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 { getLocalOption, @@ -23,7 +23,6 @@ import { document.adoptedStyleSheets.push(typescaleStyles.styleSheet!); - class ArgoViewer extends LitElement { constructor() { super(); @@ -101,10 +100,46 @@ class ArgoViewer extends LitElement { } firstUpdated() { - this.registerMessages(); } + // // @ts-expect-error - TS7006 - Parameter 'pages' implicitly has an 'any' type. + // renderArchivedPages(pages) { + // const listEl = (this.renderRoot as ShadowRoot).getElementById("archived-list"); + // if (!listEl) return; + + // listEl.innerHTML = ""; + // // @ts-expect-error - a and b are any + // pages.sort((a, b) => Number(b.ts || 0) - Number(a.ts || 0)); // newest first + + // for (const page of pages) { + // const li = document.createElement("li"); + // li.textContent = page.title || page.url; + // li.style.cursor = "pointer"; + // li.onclick = () => { + // const tsString = page.ts ? new Date(Number(page.ts)).toISOString().replace(/[-:TZ.]/g, "") : ""; + + // const query = new URLSearchParams({ + // // @ts-expect-error - TS2339 - Property 'collId' does not exist on type 'RecPopup'. + // source: `local://${this.collId}`, + // url: page.url, + // }).toString(); + + // const hash = new URLSearchParams({ + // view: "pages", + // url: page.url, + // ts: tsString, + // }).toString(); + + // const fullUrl = `${chrome.runtime.getURL("index.html")}?${query}#${hash}`; + + // chrome.tabs.create({ url: fullUrl }); + // }; + // listEl.appendChild(li); + // } + // } + // + registerMessages() { // @ts-expect-error - TS2339 - Property 'port' does not exist on type 'RecPopup'. this.port = chrome.runtime.connect({ name: "sidepanel-port" }); @@ -126,11 +161,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. @@ -210,10 +246,13 @@ class ArgoViewer extends LitElement { this.collTitle = "[No Title]"; } break; + // case "pages": + // this.renderArchivedPages(message.pages); + // 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; } @@ -346,6 +385,8 @@ class ArgoViewer extends LitElement { ` : "" } +

Archived Pages

+ `; } } @@ -368,18 +409,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..e760c30 100644 --- a/static/sidepanel.html +++ b/static/sidepanel.html @@ -21,5 +21,13 @@

Searchbar here

My Archives My Shared Archives +
+
+ +
+
+

Shared content goes here

+
+
\ No newline at end of file From 25e6479e1227aa9803391dc5731da38f2086e0e8 Mon Sep 17 00:00:00 2001 From: nikitalokhmachev-ai Date: Tue, 6 May 2025 18:32:52 -0400 Subject: [PATCH 2/3] feat: argo sidepanel styling --- src/argo-archive-list.ts | 256 ++++++++++++++++++++++++++++++--------- src/sidepanel.ts | 147 ++++++++-------------- static/sidepanel.html | 94 +++++++++++--- 3 files changed, 330 insertions(+), 167 deletions(-) diff --git a/src/argo-archive-list.ts b/src/argo-archive-list.ts index 18d8cdf..c36101e 100644 --- a/src/argo-archive-list.ts +++ b/src/argo-archive-list.ts @@ -1,79 +1,223 @@ -import { LitElement, html, css } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +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 { - @state() - private pages: any[] = []; + static styles: CSSResultGroup = [ + typescaleStyles as unknown as CSSResultGroup, + css` + md-elevated-card { + display: block; + margin: 1rem 0; + padding: 0; + overflow: visible; + } - @property({ type: String }) - collId = ""; + md-elevated-card > details { + border-radius: inherit; + overflow: hidden; + margin: 0; + background: transparent; + } - static styles = css` - ul { - list-style: none; - padding-left: 0; - } - li { - cursor: pointer; - margin: 0.3em 0; - color: #0066cc; - text-decoration: underline; - } - `; + md-elevated-card > details summary { + background: transparent !important; + padding: 0.75rem 1rem; + } - async connectedCallback() { - super.connectedCallback(); + md-elevated-card > details md-list { + background: transparent; + padding: 0 0rem 0rem; + } - this.collId = (await getLocalOption("defaultCollId")) || ""; + md-list-item { + --md-list-item-top-space: 0px; + --md-list-item-bottom-space: 0px; - const port = chrome.runtime.connect({ name: "sidepanel-port" }); + --md-list-item-leading-space: 0px; + --md-list-item-trailing-space: 12px; - // @ts-expect-error - TS7006 - Parameter 'message' implicitly has an 'any' type. - port.onMessage.addListener((message) => { - if (message.type === "pages") { - this.pages = message.pages || []; + --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 found yet.

`; + 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` -
    - ${this.pages.map((page) => { - const tsString = page.ts - ? new Date(Number(page.ts)).toISOString().replace(/[-:TZ.]/g, "") - : ""; - - const query = new URLSearchParams({ - source: `local://${page.coll || ""}`, - url: page.url, - }).toString(); - - const hash = new URLSearchParams({ - view: "pages", - url: page.url, - ts: tsString, - }).toString(); - - const fullUrl = `${chrome.runtime.getURL( - "index.html", - )}?source=local://${this.collId}&url=${encodeURIComponent( - page.url, - )}#view=pages&url=${encodeURIComponent(page.url)}&ts=${tsString}`; - - return html`
  • chrome.tabs.create({ url: fullUrl })}> - ${page.title || page.url} -
  • `; - })} -
+
+ ${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/sidepanel.ts b/src/sidepanel.ts index 8bb072f..f2e3ca3 100644 --- a/src/sidepanel.ts +++ b/src/sidepanel.ts @@ -2,11 +2,9 @@ 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,6 +19,10 @@ import { // BEHAVIOR_DONE, } from "./consts"; +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 { @@ -103,43 +105,6 @@ class ArgoViewer extends LitElement { this.registerMessages(); } - // // @ts-expect-error - TS7006 - Parameter 'pages' implicitly has an 'any' type. - // renderArchivedPages(pages) { - // const listEl = (this.renderRoot as ShadowRoot).getElementById("archived-list"); - // if (!listEl) return; - - // listEl.innerHTML = ""; - // // @ts-expect-error - a and b are any - // pages.sort((a, b) => Number(b.ts || 0) - Number(a.ts || 0)); // newest first - - // for (const page of pages) { - // const li = document.createElement("li"); - // li.textContent = page.title || page.url; - // li.style.cursor = "pointer"; - // li.onclick = () => { - // const tsString = page.ts ? new Date(Number(page.ts)).toISOString().replace(/[-:TZ.]/g, "") : ""; - - // const query = new URLSearchParams({ - // // @ts-expect-error - TS2339 - Property 'collId' does not exist on type 'RecPopup'. - // source: `local://${this.collId}`, - // url: page.url, - // }).toString(); - - // const hash = new URLSearchParams({ - // view: "pages", - // url: page.url, - // ts: tsString, - // }).toString(); - - // const fullUrl = `${chrome.runtime.getURL("index.html")}?${query}#${hash}`; - - // chrome.tabs.create({ url: fullUrl }); - // }; - // listEl.appendChild(li); - // } - // } - // - registerMessages() { // @ts-expect-error - TS2339 - Property 'port' does not exist on type 'RecPopup'. this.port = chrome.runtime.connect({ name: "sidepanel-port" }); @@ -246,9 +211,6 @@ class ArgoViewer extends LitElement { this.collTitle = "[No Title]"; } break; - // case "pages": - // this.renderArchivedPages(message.pages); - // break; } } @@ -283,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'. @@ -338,56 +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` - - ` - : "" - } -

Archived Pages

-
    -
    `; + !this.recording + ? html` + + public + Resume Archiving + + ` + : html` + + pause + Pause Archiving + + ` + } + ` + : html`` + } + + + settings + + + `; } } diff --git a/static/sidepanel.html b/static/sidepanel.html index e760c30..f27a8e4 100644 --- a/static/sidepanel.html +++ b/static/sidepanel.html @@ -1,33 +1,99 @@ + My Sidepanel + + - -

    Searchbar here

    - - - My Archives - My Shared Archives + +
    + + search + +
    + + + + + My Archives + My Shared Archives -
    + +
    -

    Shared content goes here

    +
    + + \ No newline at end of file From 0070d494175004353840374dbac9bd100d36d6e4 Mon Sep 17 00:00:00 2001 From: nikitalokhmachev-ai Date: Wed, 7 May 2025 11:12:53 -0400 Subject: [PATCH 3/3] fix: move md-tabs --- static/sidepanel.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/sidepanel.html b/static/sidepanel.html index f27a8e4..ed57b29 100644 --- a/static/sidepanel.html +++ b/static/sidepanel.html @@ -19,10 +19,10 @@ --md-sys-color-background: white; --md-sys-color-surface-container: white; --md-elevated-card-container-color: white; + } - md-tabs { + md-tabs { background-color: white; - } } .search-container {