Add the Anonymize Usernames feature (#5).
This commit is contained in:
parent
52696fe50e
commit
75006ff7eb
|
@ -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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
<//>
|
||||||
|
`;
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue