diff --git a/source/content-scripts/features/username-colors.ts b/source/content-scripts/features/username-colors.ts index e09ce3d..fc1f467 100644 --- a/source/content-scripts/features/username-colors.ts +++ b/source/content-scripts/features/username-colors.ts @@ -1,21 +1,23 @@ -import {log, querySelectorAll} from "../../utilities/exports.js"; -import {type UsernameColorsData} from "../../storage/exports.js"; +import { log, querySelectorAll, getColorFromStringHash, isColorBright } from "../../utilities/exports.js"; +import { type UsernameColorsData } from "../../storage/exports.js"; -export function runUsernameColorsFeature( +export async function runUsernameColorsFeature( data: UsernameColorsData, anonymizeUsernamesEnabled: boolean, + randomizeUsernameColorsEnabled: boolean, ) { - const count = usernameColors(data, anonymizeUsernamesEnabled); + const count = await usernameColors(data, anonymizeUsernamesEnabled, randomizeUsernameColorsEnabled); log(`Username Colors: Applied ${count} colors.`); } -function usernameColors( +async function usernameColors( data: UsernameColorsData, anonymizeUsernamesEnabled: boolean, -): number { + randomizeUsernameColorsEnabled: boolean, +): Promise { const usernameColors = new Map(); for (const { - value: {color, username: usernames}, + value: { color, username: usernames }, } of data) { for (const username of usernames.split(",")) { usernameColors.set(username.trim().toLowerCase(), color); @@ -40,12 +42,18 @@ function usernameColors( } element.classList.add("trx-username-colors"); - const color = usernameColors.get(target); - if (color === undefined) { + let color = usernameColors.get(target); + if (color) { + element.style.color = color; + } else if (randomizeUsernameColorsEnabled) { + const randomColor = await getColorFromStringHash(target); + const fontColor = isColorBright(randomColor) ? "#000" : "#FFF" + element.style.setProperty("--background-color", randomColor); + element.style.setProperty("--text-color", fontColor); + element.classList.add("trx-colored-username") + } else { continue; } - - element.style.color = color; count += 1; } diff --git a/source/content-scripts/setup.tsx b/source/content-scripts/setup.tsx index ad8dedd..5478a19 100644 --- a/source/content-scripts/setup.tsx +++ b/source/content-scripts/setup.tsx @@ -95,7 +95,8 @@ async function initialize() { if (enabledFeatures.value.has(Feature.UsernameColors)) { observerFeatures.push(async () => { const data = await fromStorage(Feature.UsernameColors); - runUsernameColorsFeature(data, anonymizeUsernamesEnabled); + const randomizeUsernameColors = await fromStorage(Data.RandomizeUsernameColors); + runUsernameColorsFeature(data, anonymizeUsernamesEnabled, randomizeUsernameColors.value); }); } diff --git a/source/options/components/username-colors.tsx b/source/options/components/username-colors.tsx index a078e2b..0cb424d 100644 --- a/source/options/components/username-colors.tsx +++ b/source/options/components/username-colors.tsx @@ -4,15 +4,18 @@ import { type UsernameColorsData, type UsernameColor, Feature, + Data, createValueUsernamecolor, fromStorage, } from "../../storage/exports.js"; import {Setting, type SettingProps} from "./index.js"; +import { Value } from "@holllo/webextension-storage"; type State = { previewChecked: "off" | "foreground" | "background"; usernameColors: UsernameColorsData; usernameColorsToRemove: UsernameColorsData; + randomizeChecked: Value; }; export class UsernameColorsSetting extends Component { @@ -23,11 +26,12 @@ export class UsernameColorsSetting extends Component { previewChecked: "off", usernameColors: undefined!, usernameColorsToRemove: [], + randomizeChecked: undefined!, }; } async componentDidMount() { - this.setState({usernameColors: await fromStorage(Feature.UsernameColors)}); + this.setState({usernameColors: await fromStorage(Feature.UsernameColors), randomizeChecked: await fromStorage(Data.RandomizeUsernameColors)}); } addNewColor = async () => { @@ -100,6 +104,13 @@ export class UsernameColorsSetting extends Component { this.setState({previewChecked}); }; + toggleRandomized = () => { + const randomizeChecked = this.state.randomizeChecked; + randomizeChecked.value = !randomizeChecked.value; + void randomizeChecked.save(); + this.setState({randomizeChecked}) + } + onInput = (event: Event, id: number, key: "color" | "username") => { const colorIndex = this.state.usernameColors.findIndex( ({value}) => value.id === id, @@ -115,7 +126,7 @@ export class UsernameColorsSetting extends Component { }; render() { - const {previewChecked, usernameColors} = this.state; + const {previewChecked, usernameColors, randomizeChecked} = this.state; if (usernameColors === undefined) { return; } @@ -163,6 +174,7 @@ export class UsernameColorsSetting extends Component { ); }); + return (

@@ -170,6 +182,9 @@ export class UsernameColorsSetting extends Component {
You can enter multiple usernames separated by a comma if you want them to use the same color. +
+ If randomize is selected then all usernames will be given a random background color. + This will not override colors you have manually assigned.

@@ -184,6 +199,19 @@ export class UsernameColorsSetting extends Component { + +
    +
  • + +
  • +
{editors} diff --git a/source/scss/_shared.scss b/source/scss/_shared.scss index 9d77dda..372d637 100644 --- a/source/scss/_shared.scss +++ b/source/scss/_shared.scss @@ -2,3 +2,10 @@ /* stylelint-disable-next-line declaration-no-important */ display: none !important; } + +.trx-colored-username { + background-color: var(--background-color); + border-radius: 3px; + color: var(--text-color); + padding: 2px 3px; +} \ No newline at end of file diff --git a/source/storage/enums.ts b/source/storage/enums.ts index c5021d5..2c5364a 100644 --- a/source/storage/enums.ts +++ b/source/storage/enums.ts @@ -22,5 +22,6 @@ export enum Data { EnabledFeatures = "enabled-features", KnownGroups = "known-groups", LatestActiveFeatureTab = "latest-active-feature-tab", + RandomizeUsernameColors = "randomize-username-colors", Version = "data-version", } diff --git a/source/storage/exports.ts b/source/storage/exports.ts index 24d38ab..aab4829 100644 --- a/source/storage/exports.ts +++ b/source/storage/exports.ts @@ -58,6 +58,13 @@ export const storageValues = { value: "2.0.0", storage: browser.storage.sync, }), + [Data.RandomizeUsernameColors]: createValue({ + deserialize: (input) => JSON.parse(input) as boolean, + serialize: (input) => JSON.stringify(input), + key: Data.RandomizeUsernameColors, + value: false, + storage: browser.storage.sync, + }), [Feature.HideTopics]: collectHideTopicsData(), [Feature.HideVotes]: createValue({ deserialize: (input) => JSON.parse(input) as HideVotesData, diff --git a/source/utilities/text.ts b/source/utilities/text.ts index 2f97e6a..3f98531 100644 --- a/source/utilities/text.ts +++ b/source/utilities/text.ts @@ -16,3 +16,22 @@ export function pluralize( return plural ?? singular + "s"; } + +/** Return a hash for a given username */ +export async function hashString(str: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(str) + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashString = hashArray + .map((b) => b.toString()) + .join(""); + return hashString; +} + +/** Return a color hex code based on hash of username string */ +export async function getColorFromStringHash(username: string): Promise { + const usernameHash = parseInt(await hashString(username)); + const color = Math.abs(usernameHash % parseInt("0xFFFFFF")).toString(16) + return `#${color}`.padEnd(7, "0") +} \ No newline at end of file