Compare commits
5 Commits
126960ba82
...
8fb439c98c
Author | SHA1 | Date |
---|---|---|
Bauke | 8fb439c98c | |
Bauke | 78553e4908 | |
Bauke | 26e04cb6f2 | |
Bauke | 74ea66083d | |
Bauke | a5891bb4c0 |
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
> **The principal enhancement suite for Tildes.**
|
> **The principal enhancement suite for Tildes.**
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* If you'd like to know what the differences between Tildes Extended and Tildes ReExtended are, see [DIFFERENCES.md](DIFFERENCES.md).
|
||||||
|
* To build Tildes ReExtended yourself, see [DEVELOPMENT.md](DEVELOPMENT.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Distributed under the [MIT](https://spdx.org/licenses/MIT.html) license, see [LICENSE](https://gitlab.com/tildes-community/tildes-reextended/-/blob/main/LICENSE) for more information.
|
Distributed under the [MIT](https://spdx.org/licenses/MIT.html) license, see [LICENSE](https://gitlab.com/tildes-community/tildes-reextended/-/blob/main/LICENSE) for more information.
|
||||||
|
|
|
@ -80,9 +80,7 @@ export class UserLabelsFeature extends Component<Props, State> {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
let username: string = element
|
let username: string = element.textContent!.replace(/@/g, "");
|
||||||
.textContent!.replace(/@/g, "")
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
if (this.props.anonymizeUsernamesEnabled) {
|
if (this.props.anonymizeUsernamesEnabled) {
|
||||||
username = element.dataset.trxUsername ?? username;
|
username = element.dataset.trxUsername ?? username;
|
||||||
|
@ -90,7 +88,7 @@ export class UserLabelsFeature extends Component<Props, State> {
|
||||||
|
|
||||||
const userLabels = sortedLabels.filter(
|
const userLabels = sortedLabels.filter(
|
||||||
({value}) =>
|
({value}) =>
|
||||||
value.username.toLowerCase() === username &&
|
value.username.toLowerCase() === username.toLowerCase() &&
|
||||||
(onlyID === undefined ? true : value.id === onlyID),
|
(onlyID === undefined ? true : value.id === onlyID),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -261,7 +259,7 @@ export class UserLabelsFeature extends Component<Props, State> {
|
||||||
this.addLabelsToUsernames(querySelectorAll(".link-user"), newId);
|
this.addLabelsToUsernames(querySelectorAll(".link-user"), newId);
|
||||||
} else {
|
} else {
|
||||||
const index = userLabels.findIndex(({value}) => value.id === id);
|
const index = userLabels.findIndex(({value}) => value.id === id);
|
||||||
userLabels.splice(index, 1);
|
await userLabels.splice(index, 1)[0].remove();
|
||||||
userLabels.push(
|
userLabels.push(
|
||||||
await createValueUserLabel({
|
await createValueUserLabel({
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -14,7 +14,9 @@ function usernameColors(
|
||||||
anonymizeUsernamesEnabled: boolean,
|
anonymizeUsernamesEnabled: boolean,
|
||||||
): number {
|
): number {
|
||||||
const usernameColors = new Map<string, string>();
|
const usernameColors = new Map<string, string>();
|
||||||
for (const {color, username: usernames} of data) {
|
for (const {
|
||||||
|
value: {color, username: usernames},
|
||||||
|
} of data) {
|
||||||
for (const username of usernames.split(",")) {
|
for (const username of usernames.split(",")) {
|
||||||
usernameColors.set(username.trim().toLowerCase(), color);
|
usernameColors.set(username.trim().toLowerCase(), color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ async function initialize() {
|
||||||
if (enabledFeatures.value.has(Feature.UsernameColors)) {
|
if (enabledFeatures.value.has(Feature.UsernameColors)) {
|
||||||
observerFeatures.push(async () => {
|
observerFeatures.push(async () => {
|
||||||
const data = await fromStorage(Feature.UsernameColors);
|
const data = await fromStorage(Feature.UsernameColors);
|
||||||
runUsernameColorsFeature(data.value, anonymizeUsernamesEnabled);
|
runUsernameColorsFeature(data, anonymizeUsernamesEnabled);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import {Component} from "preact";
|
import {Component} from "preact";
|
||||||
import {type Value} from "@holllo/webextension-storage";
|
|
||||||
import {log} from "../../utilities/exports.js";
|
import {log} from "../../utilities/exports.js";
|
||||||
import {
|
import {
|
||||||
type UsernameColorsData,
|
type UsernameColorsData,
|
||||||
type UsernameColor,
|
type UsernameColor,
|
||||||
Feature,
|
Feature,
|
||||||
|
createValueUsernamecolor,
|
||||||
fromStorage,
|
fromStorage,
|
||||||
} from "../../storage/exports.js";
|
} from "../../storage/exports.js";
|
||||||
import {Setting, type SettingProps} from "./index.js";
|
import {Setting, type SettingProps} from "./index.js";
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
previewChecked: "off" | "foreground" | "background";
|
previewChecked: "off" | "foreground" | "background";
|
||||||
usernameColors: Value<UsernameColorsData>;
|
usernameColors: UsernameColorsData;
|
||||||
|
usernameColorsToRemove: UsernameColorsData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class UsernameColorsSetting extends Component<SettingProps, State> {
|
export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
|
@ -21,6 +22,7 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
this.state = {
|
this.state = {
|
||||||
previewChecked: "off",
|
previewChecked: "off",
|
||||||
usernameColors: undefined!,
|
usernameColors: undefined!,
|
||||||
|
usernameColorsToRemove: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,35 +30,50 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
this.setState({usernameColors: await fromStorage(Feature.UsernameColors)});
|
this.setState({usernameColors: await fromStorage(Feature.UsernameColors)});
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewColor = () => {
|
addNewColor = async () => {
|
||||||
let id = 1;
|
let id = 1;
|
||||||
if (this.state.usernameColors.value.length > 0) {
|
if (this.state.usernameColors.length > 0) {
|
||||||
id =
|
id =
|
||||||
this.state.usernameColors.value.sort((a, b) => b.id - a.id)[0].id + 1;
|
this.state.usernameColors.sort((a, b) => b.value.id - a.value.id)[0]
|
||||||
|
.value.id + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newColor: UsernameColor = {
|
const newColor = await createValueUsernamecolor({
|
||||||
color: "",
|
color: "",
|
||||||
id,
|
id,
|
||||||
username: "",
|
username: "",
|
||||||
};
|
});
|
||||||
|
|
||||||
this.state.usernameColors.value.push(newColor);
|
this.state.usernameColors.push(newColor);
|
||||||
this.setState({
|
this.setState({
|
||||||
usernameColors: this.state.usernameColors,
|
usernameColors: this.state.usernameColors,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
removeColor = (targetId: number) => {
|
removeColor = async (targetId: number) => {
|
||||||
const targetIndex = this.state.usernameColors.value.findIndex(
|
const targetIndex = this.state.usernameColors.findIndex(
|
||||||
({id}) => id === targetId,
|
({value}) => value.id === targetId,
|
||||||
);
|
);
|
||||||
this.state.usernameColors.value.splice(targetIndex, 1);
|
const usernameColorsToRemove = this.state.usernameColorsToRemove;
|
||||||
this.setState({usernameColors: this.state.usernameColors});
|
usernameColorsToRemove.push(
|
||||||
|
...this.state.usernameColors.splice(targetIndex, 1),
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
usernameColors: this.state.usernameColors,
|
||||||
|
usernameColorsToRemove,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
saveChanges = async () => {
|
saveChanges = async () => {
|
||||||
await this.state.usernameColors.save();
|
for (const usernameColor of this.state.usernameColorsToRemove) {
|
||||||
|
await usernameColor.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const usernameColor of this.state.usernameColors) {
|
||||||
|
await usernameColor.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({usernameColorsToRemove: []});
|
||||||
};
|
};
|
||||||
|
|
||||||
togglePreview = async () => {
|
togglePreview = async () => {
|
||||||
|
@ -84,8 +101,8 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
onInput = (event: Event, id: number, key: "color" | "username") => {
|
onInput = (event: Event, id: number, key: "color" | "username") => {
|
||||||
const colorIndex = this.state.usernameColors.value.findIndex(
|
const colorIndex = this.state.usernameColors.findIndex(
|
||||||
(color) => color.id === id,
|
({value}) => value.id === id,
|
||||||
);
|
);
|
||||||
if (colorIndex === -1) {
|
if (colorIndex === -1) {
|
||||||
log(`Tried to edit unknown UsernameColor ID: ${id}`);
|
log(`Tried to edit unknown UsernameColor ID: ${id}`);
|
||||||
|
@ -93,7 +110,7 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const newValue = (event.target as HTMLInputElement).value;
|
const newValue = (event.target as HTMLInputElement).value;
|
||||||
this.state.usernameColors.value[colorIndex][key] = newValue;
|
this.state.usernameColors[colorIndex].value[key] = newValue;
|
||||||
this.setState({usernameColors: this.state.usernameColors});
|
this.setState({usernameColors: this.state.usernameColors});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,9 +120,9 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
usernameColors.value.sort((a, b) => a.id - b.id);
|
usernameColors.sort((a, b) => a.value.id - b.value.id);
|
||||||
|
|
||||||
const editors = usernameColors.value.map(({color, id, username}) => {
|
const editors = usernameColors.map(({value: {color, id, username}}) => {
|
||||||
const style: Record<string, string> = {};
|
const style: Record<string, string> = {};
|
||||||
if (previewChecked === "background") {
|
if (previewChecked === "background") {
|
||||||
style.backgroundColor = color;
|
style.backgroundColor = color;
|
||||||
|
@ -121,8 +138,8 @@ export class UsernameColorsSetting extends Component<SettingProps, State> {
|
||||||
this.onInput(event, id, "color");
|
this.onInput(event, id, "color");
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeHandler = () => {
|
const removeHandler = async () => {
|
||||||
this.removeColor(id);
|
await this.removeColor(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import {createValue, type Value} from "@holllo/webextension-storage";
|
import {createValue} from "@holllo/webextension-storage";
|
||||||
import browser from "webextension-polyfill";
|
import browser from "webextension-polyfill";
|
||||||
import {collectUserLabels} from "./user-label.js";
|
|
||||||
import {Data, Feature} from "./enums.js";
|
import {Data, Feature} from "./enums.js";
|
||||||
|
import {collectUsernameColors} from "./username-color.js";
|
||||||
|
import {collectUserLabels} from "./user-label.js";
|
||||||
|
|
||||||
export * from "./user-label.js";
|
|
||||||
export * from "./enums.js";
|
export * from "./enums.js";
|
||||||
|
export * from "./username-color.js";
|
||||||
|
export * from "./user-label.js";
|
||||||
|
|
||||||
export type HideVotesData = {
|
export type HideVotesData = {
|
||||||
otherComments: boolean;
|
otherComments: boolean;
|
||||||
|
@ -13,14 +15,6 @@ export type HideVotesData = {
|
||||||
ownTopics: boolean;
|
ownTopics: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UsernameColor = {
|
|
||||||
color: string;
|
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UsernameColorsData = UsernameColor[];
|
|
||||||
|
|
||||||
export const storageValues = {
|
export const storageValues = {
|
||||||
[Data.EnabledFeatures]: createValue({
|
[Data.EnabledFeatures]: createValue({
|
||||||
deserialize: (input) => new Set(JSON.parse(input) as Feature[]),
|
deserialize: (input) => new Set(JSON.parse(input) as Feature[]),
|
||||||
|
@ -63,13 +57,7 @@ export const storageValues = {
|
||||||
storage: browser.storage.sync,
|
storage: browser.storage.sync,
|
||||||
}),
|
}),
|
||||||
[Feature.UserLabels]: collectUserLabels(),
|
[Feature.UserLabels]: collectUserLabels(),
|
||||||
[Feature.UsernameColors]: createValue({
|
[Feature.UsernameColors]: collectUsernameColors(),
|
||||||
deserialize: (input) => JSON.parse(input) as UsernameColorsData,
|
|
||||||
serialize: (input) => JSON.stringify(Array.from(input)),
|
|
||||||
key: Feature.UsernameColors,
|
|
||||||
value: [],
|
|
||||||
storage: browser.storage.sync,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type StorageValues = typeof storageValues;
|
type StorageValues = typeof storageValues;
|
||||||
|
|
|
@ -38,10 +38,15 @@ await setup("Migrations", async (group) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Feature.UsernameColors: {
|
case `${Feature.UsernameColors}-4`: {
|
||||||
|
test.equals(value, '{"color":"red","id":4,"username":"Test"}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case `${Feature.UsernameColors}-18`: {
|
||||||
test.equals(
|
test.equals(
|
||||||
value,
|
value,
|
||||||
'[{"color":"red","id":4,"username":"Test"},{"color":"green","id":18,"username":"AnotherTest"}]',
|
'{"color":"green","id":18,"username":"AnotherTest"}',
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,13 @@ import {type Migration} from "@holllo/migration-helper";
|
||||||
import {
|
import {
|
||||||
Data,
|
Data,
|
||||||
Feature,
|
Feature,
|
||||||
|
createValueUsernamecolor,
|
||||||
createValueUserLabel,
|
createValueUserLabel,
|
||||||
fromStorage,
|
fromStorage,
|
||||||
|
saveUsernameColors,
|
||||||
saveUserLabels,
|
saveUserLabels,
|
||||||
} from "../exports.js";
|
} from "../exports.js";
|
||||||
import {v112DeserializeData, v112Sample, type V112Settings} from "./v1-1-2.js";
|
import {v112DeserializeData, type V112Settings} from "./v1-1-2.js";
|
||||||
|
|
||||||
export const migrations: Array<Migration<string>> = [
|
export const migrations: Array<Migration<string>> = [
|
||||||
{
|
{
|
||||||
|
@ -16,11 +18,18 @@ export const migrations: Array<Migration<string>> = [
|
||||||
data.data.userLabels = deserialized.userLabels;
|
data.data.userLabels = deserialized.userLabels;
|
||||||
data.data.usernameColors = deserialized.usernameColors;
|
data.data.usernameColors = deserialized.usernameColors;
|
||||||
|
|
||||||
|
const usernameColors = [];
|
||||||
const userLabels = [];
|
const userLabels = [];
|
||||||
|
|
||||||
|
for (const usernameColor of data.data.usernameColors) {
|
||||||
|
usernameColors.push(await createValueUsernamecolor(usernameColor));
|
||||||
|
}
|
||||||
|
|
||||||
for (const userLabel of data.data.userLabels) {
|
for (const userLabel of data.data.userLabels) {
|
||||||
userLabels.push(await createValueUserLabel(userLabel));
|
userLabels.push(await createValueUserLabel(userLabel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await saveUsernameColors(usernameColors);
|
||||||
await saveUserLabels(userLabels);
|
await saveUserLabels(userLabels);
|
||||||
|
|
||||||
const hideVotes = await fromStorage(Feature.HideVotes);
|
const hideVotes = await fromStorage(Feature.HideVotes);
|
||||||
|
@ -40,10 +49,6 @@ export const migrations: Array<Migration<string>> = [
|
||||||
version.value = "2.0.0";
|
version.value = "2.0.0";
|
||||||
await version.save();
|
await version.save();
|
||||||
|
|
||||||
const usernameColors = await fromStorage(Feature.UsernameColors);
|
|
||||||
usernameColors.value = data.data.usernameColors;
|
|
||||||
await usernameColors.save();
|
|
||||||
|
|
||||||
const enabledFeatures = await fromStorage(Data.EnabledFeatures);
|
const enabledFeatures = await fromStorage(Data.EnabledFeatures);
|
||||||
for (const [key, value] of Object.entries(data.features)) {
|
for (const [key, value] of Object.entries(data.features)) {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {type Value, createValue} from "@holllo/webextension-storage";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
import {Feature} from "./enums.js";
|
||||||
|
|
||||||
|
export type UsernameColor = {
|
||||||
|
color: string;
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UsernameColorsData = Array<Value<UsernameColor>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Value}-wrapped {@link UsernameColor}.
|
||||||
|
*/
|
||||||
|
export async function createValueUsernamecolor(
|
||||||
|
usernameColor: UsernameColor,
|
||||||
|
): Promise<UsernameColorsData[number]> {
|
||||||
|
return createValue<UsernameColor>({
|
||||||
|
deserialize: (input) => JSON.parse(input) as UsernameColor,
|
||||||
|
serialize: (input) => JSON.stringify(input),
|
||||||
|
key: `${Feature.UsernameColors}-${usernameColor.id}`,
|
||||||
|
value: usernameColor,
|
||||||
|
storage: browser.storage.sync,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all username colors from storage and combine them into a single array.
|
||||||
|
*/
|
||||||
|
export async function collectUsernameColors(): Promise<UsernameColorsData> {
|
||||||
|
const storage = await browser.storage.sync.get();
|
||||||
|
const userLabels = [];
|
||||||
|
for (const [key, value] of Object.entries(storage)) {
|
||||||
|
if (!key.startsWith(Feature.UsernameColors)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
userLabels.push(
|
||||||
|
await createValueUsernamecolor(
|
||||||
|
JSON.parse(value as string) as UsernameColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save all username colors to storage under individual keys.
|
||||||
|
*/
|
||||||
|
export async function saveUsernameColors(
|
||||||
|
usernameColors: UsernameColorsData,
|
||||||
|
): Promise<void> {
|
||||||
|
for (const usernameColor of usernameColors) {
|
||||||
|
await usernameColor.save();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue