1
Fork 0

Add the Username Colors feature (#6).

This commit is contained in:
Bauke 2022-02-25 01:06:24 +01:00
parent 51db617b77
commit ee289d562c
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
10 changed files with 250 additions and 1 deletions

View File

@ -11,6 +11,7 @@ import {
runAnonymizeUsernamesFeature, runAnonymizeUsernamesFeature,
runHideVotesFeature, runHideVotesFeature,
runMarkdownToolbarFeature, runMarkdownToolbarFeature,
runUsernameColorsFeature,
} from './scripts/exports.js'; } from './scripts/exports.js';
import Settings from './settings.js'; import Settings from './settings.js';
import {extractGroups, initializeGlobals, log} from './utilities/exports.js'; import {extractGroups, initializeGlobals, log} from './utilities/exports.js';
@ -70,6 +71,12 @@ async function initialize() {
}); });
} }
if (settings.features.usernameColors) {
observerFeatures.push(() => {
runUsernameColorsFeature(settings);
});
}
// Initialize all the observer-dependent features first. // Initialize all the observer-dependent features first.
for (const feature of observerFeatures) { for (const feature of observerFeatures) {
feature(); feature();

View File

@ -8,7 +8,8 @@ export function AnonymizeUsernamesSetting(props: SettingProps): TRXComponent {
<p class="info"> <p class="info">
Anonymizes usernames by replacing them with "Anonymous #". Anonymizes usernames by replacing them with "Anonymous #".
<br /> <br />
Note that User Labels will still be applied to any usernames as normal. Note that User Labels and Username Colors will still be applied to any
usernames as normal.
</p> </p>
<//> <//>
`; `;

View File

@ -6,3 +6,4 @@ export {HideVotesSetting} from './hide-votes.js';
export {JumpToNewCommentSetting} from './jump-to-new-comment.js'; export {JumpToNewCommentSetting} from './jump-to-new-comment.js';
export {MarkdownToolbarSetting} from './markdown-toolbar.js'; export {MarkdownToolbarSetting} from './markdown-toolbar.js';
export {UserLabelsSetting} from './user-labels.js'; export {UserLabelsSetting} from './user-labels.js';
export {UsernameColorsSetting} from './username-colors.js';

View File

@ -0,0 +1,153 @@
import {html} from 'htm/preact';
import {Component} from 'preact';
import Settings from '../../settings.js';
import {log} from '../../utilities/exports.js';
import {Setting, SettingProps} from './index.js';
type State = {
previewChecked: 'off' | 'foreground' | 'background';
usernameColors: UsernameColor[];
};
export class UsernameColorsSetting extends Component<SettingProps, State> {
constructor(props: SettingProps) {
super(props);
this.state = {
previewChecked: 'off',
usernameColors: [],
};
}
async componentDidMount() {
const settings = await Settings.fromSyncStorage();
this.setState({usernameColors: settings.data.usernameColors});
}
addNewColor = () => {
let id = 1;
if (this.state.usernameColors.length > 0) {
id = this.state.usernameColors.sort((a, b) => b.id - a.id)[0].id + 1;
}
const newColor: UsernameColor = {
color: '',
id,
username: '',
};
this.setState({
usernameColors: [...this.state.usernameColors, newColor],
});
};
removeColor = (targetId: number) => {
const usernameColors = this.state.usernameColors.filter(
({id}) => id !== targetId,
);
this.setState({usernameColors});
};
saveChanges = async () => {
const settings = await Settings.fromSyncStorage();
settings.data.usernameColors = this.state.usernameColors;
await settings.save();
};
togglePreview = async () => {
let {previewChecked} = this.state;
// eslint-disable-next-line default-case
switch (previewChecked) {
case 'off':
previewChecked = 'foreground';
break;
case 'foreground':
previewChecked = 'background';
break;
case 'background':
previewChecked = 'off';
break;
}
this.setState({previewChecked});
};
onInput = (event: Event, id: number, key: 'color' | 'username') => {
const colorIndex = this.state.usernameColors.findIndex(
(color) => color.id === id,
);
if (colorIndex === -1) {
log(`Tried to edit unknown UsernameColor ID: ${id}`);
return;
}
const newValue = (event.target as HTMLInputElement).value;
this.state.usernameColors[colorIndex][key] = newValue;
this.setState({usernameColors: this.state.usernameColors});
};
render() {
const {previewChecked, usernameColors} = this.state;
usernameColors.sort((a, b) => a.id - b.id);
const editors = usernameColors.map(({color, id, username}) => {
const style: Record<string, string> = {};
if (previewChecked === 'background') {
style.backgroundColor = color;
} else if (previewChecked === 'foreground') {
style.color = color;
}
const usernameHandler = (event: Event) => {
this.onInput(event, id, 'username');
};
const colorHandler = (event: Event) => {
this.onInput(event, id, 'color');
};
const removeHandler = () => {
this.removeColor(id);
};
return html`
<div class="username-colors-editor" key=${id}>
<input style=${style} value=${username} onInput=${usernameHandler} />
<input style=${style} value=${color} onInput=${colorHandler} />
<button class="button destructive" onClick=${removeHandler}>
Remove
</button>
</div>
`;
});
return html`
<${Setting} ...${this.props}>
<p class="info">
Assign custom colors to usernames.
<br />
You can enter multiple usernames separated by a comma if you want them
to use the same color.
</p>
<div class="username-colors-controls">
<button class="button" onClick=${this.addNewColor}>
Add New Color
</button>
<button class="button" onClick=${this.togglePreview}>
Toggle Preview
</button>
<button class="button" onClick=${this.saveChanges}>
Save Changes
</button>
</div>
${editors}
<//>
`;
}
}

View File

@ -7,6 +7,7 @@ import {
JumpToNewCommentSetting, JumpToNewCommentSetting,
MarkdownToolbarSetting, MarkdownToolbarSetting,
UserLabelsSetting, UserLabelsSetting,
UsernameColorsSetting,
} from './components/exports.js'; } from './components/exports.js';
/** /**
@ -59,6 +60,12 @@ export const features = [
value: 'User Labels', value: 'User Labels',
component: () => UserLabelsSetting, component: () => UserLabelsSetting,
}, },
{
index: 0,
key: 'usernameColors',
value: 'Username Colors',
component: () => UsernameColorsSetting,
},
{ {
index: 1, index: 1,
key: 'debug', key: 'debug',

View File

@ -5,3 +5,4 @@ export * from './hide-votes.js';
export * from './jump-to-new-comment.js'; export * from './jump-to-new-comment.js';
export * from './markdown-toolbar.js'; export * from './markdown-toolbar.js';
export * from './user-labels.js'; export * from './user-labels.js';
export * from './username-colors.js';

View File

@ -0,0 +1,45 @@
import Settings from '../settings.js';
import {log, querySelectorAll} from '../utilities/exports.js';
export function runUsernameColorsFeature(settings: Settings) {
const count = usernameColors(settings);
log(`Username Colors: Applied ${count} colors.`);
}
function usernameColors(settings: Settings): number {
const usernameColors = new Map<string, string>();
for (const {color, username: usernames} of settings.data.usernameColors) {
for (const username of usernames.split(',')) {
usernameColors.set(username.trim().toLowerCase(), color);
}
}
let count = 0;
const usernameElements = querySelectorAll<HTMLElement>(
'.link-user:not(.trx-username-colors)',
);
for (const element of usernameElements) {
if (element.classList.contains('trx-username-colors')) {
continue;
}
let target =
element.textContent?.replace(/@/g, '').trim().toLowerCase() ??
'<unknown>';
if (settings.features.anonymizeUsernames) {
target = element.dataset.trxUsername?.toLowerCase() ?? target;
}
element.classList.add('trx-username-colors');
const color = usernameColors.get(target);
if (color === undefined) {
continue;
}
element.style.color = color;
count += 1;
}
return count;
}

View File

@ -124,4 +124,28 @@
list-style: square; list-style: square;
padding: 8px 8px 8px 24px; padding: 8px 8px 8px 24px;
} }
.username-colors-controls {
display: grid;
gap: 8px;
grid-template-columns: repeat(3, 1fr);
}
.username-colors-editor {
display: grid;
gap: 8px;
grid-template-columns: auto auto min-content;
margin-top: 8px;
input {
background-color: var(--background-primary);
border: 1px solid var(--blue);
color: var(--foreground);
padding: 8px;
}
.button {
min-width: 10rem;
}
}
} }

View File

@ -56,6 +56,7 @@ export default class Settings {
knownGroups: string[]; knownGroups: string[];
latestActiveFeatureTab: string; latestActiveFeatureTab: string;
userLabels: UserLabel[]; userLabels: UserLabel[];
usernameColors: UsernameColor[];
}; };
public features: { public features: {
@ -68,6 +69,7 @@ export default class Settings {
jumpToNewComment: boolean; jumpToNewComment: boolean;
markdownToolbar: boolean; markdownToolbar: boolean;
userLabels: boolean; userLabels: boolean;
usernameColors: boolean;
}; };
private constructor() { private constructor() {
@ -118,6 +120,7 @@ export default class Settings {
], ],
latestActiveFeatureTab: 'debug', latestActiveFeatureTab: 'debug',
userLabels: [], userLabels: [],
usernameColors: [],
}; };
this.features = { this.features = {
@ -129,6 +132,7 @@ export default class Settings {
jumpToNewComment: true, jumpToNewComment: true,
markdownToolbar: true, markdownToolbar: true,
userLabels: true, userLabels: true,
usernameColors: false,
}; };
} }

6
source/types.d.ts vendored
View File

@ -27,4 +27,10 @@ declare global {
text: string; text: string;
username: string; username: string;
}; };
type UsernameColor = {
color: string;
id: number;
username: string;
};
} }