Merge pull request #3463 from SillyTavern/bogus-localstorage Add settings.json-backed KV string storage
Signed| @@ -270,6 +270,7 @@ import { initBulkEdit } from './scripts/bulk-edit.js'; | ||
| 270 | 270 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 271 | 271 | import { getContext } from './scripts/st-context.js'; |
| 272 | 272 | import { extractReasoningFromData, initReasoning, PromptReasoning, removeReasoningFromString, updateReasoningTimeUI } from './scripts/reasoning.js'; |
| 273 | +import { accountStorage } from './scripts/util/AccountStorage.js'; | |
| 273 | 274 | |
| 274 | 275 | // API OBJECT FOR EXTERNAL WIRING |
| 275 | 276 | globalThis.SillyTavern = { |
| @@ -419,7 +420,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | ||
| 419 | 420 | const entityId = getCurrentEntityId(); |
| 420 | 421 | const warningShownKey = `mediaWarningShown:${entityId}`; |
| 421 | 422 | |
| 422 | 423 | if (localStorageaccountStorage.getItem(warningShownKey) === null) { |
| 423 | 424 | const warningToast = toastr.warning( |
| 424 | 425 | t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, |
| 425 | 426 | t`External media has been blocked`, |
| @@ -430,7 +431,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | ||
| 430 | 431 | }, |
| 431 | 432 | ); |
| 432 | 433 | |
| 433 | 434 | localStorageaccountStorage.setItem(warningShownKey, 'true'); |
| 434 | 435 | } |
| 435 | 436 | } |
| 436 | 437 | }); |
| @@ -1490,7 +1491,7 @@ export async function printCharacters(fullRefresh = false) { | ||
| 1490 | 1491 | |
| 1491 | 1492 | $('#rm_print_characters_pagination').pagination({ |
| 1492 | 1493 | dataSource: entities, |
| 1493 | 1494 | pageSize: Number(localStorageaccountStorage.getItem(storageKey)) || per_page_default, |
| 1494 | 1495 | sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], |
| 1495 | 1496 | pageRange: 1, |
| 1496 | 1497 | pageNumber: saveCharactersPage || 1, |
| @@ -1534,7 +1535,7 @@ export async function printCharacters(fullRefresh = false) { | ||
| 1534 | 1535 | eventSource.emit(event_types.CHARACTER_PAGE_LOADED); |
| 1535 | 1536 | }, |
| 1536 | 1537 | afterSizeSelectorChange: function (e) { |
| 1537 | 1538 | localStorageaccountStorage.setItem(storageKey, e.target.value); |
| 1538 | 1539 | }, |
| 1539 | 1540 | afterPaging: function (e) { |
| 1540 | 1541 | saveCharactersPage = e; |
| @@ -6903,10 +6904,11 @@ export async function getSettings() { | ||
| 6903 | 6904 | $('#your_name').val(name1); |
| 6904 | 6905 | } |
| 6905 | 6906 | |
| 6907 | + accountStorage.init(settings?.accountStorage); | |
| 6906 | 6908 | await setUserControls(data.enable_accounts); |
| 6907 | 6909 | |
| 6908 | 6910 | // Allow subscribers to mutate settings |
| 6909 | 6911 | await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); |
| 6910 | 6912 | |
| 6911 | 6913 | //Load KoboldAI settings |
| 6912 | 6914 | koboldai_setting_names = data.koboldai_setting_names; |
| @@ -7074,7 +7076,8 @@ function selectKoboldGuiPreset() { | ||
| 7074 | 7076 | |
| 7075 | 7077 | export async function saveSettings(loopCounter = 0) { |
| 7076 | 7078 | if (!settingsReady) { |
| 7077 | 7079 | console.warn('Settings not ready, abortingscheduling another save'); |
| 7080 | + saveSettingsDebounced(); | |
| 7078 | 7081 | return; |
| 7079 | 7082 | } |
| 7080 | 7083 | |
| @@ -7095,6 +7098,7 @@ export async function saveSettings(loopCounter = 0) { | ||
| 7095 | 7098 | url: '/api/settings/save', |
| 7096 | 7099 | data: JSON.stringify({ |
| 7097 | 7100 | firstRun: firstRun, |
| 7101 | + accountStorage: accountStorage.getState(), | |
| 7098 | 7102 | currentVersion: currentVersion, |
| 7099 | 7103 | username: name1, |
| 7100 | 7104 | active_character: active_character, |
| @@ -7550,7 +7554,7 @@ export function select_rm_info(type, charId, previousCharId = null) { | ||
| 7550 | 7554 | } |
| 7551 | 7555 | |
| 7552 | 7556 | try { |
| 7553 | 7557 | const perPage = Number(localStorageaccountStorage.getItem('Characters_PerPage')) || per_page_default; |
| 7554 | 7558 | const page = Math.floor(charIndex / perPage) + 1; |
| 7555 | 7559 | const selector = `#rm_print_characters_block [title*="${avatarFileName}"]`; |
| 7556 | 7560 | $('#rm_print_characters_pagination').pagination('go', page); |
| @@ -7582,7 +7586,7 @@ export function select_rm_info(type, charId, previousCharId = null) { | ||
| 7582 | 7586 | return; |
| 7583 | 7587 | } |
| 7584 | 7588 | |
| 7585 | 7589 | const perPage = Number(localStorageaccountStorage.getItem('Characters_PerPage')) || per_page_default; |
| 7586 | 7590 | const page = Math.floor(charIndex / perPage) + 1; |
| 7587 | 7591 | $('#rm_print_characters_pagination').pagination('go', page); |
| 7588 | 7592 | const selector = `#rm_print_characters_block [grid="${charId}"]`; |
| @@ -9449,6 +9453,9 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) | ||
| 9449 | 9453 | continue; |
| 9450 | 9454 | } |
| 9451 | 9455 | |
| 9456 | + accountStorage.removeItem(`AlertWI_${character.avatar}`); | |
| 9457 | + accountStorage.removeItem(`AlertRegex_${character.avatar}`); | |
| 9458 | + accountStorage.removeItem(`mediaWarningShown:${character.avatar}`); | |
| 9452 | 9459 | delete tag_map[character.avatar]; |
| 9453 | 9460 | select_rm_info('char_delete', character.name); |
| 9454 | 9461 | |
| @@ -9651,8 +9658,8 @@ function addDebugFunctions() { | ||
| 9651 | 9658 | }); |
| 9652 | 9659 | |
| 9653 | 9660 | registerDebugFunction('toggleRegenerateWarning', 'Toggle Ctrl+Enter regeneration confirmation', 'Toggle the warning when regenerating a message with a Ctrl+Enter hotkey.', () => { |
| 9654 | 9661 | localStorageaccountStorage.setItem('RegenerateWithCtrlEnter', localStorageaccountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'false' : 'true'); |
| 9655 | 9662 | toastr.info('Regenerate warning is now ' + (localStorageaccountStorage.getItem('RegenerateWithCtrlEnter') === 'true' ? 'disabled' : 'enabled')); |
| 9656 | 9663 | }); |
| 9657 | 9664 | |
| 9658 | 9665 | registerDebugFunction('copySetup', 'Copy ST setup to clipboard [WIP]', 'Useful data when reporting bugs', async () => { |
| @@ -27,7 +27,6 @@ import { | ||
| 27 | 27 | send_on_enter_options, |
| 28 | 28 | } from './power-user.js'; |
| 29 | 29 | |
| 30 | -import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js'; | |
| 31 | 30 | import { selected_group, is_group_generating, openGroupById } from './group-chats.js'; |
| 32 | 31 | import { getTagKeyForEntity, applyTagsOnCharacterSelect } from './tags.js'; |
| 33 | 32 | import { |
| @@ -41,6 +40,8 @@ import { textgen_types, textgenerationwebui_settings as textgen_settings, getTex | ||
| 41 | 40 | import { debounce_timeout } from './constants.js'; |
| 42 | 41 | |
| 43 | 42 | import { Popup } from './popup.js'; |
| 43 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 44 | +import { getCurrentUserHandle } from './user.js'; | |
| 44 | 45 | |
| 45 | 46 | var RPanelPin = document.getElementById('rm_button_panel_pin'); |
| 46 | 47 | var LPanelPin = document.getElementById('lm_button_panel_pin'); |
| @@ -409,32 +410,34 @@ function RA_autoconnect(PrevApi) { | ||
| 409 | 410 | function OpenNavPanels() { |
| 410 | 411 | if (!isMobile()) { |
| 411 | 412 | //auto-open R nav if locked and previously open |
| 412 | 413 | if (LoadLocalBoolaccountStorage.getItem('NavLockOn') == 'true' && LoadLocalBoolaccountStorage.getItem('NavOpened') == 'true') { |
| 413 | 414 | //console.log("RA -- clicking right nav to open"); |
| 414 | 415 | $('#rightNavDrawerIcon').click(); |
| 415 | 416 | } |
| 416 | 417 | |
| 417 | 418 | //auto-open L nav if locked and previously open |
| 418 | 419 | if (LoadLocalBoolaccountStorage.getItem('LNavLockOn') == 'true' && LoadLocalBoolaccountStorage.getItem('LNavOpened') == 'true') { |
| 419 | 420 | console.debug('RA -- clicking left nav to open'); |
| 420 | 421 | $('#leftNavDrawerIcon').click(); |
| 421 | 422 | } |
| 422 | 423 | |
| 423 | 424 | //auto-open WI if locked and previously open |
| 424 | 425 | if (LoadLocalBoolaccountStorage.getItem('WINavLockOn') == 'true' && LoadLocalBoolaccountStorage.getItem('WINavOpened') == 'true') { |
| 425 | 426 | console.debug('RA -- clicking WI to open'); |
| 426 | 427 | $('#WIDrawerIcon').click(); |
| 427 | 428 | } |
| 428 | 429 | } |
| 429 | 430 | } |
| 430 | 431 | |
| 432 | +const getUserInputKey = () => getCurrentUserHandle() + '_userInput'; | |
| 433 | + | |
| 431 | 434 | function restoreUserInput() { |
| 432 | 435 | if (!power_user.restore_user_input) { |
| 433 | 436 | console.debug('restoreUserInput disabled'); |
| 434 | 437 | return; |
| 435 | 438 | } |
| 436 | 439 | |
| 437 | 440 | const userInput = LoadLocallocalStorage.getItem('userInput'getUserInputKey()); |
| 438 | 441 | if (userInput) { |
| 439 | 442 | $('#send_textarea').val(userInput)[0].dispatchEvent(new Event('input', { bubbles: true })); |
| 440 | 443 | } |
| @@ -442,7 +445,8 @@ function restoreUserInput() { | ||
| 442 | 445 | |
| 443 | 446 | function saveUserInput() { |
| 444 | 447 | const userInput = String($('#send_textarea').val()); |
| 445 | 448 | SaveLocallocalStorage.setItem('userInput'getUserInputKey(), userInput); |
| 449 | + console.debug('User Input -- ', userInput); | |
| 446 | 450 | } |
| 447 | 451 | const saveUserInputDebounced = debounce(saveUserInput); |
| 448 | 452 | |
| @@ -739,7 +743,7 @@ export function initRossMods() { | ||
| 739 | 743 | |
| 740 | 744 | //toggle pin class when lock toggle clicked |
| 741 | 745 | $(RPanelPin).on('click', function () { |
| 742 | 746 | SaveLocalaccountStorage.setItem('NavLockOn', $(RPanelPin).prop('checked')); |
| 743 | 747 | if ($(RPanelPin).prop('checked') == true) { |
| 744 | 748 | //console.log('adding pin class to right nav'); |
| 745 | 749 | $(RightNavPanel).addClass('pinnedOpen'); |
| @@ -757,7 +761,7 @@ export function initRossMods() { | ||
| 757 | 761 | } |
| 758 | 762 | }); |
| 759 | 763 | $(LPanelPin).on('click', function () { |
| 760 | 764 | SaveLocalaccountStorage.setItem('LNavLockOn', $(LPanelPin).prop('checked')); |
| 761 | 765 | if ($(LPanelPin).prop('checked') == true) { |
| 762 | 766 | //console.log('adding pin class to Left nav'); |
| 763 | 767 | $(LeftNavPanel).addClass('pinnedOpen'); |
| @@ -776,7 +780,7 @@ export function initRossMods() { | ||
| 776 | 780 | }); |
| 777 | 781 | |
| 778 | 782 | $(WIPanelPin).on('click', function () { |
| 779 | 783 | SaveLocalaccountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked')); |
| 780 | 784 | if ($(WIPanelPin).prop('checked') == true) { |
| 781 | 785 | console.debug('adding pin class to WI'); |
| 782 | 786 | $(WorldInfo).addClass('pinnedOpen'); |
| @@ -796,8 +800,8 @@ export function initRossMods() { | ||
| 796 | 800 | }); |
| 797 | 801 | |
| 798 | 802 | // read the state of right Nav Lock and apply to rightnav classlist |
| 799 | 803 | $(RPanelPin).prop('checked', LoadLocalBoolaccountStorage.getItem('NavLockOn') == 'true'); |
| 800 | 804 | if (LoadLocalBoolaccountStorage.getItem('NavLockOn') == 'true') { |
| 801 | 805 | //console.log('setting pin class via local var'); |
| 802 | 806 | $(RightNavPanel).addClass('pinnedOpen'); |
| 803 | 807 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -808,8 +812,8 @@ export function initRossMods() { | ||
| 808 | 812 | $(RightNavDrawerIcon).addClass('drawerPinnedOpen'); |
| 809 | 813 | } |
| 810 | 814 | // read the state of left Nav Lock and apply to leftnav classlist |
| 811 | 815 | $(LPanelPin).prop('checked', LoadLocalBoolaccountStorage.getItem('LNavLockOn') === 'true'); |
| 812 | 816 | if (LoadLocalBoolaccountStorage.getItem('LNavLockOn') == 'true') { |
| 813 | 817 | //console.log('setting pin class via local var'); |
| 814 | 818 | $(LeftNavPanel).addClass('pinnedOpen'); |
| 815 | 819 | $(LeftNavDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -821,8 +825,8 @@ export function initRossMods() { | ||
| 821 | 825 | } |
| 822 | 826 | |
| 823 | 827 | // read the state of left Nav Lock and apply to leftnav classlist |
| 824 | 828 | $(WIPanelPin).prop('checked', LoadLocalBoolaccountStorage.getItem('WINavLockOn') === 'true'); |
| 825 | 829 | if (LoadLocalBoolaccountStorage.getItem('WINavLockOn') == 'true') { |
| 826 | 830 | //console.log('setting pin class via local var'); |
| 827 | 831 | $(WorldInfo).addClass('pinnedOpen'); |
| 828 | 832 | $(WIDrawerIcon).addClass('drawerPinnedOpen'); |
| @@ -837,22 +841,22 @@ export function initRossMods() { | ||
| 837 | 841 | //save state of Right nav being open or closed |
| 838 | 842 | $('#rightNavDrawerIcon').on('click', function () { |
| 839 | 843 | if (!$('#rightNavDrawerIcon').hasClass('openIcon')) { |
| 840 | 844 | SaveLocalaccountStorage.setItem('NavOpened', 'true'); |
| 841 | 845 | } else { SaveLocalaccountStorage.setItem('NavOpened', 'false'); } |
| 842 | 846 | }); |
| 843 | 847 | |
| 844 | 848 | //save state of Left nav being open or closed |
| 845 | 849 | $('#leftNavDrawerIcon').on('click', function () { |
| 846 | 850 | if (!$('#leftNavDrawerIcon').hasClass('openIcon')) { |
| 847 | 851 | SaveLocalaccountStorage.setItem('LNavOpened', 'true'); |
| 848 | 852 | } else { SaveLocalaccountStorage.setItem('LNavOpened', 'false'); } |
| 849 | 853 | }); |
| 850 | 854 | |
| 851 | 855 | //save state of Left nav being open or closed |
| 852 | 856 | $('#WorldInfo').on('click', function () { |
| 853 | 857 | if (!$('#WorldInfo').hasClass('openIcon')) { |
| 854 | 858 | SaveLocalaccountStorage.setItem('WINavOpened', 'true'); |
| 855 | 859 | } else { SaveLocalaccountStorage.setItem('WINavOpened', 'false'); } |
| 856 | 860 | }); |
| 857 | 861 | |
| 858 | 862 | var chatbarInFocus = false; |
| @@ -868,8 +872,8 @@ export function initRossMods() { | ||
| 868 | 872 | OpenNavPanels(); |
| 869 | 873 | }, 300); |
| 870 | 874 | |
| 871 | 875 | $(SelectedCharacterTab).click(function () { SaveLocalaccountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); }); |
| 872 | 876 | $('#rm_button_characters').click(function () { SaveLocalaccountStorage.setItem('SelectedNavTab', 'rm_button_characters'); }); |
| 873 | 877 | |
| 874 | 878 | // when a char is selected from the list, save them as the auto-load character for next page load |
| 875 | 879 | |
| @@ -1077,7 +1081,7 @@ export function initRossMods() { | ||
| 1077 | 1081 | } |
| 1078 | 1082 | else if (is_send_press == false) { |
| 1079 | 1083 | const skipConfirmKey = 'RegenerateWithCtrlEnter'; |
| 1080 | 1084 | const skipConfirm = LoadLocalBoolaccountStorage.getItem(skipConfirmKey) === 'true'; |
| 1081 | 1085 | function doRegenerate() { |
| 1082 | 1086 | console.debug('Regenerating with Ctrl+Enter'); |
| 1083 | 1087 | $('#option_regenerate').trigger('click'); |
| @@ -1097,7 +1101,7 @@ export function initRossMods() { | ||
| 1097 | 1101 | return; |
| 1098 | 1102 | } |
| 1099 | 1103 | |
| 1100 | 1104 | SaveLocalaccountStorage.setItem(skipConfirmKey, String(regenerateWithCtrlEnter)); |
| 1101 | 1105 | doRegenerate(); |
| 1102 | 1106 | } |
| 1103 | 1107 | return; |
| @@ -45,6 +45,7 @@ import { DragAndDropHandler } from './dragdrop.js'; | ||
| 45 | 45 | import { renderTemplateAsync } from './templates.js'; |
| 46 | 46 | import { t } from './i18n.js'; |
| 47 | 47 | import { humanizedDateTime } from './RossAscends-mods.js'; |
| 48 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 48 | 49 | |
| 49 | 50 | /** |
| 50 | 51 | * @typedef {Object} FileAttachment |
| @@ -1078,8 +1079,8 @@ async function openAttachmentManager() { | ||
| 1078 | 1079 | renderAttachments(); |
| 1079 | 1080 | }); |
| 1080 | 1081 | |
| 1081 | 1082 | let sortField = localStorageaccountStorage.getItem('DataBank_sortField') || 'created'; |
| 1082 | 1083 | let sortOrder = localStorageaccountStorage.getItem('DataBank_sortOrder') || 'desc'; |
| 1083 | 1084 | let filterString = ''; |
| 1084 | 1085 | |
| 1085 | 1086 | const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {})); |
| @@ -1095,8 +1096,8 @@ async function openAttachmentManager() { | ||
| 1095 | 1096 | |
| 1096 | 1097 | sortField = this.selectedOptions[0].dataset.sortField; |
| 1097 | 1098 | sortOrder = this.selectedOptions[0].dataset.sortOrder; |
| 1098 | 1099 | localStorageaccountStorage.setItem('DataBank_sortField', sortField); |
| 1099 | 1100 | localStorageaccountStorage.setItem('DataBank_sortOrder', sortOrder); |
| 1100 | 1101 | renderAttachments(); |
| 1101 | 1102 | }); |
| 1102 | 1103 | function handleBulkAction(action) { |
| @@ -9,6 +9,7 @@ import { getContext } from './st-context.js'; | ||
| 9 | 9 | import { isAdmin } from './user.js'; |
| 10 | 10 | import { t } from './i18n.js'; |
| 11 | 11 | import { debounce_timeout } from './constants.js'; |
| 12 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 12 | 13 | |
| 13 | 14 | export { |
| 14 | 15 | getContext, |
| @@ -714,7 +715,7 @@ async function showExtensionsDetails() { | ||
| 714 | 715 | htmlExternal.append(htmlLoading); |
| 715 | 716 | |
| 716 | 717 | const sortOrderKey = 'extensions_sortByName'; |
| 717 | 718 | const sortByName = localStorageaccountStorage.getItem(sortOrderKey) === 'true'; |
| 718 | 719 | const sortFn = sortByName ? sortManifestsByName : sortManifestsByOrder; |
| 719 | 720 | const extensions = Object.entries(manifests).sort((a, b) => sortFn(a[1], b[1])).map(getExtensionData); |
| 720 | 721 | |
| @@ -745,7 +746,7 @@ async function showExtensionsDetails() { | ||
| 745 | 746 | text: sortByName ? t`Sort: Display Name` : t`Sort: Loading Order`, |
| 746 | 747 | action: async () => { |
| 747 | 748 | abortController.abort(); |
| 748 | 749 | localStorageaccountStorage.setItem(sortOrderKey, sortByName ? 'false' : 'true'); |
| 749 | 750 | await showExtensionsDetails(); |
| 750 | 751 | }, |
| 751 | 752 | }; |
| @@ -1153,11 +1154,11 @@ async function checkForExtensionUpdates(force) { | ||
| 1153 | 1154 | const currentDate = new Date().toDateString(); |
| 1154 | 1155 | |
| 1155 | 1156 | // Don't nag more than once a day |
| 1156 | 1157 | if (localStorageaccountStorage.getItem(STORAGE_NAG_KEY) === currentDate) { |
| 1157 | 1158 | return; |
| 1158 | 1159 | } |
| 1159 | 1160 | |
| 1160 | 1161 | localStorageaccountStorage.setItem(STORAGE_NAG_KEY, currentDate); |
| 1161 | 1162 | } |
| 1162 | 1163 | |
| 1163 | 1164 | const isCurrentUserAdmin = isAdmin(); |
| @@ -8,6 +8,7 @@ import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from | ||
| 8 | 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 9 | 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; |
| 10 | 10 | import { executeSlashCommands } from '../../slash-commands.js'; |
| 11 | +import { accountStorage } from '../../util/AccountStorage.js'; | |
| 11 | 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 12 | 13 | export { MODULE_NAME }; |
| 13 | 14 | |
| @@ -432,14 +433,14 @@ jQuery(async () => { | ||
| 432 | 433 | connectButton.on('click', async function () { |
| 433 | 434 | const url = DOMPurify.sanitize(String(assetsJsonUrl.val())); |
| 434 | 435 | const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`; |
| 435 | 436 | const skipConfirm = localStorageaccountStorage.getItem(rememberKey) === 'true'; |
| 436 | 437 | |
| 437 | 438 | const confirmation = skipConfirm || await Popup.show.confirm('Loading Asset List', `<span>Are you sure you want to connect to the following url?</span><var>${url}</var>`, { |
| 438 | 439 | customInputs: [{ id: 'assets-remember', label: 'Don\'t ask again for this URL' }], |
| 439 | 440 | onClose: popup => { |
| 440 | 441 | if (popup.result) { |
| 441 | 442 | const rememberValue = popup.inputResults.get('assets-remember'); |
| 442 | 443 | localStorageaccountStorage.setItem(rememberKey, String(rememberValue)); |
| 443 | 444 | } |
| 444 | 445 | }, |
| 445 | 446 | }); |
| @@ -10,6 +10,7 @@ import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecut | ||
| 10 | 10 | import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
| 11 | 11 | import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; |
| 12 | 12 | import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
| 13 | +import { accountStorage } from '../../../util/AccountStorage.js'; | |
| 13 | 14 | import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; |
| 14 | 15 | import { log, quickReplyApi, warn } from '../index.js'; |
| 15 | 16 | import { QuickReplyContextLink } from './QuickReplyContextLink.js'; |
| @@ -544,9 +545,9 @@ export class QuickReply { | ||
| 544 | 545 | this.editorSyntax = messageSyntaxInner; |
| 545 | 546 | /**@type {HTMLInputElement}*/ |
| 546 | 547 | const wrap = dom.querySelector('#qr--modal-wrap'); |
| 547 | 548 | wrap.checked = JSON.parse(localStorageaccountStorage.getItem('qr--wrap') ?? 'false'); |
| 548 | 549 | wrap.addEventListener('click', () => { |
| 549 | 550 | localStorageaccountStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); |
| 550 | 551 | updateWrap(); |
| 551 | 552 | }); |
| 552 | 553 | const updateWrap = () => { |
| @@ -594,27 +595,27 @@ export class QuickReply { | ||
| 594 | 595 | }; |
| 595 | 596 | /**@type {HTMLInputElement}*/ |
| 596 | 597 | const tabSize = dom.querySelector('#qr--modal-tabSize'); |
| 597 | 598 | tabSize.value = JSON.parse(localStorageaccountStorage.getItem('qr--tabSize') ?? '4'); |
| 598 | 599 | const updateTabSize = () => { |
| 599 | 600 | message.style.tabSize = tabSize.value; |
| 600 | 601 | messageSyntaxInner.style.tabSize = tabSize.value; |
| 601 | 602 | updateScrollDebounced(); |
| 602 | 603 | }; |
| 603 | 604 | tabSize.addEventListener('change', () => { |
| 604 | 605 | localStorageaccountStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); |
| 605 | 606 | updateTabSize(); |
| 606 | 607 | }); |
| 607 | 608 | /**@type {HTMLInputElement}*/ |
| 608 | 609 | const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); |
| 609 | 610 | executeShortcut.checked = JSON.parse(localStorageaccountStorage.getItem('qr--executeShortcut') ?? 'true'); |
| 610 | 611 | executeShortcut.addEventListener('click', () => { |
| 611 | 612 | localStorageaccountStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); |
| 612 | 613 | }); |
| 613 | 614 | /**@type {HTMLInputElement}*/ |
| 614 | 615 | const syntax = dom.querySelector('#qr--modal-syntax'); |
| 615 | 616 | syntax.checked = JSON.parse(localStorageaccountStorage.getItem('qr--syntax') ?? 'true'); |
| 616 | 617 | syntax.addEventListener('click', () => { |
| 617 | 618 | localStorageaccountStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); |
| 618 | 619 | updateSyntaxEnabled(); |
| 619 | 620 | }); |
| 620 | 621 | if (navigator.keyboard) { |
| @@ -10,6 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | ||
| 10 | 10 | import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; |
| 11 | 11 | import { regex_placement, runRegexScript, substitute_find_regex } from './engine.js'; |
| 12 | 12 | import { t } from '../../i18n.js'; |
| 13 | +import { accountStorage } from '../../util/AccountStorage.js'; | |
| 13 | 14 | |
| 14 | 15 | /** |
| 15 | 16 | * @typedef {object} RegexScript |
| @@ -440,8 +441,8 @@ async function checkEmbeddedRegexScripts() { | ||
| 440 | 441 | if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) { |
| 441 | 442 | const checkKey = `AlertRegex_${characters[chid].avatar}`; |
| 442 | 443 | |
| 443 | 444 | if (!localStorageaccountStorage.getItem(checkKey)) { |
| 444 | 445 | localStorageaccountStorage.setItem(checkKey, 'true'); |
| 445 | 446 | const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); |
| 446 | 447 | const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); |
| 447 | 448 | |
| @@ -1,27 +0,0 @@ | ||
| 1 | -////////////////// LOCAL STORAGE HANDLING ///////////////////// | |
| 2 | - | |
| 3 | -export function SaveLocal(target, val) { | |
| 4 | - localStorage.setItem(target, val); | |
| 5 | - console.debug('SaveLocal -- ' + target + ' : ' + val); | |
| 6 | -} | |
| 7 | -export function LoadLocal(target) { | |
| 8 | - console.debug('LoadLocal -- ' + target); | |
| 9 | - return localStorage.getItem(target); | |
| 10 | - | |
| 11 | -} | |
| 12 | -export function LoadLocalBool(target) { | |
| 13 | - let result = localStorage.getItem(target) === 'true'; | |
| 14 | - return result; | |
| 15 | -} | |
| 16 | -export function CheckLocal() { | |
| 17 | - console.log('----------local storage---------'); | |
| 18 | - var i; | |
| 19 | - for (i = 0; i < localStorage.length; i++) { | |
| 20 | - console.log(localStorage.key(i) + ' : ' + localStorage.getItem(localStorage.key(i))); | |
| 21 | - } | |
| 22 | - console.log('------------------------------'); | |
| 23 | -} | |
| 24 | - | |
| 25 | -export function ClearLocal() { localStorage.clear(); console.log('Removed All Local Storage'); } | |
| 26 | - | |
| 27 | -///////////////////////////////////////////////////////////////////////// | |
| @@ -78,6 +78,7 @@ import { FILTER_TYPES, FilterHelper } from './filters.js'; | ||
| 78 | 78 | import { isExternalMediaAllowed } from './chats.js'; |
| 79 | 79 | import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| 80 | 80 | import { t } from './i18n.js'; |
| 81 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 81 | 82 | |
| 82 | 83 | export { |
| 83 | 84 | selected_group, |
| @@ -1309,10 +1310,10 @@ function printGroupCandidates() { | ||
| 1309 | 1310 | formatNavigator: PAGINATION_TEMPLATE, |
| 1310 | 1311 | showNavigator: true, |
| 1311 | 1312 | showSizeChanger: true, |
| 1312 | 1313 | pageSize: Number(localStorageaccountStorage.getItem(storageKey)) || 5, |
| 1313 | 1314 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], |
| 1314 | 1315 | afterSizeSelectorChange: function (e) { |
| 1315 | 1316 | localStorageaccountStorage.setItem(storageKey, e.target.value); |
| 1316 | 1317 | }, |
| 1317 | 1318 | callback: function (data) { |
| 1318 | 1319 | $('#rm_group_add_members').empty(); |
| @@ -1336,10 +1337,10 @@ function printGroupMembers() { | ||
| 1336 | 1337 | formatNavigator: PAGINATION_TEMPLATE, |
| 1337 | 1338 | showNavigator: true, |
| 1338 | 1339 | showSizeChanger: true, |
| 1339 | 1340 | pageSize: Number(localStorageaccountStorage.getItem(storageKey)) || 5, |
| 1340 | 1341 | sizeChangerOptions: [5, 10, 25, 50, 100, 200, 500, 1000], |
| 1341 | 1342 | afterSizeSelectorChange: function (e) { |
| 1342 | 1343 | localStorageaccountStorage.setItem(storageKey, e.target.value); |
| 1343 | 1344 | }, |
| 1344 | 1345 | callback: function (data) { |
| 1345 | 1346 | $('.rm_group_members').empty(); |
| @@ -73,6 +73,7 @@ import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js | ||
| 73 | 73 | import { Popup, POPUP_RESULT } from './popup.js'; |
| 74 | 74 | import { t } from './i18n.js'; |
| 75 | 75 | import { ToolManager } from './tool-calling.js'; |
| 76 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 76 | 77 | |
| 77 | 78 | export { |
| 78 | 79 | openai_messages_count, |
| @@ -414,7 +415,7 @@ async function validateReverseProxy() { | ||
| 414 | 415 | throw err; |
| 415 | 416 | } |
| 416 | 417 | const rememberKey = `Proxy_SkipConfirm_${getStringHash(oai_settings.reverse_proxy)}`; |
| 417 | 418 | const skipConfirm = localStorageaccountStorage.getItem(rememberKey) === 'true'; |
| 418 | 419 | |
| 419 | 420 | const confirmation = skipConfirm || await Popup.show.confirm(t`Connecting To Proxy`, await renderTemplateAsync('proxyConnectionWarning', { proxyURL: DOMPurify.sanitize(oai_settings.reverse_proxy) })); |
| 420 | 421 | |
| @@ -425,7 +426,7 @@ async function validateReverseProxy() { | ||
| 425 | 426 | throw new Error('Proxy connection denied.'); |
| 426 | 427 | } |
| 427 | 428 | |
| 428 | 429 | localStorageaccountStorage.setItem(rememberKey, String(true)); |
| 429 | 430 | } |
| 430 | 431 | |
| 431 | 432 | /** |
| @@ -25,6 +25,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | ||
| 25 | 25 | import { t } from './i18n.js'; |
| 26 | 26 | import { openWorldInfoEditor, world_names } from './world-info.js'; |
| 27 | 27 | import { renderTemplateAsync } from './templates.js'; |
| 28 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 28 | 29 | |
| 29 | 30 | let savePersonasPage = 0; |
| 30 | 31 | const GRID_STORAGE_KEY = 'Personas_GridView'; |
| @@ -34,7 +35,7 @@ export let user_avatar = ''; | ||
| 34 | 35 | export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick)); |
| 35 | 36 | |
| 36 | 37 | function switchPersonaGridView() { |
| 37 | 38 | const state = localStorageaccountStorage.getItem(GRID_STORAGE_KEY) === 'true'; |
| 38 | 39 | $('#user_avatar_block').toggleClass('gridView', state); |
| 39 | 40 | } |
| 40 | 41 | |
| @@ -182,7 +183,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | ||
| 182 | 183 | |
| 183 | 184 | const storageKey = 'Personas_PerPage'; |
| 184 | 185 | const listId = '#user_avatar_block'; |
| 185 | 186 | const perPage = Number(localStorageaccountStorage.getItem(storageKey)) || 5; |
| 186 | 187 | |
| 187 | 188 | $('#persona_pagination_container').pagination({ |
| 188 | 189 | dataSource: entities, |
| @@ -205,7 +206,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') { | ||
| 205 | 206 | highlightSelectedAvatar(); |
| 206 | 207 | }, |
| 207 | 208 | afterSizeSelectorChange: function (e) { |
| 208 | 209 | localStorageaccountStorage.setItem(storageKey, e.target.value); |
| 209 | 210 | }, |
| 210 | 211 | afterPaging: function (e) { |
| 211 | 212 | savePersonasPage = e; |
| @@ -1132,8 +1133,8 @@ export function initPersonas() { | ||
| 1132 | 1133 | saveSettingsDebounced(); |
| 1133 | 1134 | }); |
| 1134 | 1135 | $('#persona_grid_toggle').on('click', () => { |
| 1135 | 1136 | const state = localStorageaccountStorage.getItem(GRID_STORAGE_KEY) === 'true'; |
| 1136 | 1137 | localStorageaccountStorage.setItem(GRID_STORAGE_KEY, String(!state)); |
| 1137 | 1138 | switchPersonaGridView(); |
| 1138 | 1139 | }); |
| 1139 | 1140 | |
| @@ -54,6 +54,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | ||
| 54 | 54 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 55 | 55 | import { loadSystemPrompts } from './sysprompt.js'; |
| 56 | 56 | import { fuzzySearchCategories } from './filters.js'; |
| 57 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 57 | 58 | |
| 58 | 59 | export { |
| 59 | 60 | loadPowerUserSettings, |
| @@ -2019,7 +2020,7 @@ export function renderStoryString(params) { | ||
| 2019 | 2020 | */ |
| 2020 | 2021 | function validateStoryString(storyString, params) { |
| 2021 | 2022 | /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ |
| 2022 | 2023 | const cache = JSON.parse(localStorageaccountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; |
| 2023 | 2024 | |
| 2024 | 2025 | const hash = getStringHash(storyString); |
| 2025 | 2026 | |
| @@ -2056,7 +2057,7 @@ function validateStoryString(storyString, params) { | ||
| 2056 | 2057 | toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); |
| 2057 | 2058 | } |
| 2058 | 2059 | |
| 2059 | 2060 | localStorageaccountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); |
| 2060 | 2061 | } |
| 2061 | 2062 | |
| 2062 | 2063 | |
| @@ -75,6 +75,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | ||
| 75 | 75 | import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; |
| 76 | 76 | import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; |
| 77 | 77 | import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js'; |
| 78 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 78 | 79 | export { |
| 79 | 80 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, |
| 80 | 81 | }; |
| @@ -3606,9 +3607,9 @@ export async function sendMessageAs(args, text) { | ||
| 3606 | 3607 | |
| 3607 | 3608 | if (!name) { |
| 3608 | 3609 | const namelessWarningKey = 'sendAsNamelessWarningShown'; |
| 3609 | 3610 | if (localStorageaccountStorage.getItem(namelessWarningKey) !== 'true') { |
| 3610 | 3611 | toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); |
| 3611 | 3612 | localStorageaccountStorage.setItem(namelessWarningKey, 'true'); |
| 3612 | 3613 | } |
| 3613 | 3614 | name = name2; |
| 3614 | 3615 | } |
| @@ -68,12 +68,14 @@ import { tag_map, tags } from './tags.js'; | ||
| 68 | 68 | import { textgenerationwebui_settings } from './textgen-settings.js'; |
| 69 | 69 | import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js'; |
| 70 | 70 | import { ToolManager } from './tool-calling.js'; |
| 71 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 71 | 72 | import { timestampToMoment, uuidv4 } from './utils.js'; |
| 72 | 73 | import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; |
| 73 | 74 | import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; |
| 74 | 75 | |
| 75 | 76 | export function getContext() { |
| 76 | 77 | return { |
| 78 | + accountStorage, | |
| 77 | 79 | chat, |
| 78 | 80 | characters, |
| 79 | 81 | groups, |
| @@ -6,6 +6,7 @@ import { tokenizers } from './tokenizers.js'; | ||
| 6 | 6 | import { renderTemplateAsync } from './templates.js'; |
| 7 | 7 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 8 | 8 | import { t } from './i18n.js'; |
| 9 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 9 | 10 | |
| 10 | 11 | let mancerModels = []; |
| 11 | 12 | let togetherModels = []; |
| @@ -336,7 +337,7 @@ export async function loadFeatherlessModels(data) { | ||
| 336 | 337 | populateClassSelection(data); |
| 337 | 338 | |
| 338 | 339 | // Retrieve the stored number of items per page or default to 10 |
| 339 | 340 | const perPage = Number(localStorageaccountStorage.getItem(storageKey)) || 10; |
| 340 | 341 | |
| 341 | 342 | // Initialize pagination |
| 342 | 343 | applyFiltersAndSort(); |
| @@ -412,7 +413,7 @@ export async function loadFeatherlessModels(data) { | ||
| 412 | 413 | }, |
| 413 | 414 | afterSizeSelectorChange: function (e) { |
| 414 | 415 | const newPerPage = e.target.value; |
| 415 | 416 | localStorageaccountStorage.setItem('Models_PerPage'storageKey, newPerPage); |
| 416 | 417 | setupPagination(models, Number(newPerPage), featherlessCurrentPage); // Use the stored current page number |
| 417 | 418 | }, |
| 418 | 419 | }); |
| @@ -513,7 +514,7 @@ export async function loadFeatherlessModels(data) { | ||
| 513 | 514 | const currentModelIndex = filteredModels.findIndex(x => x.id === textgen_settings.featherless_model); |
| 514 | 515 | featherlessCurrentPage = currentModelIndex >= 0 ? (currentModelIndex / perPage) + 1 : 1; |
| 515 | 516 | |
| 516 | 517 | setupPagination(filteredModels, Number(localStorageaccountStorage.getItem(storageKey)) || perPage, featherlessCurrentPage); |
| 517 | 518 | } |
| 518 | 519 | |
| 519 | 520 | // Required to keep the /model command function |
| @@ -44,6 +44,14 @@ export function isAdmin() { | ||
| 44 | 44 | } |
| 45 | 45 | |
| 46 | 46 | /** |
| 47 | + * Gets the handle string of the current user. | |
| 48 | + * @returns {string} User handle | |
| 49 | + */ | |
| 50 | +export function getCurrentUserHandle() { | |
| 51 | + return currentUser?.handle || 'default-user'; | |
| 52 | +} | |
| 53 | + | |
| 54 | +/** | |
| 47 | 55 | * Get the current user. |
| 48 | 56 | * @returns {Promise<void>} |
| 49 | 57 | */ |
| @@ -0,0 +1,137 @@ | ||
| 1 | +import { saveSettingsDebounced } from '../../script.js'; | |
| 2 | + | |
| 3 | +const MIGRATED_MARKER = '__migrated'; | |
| 4 | +const MIGRATABLE_KEYS = [ | |
| 5 | + /^AlertRegex_/, | |
| 6 | + /^AlertWI_/, | |
| 7 | + /^Assets_SkipConfirm_/, | |
| 8 | + /^Characters_PerPage$/, | |
| 9 | + /^DataBank_sortField$/, | |
| 10 | + /^DataBank_sortOrder$/, | |
| 11 | + /^extension_update_nag$/, | |
| 12 | + /^extensions_sortByName$/, | |
| 13 | + /^FeatherlessModels_PerPage$/, | |
| 14 | + /^GroupMembers_PerPage$/, | |
| 15 | + /^GroupCandidates_PerPage$/, | |
| 16 | + /^LNavLockOn$/, | |
| 17 | + /^LNavOpened$/, | |
| 18 | + /^mediaWarningShown:/, | |
| 19 | + /^NavLockOn$/, | |
| 20 | + /^NavOpened$/, | |
| 21 | + /^Personas_PerPage$/, | |
| 22 | + /^Personas_GridView$/, | |
| 23 | + /^Proxy_SkipConfirm_/, | |
| 24 | + /^qr--executeShortcut$/, | |
| 25 | + /^qr--syntax$/, | |
| 26 | + /^qr--tabSize$/, | |
| 27 | + /^qr--wrap$/, | |
| 28 | + /^RegenerateWithCtrlEnter$/, | |
| 29 | + /^SelectedNavTab$/, | |
| 30 | + /^sendAsNamelessWarningShown$/, | |
| 31 | + /^StoryStringValidationCache$/, | |
| 32 | + /^WINavOpened$/, | |
| 33 | + /^WI_PerPage$/, | |
| 34 | + /^world_info_sort_order$/, | |
| 35 | +]; | |
| 36 | + | |
| 37 | +/** | |
| 38 | + * Provides access to account storage of arbitrary key-value pairs. | |
| 39 | + */ | |
| 40 | +class AccountStorage { | |
| 41 | + /** | |
| 42 | + * @type {Record<string, string>} Storage state | |
| 43 | + */ | |
| 44 | + #state = {}; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * @type {boolean} If the storage was initialized | |
| 48 | + */ | |
| 49 | + #ready = false; | |
| 50 | + | |
| 51 | + #migrateLocalStorage() { | |
| 52 | + for (let i = 0; i < globalThis.localStorage.length; i++) { | |
| 53 | + const key = globalThis.localStorage.key(i); | |
| 54 | + const value = globalThis.localStorage.getItem(key); | |
| 55 | + | |
| 56 | + if (MIGRATABLE_KEYS.some(k => k.test(key))) { | |
| 57 | + this.#state[key] = value; | |
| 58 | + globalThis.localStorage.removeItem(key); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * Initialize the account storage. | |
| 65 | + * @param {Object} state Initial state | |
| 66 | + */ | |
| 67 | + init(state) { | |
| 68 | + if (state && typeof state === 'object') { | |
| 69 | + this.#state = Object.assign(this.#state, state); | |
| 70 | + } | |
| 71 | + | |
| 72 | + if (!Object.hasOwn(this.#state, MIGRATED_MARKER)) { | |
| 73 | + this.#migrateLocalStorage(); | |
| 74 | + this.#state[MIGRATED_MARKER] = '1'; | |
| 75 | + saveSettingsDebounced(); | |
| 76 | + } | |
| 77 | + | |
| 78 | + this.#ready = true; | |
| 79 | + } | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * Get the value of a key in account storage. | |
| 83 | + * @param {string} key Key to get | |
| 84 | + * @returns {string|null} Value of the key | |
| 85 | + */ | |
| 86 | + getItem(key) { | |
| 87 | + if (!this.#ready) { | |
| 88 | + console.warn(`AccountStorage not ready (trying to read from ${key})`); | |
| 89 | + } | |
| 90 | + | |
| 91 | + return Object.hasOwn(this.#state, key) ? String(this.#state[key]) : null; | |
| 92 | + } | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * Set a key in account storage. | |
| 96 | + * @param {string} key Key to set | |
| 97 | + * @param {string} value Value to set | |
| 98 | + */ | |
| 99 | + setItem(key, value) { | |
| 100 | + if (!this.#ready) { | |
| 101 | + console.warn(`AccountStorage not ready (trying to write to ${key})`); | |
| 102 | + } | |
| 103 | + | |
| 104 | + this.#state[key] = String(value); | |
| 105 | + saveSettingsDebounced(); | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * Remove a key from account storage. | |
| 110 | + * @param {string} key Key to remove | |
| 111 | + */ | |
| 112 | + removeItem(key) { | |
| 113 | + if (!this.#ready) { | |
| 114 | + console.warn(`AccountStorage not ready (trying to remove ${key})`); | |
| 115 | + } | |
| 116 | + | |
| 117 | + if (!Object.hasOwn(this.#state, key)) { | |
| 118 | + return; | |
| 119 | + } | |
| 120 | + | |
| 121 | + delete this.#state[key]; | |
| 122 | + saveSettingsDebounced(); | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * Gets a snapshot of the storage state. | |
| 127 | + * @returns {Record<string, string>} A deep clone of the storage state | |
| 128 | + */ | |
| 129 | + getState() { | |
| 130 | + return structuredClone(this.#state); | |
| 131 | + } | |
| 132 | +} | |
| 133 | + | |
| 134 | +/** | |
| 135 | + * Account storage instance. | |
| 136 | + */ | |
| 137 | +export const accountStorage = new AccountStorage(); | |
| @@ -21,6 +21,7 @@ import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; | ||
| 21 | 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; |
| 22 | 22 | import { renderTemplateAsync } from './templates.js'; |
| 23 | 23 | import { t } from './i18n.js'; |
| 24 | +import { accountStorage } from './util/AccountStorage.js'; | |
| 24 | 25 | |
| 25 | 26 | export const world_info_insertion_strategy = { |
| 26 | 27 | evenly: 0, |
| @@ -872,7 +873,7 @@ export function setWorldInfoSettings(settings, data) { | ||
| 872 | 873 | $('#world_editor_select').append(`<option value='${i}'>${item}</option>`); |
| 873 | 874 | }); |
| 874 | 875 | |
| 875 | 876 | $('#world_info_sort_order').val(localStorageaccountStorage.getItem(SORT_ORDER_KEY) || '0'); |
| 876 | 877 | $('#world_info').trigger('change'); |
| 877 | 878 | $('#world_editor_select').trigger('change'); |
| 878 | 879 | |
| @@ -1947,13 +1948,13 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | ||
| 1947 | 1948 | if (typeof navigation === 'number' && Number(navigation) >= 0) { |
| 1948 | 1949 | const data = getDataArray(); |
| 1949 | 1950 | const uidIndex = data.findIndex(x => x.uid === navigation); |
| 1950 | 1951 | const perPage = Number(localStorageaccountStorage.getItem(storageKey)) || perPageDefault; |
| 1951 | 1952 | startPage = Math.floor(uidIndex / perPage) + 1; |
| 1952 | 1953 | } |
| 1953 | 1954 | |
| 1954 | 1955 | $('#world_info_pagination').pagination({ |
| 1955 | 1956 | dataSource: getDataArray, |
| 1956 | 1957 | pageSize: Number(localStorageaccountStorage.getItem(storageKey)) || perPageDefault, |
| 1957 | 1958 | sizeChangerOptions: [10, 25, 50, 100, 500, 1000], |
| 1958 | 1959 | showSizeChanger: true, |
| 1959 | 1960 | pageRange: 1, |
| @@ -1983,7 +1984,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl | ||
| 1983 | 1984 | worldEntriesList.append(blocks); |
| 1984 | 1985 | }, |
| 1985 | 1986 | afterSizeSelectorChange: function (e) { |
| 1986 | 1987 | localStorageaccountStorage.setItem(storageKey, e.target.value); |
| 1987 | 1988 | }, |
| 1988 | 1989 | afterPaging: function () { |
| 1989 | 1990 | $('#world_popup_entries_list textarea[name="comment"]').each(function () { |
| @@ -2188,7 +2189,7 @@ function verifyWorldInfoSearchSortRule() { | ||
| 2188 | 2189 | // If search got cleared, we make sure to hide the option and go back to the one before |
| 2189 | 2190 | if (!searchTerm && !isHidden) { |
| 2190 | 2191 | searchOption.attr('hidden', ''); |
| 2191 | 2192 | selector.val(localStorageaccountStorage.getItem(SORT_ORDER_KEY) || '0'); |
| 2192 | 2193 | } |
| 2193 | 2194 | } |
| 2194 | 2195 | |
| @@ -4753,8 +4754,8 @@ export function checkEmbeddedWorld(chid) { | ||
| 4753 | 4754 | // Only show the alert once per character |
| 4754 | 4755 | const checkKey = `AlertWI_${characters[chid].avatar}`; |
| 4755 | 4756 | const worldName = characters[chid]?.data?.extensions?.world; |
| 4756 | 4757 | if (!localStorageaccountStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { |
| 4757 | 4758 | localStorageaccountStorage.setItem(checkKey, 'true'); |
| 4758 | 4759 | |
| 4759 | 4760 | if (power_user.world_import_dialog) { |
| 4760 | 4761 | const html = `<h3>This character has an embedded World/Lorebook.</h3> |
| @@ -5198,7 +5199,7 @@ jQuery(() => { | ||
| 5198 | 5199 | $('#world_info_sort_order').on('change', function () { |
| 5199 | 5200 | const value = String($(this).find(':selected').val()); |
| 5200 | 5201 | // Save sort order, but do not save search sorting, as this is a temporary sorting option |
| 5201 | 5202 | if (value !== 'search') localStorageaccountStorage.setItem(SORT_ORDER_KEY, value); |
| 5202 | 5203 | updateEditor(navigation_option.none); |
| 5203 | 5204 | }); |
| 5204 | 5205 | |