1
Fork 0

Add the Anonymize Usernames feature (#5).

This commit is contained in:
Bauke 2022-02-23 18:17:22 +01:00
parent 52696fe50e
commit 75006ff7eb
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
8 changed files with 98 additions and 2 deletions

View File

@ -8,6 +8,7 @@ import {
BackToTopFeature, BackToTopFeature,
JumpToNewCommentFeature, JumpToNewCommentFeature,
UserLabelsFeature, UserLabelsFeature,
runAnonymizeUsernamesFeature,
runHideVotesFeature, runHideVotesFeature,
runMarkdownToolbarFeature, runMarkdownToolbarFeature,
} from './scripts/exports.js'; } from './scripts/exports.js';
@ -36,6 +37,10 @@ async function initialize() {
// Object to hold the active components we are going to render. // Object to hold the active components we are going to render.
const components: Record<string, TRXComponent | undefined> = {}; const components: Record<string, TRXComponent | undefined> = {};
if (settings.features.anonymizeUsernames) {
runAnonymizeUsernamesFeature();
}
if (settings.features.autocomplete) { if (settings.features.autocomplete) {
components.autocomplete = html` components.autocomplete = html`
<${AutocompleteFeature} settings=${settings} /> <${AutocompleteFeature} settings=${settings} />

View File

@ -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}>
<p class="info">
Anonymizes usernames by replacing them with "Anonymous #".
<br />
Note that User Labels will still be applied to any usernames as normal.
</p>
<//>
`;
}

View File

@ -1,4 +1,5 @@
export {AboutSetting} from './about.js'; export {AboutSetting} from './about.js';
export {AnonymizeUsernamesSetting} from './anonymize-usernames.js';
export {AutocompleteSetting} from './autocomplete.js'; export {AutocompleteSetting} from './autocomplete.js';
export {BackToTopSetting} from './back-to-top.js'; export {BackToTopSetting} from './back-to-top.js';
export {HideVotesSetting} from './hide-votes.js'; export {HideVotesSetting} from './hide-votes.js';

View File

@ -1,5 +1,6 @@
import { import {
AboutSetting, AboutSetting,
AnonymizeUsernamesSetting,
AutocompleteSetting, AutocompleteSetting,
BackToTopSetting, BackToTopSetting,
HideVotesSetting, HideVotesSetting,
@ -16,6 +17,12 @@ import {
* * The component function should return the corresponding settings components. * * The component function should return the corresponding settings components.
*/ */
export const features = [ export const features = [
{
index: 0,
key: 'anonymizeUsernames',
value: 'Anonymize Usernames',
component: () => AnonymizeUsernamesSetting,
},
{ {
index: 0, index: 0,
key: 'autocomplete', key: 'autocomplete',

View File

@ -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<HTMLElement>(
'.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<string, string> {
const usernames = new Set(
elements.map((element) => usernameFromElement(element).replace(/@/g, '')),
);
const replacements: Record<string, string> = {};
for (const [index, username] of Array.from(usernames).entries()) {
replacements[username] = `Anonymous ${index}`;
}
return replacements;
}
function usernameFromElement(element: HTMLElement): string {
return (element.textContent ?? '<unknown>').trim();
}

View File

@ -1,3 +1,4 @@
export * from './anonymize-usernames.js';
export * from './autocomplete.js'; export * from './autocomplete.js';
export * from './back-to-top.js'; export * from './back-to-top.js';
export * from './hide-votes.js'; export * from './hide-votes.js';

View File

@ -64,7 +64,7 @@ export class UserLabelsFeature extends Component<Props, State> {
this.setState({hidden: true}); this.setState({hidden: true});
}; };
addLabelsToUsernames = (elements: Element[], onlyID?: number): number => { addLabelsToUsernames = (elements: HTMLElement[], onlyID?: number): number => {
const settings = this.props.settings; const settings = this.props.settings;
const inTopicListing = document.querySelector('.topic-listing') !== null; const inTopicListing = document.querySelector('.topic-listing') !== null;
@ -87,10 +87,14 @@ export class UserLabelsFeature extends Component<Props, State> {
}); });
for (const element of elements) { for (const element of elements) {
const username: string = element let username: string = element
.textContent!.replace(/@/g, '') .textContent!.replace(/@/g, '')
.toLowerCase(); .toLowerCase();
if (settings.features.anonymizeUsernames) {
username = element.dataset.trxUsername ?? username;
}
const userLabels = sortedLabels.filter( const userLabels = sortedLabels.filter(
(value) => (value) =>
value.username === username && value.username === username &&

View File

@ -60,6 +60,7 @@ export default class Settings {
public features: { public features: {
[index: string]: boolean; [index: string]: boolean;
anonymizeUsernames: boolean;
autocomplete: boolean; autocomplete: boolean;
backToTop: boolean; backToTop: boolean;
debug: boolean; debug: boolean;
@ -120,6 +121,7 @@ export default class Settings {
}; };
this.features = { this.features = {
anonymizeUsernames: false,
autocomplete: true, autocomplete: true,
backToTop: true, backToTop: true,
debug: false, debug: false,