diff --git a/popup.js b/popup.js
deleted file mode 100644
index e794336..0000000
--- a/popup.js
+++ /dev/null
@@ -1,1031 +0,0 @@
-// ---------------------------------------
-// portage by pvrz
-// github: https://github.com/pvrzz/portage
-// file name: popup.js
-// ---------------------------------------
-
-
-// ---------------------------------------
-// state
-// ---------------------------------------
-
-const state = {
- type: "extensions",
- mode: "installed",
- selectedIds: new Set(),
- expandedDomains: new Set(),
- data: {
- extensionsInstalled: [],
- extensionsImported: [],
- cookiesInstalled: {},
- cookiesImported: {},
- },
-};
-
-
-// ---------------------------------------
-// constants
-// ---------------------------------------
-
-const WEBSTORE_BASE = "https://chromewebstore.google.com/detail";
-const SELF_ID = chrome.runtime.id;
-const PBKDF2_ITERATIONS = 250000;
-const COOKIES_FORMAT = "portage-cookies";
-const COOKIES_VERSION = 1;
-const EXTENSIONS_FORMAT = "portage-extensions";
-
-
-// ---------------------------------------
-// dom refs
-// ---------------------------------------
-
-const $ = (id) => document.getElementById(id);
-const els = {
- list: $("ext-list"),
- emptyState: $("empty-state"),
- emptyTitle: $("empty-title"),
- emptySub: $("empty-sub"),
- subtitle: $("subtitle"),
- selectedCount: $("selected-count"),
- primaryBtn: $("primary-btn"),
- primaryLabel: $("primary-label"),
- statusEl: $("status"),
- importInput: $("import-input"),
- modal: $("modal-backdrop"),
- modalTitle: $("modal-title"),
- modalDesc: $("modal-desc"),
- modalPass: $("modal-passphrase"),
- modalPassConfirm: $("modal-passphrase-confirm"),
- modalSkipWrap: $("modal-skip-encrypt-wrap"),
- modalSkip: $("modal-skip-encrypt"),
- modalWarn: $("modal-warn"),
- modalConfirm: $("modal-confirm"),
- modalCancel: $("modal-cancel"),
- modalClose: $("modal-close"),
-};
-
-
-// ---------------------------------------
-// initialization
-// ---------------------------------------
-
-document.addEventListener("DOMContentLoaded", async () => {
- await loadExtensions();
- await loadCookies();
- attachEventListeners();
- render();
-});
-
-
-// ---------------------------------------
-// data loading
-// ---------------------------------------
-
-async function loadExtensions() {
- const all = await chrome.management.getAll();
- state.data.extensionsInstalled = all
- .filter((ext) => ext.type === "extension" && ext.id !== SELF_ID)
- .sort((a, b) => a.name.localeCompare(b.name));
-}
-
-async function loadCookies() {
- const all = await chrome.cookies.getAll({});
- state.data.cookiesInstalled = groupCookiesByDomain(all);
-}
-
-function groupCookiesByDomain(cookies) {
- const grouped = {};
- for (const c of cookies) {
- const key = c.domain.startsWith(".") ? c.domain.slice(1) : c.domain;
- if (!grouped[key]) grouped[key] = [];
- grouped[key].push(c);
- }
- const sorted = {};
- for (const k of Object.keys(grouped).sort()) {
- sorted[k] = grouped[k].sort((a, b) => a.name.localeCompare(b.name));
- }
- return sorted;
-}
-
-
-// ---------------------------------------
-// current view helpers
-// ---------------------------------------
-
-function currentList() {
- if (state.type === "extensions") {
- return state.mode === "installed"
- ? state.data.extensionsInstalled
- : state.data.extensionsImported;
- } else {
- return state.mode === "installed"
- ? state.data.cookiesInstalled
- : state.data.cookiesImported;
- }
-}
-
-function currentItemIds() {
- const list = currentList();
- if (state.type === "extensions") {
- return list.map((e) => e.id);
- } else {
- return Object.keys(list);
- }
-}
-
-
-// ---------------------------------------
-// render
-// ---------------------------------------
-
-function render() {
- updateSubtitle();
- updateTabsActive();
- updatePrimaryButton();
- updateImportAccept();
-
- const list = currentList();
- const empty = state.type === "extensions"
- ? list.length === 0
- : Object.keys(list).length === 0;
-
- if (empty) {
- els.list.innerHTML = "";
- els.emptyState.classList.remove("hidden");
- updateEmptyState();
- } else {
- els.emptyState.classList.add("hidden");
- if (state.type === "extensions") {
- renderExtensionList(list);
- } else {
- renderCookieGroups(list);
- }
- }
-
- updateSelectionUi();
-}
-
-function updateSubtitle() {
- const list = currentList();
- if (state.type === "extensions") {
- els.subtitle.textContent = `${list.length} ${state.mode === "installed" ? "installed" : "imported"}`;
- } else {
- const domainCount = Object.keys(list).length;
- let cookieCount = 0;
- for (const k of Object.keys(list)) cookieCount += list[k].length;
- els.subtitle.textContent = `${cookieCount} cookies · ${domainCount} domains`;
- }
-}
-
-function updateTabsActive() {
- document.querySelectorAll(".type-tab").forEach((tab) => {
- tab.classList.toggle("active", tab.dataset.type === state.type);
- });
- document.querySelectorAll(".mode-tab").forEach((tab) => {
- tab.classList.toggle("active", tab.dataset.mode === state.mode);
- });
-}
-
-function updatePrimaryButton() {
- const hasSelection = state.selectedIds.size > 0;
- els.primaryBtn.disabled = !hasSelection;
-
- if (state.type === "extensions") {
- els.primaryLabel.textContent = "open in tab group";
- } else if (state.mode === "installed") {
- els.primaryLabel.textContent = "export selected";
- } else {
- els.primaryLabel.textContent = "apply to browser";
- }
-}
-
-function updateImportAccept() {
- els.importInput.accept = state.type === "extensions" ? ".html,.htm" : ".json";
-}
-
-function updateEmptyState() {
- if (state.type === "extensions") {
- els.emptyTitle.textContent = state.mode === "imported" ? "no imported extensions" : "no extensions found";
- els.emptySub.textContent = state.mode === "imported" ? "import an html export to see it here" : "you have no installable extensions";
- } else {
- els.emptyTitle.textContent = state.mode === "imported" ? "no imported cookies" : "no cookies found";
- els.emptySub.textContent = state.mode === "imported" ? "import a cookies json file to see them here" : "your browser has no cookies stored";
- }
-}
-
-function renderExtensionList(list) {
- els.list.innerHTML = "";
- for (const ext of list) {
- const li = document.createElement("li");
- li.className = "ext-item";
- li.dataset.id = ext.id;
- if (state.selectedIds.has(ext.id)) li.classList.add("selected");
-
- const iconUrl = getBestIcon(ext);
- const isDisabled = ext.enabled === false;
- const isSideload =
- ext.installType && ext.installType !== "normal" && ext.installType !== "admin";
-
- li.innerHTML = `
-
- ${iconUrl ? `
` : `
`}
-
-
${escapeHtml(ext.name)}
-
- ${escapeHtml(ext.id)}
- ${isDisabled ? 'off' : ""}
- ${isSideload ? 'sideload' : ""}
-
-
- `;
-
- li.addEventListener("click", (e) => {
- if (e.target.classList.contains("ext-checkbox")) return;
- toggleSelection(ext.id);
- });
- li.querySelector(".ext-checkbox").addEventListener("change", () => {
- toggleSelection(ext.id);
- });
-
- els.list.appendChild(li);
- }
-}
-
-function renderCookieGroups(grouped) {
- els.list.innerHTML = "";
- for (const domain of Object.keys(grouped)) {
- const cookies = grouped[domain];
- const isSelected = state.selectedIds.has(domain);
- const isExpanded = state.expandedDomains.has(domain);
-
- const groupLi = document.createElement("li");
- groupLi.className = "domain-group";
-
- const header = document.createElement("div");
- header.className = "domain-header";
- if (isSelected) header.classList.add("selected");
- header.dataset.domain = domain;
-
- header.innerHTML = `
-
-
- ${escapeHtml(domain)}
- ${cookies.length}
- `;
-
- header.addEventListener("click", (e) => {
- if (e.target.classList.contains("ext-checkbox")) return;
- if (e.target.classList.contains("domain-chevron") || e.target.closest(".domain-chevron")) {
- toggleDomainExpansion(domain);
- } else {
- toggleSelection(domain);
- }
- });
-
- header.querySelector(".ext-checkbox").addEventListener("change", () => {
- toggleSelection(domain);
- });
-
- groupLi.appendChild(header);
-
- if (isExpanded) {
- const cookieList = document.createElement("ul");
- cookieList.className = "cookie-list";
- for (const c of cookies) {
- const cookieLi = document.createElement("li");
- cookieLi.className = "cookie-item";
- const flags = [];
- if (c.secure) flags.push('s');
- if (c.httpOnly) flags.push('h');
- if (c.session) flags.push('session');
- const truncatedValue = c.value.length > 30 ? c.value.slice(0, 30) + "…" : c.value;
- cookieLi.innerHTML = `
- ${escapeHtml(c.name)}
- ${escapeHtml(truncatedValue)}
- ${flags.join("")}
- `;
- cookieList.appendChild(cookieLi);
- }
- groupLi.appendChild(cookieList);
- }
-
- els.list.appendChild(groupLi);
- }
-}
-
-function getBestIcon(ext) {
- if (!ext.icons || ext.icons.length === 0) return null;
- return (ext.icons.find((i) => i.size === 48) || ext.icons.find((i) => i.size === 32) || ext.icons[ext.icons.length - 1]).url;
-}
-
-
-// ---------------------------------------
-// selection
-// ---------------------------------------
-
-function toggleSelection(id) {
- if (state.selectedIds.has(id)) {
- state.selectedIds.delete(id);
- } else {
- state.selectedIds.add(id);
- }
- render();
-}
-
-function toggleDomainExpansion(domain) {
- if (state.expandedDomains.has(domain)) {
- state.expandedDomains.delete(domain);
- } else {
- state.expandedDomains.add(domain);
- }
- render();
-}
-
-function selectAll() {
- state.selectedIds = new Set(currentItemIds());
- render();
-}
-
-function selectNone() {
- state.selectedIds.clear();
- render();
-}
-
-function selectInvert() {
- const ids = currentItemIds();
- const next = new Set();
- for (const id of ids) {
- if (!state.selectedIds.has(id)) next.add(id);
- }
- state.selectedIds = next;
- render();
-}
-
-function updateSelectionUi() {
- els.selectedCount.textContent = state.selectedIds.size;
- updatePrimaryButton();
-}
-
-
-// ---------------------------------------
-// primary action dispatcher
-// ---------------------------------------
-
-async function handlePrimaryAction() {
- if (state.type === "extensions") {
- await openSelectedInTabGroup();
- } else if (state.mode === "installed") {
- await exportCookies();
- } else {
- await applyCookies();
- }
-}
-
-
-// ---------------------------------------
-// extension tab group action
-// ---------------------------------------
-
-async function openSelectedInTabGroup() {
- const list = currentList();
- const selected = list.filter((e) => state.selectedIds.has(e.id));
- if (selected.length === 0) return;
-
- if (selected.length > 10) {
- if (!confirm(`open ${selected.length} tabs at once?`)) return;
- }
-
- showStatus(`opening ${selected.length} tab${selected.length === 1 ? "" : "s"}...`);
-
- try {
- const tabs = [];
- for (const ext of selected) {
- const tab = await chrome.tabs.create({
- url: `${WEBSTORE_BASE}/${ext.id}`,
- active: false,
- });
- tabs.push(tab);
- }
-
- const groupId = await chrome.tabs.group({ tabIds: tabs.map((t) => t.id) });
- await chrome.tabGroups.update(groupId, {
- title: state.mode === "imported" ? `portage: migrate ${selected.length}` : `portage (${selected.length})`,
- color: "cyan",
- collapsed: false,
- });
-
- showStatus(`opened ${tabs.length} tabs in a group`, "success");
- } catch (err) {
- console.error(err);
- showStatus(`error: ${err.message}`, "error");
- }
-}
-
-
-// ---------------------------------------
-// export dispatcher
-// ---------------------------------------
-
-function handleExport() {
- if (state.type === "extensions") {
- exportExtensionsToHtml();
- } else {
- exportCookies();
- }
-}
-
-
-// ---------------------------------------
-// extension export
-// ---------------------------------------
-
-function exportExtensionsToHtml() {
- const list = currentList();
- const toExport = state.selectedIds.size > 0
- ? list.filter((e) => state.selectedIds.has(e.id))
- : list;
-
- if (toExport.length === 0) {
- showStatus("nothing to export", "error");
- return;
- }
-
- const exportData = toExport.map((ext) => ({
- id: ext.id,
- name: ext.name,
- version: ext.version || "",
- enabled: ext.enabled !== false,
- homepageUrl: ext.homepageUrl || "",
- }));
-
- const html = buildExportHtml(exportData);
- downloadBlob(new Blob([html], { type: "text/html" }), `portage-extensions-${dateStamp()}.html`);
- showStatus(`exported ${toExport.length} extensions`, "success");
-}
-
-function buildExportHtml(data) {
- const jsonBlob = JSON.stringify({
- type: EXTENSIONS_FORMAT,
- version: 1,
- exportedAt: new Date().toISOString(),
- extensions: data,
- }, null, 2);
-
- const rows = data.map((ext, i) => `
-
- | ${i + 1} |
- ${escapeHtml(ext.name)} |
- ${escapeHtml(ext.id)} |
- ${escapeHtml(ext.version)} |
- ${ext.enabled ? "" : "off"} |
- install → |
-
- `).join("");
-
- return `
-
-
-
-portage export
-
-
-
-
-
portage export
-
${data.length} extension${data.length === 1 ? "" : "s"} · exported ${new Date().toLocaleString()}
-
-
-
-
- re-import this file in portage to use the tab-group flow
-
-
- | # | name | id | version | state | |
- ${rows}
-
-
-
-
-
-
-`;
-}
-
-
-// ---------------------------------------
-// cookie export
-// ---------------------------------------
-
-async function exportCookies() {
- if (state.selectedIds.size === 0) {
- showStatus("select at least one domain", "error");
- return;
- }
-
- const grouped = state.data.cookiesInstalled;
- const collected = [];
- for (const domain of state.selectedIds) {
- if (grouped[domain]) collected.push(...grouped[domain]);
- }
-
- if (collected.length === 0) {
- showStatus("no cookies in selection", "error");
- return;
- }
-
- openModal({
- title: "encrypt cookies?",
- description: `${collected.length} cookies from ${state.selectedIds.size} domain${state.selectedIds.size === 1 ? "" : "s"} will be exported. encryption is strongly recommended — this file contains your login sessions.`,
- mode: "encrypt",
- onConfirm: async ({ passphrase, skip }) => {
- const payload = {
- type: COOKIES_FORMAT,
- version: COOKIES_VERSION,
- exportedAt: new Date().toISOString(),
- encrypted: !skip,
- };
-
- if (skip) {
- payload.cookies = collected;
- } else {
- try {
- const encrypted = await encryptJson({ cookies: collected }, passphrase);
- Object.assign(payload, encrypted);
- } catch (err) {
- showStatus(`encryption failed: ${err.message}`, "error");
- return;
- }
- }
-
- const json = JSON.stringify(payload, null, 2);
- downloadBlob(new Blob([json], { type: "application/json" }), `portage-cookies-${dateStamp()}.json`);
- showStatus(`exported ${collected.length} cookies${skip ? " (unencrypted)" : ""}`, "success");
- },
- });
-}
-
-
-// ---------------------------------------
-// import dispatcher
-// ---------------------------------------
-
-function handleImport(file) {
- if (state.type === "extensions") {
- importExtensionsFromHtml(file);
- } else {
- importCookiesFromJson(file);
- }
-}
-
-
-// ---------------------------------------
-// extension import
-// ---------------------------------------
-
-function importExtensionsFromHtml(file) {
- const reader = new FileReader();
- reader.onload = (e) => {
- try {
- const html = e.target.result;
- const match = html.match(/