Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Follow us in Twitter: [@ExtensityChrome](https://twitter.com/ExtensityChrome)

### What's new:

v1.15.0 (May 2025)
- **New Feature**: Import/Export extension profiles as `.json` files.
- Allows users to back up and restore profile settings.

v1.14.0 (Sep 2024)
- **New Feature**: Dark Mode (based on system settings)
- Migrated from CSSO to SASS
Expand Down
6 changes: 5 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## Extensity TO-DOs

- Allow import and export of profiles configuration
- Dark mode
- [ ] Implement automatic sync with Chrome profile
- Keep manual import/export functionality for advanced users or backups
- Enable seamless syncing of profiles using `chrome.storage.sync`
- Profiles should automatically back up and restore across devices
- Add toggle option in settings to enable/disable auto-sync
89 changes: 89 additions & 0 deletions js/importExport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Wait for the DOM to fully load before attaching event listeners
document.addEventListener('DOMContentLoaded', () => {

// 🔹 EXPORT PROFILES FROM chrome.storage.sync
const exportBtn = document.getElementById('exportSettings');
if (exportBtn) {
exportBtn.addEventListener('click', () => {

// Fetch 'profiles' from Chrome's sync storage
chrome.storage.sync.get(['profiles'], (data) => {

// If no profiles exist, alert the user and exit
if (!data.profiles || Object.keys(data.profiles).length === 0) {
alert('!No profiles found to export.');
return;
}

// Convert profiles data to a JSON blob
const blob = new Blob(
[JSON.stringify(data.profiles, null, 2)],
{ type: 'application/json' }
);

// Create a temporary download link for the JSON file
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const dateStr = new Date().toISOString().split('T')[0];
a.href = url;
a.download = `extensity-profiles-${dateStr}.json`; // Dynamic filename
a.click(); // Trigger the download
URL.revokeObjectURL(url); // Clean up the URL
});
});
}

// 🔹 TRIGGER IMPORT FILE SELECTOR
const importBtn = document.getElementById('importSettings');
if (importBtn) {
importBtn.addEventListener('click', () => {
// Simulate a click on the hidden file input
document.getElementById('importFile').click();
});
}

// 🔹 HANDLE FILE INPUT CHANGE (IMPORT PROFILES)
const importFileInput = document.getElementById('importFile');
if (importFileInput) {
importFileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) return; // Exit if no file is selected

const reader = new FileReader();

// When file is successfully read
reader.onload = function (e) {
try {
const importedProfiles = JSON.parse(e.target.result);

// Validate the imported data
if (!importedProfiles || typeof importedProfiles !== 'object') {
alert('! Invalid or corrupt file.');
return;
}

// Save imported profiles into Chrome's sync storage
chrome.storage.sync.set({ profiles: importedProfiles }, () => {

// Show a success message in the UI, if available
const msg = document.getElementById('save-result');
if (msg) {
msg.classList.remove('hidden');
msg.textContent = '| Profiles imported!';
setTimeout(() => msg.classList.add('hidden'), 3000);
} else {
alert(':) Profiles imported! Please reload Extensity.');
}
});

} catch (err) {
// Handle JSON parsing errors
alert('! Failed to parse JSON file.');
}
};

// Start reading the file as text
reader.readAsText(file);
});
}
});
12 changes: 12 additions & 0 deletions profiles.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
<head>
<title>Extensity Profiles</title>
<link rel="stylesheet" type="text/css" href="styles/options.css" />

<!-- Load the CSS responsible for handling Import/Export functionality -->
<link rel="stylesheet" type="text/css" href="styles/importExport.css" />
<link rel="stylesheet" type="text/css" href="styles/font-awesome.min.css" />
<script type="text/javascript" src="js/libs/underscore-min.js"></script>
<script type="text/javascript" src="js/libs/underscore.string.min.js"></script>
<script type="text/javascript" src="js/libs/knockout-3.5.1.js"></script>
<script type="text/javascript" src="js/libs/knockout-secure-binding.min.js"></script>
<script type="text/javascript" src="js/engine.js"></script>
<script type="text/javascript" src="js/profiles.js"></script>
<!-- Load the script responsible for handling Import/Export functionality -->
<script type="text/javascript" src="js/importExport.js"></script>
</head>
<body>
<section id="header">
Expand Down Expand Up @@ -75,6 +80,13 @@ <h1><img src="images/icon48.png" /> Extensity Options</h1>
<i class="fa fa-warning"></i> You have too many Extensions or Profiles to sync with Google. <b>Your Profiles are still saved, but only for this browser</b>.
</p>

<!-- Import/Export Settings -->
<div id="backup-tools">
<button id="exportSettings">Export Settings</button>
<button id="importSettings">Import Settings</button>
<input type="file" id="importFile" style="display: none;">
</div>

</div>


Expand Down
7 changes: 7 additions & 0 deletions styles/importExport.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#backup-tools{
margin-top: 10px;
}

#backup-tools button{
margin-bottom: 5px;
}