commit 31165722eb254cbcdc0a1d40b1ad30c68d485f34 Author: Elmar Kresse Date: Tue Jan 20 00:59:09 2026 +0100 feat: Add initial Firefox container tab extension with popup UI, background script, manifest, and a comprehensive set of icons. diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..992b062 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,81 @@ +name: Build Firefox Extension + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get version from manifest + id: version + run: | + VERSION=$(jq -r '.version' manifest.json) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building version: $VERSION" + + - name: Create extension package + run: | + # Create build directory + mkdir -p build + + # Package the extension as XPI (which is just a ZIP file) + zip -r build/container-bookmarks-${{ steps.version.outputs.version }}.xpi \ + manifest.json \ + background.js \ + icons/ \ + popup/ \ + README.md \ + -x "*.git*" \ + -x "*.md" \ + -x "build/*" \ + -x "node_modules/*" \ + -x ".gitea/*" + + # Also create a ZIP for convenience + cp build/container-bookmarks-${{ steps.version.outputs.version }}.xpi \ + build/container-bookmarks-${{ steps.version.outputs.version }}.zip + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: container-bookmarks-${{ steps.version.outputs.version }} + path: build/*.xpi + + release: + runs-on: ubuntu-latest + needs: build + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get version from manifest + id: version + run: | + VERSION=$(jq -r '.version' manifest.json) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: container-bookmarks-${{ steps.version.outputs.version }} + path: build/ + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: build/*.xpi + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0eab0c8 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Container Bookmarks + +A Firefox extension that associates bookmarks with containers. When you click a bookmark, it automatically opens in the assigned container. + +## Features + +- **Right-click context menu**: Assign containers to any bookmark +- **Automatic container opening**: Bookmarks open directly in their assigned container +- **Popup overview**: View and manage all container-assigned bookmarks +- **Auto-cleanup**: Mappings are removed when bookmarks or containers are deleted + +## Installation + +### Temporary Installation (for development) + +1. Open Firefox and navigate to `about:debugging#/runtime/this-firefox` +2. Click **"Load Temporary Add-on..."** +3. Navigate to this folder and select `manifest.json` +4. The extension icon will appear in your toolbar + +### Permanent Installation + +Package the extension as a `.xpi` file and submit to [addons.mozilla.org](https://addons.mozilla.org) for review. + +## Usage + +### Assigning a Container to a Bookmark + +1. Right-click on any bookmark (in bookmark toolbar, menu, or sidebar) +2. Click **"Set Container"** +3. Select the container you want + +### Opening a Bookmark in a Specific Container (One-time) + +1. Right-click on any bookmark +2. Click **"Open in Container"** +3. Select the container + +### Viewing Assigned Bookmarks + +1. Click the extension icon in the toolbar +2. See all bookmarks with container assignments +3. Click **×** to remove an assignment + +## Permissions + +- `bookmarks`: Read and manage bookmarks +- `contextualIdentities`: Access container information +- `cookies`: Required for container support +- `storage`: Persist bookmark-container mappings +- `tabs`: Open tabs in specific containers +- `menus`: Add context menu items + +## Development + +```bash +# Clone the repository +git clone https://github.com/yourusername/firefox-container-tab.git + +# Load in Firefox for testing (see Installation above) +``` + +## License + +MIT License diff --git a/background.js b/background.js new file mode 100644 index 0000000..e9ef06d --- /dev/null +++ b/background.js @@ -0,0 +1,530 @@ +/** + * Container Bookmarks - Background Script + * Manages bookmark-container mappings and context menu integration + */ + +// Storage key for bookmark mappings +const STORAGE_KEY = 'bookmark_container_mappings'; + +// Fragment prefix used to tag bookmarks for container identification +// Format: #cb- (e.g., #cb-abc123) +const FRAGMENT_PREFIX = '#cb-'; + +// Cache for containers +let containersCache = []; + +// ============================================================================ +// URL Fragment Helper Functions +// ============================================================================ + +/** + * Generate a unique fragment for a bookmark + */ +function generateFragment(bookmarkId) { + return `${FRAGMENT_PREFIX}${bookmarkId}`; +} + +/** + * Check if a URL contains a container bookmark fragment + */ +function hasContainerFragment(url) { + return url.includes(FRAGMENT_PREFIX); +} + +/** + * Extract the bookmark ID from a URL with a container fragment + */ +function extractBookmarkIdFromUrl(url) { + const fragmentIndex = url.indexOf(FRAGMENT_PREFIX); + if (fragmentIndex === -1) return null; + + // Extract everything after the prefix until the end or next # or ? + const afterPrefix = url.substring(fragmentIndex + FRAGMENT_PREFIX.length); + // The bookmark ID continues until end of string (fragment is always at the end) + return afterPrefix.split(/[#?]/)[0] || afterPrefix; +} + +/** + * Get the clean URL (without container fragment) + */ +function getCleanUrl(url) { + const fragmentIndex = url.indexOf(FRAGMENT_PREFIX); + if (fragmentIndex === -1) return url; + + // Return everything before the fragment prefix + return url.substring(0, fragmentIndex); +} + +/** + * Add container fragment to a URL + */ +function addFragmentToUrl(url, bookmarkId) { + // First, remove any existing container fragment + const cleanUrl = getCleanUrl(url); + return `${cleanUrl}${generateFragment(bookmarkId)}`; +} + +// ============================================================================ +// Storage Functions +// ============================================================================ + +/** + * Get all bookmark-container mappings from storage + */ +async function getMappings() { + const result = await browser.storage.local.get(STORAGE_KEY); + return result[STORAGE_KEY] || {}; +} + +/** + * Save a bookmark-container mapping and add unique fragment to bookmark URL + */ +async function saveMapping(bookmarkId, containerId, containerName) { + // Get the bookmark + const bookmarks = await browser.bookmarks.get(bookmarkId); + if (!bookmarks || bookmarks.length === 0) { + console.error('Bookmark not found:', bookmarkId); + return; + } + + const bookmark = bookmarks[0]; + if (!bookmark.url) { + console.error('Bookmark has no URL:', bookmarkId); + return; + } + + // Add unique fragment to bookmark URL (if not already present) + const newUrl = addFragmentToUrl(bookmark.url, bookmarkId); + if (newUrl !== bookmark.url) { + await browser.bookmarks.update(bookmarkId, { url: newUrl }); + console.log(`Updated bookmark URL: ${bookmark.url} -> ${newUrl}`); + } + + // Save the mapping + const mappings = await getMappings(); + mappings[bookmarkId] = { + containerId, + containerName, + createdAt: Date.now() + }; + await browser.storage.local.set({ [STORAGE_KEY]: mappings }); + console.log(`Saved mapping: bookmark ${bookmarkId} -> container ${containerName}`); +} + +/** + * Remove a bookmark-container mapping and strip fragment from bookmark URL + */ +async function removeMapping(bookmarkId) { + const mappings = await getMappings(); + if (mappings[bookmarkId]) { + // Try to restore the original bookmark URL (remove fragment) + try { + const bookmarks = await browser.bookmarks.get(bookmarkId); + if (bookmarks && bookmarks.length > 0 && bookmarks[0].url) { + const cleanUrl = getCleanUrl(bookmarks[0].url); + if (cleanUrl !== bookmarks[0].url) { + await browser.bookmarks.update(bookmarkId, { url: cleanUrl }); + console.log(`Restored bookmark URL: ${bookmarks[0].url} -> ${cleanUrl}`); + } + } + } catch (error) { + // Bookmark might have been deleted + console.log('Could not restore bookmark URL (bookmark may be deleted)'); + } + + delete mappings[bookmarkId]; + await browser.storage.local.set({ [STORAGE_KEY]: mappings }); + console.log(`Removed mapping for bookmark ${bookmarkId}`); + } +} + +/** + * Get the container mapping for a specific bookmark + */ +async function getMapping(bookmarkId) { + const mappings = await getMappings(); + return mappings[bookmarkId] || null; +} + +// ============================================================================ +// Container Functions +// ============================================================================ + +/** + * Refresh the containers cache + */ +async function refreshContainersCache() { + try { + containersCache = await browser.contextualIdentities.query({}); + console.log(`Loaded ${containersCache.length} containers`); + } catch (error) { + console.error('Failed to load containers:', error); + containersCache = []; + } +} + +/** + * Get container by cookieStoreId + */ +function getContainerById(cookieStoreId) { + return containersCache.find(c => c.cookieStoreId === cookieStoreId); +} + +/** + * Get hex color value for container color + */ +function getColorHex(color) { + const colorMap = { + 'blue': '#37adff', + 'turquoise': '#00c79a', + 'green': '#51cd00', + 'yellow': '#ffcb00', + 'orange': '#ff9f00', + 'red': '#ff613d', + 'pink': '#ff4bda', + 'purple': '#af51f5', + 'toolbar': '#7c7c7d' + }; + return colorMap[color] || '#7c7c7d'; +} + + +// ============================================================================ +// Context Menu Functions +// ============================================================================ + +/** + * Create the context menu structure + */ +async function createContextMenus() { + // Remove existing menus first + await browser.menus.removeAll(); + + // Refresh containers + await refreshContainersCache(); + + // Parent menu for "Set Container" + browser.menus.create({ + id: 'set-container-parent', + title: 'Set Container', + contexts: ['bookmark'] + }); + + // Add container options + for (const container of containersCache) { + const iconName = container.icon || 'circle'; + const colorName = container.color || 'toolbar'; + browser.menus.create({ + id: `set-container-${container.cookieStoreId}`, + parentId: 'set-container-parent', + title: container.name, + icons: { + 16: `icons/${colorName}-${iconName}.svg` + }, + contexts: ['bookmark'] + }); + } + + // Separator + browser.menus.create({ + id: 'set-container-separator', + parentId: 'set-container-parent', + type: 'separator', + contexts: ['bookmark'] + }); + + // Remove container option + browser.menus.create({ + id: 'remove-container', + parentId: 'set-container-parent', + title: '✗ Remove Container Assignment', + contexts: ['bookmark'] + }); + + // Open in Container menu + browser.menus.create({ + id: 'open-in-container-parent', + title: 'Open in Container', + contexts: ['bookmark'] + }); + + for (const container of containersCache) { + const iconName = container.icon || 'circle'; + const colorName = container.color || 'toolbar'; + browser.menus.create({ + id: `open-in-container-${container.cookieStoreId}`, + parentId: 'open-in-container-parent', + title: container.name, + icons: { + 16: `icons/${colorName}-${iconName}.svg` + }, + contexts: ['bookmark'] + }); + } + + console.log('Context menus created'); +} + +/** + * Handle context menu clicks + */ +async function handleMenuClick(info, tab) { + const menuItemId = info.menuItemId; + const bookmarkId = info.bookmarkId; + + if (!bookmarkId) { + console.error('No bookmark ID in menu click'); + return; + } + + // Get bookmark info + const bookmarks = await browser.bookmarks.get(bookmarkId); + if (!bookmarks || bookmarks.length === 0) { + console.error('Bookmark not found:', bookmarkId); + return; + } + + const bookmark = bookmarks[0]; + + // Handle "Set Container" clicks + if (menuItemId.startsWith('set-container-')) { + const containerId = menuItemId.replace('set-container-', ''); + const container = getContainerById(containerId); + + if (container) { + await saveMapping(bookmarkId, containerId, container.name); + // Show notification + browser.notifications.create({ + type: 'basic', + title: 'Container Bookmarks', + message: `"${bookmark.title}" will now open in ${container.name}` + }); + } + return; + } + + // Handle "Remove Container" click + if (menuItemId === 'remove-container') { + await removeMapping(bookmarkId); + browser.notifications.create({ + type: 'basic', + title: 'Container Bookmarks', + message: `Container removed from "${bookmark.title}"` + }); + return; + } + + // Handle "Open in Container" clicks + if (menuItemId.startsWith('open-in-container-')) { + const containerId = menuItemId.replace('open-in-container-', ''); + + if (bookmark.url) { + // Use clean URL (without container fragment if present) + const cleanUrl = getCleanUrl(bookmark.url); + await browser.tabs.create({ + url: cleanUrl, + cookieStoreId: containerId + }); + } + return; + } +} + +// ============================================================================ +// Bookmark Click Interception +// ============================================================================ + +// Cache of URL -> containerId for faster matching +let urlToContainerCache = {}; + +/** + * Build URL to container cache from mappings + */ +async function buildUrlCache() { + const mappings = await getMappings(); + urlToContainerCache = {}; + + for (const [bookmarkId, mapping] of Object.entries(mappings)) { + try { + const bookmarks = await browser.bookmarks.get(bookmarkId); + if (bookmarks && bookmarks.length > 0 && bookmarks[0].url) { + urlToContainerCache[bookmarks[0].url] = { + containerId: mapping.containerId, + containerName: mapping.containerName, + bookmarkId: bookmarkId + }; + } + } catch (error) { + // Bookmark was deleted, clean up + await removeMapping(bookmarkId); + } + } + + console.log(`URL cache built with ${Object.keys(urlToContainerCache).length} entries`); +} + +// Track tabs that we're currently redirecting to prevent loops +const redirectingTabs = new Set(); + +/** + * Handle navigation commits - intercept bookmark clicks with container fragments + * + * When a bookmark with a container fragment is clicked, we: + * 1. Detect the fragment (e.g., #cb-abc123) + * 2. Look up the container mapping for that bookmark ID + * 3. Navigate to the clean URL (without fragment) in the correct container + */ +async function handleNavigation(details) { + const { tabId, url, transitionType, frameId } = details; + + // Only handle main frame navigations + if (frameId !== 0) { + return; + } + + // Skip special URLs + if (url.startsWith('about:') || url.startsWith('moz-extension:')) { + return; + } + + // Skip if this tab is one we're redirecting (prevent infinite loop) + if (redirectingTabs.has(tabId)) { + return; + } + + // Check if this URL has a container bookmark fragment + if (!hasContainerFragment(url)) { + return; + } + + // Extract bookmark ID from the URL fragment + const bookmarkId = extractBookmarkIdFromUrl(url); + if (!bookmarkId) { + console.log(`Could not extract bookmark ID from URL: ${url}`); + return; + } + + // Look up the mapping for this bookmark + const mapping = await getMapping(bookmarkId); + if (!mapping) { + console.log(`No container mapping found for bookmark ID: ${bookmarkId}`); + return; + } + + // Get the clean URL (without the fragment) + const cleanUrl = getCleanUrl(url); + + // Get the current tab info + let tab; + try { + tab = await browser.tabs.get(tabId); + } catch (error) { + console.error('Failed to get tab info:', error); + return; + } + + // Skip if already in the correct container + if (tab.cookieStoreId === mapping.containerId) { + // Still need to redirect to clean URL if already in correct container + if (cleanUrl !== url) { + console.log(`Already in correct container, redirecting to clean URL: ${cleanUrl}`); + redirectingTabs.add(tabId); + await browser.tabs.update(tabId, { url: cleanUrl }); + setTimeout(() => redirectingTabs.delete(tabId), 1000); + } + return; + } + + console.log(`Intercepting navigation to ${url} -> redirecting to ${cleanUrl} in container ${mapping.containerName}`); + + // Mark this tab as being redirected + redirectingTabs.add(tabId); + + try { + // Close the current tab + await browser.tabs.remove(tabId); + + // Open the CLEAN URL in the correct container + const newTab = await browser.tabs.create({ + url: cleanUrl, + cookieStoreId: mapping.containerId, + index: tab.index, + active: tab.active + }); + + // Mark the new tab as redirecting so we don't intercept it again + redirectingTabs.add(newTab.id); + + // Remove from redirecting set after a short delay + setTimeout(() => { + redirectingTabs.delete(tabId); + redirectingTabs.delete(newTab.id); + }, 1000); + + } catch (error) { + console.error('Failed to redirect tab:', error); + redirectingTabs.delete(tabId); + } +} + +// ============================================================================ +// Event Listeners +// ============================================================================ + +// Listen for menu clicks +browser.menus.onClicked.addListener(handleMenuClick); + +// Listen for navigation commits - intercept all except typed (address bar) +browser.webNavigation.onCommitted.addListener(handleNavigation); + +// Listen for bookmark deletions to clean up mappings +browser.bookmarks.onRemoved.addListener(async (bookmarkId) => { + await removeMapping(bookmarkId); + await buildUrlCache(); +}); + +// Listen for bookmark URL changes +browser.bookmarks.onChanged.addListener(async (bookmarkId, changeInfo) => { + if (changeInfo.url) { + await buildUrlCache(); + } +}); + +// Listen for storage changes to rebuild cache +browser.storage.onChanged.addListener((changes, areaName) => { + if (areaName === 'local' && changes[STORAGE_KEY]) { + buildUrlCache(); + } +}); + +// Listen for container changes to refresh menu +browser.contextualIdentities.onCreated.addListener(createContextMenus); +browser.contextualIdentities.onRemoved.addListener(async (changeInfo) => { + // Clean up mappings for deleted container + const mappings = await getMappings(); + const deletedContainerId = changeInfo.contextualIdentity.cookieStoreId; + + for (const [bookmarkId, mapping] of Object.entries(mappings)) { + if (mapping.containerId === deletedContainerId) { + await removeMapping(bookmarkId); + } + } + + await createContextMenus(); + await buildUrlCache(); +}); +browser.contextualIdentities.onUpdated.addListener(createContextMenus); + +// ============================================================================ +// Initialization +// ============================================================================ + +// Initialize on startup +async function init() { + await createContextMenus(); + await buildUrlCache(); + console.log('Container Bookmarks extension loaded'); +} + +init(); + diff --git a/generate-icons.js b/generate-icons.js new file mode 100644 index 0000000..fd7fef4 --- /dev/null +++ b/generate-icons.js @@ -0,0 +1,49 @@ +// Generate colored SVG icon files for all color-icon combinations +const fs = require('fs'); +const path = require('path'); + +const colors = { + 'blue': '#37adff', + 'turquoise': '#00c79a', + 'green': '#51cd00', + 'yellow': '#ffcb00', + 'orange': '#ff9f00', + 'red': '#ff613d', + 'pink': '#ff4bda', + 'purple': '#af51f5', + 'toolbar': '#7c7c7d' +}; + +const icons = { + 'fence': 'M28 4l-2 2v4h-4V6l-2-2-2 2v4h-4V6l-2-2-2 2v4H6V6L4 4 2 6v22h4v-4h4v4h4v-4h4v4h4v-4h4v4h4V6l-2-2zM6 22V12h4v10H6zm8 0V12h4v10h-4zm8 0V12h4v10h-4z', + 'dollar': 'M16.2,0c-8.9,0-16,7.3-16,16c0,8.9,7.1,16,15.8,16s15.8-7.1,15.8-16C32,7.3,24.9,0,16.2,0z M17.1,25.1v1.6c0,0.4-0.4,0.5-0.7,0.5c-0.4,0-0.7-0.2-0.7-0.5v-1.6c-3.2-0.2-5-1.8-5.5-4.3c0-0.2,0-0.2,0-0.4c0-0.5,0.4-0.9,0.9-0.9c0.2,0,0.2,0,0.4,0c0.5,0,0.9,0.2,1.1,0.7c0.4,1.8,1.2,2.7,3.4,2.8v-6.8c-3.6-0.4-5.3-1.8-5.3-4.6c0-3,2.5-4.6,5.2-4.8V5.7c0-0.4,0.4-0.5,0.7-0.5c0.4,0,0.7,0.2,0.7,0.5v1.1c2.7,0.4,4.4,1.8,5,3.9c0,0.2,0,0.2,0,0.4c0,0.5-0.4,0.7-0.7,0.9c-0.2,0-0.2,0-0.4,0c-0.4,0-0.7-0.2-0.9-0.7c-0.4-1.4-1.2-2.3-3-2.5v6c3.2,0.7,5.5,1.8,5.5,5.2C22.8,23.5,20.1,25.1,17.1,25.1z M12.4,11.6c0,1.6,0.7,2.5,3.2,3V8.7C13.7,8.9,12.4,10,12.4,11.6z M17.1,16.9v6.4c2.3-0.2,3.6-1.2,3.6-3.2C20.6,17.8,19.2,17.2,17.1,16.9z', + 'briefcase': 'M23.1,5.3c0-1.4-1.2-2.7-2.8-2.7h-8.7c-1.4,0-2.7,1.2-2.7,2.7v4.4H7.1v19.6h17.8V9.8h-1.8V5.3z M20.8,9.8H11V5.3c0-0.4,0.2-0.5,0.5-0.5h8.7c0.4,0,0.5,0.2,0.5,0.5V9.8z M1.8,9.8h2.7v19.6H1.8c-0.9,0-1.8-0.9-1.8-1.8v-16C0,10.5,0.9,9.8,1.8,9.8z M32,11.6v16c0,0.9-0.7,1.8-1.8,1.8h-2.7V9.8h2.7C31.3,9.8,32,10.5,32,11.6z', + 'fingerprint': 'M7.18,12c-.07,0-.14,0-.21-.01-.64-.09-1.07-.6-.96-1.13C7.42,4.26,12.26,2.57,15.02,2.16c4.62-.7,9.32,1.06,10.81,3.14.33.46.15,1.06-.41,1.33-.56.28-1.28.12-1.61-.35-.92-1.28-4.64-2.78-8.36-2.22-2.64.4-6.02,1.96-7.11,7.12-.1.47-.6.81-1.16.81zm9.45,14c-.49,0-.98-.03-1.49-.11-4.51-.64-8.44-4.08-9.13-8.01-.1-.59.31-1.14.94-1.24.63-.11,1.21.29,1.32.88.53,3,3.7,5.74,7.21,6.23,1.91.27,5.47.06,7.83-4.38,1.14-2.15-.29-7.5-3.35-10.03-1.71-1.42-3.5-1.58-5.29-.45-1.4.88-2.86,3.39-2.8,5.73.05,1.85.97,3.3,2.75,4.31.6.34,2.32.56,3.32-.1.78-.52,1.02-1.55.69-3.07-.25-1.19-1.41-3.31-1.97-3.39,0,0-.2.06-.54.54-.27.38-.54,1.82-.18,2.45.07.12.12.19.37.19.63,0,1.15.48,1.15,1.08s-.51,1.08-1.15,1.08c-1.06,0-1.91-.47-2.39-1.33-.82-1.44-.37-3.72.29-4.66,1.03-1.46,2.14-1.6,2.89-1.46,2.59.48,3.77,5.04,3.78,5.08.64,3.03-.53,4.53-1.64,5.26-1.85,1.23-4.57.9-5.83.2-3.11-1.76-3.82-4.29-3.87-6.1-.08-3.32,1.91-6.38,3.82-7.58,2.65-1.66,5.6-1.43,8.08.63,3.65,3.03,5.62,9.4,3.9,12.61-1.94,3.65-5.08,5.66-8.73,5.66zm-.55,4c-1.15,0-2.18-.14-3.04-.34-2.83-.64-5.45-2.21-6.85-4.09-.35-.47-.21-1.09.3-1.4.51-.31,1.21-.18,1.56.28,1.83,2.47,7.88,5.53,14.19,1.77.52-.31,1.22-.19,1.56.28.35.47.21,1.09-.3,1.4-2.7,1.62-5.27,2.11-7.42,2.11z', + 'cart': 'M26.9,21.4H9.4c-0.7,0-1.3,0.5-1.3,1.3s0.5,1.3,1.3,1.3h17.5c0.7,0,1.3-0.5,1.3-1.3C28.5,21.9,27.8,21.4,26.9,21.4z M13.3,30.1c1.3,0,2.7-1.2,2.7-2.7c0-1.3-1.2-2.7-2.7-2.7s-2.7,1.2-2.7,2.7C10.6,29,12,30.1,13.3,30.1z M23.9,30.1c1.3,0,2.7-1.2,2.7-2.7c0-1.3-1.2-2.7-2.7-2.7c-1.5,0-2.7,1.2-2.7,2.7C21.4,29,22.6,30.1,23.9,30.1z M31.5,7.4L31.5,7.4H7.6V7.2L5.7,2.5C5.4,2.2,5.1,1.9,4.6,1.9H0.7C0,1.9,0,2.5,0,2.9C0,3.5-0.2,4,0.7,4.2h2.7l0.7,1.5l4,13.3c0,0.2,0.2,0.5,0.8,0.5h18.5c0.3,0,0.7-0.2,0.7-0.5L32,8.3C32,8.1,31.8,7.4,31.5,7.4z', + 'vacation': 'M3.6,27l-2.5-1.8L0.8,25c-0.7-0.4-0.7-1.2-0.4-2c0.4-0.5,1.1-0.7,1.6-0.5l3.6,1.2c0-0.4,0.2-0.9,0.4-1.4c0.2-0.7,0.5-1.6,1.1-2.3c0.2-0.4,0.5-0.7,0.7-1.2c0.2-0.4,0.5-0.9,0.9-1.2c0.4-0.9,1.1-1.6,1.8-2.5c0.7-0.9,1.4-1.6,2.3-2.5c0.4-0.4,0.9-0.7,1.2-1.2c0.4-0.2,0.9-0.7,1.2-1.1c0.2-0.2,0.2-0.2,0.4-0.4L3.1,7.3c-0.2,0-0.2,0-0.4,0l-2,0.9C0.2,8.3-0.3,7.6,0.2,7.1l2-2C2.4,5,2.4,5,2.5,5h17.9c0.5-0.5,1.2-1.1,1.8-1.6c0.7-0.7,1.4-1.2,2.1-1.8c0.4-0.4,0.7-0.5,1.1-0.7c0.4-0.2,0.7-0.4,1.1-0.5c0.5,0,0.9-0.2,1.2-0.2s0.7,0,1.1,0s0.7,0,1.1,0c0.4,0,0.5,0,0.7,0.2c0.5,0,0.7,0.2,0.7,0.2c0.2,0,0.2,0.2,0.4,0.4c0,0,0,0.4,0.2,0.7c0,0.2,0,0.5,0.2,0.7c0,0.4,0,0.7,0,1.1s0,0.7,0,1.1c0,0.4,0,0.9-0.2,1.2c-0.2,0.4-0.4,0.7-0.5,1.1c-0.2,0.4-0.5,0.7-0.7,1.1c-0.5,0.7-1.1,1.4-1.8,2.1c-0.4,0.4-0.7,0.7-1.1,1.1v17.8c0,0.2,0,0.4-0.2,0.4l-2,2c-0.5,0.5-1.2,0-1.1-0.5l0.7-2c0-0.2,0-0.2,0-0.4L22.8,16c-0.4,0.4-0.7,0.7-0.9,0.9c-0.4,0.4-0.7,0.9-1.2,1.2c-0.4,0.4-0.7,0.9-1.2,1.2c-0.7,0.9-1.6,1.6-2.5,2.3c-0.9,0.7-1.6,1.4-2.5,2c-0.4,0.4-0.9,0.5-1.2,0.9c-0.4,0.2-0.7,0.5-1.2,0.7c-0.7,0.4-1.6,0.7-2.3,1.1c-0.4,0.2-0.7,0.2-1.2,0.4L9.6,30c0.4,0.7,0,1.4-0.7,1.8c-0.5,0.2-1.2,0-1.6-0.5l-0.2-0.4l-1.8-2.3c-0.2,0-0.2,0-0.4,0.2c-0.4,0-0.5,0.2-0.7,0.2s-0.2,0-0.4,0c-0.2,0-0.2,0-0.4,0c-0.2,0-0.4,0-0.5,0c-0.2,0-0.2,0-0.2,0s0,0,0-0.2c0-0.2,0-0.2,0-0.5c0-0.2,0-0.2,0-0.4c0-0.2,0-0.2,0-0.4C3.4,27.5,3.4,27.3,3.6,27L3.6,27z M5.7,28.4L5.7,28.4L5.7,28.4L5.7,28.4z', + 'gift': 'M30.3,8.1h-4.5V8c0.7-0.7,1.3-1.9,1.3-3.2c0-2.6-2.1-4.7-4.9-4.7c-1.5,0-4.5,1.5-6.4,3.4C14,1.6,11,0.1,9.5,0.1c-2.6,0-4.9,2.1-4.9,4.7C4.7,6.1,5.2,7.2,6,8H1.7C0.6,8,0,8.7,0,9.6v4.5c0,0.2,0.2,0.4,0.4,0.4h13.8V9.6h3.2v4.9h14.2c0.2,0,0.4-0.2,0.4-0.4V9.6C32,8.7,31.4,8.1,30.3,8.1z M9.5,6.5C8.6,6.5,8,5.9,8,4.8s0.6-1.5,1.5-1.5s3.7,1.9,4.7,2.8C13.7,6.3,9.5,6.5,9.5,6.5z M22.3,6.5c0,0-4.1-0.2-4.7-0.4c0.9-1.1,3.7-2.8,4.7-2.8S24,3.8,24,4.8S23.2,6.5,22.3,6.5z M1.7,17.7h12.7v14.2H1.7V17.7z M17.6,17.7h12.7v14.2H17.6V17.7z', + 'food': 'M14.1,0.9v5.3h-1.4V0.9c0-1.1-1.4-1.1-1.4,0v5.3h-1.2V0.9c0-1.1-1.4-1.1-1.4,0v5.3H7.2V0.9c0-1.2-1.6-1.1-1.6,0v10.4c0,1.8,1.2,3,2.8,3v15.2c0,1.6,1.1,2.5,2.1,2.5s2.1-0.9,2.1-2.5V14.3c1.6,0,2.8-1.4,2.8-2.8V0.9C15.6-0.4,14.1-0.2,14.1,0.9z M19.8,3.7v25.8c0,3.2,4.2,3.2,4.2,0V17.1h2.3V3.7C26.5-1.2,19.8-1.2,19.8,3.7z', + 'fruit': 'M16.5,8c-2.1-0.9-3.9-1.2-6.6-0.9C4.6,8,1.8,12.6,1.8,18c0,5.9,4.8,14,9.8,14c1.6,0,3.9-1.2,4.4-1.2c0.5,0,2.8,1.2,4.4,1.2c5,0,9.8-8.4,9.8-14c0-5.9-3.2-10.8-9.8-10.8C19,7.1,17.8,7.5,16.5,8z M11.7,0c1.1,0.2,3.2,0.9,4.1,2.3c0.9,1.4,0.5,3.6,0.2,4.6c-1.2-0.2-3.2-0.7-4.1-2.3C11,3.2,11.4,1.1,11.7,0L11.7,0z', + 'pet': 'M28.5,8.1c0-1.1-1-1.9-2.1-2.4V3.7c-0.2-0.2-0.3-0.3-0.6-0.3c-0.6,0-1.1,0.8-1.3,2.1c-0.2,0-0.3,0-0.5,0l0,0c0-0.2,0-0.3-0.2-0.5c-0.3-1.1-0.8-1.9-1.3-2.6C22,2.6,21.7,3.2,21.7,4L22,6.3c-0.3,0.2-0.6,0.3-1,0.6l-3.5,3.7l0,0c0,0-6.3-0.8-10.9,0.2c-0.6,0-1,0.2-1.1,0.3c-0.5,0.2-0.8,0.3-1.1,0.6c-1.1-0.8-2.2-2.1-3.2-4c0-0.3-0.5-0.5-0.8-0.5s-0.5,0.6-0.3,1c0.8,2.1,2.1,3.5,3.4,4.5c-0.5,0.5-0.8,1-1,1.6c0,0-0.3,2.2-0.3,5.5l1.4,8c0,1,0.8,1.8,1.9,1.8c1,0,1.9-0.8,1.9-1.8V23l0.5-1.3h8.8l0.8,1.3v4.7c0,1,0.8,1.8,1.9,1.8c1,0,1.6-0.6,1.8-1.4l0,0l1.9-9l0,0l2.1-6.4h3c3.4,0,3.7-2.9,3.7-2.9L28.5,8.1z', + 'tree': 'M0.7,18c0,4.9,3.6,8.8,8.1,9.5v4.3c0.2,0,3.2,0,3.2,0v-4.3c1.8-0.4,3.6-1.1,4.9-2.5c0.2-0.2,0.2-0.2,0.2-0.5c-0.2-0.4-0.2-1.1-0.2-1.6c0-2,0.2-4.9,1.6-7.9c0,0,0.9-1.6,0.7-1.8C18,7.2,14.4,0,10.4,0C5,0,0.7,12.6,0.7,18z M18.3,22.8c0,3.1,2.2,5.6,4.9,6.3V32h3.2v-2.9c2.7-0.7,4.9-3.2,4.9-6.3c0-3.6-2.9-12.9-6.5-12.9S18.3,19.2,18.3,22.8z', + 'chill': 'M9.1,18.5l-5.7,5.9C3.2,23.8,3,23.3,3,22.6c0-2.5,2-4.4,4.4-4.4C7.8,18.1,8.5,18.3,9.1,18.5 M26.5,18.5l-5.7,5.9c-0.2-0.5-0.4-1.1-0.4-1.8c0-2.5,2-4.4,4.4-4.4C25.4,18.1,26,18.3,26.5,18.5 M24.7,2L24.7,2c-0.7,0-1.4,0.7-1.4,1.4s0.7,1.4,1.4,1.4c2.5,0,4.4,2,4.4,4.4v7.6c-1.6-1.2-3.6-1.8-5.5-1.4c-2.1,0.4-3.9,1.6-5,3.4c-1.6-1.2-3.9-1.2-5.5,0c-1.1-1.8-2.8-3-5-3.4c-2-0.4-3.9,0.2-5.5,1.4V9.2c0-2.5,2-4.4,4.4-4.4c0.5,0,0.9-0.4,1.2-0.7c0.2-0.4,0.2-0.9,0-1.4C8.2,2.3,7.6,2,7.1,2C3.2,2,0,5.2,0,9.2v13.5C0,26.7,3.2,30,7.1,30l0,0c3.9,0,7.1-3.2,7.1-7.3c0-0.2,0-0.4,0-0.5c0.2-0.9,0.9-1.4,1.8-1.4s1.6,0.5,1.8,1.4v0.2c0,0.2,0,0.2,0,0.4c0,2,0.7,3.7,2.1,5c1.4,1.4,3,2.1,5,2.1l0,0c2,0,3.6-0.7,5-2.1c1.4-1.2,2.1-3.2,2.1-5V9.2C32,5.2,28.8,2,24.7,2', + 'circle': 'M16 0a16 16 0 1 0 16 16A16 16 0 0 0 16 0z' +}; + +const iconsDir = path.join(__dirname, 'icons'); + +// Generate SVG for each color-icon combination +for (const [colorName, colorHex] of Object.entries(colors)) { + for (const [iconName, iconPath] of Object.entries(icons)) { + const svgContent = ` + +`; + + const filename = `${colorName}-${iconName}.svg`; + const filepath = path.join(iconsDir, filename); + fs.writeFileSync(filepath, svgContent); + console.log(`Created: ${filename}`); + } +} + +console.log('Done! Generated ' + Object.keys(colors).length * Object.keys(icons).length + ' icons'); diff --git a/icons/blue-briefcase.svg b/icons/blue-briefcase.svg new file mode 100644 index 0000000..f656bdd --- /dev/null +++ b/icons/blue-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-cart.svg b/icons/blue-cart.svg new file mode 100644 index 0000000..2160a13 --- /dev/null +++ b/icons/blue-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-chill.svg b/icons/blue-chill.svg new file mode 100644 index 0000000..1209f23 --- /dev/null +++ b/icons/blue-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-circle.svg b/icons/blue-circle.svg new file mode 100644 index 0000000..41557c2 --- /dev/null +++ b/icons/blue-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-dollar.svg b/icons/blue-dollar.svg new file mode 100644 index 0000000..895fc6d --- /dev/null +++ b/icons/blue-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-fence.svg b/icons/blue-fence.svg new file mode 100644 index 0000000..3608011 --- /dev/null +++ b/icons/blue-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-fingerprint.svg b/icons/blue-fingerprint.svg new file mode 100644 index 0000000..22159e6 --- /dev/null +++ b/icons/blue-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-food.svg b/icons/blue-food.svg new file mode 100644 index 0000000..ab1efa1 --- /dev/null +++ b/icons/blue-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-fruit.svg b/icons/blue-fruit.svg new file mode 100644 index 0000000..19e4e5a --- /dev/null +++ b/icons/blue-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-gift.svg b/icons/blue-gift.svg new file mode 100644 index 0000000..f344abf --- /dev/null +++ b/icons/blue-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-pet.svg b/icons/blue-pet.svg new file mode 100644 index 0000000..bc05f0f --- /dev/null +++ b/icons/blue-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-tree.svg b/icons/blue-tree.svg new file mode 100644 index 0000000..7c7308f --- /dev/null +++ b/icons/blue-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/blue-vacation.svg b/icons/blue-vacation.svg new file mode 100644 index 0000000..7fe63d9 --- /dev/null +++ b/icons/blue-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/briefcase.svg b/icons/briefcase.svg new file mode 100644 index 0000000..1a90390 --- /dev/null +++ b/icons/briefcase.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/cart.svg b/icons/cart.svg new file mode 100644 index 0000000..fcd71e6 --- /dev/null +++ b/icons/cart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/chill.svg b/icons/chill.svg new file mode 100644 index 0000000..66d2766 --- /dev/null +++ b/icons/chill.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/circle.svg b/icons/circle.svg new file mode 100644 index 0000000..3b74cdc --- /dev/null +++ b/icons/circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/dollar.svg b/icons/dollar.svg new file mode 100644 index 0000000..d4727c0 --- /dev/null +++ b/icons/dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/fence.svg b/icons/fence.svg new file mode 100644 index 0000000..3552f74 --- /dev/null +++ b/icons/fence.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/fingerprint.svg b/icons/fingerprint.svg new file mode 100644 index 0000000..f93efc8 --- /dev/null +++ b/icons/fingerprint.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/icons/food.svg b/icons/food.svg new file mode 100644 index 0000000..c333cc0 --- /dev/null +++ b/icons/food.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/fruit.svg b/icons/fruit.svg new file mode 100644 index 0000000..c9fd538 --- /dev/null +++ b/icons/fruit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/gift.svg b/icons/gift.svg new file mode 100644 index 0000000..2e1e93f --- /dev/null +++ b/icons/gift.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/green-briefcase.svg b/icons/green-briefcase.svg new file mode 100644 index 0000000..ac4f2ae --- /dev/null +++ b/icons/green-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-cart.svg b/icons/green-cart.svg new file mode 100644 index 0000000..f26fd08 --- /dev/null +++ b/icons/green-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-chill.svg b/icons/green-chill.svg new file mode 100644 index 0000000..b375ce8 --- /dev/null +++ b/icons/green-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-circle.svg b/icons/green-circle.svg new file mode 100644 index 0000000..9cbbfe9 --- /dev/null +++ b/icons/green-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-dollar.svg b/icons/green-dollar.svg new file mode 100644 index 0000000..25c2391 --- /dev/null +++ b/icons/green-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-fence.svg b/icons/green-fence.svg new file mode 100644 index 0000000..a890f2a --- /dev/null +++ b/icons/green-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-fingerprint.svg b/icons/green-fingerprint.svg new file mode 100644 index 0000000..f9c255b --- /dev/null +++ b/icons/green-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-food.svg b/icons/green-food.svg new file mode 100644 index 0000000..9c83c47 --- /dev/null +++ b/icons/green-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-fruit.svg b/icons/green-fruit.svg new file mode 100644 index 0000000..f427be3 --- /dev/null +++ b/icons/green-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-gift.svg b/icons/green-gift.svg new file mode 100644 index 0000000..acee47b --- /dev/null +++ b/icons/green-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-pet.svg b/icons/green-pet.svg new file mode 100644 index 0000000..49bc78d --- /dev/null +++ b/icons/green-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-tree.svg b/icons/green-tree.svg new file mode 100644 index 0000000..296dac0 --- /dev/null +++ b/icons/green-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/green-vacation.svg b/icons/green-vacation.svg new file mode 100644 index 0000000..ddcd9d5 --- /dev/null +++ b/icons/green-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/icon-48.png b/icons/icon-48.png new file mode 100644 index 0000000..0cc610d Binary files /dev/null and b/icons/icon-48.png differ diff --git a/icons/icon-96.png b/icons/icon-96.png new file mode 100644 index 0000000..0cc610d Binary files /dev/null and b/icons/icon-96.png differ diff --git a/icons/orange-briefcase.svg b/icons/orange-briefcase.svg new file mode 100644 index 0000000..b05e949 --- /dev/null +++ b/icons/orange-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-cart.svg b/icons/orange-cart.svg new file mode 100644 index 0000000..d328200 --- /dev/null +++ b/icons/orange-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-chill.svg b/icons/orange-chill.svg new file mode 100644 index 0000000..8f2f2cb --- /dev/null +++ b/icons/orange-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-circle.svg b/icons/orange-circle.svg new file mode 100644 index 0000000..dbe52bd --- /dev/null +++ b/icons/orange-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-dollar.svg b/icons/orange-dollar.svg new file mode 100644 index 0000000..44e3a1e --- /dev/null +++ b/icons/orange-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-fence.svg b/icons/orange-fence.svg new file mode 100644 index 0000000..8bf5927 --- /dev/null +++ b/icons/orange-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-fingerprint.svg b/icons/orange-fingerprint.svg new file mode 100644 index 0000000..1dec782 --- /dev/null +++ b/icons/orange-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-food.svg b/icons/orange-food.svg new file mode 100644 index 0000000..1893796 --- /dev/null +++ b/icons/orange-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-fruit.svg b/icons/orange-fruit.svg new file mode 100644 index 0000000..5e1e8a3 --- /dev/null +++ b/icons/orange-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-gift.svg b/icons/orange-gift.svg new file mode 100644 index 0000000..44f6f3c --- /dev/null +++ b/icons/orange-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-pet.svg b/icons/orange-pet.svg new file mode 100644 index 0000000..46b0f9e --- /dev/null +++ b/icons/orange-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-tree.svg b/icons/orange-tree.svg new file mode 100644 index 0000000..c5e6834 --- /dev/null +++ b/icons/orange-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/orange-vacation.svg b/icons/orange-vacation.svg new file mode 100644 index 0000000..2f1f50c --- /dev/null +++ b/icons/orange-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pet.svg b/icons/pet.svg new file mode 100644 index 0000000..0833b1c --- /dev/null +++ b/icons/pet.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/pink-briefcase.svg b/icons/pink-briefcase.svg new file mode 100644 index 0000000..a5814fb --- /dev/null +++ b/icons/pink-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-cart.svg b/icons/pink-cart.svg new file mode 100644 index 0000000..aff839f --- /dev/null +++ b/icons/pink-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-chill.svg b/icons/pink-chill.svg new file mode 100644 index 0000000..26900b8 --- /dev/null +++ b/icons/pink-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-circle.svg b/icons/pink-circle.svg new file mode 100644 index 0000000..3435760 --- /dev/null +++ b/icons/pink-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-dollar.svg b/icons/pink-dollar.svg new file mode 100644 index 0000000..02f5b4f --- /dev/null +++ b/icons/pink-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-fence.svg b/icons/pink-fence.svg new file mode 100644 index 0000000..a137853 --- /dev/null +++ b/icons/pink-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-fingerprint.svg b/icons/pink-fingerprint.svg new file mode 100644 index 0000000..b3f6bda --- /dev/null +++ b/icons/pink-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-food.svg b/icons/pink-food.svg new file mode 100644 index 0000000..f26bf1b --- /dev/null +++ b/icons/pink-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-fruit.svg b/icons/pink-fruit.svg new file mode 100644 index 0000000..0e44a41 --- /dev/null +++ b/icons/pink-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-gift.svg b/icons/pink-gift.svg new file mode 100644 index 0000000..c97c7f5 --- /dev/null +++ b/icons/pink-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-pet.svg b/icons/pink-pet.svg new file mode 100644 index 0000000..5c06b33 --- /dev/null +++ b/icons/pink-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-tree.svg b/icons/pink-tree.svg new file mode 100644 index 0000000..977a183 --- /dev/null +++ b/icons/pink-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/pink-vacation.svg b/icons/pink-vacation.svg new file mode 100644 index 0000000..67c4da6 --- /dev/null +++ b/icons/pink-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-briefcase.svg b/icons/purple-briefcase.svg new file mode 100644 index 0000000..2079a88 --- /dev/null +++ b/icons/purple-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-cart.svg b/icons/purple-cart.svg new file mode 100644 index 0000000..8234af1 --- /dev/null +++ b/icons/purple-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-chill.svg b/icons/purple-chill.svg new file mode 100644 index 0000000..c2868a5 --- /dev/null +++ b/icons/purple-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-circle.svg b/icons/purple-circle.svg new file mode 100644 index 0000000..0d6e57a --- /dev/null +++ b/icons/purple-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-dollar.svg b/icons/purple-dollar.svg new file mode 100644 index 0000000..8a7733e --- /dev/null +++ b/icons/purple-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-fence.svg b/icons/purple-fence.svg new file mode 100644 index 0000000..708ae69 --- /dev/null +++ b/icons/purple-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-fingerprint.svg b/icons/purple-fingerprint.svg new file mode 100644 index 0000000..9356445 --- /dev/null +++ b/icons/purple-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-food.svg b/icons/purple-food.svg new file mode 100644 index 0000000..ab79e6a --- /dev/null +++ b/icons/purple-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-fruit.svg b/icons/purple-fruit.svg new file mode 100644 index 0000000..d6e70d6 --- /dev/null +++ b/icons/purple-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-gift.svg b/icons/purple-gift.svg new file mode 100644 index 0000000..d446430 --- /dev/null +++ b/icons/purple-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-pet.svg b/icons/purple-pet.svg new file mode 100644 index 0000000..6fb32f6 --- /dev/null +++ b/icons/purple-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-tree.svg b/icons/purple-tree.svg new file mode 100644 index 0000000..5c128d4 --- /dev/null +++ b/icons/purple-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/purple-vacation.svg b/icons/purple-vacation.svg new file mode 100644 index 0000000..ee54fe7 --- /dev/null +++ b/icons/purple-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-briefcase.svg b/icons/red-briefcase.svg new file mode 100644 index 0000000..cb8e8ec --- /dev/null +++ b/icons/red-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-cart.svg b/icons/red-cart.svg new file mode 100644 index 0000000..6b6ae8b --- /dev/null +++ b/icons/red-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-chill.svg b/icons/red-chill.svg new file mode 100644 index 0000000..beb76f4 --- /dev/null +++ b/icons/red-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-circle.svg b/icons/red-circle.svg new file mode 100644 index 0000000..f9e965e --- /dev/null +++ b/icons/red-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-dollar.svg b/icons/red-dollar.svg new file mode 100644 index 0000000..7128539 --- /dev/null +++ b/icons/red-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-fence.svg b/icons/red-fence.svg new file mode 100644 index 0000000..d65d238 --- /dev/null +++ b/icons/red-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-fingerprint.svg b/icons/red-fingerprint.svg new file mode 100644 index 0000000..34e8159 --- /dev/null +++ b/icons/red-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-food.svg b/icons/red-food.svg new file mode 100644 index 0000000..7abf479 --- /dev/null +++ b/icons/red-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-fruit.svg b/icons/red-fruit.svg new file mode 100644 index 0000000..07fe791 --- /dev/null +++ b/icons/red-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-gift.svg b/icons/red-gift.svg new file mode 100644 index 0000000..8e7cae8 --- /dev/null +++ b/icons/red-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-pet.svg b/icons/red-pet.svg new file mode 100644 index 0000000..ff491fc --- /dev/null +++ b/icons/red-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-tree.svg b/icons/red-tree.svg new file mode 100644 index 0000000..3e3cfae --- /dev/null +++ b/icons/red-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/red-vacation.svg b/icons/red-vacation.svg new file mode 100644 index 0000000..6d58798 --- /dev/null +++ b/icons/red-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-briefcase.svg b/icons/toolbar-briefcase.svg new file mode 100644 index 0000000..c1bb54c --- /dev/null +++ b/icons/toolbar-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-cart.svg b/icons/toolbar-cart.svg new file mode 100644 index 0000000..8514056 --- /dev/null +++ b/icons/toolbar-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-chill.svg b/icons/toolbar-chill.svg new file mode 100644 index 0000000..e3ee8d6 --- /dev/null +++ b/icons/toolbar-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-circle.svg b/icons/toolbar-circle.svg new file mode 100644 index 0000000..078e8c8 --- /dev/null +++ b/icons/toolbar-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-dollar.svg b/icons/toolbar-dollar.svg new file mode 100644 index 0000000..1b579ed --- /dev/null +++ b/icons/toolbar-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-fence.svg b/icons/toolbar-fence.svg new file mode 100644 index 0000000..eee0020 --- /dev/null +++ b/icons/toolbar-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-fingerprint.svg b/icons/toolbar-fingerprint.svg new file mode 100644 index 0000000..28a53de --- /dev/null +++ b/icons/toolbar-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-food.svg b/icons/toolbar-food.svg new file mode 100644 index 0000000..6c4af87 --- /dev/null +++ b/icons/toolbar-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-fruit.svg b/icons/toolbar-fruit.svg new file mode 100644 index 0000000..8c1a102 --- /dev/null +++ b/icons/toolbar-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-gift.svg b/icons/toolbar-gift.svg new file mode 100644 index 0000000..a11ed0b --- /dev/null +++ b/icons/toolbar-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-pet.svg b/icons/toolbar-pet.svg new file mode 100644 index 0000000..e410868 --- /dev/null +++ b/icons/toolbar-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-tree.svg b/icons/toolbar-tree.svg new file mode 100644 index 0000000..4eab474 --- /dev/null +++ b/icons/toolbar-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/toolbar-vacation.svg b/icons/toolbar-vacation.svg new file mode 100644 index 0000000..e3e0a7c --- /dev/null +++ b/icons/toolbar-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/tree.svg b/icons/tree.svg new file mode 100644 index 0000000..5b2b3c1 --- /dev/null +++ b/icons/tree.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/turquoise-briefcase.svg b/icons/turquoise-briefcase.svg new file mode 100644 index 0000000..de45fcc --- /dev/null +++ b/icons/turquoise-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-cart.svg b/icons/turquoise-cart.svg new file mode 100644 index 0000000..02069e2 --- /dev/null +++ b/icons/turquoise-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-chill.svg b/icons/turquoise-chill.svg new file mode 100644 index 0000000..1245031 --- /dev/null +++ b/icons/turquoise-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-circle.svg b/icons/turquoise-circle.svg new file mode 100644 index 0000000..194fd30 --- /dev/null +++ b/icons/turquoise-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-dollar.svg b/icons/turquoise-dollar.svg new file mode 100644 index 0000000..c093e2e --- /dev/null +++ b/icons/turquoise-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-fence.svg b/icons/turquoise-fence.svg new file mode 100644 index 0000000..28d1896 --- /dev/null +++ b/icons/turquoise-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-fingerprint.svg b/icons/turquoise-fingerprint.svg new file mode 100644 index 0000000..af64dd5 --- /dev/null +++ b/icons/turquoise-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-food.svg b/icons/turquoise-food.svg new file mode 100644 index 0000000..9e1b0d2 --- /dev/null +++ b/icons/turquoise-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-fruit.svg b/icons/turquoise-fruit.svg new file mode 100644 index 0000000..371b1dc --- /dev/null +++ b/icons/turquoise-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-gift.svg b/icons/turquoise-gift.svg new file mode 100644 index 0000000..85d4676 --- /dev/null +++ b/icons/turquoise-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-pet.svg b/icons/turquoise-pet.svg new file mode 100644 index 0000000..79c6717 --- /dev/null +++ b/icons/turquoise-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-tree.svg b/icons/turquoise-tree.svg new file mode 100644 index 0000000..c6c221f --- /dev/null +++ b/icons/turquoise-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/turquoise-vacation.svg b/icons/turquoise-vacation.svg new file mode 100644 index 0000000..3966d9d --- /dev/null +++ b/icons/turquoise-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/usercontext.svg b/icons/usercontext.svg new file mode 100644 index 0000000..a111857 --- /dev/null +++ b/icons/usercontext.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/vacation.svg b/icons/vacation.svg new file mode 100644 index 0000000..4e23277 --- /dev/null +++ b/icons/vacation.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/yellow-briefcase.svg b/icons/yellow-briefcase.svg new file mode 100644 index 0000000..d7eefa9 --- /dev/null +++ b/icons/yellow-briefcase.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-cart.svg b/icons/yellow-cart.svg new file mode 100644 index 0000000..6d4d761 --- /dev/null +++ b/icons/yellow-cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-chill.svg b/icons/yellow-chill.svg new file mode 100644 index 0000000..b9a40a6 --- /dev/null +++ b/icons/yellow-chill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-circle.svg b/icons/yellow-circle.svg new file mode 100644 index 0000000..b3d0e10 --- /dev/null +++ b/icons/yellow-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-dollar.svg b/icons/yellow-dollar.svg new file mode 100644 index 0000000..a50aeaf --- /dev/null +++ b/icons/yellow-dollar.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-fence.svg b/icons/yellow-fence.svg new file mode 100644 index 0000000..fcb95c3 --- /dev/null +++ b/icons/yellow-fence.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-fingerprint.svg b/icons/yellow-fingerprint.svg new file mode 100644 index 0000000..d92f7ff --- /dev/null +++ b/icons/yellow-fingerprint.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-food.svg b/icons/yellow-food.svg new file mode 100644 index 0000000..01d7943 --- /dev/null +++ b/icons/yellow-food.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-fruit.svg b/icons/yellow-fruit.svg new file mode 100644 index 0000000..0c51099 --- /dev/null +++ b/icons/yellow-fruit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-gift.svg b/icons/yellow-gift.svg new file mode 100644 index 0000000..f5659c2 --- /dev/null +++ b/icons/yellow-gift.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-pet.svg b/icons/yellow-pet.svg new file mode 100644 index 0000000..cfe5730 --- /dev/null +++ b/icons/yellow-pet.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-tree.svg b/icons/yellow-tree.svg new file mode 100644 index 0000000..4a33796 --- /dev/null +++ b/icons/yellow-tree.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/icons/yellow-vacation.svg b/icons/yellow-vacation.svg new file mode 100644 index 0000000..ef5f7f6 --- /dev/null +++ b/icons/yellow-vacation.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f1fde45 --- /dev/null +++ b/manifest.json @@ -0,0 +1,41 @@ +{ + "manifest_version": 2, + "name": "Container Bookmarks", + "version": "1.0.0", + "description": "Associate bookmarks with Firefox containers. Open bookmarks in your preferred container automatically.", + "author": "masterElmar", + "icons": { + "48": "icons/icon-48.png", + "96": "icons/icon-96.png" + }, + "permissions": [ + "bookmarks", + "contextualIdentities", + "cookies", + "storage", + "tabs", + "menus", + "notifications", + "webNavigation" + ], + "background": { + "scripts": [ + "background.js" + ], + "persistent": false + }, + "browser_action": { + "default_icon": { + "48": "icons/icon-48.png", + "96": "icons/icon-96.png" + }, + "default_title": "Container Bookmarks", + "default_popup": "popup/popup.html" + }, + "browser_specific_settings": { + "gecko": { + "id": "container-bookmarks@masterelmar", + "strict_min_version": "57.0" + } + } +} \ No newline at end of file diff --git a/popup/popup.css b/popup/popup.css new file mode 100644 index 0000000..cb208ef --- /dev/null +++ b/popup/popup.css @@ -0,0 +1,192 @@ +/* Container Bookmarks - Popup Styles */ +/* Firefox Photon Design System inspired */ + +:root { + --background-color: #1c1b22; + --surface-color: #2b2a33; + --text-color: #fbfbfe; + --text-secondary: #b1b1b3; + --border-color: #3a3944; + --primary-color: #00ddff; + --danger-color: #ff6b6b; + --hover-color: #3a3944; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 13px; + background-color: var(--background-color); + color: var(--text-color); + width: 320px; + min-height: 200px; +} + +.popup-container { + display: flex; + flex-direction: column; + min-height: 200px; +} + +/* Header */ +.header { + padding: 16px; + border-bottom: 1px solid var(--border-color); + background: linear-gradient(135deg, #2b2a33 0%, #1c1b22 100%); +} + +.header h1 { + font-size: 16px; + font-weight: 600; + color: var(--primary-color); +} + +/* Content */ +.content { + flex: 1; + padding: 12px; +} + +.section { + margin-bottom: 16px; +} + +.section h2 { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--text-secondary); + margin-bottom: 8px; +} + +/* Mappings List */ +.mappings-list { + max-height: 200px; + overflow-y: auto; +} + +.mapping-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + background-color: var(--surface-color); + border-radius: 6px; + margin-bottom: 6px; + transition: background-color 0.15s ease; +} + +.mapping-item:hover { + background-color: var(--hover-color); +} + +.container-indicator { + width: 4px; + height: 28px; + border-radius: 2px; + flex-shrink: 0; +} + +.mapping-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.bookmark-title { + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.container-name { + font-size: 11px; + color: var(--text-secondary); +} + +.remove-btn { + width: 20px; + height: 20px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 16px; + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; + flex-shrink: 0; +} + +.remove-btn:hover { + background-color: var(--danger-color); + color: white; +} + +/* Empty/Loading States */ +.empty, .loading { + text-align: center; + color: var(--text-secondary); + padding: 20px; + line-height: 1.5; +} + +/* Tips */ +.tips { + list-style: none; + padding: 0; +} + +.tips li { + position: relative; + padding-left: 16px; + margin-bottom: 6px; + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; +} + +.tips li::before { + content: '•'; + position: absolute; + left: 0; + color: var(--primary-color); +} + +/* Footer */ +.footer { + padding: 10px 16px; + border-top: 1px solid var(--border-color); + font-size: 11px; + color: var(--text-secondary); + text-align: center; +} + +/* Scrollbar */ +.mappings-list::-webkit-scrollbar { + width: 6px; +} + +.mappings-list::-webkit-scrollbar-track { + background: transparent; +} + +.mappings-list::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 3px; +} + +.mappings-list::-webkit-scrollbar-thumb:hover { + background-color: var(--text-secondary); +} diff --git a/popup/popup.html b/popup/popup.html new file mode 100644 index 0000000..86fd179 --- /dev/null +++ b/popup/popup.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/popup/popup.js b/popup/popup.js new file mode 100644 index 0000000..93a91d3 --- /dev/null +++ b/popup/popup.js @@ -0,0 +1,125 @@ +/** + * Container Bookmarks - Popup Script + */ + +const STORAGE_KEY = 'bookmark_container_mappings'; + +// Container color palette (matches Firefox) +const CONTAINER_COLORS = { + 'blue': '#37adff', + 'turquoise': '#00c79a', + 'green': '#51cd00', + 'yellow': '#ffcb00', + 'orange': '#ff9f00', + 'red': '#ff613d', + 'pink': '#ff4bda', + 'purple': '#af51f5', + 'toolbar': '#7c7c7d' +}; + +/** + * Get all bookmark-container mappings + */ +async function getMappings() { + const result = await browser.storage.local.get(STORAGE_KEY); + return result[STORAGE_KEY] || {}; +} + +/** + * Get all containers + */ +async function getContainers() { + try { + return await browser.contextualIdentities.query({}); + } catch (error) { + console.error('Failed to get containers:', error); + return []; + } +} + +/** + * Remove a mapping + */ +async function removeMapping(bookmarkId) { + const mappings = await getMappings(); + delete mappings[bookmarkId]; + await browser.storage.local.set({ [STORAGE_KEY]: mappings }); +} + +/** + * Render the mappings list + */ +async function renderMappings() { + const mappingsListEl = document.getElementById('mappings-list'); + const statsEl = document.getElementById('stats'); + + const mappings = await getMappings(); + const containers = await getContainers(); + const containerMap = {}; + containers.forEach(c => containerMap[c.cookieStoreId] = c); + + const entries = Object.entries(mappings); + + if (entries.length === 0) { + mappingsListEl.innerHTML = '

No container assignments yet.
Right-click a bookmark to get started!

'; + statsEl.textContent = '0 bookmarks assigned'; + return; + } + + mappingsListEl.innerHTML = ''; + + for (const [bookmarkId, mapping] of entries) { + try { + const bookmarks = await browser.bookmarks.get(bookmarkId); + if (!bookmarks || bookmarks.length === 0) { + // Orphaned mapping, clean up + await removeMapping(bookmarkId); + continue; + } + + const bookmark = bookmarks[0]; + const container = containerMap[mapping.containerId]; + const containerColor = container ? CONTAINER_COLORS[container.color] || '#7c7c7d' : '#7c7c7d'; + const containerName = container ? container.name : mapping.containerName; + + const item = document.createElement('div'); + item.className = 'mapping-item'; + item.innerHTML = ` +
+
+ ${truncate(bookmark.title, 30)} + ${containerName} +
+ + `; + + mappingsListEl.appendChild(item); + } catch (error) { + // Bookmark doesn't exist, clean up + await removeMapping(bookmarkId); + } + } + + const validCount = mappingsListEl.children.length; + statsEl.textContent = `${validCount} bookmark${validCount !== 1 ? 's' : ''} assigned`; + + // Add click handlers for remove buttons + document.querySelectorAll('.remove-btn').forEach(btn => { + btn.addEventListener('click', async (e) => { + const bookmarkId = e.target.dataset.bookmarkId; + await removeMapping(bookmarkId); + renderMappings(); + }); + }); +} + +/** + * Truncate text with ellipsis + */ +function truncate(text, maxLength) { + if (text.length <= maxLength) return text; + return text.substring(0, maxLength - 1) + '…'; +} + +// Initialize +document.addEventListener('DOMContentLoaded', renderMappings);