diff --git a/src/scripts/main.js b/src/scripts/main.js index af0ccb8..f49fbde 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,5 +1,37 @@ const SPECIAL_CHARS = '^$&+?.()|{}[]/'.split(''); const browser = chrome; + +// Synchronous, in-memory mirror of chrome.storage.local. It is preloaded +// before AngularJS bootstraps (see the bottom of this file) so that the +// existing synchronous `localStorage`-style access keeps working, while every +// write is mirrored back to chrome.storage.local (the source of truth in MV3). +const _storeCache = {}; +const store = new Proxy(_storeCache, { + get(target, prop) { + if (prop === 'getItem') { + return (key) => target[key]; + } + if (prop === 'setItem') { + return (key, value) => { + target[key] = String(value); + chrome.storage.local.set({ [key]: String(value) }); + }; + } + if (prop === 'removeItem') { + return (key) => { + delete target[key]; + chrome.storage.local.remove(key); + }; + } + return target[prop]; + }, + set(target, prop, value) { + target[prop] = String(value); + chrome.storage.local.set({ [prop]: String(value) }); + return true; + } +}); + const modHeader = angular.module('modheader-popup', ['ngMaterial']); modHeader.config(['$compileProvider', function ($compileProvider) { $compileProvider.debugInfoEnabled(false); @@ -43,9 +75,9 @@ modHeader.factory('dataSource', function($mdToast) { dataSource.addFilter = function(filters) { let urlRegex = ''; - if (localStorage.currentTabUrl) { + if (store.currentTabUrl) { const parser = document.createElement('a'); - parser.href = localStorage.currentTabUrl; + parser.href = store.currentTabUrl; urlRegex = parser.origin + '/.*'; } filters.push({ @@ -81,7 +113,7 @@ modHeader.factory('dataSource', function($mdToast) { dataSource.pause = function() { dataSource.isPaused = true; - localStorage.isPaused = true; + store.isPaused = true; $mdToast.show( $mdToast.simple() .content('ModHeader paused') @@ -92,7 +124,7 @@ modHeader.factory('dataSource', function($mdToast) { dataSource.play = function() { dataSource.isPaused = false; - localStorage.removeItem('isPaused'); + store.removeItem('isPaused'); $mdToast.show( $mdToast.simple() .content('ModHeader unpaused') @@ -102,8 +134,8 @@ modHeader.factory('dataSource', function($mdToast) { }; dataSource.lockToTab = function() { - dataSource.lockedTabId = localStorage.activeTabId; - localStorage.lockedTabId = dataSource.lockedTabId; + dataSource.lockedTabId = store.activeTabId; + store.lockedTabId = dataSource.lockedTabId; $mdToast.show( $mdToast.simple() .content('Restricted ModHeader to the current tab') @@ -114,7 +146,7 @@ modHeader.factory('dataSource', function($mdToast) { dataSource.unlockAllTab = function() { dataSource.lockedTabId = null; - localStorage.removeItem('lockedTabId'); + store.removeItem('lockedTabId'); $mdToast.show( $mdToast.simple() .content('Applying ModHeader to all tabs') @@ -152,8 +184,8 @@ modHeader.factory('dataSource', function($mdToast) { dataSource.predicate = ''; dataSource.reverse = false; - if (localStorage.profiles) { - dataSource.profiles = angular.fromJson(localStorage.profiles); + if (store.profiles) { + dataSource.profiles = angular.fromJson(store.profiles); for (let profile of dataSource.profiles) { fixProfile(profile); } @@ -182,23 +214,23 @@ modHeader.factory('dataSource', function($mdToast) { profile.appendMode = ''; } }); - if (localStorage.selectedProfile) { - dataSource.selectedProfile = dataSource.profiles[Number(localStorage.selectedProfile)]; + if (store.selectedProfile) { + dataSource.selectedProfile = dataSource.profiles[Number(store.selectedProfile)]; } if (!dataSource.selectedProfile) { dataSource.selectedProfile = dataSource.profiles[0]; } - if (localStorage.isPaused) { - dataSource.isPaused = localStorage.isPaused; + if (store.isPaused) { + dataSource.isPaused = store.isPaused; } - if (localStorage.lockedTabId) { - dataSource.lockedTabId = localStorage.lockedTabId; + if (store.lockedTabId) { + dataSource.lockedTabId = store.lockedTabId; } dataSource.save = function() { var serializedProfiles = angular.toJson(dataSource.profiles); var selectedProfileIndex = dataSource.profiles.indexOf(dataSource.selectedProfile); - localStorage.profiles = serializedProfiles; - localStorage.selectedProfile = selectedProfileIndex; + store.profiles = serializedProfiles; + store.selectedProfile = selectedProfileIndex; }; return dataSource; }); @@ -338,71 +370,6 @@ modHeader.factory('profileService', function( } }; - profileService.openCloudBackup = (event) => { - const parentEl = angular.element(document.body); - $mdDialog.show({ - parent: parentEl, - targetEvent: event, - focusOnOpen: false, - templateUrl: 'cloudbackupdialog.tmpl.html', - controller: DialogController_ - }).then((profiles) => { - if (!profiles) { - return; - } - try { - dataSource.profiles = profiles; - dataSource.selectedProfile = dataSource.profiles[0]; - dataSource.save(); - - $mdToast.show( - $mdToast.simple() - .content('Profiles successfully import') - .position('top') - .hideDelay(1000) - ); - } catch (e) { - $mdToast.show( - $mdToast.simple() - .content('Failed to import profiles') - .position('top') - .hideDelay(1000) - ); - } - }); - function DialogController_($scope, $mdDialog) { - browser.storage.sync.get(null, (items) => { - let savedData = []; - if (!items) { - items = []; - } - for (const key in items) { - try { - const serializedProfiles = items[key]; - const profiles = angular.fromJson(serializedProfiles); - for (let profile of profiles) { - fixProfile(profile); - } - savedData.push({ - 'timeInMs': key, - 'profiles': profiles, - }); - } catch(e) { - // skip invalid profile. - } - } - $scope.savedData = savedData; - }); - - $scope.selectProfiles = function(profiles) { - $mdDialog.hide(profiles); - }; - - $scope.closeDialog = function() { - $mdDialog.hide(); - }; - } - }; return profileService; }); @@ -558,6 +525,15 @@ modHeader.controller('AppController', function( $scope.dataSource = dataSource; $scope.profileService = profileService; + // chrome.storage.local writes are async and may not flush during the popup's + // unload, so persist eagerly on every change instead of relying on onunload. + $scope.$watch('dataSource.profiles', function() { + dataSource.save(); + }, true); + $scope.$watch('dataSource.selectedProfile', function() { + dataSource.save(); + }); + const tips = [ {text: 'Tip: You can switch between multiple profile'}, {text: 'Tip: You can export your profile to share with others'}, @@ -569,19 +545,6 @@ modHeader.controller('AppController', function( {text: 'Tip: Go to profile setting to toggle comment column'}, {text: 'Tip: Append header value to existing one in profile setting'}, {text: 'Tip: Pause button will temporarily pause all modifications'}, - {text: 'Tip: Go to cloud backup to retrieve your auto-synced profile'}, - { - text: 'If you like ModHeader, please consider donating', - buttonText: 'Donate', - url: 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=3XFKZ8PCRB8P6¤cy_code=USD&amount=5&source=url' - }, - { - text: 'Enjoying ModHeader, leave us a review', - buttonText: 'Review', - url: navigator.userAgent.indexOf('Firefox') >= 0 - ? 'https://addons.mozilla.org/firefox/addon/modheader-firefox/' - : 'https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj' - }, ]; const tip = tips[Math.floor(Math.random() * tips.length)]; $mdToast.show({ @@ -602,3 +565,21 @@ modHeader.controller('ToastCtrl', function($mdToast, $mdDialog, $document, $scop browser.tabs.create({url: url}); }; }); + +// Preload chrome.storage.local into the synchronous cache, migrate any legacy +// MV2 localStorage data on first run, then bootstrap AngularJS manually. +document.addEventListener('DOMContentLoaded', async () => { + let items = await chrome.storage.local.get(null); + if ((!items || !items.profiles) + && window.localStorage && window.localStorage.getItem('profiles')) { + const legacy = {}; + for (let i = 0; i < window.localStorage.length; ++i) { + const key = window.localStorage.key(i); + legacy[key] = window.localStorage.getItem(key); + } + await chrome.storage.local.set(legacy); + items = Object.assign({}, items, legacy); + } + Object.assign(_storeCache, items || {}); + angular.bootstrap(document.body, ['modheader-popup']); +}); diff --git a/src/styles/_DS_Store b/src/styles/_DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0