Import source: popup/popup
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
+214
@@ -0,0 +1,214 @@
|
||||
@font-face {
|
||||
font-family: "Vazirmatn";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("../fonts/Vazirmatn-Regular.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Vazirmatn";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("../fonts/Vazirmatn-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--paper: #f3efe5;
|
||||
--paper-deep: #e7dfd0;
|
||||
--ink: #172628;
|
||||
--muted: #6f7772;
|
||||
--line: #d4ccbd;
|
||||
--teal: #176f68;
|
||||
--teal-dark: #0e514d;
|
||||
--saffron: #e5a82c;
|
||||
--danger: #9d4b3f;
|
||||
--white: #fffdf8;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
width: 348px;
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(rgba(23, 38, 40, .025) 1px, transparent 1px),
|
||||
var(--paper);
|
||||
background-size: 100% 4px;
|
||||
font-family: "Vazirmatn", "Segoe UI", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.masthead {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 82px;
|
||||
padding: 16px 18px;
|
||||
overflow: hidden;
|
||||
color: var(--white);
|
||||
background: var(--ink);
|
||||
}
|
||||
|
||||
.masthead::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -24px;
|
||||
bottom: -48px;
|
||||
width: 118px;
|
||||
height: 118px;
|
||||
border: 18px solid rgba(229, 168, 44, .16);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 47px;
|
||||
height: 47px;
|
||||
margin-right: 13px;
|
||||
border: 1px solid rgba(255, 255, 255, .28);
|
||||
border-radius: 4px 16px 4px 4px;
|
||||
color: var(--ink);
|
||||
background: var(--saffron);
|
||||
font-size: 27px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
box-shadow: 4px 4px 0 rgba(255, 255, 255, .09);
|
||||
}
|
||||
|
||||
.brand-copy { position: relative; z-index: 1; }
|
||||
.brand-copy h1 { margin: 2px 0 0; font-size: 20px; line-height: 1; letter-spacing: -.25px; }
|
||||
.eyebrow, .section-label {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.25px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.masthead .eyebrow { color: #aab6b1; }
|
||||
.edition {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-self: flex-start;
|
||||
margin-left: auto;
|
||||
color: #8d9b96;
|
||||
font: 700 10px/1 "Courier New", monospace;
|
||||
}
|
||||
|
||||
main { padding: 14px; }
|
||||
|
||||
.site-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-height: 59px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-left: 3px solid var(--saffron);
|
||||
background: rgba(255, 253, 248, .62);
|
||||
}
|
||||
.site-copy { min-width: 0; }
|
||||
.site-host {
|
||||
display: block;
|
||||
max-width: 184px;
|
||||
margin-top: 3px;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
}
|
||||
.site-status {
|
||||
flex: 0 0 auto;
|
||||
max-width: 118px;
|
||||
margin: 0;
|
||||
color: var(--teal-dark);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
text-align: right;
|
||||
}
|
||||
.site-status::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-right: 5px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
.site-status[data-tone="paused"] { color: #86650e; }
|
||||
.site-status[data-tone="error"] { color: var(--danger); }
|
||||
|
||||
.settings-card, .master-card {
|
||||
margin-top: 11px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--white);
|
||||
box-shadow: 0 5px 18px rgba(44, 51, 45, .055);
|
||||
}
|
||||
.master-card { border-color: #b8c7be; }
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 61px;
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 140ms ease, opacity 140ms ease;
|
||||
}
|
||||
.setting-row + .setting-row { border-top: 1px solid #e9e4da; }
|
||||
.setting-row:hover { background: #f8f5ee; }
|
||||
.setting-row.is-disabled { cursor: default; opacity: .48; }
|
||||
.setting-copy { flex: 1; min-width: 0; }
|
||||
.setting-copy strong { display: block; font-size: 12.5px; line-height: 1.3; }
|
||||
.setting-copy small { display: block; margin-top: 3px; color: var(--muted); font-size: 10.5px; line-height: 1.35; }
|
||||
.master-row { min-height: 54px; }
|
||||
.master-row .section-label { margin-bottom: 2px; }
|
||||
|
||||
.switch { position: relative; flex: 0 0 auto; width: 40px; height: 23px; }
|
||||
.switch input { position: absolute; width: 1px; height: 1px; opacity: 0; }
|
||||
.switch-track {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid #9ca49f;
|
||||
border-radius: 3px 12px 12px 3px;
|
||||
background: var(--paper-deep);
|
||||
transition: background-color 160ms ease, border-color 160ms ease;
|
||||
}
|
||||
.switch-track::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 2px 7px 7px 2px;
|
||||
background: var(--white);
|
||||
box-shadow: 0 1px 3px rgba(23, 38, 40, .28);
|
||||
transition: transform 160ms ease;
|
||||
}
|
||||
.switch input:checked + .switch-track { border-color: var(--teal-dark); background: var(--teal); }
|
||||
.switch input:checked + .switch-track::after { transform: translateX(17px); }
|
||||
.switch input:focus-visible + .switch-track { outline: 3px solid rgba(229, 168, 44, .55); outline-offset: 3px; }
|
||||
.switch input:disabled + .switch-track { filter: grayscale(.6); }
|
||||
|
||||
.privacy-note {
|
||||
margin: 13px 0 1px;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { transition-duration: .01ms !important; }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Persian RTL Fixer</title>
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="masthead">
|
||||
<div class="brand-mark" aria-hidden="true">ف</div>
|
||||
<div class="brand-copy">
|
||||
<p class="eyebrow">Persian reading tools</p>
|
||||
<h1>RTL Fixer</h1>
|
||||
</div>
|
||||
<span class="edition">01</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="site-panel" aria-labelledby="site-heading">
|
||||
<div class="site-copy">
|
||||
<span id="site-heading" class="section-label">Current site</span>
|
||||
<strong id="host" class="site-host">Loading…</strong>
|
||||
</div>
|
||||
<p id="siteStatus" class="site-status" role="status" aria-live="polite">Checking status…</p>
|
||||
</section>
|
||||
|
||||
<section class="settings-card" aria-label="Site and correction settings">
|
||||
<label class="setting-row" for="thisSite">
|
||||
<span class="setting-copy">
|
||||
<strong>Enable on this site</strong>
|
||||
<small>Pause corrections for this domain</small>
|
||||
</span>
|
||||
<span class="switch">
|
||||
<input type="checkbox" id="thisSite" />
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="setting-row" for="fixDirection">
|
||||
<span class="setting-copy">
|
||||
<strong>Conservative RTL correction</strong>
|
||||
<small>Fix clearly Persian text without flipping layouts</small>
|
||||
</span>
|
||||
<span class="switch">
|
||||
<input type="checkbox" id="fixDirection" />
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="setting-row" for="useFont">
|
||||
<span class="setting-copy">
|
||||
<strong>Vazirmatn for unstyled text</strong>
|
||||
<small>Preserve every font chosen by the website</small>
|
||||
</span>
|
||||
<span class="switch">
|
||||
<input type="checkbox" id="useFont" />
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="setting-row" for="fixInputs">
|
||||
<span class="setting-copy">
|
||||
<strong>Smart input direction</strong>
|
||||
<small>Adapt text fields as you type Persian</small>
|
||||
</span>
|
||||
<span class="switch">
|
||||
<input type="checkbox" id="fixInputs" />
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</span>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="master-card" aria-label="Global extension setting">
|
||||
<label class="setting-row master-row" for="enabled">
|
||||
<span class="setting-copy">
|
||||
<span class="section-label">Everywhere</span>
|
||||
<strong>Master switch</strong>
|
||||
</span>
|
||||
<span class="switch">
|
||||
<input type="checkbox" id="enabled" />
|
||||
<span class="switch-track" aria-hidden="true"></span>
|
||||
</span>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<p class="privacy-note">Runs locally · Never rewrites your text</p>
|
||||
</main>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const DEFAULTS = {
|
||||
enabled: true,
|
||||
fixDirection: true,
|
||||
useFont: true,
|
||||
fixInputs: true,
|
||||
disabledSites: []
|
||||
};
|
||||
|
||||
const els = {
|
||||
host: document.getElementById("host"),
|
||||
status: document.getElementById("siteStatus"),
|
||||
thisSite: document.getElementById("thisSite"),
|
||||
fixDirection: document.getElementById("fixDirection"),
|
||||
useFont: document.getElementById("useFont"),
|
||||
fixInputs: document.getElementById("fixInputs"),
|
||||
enabled: document.getElementById("enabled")
|
||||
};
|
||||
|
||||
let hostname = "";
|
||||
let supportedPage = false;
|
||||
let storageFailed = false;
|
||||
let state = { ...DEFAULTS };
|
||||
|
||||
function pageInfo(url) {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const supported = (parsed.protocol === "http:" || parsed.protocol === "https:") && !!parsed.hostname;
|
||||
return { hostname: supported ? parsed.hostname : "", supported };
|
||||
} catch (error) {
|
||||
return { hostname: "", supported: false };
|
||||
}
|
||||
}
|
||||
|
||||
function setDisabled(el, disabled) {
|
||||
el.disabled = disabled;
|
||||
const row = el.closest(".setting-row");
|
||||
if (row) row.classList.toggle("is-disabled", disabled);
|
||||
}
|
||||
|
||||
function setStatus(text, tone) {
|
||||
els.status.textContent = text;
|
||||
els.status.dataset.tone = tone;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const siteDisabled = hostname ? state.disabledSites.includes(hostname) : false;
|
||||
const masterOff = !state.enabled;
|
||||
|
||||
els.host.textContent = supportedPage ? hostname : "Unavailable";
|
||||
els.enabled.checked = !!state.enabled;
|
||||
els.thisSite.checked = supportedPage && !siteDisabled;
|
||||
els.fixDirection.checked = !!state.fixDirection;
|
||||
els.useFont.checked = !!state.useFont;
|
||||
els.fixInputs.checked = !!state.fixInputs;
|
||||
|
||||
setDisabled(els.thisSite, storageFailed || masterOff || !supportedPage);
|
||||
setDisabled(els.fixDirection, storageFailed || masterOff);
|
||||
setDisabled(els.useFont, storageFailed || masterOff);
|
||||
setDisabled(els.fixInputs, storageFailed || masterOff);
|
||||
|
||||
if (storageFailed) setStatus("Settings unavailable", "error");
|
||||
else if (!supportedPage) setStatus("This page cannot be modified", "error");
|
||||
else if (masterOff) setStatus("Extension paused everywhere", "paused");
|
||||
else if (siteDisabled) setStatus("Paused on this site", "paused");
|
||||
else setStatus("Persian correction active", "active");
|
||||
}
|
||||
|
||||
function save(patch) {
|
||||
Object.assign(state, patch);
|
||||
chrome.storage.local.set(patch, () => {
|
||||
if (chrome.runtime.lastError) storageFailed = true;
|
||||
render();
|
||||
});
|
||||
render();
|
||||
}
|
||||
|
||||
function init() {
|
||||
chrome.storage.local.get(DEFAULTS, (stored) => {
|
||||
if (chrome.runtime.lastError) storageFailed = true;
|
||||
else state = { ...DEFAULTS, ...stored };
|
||||
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
const info = pageInfo(tabs && tabs[0] ? tabs[0].url || "" : "");
|
||||
hostname = info.hostname;
|
||||
supportedPage = info.supported;
|
||||
render();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
els.enabled.addEventListener("change", (event) => save({ enabled: event.target.checked }));
|
||||
els.fixDirection.addEventListener("change", (event) => save({ fixDirection: event.target.checked }));
|
||||
els.useFont.addEventListener("change", (event) => save({ useFont: event.target.checked }));
|
||||
els.fixInputs.addEventListener("change", (event) => save({ fixInputs: event.target.checked }));
|
||||
els.thisSite.addEventListener("change", (event) => {
|
||||
if (!hostname) return;
|
||||
const disabledSites = state.disabledSites.filter((site) => site !== hostname);
|
||||
if (!event.target.checked) disabledSites.push(hostname);
|
||||
save({ disabledSites });
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
if (area !== "local") return;
|
||||
let changed = false;
|
||||
for (const key of Object.keys(DEFAULTS)) {
|
||||
if (changes[key]) {
|
||||
state[key] = changes[key].newValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) render();
|
||||
});
|
||||
|
||||
init();
|
||||
})();
|
||||
Reference in New Issue
Block a user