From d5bdf1cb90583a475670e9ce03dd9605dd392204 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:17:48 +0200 Subject: [PATCH 1/7] Add settings.json-backed KV string storage Fixes #3461, #3443 --- public/script.js | 21 ++-- public/scripts/RossAscends-mods.js | 51 +++++----- public/scripts/chats.js | 9 +- public/scripts/extensions.js | 9 +- public/scripts/extensions/assets/index.js | 5 +- public/scripts/extensions/regex/index.js | 5 +- public/scripts/f-localStorage.js | 27 ----- public/scripts/group-chats.js | 9 +- public/scripts/openai.js | 5 +- public/scripts/personas.js | 11 +- public/scripts/power-user.js | 5 +- public/scripts/slash-commands.js | 5 +- public/scripts/st-context.js | 2 + public/scripts/textgen-models.js | 7 +- public/scripts/util/AccountStorage.js | 116 ++++++++++++++++++++++ public/scripts/world-info.js | 17 ++-- 16 files changed, 205 insertions(+), 99 deletions(-) delete mode 100644 public/scripts/f-localStorage.js create mode 100644 public/scripts/util/AccountStorage.js diff --git a/public/script.js b/public/script.js index e848b2cd7..ca0d0f885 100644 --- a/public/script.js +++ b/public/script.js @@ -270,6 +270,7 @@ import { initBulkEdit } from './scripts/bulk-edit.js'; import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; import { getContext } from './scripts/st-context.js'; import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js'; +import { accountStorage } from './scripts/util/AccountStorage.js'; // API OBJECT FOR EXTERNAL WIRING globalThis.SillyTavern = { @@ -419,7 +420,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { const entityId = getCurrentEntityId(); const warningShownKey = `mediaWarningShown:${entityId}`; - if (localStorage.getItem(warningShownKey) === null) { + if (accountStorage.getItem(warningShownKey) === null) { const warningToast = toastr.warning( t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, t`External media has been blocked`, @@ -430,7 +431,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { }, ); - localStorage.setItem(warningShownKey, 'true'); + accountStorage.setItem(warningShownKey, 'true'); } } }); @@ -1490,7 +1491,7 @@ export async function printCharacters(fullRefresh = false) { $('#rm_print_characters_pagination').pagination({ dataSource: entities, - pageSize: Number(localStorage.getItem(storageKey)) || per_page_default, + pageSize: Number(accountStorage.getItem(storageKey)) || per_page_default, sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], pageRange: 1, pageNumber: saveCharactersPage || 1, @@ -1534,7 +1535,7 @@ export async function printCharacters(fullRefresh = false) { eventSource.emit(event_types.CHARACTER_PAGE_LOADED); }, afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); + accountStorage.setItem(storageKey, e.target.value); }, afterPaging: function (e) { saveCharactersPage = e; @@ -6902,10 +6903,11 @@ export async function getSettings() { $('#your_name').val(name1); } + accountStorage.init(settings?.accountStorage); await setUserControls(data.enable_accounts); // Allow subscribers to mutate settings - eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); + await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); //Load KoboldAI settings koboldai_setting_names = data.koboldai_setting_names; @@ -7094,6 +7096,7 @@ export async function saveSettings(loopCounter = 0) { url: '/api/settings/save', data: JSON.stringify({ firstRun: firstRun, + accountStorage: accountStorage.state, currentVersion: currentVersion, username: name1, active_character: active_character, @@ -7549,7 +7552,7 @@ export function select_rm_info(type, charId, previousCharId = null) { } try { - const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; + const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default; const page = Math.floor(charIndex / perPage) + 1; const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`; $('#rm_print_characters_pagination').pagination('go', page); @@ -7581,7 +7584,7 @@ export function select_rm_info(type, charId, previousCharId = null) { return; } - const perPage = Number(localStorage.getItem('Characters_PerPage')) || per_page_default; + const perPage = Number(accountStorage.getItem('Characters_PerPage')) || per_page_default; const page = Math.floor(charIndex / perPage) + 1; $('#rm_print_characters_pagination').pagination('go', page); const selector = `#rm_print_characters_block [grid="${charId}"]`; @@ -9650,8 +9653,8 @@ function addDebugFunctions() { }); registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => { - localStorage.setItem('RegenerateWithCtrlEnter', localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); - toastr.info('Regenerate warning is now ' + (localStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); + accountStorage.setItem('RegenerateWithCtrlEnter', accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); + toastr.info('Regenerate warning is now ' + (accountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); }); registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => { diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index f76b4e95f..c2a5e2774 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -27,7 +27,6 @@ import { send_on_enter_options, } from './power-user.js'; -import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js'; import { selected_group, is_group_generating, openGroupById } from './group-chats.js'; import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js'; import { @@ -41,6 +40,7 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex import { debounce_timeout } from './constants.js'; import { Popup } from './popup.js'; +import { accountStorage } from './util/AccountStorage.js'; var RPanelPin = document.getElementById('rm_button_panel_pin'); var LPanelPin = document.getElementById('lm_button_panel_pin'); @@ -409,19 +409,19 @@ function RA_autoconnect(PrevApi) { function OpenNavPanels() { if (!isMobile()) { //auto-open R nav if locked and previously open - if (LoadLocalBool('NavLockOn') == true && LoadLocalBool('NavOpened') == true) { + if (accountStorage.getItem('NavLockOn') == 'true' && accountStorage.getItem('NavOpened') == 'true') { //console.log("RA -- clicking right nav to open"); $('#rightNavDrawerIcon').click(); } //auto-open L nav if locked and previously open - if (LoadLocalBool('LNavLockOn') == true && LoadLocalBool('LNavOpened') == true) { + if (accountStorage.getItem('LNavLockOn') == 'true' && accountStorage.getItem('LNavOpened') == 'true') { console.debug('RA -- clicking left nav to open'); $('#leftNavDrawerIcon').click(); } //auto-open WI if locked and previously open - if (LoadLocalBool('WINavLockOn') == true && LoadLocalBool('WINavOpened') == true) { + if (accountStorage.getItem('WINavLockOn') == 'true' && accountStorage.getItem('WINavOpened') == 'true') { console.debug('RA -- clicking WI to open'); $('#WIDrawerIcon').click(); } @@ -434,7 +434,7 @@ function restoreUserInput() { return; } - const userInput = LoadLocal('userInput'); + const userInput = localStorage.getItem('userInput'); if (userInput) { $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); } @@ -442,7 +442,8 @@ function restoreUserInput() { function saveUserInput() { const userInput = String($('#send_textarea').val()); - SaveLocal('userInput', userInput); + localStorage.setItem('userInput', userInput); + console.debug('User Input -- ', userInput); } const saveUserInputDebounced = debounce(saveUserInput); @@ -739,7 +740,7 @@ export function initRossMods() { //toggle pin class when lock toggle clicked $(RPanelPin).on('click', function () { - SaveLocal('NavLockOn', $(RPanelPin).prop('checked')); + accountStorage.setItem('NavLockOn', $(RPanelPin).prop('checked')); if ($(RPanelPin).prop('checked') == true) { //console.log('adding pin class to right nav'); $(RightNavPanel).addClass('pinnedOpen'); @@ -757,7 +758,7 @@ export function initRossMods() { } }); $(LPanelPin).on('click', function () { - SaveLocal('LNavLockOn', $(LPanelPin).prop('checked')); + accountStorage.setItem('LNavLockOn', $(LPanelPin).prop('checked')); if ($(LPanelPin).prop('checked') == true) { //console.log('adding pin class to Left nav'); $(LeftNavPanel).addClass('pinnedOpen'); @@ -776,7 +777,7 @@ export function initRossMods() { }); $(WIPanelPin).on('click', function () { - SaveLocal('WINavLockOn', $(WIPanelPin).prop('checked')); + accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked')); if ($(WIPanelPin).prop('checked') == true) { console.debug('adding pin class to WI'); $(WorldInfo).addClass('pinnedOpen'); @@ -796,8 +797,8 @@ export function initRossMods() { }); // read the state of right Nav Lock and apply to rightnav classlist - $(RPanelPin).prop('checked', LoadLocalBool('NavLockOn')); - if (LoadLocalBool('NavLockOn') == true) { + $(RPanelPin).prop('checked', accountStorage.getItem('NavLockOn') == 'true'); + if (accountStorage.getItem('NavLockOn') == 'true') { //console.log('setting pin class via local var'); $(RightNavPanel).addClass('pinnedOpen'); $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); @@ -808,8 +809,8 @@ export function initRossMods() { $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); } // read the state of left Nav Lock and apply to leftnav classlist - $(LPanelPin).prop('checked', LoadLocalBool('LNavLockOn')); - if (LoadLocalBool('LNavLockOn') == true) { + $(LPanelPin).prop('checked', accountStorage.getItem('LNavLockOn') === 'true'); + if (accountStorage.getItem('LNavLockOn') == 'true') { //console.log('setting pin class via local var'); $(LeftNavPanel).addClass('pinnedOpen'); $(LeftNavDrawerIcon).addClass('drawerPinnedOpen'); @@ -821,8 +822,8 @@ export function initRossMods() { } // read the state of left Nav Lock and apply to leftnav classlist - $(WIPanelPin).prop('checked', LoadLocalBool('WINavLockOn')); - if (LoadLocalBool('WINavLockOn') == true) { + $(WIPanelPin).prop('checked', accountStorage.getItem('WINavLockOn') === 'true'); + if (accountStorage.getItem('WINavLockOn') == 'true') { //console.log('setting pin class via local var'); $(WorldInfo).addClass('pinnedOpen'); $(WIDrawerIcon).addClass('drawerPinnedOpen'); @@ -837,22 +838,22 @@ export function initRossMods() { //save state of Right nav being open or closed $('#rightNavDrawerIcon').on('click', function () { if (!$('#rightNavDrawerIcon').hasClass('openIcon')) { - SaveLocal('NavOpened', 'true'); - } else { SaveLocal('NavOpened', 'false'); } + accountStorage.setItem('NavOpened', 'true'); + } else { accountStorage.setItem('NavOpened', 'false'); } }); //save state of Left nav being open or closed $('#leftNavDrawerIcon').on('click', function () { if (!$('#leftNavDrawerIcon').hasClass('openIcon')) { - SaveLocal('LNavOpened', 'true'); - } else { SaveLocal('LNavOpened', 'false'); } + accountStorage.setItem('LNavOpened', 'true'); + } else { accountStorage.setItem('LNavOpened', 'false'); } }); //save state of Left nav being open or closed $('#WorldInfo').on('click', function () { if (!$('#WorldInfo').hasClass('openIcon')) { - SaveLocal('WINavOpened', 'true'); - } else { SaveLocal('WINavOpened', 'false'); } + accountStorage.setItem('WINavOpened', 'true'); + } else { accountStorage.setItem('WINavOpened', 'false'); } }); var chatbarInFocus = false; @@ -868,8 +869,8 @@ export function initRossMods() { OpenNavPanels(); }, 300); - $(SelectedCharacterTab).click(function () { SaveLocal('SelectedNavTab', 'rm_button_selected_ch'); }); - $('#rm_button_characters').click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); }); + $(SelectedCharacterTab).click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); }); + $('#rm_button_characters').click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_characters'); }); // when a char is selected from the list, save them as the auto-load character for next page load @@ -1077,7 +1078,7 @@ export function initRossMods() { } else if (is_send_press == false) { const skipConfirmKey = 'RegenerateWithCtrlEnter'; - const skipConfirm = LoadLocalBool(skipConfirmKey); + const skipConfirm = accountStorage.getItem(skipConfirmKey) === 'true'; function doRegenerate() { console.debug('Regenerating with Ctrl+Enter'); $('#option_regenerate').trigger('click'); @@ -1097,7 +1098,7 @@ export function initRossMods() { return; } - SaveLocal(skipConfirmKey, regenerateWithCtrlEnter); + accountStorage.setItem(skipConfirmKey, String(regenerateWithCtrlEnter)); doRegenerate(); } return; diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 32c8e93e5..042030f2c 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -45,6 +45,7 @@ import { DragAndDropHandler } from './dragdrop.js'; import { renderTemplateAsync } from './templates.js'; import { t } from './i18n.js'; import { humanizedDateTime } from './RossAscends-mods.js'; +import { accountStorage } from './util/AccountStorage.js'; /** * @typedef {Object} FileAttachment @@ -1078,8 +1079,8 @@ async function openAttachmentManager() { renderAttachments(); }); - let sortField = localStorage.getItem('DataBank_sortField') || 'created'; - let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc'; + let sortField = accountStorage.getItem('DataBank_sortField') || 'created'; + let sortOrder = accountStorage.getItem('DataBank_sortOrder') || 'desc'; let filterString = ''; const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {})); @@ -1095,8 +1096,8 @@ async function openAttachmentManager() { sortField = this.selectedOptions[0].dataset.sortField; sortOrder = this.selectedOptions[0].dataset.sortOrder; - localStorage.setItem('DataBank_sortField', sortField); - localStorage.setItem('DataBank_sortOrder', sortOrder); + accountStorage.setItem('DataBank_sortField', sortField); + accountStorage.setItem('DataBank_sortOrder', sortOrder); renderAttachments(); }); function handleBulkAction(action) { diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index c0ba05067..83f8dfe48 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -9,6 +9,7 @@ import { getContext } from './st-context.js'; import { isAdmin } from './user.js'; import { t } from './i18n.js'; import { debounce_timeout } from './constants.js'; +import { accountStorage } from './util/AccountStorage.js'; export { getContext, @@ -714,7 +715,7 @@ async function showExtensionsDetails() { htmlExternal.append(htmlLoading); const sortOrderKey = 'extensions_sortByName'; - const sortByName = localStorage.getItem(sortOrderKey) === 'true'; + const sortByName = accountStorage.getItem(sortOrderKey) === 'true'; const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder; const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData); @@ -745,7 +746,7 @@ async function showExtensionsDetails() { text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`, action: async () => { abortController.abort(); - localStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); + accountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); await showExtensionsDetails(); }, }; @@ -1153,11 +1154,11 @@ async function checkForExtensionUpdates(force) { const currentDate = new Date().toDateString(); // Don't nag more than once a day - if (localStorage.getItem(STORAGE_NAG_KEY) === currentDate) { + if (accountStorage.getItem(STORAGE_NAG_KEY) === currentDate) { return; } - localStorage.setItem(STORAGE_NAG_KEY, currentDate); + accountStorage.setItem(STORAGE_NAG_KEY, currentDate); } const isCurrentUserAdmin = isAdmin(); diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index d491e8aaf..a9c48f318 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -8,6 +8,7 @@ import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; import { executeSlashCommands } from '../../slash-commands.js'; +import { accountStorage } from '../../util/AccountStorage.js'; import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; export { MODULE_NAME }; @@ -432,14 +433,14 @@ jQuery(async () => { connectButton.on('click', async function () { const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; - const skipConfirm = localStorage.getItem(rememberKey) === 'true'; + const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `Are you sure you want to connect to the following url?${url}`, { customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], onClose: popup => { if (popup.result) { const rememberValue = popup.inputResults.get('assets-remember'); - localStorage.setItem(rememberKey, String(rememberValue)); + accountStorage.setItem(rememberKey, String(rememberValue)); } }, }); diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js index 8ac6ae6d6..5166befea 100644 --- a/public/scripts/extensions/regex/index.js +++ b/public/scripts/extensions/regex/index.js @@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js'; import { t } from '../../i18n.js'; +import { accountStorage } from '../../util/AccountStorage.js'; /** * @typedef {object} RegexScript @@ -440,8 +441,8 @@ async function checkEmbeddedRegexScripts() { if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) { const checkKey = `AlertRegex_${characters[chid].avatar}`; - if (!localStorage.getItem(checkKey)) { - localStorage.setItem(checkKey, 'true'); + if (!accountStorage.getItem(checkKey)) { + accountStorage.setItem(checkKey, 'true'); const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); diff --git a/public/scripts/f-localStorage.js b/public/scripts/f-localStorage.js deleted file mode 100644 index adcb3af4e..000000000 --- a/public/scripts/f-localStorage.js +++ /dev/null @@ -1,27 +0,0 @@ -////////////////// LOCAL STORAGE HANDLING ///////////////////// - -export function SaveLocal(target, val) { - localStorage.setItem(target, val); - console.debug('SaveLocal -- ' + target + ' : ' + val); -} -export function LoadLocal(target) { - console.debug('LoadLocal -- ' + target); - return localStorage.getItem(target); - -} -export function LoadLocalBool(target) { - let result = localStorage.getItem(target) === 'true'; - return result; -} -export function CheckLocal() { - console.log('----------local storage---------'); - var i; - for (i = 0; i < localStorage.length; i++) { - console.log(localStorage.key(i) + ' : ' + localStorage.getItem(localStorage.key(i))); - } - console.log('------------------------------'); -} - -export function ClearLocal() { localStorage.clear(); console.log('Removed All Local Storage'); } - -///////////////////////////////////////////////////////////////////////// diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 5bf675fa2..54f19e90e 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -78,6 +78,7 @@ import { FILTER_TYPES, FilterHelper } from './filters.js'; import { isExternalMediaAllowed } from './chats.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { t } from './i18n.js'; +import { accountStorage } from './util/AccountStorage.js'; export { selected_group, @@ -1309,10 +1310,10 @@ function printGroupCandidates() { formatNavigator: PAGINATION_TEMPLATE, showNavigator: true, showSizeChanger: true, - pageSize: Number(localStorage.getItem(storageKey)) || 5, + pageSize: Number(accountStorage.getItem(storageKey)) || 5, sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); + accountStorage.setItem(storageKey, e.target.value); }, callback: function (data) { $('#rm_group_add_members').empty(); @@ -1336,10 +1337,10 @@ function printGroupMembers() { formatNavigator: PAGINATION_TEMPLATE, showNavigator: true, showSizeChanger: true, - pageSize: Number(localStorage.getItem(storageKey)) || 5, + pageSize: Number(accountStorage.getItem(storageKey)) || 5, sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); + accountStorage.setItem(storageKey, e.target.value); }, callback: function (data) { $('.rm_group_members').empty(); diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 3eb068f16..8248dff25 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -73,6 +73,7 @@ import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js import { Popup, POPUP_RESULT } from './popup.js'; import { t } from './i18n.js'; import { ToolManager } from './tool-calling.js'; +import { accountStorage } from './util/AccountStorage.js'; export { openai_messages_count, @@ -414,7 +415,7 @@ async function validateReverseProxy() { throw err; } const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`; - const skipConfirm = localStorage.getItem(rememberKey) === 'true'; + const skipConfirm = accountStorage.getItem(rememberKey) === 'true'; const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) })); @@ -425,7 +426,7 @@ async function validateReverseProxy() { throw new Error('Proxy connection denied.'); } - localStorage.setItem(rememberKey, String(true)); + accountStorage.setItem(rememberKey, String(true)); } /** diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 69558fb7b..b90ebd7b5 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -25,6 +25,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { t } from './i18n.js'; import { openWorldInfoEditor, world_names } from './world-info.js'; import { renderTemplateAsync } from './templates.js'; +import { accountStorage } from './util/AccountStorage.js'; let savePersonasPage = 0; const GRID_STORAGE_KEY = 'Personas_GridView'; @@ -34,7 +35,7 @@ export let user_avatar = ''; export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick)); function switchPersonaGridView() { - const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; + const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true'; $('#user_avatar_block').toggleClass('gridView', state); } @@ -182,7 +183,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { const storageKey = 'Personas_PerPage'; const listId = '#user_avatar_block'; - const perPage = Number(localStorage.getItem(storageKey)) || 5; + const perPage = Number(accountStorage.getItem(storageKey)) || 5; $('#persona_pagination_container').pagination({ dataSource: entities, @@ -205,7 +206,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { highlightSelectedAvatar(); }, afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); + accountStorage.setItem(storageKey, e.target.value); }, afterPaging: function (e) { savePersonasPage = e; @@ -1132,8 +1133,8 @@ export function initPersonas() { saveSettingsDebounced(); }); $('#persona_grid_toggle').on('click', () => { - const state = localStorage.getItem(GRID_STORAGE_KEY) === 'true'; - localStorage.setItem(GRID_STORAGE_KEY, String(!state)); + const state = accountStorage.getItem(GRID_STORAGE_KEY) === 'true'; + accountStorage.setItem(GRID_STORAGE_KEY, String(!state)); switchPersonaGridView(); }); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 66b298954..8e71ea76a 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -54,6 +54,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom import { POPUP_TYPE, callGenericPopup } from './popup.js'; import { loadSystemPrompts } from './sysprompt.js'; import { fuzzySearchCategories } from './filters.js'; +import { accountStorage } from './util/AccountStorage.js'; export { loadPowerUserSettings, @@ -2019,7 +2020,7 @@ export function renderStoryString(params) { */ function validateStoryString(storyString, params) { /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ - const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; + const cache = JSON.parse(accountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; const hash = getStringHash(storyString); @@ -2056,7 +2057,7 @@ function validateStoryString(storyString, params) { toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); } - localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); + accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); } diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index f22a5f2b0..42eb20f4a 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -75,6 +75,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; +import { accountStorage } from './util/AccountStorage.js'; export { executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, }; @@ -3606,9 +3607,9 @@ export async function sendMessageAs(args, text) { if (!name) { const namelessWarningKey = 'sendAsNamelessWarningShown'; - if (localStorage.getItem(namelessWarningKey) !== 'true') { + if (accountStorage.getItem(namelessWarningKey) !== 'true') { toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); - localStorage.setItem(namelessWarningKey, 'true'); + accountStorage.setItem(namelessWarningKey, 'true'); } name = name2; } diff --git a/public/scripts/st-context.js b/public/scripts/st-context.js index 3fb99bd61..a9a604032 100644 --- a/public/scripts/st-context.js +++ b/public/scripts/st-context.js @@ -68,12 +68,14 @@ import { tag_map, tags } from './tags.js'; import { textgenerationwebui_settings } from './textgen-settings.js'; import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; import { ToolManager } from './tool-calling.js'; +import { accountStorage } from './util/AccountStorage.js'; import { timestampToMoment, uuidv4 } from './utils.js'; import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; export function getContext() { return { + accountStorage, chat, characters, groups, diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index 9523cfba5..962df1a5d 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -6,6 +6,7 @@ import { tokenizers } from './tokenizers.js'; import { renderTemplateAsync } from './templates.js'; import { POPUP_TYPE, callGenericPopup } from './popup.js'; import { t } from './i18n.js'; +import { accountStorage } from './util/AccountStorage.js'; let mancerModels = []; let togetherModels = []; @@ -336,7 +337,7 @@ export async function loadFeatherlessModels(data) { populateClassSelection(data); // Retrieve the stored number of items per page or default to 10 - const perPage = Number(localStorage.getItem(storageKey)) || 10; + const perPage = Number(accountStorage.getItem(storageKey)) || 10; // Initialize pagination applyFiltersAndSort(); @@ -412,7 +413,7 @@ export async function loadFeatherlessModels(data) { }, afterSizeSelectorChange: function (e) { const newPerPage = e.target.value; - localStorage.setItem('Models_PerPage', newPerPage); + accountStorage.setItem(storageKey, newPerPage); setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number }, }); @@ -513,7 +514,7 @@ export async function loadFeatherlessModels(data) { const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model); featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1; - setupPagination(filteredModels, Number(localStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); + setupPagination(filteredModels, Number(accountStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); } // Required to keep the /model command function diff --git a/public/scripts/util/AccountStorage.js b/public/scripts/util/AccountStorage.js new file mode 100644 index 000000000..fe163813d --- /dev/null +++ b/public/scripts/util/AccountStorage.js @@ -0,0 +1,116 @@ +import { saveSettingsDebounced } from '../../script.js'; + +const MIGRATED_MARKER = '__migrated'; +const MIGRATABLE_KEYS = [ + /^AlertRegex_/, + /^AlertWI_/, + /^Assets_SkipConfirm_/, + /^Characters_PerPage$/, + /^DataBank_sortField$/, + /^DataBank_sortOrder$/, + /^extension_update_nag$/, + /^extensions_sortByName$/, + /^FeatherlessModels_PerPage$/, + /^GroupMembers_PerPage$/, + /^GroupCandidates_PerPage$/, + /^LNavLockOn$/, + /^LNavOpened$/, + /^mediaWarningShown:/, + /^NavLockOn$/, + /^NavOpened$/, + /^Personas_PerPage$/, + /^Personas_GridView$/, + /^Proxy_SkipConfirm_/, + /^RegenerateWithCtrlEnter$/, + /^SelectedNavTab$/, + /^sendAsNamelessWarningShown$/, + /^StoryStringValidationCache$/, + /^WINavOpened$/, + /^WI_PerPage$/, + /^world_info_sort_order$/, +]; + +/** + * Provides access to account storage of arbitrary key-value pairs. + */ +class AccountStorage { + constructor() { + this.state = {}; + this.ready = false; + } + + #migrateLocalStorage() { + for (let i = 0; i < globalThis.localStorage.length; i++) { + const key = globalThis.localStorage.key(i); + const value = globalThis.localStorage.getItem(key); + + if (MIGRATABLE_KEYS.some(k => k.test(key))) { + this.state[key] = value; + globalThis.localStorage.removeItem(key); + } + } + } + + /** + * Initialize the account storage. + * @param {Object} state Initial state + */ + init(state) { + if (state && typeof state === 'object') { + this.state = Object.assign(this.state, state); + } + + if (!Object.hasOwn(this.state, MIGRATED_MARKER)) { + this.#migrateLocalStorage(); + this.state[MIGRATED_MARKER] = 1; + saveSettingsDebounced(); + } + + this.ready = true; + } + + /** + * Get the value of a key in account storage. + * @param {string} key Key to get + * @returns {string|null} Value of the key + */ + getItem(key) { + if (!this.ready) { + console.warn(`AccountStorage not ready (trying to read from ${key})`); + } + + return Object.hasOwn(this.state, key) ? String(this.state[key]) : null; + } + + /** + * Set a key in account storage. + * @param {string} key Key to set + * @param {string} value Value to set + */ + setItem(key, value) { + if (!this.ready) { + console.warn(`AccountStorage not ready (trying to write to ${key})`); + } + + this.state[key] = String(value); + saveSettingsDebounced(); + } + + /** + * Remove a key from account storage. + * @param {string} key Key to remove + */ + removeItem(key) { + if (!this.ready) { + console.warn(`AccountStorage not ready (trying to remove ${key})`); + } + + delete this.state[key]; + saveSettingsDebounced(); + } +} + +/** + * Account storage instance. + */ +export const accountStorage = new AccountStorage(); diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 2dfb4c869..57866be99 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -21,6 +21,7 @@ import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; import { StructuredCloneMap } from './util/StructuredCloneMap.js'; import { renderTemplateAsync } from './templates.js'; import { t } from './i18n.js'; +import { accountStorage } from './util/AccountStorage.js'; export const world_info_insertion_strategy = { evenly: 0, @@ -872,7 +873,7 @@ export function setWorldInfoSettings(settings, data) { $('#world_editor_select').append(``); }); - $('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0'); + $('#world_info_sort_order').val(accountStorage.getItem(SORT_ORDER_KEY) || '0'); $('#world_info').trigger('change'); $('#world_editor_select').trigger('change'); @@ -1947,13 +1948,13 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl if (typeof navigation === 'number' && Number(navigation) >= 0) { const data = getDataArray(); const uidIndex = data.findIndex(x => x.uid === navigation); - const perPage = Number(localStorage.getItem(storageKey)) || perPageDefault; + const perPage = Number(accountStorage.getItem(storageKey)) || perPageDefault; startPage = Math.floor(uidIndex / perPage) + 1; } $('#world_info_pagination').pagination({ dataSource: getDataArray, - pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault, + pageSize: Number(accountStorage.getItem(storageKey)) || perPageDefault, sizeChangerOptions: [10, 25, 50, 100, 500, 1000], showSizeChanger: true, pageRange: 1, @@ -1983,7 +1984,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl worldEntriesList.append(blocks); }, afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); + accountStorage.setItem(storageKey, e.target.value); }, afterPaging: function () { $('#world_popup_entries_list textarea[name="comment"]').each(function () { @@ -2188,7 +2189,7 @@ function verifyWorldInfoSearchSortRule() { // If search got cleared, we make sure to hide the option and go back to the one before if (!searchTerm && !isHidden) { searchOption.attr('hidden', ''); - selector.val(localStorage.getItem(SORT_ORDER_KEY) || '0'); + selector.val(accountStorage.getItem(SORT_ORDER_KEY) || '0'); } } @@ -4753,8 +4754,8 @@ export function checkEmbeddedWorld(chid) { // Only show the alert once per character const checkKey = `AlertWI_${characters[chid].avatar}`; const worldName = characters[chid]?.data?.extensions?.world; - if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { - localStorage.setItem(checkKey, 'true'); + if (!accountStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { + accountStorage.setItem(checkKey, 'true'); if (power_user.world_import_dialog) { const html = `

This character has an embedded World/Lorebook.

@@ -5198,7 +5199,7 @@ jQuery(() => { $('#world_info_sort_order').on('change', function () { const value = String($(this).find(':selected').val()); // Save sort order, but do not save search sorting, as this is a temporary sorting option - if (value !== 'search') localStorage.setItem(SORT_ORDER_KEY, value); + if (value !== 'search') accountStorage.setItem(SORT_ORDER_KEY, value); updateEditor(navigation_option.none); }); From ee101353d8832bf6f97031f00ccff87b41a623f6 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:31:37 +0200 Subject: [PATCH 2/7] Clear media alerts from storage on character deletion --- public/script.js | 6 ++++++ public/scripts/util/AccountStorage.js | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/public/script.js b/public/script.js index ca0d0f885..28d7c7b1c 100644 --- a/public/script.js +++ b/public/script.js @@ -9451,6 +9451,9 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) continue; } + accountStorage.removeItem(`AlertWI_${character.avatar}`); + accountStorage.removeItem(`AlertRegex_${character.avatar}`); + accountStorage.removeItem(`mediaWarningShown:${character.avatar}`); delete tag_map[character.avatar]; select_rm_info('char_delete', character.name); @@ -11658,6 +11661,9 @@ jQuery(async function () { eventSource.on(event_types.GROUP_CHAT_DELETED, async (name) => { await deleteItemizedPrompts(name); }); + eventSource.on(event_types.CHARACTER_DELETED, async() => { + + }); initCustomSelectedSamplers(); }); diff --git a/public/scripts/util/AccountStorage.js b/public/scripts/util/AccountStorage.js index fe163813d..e2f35d074 100644 --- a/public/scripts/util/AccountStorage.js +++ b/public/scripts/util/AccountStorage.js @@ -105,6 +105,10 @@ class AccountStorage { console.warn(`AccountStorage not ready (trying to remove ${key})`); } + if (!Object.hasOwn(this.state, key)) { + return; + } + delete this.state[key]; saveSettingsDebounced(); } From 93b6612c758aef226ca55088a7d712a7f8403bfd Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:36:25 +0200 Subject: [PATCH 3/7] Remove leftover event handler --- public/script.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/script.js b/public/script.js index 28d7c7b1c..0afda016a 100644 --- a/public/script.js +++ b/public/script.js @@ -11661,9 +11661,6 @@ jQuery(async function () { eventSource.on(event_types.GROUP_CHAT_DELETED, async (name) => { await deleteItemizedPrompts(name); }); - eventSource.on(event_types.CHARACTER_DELETED, async() => { - - }); initCustomSelectedSamplers(); }); From 798f8d92a3cd3085400e8176033bc67f73c4a702 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:43:19 +0200 Subject: [PATCH 4/7] Update saveSettings function to schedule another save when settings are not ready --- public/script.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 3c887a8c3..5872cbb5d 100644 --- a/public/script.js +++ b/public/script.js @@ -7076,7 +7076,8 @@ function selectKoboldGuiPreset() { export async function saveSettings(loopCounter = 0) { if (!settingsReady) { - console.warn('Settings not ready, aborting save'); + console.warn('Settings not ready, scheduling another save'); + saveSettingsDebounced(); return; } From 5085f6cdc9f68dc702ab3a78e5dc7f0efe30c770 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:58:36 +0200 Subject: [PATCH 5/7] Refactor AccountStorage to use private fields for state management --- public/script.js | 2 +- public/scripts/util/AccountStorage.js | 45 +++++++++++++++++---------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/public/script.js b/public/script.js index 5872cbb5d..48e623736 100644 --- a/public/script.js +++ b/public/script.js @@ -7098,7 +7098,7 @@ export async function saveSettings(loopCounter = 0) { url: '/api/settings/save', data: JSON.stringify({ firstRun: firstRun, - accountStorage: accountStorage.state, + accountStorage: accountStorage.getState(), currentVersion: currentVersion, username: name1, active_character: active_character, diff --git a/public/scripts/util/AccountStorage.js b/public/scripts/util/AccountStorage.js index e2f35d074..2bffbaa2b 100644 --- a/public/scripts/util/AccountStorage.js +++ b/public/scripts/util/AccountStorage.js @@ -34,10 +34,15 @@ const MIGRATABLE_KEYS = [ * Provides access to account storage of arbitrary key-value pairs. */ class AccountStorage { - constructor() { - this.state = {}; - this.ready = false; - } + /** + * @type {Record} Storage state + */ + #state = {}; + + /** + * @type {boolean} If the storage was initialized + */ + #ready = false; #migrateLocalStorage() { for (let i = 0; i < globalThis.localStorage.length; i++) { @@ -45,7 +50,7 @@ class AccountStorage { const value = globalThis.localStorage.getItem(key); if (MIGRATABLE_KEYS.some(k => k.test(key))) { - this.state[key] = value; + this.#state[key] = value; globalThis.localStorage.removeItem(key); } } @@ -57,16 +62,16 @@ class AccountStorage { */ init(state) { if (state && typeof state === 'object') { - this.state = Object.assign(this.state, state); + this.#state = Object.assign(this.#state, state); } - if (!Object.hasOwn(this.state, MIGRATED_MARKER)) { + if (!Object.hasOwn(this.#state, MIGRATED_MARKER)) { this.#migrateLocalStorage(); - this.state[MIGRATED_MARKER] = 1; + this.#state[MIGRATED_MARKER] = '1'; saveSettingsDebounced(); } - this.ready = true; + this.#ready = true; } /** @@ -75,11 +80,11 @@ class AccountStorage { * @returns {string|null} Value of the key */ getItem(key) { - if (!this.ready) { + if (!this.#ready) { console.warn(`AccountStorage not ready (trying to read from ${key})`); } - return Object.hasOwn(this.state, key) ? String(this.state[key]) : null; + return Object.hasOwn(this.#state, key) ? String(this.#state[key]) : null; } /** @@ -88,11 +93,11 @@ class AccountStorage { * @param {string} value Value to set */ setItem(key, value) { - if (!this.ready) { + if (!this.#ready) { console.warn(`AccountStorage not ready (trying to write to ${key})`); } - this.state[key] = String(value); + this.#state[key] = String(value); saveSettingsDebounced(); } @@ -101,17 +106,25 @@ class AccountStorage { * @param {string} key Key to remove */ removeItem(key) { - if (!this.ready) { + if (!this.#ready) { console.warn(`AccountStorage not ready (trying to remove ${key})`); } - if (!Object.hasOwn(this.state, key)) { + if (!Object.hasOwn(this.#state, key)) { return; } - delete this.state[key]; + delete this.#state[key]; saveSettingsDebounced(); } + + /** + * Gets a snapshot of the storage state. + * @returns {Record} A deep clone of the storage state + */ + getState() { + return structuredClone(this.#state); + } } /** From 340c03cedf5f113621d2df110a1eabcb151f78c1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:21:13 +0200 Subject: [PATCH 6/7] Make user input saving account-specific --- public/scripts/RossAscends-mods.js | 7 +++++-- public/scripts/user.js | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index c2a5e2774..236955596 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -41,6 +41,7 @@ import { debounce_timeout } from './constants.js'; import { Popup } from './popup.js'; import { accountStorage } from './util/AccountStorage.js'; +import { getCurrentUserHandle } from './user.js'; var RPanelPin = document.getElementById('rm_button_panel_pin'); var LPanelPin = document.getElementById('lm_button_panel_pin'); @@ -428,13 +429,15 @@ function OpenNavPanels() { } } +const getUserInputKey = () => getCurrentUserHandle() + '_userInput'; + function restoreUserInput() { if (!power_user.restore_user_input) { console.debug('restoreUserInput disabled'); return; } - const userInput = localStorage.getItem('userInput'); + const userInput = localStorage.getItem(getUserInputKey()); if (userInput) { $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); } @@ -442,7 +445,7 @@ function restoreUserInput() { function saveUserInput() { const userInput = String($('#send_textarea').val()); - localStorage.setItem('userInput', userInput); + localStorage.setItem(getUserInputKey(), userInput); console.debug('User Input -- ', userInput); } const saveUserInputDebounced = debounce(saveUserInput); diff --git a/public/scripts/user.js b/public/scripts/user.js index 5db961019..4aa61967b 100644 --- a/public/scripts/user.js +++ b/public/scripts/user.js @@ -43,6 +43,14 @@ export function isAdmin() { return Boolean(currentUser.admin); } +/** + * Gets the handle string of the current user. + * @returns {string} User handle + */ +export function getCurrentUserHandle() { + return currentUser?.handle || 'default-user'; +} + /** * Get the current user. * @returns {Promise} From 34d17a4fcb20f8b89d3cdb5b765892376604509c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 12 Feb 2025 11:29:27 +0200 Subject: [PATCH 7/7] Migrate QR editor preferences to accountStorage --- .../extensions/quick-reply/src/QuickReply.js | 17 +++++++++-------- public/scripts/util/AccountStorage.js | 4 ++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index ff7d5121f..d1e0ac012 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -10,6 +10,7 @@ import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecut import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; +import { accountStorage } from '../../../util/AccountStorage.js'; import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; import { log, quickReplyApi, warn } from '../index.js'; import { QuickReplyContextLink } from './QuickReplyContextLink.js'; @@ -544,9 +545,9 @@ export class QuickReply { this.editorSyntax = messageSyntaxInner; /**@type {HTMLInputElement}*/ const wrap = dom.querySelector('#qr--modal-wrap'); - wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); + wrap.checked = JSON.parse(accountStorage.getItem('qr--wrap') ?? 'false'); wrap.addEventListener('click', () => { - localStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); + accountStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); updateWrap(); }); const updateWrap = () => { @@ -594,27 +595,27 @@ export class QuickReply { }; /**@type {HTMLInputElement}*/ const tabSize = dom.querySelector('#qr--modal-tabSize'); - tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4'); + tabSize.value = JSON.parse(accountStorage.getItem('qr--tabSize') ?? '4'); const updateTabSize = () => { message.style.tabSize = tabSize.value; messageSyntaxInner.style.tabSize = tabSize.value; updateScrollDebounced(); }; tabSize.addEventListener('change', () => { - localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); + accountStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); updateTabSize(); }); /**@type {HTMLInputElement}*/ const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); - executeShortcut.checked = JSON.parse(localStorage.getItem('qr--executeShortcut') ?? 'true'); + executeShortcut.checked = JSON.parse(accountStorage.getItem('qr--executeShortcut') ?? 'true'); executeShortcut.addEventListener('click', () => { - localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); + accountStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); }); /**@type {HTMLInputElement}*/ const syntax = dom.querySelector('#qr--modal-syntax'); - syntax.checked = JSON.parse(localStorage.getItem('qr--syntax') ?? 'true'); + syntax.checked = JSON.parse(accountStorage.getItem('qr--syntax') ?? 'true'); syntax.addEventListener('click', () => { - localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); + accountStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); updateSyntaxEnabled(); }); if (navigator.keyboard) { diff --git a/public/scripts/util/AccountStorage.js b/public/scripts/util/AccountStorage.js index 2bffbaa2b..01d07c0aa 100644 --- a/public/scripts/util/AccountStorage.js +++ b/public/scripts/util/AccountStorage.js @@ -21,6 +21,10 @@ const MIGRATABLE_KEYS = [ /^Personas_PerPage$/, /^Personas_GridView$/, /^Proxy_SkipConfirm_/, + /^qr--executeShortcut$/, + /^qr--syntax$/, + /^qr--tabSize$/, + /^qr--wrap$/, /^RegenerateWithCtrlEnter$/, /^SelectedNavTab$/, /^sendAsNamelessWarningShown$/,