Skip to content

Commit 36d69a1

Browse files
authored
Merge pull request #3 from tshmieldev/tshmieldev/commentsandmobilestuff
Filter comments toggle, X on phones, and m.youtube.com
2 parents 09de756 + 0cf865d commit 36d69a1

19 files changed

Lines changed: 401 additions & 64 deletions

‎CONTRIBUTING.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,13 @@ scripts/ Per-target extension build, manifest derivation, release, icons
157157
importing a site entry alone installs no listeners or observers. The background
158158
worker and popup remain separate extension entry points.
159159

160-
**YouTube is rules only.** The adapter reads public settings, sets
160+
**YouTube is rules only, on both of its sites.** `www.youtube.com` and
161+
`m.youtube.com` run the same adapter: it sets attributes and the stylesheet
162+
decides what they mean, so the mobile site cost selectors rather than code.
163+
`ytd-` elements are the desktop site, `ytm-` the mobile one, and the newer
164+
`*-view-model` elements and `yt*ViewModel` classes are shared; a selector that
165+
matches nothing on the site in front of it costs nothing, so the pairs sit
166+
together per feature. The adapter reads public settings, sets
161167
`data-aitf-yt-shorts` and `data-aitf-yt-comments` on `<html>`, plus
162168
`data-aitf-yt-thumbs="blurred"` or `"hidden"`, and follows storage changes.
163169
Hidden thumbnails give up their height; the badges that sat on the picture

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ Describe what you want to see — or never see again — in plain language.
4141
- **Any OpenAI-compatible provider.** OpenRouter, OpenAI, Anthropic, or your
4242
own endpoint.
4343
- **YouTube, the quiet way.** Hide Shorts, hide comments, and show, blur or
44-
drop thumbnails. Durations stay. No model involved.
44+
drop thumbnails. Durations stay. No model involved. Works on the mobile site
45+
too.
4546
- **Greyscale.** Wash the colour out of the chrome, the content, or both, on X
4647
and on YouTube. Less pull, same information.
4748

‎manifest.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"https://x.com/*",
99
"https://twitter.com/*",
1010
"https://www.youtube.com/*",
11+
"https://m.youtube.com/*",
1112
"https://openrouter.ai/*",
1213
"https://api.openai.com/*",
1314
"https://api.anthropic.com/*"
@@ -31,7 +32,7 @@
3132
"run_at": "document_start"
3233
},
3334
{
34-
"matches": ["https://www.youtube.com/*"],
35+
"matches": ["https://www.youtube.com/*", "https://m.youtube.com/*"],
3536
"js": ["content.js"],
3637
"css": ["content.css"],
3738
"run_at": "document_start"

‎privacy-policy.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ or [Anthropic](https://www.anthropic.com/legal/privacy).
5151
- **Storage** — to save your settings, key, and decision cache locally.
5252
- **Access to x.com and twitter.com** — to read posts on the page and hide the
5353
ones that match your criteria.
54-
- **Access to www.youtube.com** — to apply the YouTube page rules you turn on.
55-
Nothing on YouTube is read or sent anywhere; the rules are stylesheet rules.
54+
- **Access to www.youtube.com and m.youtube.com** — to apply the YouTube page
55+
rules you turn on, on the desktop and mobile sites alike. Nothing on YouTube
56+
is read or sent anywhere; the rules are stylesheet rules.
5657
- **Access to your provider's API** (`openrouter.ai`, `api.openai.com`,
5758
`api.anthropic.com`, or a custom endpoint you enter) — to send classification
5859
requests. A custom endpoint asks for its own permission when you save it.

‎src/background/index.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ chrome.runtime.onMessage.addListener((raw: unknown, sender, respond) => {
131131
const message = yield* Schema.decodeUnknown(Request)(raw);
132132
const privileged = fromExtensionPage(sender);
133133
const fromSite =
134-
sender.tab && /^https:\/\/(?:x\.com|twitter\.com|www\.youtube\.com)\//.test(sender.url ?? '');
134+
sender.tab &&
135+
/^https:\/\/(?:x\.com|twitter\.com|(?:www|m)\.youtube\.com)\//.test(sender.url ?? '');
135136
if (!privileged && !(fromSite && contentRequests.has(message.type))) {
136137
return yield* new OperationError({
137138
message: 'This operation is not available to content scripts.',

‎src/common/settings.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const Settings = Schema.Struct({
4141
presets: Schema.Array(Preset),
4242
corrections: Schema.Array(Correction).pipe(Schema.maxItems(MAX_CORRECTIONS)),
4343
notInterested: Schema.Boolean,
44+
filterComments: Schema.Boolean,
4445
analyzeImages: Schema.Boolean,
4546
maxImagesPerPost: boundedInt(1, 4),
4647
hideStyle: Schema.Literal('collapse', 'blur'),
@@ -83,6 +84,7 @@ export const defaults: Settings = {
8384
presets: [],
8485
corrections: [],
8586
notInterested: false,
87+
filterComments: false,
8688
analyzeImages: false,
8789
maxImagesPerPost: 2,
8890
hideStyle: 'collapse',
@@ -138,6 +140,7 @@ export const siteOrigins = [
138140
'https://x.com/*',
139141
'https://twitter.com/*',
140142
'https://www.youtube.com/*',
143+
'https://m.youtube.com/*',
141144
] as const;
142145

143146
/** The origin the configured provider is reached at, if it has a usable one. A

‎src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function startSite(site: Pick<Location, 'protocol' | 'hostname'> = locati
1010
case 'twitter.com':
1111
return startX();
1212
case 'www.youtube.com':
13+
case 'm.youtube.com':
1314
return startYouTube();
1415
}
1516
}

‎src/popup/App.tsx‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ListSheet, type ListKey } from './ListSheet';
99
import { ModelBrowser } from './ModelBrowser';
1010
import { Rail, type Section, type Site } from './Rail';
1111
import { XPanel, type XTab } from './XPanel';
12-
import { YouTubePanel } from './YouTubePanel';
12+
import { YouTubePanel, type YouTubeTab } from './YouTubePanel';
1313
import { Notice } from './ui';
1414

1515
type Form = { draft: Settings; saved: Settings };
@@ -18,7 +18,7 @@ const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b)
1818
const noop = () => {};
1919
/** `https://x.com/*` reads as x.com to the person being asked about it. */
2020
const host = (origin: string) => origin.replace(/^https:\/\//, '').replace(/\/\*$/, '');
21-
const filtered = /^https:\/\/(?:x\.com|twitter\.com|www\.youtube\.com)\//;
21+
const filtered = /^https:\/\/(?:x\.com|twitter\.com|(?:www|m)\.youtube\.com)\//;
2222

2323
/** The page the reader was looking at when they opened Sharp. Desktop Chrome
2424
* floats the popup above the page, so that is simply the active tab —
@@ -65,6 +65,7 @@ export function App() {
6565
const [section, setSection] = useState<Section>('x');
6666
const [xTab, setXTab] = useState<XTab>('filtering');
6767
const [generalTab, setGeneralTab] = useState<GeneralTab>('connection');
68+
const [youtubeTab, setYoutubeTab] = useState<YouTubeTab>('filtering');
6869
const [overlay, setOverlay] = useState<Overlay | null>(null);
6970
const [stats, setStats] = useState<Stats | null>(null);
7071
const [bytes, setBytes] = useState(0);
@@ -116,7 +117,7 @@ export function App() {
116117
if (/^https:\/\/(?:x|twitter)\.com\//.test(url)) {
117118
setSite('x');
118119
setThread(threadId(new URL(url).pathname));
119-
} else if (/^https:\/\/www\.youtube\.com\//.test(url)) {
120+
} else if (/^https:\/\/(?:www|m)\.youtube\.com\//.test(url)) {
120121
setSite('youtube');
121122
setSection('youtube');
122123
}
@@ -294,9 +295,13 @@ export function App() {
294295
['filtering', 'Filtering'],
295296
['rules', 'Rules'],
296297
['activity', 'Activity'],
298+
['misc', 'Misc'],
297299
] as const)
298300
: section === 'youtube'
299-
? ([['filtering', 'Filtering']] as const)
301+
? ([
302+
['filtering', 'Filtering'],
303+
['misc', 'Misc'],
304+
] as const)
300305
: ([
301306
['connection', 'Connection'],
302307
['appearance', 'Appearance'],
@@ -309,11 +314,12 @@ export function App() {
309314
class="tab"
310315
role="tab"
311316
aria-selected={
312-
section === 'x' ? xTab === id : section === 'youtube' || generalTab === id
317+
(section === 'x' ? xTab : section === 'youtube' ? youtubeTab : generalTab) === id
313318
}
314319
onClick={() => {
315320
if (section === 'x') setXTab(id as XTab);
316-
else if (section === 'general') setGeneralTab(id as GeneralTab);
321+
else if (section === 'youtube') setYoutubeTab(id as YouTubeTab);
322+
else setGeneralTab(id as GeneralTab);
317323
}}
318324
>
319325
{label}
@@ -394,7 +400,7 @@ export function App() {
394400
}
395401
/>
396402
) : section === 'youtube' ? (
397-
<YouTubePanel {...editor} />
403+
<YouTubePanel {...editor} tab={youtubeTab} />
398404
) : (
399405
<GeneralPanel
400406
{...editor}

‎src/popup/XPanel.tsx‎

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as fmt from './format';
66
import { lists, type ListKey } from './ListSheet';
77
import { RangeField, ToggleRow, type SettingsEditor } from './ui';
88

9-
export type XTab = 'filtering' | 'rules' | 'activity';
9+
export type XTab = 'filtering' | 'rules' | 'activity' | 'misc';
1010

1111
// A hidden post is one the reader did not have to read past. This is roughly
1212
// the time a post holds the eye on the way by, and the number is labelled as
@@ -91,6 +91,26 @@ export function XPanel({
9191
onSavePreset(name);
9292
};
9393

94+
if (tab === 'misc') {
95+
return (
96+
<div class="panel">
97+
<section class="group">
98+
<h2>Greyscale</h2>
99+
<ToggleRow
100+
label="Greyscale UI"
101+
checked={settings.greyscaleUi}
102+
onChange={(value) => update('greyscaleUi', value)}
103+
/>
104+
<ToggleRow
105+
label="Greyscale content"
106+
checked={settings.greyscaleContent}
107+
onChange={(value) => update('greyscaleContent', value)}
108+
/>
109+
</section>
110+
</div>
111+
);
112+
}
113+
94114
if (tab === 'rules') {
95115
return (
96116
<div class="panel">
@@ -332,6 +352,20 @@ export function XPanel({
332352
)}
333353
</section>
334354

355+
<section class="group">
356+
<h2>Comments</h2>
357+
<ToggleRow
358+
label="Filter comments"
359+
hint={
360+
settings.filterComments
361+
? 'Replies under a post are judged like any other post.'
362+
: 'Replies under a post are left alone. Only the timeline is filtered.'
363+
}
364+
checked={settings.filterComments}
365+
onChange={(value) => update('filterComments', value)}
366+
/>
367+
</section>
368+
335369
{settings.corrections.length > 0 && (
336370
<section class="group">
337371
<h2>Corrections</h2>
@@ -411,20 +445,6 @@ export function XPanel({
411445
onChange={(value) => update('analyzeImages', value)}
412446
/>
413447
</section>
414-
415-
<section class="group">
416-
<h2>Misc</h2>
417-
<ToggleRow
418-
label="Greyscale UI"
419-
checked={settings.greyscaleUi}
420-
onChange={(value) => update('greyscaleUi', value)}
421-
/>
422-
<ToggleRow
423-
label="Greyscale content"
424-
checked={settings.greyscaleContent}
425-
onChange={(value) => update('greyscaleContent', value)}
426-
/>
427-
</section>
428448
</div>
429449
);
430450
}

‎src/popup/YouTubePanel.tsx‎

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,29 @@ const thumbnailOptions = [
66
{ value: 'hidden', label: 'Hidden' },
77
] as const;
88

9-
export function YouTubePanel({ settings, update }: SettingsEditor) {
9+
export type YouTubeTab = 'filtering' | 'misc';
10+
11+
export function YouTubePanel({ settings, update, tab }: SettingsEditor & { tab: YouTubeTab }) {
12+
if (tab === 'misc') {
13+
return (
14+
<div class="panel">
15+
<section class="group">
16+
<h2>Greyscale</h2>
17+
<ToggleRow
18+
label="Greyscale UI"
19+
checked={settings.youtubeGreyscaleUi}
20+
onChange={(value) => update('youtubeGreyscaleUi', value)}
21+
/>
22+
<ToggleRow
23+
label="Greyscale content"
24+
checked={settings.youtubeGreyscaleContent}
25+
onChange={(value) => update('youtubeGreyscaleContent', value)}
26+
/>
27+
</section>
28+
</div>
29+
);
30+
}
31+
1032
return (
1133
<div class="panel">
1234
<section class="group">
@@ -38,19 +60,6 @@ export function YouTubePanel({ settings, update }: SettingsEditor) {
3860
onChange={(value) => update('thumbnails', value)}
3961
/>
4062
</section>
41-
<section class="group">
42-
<h2>Misc</h2>
43-
<ToggleRow
44-
label="Greyscale UI"
45-
checked={settings.youtubeGreyscaleUi}
46-
onChange={(value) => update('youtubeGreyscaleUi', value)}
47-
/>
48-
<ToggleRow
49-
label="Greyscale content"
50-
checked={settings.youtubeGreyscaleContent}
51-
onChange={(value) => update('youtubeGreyscaleContent', value)}
52-
/>
53-
</section>
5463
</div>
5564
);
5665
}

0 commit comments

Comments
 (0)