Add the Username Colors feature (#6).
This commit is contained in:
parent
51db617b77
commit
ee289d562c
|
@ -11,6 +11,7 @@ import {
|
|||
runAnonymizeUsernamesFeature,
|
||||
runHideVotesFeature,
|
||||
runMarkdownToolbarFeature,
|
||||
runUsernameColorsFeature,
|
||||
} from './scripts/exports.js';
|
||||
import Settings from './settings.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.
|
||||
for (const feature of observerFeatures) {
|
||||
feature();
|
||||
|
|
|
@ -8,7 +8,8 @@ export function AnonymizeUsernamesSetting(props: SettingProps): TRXComponent {
|
|||
<p class="info">
|
||||
Anonymizes usernames by replacing them with "Anonymous #".
|
||||
<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>
|
||||
<//>
|
||||
`;
|
||||
|
|
|
@ -6,3 +6,4 @@ export {HideVotesSetting} from './hide-votes.js';
|
|||
export {JumpToNewCommentSetting} from './jump-to-new-comment.js';
|
||||
export {MarkdownToolbarSetting} from './markdown-toolbar.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,
|
||||
MarkdownToolbarSetting,
|
||||
UserLabelsSetting,
|
||||
UsernameColorsSetting,
|
||||
} from './components/exports.js';
|
||||
|
||||
/**
|
||||
|
@ -59,6 +60,12 @@ export const features = [
|
|||
value: 'User Labels',
|
||||
component: () => UserLabelsSetting,
|
||||
},
|
||||
{
|
||||
index: 0,
|
||||
key: 'usernameColors',
|
||||
value: 'Username Colors',
|
||||
component: () => UsernameColorsSetting,
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
key: 'debug',
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from './hide-votes.js';
|
|||
export * from './jump-to-new-comment.js';
|
||||
export * from './markdown-toolbar.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;
|
||||
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[];
|
||||
latestActiveFeatureTab: string;
|
||||
userLabels: UserLabel[];
|
||||
usernameColors: UsernameColor[];
|
||||
};
|
||||
|
||||
public features: {
|
||||
|
@ -68,6 +69,7 @@ export default class Settings {
|
|||
jumpToNewComment: boolean;
|
||||
markdownToolbar: boolean;
|
||||
userLabels: boolean;
|
||||
usernameColors: boolean;
|
||||
};
|
||||
|
||||
private constructor() {
|
||||
|
@ -118,6 +120,7 @@ export default class Settings {
|
|||
],
|
||||
latestActiveFeatureTab: 'debug',
|
||||
userLabels: [],
|
||||
usernameColors: [],
|
||||
};
|
||||
|
||||
this.features = {
|
||||
|
@ -129,6 +132,7 @@ export default class Settings {
|
|||
jumpToNewComment: true,
|
||||
markdownToolbar: true,
|
||||
userLabels: true,
|
||||
usernameColors: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -27,4 +27,10 @@ declare global {
|
|||
text: string;
|
||||
username: string;
|
||||
};
|
||||
|
||||
type UsernameColor = {
|
||||
color: string;
|
||||
id: number;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue