Add files via upload
401
src/js/background.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
// URLs and Constants
|
||||
const filterListURLUnsafe =
|
||||
"https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist.txt";
|
||||
const filterListURLPotentiallyUnsafe =
|
||||
"https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist-plus.txt";
|
||||
const safeListURL = "https://api.fmhy.net/single-page";
|
||||
const starredListURL =
|
||||
"https://raw.githubusercontent.com/fmhy/bookmarks/refs/heads/main/fmhy_in_bookmarks_starred_only.html";
|
||||
const fmhyFilterListURL =
|
||||
"https://raw.githubusercontent.com/fmhy/FMHY-SafeGuard/refs/heads/main/fmhy-filterlist.txt";
|
||||
const DEFAULT_CHECK_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
|
||||
|
||||
// State Variables
|
||||
let unsafeSitesRegex = null;
|
||||
let potentiallyUnsafeSitesRegex = null;
|
||||
let fmhySitesRegex = null;
|
||||
let safeSites = [];
|
||||
let starredSites = [];
|
||||
const approvedUrls = new Map(); // Map to store approved URLs per tab
|
||||
|
||||
// Helper Functions
|
||||
function extractUrlsFromMarkdown(markdown) {
|
||||
const urlRegex = /https?:\/\/[^\s)]+/g;
|
||||
return markdown.match(urlRegex) || [];
|
||||
}
|
||||
|
||||
function extractUrlsFromBookmarks(html) {
|
||||
const urlRegex = /<A HREF="(https?:\/\/[^\s"]+)"/g;
|
||||
let matches;
|
||||
const urls = [];
|
||||
while ((matches = urlRegex.exec(html)) !== null) {
|
||||
urls.push(matches[1]);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
function normalizeUrl(url) {
|
||||
if (!url) {
|
||||
console.warn("Received null or undefined URL.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = `https://${url}`;
|
||||
}
|
||||
const urlObj = new URL(url);
|
||||
urlObj.search = "";
|
||||
urlObj.hash = "";
|
||||
return urlObj.href.replace(/\/+$/, "");
|
||||
} catch (error) {
|
||||
console.warn(`Invalid URL skipped: ${url}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function extractRootUrl(url) {
|
||||
if (!url) {
|
||||
console.warn("Received null or undefined URL for root extraction.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return `${urlObj.protocol}//${urlObj.hostname}`;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to extract root URL from: ${url}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function generateRegexFromList(list) {
|
||||
const escapedList = list.map((domain) =>
|
||||
domain.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
);
|
||||
return new RegExp(`(${escapedList.join("|")})`, "i");
|
||||
}
|
||||
|
||||
function extractUrlsFromFilterList(text) {
|
||||
return text
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("!"))
|
||||
.map((line) => normalizeUrl(line))
|
||||
.filter((url) => url !== null);
|
||||
}
|
||||
|
||||
// Fetch and Update Functions
|
||||
async function fetchFilterLists() {
|
||||
console.log("Fetching filter lists...");
|
||||
try {
|
||||
const [unsafeResponse, potentiallyUnsafeResponse, fmhyResponse] =
|
||||
await Promise.all([
|
||||
fetch(filterListURLUnsafe),
|
||||
fetch(filterListURLPotentiallyUnsafe),
|
||||
fetch(fmhyFilterListURL),
|
||||
]);
|
||||
|
||||
let unsafeSites = [];
|
||||
let potentiallyUnsafeSites = [];
|
||||
let fmhySites = [];
|
||||
|
||||
if (unsafeResponse.ok) {
|
||||
const unsafeText = await unsafeResponse.text();
|
||||
unsafeSites = extractUrlsFromFilterList(unsafeText);
|
||||
unsafeSitesRegex = generateRegexFromList(unsafeSites);
|
||||
}
|
||||
|
||||
if (potentiallyUnsafeResponse.ok) {
|
||||
const potentiallyUnsafeText = await potentiallyUnsafeResponse.text();
|
||||
potentiallyUnsafeSites = extractUrlsFromFilterList(potentiallyUnsafeText);
|
||||
potentiallyUnsafeSitesRegex = generateRegexFromList(
|
||||
potentiallyUnsafeSites
|
||||
);
|
||||
}
|
||||
|
||||
if (fmhyResponse.ok) {
|
||||
const fmhyText = await fmhyResponse.text();
|
||||
fmhySites = extractUrlsFromFilterList(fmhyText);
|
||||
fmhySitesRegex = generateRegexFromList(fmhySites);
|
||||
}
|
||||
|
||||
await browserAPI.storage.local.set({
|
||||
unsafeFilterCount: unsafeSites.length,
|
||||
potentiallyUnsafeFilterCount: potentiallyUnsafeSites.length,
|
||||
fmhyFilterCount: fmhySites.length,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
});
|
||||
|
||||
console.log("Stored FMHY filter count:", fmhySites.length);
|
||||
|
||||
notifySettingsPage();
|
||||
} catch (error) {
|
||||
console.error("Error fetching filter lists:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSafeSites() {
|
||||
console.log("Fetching safe sites...");
|
||||
try {
|
||||
const response = await fetch(safeListURL);
|
||||
if (response.ok) {
|
||||
const markdown = await response.text();
|
||||
const urls = extractUrlsFromMarkdown(markdown);
|
||||
safeSites = [...new Set(urls.map((url) => normalizeUrl(url.trim())))];
|
||||
|
||||
await browserAPI.storage.local.set({
|
||||
safeSiteCount: safeSites.length,
|
||||
});
|
||||
|
||||
console.log("Stored safe site count:", safeSites.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching safe sites:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchStarredSites() {
|
||||
console.log("Fetching starred sites...");
|
||||
try {
|
||||
const response = await fetch(starredListURL);
|
||||
if (response.ok) {
|
||||
const html = await response.text();
|
||||
const urls = extractUrlsFromBookmarks(html);
|
||||
starredSites = [...new Set([...urls.map(normalizeUrl), ...starredSites])];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching starred sites:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// UI Update Functions
|
||||
function updatePageAction(status, tabId) {
|
||||
const icons = {
|
||||
safe: {
|
||||
19: "../res/icons/safe_19.png",
|
||||
38: "../res/icons/safe_38.png",
|
||||
},
|
||||
unsafe: {
|
||||
19: "../res/icons/unsafe_19.png",
|
||||
38: "../res/icons/unsafe_38.png",
|
||||
},
|
||||
potentially_unsafe: {
|
||||
19: "../res/icons/potentially_unsafe_19.png",
|
||||
38: "../res/icons/potentially_unsafe_38.png",
|
||||
},
|
||||
starred: {
|
||||
19: "../res/icons/starred_19.png",
|
||||
38: "../res/icons/starred_38.png",
|
||||
},
|
||||
fmhy: {
|
||||
19: "../res/icons/fmhy_19.png",
|
||||
38: "../res/icons/fmhy_38.png",
|
||||
},
|
||||
default: {
|
||||
19: "../res/icons/default_19.png",
|
||||
38: "../res/icons/default_38.png",
|
||||
},
|
||||
};
|
||||
|
||||
const icon = icons[status] || icons["default"];
|
||||
|
||||
browserAPI.action.setIcon({
|
||||
tabId: tabId,
|
||||
path: icon,
|
||||
});
|
||||
}
|
||||
|
||||
async function notifySettingsPage() {
|
||||
const tabs = await browserAPI.tabs.query({});
|
||||
for (const tab of tabs) {
|
||||
try {
|
||||
await browserAPI.tabs.sendMessage(tab.id, { type: "filterlistUpdated" });
|
||||
} catch (e) {
|
||||
// Ignore errors for tabs that can't receive messages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Site Status Checking
|
||||
function checkSiteAndUpdatePageAction(tabId, url) {
|
||||
if (!url) {
|
||||
updatePageAction("default", tabId);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedUrl = normalizeUrl(url.trim());
|
||||
const rootUrl = extractRootUrl(normalizedUrl);
|
||||
|
||||
const isUnsafe =
|
||||
unsafeSitesRegex?.test(rootUrl) || unsafeSitesRegex?.test(normalizedUrl);
|
||||
const isPotentiallyUnsafe =
|
||||
potentiallyUnsafeSitesRegex?.test(rootUrl) ||
|
||||
potentiallyUnsafeSitesRegex?.test(normalizedUrl);
|
||||
const isFMHY =
|
||||
fmhySitesRegex?.test(rootUrl) || fmhySitesRegex?.test(normalizedUrl);
|
||||
const isStarred =
|
||||
starredSites.includes(rootUrl) || starredSites.includes(normalizedUrl);
|
||||
const isSafe =
|
||||
safeSites.includes(rootUrl) || safeSites.includes(normalizedUrl);
|
||||
|
||||
const tabApprovedUrls = approvedUrls.get(tabId) || [];
|
||||
const isApproved = tabApprovedUrls.includes(normalizedUrl);
|
||||
|
||||
if (isUnsafe && !isApproved) {
|
||||
updatePageAction("unsafe", tabId);
|
||||
openWarningPage(tabId, url);
|
||||
} else if (isPotentiallyUnsafe) {
|
||||
updatePageAction("potentially_unsafe", tabId);
|
||||
} else if (isFMHY) {
|
||||
updatePageAction("fmhy", tabId);
|
||||
} else if (isStarred) {
|
||||
updatePageAction("starred", tabId);
|
||||
} else if (isSafe) {
|
||||
updatePageAction("safe", tabId);
|
||||
} else {
|
||||
updatePageAction("default", tabId);
|
||||
}
|
||||
}
|
||||
|
||||
// Update Schedule Management
|
||||
async function shouldUpdate() {
|
||||
try {
|
||||
const { lastUpdated } = await browserAPI.storage.local.get("lastUpdated");
|
||||
const { updateFrequency } = await browserAPI.storage.sync.get({
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
if (!lastUpdated) return true;
|
||||
|
||||
const lastUpdate = new Date(lastUpdated);
|
||||
const now = new Date();
|
||||
const diffHours = (now - lastUpdate) / (1000 * 60 * 60);
|
||||
|
||||
switch (updateFrequency) {
|
||||
case "daily":
|
||||
return diffHours >= 24;
|
||||
case "weekly":
|
||||
return diffHours >= 168;
|
||||
case "monthly":
|
||||
return diffHours >= 720;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking update schedule:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function setupUpdateSchedule() {
|
||||
await browserAPI.alarms.clearAll();
|
||||
browserAPI.alarms.create("checkUpdate", {
|
||||
periodInMinutes: 60,
|
||||
});
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.action === "checkSiteStatus") {
|
||||
const normalizedUrl = normalizeUrl(message.url.trim());
|
||||
const rootUrl = extractRootUrl(normalizedUrl);
|
||||
|
||||
let isUnsafe =
|
||||
unsafeSitesRegex?.test(rootUrl) || unsafeSitesRegex?.test(normalizedUrl);
|
||||
let isPotentiallyUnsafe =
|
||||
potentiallyUnsafeSitesRegex?.test(rootUrl) ||
|
||||
potentiallyUnsafeSitesRegex?.test(normalizedUrl);
|
||||
let isFMHY =
|
||||
fmhySitesRegex?.test(rootUrl) || fmhySitesRegex?.test(normalizedUrl);
|
||||
let isStarred =
|
||||
starredSites.includes(rootUrl) || starredSites.includes(normalizedUrl);
|
||||
let isSafe =
|
||||
safeSites.includes(rootUrl) || safeSites.includes(normalizedUrl);
|
||||
|
||||
let status = "no_data";
|
||||
if (isFMHY) {
|
||||
status = "fmhy";
|
||||
} else if (isStarred) {
|
||||
status = "starred";
|
||||
} else if (isUnsafe) {
|
||||
status = "unsafe";
|
||||
} else if (isPotentiallyUnsafe) {
|
||||
status = "potentially_unsafe";
|
||||
} else if (isSafe) {
|
||||
status = "safe";
|
||||
}
|
||||
|
||||
sendResponse({ status: status });
|
||||
return true; // Indicates asynchronous response handling
|
||||
}
|
||||
});
|
||||
|
||||
async function openWarningPage(tabId, unsafeUrl) {
|
||||
const tabApprovedUrls = approvedUrls.get(tabId) || [];
|
||||
if (tabApprovedUrls.includes(normalizeUrl(unsafeUrl))) {
|
||||
console.log(`URL ${unsafeUrl} was already approved for tab ${tabId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { warningPage } = await browserAPI.storage.sync.get({
|
||||
warningPage: true,
|
||||
});
|
||||
|
||||
if (!warningPage) {
|
||||
console.log("Warning page is disabled by the user settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
const warningPageUrl = browserAPI.runtime.getURL(
|
||||
`../pub/warning-page.html?url=${encodeURIComponent(unsafeUrl)}`
|
||||
);
|
||||
browserAPI.tabs.update(tabId, { url: warningPageUrl });
|
||||
}
|
||||
|
||||
browserAPI.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === "complete" && tab.url) {
|
||||
checkSiteAndUpdatePageAction(tabId, tab.url);
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.tabs.onActivated.addListener(async (activeInfo) => {
|
||||
const tab = await browserAPI.tabs.get(activeInfo.tabId);
|
||||
if (tab.url) {
|
||||
checkSiteAndUpdatePageAction(tab.id, tab.url);
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.alarms.onAlarm.addListener(async (alarm) => {
|
||||
if (alarm.name === "checkUpdate") {
|
||||
const needsUpdate = await shouldUpdate();
|
||||
if (needsUpdate) {
|
||||
await fetchFilterLists();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.tabs.onRemoved.addListener((tabId) => {
|
||||
approvedUrls.delete(tabId);
|
||||
browserAPI.storage.local.remove(`proceedTab_${tabId}`);
|
||||
});
|
||||
|
||||
// Initialize extension
|
||||
async function initializeExtension() {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchFilterLists(),
|
||||
fetchSafeSites(),
|
||||
fetchStarredSites(),
|
||||
setupUpdateSchedule(),
|
||||
]);
|
||||
console.log("Extension initialized successfully.");
|
||||
} catch (error) {
|
||||
console.error("Error during extension initialization:", error);
|
||||
}
|
||||
}
|
||||
|
||||
initializeExtension();
|
||||
214
src/pub/index.html
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FMHY SafeGuard</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../res/fonts/inter.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--background-color: rgb(26, 26, 26);
|
||||
--text-color: #848a94;
|
||||
--text-color-light: #e8e8e8;
|
||||
--accent-color: #c4b5fd;
|
||||
--link-color: #78b3e2;
|
||||
--hover-bg: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--background-color: #f5f5f5;
|
||||
--text-color: #4b5563;
|
||||
--text-color-light: #1a1a1a;
|
||||
--accent-color: #6c63ff;
|
||||
--link-color: #0366d6;
|
||||
--hover-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
background-color: var(--background-color);
|
||||
font-family: "Inter", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
min-width: 300px;
|
||||
max-width: 320px; /* Limit the width to make it more compact */
|
||||
position: relative; /* For absolute positioning of settings button */
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 24px;
|
||||
background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
var(--accent-color) 30%,
|
||||
var(--link-color)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Settings Button */
|
||||
.settings-button {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.settings-button:hover {
|
||||
background: var(--hover-bg);
|
||||
}
|
||||
|
||||
.settings-button svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.settings-button:hover svg {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.settings-button path {
|
||||
fill: var(--text-color);
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
|
||||
.settings-button:hover path {
|
||||
fill: var(--accent-color);
|
||||
}
|
||||
|
||||
#status-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status-image {
|
||||
width: 60px; /* Reduced icon size */
|
||||
height: 60px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#status-message {
|
||||
font-size: 14px; /* Smaller text for compact design */
|
||||
margin-top: 10px;
|
||||
color: var(--text-color-light);
|
||||
line-height: 1.3;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#status-message strong {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
#error-message {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
font-size: 11px; /* Smaller footer text */
|
||||
margin-top: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#feedback {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
#feedback a {
|
||||
color: var(--link-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#feedback a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Animation for status change */
|
||||
.status-image.active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<h1>FMHY SafeGuard</h1>
|
||||
</div>
|
||||
<button class="settings-button" id="settingsButton" title="Open Settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.65.07-.97 0-.32-.03-.65-.07-.97l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65c-.04-.24-.25-.42-.5-.42h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 12c-.04.32-.07.65-.07.97 0 .32.03.65.07.97l-2.11 1.63c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.31.61.22l2.49-1c.52.39 1.06.73 1.69.98l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.25 1.17-.59 1.69-.98l2.49 1c.22-.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.63Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="status-container">
|
||||
<img
|
||||
id="status-icon"
|
||||
class="status-image"
|
||||
src="safe.png"
|
||||
alt="Status Icon"
|
||||
/>
|
||||
<p id="status-message">Checking site status...</p>
|
||||
</div>
|
||||
<p id="error-message"></p>
|
||||
<div id="feedback">
|
||||
<p>
|
||||
Think this is a mistake?
|
||||
<a href="https://github.com/fmhy/FMHY-SafeGuard/issues" target="_blank"
|
||||
>Let us know</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p>
|
||||
Powered by
|
||||
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
|
||||
>FMHY Filterlist</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
182
src/pub/index.js
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
console.log("Popup loaded, preparing to check site status...");
|
||||
|
||||
const statusIcon = document.getElementById("status-icon");
|
||||
const statusMessage = document.getElementById("status-message");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
// Helper function to normalize URLs (removes trailing slashes, query parameters, and fragments)
|
||||
const normalizeUrl = (url) => {
|
||||
const urlObj = new URL(url);
|
||||
urlObj.search = ""; // Remove query parameters
|
||||
urlObj.hash = ""; // Remove fragments
|
||||
return urlObj.href.replace(/\/+$/, ""); // Remove trailing slash only
|
||||
};
|
||||
|
||||
// Helper function to extract the root domain from a URL
|
||||
function extractRootDomain(url) {
|
||||
let urlObj = new URL(url);
|
||||
return `${urlObj.protocol}//${urlObj.hostname}`;
|
||||
}
|
||||
|
||||
// Function to apply theme based on settings
|
||||
async function applyTheme() {
|
||||
try {
|
||||
const { theme } = await browserAPI.storage.sync.get("theme");
|
||||
|
||||
if (theme === "dark") {
|
||||
document.body.setAttribute("data-theme", "dark");
|
||||
} else if (theme === "light") {
|
||||
document.body.setAttribute("data-theme", "light");
|
||||
} else {
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute(
|
||||
"data-theme",
|
||||
prefersDark ? "dark" : "light"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error applying theme:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply theme on load
|
||||
await applyTheme();
|
||||
|
||||
try {
|
||||
// Get the active tab's URL
|
||||
const [activeTab] = await browserAPI.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
if (!activeTab || !activeTab.url) {
|
||||
throw new Error("No active tab found or URL is unavailable.");
|
||||
}
|
||||
|
||||
const currentUrl = normalizeUrl(activeTab.url);
|
||||
const rootDomain = extractRootDomain(currentUrl);
|
||||
console.log(`Active tab URL: ${currentUrl}, Root domain: ${rootDomain}`);
|
||||
|
||||
// Send a message to the background script to check the site's status
|
||||
const response = await browserAPI.runtime.sendMessage({
|
||||
action: "checkSiteStatus",
|
||||
url: currentUrl,
|
||||
});
|
||||
|
||||
if (!response || !response.status) {
|
||||
throw new Error(
|
||||
"Failed to retrieve site status from the background script."
|
||||
);
|
||||
}
|
||||
|
||||
// Determine if the root domain or the full URL is marked as safe/starred
|
||||
const isRootDomainMarked = await browserAPI.runtime.sendMessage({
|
||||
action: "checkSiteStatus",
|
||||
url: rootDomain,
|
||||
});
|
||||
|
||||
// Handle different site statuses and update the UI accordingly
|
||||
if (
|
||||
isRootDomainMarked.status === response.status &&
|
||||
response.status !== "no_data"
|
||||
) {
|
||||
// If both the root domain and the current URL share the same status, show the root domain in the message
|
||||
handleStatusUpdate(response.status, rootDomain);
|
||||
} else {
|
||||
// Otherwise, show the current URL in the message
|
||||
handleStatusUpdate(response.status, currentUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error while checking site status:", error);
|
||||
errorMessage.textContent = `Error: ${error.message}`;
|
||||
updateUI("error", "An error occurred while retrieving the site status.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI based on the site status
|
||||
* @param {string} status - The status of the site (e.g., "safe", "unsafe")
|
||||
* @param {string} displayUrl - The URL or root domain to display in the message
|
||||
*/
|
||||
function handleStatusUpdate(status, displayUrl) {
|
||||
switch (status) {
|
||||
case "unsafe":
|
||||
updateUI(
|
||||
"unsafe",
|
||||
`${displayUrl} is flagged as <strong>unsafe</strong>. Be cautious when interacting with this site.`
|
||||
);
|
||||
break;
|
||||
case "potentially_unsafe":
|
||||
updateUI(
|
||||
"potentially_unsafe",
|
||||
`${displayUrl} is <strong>potentially unsafe</strong>. Proceed with caution.`
|
||||
);
|
||||
break;
|
||||
case "fmhy":
|
||||
updateUI(
|
||||
"fmhy",
|
||||
`${displayUrl} is an <strong>FMHY</strong> related site. Proceed confidently.`
|
||||
);
|
||||
break;
|
||||
case "safe":
|
||||
updateUI("safe", `${displayUrl} is <strong>safe</strong> to browse.`);
|
||||
break;
|
||||
case "starred":
|
||||
updateUI(
|
||||
"starred",
|
||||
`${displayUrl} is a <strong>starred</strong> site.`
|
||||
);
|
||||
break;
|
||||
case "no_data":
|
||||
updateUI(
|
||||
"no_data",
|
||||
`No data available for <strong>${displayUrl}</strong>.`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
updateUI(
|
||||
"unknown",
|
||||
`An unknown status was received for <strong>${displayUrl}</strong>.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI with the appropriate icon, message, and effects.
|
||||
* @param {string} status - The status of the site (e.g., "safe", "unsafe").
|
||||
* @param {string} message - The message to display to the user.
|
||||
*/
|
||||
function updateUI(status, message) {
|
||||
const icons = {
|
||||
unsafe: "../res/icons/unsafe.png",
|
||||
potentially_unsafe: "../res/icons/potentially_unsafe.png",
|
||||
fmhy: "../res/icons/fmhy.png",
|
||||
safe: "../res/icons/safe.png",
|
||||
starred: "../res/icons/starred.png",
|
||||
no_data: "../res/ext_icon_144.png",
|
||||
error: "../res/icons/error.png",
|
||||
unknown: "../res/ext_icon_144.png",
|
||||
};
|
||||
|
||||
// Update the icon and message
|
||||
statusIcon.src = icons[status] || icons["unknown"];
|
||||
statusMessage.innerHTML = message || "An unknown error occurred.";
|
||||
|
||||
// Add a small animation when the status changes
|
||||
statusIcon.classList.add("active");
|
||||
setTimeout(() => statusIcon.classList.remove("active"), 300);
|
||||
|
||||
console.log(`UI updated: ${message}`);
|
||||
}
|
||||
|
||||
// Add settings button functionality
|
||||
document.getElementById("settingsButton").addEventListener("click", () => {
|
||||
// Open the settings page in a new tab
|
||||
browserAPI.runtime.openOptionsPage();
|
||||
});
|
||||
});
|
||||
605
src/pub/settings-page.html
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FMHY SafeGuard - Settings</title>
|
||||
<link rel="icon" type="image/x-icon" href="../res/ext_icon_144.png" />
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../res/fonts/inter.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: rgb(26, 26, 26);
|
||||
--card-bg: rgba(255, 255, 255, 0.05);
|
||||
--text-primary: #e8e8e8;
|
||||
--text-secondary: #848a94;
|
||||
--accent-purple: #c4b5fd;
|
||||
--accent-blue: #7bc5e4;
|
||||
--toggle-bg: #374151;
|
||||
--section-border: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--background: #f5f5f5;
|
||||
--card-bg: rgba(255, 255, 255, 0.9);
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #4b5563;
|
||||
--toggle-bg: #d1d5db;
|
||||
--section-border: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
font-family: "Inter", sans-serif;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.5;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1.5rem;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple) 30%,
|
||||
var(--accent-blue)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--section-border);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid var(--section-border);
|
||||
}
|
||||
|
||||
.settings-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.last-updated span {
|
||||
color: var(--accent-blue);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.section-description {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease, background-color 0.3s ease;
|
||||
border: 1px solid var(--section-border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.stat-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.update-status {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
text-align: right;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.update-icon {
|
||||
animation: spin 2s linear infinite;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark theme specific adjustments */
|
||||
[data-theme="dark"] .stat-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .stat-card:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Light theme specific adjustments */
|
||||
[data-theme="light"] .stat-card {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
[data-theme="light"] .stat-card:hover {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.stats-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--toggle-bg);
|
||||
transition: 0.4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(22px);
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 2rem 0.5rem 1rem;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Dark theme specific select styling */
|
||||
[data-theme="dark"] select {
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] select option {
|
||||
background-color: rgb(30, 30, 30);
|
||||
color: var(--text-primary);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] select option:checked,
|
||||
[data-theme="dark"] select option:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Selected option styling */
|
||||
[data-theme="dark"] select option:checked {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Light theme specific select styling */
|
||||
[data-theme="light"] select {
|
||||
background: #ffffff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="light"] select option {
|
||||
background-color: #ffffff;
|
||||
color: var(--text-primary);
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* Hover and focus states */
|
||||
select:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.select-wrapper::after {
|
||||
content: "▼";
|
||||
font-size: 0.8rem;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-weight: 600; /* Made slightly bolder */
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
color: white;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); /* Subtle text shadow for better readability */
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--accent-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 1rem 1.5rem;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
transform: translateY(150%);
|
||||
transition: transform 0.3s ease-out;
|
||||
z-index: 1000;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.notification.error {
|
||||
background: linear-gradient(120deg, #ff6b6b, #ff8787);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">FMHY SafeGuard Settings</h1>
|
||||
|
||||
<div class="settings-card">
|
||||
<!-- Filterlist Stats -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<div class="section-title">Filterlist Statistics</div>
|
||||
<div class="last-updated">
|
||||
Last Updated: <span id="lastUpdated">Today at 2:56:48 PM</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Unsafe Sites</div>
|
||||
<div class="stat-value" id="unsafeFilterCount">266</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Potentially Unsafe Sites</div>
|
||||
<div class="stat-value" id="potentiallyUnsafeFilterCount">46</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Safe Sites</div>
|
||||
<div class="stat-value" id="safeSiteCount">21901</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-status" id="updateStatus">
|
||||
Next update scheduled for: Checking...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appearance -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-grid">
|
||||
<div>
|
||||
<h2 class="section-title">Theme</h2>
|
||||
<p class="section-description">
|
||||
Choose your preferred appearance mode
|
||||
</p>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select id="themeSelect">
|
||||
<option value="system">System Default</option>
|
||||
<option value="dark">Dark Mode</option>
|
||||
<option value="light">Light Mode</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Settings -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-grid">
|
||||
<div>
|
||||
<h2 class="section-title">Warning Page</h2>
|
||||
<p class="section-description">
|
||||
Show warning page for unsafe sites
|
||||
</p>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="warningToggle" checked />
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Update Settings -->
|
||||
<div class="settings-section">
|
||||
<div class="settings-grid">
|
||||
<div>
|
||||
<h2 class="section-title">Auto-Update Filterlist</h2>
|
||||
<p class="section-description">
|
||||
Automatically update security filters
|
||||
</p>
|
||||
</div>
|
||||
<div class="select-wrapper">
|
||||
<select id="updateFrequency">
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div style="text-align: center">
|
||||
<button class="btn" id="saveSettings">Save Settings</button>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>FMHY SafeGuard <span id="versionNumber"></p>
|
||||
<p>
|
||||
Powered by
|
||||
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
|
||||
>FMHY Filterlist</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notification Toast -->
|
||||
<div class="notification" id="notification">
|
||||
Settings saved successfully!
|
||||
</div>
|
||||
|
||||
<script src="settings-page.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
261
src/pub/settings-page.js
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
// Load and display the extension version from manifest.json
|
||||
const manifest = browserAPI.runtime.getManifest();
|
||||
document.getElementById("versionNumber").textContent = manifest.version;
|
||||
|
||||
// Get all DOM elements
|
||||
const themeSelect = document.getElementById("themeSelect");
|
||||
const warningToggle = document.getElementById("warningToggle");
|
||||
const updateFrequency = document.getElementById("updateFrequency");
|
||||
const saveButton = document.getElementById("saveSettings");
|
||||
const notification = document.getElementById("notification");
|
||||
const lastUpdated = document.getElementById("lastUpdated");
|
||||
const updateStatus = document.getElementById("updateStatus");
|
||||
|
||||
// Theme application function
|
||||
function applyTheme(theme) {
|
||||
if (theme === "system") {
|
||||
const prefersDark = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
).matches;
|
||||
document.body.setAttribute("data-theme", prefersDark ? "dark" : "light");
|
||||
} else {
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
}
|
||||
}
|
||||
|
||||
// Format date function
|
||||
function formatDate(date) {
|
||||
if (!date) return "Never";
|
||||
const d = new Date(date);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - d);
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) {
|
||||
return (
|
||||
"Today at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else if (diffDays === 1) {
|
||||
return (
|
||||
"Yesterday at " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
d.toLocaleDateString() +
|
||||
" " +
|
||||
d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate next update time
|
||||
function calculateNextUpdate(lastUpdate, frequency) {
|
||||
if (!lastUpdate) return "Not scheduled";
|
||||
const lastUpdateDate = new Date(lastUpdate);
|
||||
let nextUpdate = new Date(lastUpdateDate);
|
||||
|
||||
switch (frequency) {
|
||||
case "daily":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "weekly":
|
||||
nextUpdate.setDate(nextUpdate.getDate() + 7);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
case "monthly":
|
||||
nextUpdate.setMonth(nextUpdate.getMonth() + 1);
|
||||
nextUpdate.setHours(0, 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return "Not scheduled";
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (nextUpdate < now) {
|
||||
return "Update pending...";
|
||||
}
|
||||
|
||||
const timeUntil = nextUpdate - now;
|
||||
const hoursUntil = Math.floor(timeUntil / (1000 * 60 * 60));
|
||||
const minutesUntil = Math.floor(
|
||||
(timeUntil % (1000 * 60 * 60)) / (1000 * 60)
|
||||
);
|
||||
|
||||
if (hoursUntil < 24) {
|
||||
if (hoursUntil === 0) {
|
||||
return `in ${minutesUntil} minutes`;
|
||||
} else {
|
||||
return `in ${hoursUntil}h ${minutesUntil}m`;
|
||||
}
|
||||
} else {
|
||||
const days = Math.floor(hoursUntil / 24);
|
||||
if (days === 1) {
|
||||
return "tomorrow";
|
||||
} else {
|
||||
return `in ${days} days`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the UI with next update time
|
||||
async function updateNextUpdateStatus() {
|
||||
try {
|
||||
const stats = await browserAPI.storage.local.get({
|
||||
lastUpdated: null,
|
||||
});
|
||||
const settings = await browserAPI.storage.sync.get({
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
const nextUpdateText = calculateNextUpdate(
|
||||
stats.lastUpdated,
|
||||
settings.updateFrequency
|
||||
);
|
||||
|
||||
if (updateStatus) {
|
||||
updateStatus.innerHTML = `
|
||||
<svg class="update-icon" viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M12 4V2C6.477 2 2 6.477 2 12C2 17.523 6.477 22 12 22C17.523 22 22 17.523 22 12H20C20 16.418 16.418 20 12 20C7.582 20 4 16.418 4 12C4 7.582 7.582 4 12 4Z"/>
|
||||
</svg>
|
||||
Next update ${nextUpdateText}
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating next update status:", error);
|
||||
if (updateStatus) {
|
||||
updateStatus.textContent = "Unable to check next update time";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load filterlist stats
|
||||
async function loadFilterlistStats() {
|
||||
try {
|
||||
const stats = await browserAPI.storage.local.get({
|
||||
unsafeFilterCount: 0,
|
||||
potentiallyUnsafeFilterCount: 0,
|
||||
safeSiteCount: 0,
|
||||
lastUpdated: null,
|
||||
});
|
||||
|
||||
console.log("Fetched stats:", stats);
|
||||
|
||||
document.getElementById("unsafeFilterCount").textContent =
|
||||
stats.unsafeFilterCount;
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
stats.potentiallyUnsafeFilterCount;
|
||||
document.getElementById("safeSiteCount").textContent =
|
||||
stats.safeSiteCount;
|
||||
document.getElementById("lastUpdated").textContent = formatDate(
|
||||
stats.lastUpdated
|
||||
);
|
||||
|
||||
await updateNextUpdateStatus();
|
||||
} catch (error) {
|
||||
console.error("Error loading filterlist stats:", error);
|
||||
document.getElementById("unsafeFilterCount").textContent = "Error";
|
||||
document.getElementById("potentiallyUnsafeFilterCount").textContent =
|
||||
"Error";
|
||||
document.getElementById("safeSiteCount").textContent = "Error";
|
||||
document.getElementById("lastUpdated").textContent = "Error";
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings function
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const savedSettings = await browserAPI.storage.sync.get({
|
||||
theme: "system",
|
||||
warningPage: true,
|
||||
updateFrequency: "daily",
|
||||
});
|
||||
|
||||
if (themeSelect) themeSelect.value = savedSettings.theme;
|
||||
if (warningToggle) warningToggle.checked = savedSettings.warningPage;
|
||||
if (updateFrequency)
|
||||
updateFrequency.value = savedSettings.updateFrequency;
|
||||
|
||||
applyTheme(savedSettings.theme);
|
||||
await loadFilterlistStats();
|
||||
} catch (error) {
|
||||
console.error("Error loading settings:", error);
|
||||
showNotification("Error loading settings", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Show notification function
|
||||
function showNotification(message, isError = false) {
|
||||
if (notification) {
|
||||
notification.textContent = message;
|
||||
if (isError) {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, #ff6b6b, #ff8787)";
|
||||
} else {
|
||||
notification.style.background =
|
||||
"linear-gradient(120deg, var(--accent-purple), var(--accent-blue))";
|
||||
}
|
||||
notification.classList.add("show");
|
||||
setTimeout(() => {
|
||||
notification.classList.remove("show");
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings function
|
||||
async function saveSettings() {
|
||||
try {
|
||||
const settings = {
|
||||
theme: themeSelect.value,
|
||||
warningPage: warningToggle.checked,
|
||||
updateFrequency: updateFrequency.value,
|
||||
};
|
||||
|
||||
await browserAPI.storage.sync.set(settings);
|
||||
showNotification("Settings saved successfully!");
|
||||
applyTheme(settings.theme);
|
||||
await updateNextUpdateStatus();
|
||||
|
||||
await browserAPI.runtime.sendMessage({
|
||||
type: "settingsUpdated",
|
||||
settings: settings,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error saving settings:", error);
|
||||
showNotification("Error saving settings", true);
|
||||
}
|
||||
}
|
||||
|
||||
// Event Listeners
|
||||
if (saveButton) {
|
||||
saveButton.addEventListener("click", saveSettings);
|
||||
}
|
||||
|
||||
if (themeSelect) {
|
||||
themeSelect.addEventListener("change", (e) => {
|
||||
applyTheme(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", (e) => {
|
||||
if (themeSelect && themeSelect.value === "system") {
|
||||
applyTheme("system");
|
||||
}
|
||||
});
|
||||
|
||||
browserAPI.runtime.onMessage.addListener((message) => {
|
||||
if (message.type === "filterlistUpdated") {
|
||||
loadFilterlistStats();
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(updateNextUpdateStatus, 60000);
|
||||
loadSettings();
|
||||
});
|
||||
212
src/pub/warning-page.html
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FMHY SafeGuard - Warning</title>
|
||||
<link rel="icon" type="image/x-icon" href="../res/ext_icon_144.png" />
|
||||
<script src="warning-page.js"></script>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../res/fonts/inter.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: rgb(26, 26, 26);
|
||||
--text-primary: #e8e8e8;
|
||||
--text-secondary: #848a94;
|
||||
--accent-purple: #c4b5fd;
|
||||
--accent-blue: #7bc5e4;
|
||||
--danger: #ff4444;
|
||||
--card-bg: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
font-family: "Inter", sans-serif;
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin: 2rem 0;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple) 30%,
|
||||
var(--accent-blue)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 1.5rem 0;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.url {
|
||||
color: var(--danger);
|
||||
font-weight: 500;
|
||||
margin-bottom: 1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
border: 1px solid var(--text-secondary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 2rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--accent-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="title">FMHY SafeGuard</h1>
|
||||
<div class="warning-card">
|
||||
<img
|
||||
src="../res/icons/unsafe.png"
|
||||
alt="Warning"
|
||||
class="warning-icon"
|
||||
id="warningIcon"
|
||||
/>
|
||||
<p class="url" id="unsafeUrl"></p>
|
||||
<p class="warning-text">
|
||||
This site has been flagged as <strong>unsafe</strong>.<br />
|
||||
Be cautious when interacting with this site.
|
||||
</p>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-secondary" id="goBack">
|
||||
Go Back (Recommended)
|
||||
</button>
|
||||
<button class="btn btn-primary" id="proceed">Proceed Anyway</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>
|
||||
Think this is a mistake?
|
||||
<a
|
||||
href="https://github.com/fmhy/FMHY-SafeGuard/issues"
|
||||
target="_blank"
|
||||
>Let us know</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
Powered by
|
||||
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
|
||||
>FMHY Filterlist</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
33
src/pub/warning-page.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// Cross-browser compatibility shim
|
||||
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const unsafeUrl = urlParams.get("url") || "unknown site";
|
||||
document.getElementById("unsafeUrl").textContent = unsafeUrl;
|
||||
console.log(`Warning page loaded for URL: ${unsafeUrl}`);
|
||||
|
||||
document.getElementById("goBack").addEventListener("click", () => {
|
||||
console.log("User clicked Go Back.");
|
||||
// Go back twice to skip over the warning page
|
||||
window.history.go(-2);
|
||||
});
|
||||
|
||||
document.getElementById("proceed").addEventListener("click", async () => {
|
||||
if (confirm("Are you sure you want to proceed? This site may be unsafe.")) {
|
||||
const [tab] = await browserAPI.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
|
||||
console.log(`Sending proceedAnyway message for tab ${tab.id}`);
|
||||
await browserAPI.runtime.sendMessage({
|
||||
action: "proceedAnyway",
|
||||
tabId: tab.id,
|
||||
});
|
||||
|
||||
console.log("Proceed flag set, navigating to unsafe URL...");
|
||||
await browserAPI.tabs.update(tab.id, { url: unsafeUrl });
|
||||
}
|
||||
});
|
||||
});
|
||||
355
src/pub/welcome-page.html
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Welcome to FMHY SafeGuard</title>
|
||||
<link rel="icon" type="image/x-icon" href="../res/ext_icon_144.png" />
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../res/fonts/inter.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: rgb(26, 26, 26);
|
||||
--card-bg: rgba(255, 255, 255, 0.05);
|
||||
--text-primary: #e8e8e8;
|
||||
--text-secondary: #848a94;
|
||||
--accent-purple: #c4b5fd;
|
||||
--accent-blue: #7bc5e4;
|
||||
--section-border: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
font-family: "Inter", sans-serif;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.welcome-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple) 30%,
|
||||
var(--accent-blue)
|
||||
);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--section-border);
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.step:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.status-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Add responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.status-list {
|
||||
gap: 1rem;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--accent-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
var(--accent-purple),
|
||||
var(--accent-blue)
|
||||
);
|
||||
color: white;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
border: 1px solid rgba(255, 107, 107, 0.2);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.warning-title {
|
||||
color: #ff6b6b;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.step {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="welcome-header">
|
||||
<h1 class="title">Welcome to FMHY SafeGuard</h1>
|
||||
<p class="subtitle">Let's get you set up to browse safely</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="step">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
<h2 class="step-title">Pin Extension to Toolbar</h2>
|
||||
<p class="step-description">
|
||||
For quick access, pin FMHY SafeGuard to your browser toolbar:
|
||||
</p>
|
||||
<ol style="color: var(--text-secondary); margin-left: 1.5rem">
|
||||
<li>
|
||||
Click the extensions menu (puzzle piece icon) in your toolbar
|
||||
</li>
|
||||
<li>Find "FMHY SafeGuard" in the list</li>
|
||||
<li>
|
||||
Click the pin icon next to it or right-click it and select "Pin
|
||||
to Toolbar"
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
<h2 class="step-title">How It Works</h2>
|
||||
<p class="step-description">
|
||||
FMHY SafeGuard automatically checks websites against our security
|
||||
database:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); margin-left: 1.5rem">
|
||||
<li>🛡️ Blocks access to known unsafe sites</li>
|
||||
<li>⚠️ Shows warnings for potentially unsafe sites</li>
|
||||
<li>✅ Identifies trusted safe sites</li>
|
||||
</ul>
|
||||
<p class="step-description" style="margin-top: 1rem">
|
||||
The extension icon color indicates the current site's status:
|
||||
</p>
|
||||
<div class="status-list">
|
||||
<div class="status-item">
|
||||
<img
|
||||
src="../res/icons/default_19.png"
|
||||
alt="Not in Wiki"
|
||||
class="status-icon"
|
||||
/>
|
||||
<span>Not in Wiki</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<img
|
||||
src="../res/icons/starred_19.png"
|
||||
alt="Starred"
|
||||
class="status-icon"
|
||||
/>
|
||||
<span>Starred</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<img
|
||||
src="../res/icons/safe_19.png"
|
||||
alt="Safe"
|
||||
class="status-icon"
|
||||
/>
|
||||
<span>Safe</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<img
|
||||
src="../res/icons/potentially_unsafe_19.png"
|
||||
alt="Potentially Unsafe"
|
||||
class="status-icon"
|
||||
/>
|
||||
<span>Potentially Unsafe</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<img
|
||||
src="../res/icons/unsafe_19.png"
|
||||
alt="Unsafe"
|
||||
class="status-icon"
|
||||
/>
|
||||
<span>Unsafe</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
<h2 class="step-title">Customize Your Settings</h2>
|
||||
<p class="step-description">
|
||||
Configure the extension to work best for you:
|
||||
</p>
|
||||
<ul style="color: var(--text-secondary); margin-left: 1.5rem">
|
||||
<li>Choose light or dark theme</li>
|
||||
<li>Enable/disable warning pages</li>
|
||||
<li>Set automatic update frequency</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-container">
|
||||
<a href="settings-page.html" class="btn">Open Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>FMHY SafeGuard</p>
|
||||
<p>
|
||||
Powered by
|
||||
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
|
||||
>FMHY Filterlist</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
src/res/ext_icon_144.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
src/res/fonts/inter.woff2
Normal file
BIN
src/res/icons/default.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/res/icons/default_19.png
Normal file
|
After Width: | Height: | Size: 917 B |
BIN
src/res/icons/default_38.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
src/res/icons/error.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
src/res/icons/fmhy.png
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
src/res/icons/fmhy_19.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
src/res/icons/fmhy_38.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/res/icons/potentially_unsafe.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/res/icons/potentially_unsafe_19.png
Normal file
|
After Width: | Height: | Size: 880 B |
BIN
src/res/icons/potentially_unsafe_38.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/res/icons/safe.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/res/icons/safe_19.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
src/res/icons/safe_38.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
src/res/icons/starred.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/res/icons/starred_19.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
src/res/icons/starred_38.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
src/res/icons/unsafe.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
src/res/icons/unsafe_19.png
Normal file
|
After Width: | Height: | Size: 886 B |
BIN
src/res/icons/unsafe_38.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |