From 75006ff7eb910e55a9bf34ffdaea2cdcf6975800 Mon Sep 17 00:00:00 2001 From: Bauke Date: Wed, 23 Feb 2022 18:17:22 +0100 Subject: [PATCH] Add the Anonymize Usernames feature (#5). --- source/content-scripts.ts | 5 ++ .../options/components/anonymize-usernames.ts | 15 +++++ source/options/components/exports.ts | 1 + source/options/features.ts | 7 +++ source/scripts/anonymize-usernames.ts | 61 +++++++++++++++++++ source/scripts/exports.ts | 1 + source/scripts/user-labels.ts | 8 ++- source/settings.ts | 2 + 8 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 source/options/components/anonymize-usernames.ts create mode 100644 source/scripts/anonymize-usernames.ts diff --git a/source/content-scripts.ts b/source/content-scripts.ts index db724d7..7b402a9 100644 --- a/source/content-scripts.ts +++ b/source/content-scripts.ts @@ -8,6 +8,7 @@ import { BackToTopFeature, JumpToNewCommentFeature, UserLabelsFeature, + runAnonymizeUsernamesFeature, runHideVotesFeature, runMarkdownToolbarFeature, } from './scripts/exports.js'; @@ -36,6 +37,10 @@ async function initialize() { // Object to hold the active components we are going to render. const components: Record = {}; + if (settings.features.anonymizeUsernames) { + runAnonymizeUsernamesFeature(); + } + if (settings.features.autocomplete) { components.autocomplete = html` <${AutocompleteFeature} settings=${settings} /> diff --git a/source/options/components/anonymize-usernames.ts b/source/options/components/anonymize-usernames.ts new file mode 100644 index 0000000..bb44c58 --- /dev/null +++ b/source/options/components/anonymize-usernames.ts @@ -0,0 +1,15 @@ +import {html} from 'htm/preact'; + +import {Setting, SettingProps} from './index.js'; + +export function AnonymizeUsernamesSetting(props: SettingProps): TRXComponent { + return html` + <${Setting} ...${props}> +

+ Anonymizes usernames by replacing them with "Anonymous #". +
+ Note that User Labels will still be applied to any usernames as normal. +

+ + `; +} diff --git a/source/options/components/exports.ts b/source/options/components/exports.ts index c1d41a7..7444aaf 100644 --- a/source/options/components/exports.ts +++ b/source/options/components/exports.ts @@ -1,4 +1,5 @@ export {AboutSetting} from './about.js'; +export {AnonymizeUsernamesSetting} from './anonymize-usernames.js'; export {AutocompleteSetting} from './autocomplete.js'; export {BackToTopSetting} from './back-to-top.js'; export {HideVotesSetting} from './hide-votes.js'; diff --git a/source/options/features.ts b/source/options/features.ts index 3503f64..c577dc7 100644 --- a/source/options/features.ts +++ b/source/options/features.ts @@ -1,5 +1,6 @@ import { AboutSetting, + AnonymizeUsernamesSetting, AutocompleteSetting, BackToTopSetting, HideVotesSetting, @@ -16,6 +17,12 @@ import { * * The component function should return the corresponding settings components. */ export const features = [ + { + index: 0, + key: 'anonymizeUsernames', + value: 'Anonymize Usernames', + component: () => AnonymizeUsernamesSetting, + }, { index: 0, key: 'autocomplete', diff --git a/source/scripts/anonymize-usernames.ts b/source/scripts/anonymize-usernames.ts new file mode 100644 index 0000000..25b2f43 --- /dev/null +++ b/source/scripts/anonymize-usernames.ts @@ -0,0 +1,61 @@ +import {log, querySelectorAll} from '../utilities/exports.js'; + +export function runAnonymizeUsernamesFeature() { + const observer = new window.MutationObserver(() => { + observer.disconnect(); + anonymizeUsernames(); + startObserver(); + }); + + function startObserver() { + observer.observe(document, { + childList: true, + subtree: true, + }); + } + + const count = anonymizeUsernames(); + startObserver(); + + log(`Anonymize Usernames: Initialized for ${count} user links.`); +} + +function anonymizeUsernames(): number { + const usernameElements = querySelectorAll( + '.link-user:not(.trx-anonymized)', + ); + const replacements = generateReplacements(usernameElements); + + for (const element of usernameElements) { + let username = usernameFromElement(element); + const isMention = username.startsWith('@'); + if (isMention) { + username = username.slice(1); + } + + const replacement = replacements[username]; + element.textContent = isMention ? `@${replacement}` : `${replacement}`; + + element.classList.add('trx-anonymized'); + element.dataset.trxUsername = username; + } + + return usernameElements.length; +} + +function generateReplacements(elements: HTMLElement[]): Record { + const usernames = new Set( + elements.map((element) => usernameFromElement(element).replace(/@/g, '')), + ); + + const replacements: Record = {}; + for (const [index, username] of Array.from(usernames).entries()) { + replacements[username] = `Anonymous ${index}`; + } + + return replacements; +} + +function usernameFromElement(element: HTMLElement): string { + return (element.textContent ?? '').trim(); +} diff --git a/source/scripts/exports.ts b/source/scripts/exports.ts index 1cdec2e..05381ac 100644 --- a/source/scripts/exports.ts +++ b/source/scripts/exports.ts @@ -1,3 +1,4 @@ +export * from './anonymize-usernames.js'; export * from './autocomplete.js'; export * from './back-to-top.js'; export * from './hide-votes.js'; diff --git a/source/scripts/user-labels.ts b/source/scripts/user-labels.ts index c7dd388..071d6c3 100644 --- a/source/scripts/user-labels.ts +++ b/source/scripts/user-labels.ts @@ -64,7 +64,7 @@ export class UserLabelsFeature extends Component { this.setState({hidden: true}); }; - addLabelsToUsernames = (elements: Element[], onlyID?: number): number => { + addLabelsToUsernames = (elements: HTMLElement[], onlyID?: number): number => { const settings = this.props.settings; const inTopicListing = document.querySelector('.topic-listing') !== null; @@ -87,10 +87,14 @@ export class UserLabelsFeature extends Component { }); for (const element of elements) { - const username: string = element + let username: string = element .textContent!.replace(/@/g, '') .toLowerCase(); + if (settings.features.anonymizeUsernames) { + username = element.dataset.trxUsername ?? username; + } + const userLabels = sortedLabels.filter( (value) => value.username === username && diff --git a/source/settings.ts b/source/settings.ts index 91ab98b..133db79 100644 --- a/source/settings.ts +++ b/source/settings.ts @@ -60,6 +60,7 @@ export default class Settings { public features: { [index: string]: boolean; + anonymizeUsernames: boolean; autocomplete: boolean; backToTop: boolean; debug: boolean; @@ -120,6 +121,7 @@ export default class Settings { }; this.features = { + anonymizeUsernames: false, autocomplete: true, backToTop: true, debug: false,