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
+
+
+
+
+
+
+
+
+
-