diff --git a/.gitignore b/.gitignore index 98d0ba7..4f288db 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ stats-*.json .wxt .wxt-runner web-ext.config.ts +web-ext-artifacts # Editor directories and files .vscode/* diff --git a/package.json b/package.json index ed6492b..b623bc2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "at-fronter", "description": "view who was fronting when a record was made", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "wxt", diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index 0e85c25..2c3c02a 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -1,6 +1,7 @@ import { expect } from "@/lib/result"; import { type Fronter, + fronterGetSocialAppHrefs, fronterGetSocialAppHref, getFronter, getSpFronters, @@ -80,10 +81,12 @@ export default defineBackground({ browser.tabs.sendMessage(sender.tab?.id!, { type: "TIMELINE_FRONTER", results: new Map( - results.map((fronter) => [ - fronterGetSocialAppHref(fronter, fronter.rkey), - fronter, - ]), + results.flatMap((fronter) => + fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [ + href, + fronter, + ]), + ), ), }); }; @@ -126,10 +129,12 @@ export default defineBackground({ (await Promise.allSettled(allPromises)) .filter((result) => result.status === "fulfilled") .flatMap((result) => result.value ?? []) - .map((fronter) => [ - fronterGetSocialAppHref(fronter, fronter.rkey), - fronter, - ]), + .flatMap((fronter) => + fronterGetSocialAppHrefs(fronter, fronter.rkey).map((href) => [ + href, + fronter, + ]), + ), ); browser.tabs.sendMessage(sender.tab?.id!, { type: "TIMELINE_FRONTER", @@ -170,10 +175,11 @@ export default defineBackground({ (await Promise.allSettled(promises)) .filter((result) => result.status === "fulfilled") .flatMap((result) => result.value ?? []) - .map((fronter) => [ - fronterGetSocialAppHref(fronter, fronter.rkey, fronter.depth), - fronter, - ]), + .flatMap((fronter) => + fronterGetSocialAppHrefs(fronter, fronter.rkey, fronter.depth).map( + (href) => [href, fronter], + ), + ), ); browser.tabs.sendMessage(sender.tab?.id!, { type: "THREAD_FRONTER", diff --git a/src/entrypoints/content.ts b/src/entrypoints/content.ts index 29f1a26..1be76bd 100644 --- a/src/entrypoints/content.ts +++ b/src/entrypoints/content.ts @@ -2,6 +2,7 @@ import { expect } from "@/lib/result"; import { Fronter, fronterGetSocialAppHref, + fronterGetSocialAppHrefs, parseSocialAppPostUrl, } from "@/lib/utils"; import { parseResourceUri, ResourceUri } from "@atcute/lexicons"; @@ -145,7 +146,10 @@ export default defineContentScript({ fronters.entries().flatMap(([uri, fronter]) => { if (!fronter) return []; const rkey = expect(parseResourceUri(uri)).rkey!; - return [[fronterGetSocialAppHref(fronter, rkey), fronter]]; + return fronterGetSocialAppHrefs(fronter, rkey).map((href) => [ + href, + fronter, + ]); }), ); applyFrontersToPage(updated); diff --git a/src/entrypoints/popup/App.svelte b/src/entrypoints/popup/App.svelte index efed9fc..5386825 100644 --- a/src/entrypoints/popup/App.svelte +++ b/src/entrypoints/popup/App.svelte @@ -9,9 +9,10 @@ let isQuerying = $state(false); let fronterName = $state(""); let spToken = $state(""); + let isFromCurrentTab = $state(false); const makeOutput = (fronter: any) => { - return `HANDLE: ${fronter.handle ?? "handle.invalid"}
FRONTER: ${fronter.fronterName}`; + return `HANDLE: ${fronter.handle ?? "handle.invalid"}
FRONTER(S): ${fronter.names.join(", ")}`; }; const queryRecord = async (recordUri: ResourceUri) => { @@ -50,6 +51,7 @@ const clearResult = () => { queryResult = ""; recordAtUri = ""; + isFromCurrentTab = false; }; onMount(async () => { @@ -73,16 +75,13 @@ if (tabFronter) { queryResult = makeOutput(tabFronter); recordAtUri = tabFronter.recordUri; + isFromCurrentTab = true; } });
-
-
AT_FRONTER
-
-
@@ -116,7 +115,17 @@
- OUTPUT +
+ OUTPUT + {#if isFromCurrentTab} +
+ FROM_CURRENT_TAB +
+
+ {/if} +
{#if queryResult && !isQuerying}
- -
- FRONTER_NAME -
+
+
+ SP_TOKEN + +
+
+ + token requires only read permissions + +
+
+
+
+ FRONTER_NAME
-
- -
- SP_TOKEN -
- +
+ + overrides Simply Plural fronters when set +
@@ -225,39 +243,21 @@ background: linear-gradient(180deg, #000000 0%, #0a0a0a 100%); } - .header { - display: flex; - align-items: center; - justify-content: center; - padding: 20px 20px; - background: #000000; - border-bottom: 1px solid #333333; - position: relative; - } - - .header::after { - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 1px; - background: linear-gradient(90deg, transparent, #555555, transparent); - } - .title { - font-size: 18px; - font-weight: 800; - letter-spacing: 3px; - color: #ffffff; + font-size: 10px; + font-weight: 700; + letter-spacing: 2px; + color: #999999; + line-height: 1; + vertical-align: baseline; } .content { flex: 1; display: flex; flex-direction: column; - gap: 24px; - padding: 24px 20px; + gap: 20px; + padding: 18px 16px; overflow-y: auto; } @@ -270,7 +270,37 @@ .config-panel { display: flex; flex-direction: column; - gap: 16px; + gap: 12px; + } + + .config-card { + background: #0d0d0d; + border: 1px solid #2a2a2a; + border-left: 3px solid #444444; + padding: 10px; + display: flex; + flex-direction: column; + gap: 6px; + transition: border-left-color 0.2s ease; + } + + .config-card:hover { + border-left-color: #555555; + } + + .config-note { + padding: 0; + background: transparent; + border: none; + margin: 0; + } + + .note-text { + font-size: 11px; + color: #bbbbbb; + line-height: 1.3; + font-weight: 500; + letter-spacing: 0.5px; } .panel-header { @@ -310,7 +340,7 @@ .record-input { flex: 1; - padding: 16px 18px; + padding: 12px 14px; background: transparent; border: none; outline: none; @@ -331,7 +361,7 @@ .exec-button { position: relative; - padding: 16px 28px; + padding: 8px 10px; background: #2a2a2a; border: none; border-left: 1px solid #444444; @@ -396,6 +426,42 @@ min-height: 32px; } + .output-header-left { + display: flex; + align-items: center; + gap: 12px; + } + + .tab-indicator { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + background: #1a1a1a; + border: 1px solid #333333; + position: relative; + overflow: hidden; + } + + .tab-indicator-text { + font-size: 9px; + color: #00ff41; + font-weight: 700; + letter-spacing: 1px; + position: relative; + z-index: 1; + } + + .tab-indicator-accent { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 1px; + background: #00ff41; + animation: pulse 2s ease-in-out infinite; + } + .clear-button-container { width: 60px; display: flex; @@ -435,7 +501,7 @@ } .output-content { - padding: 18px; + padding: 14px; height: 100%; display: flex; align-items: center; @@ -495,30 +561,28 @@ .config-row { display: flex; - flex-direction: column; - gap: 8px; + align-items: center; + gap: 12px; + margin-bottom: 0; } .config-label { - font-size: 11px; - color: #aaaaaa; - letter-spacing: 1.5px; + font-size: 12px; + color: #cccccc; + letter-spacing: 1px; font-weight: 700; - } - - .config-input-wrapper { - display: flex; - align-items: center; + white-space: nowrap; + min-width: 90px; } .config-input { flex: 1; - padding: 14px 18px; + padding: 10px 12px; background: #181818; border: 1px solid #333333; color: #ffffff; font-family: inherit; - font-size: 13px; + font-size: 12px; font-weight: 500; transition: all 0.2s ease; position: relative; @@ -540,15 +604,17 @@ .footer { display: flex; - align-items: center; + align-items: baseline; justify-content: center; - padding: 16px 20px; + gap: 8px; + padding: 12px 16px; background: #000000; - border-top: 1px solid #333333; - font-size: 10px; - color: #888888; - font-weight: 600; - letter-spacing: 1px; + border-top: 1px solid #222222; + font-size: 9px; + color: #666666; + font-weight: 500; + letter-spacing: 0.5px; + line-height: 1; position: relative; } @@ -559,18 +625,33 @@ left: 0; width: 100%; height: 1px; - background: linear-gradient(90deg, transparent, #555555, transparent); + background: linear-gradient(90deg, transparent, #333333, transparent); } - .footer a { - color: #aaaaaa; + .footer-separator { + color: #444444; + font-weight: 400; + line-height: 1; + vertical-align: baseline; + } + + .footer-source { + color: #777777; + line-height: 1; + vertical-align: baseline; + } + + .footer-link { + color: #999999; text-decoration: none; font-weight: 700; transition: color 0.2s ease; + line-height: 1; + vertical-align: baseline; } - .footer a:hover { - color: #ffffff; + .footer-link:hover { + color: #cccccc; } /* Animations */ @@ -592,21 +673,4 @@ left: 100%; } } - - /* Scrollbar */ - .content::-webkit-scrollbar { - width: 2px; - } - - .content::-webkit-scrollbar-track { - background: #000000; - } - - .content::-webkit-scrollbar-thumb { - background: #333333; - } - - .content::-webkit-scrollbar-thumb:hover { - background: #555555; - } diff --git a/src/entrypoints/popup/app.css b/src/entrypoints/popup/app.css index 49f434a..2b43b68 100644 --- a/src/entrypoints/popup/app.css +++ b/src/entrypoints/popup/app.css @@ -87,29 +87,94 @@ input::placeholder { color: #ffffff; } +/* Cross-browser scrollbar styling */ + +/* Standard scrollbar properties (Firefox, Chrome 121+, Edge 121+) */ +* { + scrollbar-width: thin; + scrollbar-color: #333333 #0a0a0a; +} + +/* Content areas get even thinner scrollbars */ +.content, +.output-content, +textarea, +input { + scrollbar-width: thin; + scrollbar-color: #2a2a2a #000000; +} + +/* Webkit scrollbar styling for older browsers and better customization */ /* Global scrollbar styling */ ::-webkit-scrollbar { - width: 2px; - height: 2px; + width: 8px; + height: 8px; } ::-webkit-scrollbar-track { - background: #000000; + background: #0a0a0a; + border-radius: 0; } ::-webkit-scrollbar-thumb { background: #333333; + border-radius: 0; border: none; + transition: background 0.2s ease; } ::-webkit-scrollbar-thumb:hover { background: #555555; } +::-webkit-scrollbar-thumb:active { + background: #666666; +} + ::-webkit-scrollbar-corner { + background: #0a0a0a; +} + +/* Scrollbar for specific containers */ +.content::-webkit-scrollbar, +.output-content::-webkit-scrollbar, +textarea::-webkit-scrollbar, +input::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.content::-webkit-scrollbar-track, +.output-content::-webkit-scrollbar-track, +textarea::-webkit-scrollbar-track, +input::-webkit-scrollbar-track { background: #000000; } +.content::-webkit-scrollbar-thumb, +.output-content::-webkit-scrollbar-thumb, +textarea::-webkit-scrollbar-thumb, +input::-webkit-scrollbar-thumb { + background: #2a2a2a; + border-radius: 0; + border: none; + transition: background 0.15s ease; +} + +.content::-webkit-scrollbar-thumb:hover, +.output-content::-webkit-scrollbar-thumb:hover, +textarea::-webkit-scrollbar-thumb:hover, +input::-webkit-scrollbar-thumb:hover { + background: #444444; +} + +.content::-webkit-scrollbar-thumb:active, +.output-content::-webkit-scrollbar-thumb:active, +textarea::-webkit-scrollbar-thumb:active, +input::-webkit-scrollbar-thumb:active { + background: #555555; +} + /* Animations */ @keyframes scanline { 0% { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 0880e96..ead35b9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -265,15 +265,25 @@ export const getSpFronters = async (): Promise => { })); }; -export const fronterGetSocialAppHref = ( +export const fronterGetSocialAppHrefs = ( fronter: Fronter, rkey: RecordKey, depth?: number, ) => { - // console.log("fronterGetSocialAppHref", fronter, rkey, depth); - return depth === 0 - ? `/profile/${fronter.handle ?? fronter.did}` - : `/profile/${fronter.handle ?? fronter.did}/post/${rkey}`; + return [ + fronter.handle + ? [fronterGetSocialAppHref(fronter.handle, rkey, depth)] + : [], + fronterGetSocialAppHref(fronter.did, rkey, depth), + ].flat(); +}; + +export const fronterGetSocialAppHref = ( + repo: string, + rkey: RecordKey, + depth?: number, +) => { + return depth === 0 ? `/profile/${repo}` : `/profile/${repo}/post/${rkey}`; }; export const parseSocialAppPostUrl = (url: string) => {