diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..8c715ed
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,65 @@
+name: CI
+
+# Controls when the workflow will run
+# For branch protection rules, you can also use the "push" event but with the "branches"
+# or "tags" filters to specify which branches or tags should trigger the workflow.
+# For example, only trigger on push to the "main" branch.
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel.
+# For more information, see: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions
+
+jobs:
+ # This job builds the extension and runs linters/tests.
+ build-and-test:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository into the current runner environment
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ # Set up Node.js environment. This uses the Node.js 20.x version.
+ # Consider updating to the latest LTS or the version specified in your project's .nvmrc or package.json.
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ # Install dependencies using npm
+ - name: Install Dependencies
+ run: npm ci
+
+ # Run linters and formatters. Assuming Biome is configured and the run command is `biome check --apply` for apply, or `biome ci` for checking.
+ # If you use Ruff for JS/TS, adjust accordingly. The prompt implies a JS project, so npm/Biome is most likely.
+ - name: Lint and Format Check
+ run: npm run lint # Assuming 'lint' script runs Biome check
+
+ # Run tests using Vitest. Assuming 'test' script runs Vitest.
+ - name: Run Unit Tests
+ run: npm test # Assuming 'test' script runs Vitest
+
+ # Optional: If Playwright is used for E2E tests, add a step for it.
+ # - name: Run E2E Tests
+ # run: npm run test:e2e # Assuming 'test:e2e' script runs Playwright
+
+ # Optional: Build the extension artifact.
+ # This assumes a build script exists, e.g., `npm run build` which outputs to a 'dist' or 'build' folder.
+ # The exact command might depend on your Vite configuration for Chrome Extensions.
+ - name: Build Extension
+ run: npm run build # Assuming 'build' script creates extension files
+
+ # Optional: Upload build artifacts for later use (e.g., release or deployment).
+ # Adjust 'path' to your build output directory.
+ - name: Upload Build Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: chrome-extension-build
+ path: dist # Or 'build', or wherever your build output is located
diff --git a/.gitignore b/.gitignore
index a9ba450..958ae8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,23 @@
-dist/
-.project
-.vscode
+# Node/NPM
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+node_modules/
+package-lock.json
+yarn.lock
+
+# Build output
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Editor directories and files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Misc
+.DS_Store
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37cb461
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,5 @@
+Copyright © 2025 chirag127
+
+This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
+
+---
\ No newline at end of file
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/badges.yml b/badges.yml
new file mode 100644
index 0000000..e46f151
--- /dev/null
+++ b/badges.yml
@@ -0,0 +1,18 @@
+build_status:
+ - url: 'https://github.com/chirag127/Chrome-Extension-Manager-Utility/actions/workflows/ci.yml'
+ img: 'https://img.shields.io/github/actions/workflow/status/chirag127/Chrome-Extension-Manager-Utility/ci.yml?style=flat-square'
+code_coverage:
+ - url: 'https://app.codecov.io/github/chirag127/Chrome-Extension-Manager-Utility'
+ img: 'https://img.shields.io/codecov/c/github/chirag127/Chrome-Extension-Manager-Utility?style=flat-square'
+tech_stack:
+ - img: 'https://img.shields.io/badge/language-javascript-323330?style=flat-square&logo=javascript'
+ - img: 'https://img.shields.io/badge/framework-vite-646CFF?style=flat-square&logo=vite'
+ - img: 'https://img.shields.io/badge/css-tailwind-38B2AC?style=flat-square&logo=tailwindcss'
+lint_format:
+ - img: 'https://img.shields.io/badge/linter-biome-6F7E88?style=flat-square&logo=biome'
+license:
+ - url: 'https://github.com/chirag127/Chrome-Extension-Manager-Utility/blob/main/LICENSE'
+ img: 'https://img.shields.io/badge/license-CC%20BY--NC%204.0-blue?style=flat-square'
+stars:
+ - url: 'https://github.com/chirag127/Chrome-Extension-Manager-Utility/stargazers'
+ img: 'https://img.shields.io/github/stars/chirag127/Chrome-Extension-Manager-Utility?style=flat-square'
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 6f944b6..a682797 100644
--- a/js/profiles.js
+++ b/js/profiles.js
@@ -1,95 +1,294 @@
-document.addEventListener("DOMContentLoaded", function() {
+document.addEventListener("DOMContentLoaded", function () {
+ var ProfilesViewModel = function () {
+ var self = this;
- 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.ext = new ExtensionCollectionModel();
- self.profiles = new ProfileCollectionModel();
- self.current_profile = ko.observable();
- self.add_name = ko.observable("");
+ // Import/Export properties will be handled with direct DOM manipulation
- self.current_name = ko.pureComputed(function() {
- return (self.current_profile()) ? self.current_profile().name() : null;
- });
+ 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.editable = ko.computed(function () {
+ return self.current_profile() || false;
+ });
- self.select = function(data) {
- self.current_profile(data);
- };
+ self.select = function (data) {
+ self.current_profile(data);
+ };
- self.selectAlwaysOn = function(data) {
- self.selectReserved(data, "always_on");
- }
+ self.selectAlwaysOn = function (data) {
+ self.selectReserved(data, "always_on");
+ };
- self.selectFavorites = function(data) {
- self.selectReserved(data, "favorites");
- }
+ self.selectFavorites = function (data) {
+ self.selectReserved(data, "favorites");
+ };
- self.selectReserved = function(data, n) {
- self.add_name("__"+n);
- self.add();
- };
+ 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([]);
+ };
+
+ // We'll add the import/export functionality after the view model is initialized
- self.selectByIndex = function(idx) {
- self.current_profile(self.profiles.items()[idx]);
+ try {
+ new DismissalsCollection().dismiss("profile_page_viewed");
+ self.selectByIndex(0);
+ } catch (e) {
+ /*No profiles*/
+ }
};
- 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);
+ vm = new ProfilesViewModel();
+
+ ko.bindingProvider.instance = new ko.secureBindingsProvider({});
+ ko.applyBindings(vm, document.getElementById("profiles"));
+
+ // Add import/export functionality using direct DOM manipulation
+ // We're already inside a DOMContentLoaded event, so we can add our code directly
+ (function () {
+ // Get DOM elements
+ var exportBtn = document.getElementById("export-profiles-btn");
+ var importBtn = document.getElementById("import-profiles-btn");
+ var importModal = document.getElementById("import-modal");
+ var closeModalBtn = document.getElementById("close-import-modal");
+ var cancelImportBtn = document.getElementById("cancel-import-btn");
+ var confirmImportBtn = document.getElementById(
+ "import-profiles-confirm-btn"
+ );
+ var importFileInput = document.getElementById("import-file");
+ var importErrorDiv = document.getElementById("import-error");
+ var importSuccessDiv = document.getElementById("import-success");
+
+ // Function to show the import modal
+ function showImportModal() {
+ // Clear previous messages
+ importErrorDiv.style.display = "none";
+ importSuccessDiv.style.display = "none";
+ importErrorDiv.textContent = "";
+ importSuccessDiv.textContent = "";
+ // Clear file input
+ importFileInput.value = "";
+ // Show modal
+ importModal.style.display = "block";
}
- else {
- self.current_profile(p);
+
+ // Function to hide the import modal
+ function hideImportModal() {
+ importModal.style.display = "none";
}
- 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.
- }
- };
+ // Function to show error message
+ function showError(message) {
+ importErrorDiv.textContent = message;
+ importErrorDiv.style.display = "block";
+ importSuccessDiv.style.display = "none";
+ }
- self.save = function() {
- self.profiles.save(function() {
- fadeOutMessage("save-result");
- });
- };
+ // Function to show success message
+ function showSuccess(message) {
+ importSuccessDiv.textContent = message;
+ importSuccessDiv.style.display = "block";
+ importErrorDiv.style.display = "none";
+ }
- self.close = function() { window.close(); }
+ // Function to export profiles
+ function exportProfiles() {
+ // Create a JSON object with all profiles
+ var profilesData = {};
- self.toggleAll = function() {
- var exts = _(self.ext.extensions()).map(function(i) { return i.id(); });
- self.current_profile().items(exts);
- };
+ _(vm.profiles.items()).each(function (profile) {
+ if (profile.name()) {
+ profilesData[profile.name()] = profile.items();
+ }
+ });
- self.toggleNone = function() {
- if(self.current_profile()) self.current_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);
+ }
+
+ // Function to import profiles
+ function importProfiles() {
+ if (!importFileInput.files || importFileInput.files.length === 0) {
+ showError("Please select a file to import.");
+ return;
+ }
+
+ var file = importFileInput.files[0];
+ var reader = new FileReader();
+
+ reader.onload = function (e) {
+ try {
+ var importedData = JSON.parse(e.target.result);
- try {
- (new DismissalsCollection()).dismiss("profile_page_viewed");
- self.selectByIndex(0);
- }
- catch(e) { /*No profiles*/ }
+ // Validate the imported data
+ if (!importedData.profiles) {
+ showError("Invalid profile data: missing profiles.");
+ return;
+ }
- };
+ // Get the selected import mode
+ var importMode = document.querySelector(
+ 'input[name="import-mode"]:checked'
+ ).value;
- vm = new ProfilesViewModel();
+ // Process the import based on the selected mode
+ if (importMode === "replace") {
+ // Clear existing profiles
+ vm.profiles.items.removeAll();
+ }
+
+ // Import the profiles
+ var importCount = 0;
+ var updateCount = 0;
+
+ _(importedData.profiles).each(function (items, name) {
+ var existingProfile = vm.profiles.find(name);
+
+ if (existingProfile) {
+ // Update existing profile if in merge mode
+ if (importMode === "merge") {
+ existingProfile.items(items);
+ updateCount++;
+ }
+ } else {
+ // Add new profile
+ vm.profiles.add(name, items);
+ importCount++;
+ }
+ });
+
+ // Save the changes
+ vm.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.";
+ }
+ showSuccess(message);
+
+ // Reset the file input
+ importFileInput.value = "";
+
+ // Refresh the view after a short delay
+ setTimeout(function () {
+ hideImportModal();
+ // Reload the page to show the updated profiles
+ window.location.reload();
+ }, 2000);
+ });
+ } catch (error) {
+ showError("Error importing profiles: " + error.message);
+ }
+ };
+
+ reader.onerror = function () {
+ showError("Error reading file.");
+ };
+
+ reader.readAsText(file);
+ }
- ko.bindingProvider.instance = new ko.secureBindingsProvider({});
- ko.applyBindings(vm, document.getElementById('profiles'));
+ // Add event listeners
+ exportBtn.addEventListener("click", exportProfiles);
+ importBtn.addEventListener("click", showImportModal);
+ closeModalBtn.addEventListener("click", hideImportModal);
+ cancelImportBtn.addEventListener("click", hideImportModal);
+ confirmImportBtn.addEventListener("click", importProfiles);
-});
\ No newline at end of file
+ // Close modal when clicking outside of it
+ window.addEventListener("click", function (event) {
+ if (event.target === importModal) {
+ hideImportModal();
+ }
+ });
+ })();
+});
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..6d792ea 100644
--- a/profiles.html
+++ b/profiles.html
@@ -1,101 +1,149 @@
+
-Extensity Profiles
-
-
-
-
-
-
-
-
+ Extensity Profiles
+
+
+
+
+
+
+
+
+
-