1
Fork 0

Compare commits

..

2 Commits

9 changed files with 277 additions and 4 deletions

View File

@ -15,6 +15,7 @@
"@bauke/eslint-config": "^0.1.2", "@bauke/eslint-config": "^0.1.2",
"@bauke/prettier-config": "^0.1.2", "@bauke/prettier-config": "^0.1.2",
"@bauke/stylelint-config": "^0.1.2", "@bauke/stylelint-config": "^0.1.2",
"@holllo/test": "^0.2.1",
"@types/debounce": "^1.2.1", "@types/debounce": "^1.2.1",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/platform": "^1.3.4", "@types/platform": "^1.3.4",

View File

@ -1,5 +1,9 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies: dependencies:
'@holllo/migration-helper': '@holllo/migration-helper':
specifier: ^0.1.4 specifier: ^0.1.4
@ -36,6 +40,9 @@ devDependencies:
'@bauke/stylelint-config': '@bauke/stylelint-config':
specifier: ^0.1.2 specifier: ^0.1.2
version: 0.1.2(stylelint-config-standard-scss@6.1.0)(stylelint@15.9.0) version: 0.1.2(stylelint-config-standard-scss@6.1.0)(stylelint@15.9.0)
'@holllo/test':
specifier: ^0.2.1
version: 0.2.1
'@types/debounce': '@types/debounce':
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
@ -713,6 +720,10 @@ packages:
resolution: {integrity: sha512-4XMYlIOSFzTDWSFizKRSYUHWEfQBNZbPI/Tc5Qhef4lhxZBCAzJt1+RutB5TpCdR6sllJN+Dt5WblCmZXCaoLw==} resolution: {integrity: sha512-4XMYlIOSFzTDWSFizKRSYUHWEfQBNZbPI/Tc5Qhef4lhxZBCAzJt1+RutB5TpCdR6sllJN+Dt5WblCmZXCaoLw==}
dev: false dev: false
/@holllo/test@0.2.1:
resolution: {integrity: sha512-QlIvEqvuEfu8vapnwai8A+1TmZGkPObgU32VEXHBc3XEKhupHZRFB778oLPYlJVuSsi4TT99890iSR3nlvVwtQ==}
dev: true
/@holllo/webextension-storage@0.2.0(webextension-polyfill@0.10.0): /@holllo/webextension-storage@0.2.0(webextension-polyfill@0.10.0):
resolution: {integrity: sha512-WiSkkY/Jg3PhlHOH8eGvRBBtvZwHrJ0FD/LF8lNZAc3uaRdonF79o/Xt9CefYUjV6FSbHl/vsccXyAoitvkRIQ==} resolution: {integrity: sha512-WiSkkY/Jg3PhlHOH8eGvRBBtvZwHrJ0FD/LF8lNZAc3uaRdonF79o/Xt9CefYUjV6FSbHl/vsccXyAoitvkRIQ==}
peerDependencies: peerDependencies:

View File

@ -1,4 +1,6 @@
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import {migrations} from "../storage/migrations/migrations.js";
import {log} from "../utilities/logging.js";
if ($browser === "firefox") { if ($browser === "firefox") {
browser.browserAction.onClicked.addListener(openOptionsPage); browser.browserAction.onClicked.addListener(openOptionsPage);
@ -7,6 +9,14 @@ if ($browser === "firefox") {
} }
browser.runtime.onInstalled.addListener(async () => { browser.runtime.onInstalled.addListener(async () => {
const existingStorage = await browser.storage.sync.get();
if (existingStorage.version === "1.1.2") {
log("Running 1.1.2 to 2.0.0 data migration.", true);
await browser.storage.local.set({backup: JSON.stringify(existingStorage)});
await browser.storage.sync.clear();
await migrations[0].migrate(existingStorage);
}
if ($dev) { if ($dev) {
await openOptionsPage(); await openOptionsPage();
} }

View File

@ -11,7 +11,11 @@ import {type Feature, Data, fromStorage} from "../storage/common.js";
import {AppContext} from "./context.js"; import {AppContext} from "./context.js";
import {features} from "./features.js"; import {features} from "./features.js";
window.addEventListener("load", async () => { window.addEventListener("DOMContentLoaded", async () => {
if ($test) {
await import("../storage/migrations/migrations.test.js");
}
initializeGlobals(); initializeGlobals();
const manifest = browser.runtime.getManifest(); const manifest = browser.runtime.getManifest();

View File

@ -1,8 +1,8 @@
import {type Value, createValue} from "@holllo/webextension-storage"; import {createValue} from "@holllo/webextension-storage";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
export enum Feature { export enum Feature {
AnonymizeUsernames = "anonymize-users", AnonymizeUsernames = "anonymize-usernames",
Autocomplete = "autocomplete", Autocomplete = "autocomplete",
BackToTop = "back-to-top", BackToTop = "back-to-top",
Debug = "debug", Debug = "debug",
@ -18,6 +18,7 @@ export enum Data {
EnabledFeatures = "enabled-features", EnabledFeatures = "enabled-features",
KnownGroups = "known-groups", KnownGroups = "known-groups",
LatestActiveFeatureTab = "latest-active-feature-tab", LatestActiveFeatureTab = "latest-active-feature-tab",
Version = "data-version",
} }
export type HideVotesData = { export type HideVotesData = {
@ -111,6 +112,13 @@ export const storageValues = {
value: Feature.Debug, value: Feature.Debug,
storage: browser.storage.sync, storage: browser.storage.sync,
}), }),
[Data.Version]: createValue({
deserialize: (input) => JSON.parse(input) as string,
serialize: (input) => JSON.stringify(input),
key: Data.Version,
value: "2.0.0",
storage: browser.storage.sync,
}),
[Feature.HideVotes]: createValue({ [Feature.HideVotes]: createValue({
deserialize: (input) => JSON.parse(input) as HideVotesData, deserialize: (input) => JSON.parse(input) as HideVotesData,
serialize: (input) => JSON.stringify(input), serialize: (input) => JSON.stringify(input),

View File

@ -0,0 +1,71 @@
import browser from "webextension-polyfill";
import {setup} from "@holllo/test";
import {Data, Feature} from "../common.js";
import {migrations} from "./migrations.js";
import {v112Sample} from "./v1-1-2.js";
await setup("Migrations", async (group) => {
group.test("2.0.0", async (test) => {
await browser.storage.sync.clear();
await migrations[0].migrate(v112Sample);
const storage = await browser.storage.sync.get();
for (const [key, value] of Object.entries(storage)) {
switch (key) {
case Data.EnabledFeatures: {
test.equals(
value,
'["autocomplete","back-to-top","debug","hide-votes","jump-to-new-comment","markdown-toolbar","themed-logo","user-labels"]',
);
break;
}
case Data.KnownGroups: {
test.equals(value, '["~group","~group.subgroup","~test"]');
break;
}
case Data.Version: {
test.equals(value, '"2.0.0"');
break;
}
case Feature.HideVotes: {
test.equals(
value,
'{"otherComments":true,"otherTopics":true,"ownComments":true,"ownTopics":false}',
);
break;
}
case Feature.UsernameColors: {
test.equals(
value,
'[{"color":"red","id":4,"username":"Test"},{"color":"green","id":18,"username":"AnotherTest"}]',
);
break;
}
case `${Feature.UserLabels}-1`: {
test.equals(
value,
'{"color":"#ff00ff","id":1,"priority":0,"text":"Test Label","username":"Test"}',
);
break;
}
case `${Feature.UserLabels}-15`: {
test.equals(
value,
'{"id":15,"color":"var(--syntax-string-color)","priority":0,"text":"Another Label","username":"AnotherTest"}',
);
break;
}
default: {
console.log(key, JSON.stringify(value));
}
}
}
});
});

View File

@ -0,0 +1,52 @@
import {setup} from "@holllo/test";
import {type Migration} from "@holllo/migration-helper";
import browser from "webextension-polyfill";
import {Data, Feature, fromStorage, saveUserLabels} from "../common.js";
import {v112DeserializeData, v112Sample, type V112Settings} from "./v1-1-2.js";
export const migrations: Array<Migration<string>> = [
{
version: "2.0.0",
async migrate(data: V112Settings): Promise<void> {
const deserialized = v112DeserializeData(data);
data.data.userLabels = deserialized.userLabels;
data.data.usernameColors = deserialized.usernameColors;
await saveUserLabels(data.data.userLabels);
const hideVotes = await fromStorage(Feature.HideVotes);
hideVotes.value = {
otherComments: data.data.hideVotes.comments,
otherTopics: data.data.hideVotes.topics,
ownComments: data.data.hideVotes.ownComments,
ownTopics: data.data.hideVotes.ownTopics,
};
await hideVotes.save();
const knownGroups = await fromStorage(Data.KnownGroups);
knownGroups.value = new Set(data.data.knownGroups);
await knownGroups.save();
const version = await fromStorage(Data.Version);
version.value = "2.0.0";
await version.save();
const usernameColors = await fromStorage(Feature.UsernameColors);
usernameColors.value = data.data.usernameColors;
await usernameColors.save();
const enabledFeatures = await fromStorage(Data.EnabledFeatures);
for (const [key, value] of Object.entries(data.features)) {
if (value) {
const snakeCasedKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
if (Object.values(Feature).includes(snakeCasedKey as Feature)) {
enabledFeatures.value.add(snakeCasedKey as Feature);
} else {
throw new Error(`Unknown key: ${key}`);
}
}
}
await enabledFeatures.save();
},
},
];

View File

@ -0,0 +1,114 @@
export function v112DeserializeData(data: Record<string, any>): {
userLabels: V112Settings["data"]["userLabels"];
usernameColors: V112Settings["data"]["usernameColors"];
} {
const deserialized: ReturnType<typeof v112DeserializeData> = {
userLabels: [],
usernameColors: [],
};
for (const [key, value] of Object.entries(data)) {
if (key.startsWith("userLabel")) {
deserialized.userLabels.push(
value as (typeof deserialized)["userLabels"][number],
);
} else if (key.startsWith("usernameColor")) {
deserialized.usernameColors.push(
value as (typeof deserialized)["usernameColors"][number],
);
}
}
return deserialized;
}
export type V112Settings = {
[index: string]: any;
data: {
hideVotes: {
comments: boolean;
topics: boolean;
ownComments: boolean;
ownTopics: boolean;
};
knownGroups: string[];
latestActiveFeatureTab: string;
userLabels: Array<{
color: string;
id: number;
priority: number;
text: string;
username: string;
}>;
usernameColors: Array<{
color: string;
id: number;
username: string;
}>;
};
features: {
anonymizeUsernames: boolean;
autocomplete: boolean;
backToTop: boolean;
debug: boolean;
hideVotes: boolean;
jumpToNewComment: boolean;
markdownToolbar: boolean;
themedLogo: boolean;
userLabels: boolean;
usernameColors: boolean;
};
version: string;
};
export const v112Sample: V112Settings = {
data: {
hideVotes: {
comments: true,
ownComments: true,
ownTopics: false,
topics: true,
},
knownGroups: ["~group", "~group.subgroup", "~test"],
latestActiveFeatureTab: "userLabels",
userLabels: [],
usernameColors: [],
},
features: {
anonymizeUsernames: false,
autocomplete: true,
backToTop: true,
debug: true,
hideVotes: true,
jumpToNewComment: true,
markdownToolbar: true,
themedLogo: true,
userLabels: true,
usernameColors: false,
},
version: "1.1.2",
userLabel1: {
color: "#ff00ff",
id: 1,
priority: 0,
text: "Test Label",
username: "Test",
},
userLabel15: {
id: 15,
color: "var(--syntax-string-color)",
priority: 0,
text: "Another Label",
username: "AnotherTest",
},
usernameColor4: {
color: "red",
id: 4,
username: "Test",
},
usernameColor18: {
color: "green",
id: 18,
username: "AnotherTest",
},
};

View File

@ -1,3 +1,5 @@
import {type JSX} from "preact";
type Props = { type Props = {
class?: string; class?: string;
text: string; text: string;
@ -5,7 +7,7 @@ type Props = {
}; };
/** An `<a />` helper component with `target="_blank"` and `rel="noopener"`. */ /** An `<a />` helper component with `target="_blank"` and `rel="noopener"`. */
export function Link(props: Props): TRXComponent { export function Link(props: Props): JSX.Element {
return ( return (
<a class={props.class} href={props.url} target="_blank" rel="noopener"> <a class={props.class} href={props.url} target="_blank" rel="noopener">
{props.text} {props.text}