diff options
author | Sameera Sandakelum <[email protected]> | 2025-04-10 15:11:42 +0530 |
---|---|---|
committer | GitHub <[email protected]> | 2025-04-10 15:11:42 +0530 |
commit | 3be3f2d5b07b3af1957fabad58be4863bc5b9ac1 (patch) | |
tree | f6f74e13215b56cb9b116cff819a5d19eb54bba1 | |
parent | 7f45e1729c777de8f37e2f98bd58957a13d7027b (diff) | |
parent | 6e0a2f92e25b927c559a9b532fe1b087df921e74 (diff) |
Merge pull request #8 from sameerasw/data-viewer
New data viewer page and ability to reset forced sites and all data
-rw-r--r-- | data-viewer/data-viewer.css | 402 | ||||
-rw-r--r-- | data-viewer/data-viewer.html | 69 | ||||
-rw-r--r-- | data-viewer/data-viewer.js | 405 | ||||
-rw-r--r-- | manifest.json | 5 | ||||
-rw-r--r-- | popup/popup.html | 6 | ||||
-rw-r--r-- | popup/popup.js | 7 |
6 files changed, 894 insertions, 0 deletions
diff --git a/data-viewer/data-viewer.css b/data-viewer/data-viewer.css new file mode 100644 index 0000000..7e4d8b3 --- /dev/null +++ b/data-viewer/data-viewer.css @@ -0,0 +1,402 @@ +/* Data Viewer specific styles */ + +/* General layout adjustments */ +.app-content { + max-width: 960px; + margin: 0 auto; + padding: 20px; +} + +body { + width: auto; + min-width: 600px; +} + +/* Data section styling */ +.data-section { + margin-bottom: 24px; + background-color: var(--secondary-bg); + border-radius: var(--radius-md); + padding: 16px; +} + +.data-container { + border-radius: var(--radius-sm); + overflow: hidden; +} + +/* Table styling */ +.data-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; + background-color: var(--bg-color); +} + +.data-table th, +.data-table td { + padding: 10px 12px; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +.data-table th { + background-color: var(--bg-color); + font-weight: 600; + color: var(--accent-color); + position: sticky; + top: 0; +} + +.data-table tr:hover { + background-color: rgba(255, 255, 255, 0.05); +} + +/* Status badges */ +.badge { + display: inline-block; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} + +.badge.enabled { + background-color: var(--success-color); + color: white; +} + +.badge.disabled { + background-color: var(--border-color); + color: var(--text-secondary); +} + +/* No data message */ +.no-data { + padding: 16px; + text-align: center; + color: var(--text-secondary); + font-style: italic; + background-color: var(--bg-color); + border-radius: var(--radius-sm); +} + +/* Website list styling */ +.websites-container { + margin-top: 16px; +} + +.website-panel { + margin-bottom: 8px; + background-color: var(--bg-color); + border-radius: var(--radius-md); + overflow: hidden; +} + +.website-header { + cursor: pointer; + padding: 12px 16px; + font-weight: 500; + position: relative; + display: flex; + align-items: center; + justify-content: space-between; +} + +.website-header::after { + content: "\f078"; /* chevron-down icon */ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + transition: transform 0.3s ease; +} + +.website-header.active::after { + transform: rotate(180deg); +} + +.website-header-content { + display: flex; + align-items: center; + flex: 1; +} + +.website-name { + font-weight: 600; + color: var(--text-primary); +} + +.feature-count { + font-size: 12px; + color: var(--text-secondary); + margin-left: 12px; +} + +.settings-badge { + background-color: var(--accent-color); + color: white; + font-size: 11px; + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; +} + +.website-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; + background-color: var(--secondary-bg); +} + +/* Search functionality */ +.search-container { + position: relative; + margin-bottom: 16px; +} + +.search-input { + width: 100%; + padding: 10px 16px 10px 36px; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + background-color: var(--bg-color); + color: var(--text-primary); + font-size: 14px; +} + +.search-input:focus { + outline: none; + border-color: var(--accent-color); +} + +.search-icon { + position: absolute; + left: 12px; + top: 12px; + color: var(--text-secondary); +} + +/* Tab system */ +.tabs-container { + display: flex; + background-color: var(--bg-color); + border-bottom: 1px solid var(--border-color); +} + +.tab { + padding: 10px 16px; + cursor: pointer; + font-weight: 500; + border-bottom: 2px solid transparent; + transition: all 0.2s ease; +} + +.tab.active { + border-bottom-color: var(--accent-color); + color: var(--accent-color); +} + +.tab-contents { + padding: 16px; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Features list */ +.features-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 8px; +} + +.feature-item { + padding: 8px 12px; + background-color: var(--bg-color); + border-radius: var(--radius-sm); + font-size: 13px; +} + +/* CSS blocks */ +.css-block { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.css-block:last-child { + border-bottom: none; +} + +.css-block-header { + padding: 12px 16px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(0, 0, 0, 0.2); + position: relative; +} + +.css-block-header::after { + content: "\f078"; /* chevron-down icon */ + font-family: "Font Awesome 5 Free"; + font-weight: 900; + transition: transform 0.3s ease; +} + +.css-block-header.active::after { + transform: rotate(180deg); +} + +.feature-name { + font-weight: 500; +} + +.feature-status { + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + margin-right: 24px; +} + +.feature-status.enabled { + background-color: var(--success-color); + color: white; +} + +.feature-status.disabled { + background-color: var(--border-color); + color: var(--text-secondary); +} + +.css-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; +} + +/* CSS code display */ +.css-code { + background-color: rgba(0, 0, 0, 0.2); + padding: 16px; + margin: 0; + overflow-x: auto; + font-family: "Monaco", "Courier New", monospace; + font-size: 12px; + line-height: 1.5; + color: var(--text-primary); + max-height: 400px; + overflow-y: auto; + white-space: pre-wrap; +} + +.show-more { + background: none; + border: none; + color: var(--accent-color); + cursor: pointer; + font-size: 12px; + padding: 0; + margin-top: 8px; + text-decoration: underline; +} + +.show-more:hover { + color: var(--hover-color); +} + +/* Mode info box */ +.mode-info { + padding: 12px; + margin-bottom: 16px; + background-color: var(--bg-color); + border-radius: var(--radius-sm); + border-left: 3px solid var(--accent-color); +} + +/* Expand/collapse all button */ +.view-all-button { + margin: 8px 0; +} + +/* Clear list button styling */ +.clear-list-button { + margin-top: 8px; + margin-bottom: 16px; + background-color: var(--secondary-bg); + border-color: var(--border-color); + font-size: 12px; + padding: 6px 12px; +} + +.clear-list-button:hover { + background-color: rgba(220, 53, 69, 0.1); + border-color: var(--danger-color); + color: var(--danger-color); +} + +/* Null and object value styling */ +.null-value { + color: #999; + font-style: italic; +} + +.object-value { + color: var(--accent-color); + font-style: italic; +} + +/* Danger zone styling */ +.danger-zone { + margin-top: 24px; + padding: 16px; + background-color: rgba(220, 53, 69, 0.1); + border: 1px solid rgba(220, 53, 69, 0.3); + border-radius: var(--radius-md); +} + +.danger-title { + color: var(--danger-color); + font-size: 16px; + font-weight: 600; + margin-top: 0; + margin-bottom: 12px; +} + +.danger-actions { + display: flex; + justify-content: center; +} + +.action-button.danger { + background-color: var(--danger-color); + color: white; +} + +.action-button.danger:hover { + background-color: #e03444; + box-shadow: 0 0 8px rgba(220, 53, 69, 0.5); +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .app-content { + padding: 15px; + } + + body { + min-width: 320px; + } + + .data-table th, + .data-table td { + padding: 8px 10px; + } + + .features-list { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } +} diff --git a/data-viewer/data-viewer.html b/data-viewer/data-viewer.html new file mode 100644 index 0000000..12af92c --- /dev/null +++ b/data-viewer/data-viewer.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>ZenInternet - Data Viewer</title> + <link rel="stylesheet" href="../popup/popup.css"> + <link rel="stylesheet" href="data-viewer.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> +</head> + +<body> + <div class="container"> + <header class="app-header"> + <div class="logo-container"> + <img src="../assets/images/logo_48.png" alt="ZenInternet Logo" class="logo-img"> + <h1 class="app-title">ZenInternet Data Viewer</h1> + </div> + <div class="author">by <a href="https://www.sameerasw.com/" target="_blank">@sameerasw</a></div> + <div id="addon-version" class="addon-version"></div> + </header> + + <main class="app-content"> + <div class="data-section"> + <h2 class="section-title">Global Settings</h2> + <div id="global-settings-data" class="data-container"></div> + </div> + + <div class="data-section"> + <h2 class="section-title">Skip/Enable List</h2> + <div id="skip-list-data" class="data-container"></div> + </div> + + <div class="data-section"> + <h2 class="section-title">Websites and CSS</h2> + <div id="combined-websites-data" class="data-container"></div> + </div> + + <div class="actions"> + <button id="back-button" class="action-button secondary"> + <i class="fas fa-arrow-left"></i> Back to Extension + </button> + </div> + + <div class="danger-zone"> + <h3 class="danger-title">Danger Zone</h3> + <div class="danger-actions"> + <button id="delete-all-data" class="action-button danger"> + <i class="fas fa-trash"></i> Delete All Data + </button> + </div> + </div> + </main> + + <footer class="app-footer"> + <a href="https://sameerasw.github.io/my-internet/" class="footer-link" target="_blank"> + <i class="fas fa-book"></i> Styles Repository + </a> + <div class="footer-divider"></div> + <a href="https://github.com/sameerasw/zeninternet" class="footer-link" target="_blank"> + <i class="fa-brands fa-github"></i> Project Repository + </a> + </footer> + </div> + <script src="data-viewer.js"></script> +</body> + +</html>
\ No newline at end of file diff --git a/data-viewer/data-viewer.js b/data-viewer/data-viewer.js new file mode 100644 index 0000000..f7a7043 --- /dev/null +++ b/data-viewer/data-viewer.js @@ -0,0 +1,405 @@ +document.addEventListener("DOMContentLoaded", function () { + const BROWSER_STORAGE_KEY = "transparentZenSettings"; + const SKIP_FORCE_THEMING_KEY = "skipForceThemingList"; + + const globalSettingsElement = document.getElementById("global-settings-data"); + const skipListElement = document.getElementById("skip-list-data"); + const combinedWebsitesElement = document.getElementById( + "combined-websites-data" + ); + const backButton = document.getElementById("back-button"); + const refreshButton = document.getElementById("refresh-data"); + const deleteAllButton = document.getElementById("delete-all-data"); + const versionElement = document.getElementById("addon-version"); + + // Load and display the data + loadAllData(); + + // Display addon version + displayAddonVersion(); + + // Event listener for the back button + backButton.addEventListener("click", function () { + window.close(); + }); + + // Event listener for refresh button + refreshButton.addEventListener("click", function () { + loadAllData(); + }); + + // Event listener for delete all data button + deleteAllButton.addEventListener("click", function() { + if (confirm("WARNING: This will delete ALL extension data including settings, website styles, and preferences. This action cannot be undone!\n\nAre you sure you want to proceed?")) { + deleteAllData(); + } + }); + + async function deleteAllData() { + try { + // Clear all storage data + await browser.storage.local.clear(); + + // Show confirmation message + alert("All data has been deleted successfully. The page will now reload."); + + // Reload the page to show empty state + window.location.reload(); + } catch (error) { + console.error("Error deleting data:", error); + alert("An error occurred while trying to delete data: " + error.message); + } + } + + async function displayAddonVersion() { + const manifest = browser.runtime.getManifest(); + versionElement.textContent = `Version: ${manifest.version}`; + } + + async function loadAllData() { + try { + // Load all data from storage + const data = await browser.storage.local.get(null); + + // Display global settings + const globalSettings = data[BROWSER_STORAGE_KEY] || {}; + displayGlobalSettings(globalSettings); + + // Display skip/enable list + const skipList = data[SKIP_FORCE_THEMING_KEY] || []; + displaySkipList(skipList, globalSettings.whitelistMode); + + // Display combined websites and settings + displayCombinedWebsiteData(data); + + console.info("Data loaded successfully"); + } catch (error) { + console.error("Error loading data:", error); + } + } + + function displayGlobalSettings(settings) { + globalSettingsElement.innerHTML = ""; + + const table = document.createElement("table"); + table.classList.add("data-table"); + + // Table header + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + headerRow.innerHTML = `<th>Setting</th><th>Value</th>`; + thead.appendChild(headerRow); + table.appendChild(thead); + + // Table body + const tbody = document.createElement("tbody"); + + for (const [key, value] of Object.entries(settings)) { + // Skip lastFetchedTime as it will be formatted differently + if (key === "lastFetchedTime") continue; + + const row = document.createElement("tr"); + row.innerHTML = ` + <td>${formatSettingName(key)}</td> + <td>${formatSettingValue(value)}</td> + `; + tbody.appendChild(row); + } + + // Add last fetched time with formatted date if available + if (settings.lastFetchedTime) { + const row = document.createElement("tr"); + row.innerHTML = ` + <td>${formatSettingName("lastFetchedTime")}</td> + <td>${new Date(settings.lastFetchedTime).toLocaleString()}</td> + `; + tbody.appendChild(row); + } + + table.appendChild(tbody); + globalSettingsElement.appendChild(table); + } + + function displaySkipList(skipList, isWhitelistMode) { + skipListElement.innerHTML = ""; + + const modeType = isWhitelistMode ? "Whitelist" : "Blacklist"; + const actionType = isWhitelistMode ? "Enabled" : "Skipped"; + + const modeInfo = document.createElement("div"); + modeInfo.classList.add("mode-info"); + modeInfo.innerHTML = `<strong>Current Mode:</strong> ${modeType} Mode - ${ + isWhitelistMode + ? "Only apply forced styling to sites in the list" + : "Apply forced styling to all sites except those in the list" + }`; + skipListElement.appendChild(modeInfo); + + // Add Clear List button + if (skipList.length > 0) { + const clearListButton = document.createElement("button"); + clearListButton.classList.add("action-button", "secondary", "clear-list-button"); + clearListButton.innerHTML = '<i class="fas fa-trash"></i> Clear List'; + clearListButton.addEventListener("click", function() { + if (confirm(`Are you sure you want to clear the entire ${modeType} list? This will affect how styling is applied to websites.`)) { + clearSkipList(); + } + }); + skipListElement.appendChild(clearListButton); + } + + if (skipList.length === 0) { + skipListElement.innerHTML += + '<div class="no-data">No websites in the list.</div>'; + return; + } + + const table = document.createElement("table"); + table.classList.add("data-table"); + + // Table header + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + headerRow.innerHTML = `<th>${actionType} Websites</th>`; + thead.appendChild(headerRow); + table.appendChild(thead); + + // Table body + const tbody = document.createElement("tbody"); + + for (const site of skipList) { + const row = document.createElement("tr"); + row.innerHTML = `<td>${site}</td>`; + tbody.appendChild(row); + } + + table.appendChild(tbody); + skipListElement.appendChild(table); + } + + async function clearSkipList() { + try { + await browser.storage.local.remove(SKIP_FORCE_THEMING_KEY); + alert(`${SKIP_FORCE_THEMING_KEY} has been cleared successfully.`); + loadAllData(); // Reload data to reflect changes + } catch (error) { + console.error("Error clearing skip list:", error); + alert("An error occurred while trying to clear the list: " + error.message); + } + } + + function displayCombinedWebsiteData(data) { + combinedWebsitesElement.innerHTML = ""; + + const styles = data.styles || {}; + const websites = styles.website || {}; + const websiteKeys = Object.keys(websites); + + // Find all site-specific settings + const siteSettings = {}; + for (const [key, value] of Object.entries(data)) { + if (key.startsWith(BROWSER_STORAGE_KEY + ".")) { + const siteName = key.substring(BROWSER_STORAGE_KEY.length + 1); + siteSettings[siteName] = value; + } + } + + if (websiteKeys.length === 0) { + combinedWebsitesElement.innerHTML = + '<div class="no-data">No websites found. Try fetching styles first.</div>'; + return; + } + + // Create search filter + const searchContainer = document.createElement("div"); + searchContainer.classList.add("search-container"); + + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.placeholder = "Search websites..."; + searchInput.classList.add("search-input"); + searchInput.addEventListener("input", function () { + filterWebsites(this.value.toLowerCase()); + }); + + const searchIcon = document.createElement("i"); + searchIcon.className = "fas fa-search search-icon"; + + searchContainer.appendChild(searchIcon); + searchContainer.appendChild(searchInput); + + combinedWebsitesElement.appendChild(searchContainer); + + // Create expand all button + const expandAllButton = document.createElement("button"); + expandAllButton.classList.add( + "action-button", + "secondary", + "view-all-button" + ); + expandAllButton.textContent = "Expand All"; + expandAllButton.addEventListener("click", function () { + const expanded = this.textContent === "Collapse All"; + const panels = document.querySelectorAll(".website-panel"); + + panels.forEach((panel) => { + const header = panel.querySelector(".website-header"); + const content = panel.querySelector(".website-content"); + + if (expanded) { + header.classList.remove("active"); + content.style.maxHeight = null; + + // Also collapse all CSS blocks + content.querySelectorAll(".css-block-header").forEach((cssHeader) => { + cssHeader.classList.remove("active"); + const cssContent = cssHeader.nextElementSibling; + if (cssContent) cssContent.style.maxHeight = null; + }); + } else { + header.classList.add("active"); + content.style.maxHeight = content.scrollHeight + "px"; + } + }); + + this.textContent = expanded ? "Expand All" : "Collapse All"; + }); + + combinedWebsitesElement.appendChild(expandAllButton); + + const websitesContainer = document.createElement("div"); + websitesContainer.classList.add("websites-container"); + combinedWebsitesElement.appendChild(websitesContainer); + + // Sort websites alphabetically + websiteKeys.sort(); + + // Create panels for each website + for (const website of websiteKeys) { + const websitePanel = document.createElement("div"); + websitePanel.classList.add("website-panel"); + websitePanel.dataset.website = website.toLowerCase(); + + const header = document.createElement("div"); + header.classList.add("website-header"); + + // Create website name with feature count + const features = websites[website]; + const featureCount = Object.keys(features).length; + + // Get site settings if available + const siteName = website.replace(".css", ""); + const domainName = siteName.startsWith("+") + ? siteName.slice(1) + : siteName; + const settingsData = + siteSettings[domainName] || siteSettings[`www.${domainName}`] || {}; + + header.innerHTML = ` + <div class="website-header-content"> + <span class="website-name">${website}</span> + <span class="feature-count">${featureCount} features</span> + </div> + `; + + header.addEventListener("click", function () { + this.classList.toggle("active"); + const content = this.nextElementSibling; + if (content.style.maxHeight) { + content.style.maxHeight = null; + } else { + content.style.maxHeight = content.scrollHeight + "px"; + } + }); + + const content = document.createElement("div"); + content.classList.add("website-content"); + + // Create CSS blocks for each feature + for (const [feature, css] of Object.entries(features)) { + const cssBlock = document.createElement("div"); + cssBlock.classList.add("css-block"); + + // Get the feature's enabled status from site settings + const isEnabled = settingsData[feature] !== false; // true by default + + // Create the block header with feature name and status + const cssBlockHeader = document.createElement("div"); + cssBlockHeader.classList.add("css-block-header"); + cssBlockHeader.innerHTML = ` + <span class="feature-name">${feature}</span> + <span class="feature-status ${isEnabled ? "enabled" : "disabled"}">${ + isEnabled ? "Enabled" : "Disabled" + }</span> + `; + + // Make the CSS block header toggleable + cssBlockHeader.addEventListener("click", function (e) { + // Don't expand if clicking on status badge + if (e.target.classList.contains("feature-status")) return; + + this.classList.toggle("active"); + const cssContent = this.nextElementSibling; + if (cssContent.style.maxHeight) { + cssContent.style.maxHeight = null; + } else { + cssContent.style.maxHeight = cssContent.scrollHeight + "px"; + } + }); + + // Create the CSS content area + const cssContent = document.createElement("div"); + cssContent.classList.add("css-content"); + + const cssCode = document.createElement("pre"); + cssCode.classList.add("css-code"); + cssCode.textContent = css; + cssContent.appendChild(cssCode); + + cssBlock.appendChild(cssBlockHeader); + cssBlock.appendChild(cssContent); + content.appendChild(cssBlock); + } + + websitePanel.appendChild(header); + websitePanel.appendChild(content); + websitesContainer.appendChild(websitePanel); + } + + // Filter function for search + function filterWebsites(query) { + const panels = websitesContainer.querySelectorAll(".website-panel"); + + panels.forEach((panel) => { + const website = panel.dataset.website; + if (website.includes(query)) { + panel.style.display = ""; + } else { + panel.style.display = "none"; + } + }); + } + } + + // Helper Functions + function formatSettingName(name) { + // Convert camelCase to Title Case with spaces + return name + .replace(/([A-Z])/g, " $1") + .replace(/^./, (str) => str.toUpperCase()); + } + + function formatSettingValue(value) { + if (typeof value === "boolean") { + return value + ? '<span class="badge enabled">Enabled</span>' + : '<span class="badge disabled">Disabled</span>'; + } else if (value === null) { + return '<span class="null-value">null</span>'; + } else if (typeof value === "object") { + return '<span class="object-value">{Object}</span>'; + } else { + return value; + } + } +}); diff --git a/manifest.json b/manifest.json index 356ff24..92a8578 100644 --- a/manifest.json +++ b/manifest.json @@ -33,5 +33,10 @@ "js": ["inject-css.js"], "run_at": "document_start" } + ], + "web_accessible_resources": [ + "data-viewer/data-viewer.html", + "data-viewer/data-viewer.js", + "data-viewer/data-viewer.css" ] } diff --git a/popup/popup.html b/popup/popup.html index 8ffa03d..1c5c84c 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -87,6 +87,12 @@ </div> </div> + <div class="actions"> + <button id="view-data" class="action-button secondary"> + <i class="fas fa-database"></i> View All Stored Data + </button> + </div> + </main> diff --git a/popup/popup.js b/popup/popup.js index d81ed7d..82fc942 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -54,6 +54,13 @@ new (class ExtensionPopup { this.handleWhitelistModeChange.bind(this) ); + // Add event listener for the data viewer button + document.getElementById("view-data")?.addEventListener("click", () => { + browser.tabs.create({ + url: browser.runtime.getURL("data-viewer/data-viewer.html"), + }); + }); + // Setup auto-update and display last fetched time this.setupAutoUpdate(); this.displayLastFetchedTime(); |