From 5fb151807e6efcc8c165399d13c56ff7b320d9a6 Mon Sep 17 00:00:00 2001 From: Bauke Date: Sat, 24 Jun 2023 14:00:27 +0200 Subject: [PATCH] Fix user labels storage issue. --- package.json | 3 ++ .../content-scripts/features/user-labels.tsx | 33 ++++++------ source/content-scripts/setup.tsx | 2 +- source/options/user-label-editor.tsx | 9 ++-- source/scss/user-label-editor.scss | 2 + source/storage/common.ts | 53 ++++++++++++++++--- 6 files changed, 72 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index cc0dfdf..a5e4901 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "xo": { "extends": "@bauke/eslint-config", "prettier": true, + "rules": { + "no-await-in-loop": "off" + }, "space": true } } diff --git a/source/content-scripts/features/user-labels.tsx b/source/content-scripts/features/user-labels.tsx index f2571ed..f0f3d5f 100644 --- a/source/content-scripts/features/user-labels.tsx +++ b/source/content-scripts/features/user-labels.tsx @@ -1,7 +1,6 @@ import debounce from "debounce"; import {Component, render} from "preact"; -import {type Value} from "@holllo/webextension-storage"; -import {type UserLabelsData} from "../../storage/common.js"; +import {type UserLabelsData, saveUserLabels} from "../../storage/common.js"; import { createElementFromString, isColorBright, @@ -13,7 +12,7 @@ import { type Props = { anonymizeUsernamesEnabled: boolean; - userLabels: Value; + userLabels: UserLabelsData; }; type State = { @@ -70,7 +69,7 @@ export class UserLabelsFeature extends Component { // Sort the labels by priority or alphabetically, so 2 labels with the same // priority will be sorted alphabetically. - const sortedLabels = userLabels.value.sort((a, b): number => { + const sortedLabels = userLabels.sort((a, b): number => { if (inTopicListing) { // If we're in the topic listing sort with highest priority first. if (a.priority !== b.priority) { @@ -97,7 +96,7 @@ export class UserLabelsFeature extends Component { const userLabels = sortedLabels.filter( (value) => - value.username === username && + value.username.toLowerCase() === username && (onlyID === undefined ? true : value.id === onlyID), ); @@ -192,9 +191,7 @@ export class UserLabelsFeature extends Component { if (this.state.target === target && !this.state.hidden) { this.hide(); } else { - const label = this.props.userLabels.value.find( - (value) => value.id === id, - ); + const label = this.props.userLabels.find((value) => value.id === id); if (label === undefined) { log( "User Labels: Tried to edit label with ID that could not be found.", @@ -252,11 +249,11 @@ export class UserLabelsFeature extends Component { // If no ID is present then save a new label otherwise edit the existing one. if (id === undefined) { let newId = 1; - if (userLabels.value.length > 0) { - newId = userLabels.value.sort((a, b) => b.id - a.id)[0].id + 1; + if (userLabels.length > 0) { + newId = userLabels.sort((a, b) => b.id - a.id)[0].id + 1; } - userLabels.value.push({ + userLabels.push({ color, id: newId, priority, @@ -266,9 +263,9 @@ export class UserLabelsFeature extends Component { this.addLabelsToUsernames(querySelectorAll(".link-user"), newId); } else { - const index = userLabels.value.findIndex((value) => value.id === id); - userLabels.value.splice(index, 1); - userLabels.value.push({ + const index = userLabels.findIndex((value) => value.id === id); + userLabels.splice(index, 1); + userLabels.push({ id, color, priority, @@ -289,7 +286,7 @@ export class UserLabelsFeature extends Component { } } - await userLabels.save(); + await saveUserLabels(userLabels); this.props.userLabels = userLabels; this.hide(); }; @@ -303,7 +300,7 @@ export class UserLabelsFeature extends Component { } const {userLabels} = this.props; - const index = userLabels.value.findIndex((value) => value.id === id); + const index = userLabels.findIndex((value) => value.id === id); if (index === undefined) { log( `User Labels: Tried to remove label with ID ${id} that could not be found.`, @@ -316,8 +313,8 @@ export class UserLabelsFeature extends Component { value.remove(); } - userLabels.value.splice(index, 1); - await userLabels.save(); + userLabels.splice(index, 1); + await saveUserLabels(userLabels); this.props.userLabels = userLabels; this.hide(); }; diff --git a/source/content-scripts/setup.tsx b/source/content-scripts/setup.tsx index e24063b..1dfcb01 100644 --- a/source/content-scripts/setup.tsx +++ b/source/content-scripts/setup.tsx @@ -102,7 +102,7 @@ async function initialize() { ); } diff --git a/source/options/user-label-editor.tsx b/source/options/user-label-editor.tsx index 4f84b92..88d1044 100644 --- a/source/options/user-label-editor.tsx +++ b/source/options/user-label-editor.tsx @@ -10,6 +10,7 @@ import { type UserLabel, fromStorage, Feature, + saveUserLabels, } from "../storage/common.js"; import "../scss/index.scss"; import "../scss/user-label-editor.scss"; @@ -21,7 +22,7 @@ window.addEventListener("load", async () => { }); type Props = { - userLabels: Value; + userLabels: UserLabelsData; }; type State = { @@ -37,7 +38,7 @@ class App extends Component { this.state = { hasUnsavedChanges: false, newLabelUsername: "", - userLabels: props.userLabels.value, + userLabels: props.userLabels, }; } @@ -101,8 +102,8 @@ class App extends Component { }; saveUserLabels = () => { - this.props.userLabels.value = this.state.userLabels; - void this.props.userLabels.save(); + this.props.userLabels = this.state.userLabels; + void saveUserLabels(this.props.userLabels); this.setState({hasUnsavedChanges: false}); }; diff --git a/source/scss/user-label-editor.scss b/source/scss/user-label-editor.scss index 3ce83f4..49bcde0 100644 --- a/source/scss/user-label-editor.scss +++ b/source/scss/user-label-editor.scss @@ -33,6 +33,7 @@ .group { border: 1px solid var(--blue); + overflow: scroll; padding: 16px; h2 { @@ -40,6 +41,7 @@ display: flex; gap: 8px; margin-bottom: 16px; + overflow: scroll; } input { diff --git a/source/storage/common.ts b/source/storage/common.ts index d24a7b6..2b1361e 100644 --- a/source/storage/common.ts +++ b/source/storage/common.ts @@ -45,6 +45,50 @@ export type UsernameColor = { export type UsernameColorsData = UsernameColor[]; +/** + * Get all user labels from storage and combine them into a single array. + */ +export async function collectUserLabels(): Promise { + const storage = await browser.storage.sync.get(); + const userLabels = []; + for (const [key, value] of Object.entries(storage)) { + if (!key.startsWith(Feature.UserLabels)) { + continue; + } + + userLabels.push(JSON.parse(value as string) as UserLabel); + } + + return userLabels; +} + +/** + * Save all user labels to storage under individual keys. + * + * They are stored under individual keys so that we don't run into storage quota + * limits. If it was stored under a single key we would only be able to fit + * around 80-100 labels before hitting the limit. + * @param userLabels The user labels array to save. + */ +export async function saveUserLabels( + userLabels: UserLabelsData, +): Promise { + const storage = await browser.storage.sync.get(); + for (const key of Object.keys(storage)) { + if (!key.startsWith(Feature.UserLabels)) { + continue; + } + + await browser.storage.sync.remove(key); + } + + for (const label of userLabels) { + await browser.storage.sync.set({ + [`${Feature.UserLabels}-${label.id}`]: JSON.stringify(label), + }); + } +} + export const storageValues = { [Data.EnabledFeatures]: createValue({ deserialize: (input) => new Set(JSON.parse(input) as Feature[]), @@ -79,13 +123,8 @@ export const storageValues = { }, storage: browser.storage.sync, }), - [Feature.UserLabels]: createValue({ - deserialize: (input) => JSON.parse(input) as UserLabelsData, - serialize: (input) => JSON.stringify(Array.from(input)), - key: Feature.UserLabels, - value: [], - storage: browser.storage.sync, - }), + // eslint-disable-next-line unicorn/prefer-top-level-await + [Feature.UserLabels]: collectUserLabels(), [Feature.UsernameColors]: createValue({ deserialize: (input) => JSON.parse(input) as UsernameColorsData, serialize: (input) => JSON.stringify(Array.from(input)),