Add the Username Colors feature (#6).
This commit is contained in:
parent
51db617b77
commit
ee289d562c
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
<//>
|
<//>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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}
|
||||||
|
<//>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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',
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,4 +27,10 @@ declare global {
|
||||||
text: string;
|
text: string;
|
||||||
username: string;
|
username: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UsernameColor = {
|
||||||
|
color: string;
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue