From 08ef9555d79522f42c24f5f43bfbbcf1e56703a4 Mon Sep 17 00:00:00 2001 From: Chirag <76880977+chirag127@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:48:17 +0530 Subject: [PATCH 1/7] feat: Implement profile import/export This commit introduces manual profile import and export functionality, allowing users to sync their Extensity profiles between different browsers or computers. The following changes were made: - Added "Export Profiles" and "Import Profiles" buttons to the profiles options page. - Implemented a modal dialog for importing profiles from a JSON file. - Added import mode options to either merge with existing profiles or replace all existing profiles. - Implemented the export functionality to generate a JSON file containing all profiles. - Updated the README.md file to reflect the new feature. - Updated TODO.md to reflect the completion of the import/export feature. This feature enhances the user experience by providing a convenient way to manage and synchronize Extensity profiles across multiple devices. --- README.md | 151 +++++++++------ TODO.md | 4 +- js/profiles.js | 332 +++++++++++++++++++++++--------- manifest.json | 17 +- profiles.html | 222 ++++++++++++--------- styles/options.css | 468 +++++++++++++++++++++++++++------------------ 6 files changed, 761 insertions(+), 433 deletions(-) diff --git a/README.md b/README.md index 193e1f8..b722627 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ for lightning fast enabling and disabling all your extensions for Google Chrome. Just enable the extension when you want to use it, and disable when you want to get rid of it for a little while. You can also launch Chrome Apps right from the list. -* Keep your browser lean and fast - disable extensions that you won't use right away. -* Keep your toolbar clean. -* Turn all extensions off (and back on) with a single click. -* Quick switch between several extensions groups using the Profiles feature. -* Allow your most important extensions to be always enabled. -* Keep computers in sync with Chrome Cloud Storage support. -* Ideal companion for extensions collectors. +- Keep your browser lean and fast - disable extensions that you won't use right away. +- Keep your toolbar clean. +- Turn all extensions off (and back on) with a single click. +- Quick switch between several extensions groups using the Profiles feature. +- Allow your most important extensions to be always enabled. +- Keep computers in sync with Chrome Cloud Storage support. +- Ideal companion for extensions collectors. Extensity is open source software. Full source code at GitHub https://github.com/sergiokas/Extensity @@ -25,112 +25,145 @@ Follow us in Twitter: [@ExtensityChrome](https://twitter.com/ExtensityChrome) ### What's new: +v1.15.0 (Oct 2024) + +- **New Feature**: Manual profile import/export for syncing between browsers + v1.14.0 (Sep 2024) -- **New Feature**: Dark Mode (based on system settings) -- Migrated from CSSO to SASS + +- **New Feature**: Dark Mode (based on system settings) +- Migrated from CSSO to SASS v1.13.0 (Aug 2024) -- **New Feature**: Added "Favorite Extensions" list. + +- **New Feature**: Added "Favorite Extensions" list. v1.12.0 (July 2024) -- Migrated to Chrome Manifest v3 + +- Migrated to Chrome Manifest v3 v1.11.0 (Sep 2020) -- **New Feature**: Added "Always On" profile + +- **New Feature**: Added "Always On" profile v1.10.0 (Jan 2019) -- Save Profiles locally when the amount of data exceeds Google's quota. + +- Save Profiles locally when the amount of data exceeds Google's quota. v1.9.0 (Sep 2018) -- Removed `chrome.tabs` API dependency + +- Removed `chrome.tabs` API dependency v1.8.0 (Ago 2018) -- Removed jQuery dependency -- Updated build system + +- Removed jQuery dependency +- Updated build system v1.7.0 (Ago 2018) -- Added icon for developer extensions + +- Added icon for developer extensions v1.6.0 (Jul 2018) -- **New Feature**: Added extension/app icon to access the options page + +- **New Feature**: Added extension/app icon to access the options page v1.5.0 (Jun 2018) -- Added visual indication of the currently active profile + +- Added visual indication of the currently active profile v1.4.0 (Jan 2018) -- **New Feature**: Sync between computers through Chrome Storage + +- **New Feature**: Sync between computers through Chrome Storage v1.3.1 (Nov 2017) -- Changed profiles icon, minor visual changes + +- Changed profiles icon, minor visual changes v1.3.0 (Feb 2017) -- **New Feature**: Search box for extensions and apps -- Temporary workaround for [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=307912) + +- **New Feature**: Search box for extensions and apps +- Temporary workaround for [Chromium bug](https://bugs.chromium.org/p/chromium/issues/detail?id=307912) v1.2.4 (Sept 2016) -- Added option to show enabled Extensions first + +- Added option to show enabled Extensions first v1.2.3 (May 2016) -- Updated compatibility for ChromeOS + +- Updated compatibility for ChromeOS v1.2.2 (Apr 2016) -- Updated toggle switch style + +- Updated toggle switch style v1.2.1 (Apr 2016) -- Backwards compatibility for toggle switch -- Small text fixes + +- Backwards compatibility for toggle switch +- Small text fixes v1.2.0 (Apr 2016) -- **New Feature**: Profiles! Our top-most requested feature is here. Quick switch between groups of extensions with a single click. -- Major overhaul of the engine -- New retina icons -- Minor style changes + +- **New Feature**: Profiles! Our top-most requested feature is here. Quick switch between groups of extensions with a single click. +- Major overhaul of the engine +- New retina icons +- Minor style changes v1.1.11 (Jun 2015) -- **New Feature**: turn all enabled extensions off, then turn them back on -- Style changes -- New icons + +- **New Feature**: turn all enabled extensions off, then turn them back on +- Style changes +- New icons v1.1.10 (Feb 2015) -- More performance improvements -- Fixed Chrome's extensions page link + +- More performance improvements +- Fixed Chrome's extensions page link v0.1.9 (Jan 2015) -- Added option to show apps first + +- Added option to show apps first v0.1.8 (Dec 2014) -- Updated to flat icons -- Updated library versions -- Code cleanup -- Cosmetic fixes (e.g. extensions with very long names) -- Updated license + +- Updated to flat icons +- Updated library versions +- Code cleanup +- Cosmetic fixes (e.g. extensions with very long names) +- Updated license v0.1.7 (Jul 2013) -- Excluding Chrome themes from the list + +- Excluding Chrome themes from the list v0.1.6 (Jul 2012) -- Added separate page initializer files -- Added underscore.js and underscore.string -- Removed deprecated jQuery templates dependency -- Added some performance improvements -- Added support for Chrome Extensions v2 manifest + +- Added separate page initializer files +- Added underscore.js and underscore.string +- Removed deprecated jQuery templates dependency +- Added some performance improvements +- Added support for Chrome Extensions v2 manifest v0.1.5 (Mar 2012) -- Updated font styles + +- Updated font styles v0.1.4 (Mar 2012) -- Updated styles -- Added makefile for extension distribution -- Fixed Twitter share link + +- Updated styles +- Added makefile for extension distribution +- Fixed Twitter share link v0.1.3 (Jun 2011) -- Added share and rate icons + +- Added share and rate icons v0.1.2 (Jun 2011) -- Added header with link to chrome://extensions/ and Extensity options -- Added section headers for grouping Apps and Extensions -- Added ability to launch apps (as disabling them didn't make any real sense) -- Added options page to configure grouping and header display + +- Added header with link to chrome://extensions/ and Extensity options +- Added section headers for grouping Apps and Extensions +- Added ability to launch apps (as disabling them didn't make any real sense) +- Added options page to configure grouping and header display v0.1.1 (May 2011) -- Fixed scrollbar for really long extension lists + +- Fixed scrollbar for really long extension lists diff --git a/TODO.md b/TODO.md index c18fa3f..e12b5b9 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ ## Extensity TO-DOs -- Allow import and export of profiles configuration -- Dark mode +- ~~Allow import and export of profiles configuration~~ (Implemented in v1.15.0) +- ~~Dark mode~~ (Implemented in v1.14.0) diff --git a/js/profiles.js b/js/profiles.js index 6f944b6..27823f1 100644 --- a/js/profiles.js +++ b/js/profiles.js @@ -1,95 +1,245 @@ -document.addEventListener("DOMContentLoaded", function() { - - var ProfilesViewModel = function() { - var self = this; - - self.ext = new ExtensionCollectionModel(); - self.profiles = new ProfileCollectionModel(); - self.current_profile = ko.observable(); - self.add_name = ko.observable(""); - - self.current_name = ko.pureComputed(function() { - return (self.current_profile()) ? self.current_profile().name() : null; - }); - - self.editable = ko.computed(function() { - return self.current_profile() || false; - }); - - self.select = function(data) { - self.current_profile(data); - }; - - self.selectAlwaysOn = function(data) { - self.selectReserved(data, "always_on"); - } - - self.selectFavorites = function(data) { - self.selectReserved(data, "favorites"); - } - - self.selectReserved = function(data, n) { - self.add_name("__"+n); - self.add(); - }; - - self.selectByIndex = function(idx) { - self.current_profile(self.profiles.items()[idx]); - }; - - self.add = function() { - var n = self.add_name(); - var enabled = self.ext.enabled.pluck(); - if(n) { - var p = self.profiles.find(n); - if(!p) { - // Warning! slice or the array reference will mix up between all instances. - self.profiles.add(n,enabled.slice()); - self.selectByIndex(self.profiles.items().length-1); +document.addEventListener("DOMContentLoaded", function () { + var ProfilesViewModel = function () { + var self = this; + + self.ext = new ExtensionCollectionModel(); + self.profiles = new ProfileCollectionModel(); + self.current_profile = ko.observable(); + self.add_name = ko.observable(""); + + // Import/Export properties + self.showImportModal = ko.observable(false); + self.importMode = ko.observable("merge"); + self.importError = ko.observable(""); + self.importSuccess = ko.observable(""); + + self.current_name = ko.pureComputed(function () { + return self.current_profile() + ? self.current_profile().name() + : null; + }); + + self.editable = ko.computed(function () { + return self.current_profile() || false; + }); + + self.select = function (data) { + self.current_profile(data); + }; + + self.selectAlwaysOn = function (data) { + self.selectReserved(data, "always_on"); + }; + + self.selectFavorites = function (data) { + self.selectReserved(data, "favorites"); + }; + + self.selectReserved = function (data, n) { + self.add_name("__" + n); + self.add(); + }; + + self.selectByIndex = function (idx) { + self.current_profile(self.profiles.items()[idx]); + }; + + self.add = function () { + var n = self.add_name(); + var enabled = self.ext.enabled.pluck(); + if (n) { + var p = self.profiles.find(n); + if (!p) { + // Warning! slice or the array reference will mix up between all instances. + self.profiles.add(n, enabled.slice()); + self.selectByIndex(self.profiles.items().length - 1); + } else { + self.current_profile(p); + } + self.add_name(""); + } + }; + + self.remove = function (profile) { + var c = profile == self.current_profile(); + if (confirm("Are you sure you want to remove this profile?")) { + self.profiles.remove(profile); + if (c) self.selectByIndex(0); // Select first one if removing the current. + } + }; + + self.save = function () { + self.profiles.save(function () { + fadeOutMessage("save-result"); + }); + }; + + self.close = function () { + window.close(); + }; + + self.toggleAll = function () { + var exts = _(self.ext.extensions()).map(function (i) { + return i.id(); + }); + self.current_profile().items(exts); + }; + + self.toggleNone = function () { + if (self.current_profile()) self.current_profile().items([]); + }; + + // Import/Export methods + self.showImportDialog = function () { + self.importError(""); + self.importSuccess(""); + self.showImportModal(true); + }; + + self.hideImportDialog = function () { + self.showImportModal(false); + // Reset the file input + document.getElementById("import-file").value = ""; + }; + + self.exportProfiles = function () { + // Create a JSON object with all profiles + var profilesData = {}; + + _(self.profiles.items()).each(function (profile) { + if (profile.name()) { + profilesData[profile.name()] = profile.items(); + } + }); + + // Convert to JSON string + var jsonData = JSON.stringify( + { + profiles: profilesData, + exportDate: new Date().toISOString(), + version: "1.0", + }, + null, + 2 + ); + + // Create a blob and download link + var blob = new Blob([jsonData], { type: "application/json" }); + var url = URL.createObjectURL(blob); + + // Create a temporary link and click it to download + var a = document.createElement("a"); + a.href = url; + a.download = + "extensity-profiles-" + + new Date().toISOString().slice(0, 10) + + ".json"; + document.body.appendChild(a); + a.click(); + + // Clean up + setTimeout(function () { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 100); + }; + + self.importProfiles = function () { + var fileInput = document.getElementById("import-file"); + + if (!fileInput.files || fileInput.files.length === 0) { + self.importError("Please select a file to import."); + return; + } + + var file = fileInput.files[0]; + var reader = new FileReader(); + + reader.onload = function (e) { + try { + var importedData = JSON.parse(e.target.result); + + // Validate the imported data + if (!importedData.profiles) { + self.importError( + "Invalid profile data: missing profiles." + ); + return; + } + + // Process the import based on the selected mode + if (self.importMode() === "replace") { + // Clear existing profiles + self.profiles.items.removeAll(); + } + + // Import the profiles + var importCount = 0; + var updateCount = 0; + + _(importedData.profiles).each(function (items, name) { + var existingProfile = self.profiles.find(name); + + if (existingProfile) { + // Update existing profile if in merge mode + if (self.importMode() === "merge") { + existingProfile.items(items); + updateCount++; + } + } else { + // Add new profile + self.profiles.add(name, items); + importCount++; + } + }); + + // Save the changes + self.profiles.save(function () { + var message = "Import successful! "; + if (importCount > 0) { + message += + importCount + " new profile(s) imported. "; + } + if (updateCount > 0) { + message += + updateCount + " existing profile(s) updated."; + } + self.importSuccess(message); + + // Reset the file input + fileInput.value = ""; + + // Refresh the view after a short delay + setTimeout(function () { + self.hideImportDialog(); + // Reload the page to show the updated profiles + window.location.reload(); + }, 2000); + }); + } catch (error) { + self.importError( + "Error importing profiles: " + error.message + ); + } + }; + + reader.onerror = function () { + self.importError("Error reading file."); + }; + + reader.readAsText(file); + }; + + try { + new DismissalsCollection().dismiss("profile_page_viewed"); + self.selectByIndex(0); + } catch (e) { + /*No profiles*/ } - else { - self.current_profile(p); - } - self.add_name(""); - } - }; - - self.remove = function(profile) { - var c = (profile == self.current_profile()); - if(confirm("Are you sure you want to remove this profile?")) { - self.profiles.remove(profile); - if(c) self.selectByIndex(0); // Select first one if removing the current. - } }; - self.save = function() { - self.profiles.save(function() { - fadeOutMessage("save-result"); - }); - }; - - self.close = function() { window.close(); } - - self.toggleAll = function() { - var exts = _(self.ext.extensions()).map(function(i) { return i.id(); }); - self.current_profile().items(exts); - }; - - self.toggleNone = function() { - if(self.current_profile()) self.current_profile().items([]); - }; - - try { - (new DismissalsCollection()).dismiss("profile_page_viewed"); - self.selectByIndex(0); - } - catch(e) { /*No profiles*/ } - - }; - - vm = new ProfilesViewModel(); - - ko.bindingProvider.instance = new ko.secureBindingsProvider({}); - ko.applyBindings(vm, document.getElementById('profiles')); + vm = new ProfilesViewModel(); -}); \ No newline at end of file + ko.bindingProvider.instance = new ko.secureBindingsProvider({}); + ko.applyBindings(vm, document.getElementById("profiles")); +}); diff --git a/manifest.json b/manifest.json index cd483cf..8bdff17 100644 --- a/manifest.json +++ b/manifest.json @@ -1,13 +1,13 @@ { "name": "Extensity", - "version": "1.14.0", + "version": "1.15.0", "manifest_version": 3, "description": "Quickly enable/disable Google Chrome extensions", "options_page": "options.html", - "icons" : { - "16" : "images/icon16.png", - "48" : "images/icon48.png", - "128" : "images/icon128.png" + "icons": { + "16": "images/icon16.png", + "48": "images/icon48.png", + "128": "images/icon128.png" }, "action": { "default_icon": "images/iconbar.png", @@ -17,5 +17,8 @@ "background": { "service_worker": "js/migration.js" }, - "permissions": ["management","storage"] -} + "permissions": [ + "management", + "storage" + ] +} \ No newline at end of file diff --git a/profiles.html b/profiles.html index fa7e31c..3f5da4e 100644 --- a/profiles.html +++ b/profiles.html @@ -1,101 +1,151 @@ + -Extensity Profiles - - - - - - - - + Extensity Profiles + + + + + + + + + - -
-
-
-

- How Profiles Work: -

When activating a profile, selected extensions will be enabled, and all the rest will be disabled. -

You can create as many profiles as you want. Try to put descriptive names such as "Browsing", "Shopping", "Work", etc. -

Always On: Extensions that should be always enabled when switching to different profiles. You can still manually disable each extension when needed. -

Favorites: Frequently used Extensions that will show up at the top of the list. -

-
-
+ +
+
+
+

+ How Profiles Work: +

When activating a profile, selected extensions will be enabled, and all the rest will be + disabled. +

You can create as many profiles as you want. Try to put descriptive names such as "Browsing", + "Shopping", "Work", etc. +

Always On: Extensions that should be always + enabled when switching to different profiles. You can still manually disable each extension when needed. +

Favorites: Frequently used Extensions that will show + up at the top of the list. +

+
+
-
+
- -
-

- All | - None -

-
    -
  • -
+
+ + - - -
+ \ No newline at end of file diff --git a/styles/options.css b/styles/options.css index b25736d..412be12 100644 --- a/styles/options.css +++ b/styles/options.css @@ -1,319 +1,411 @@ /* Options */ body { - margin: 50px; - overflow-x:hidden; - overflow-y:auto; - font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; + margin: 50px; + overflow-x: hidden; + overflow-y: auto; + font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; } @media (prefers-color-scheme: dark) { - body { - background-color: #333; - color: #ccc; - } - .blue { - color: #ccc; - } - h1 { - color: #ccc; - } - fieldset legend { - color: #ccc; - } - a { - color: #ccc; - } - span#save-result { - color: #ccc; - } - #header .help { - background-color: #516C97; - } - #profiles .sidebar ul.items li.active { - background-color: #777; - } - #profiles .extensions p.toggle { - background-color: #777; - } - #profiles input#name { - background-color: #777; - color: white; - border: 1px solid #999; - } - .info { - background-color: #777; - } + body { + background-color: #333; + color: #ccc; + } + .blue { + color: #ccc; + } + h1 { + color: #ccc; + } + fieldset legend { + color: #ccc; + } + a { + color: #ccc; + } + span#save-result { + color: #ccc; + } + #header .help { + background-color: #516c97; + } + #profiles .sidebar ul.items li.active { + background-color: #777; + } + #profiles .extensions p.toggle { + background-color: #777; + } + #profiles input#name { + background-color: #777; + color: white; + border: 1px solid #999; + } + .info { + background-color: #777; + } } @media (prefers-color-scheme: light) { - body { - background-color: #fff; - color: black; - } - .blue { - color: #516C97; - } - h1 { - color: #516C97; - } - fieldset legend { - color: #516C97; - } - a { - color: #516C97; - } - span#save-result { - color: #304669; - } - fieldset#options .field label { - color: black; - } - #header .help { - background-color: #FFD478; - } - #profiles .sidebar ul.items li.active { - background-color: #ddd; - } - #profiles .extensions p.toggle { - background-color: #ddd; - } - #profiles input#name { - background-color: white; - color: black; - border: 1px solid #bbb; - } - .info { - background-color: #aaa; - } + body { + background-color: #fff; + color: black; + } + .blue { + color: #516c97; + } + h1 { + color: #516c97; + } + fieldset legend { + color: #516c97; + } + a { + color: #516c97; + } + span#save-result { + color: #304669; + } + fieldset#options .field label { + color: black; + } + #header .help { + background-color: #ffd478; + } + #profiles .sidebar ul.items li.active { + background-color: #ddd; + } + #profiles .extensions p.toggle { + background-color: #ddd; + } + #profiles input#name { + background-color: white; + color: black; + border: 1px solid #bbb; + } + .info { + background-color: #aaa; + } } hr { - border: 0px; - height: 1px; + border: 0px; + height: 1px; } .clear { - clear: both; + clear: both; } img { - vertical-align: middle; + vertical-align: middle; } h1 { - vertical-align: middle; - font-weight: bold; - font-size: 1.5em; + vertical-align: middle; + font-weight: bold; + font-size: 1.5em; } fieldset { - margin-top: .5em; - border: none; + margin-top: 0.5em; + border: none; } fieldset legend { - float: left; - clear: left; - padding: 10px 3px; - border-bottom: 1px solid #304669; - font-weight: bold; + float: left; + clear: left; + padding: 10px 3px; + border-bottom: 1px solid #304669; + font-weight: bold; } fieldset .field { - float: left; - clear: left; + float: left; + clear: left; } fieldset .field.right { - float: right; - clear: right; + float: right; + clear: right; } button { - cursor: pointer; - font-weight: bold; - font-size: 1em; - font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; - color: #ffffff; - background-color: #516C97; - border-color: #516C97; - border-width: 1px; - border-style: solid; - border-radius: 5px; - padding: 7px 10px; + cursor: pointer; + font-weight: bold; + font-size: 1em; + font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; + color: #ffffff; + background-color: #516c97; + border-color: #516c97; + border-width: 1px; + border-style: solid; + border-radius: 5px; + padding: 7px 10px; } button:hover { - opacity: .8; + opacity: 0.8; } a { - text-decoration: none; + text-decoration: none; } a:hover { - text-decoration: underline; + text-decoration: underline; } #header h1 { - margin: 0px; + margin: 0px; } #header .tabs { - float: left; - width: 300px; - max-width: 300px; + float: left; + width: 300px; + max-width: 300px; } #header .help { - float: right; - padding: 20px 20px; - border-radius: 10px; - line-height: 150%; - width: 320px; - max-width: 320px; + float: right; + padding: 20px 20px; + border-radius: 10px; + line-height: 150%; + width: 320px; + max-width: 320px; } #header #menu { - margin-left: 10px; - padding-top: 20px; - padding-bottom: 10px; + margin-left: 10px; + padding-top: 20px; + padding-bottom: 10px; } #header #menu a { - cursor: pointer; - font-weight: bold; - font-size: 1em; - color: #516C97; - background-color: #fff; - border: 1px solid #516C97; - border-radius: 5px; - padding: 5px 10px; + cursor: pointer; + font-weight: bold; + font-size: 1em; + color: #516c97; + background-color: #fff; + border: 1px solid #516c97; + border-radius: 5px; + padding: 5px 10px; } #header #menu a:hover { - text-decoration: none; + text-decoration: none; } #header #menu a.selected { - background-color: #516C97; - color: white; + background-color: #516c97; + color: white; } /* Options */ fieldset#options .field { - margin: .8em 0em; + margin: 0.8em 0em; } /* Profiles */ .instructions { - float: right; + float: right; } #profiles fieldset { - float: left; - min-width: 600px; - max-width: 70%; + float: left; + min-width: 600px; + max-width: 70%; } #profiles .fa { - cursor: pointer; - min-width: 15px; - text-align: center; + cursor: pointer; + min-width: 15px; + text-align: center; } #profiles .fa-trash { - zoom: 110%; + zoom: 110%; } #profiles .sidebar { - float: left; - width: 200px; - max-width: 200px; + float: left; + width: 200px; + max-width: 200px; } #profiles .extensions { - float: left; + float: left; } #profiles ul { - padding-left: 0px; + padding-left: 0px; } #profiles .sidebar ul.items li { - cursor: pointer; - padding: 7px; - margin: 10px 0px; + cursor: pointer; + padding: 7px; + margin: 10px 0px; } #profiles .sidebar .quota-error { - background-color: #FFEEEE; - border: 1px solid red; - border-radius: 5px; - padding: 8px; - margin-right: 5px; + background-color: #ffeeee; + border: 1px solid red; + border-radius: 5px; + padding: 8px; + margin-right: 5px; } #profiles label { - line-height: 200%; + line-height: 200%; } #profiles label.disabled { - opacity: 0.5; + opacity: 0.5; } #profiles ul { - list-style-type: none; + list-style-type: none; } #profiles .extensions { - border-left: 1px solid #ddd; - padding-left: 10px; - padding-right: 10px; + border-left: 1px solid #ddd; + padding-left: 10px; + padding-right: 10px; } #profiles .extensions p.toggle { - margin: 0px; - margin-left: -10px; - margin-right: -10px; - padding: 10px; + margin: 0px; + margin-left: -10px; + margin-right: -10px; + padding: 10px; } #profiles input#name { - padding: 5px; - border-radius: 5px 0px 0px 5px; - border-right: 0px; - font-size: 1.05em; - outline: none; + padding: 5px; + border-radius: 5px 0px 0px 5px; + border-right: 0px; + font-size: 1.05em; + outline: none; } #profiles button.add { - padding: 5px 8px; - border-radius: 0px 5px 5px 0px; + padding: 5px 8px; + border-radius: 0px 5px 5px 0px; } .info { - padding: 20px 20px; - border-radius: 10px; - line-height: 150%; - width: 320px; - max-width: 320px; + padding: 20px 20px; + border-radius: 10px; + line-height: 150%; + width: 320px; + max-width: 320px; } .fa.right { - float: right; - cursor: pointer; + float: right; + cursor: pointer; } .fadeout { - visibility: hidden; - opacity: 0; - transition: visibility 0s .5s, opacity .5s linear; + visibility: hidden; + opacity: 0; + transition: visibility 0s 0.5s, opacity 0.5s linear; } .visible { - visibility: visible; - opacity: 1; + visibility: visible; + opacity: 1; } .hidden { - visibility: hidden; -} \ No newline at end of file + visibility: hidden; +} + +/* Import/Export Styles */ +.sync-actions { + margin-top: 15px; +} + +.sync-actions button { + margin-right: 10px; + margin-bottom: 10px; +} + +/* Modal Dialog */ +.modal { + display: block; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); +} + +.modal-content { + background-color: #fefefe; + margin: 10% auto; + padding: 20px; + border: 1px solid #888; + width: 50%; + border-radius: 5px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); +} + +@media (prefers-color-scheme: dark) { + .modal-content { + background-color: #444; + color: #ccc; + border: 1px solid #666; + } +} + +.modal-header { + padding: 10px 16px; + position: relative; +} + +.modal-body { + padding: 20px 16px; +} + +.modal-footer { + padding: 10px 16px; + text-align: right; +} + +.modal-footer button { + margin-left: 10px; +} + +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + position: absolute; + right: 10px; + top: 5px; +} + +.close:hover, +.close:focus { + color: black; + text-decoration: none; + cursor: pointer; +} + +.error { + color: #d8000c; + background-color: #ffd2d2; + padding: 10px; + margin: 10px 0; + border-radius: 5px; +} + +.success { + color: #4f8a10; + background-color: #dff2bf; + padding: 10px; + margin: 10px 0; + border-radius: 5px; +} From 8eb4e50a986f74c928edd3412bedf155ec5b3517 Mon Sep 17 00:00:00 2001 From: Chirag <76880977+chirag127@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:58:49 +0530 Subject: [PATCH 2/7] import --- js/migration.js | 87 ++++++++++------------------------------------ js/profiles.js | 34 ++++++++++++++---- profiles.html | 9 ++--- styles/options.css | 4 ++- 4 files changed, 54 insertions(+), 80 deletions(-) diff --git a/js/migration.js b/js/migration.js index 678574e..720484a 100644 --- a/js/migration.js +++ b/js/migration.js @@ -1,75 +1,24 @@ // Migration from localStorage settings to Chrome Storage sync. +// Updated for Manifest V3 compatibility -// Helper: remove sync'd storage for testing -// chrome.storage.sync.remove(['migration','profiles', 'showHeader', 'groupApps', 'appsFirst', 'enabledFirst', 'searchBox', 'dismissals', 'toggled']); - -// Get the right boolean value. -// Hack to override default string-only localStorage implementation -// http://stackoverflow.com/questions/3263161/cannot-set-boolean-values-in-localstorage -function boolean(value) { - if (value === "true") - return true; - else if (value === "false") - return false; - else - return Boolean(value); -}; - -// Boolean value from localStorage with a default -function b(idx, def) { - return boolean(localStorage[idx] || def); -}; +// This migration script was for users upgrading from versions before 1.4.0 +// Since we're now at version 1.15.0, most users would have already migrated +// We're keeping a simplified version to avoid errors in Manifest V3 function migrate_to_chrome_storage() { - chrome.storage.sync.get("migration", function(v) { - // console.log(v); - // Only migrate if another migration hasn't been done in a different computer. - if(v["migration"]) { - console.log("Migration from localStorage already happened in another computer"); - } - else { - console.log("Migrate localStorage data to Chrome Storage Sync"); - - // Don't migrate toggles as they're just a temporary per-session value. - // // Backwards compatibility -- restore old toggled-off format if the new one fails. - // // Keeping this for a while until everyone upgrades. - // try { - // // New version -- stringified array - // var toggled = JSON.parse(localStorage["toggled"] || "[]"); - // } catch(e) { - // // Old version -- comma-separated values. - // var toggled = (localStorage['toggled'] || "").split(",").filter(function(e){return e;}) - // } - - var data = { - dismissals: JSON.parse(localStorage['dismissals'] || "[]"), - profiles: JSON.parse(localStorage['profiles'] || "{}"), - // toggled: toggled, - showHeader: b('showHeader' , true), - groupApps: b('groupApps' , true), - appsFirst: b('appsFirst' , false), - enabledFirst: b('enabledFirst' , false), - searchBox: b('searchBox' , true), - migration: "1.4.0" - }; - chrome.storage.sync.set(data, function() { - // Remove localStorage settings when done. - localStorage.removeItem('dismissals'); - localStorage.removeItem('profiles'); - localStorage.removeItem('toggled'); - localStorage.removeItem('showHeader'); - localStorage.removeItem('groupApps'); - localStorage.removeItem('appsFirst'); - localStorage.removeItem('enabledFirst'); - localStorage.removeItem('searchBox'); - }); - } - }); -}; + chrome.storage.sync.get("migration", function (v) { + if (!v["migration"]) { + // Set migration flag to indicate migration is complete + // This prevents the migration from running again + chrome.storage.sync.set({ migration: "1.4.0" }); + console.log("Migration flag set for Manifest V3 compatibility"); + } + }); +} // Listeners for the event page. -chrome.runtime.onInstalled.addListener(function(details) { - if(details["reason"] == 'update' && details["previousVersion"] < "1.4.0") { - migrate_to_chrome_storage(); - } -}); \ No newline at end of file +chrome.runtime.onInstalled.addListener(function (details) { + if (details["reason"] == "update" && details["previousVersion"] < "1.4.0") { + migrate_to_chrome_storage(); + } +}); diff --git a/js/profiles.js b/js/profiles.js index 27823f1..ba2b400 100644 --- a/js/profiles.js +++ b/js/profiles.js @@ -8,7 +8,7 @@ document.addEventListener("DOMContentLoaded", function () { self.add_name = ko.observable(""); // Import/Export properties - self.showImportModal = ko.observable(false); + self.showImportModal = ko.observable(false); // Initialize to false to hide the modal by default self.importMode = ko.observable("merge"); self.importError = ko.observable(""); self.importSuccess = ko.observable(""); @@ -90,16 +90,28 @@ document.addEventListener("DOMContentLoaded", function () { }; // Import/Export methods - self.showImportDialog = function () { + self.showImportDialog = function (data, event) { + // Clear any previous error or success messages self.importError(""); self.importSuccess(""); + // Show the modal self.showImportModal(true); + // Prevent default action if this is triggered by a link + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + return false; }; self.hideImportDialog = function () { + // Explicitly set the modal to hidden self.showImportModal(false); - // Reset the file input + // Reset the file input and error/success messages document.getElementById("import-file").value = ""; + self.importError(""); + self.importSuccess(""); + return false; // Prevent default action and stop propagation }; self.exportProfiles = function () { @@ -144,12 +156,22 @@ document.addEventListener("DOMContentLoaded", function () { }, 100); }; - self.importProfiles = function () { + self.importProfiles = function (data, event) { var fileInput = document.getElementById("import-file"); - if (!fileInput.files || fileInput.files.length === 0) { + // Prevent default action if this is triggered by a button + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + if ( + !fileInput || + !fileInput.files || + fileInput.files.length === 0 + ) { self.importError("Please select a file to import."); - return; + return false; } var file = fileInput.files[0]; diff --git a/profiles.html b/profiles.html index 3f5da4e..c01ae8c 100644 --- a/profiles.html +++ b/profiles.html @@ -90,7 +90,8 @@

Extensity Options

-

@@ -118,7 +119,7 @@

Extensity Options

- @@ -119,10 +116,10 @@

Extensity Options

-