mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
Merge pull request #361 from StroepWafel/Privacy-statement
re-add Privacy statement stuff
This commit is contained in:
@@ -2,6 +2,34 @@ import browser from "webextension-polyfill";
|
|||||||
import type { SettingsState } from "@/types/storage";
|
import type { SettingsState } from "@/types/storage";
|
||||||
import { fetchNews } from "./background/news";
|
import { fetchNews } from "./background/news";
|
||||||
|
|
||||||
|
interface PrivacyPolicyResponse {
|
||||||
|
last_updated: string;
|
||||||
|
whatsnew_html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPrivacyPolicy(sendResponse: (response?: any) => void) {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://betterseqta.org/api/policy/privacy", {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error("[BetterSEQTA+] Failed to fetch privacy policy:", response.status);
|
||||||
|
sendResponse({ error: `HTTP ${response.status}`, data: null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json() as PrivacyPolicyResponse;
|
||||||
|
sendResponse({ error: null, data });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[BetterSEQTA+] Error fetching privacy policy:", error);
|
||||||
|
sendResponse({ error: String(error), data: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function reloadSeqtaPages() {
|
function reloadSeqtaPages() {
|
||||||
const result = browser.tabs.query({});
|
const result = browser.tabs.query({});
|
||||||
function open(tabs: any) {
|
function open(tabs: any) {
|
||||||
@@ -56,6 +84,10 @@ browser.runtime.onMessage.addListener(
|
|||||||
fetchNews(request.source ?? "australia", sendResponse);
|
fetchNews(request.source ?? "australia", sendResponse);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case "fetchPrivacyPolicy":
|
||||||
|
fetchPrivacyPolicy(sendResponse);
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log("Unknown request type");
|
console.log("Unknown request type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"last_updated": "2024-06-15T12:00:00Z",
|
||||||
|
"whatsnew_html": "<div class=\"whatsnewTextContainer\" style=\"overflow-y: auto; font-size: 1.3rem; line-height: 1.6;\"><p>It has come to our attention that several schools have expressed concerns about BetterSEQTA+. This is very disheartening, so we have decided to release a statement on the situation.</p><p>To view our privacy policy, please click the <strong>shield icon</strong> in the settings menu, or <a href=\"https://betterseqta.org/privacy\" target=\"_blank\" rel=\"noopener noreferrer\" id=\"privacy-link\" style=\"color: inherit; text-decoration: underline; cursor: pointer; white-space: nowrap;\">click here</a>.</p><p style=\"font-weight: bold; margin-top: 15px;\">We never collect any information from you, and aim to provide the best features possible.</p></div>"
|
||||||
|
}
|
||||||
@@ -55,6 +55,11 @@
|
|||||||
closeExtensionPopup();
|
closeExtensionPopup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openPrivacyStatement = () => {
|
||||||
|
window.open("https://betterseqta.org/privacy", "_blank");
|
||||||
|
closeExtensionPopup();
|
||||||
|
};
|
||||||
|
|
||||||
let { standalone } = $props<{ standalone?: boolean }>();
|
let { standalone } = $props<{ standalone?: boolean }>();
|
||||||
let showColourPicker = $state<boolean>(false);
|
let showColourPicker = $state<boolean>(false);
|
||||||
|
|
||||||
@@ -101,23 +106,32 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{#if !standalone}
|
{#if !standalone}
|
||||||
|
<div class="absolute top-1 right-1 flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onclick={openAbout}
|
onclick={openAbout}
|
||||||
class="absolute top-1 right-[63px] w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
class="w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
{"\ueb73"}
|
{"\ueb73"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onclick={openPrivacyStatement}
|
||||||
|
class="w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700 flex items-center justify-center"
|
||||||
|
aria-label="Privacy Statement"
|
||||||
|
>
|
||||||
|
{"\uecba"}
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={openChangelog}
|
onclick={openChangelog}
|
||||||
class="absolute top-1 right-10 w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700"
|
class="w-8 h-8 text-lg rounded-xl font-IconFamily bg-zinc-100 dark:bg-zinc-700 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
{"\ue929"}
|
{"\ue929"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onclick={openMinecraftServer}
|
onclick={openMinecraftServer}
|
||||||
class="absolute top-1 right-1 w-8 h-8 bg-zinc-100 dark:bg-zinc-700 rounded-xl p-1"
|
class="w-8 h-8 bg-zinc-100 dark:bg-zinc-700 rounded-xl p-1 flex items-center justify-center"
|
||||||
aria-label="Open Minecraft Server"
|
aria-label="Open Minecraft Server"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -248,6 +262,7 @@
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
import type { SettingsList } from "@/interface/types/SettingsProps"
|
import type { SettingsList } from "@/interface/types/SettingsProps"
|
||||||
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState.ts"
|
||||||
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
import PickerSwatch from "@/interface/components/PickerSwatch.svelte"
|
||||||
|
import { checkAndShowPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification"
|
||||||
|
import { closeExtensionPopup } from "@/seqta/utils/Closers/closeExtensionPopup"
|
||||||
|
|
||||||
import { getAllPluginSettings } from "@/plugins"
|
import { getAllPluginSettings } from "@/plugins"
|
||||||
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
import type { BooleanSetting, StringSetting, NumberSetting, SelectSetting, ButtonSetting, HotkeySetting, ComponentSetting } from "@/plugins/core/types"
|
||||||
@@ -340,6 +342,25 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between items-center px-4 py-3">
|
||||||
|
<div class="pr-4">
|
||||||
|
<h2 class="text-sm font-bold">Show Privacy Notification</h2>
|
||||||
|
<p class="text-xs">Show the privacy notification popup on next page load</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
settingsState.privacyStatementShown = false;
|
||||||
|
settingsState.privacyStatementLastUpdated = undefined;
|
||||||
|
closeExtensionPopup();
|
||||||
|
// Small delay to ensure popup is closed before showing notification
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
await checkAndShowPrivacyNotification();
|
||||||
|
}}
|
||||||
|
text="Show Now"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import loading from "@/seqta/ui/Loading";
|
|||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||||
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
import { OpenWhatsNewPopup } from "@/seqta/utils/Openers/OpenWhatsNewPopup";
|
||||||
|
import { checkAndShowPrivacyNotification } from "@/seqta/utils/Openers/OpenPrivacyNotification";
|
||||||
|
|
||||||
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
|
import { updateTimetableTimes } from "@/seqta/utils/updateTimetableTimes";
|
||||||
|
|
||||||
@@ -93,7 +94,13 @@ export async function finishLoad() {
|
|||||||
console.error("Error during loading cleanup:", err);
|
console.error("Error during loading cleanup:", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
// Check and show privacy statement notification (before what's new)
|
||||||
|
// This will check the API and compare timestamps
|
||||||
|
if (!document.getElementById("privacy-notification")) {
|
||||||
|
await checkAndShowPrivacyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsState.justupdated && !document.getElementById("whatsnewbk") && !document.getElementById("privacy-notification")) {
|
||||||
OpenWhatsNewPopup();
|
OpenWhatsNewPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { settingsState } from "../listeners/SettingsState";
|
||||||
|
import { animate } from "motion";
|
||||||
|
import { OpenWhatsNewPopup } from "./OpenWhatsNewPopup";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
import browser from "webextension-polyfill";
|
||||||
|
|
||||||
|
interface PrivacyPolicyResponse {
|
||||||
|
last_updated: string;
|
||||||
|
whatsnew_html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPrivacyPolicy(): Promise<PrivacyPolicyResponse | null> {
|
||||||
|
try {
|
||||||
|
// Use background script to avoid CORS issues
|
||||||
|
const response = await browser.runtime.sendMessage({ type: "fetchPrivacyPolicy" }) as { error: string | null; data: PrivacyPolicyResponse | null };
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
console.error("[BetterSEQTA+] Failed to fetch privacy policy:", response.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[BetterSEQTA+] Error fetching privacy policy:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAndShowPrivacyNotification() {
|
||||||
|
if (document.getElementById("privacy-notification")) return;
|
||||||
|
|
||||||
|
// Fetch the privacy policy from the API
|
||||||
|
const policyData = await fetchPrivacyPolicy();
|
||||||
|
|
||||||
|
if (!policyData) {
|
||||||
|
// If API fails, fall back to showing the notification if never shown
|
||||||
|
if (!settingsState.privacyStatementShown) {
|
||||||
|
showPrivacyNotificationWithContent(null);
|
||||||
|
settingsState.privacyStatementShown = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should show the notification
|
||||||
|
const storedTimestamp = settingsState.privacyStatementLastUpdated;
|
||||||
|
const shouldShow = !storedTimestamp ||
|
||||||
|
new Date(policyData.last_updated) > new Date(storedTimestamp);
|
||||||
|
|
||||||
|
if (shouldShow) {
|
||||||
|
// Sanitize the HTML content to prevent XSS attacks
|
||||||
|
// DOMPurify will remove any dangerous scripts, event handlers, etc
|
||||||
|
// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
// SANITIZE CONTENT:
|
||||||
|
// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
// The content in sanitizedHTML is the only content that is allowed to be displayed in the notification:
|
||||||
|
const sanitizedHTML = DOMPurify.sanitize(policyData.whatsnew_html, {
|
||||||
|
ALLOWED_TAGS: ['div', 'p', 'strong', 'a', 'h1', 'h2', 'h3', 'ul', 'li', 'span', 'em', 'b', 'i'],
|
||||||
|
ALLOWED_ATTR: ['class', 'style', 'href', 'target', 'rel', 'id'],
|
||||||
|
ALLOW_DATA_ATTR: false,
|
||||||
|
ALLOW_UNKNOWN_PROTOCOLS: false,
|
||||||
|
// Ensure links are safe - allow https/http/mailto only
|
||||||
|
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||||
|
// Force all external links to have target="_blank" and rel="noopener noreferrer"
|
||||||
|
ADD_ATTR: ['target', 'rel'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post-process to sanitize URLs and ensure all links have proper attributes
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = sanitizedHTML;
|
||||||
|
|
||||||
|
// First, remove hrefs from links that aren't to betterseqta.org
|
||||||
|
tempDiv.querySelectorAll("a").forEach(a => {
|
||||||
|
const href = a.getAttribute("href") || "";
|
||||||
|
// Allow only links to your domain
|
||||||
|
if (!href.startsWith("https://betterseqta.org")) {
|
||||||
|
a.removeAttribute("href"); // neuter link
|
||||||
|
a.style.textDecoration = "none"; // optional visual fix
|
||||||
|
a.style.cursor = "default"; // optional
|
||||||
|
} else {
|
||||||
|
// Ensure all betterseqta.org links have proper attributes
|
||||||
|
(a as HTMLAnchorElement).target = "_blank";
|
||||||
|
(a as HTMLAnchorElement).rel = "noopener noreferrer";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanHTML = tempDiv.innerHTML;
|
||||||
|
|
||||||
|
showPrivacyNotificationWithContent(cleanHTML);
|
||||||
|
|
||||||
|
// Update the stored timestamp
|
||||||
|
settingsState.privacyStatementLastUpdated = policyData.last_updated;
|
||||||
|
settingsState.privacyStatementShown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPrivacyNotificationWithContent(htmlContent: string | null) {
|
||||||
|
if (document.getElementById("privacy-notification")) return;
|
||||||
|
|
||||||
|
const popupBackground = document.createElement("div");
|
||||||
|
popupBackground.id = "privacy-notification";
|
||||||
|
popupBackground.classList.add("whatsnewBackground");
|
||||||
|
popupBackground.style.zIndex = "10000001";
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.classList.add("whatsnewContainer");
|
||||||
|
container.style.height = "auto";
|
||||||
|
container.style.maxWidth = "800px";
|
||||||
|
container.style.width = "90%";
|
||||||
|
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Important Information</p>
|
||||||
|
</div>`,
|
||||||
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
// Use fetched content if available, otherwise use fallback
|
||||||
|
let text: HTMLElement;
|
||||||
|
if (htmlContent) {
|
||||||
|
// Parse the sanitized HTML
|
||||||
|
text = stringToHTML(htmlContent).firstChild as HTMLElement;
|
||||||
|
} else {
|
||||||
|
// Fallback content if API fails
|
||||||
|
text = stringToHTML(/* html */ `
|
||||||
|
<div class="whatsnewTextContainer" style="overflow-y: auto; font-size: 1.3rem; line-height: 1.6;">
|
||||||
|
<p>
|
||||||
|
It has come to our attention that several schools have expressed concerns about BetterSEQTA+. This is very disheartening, so we have decided to release a statement on the situation.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To view our privacy policy, please click the <strong>shield icon</strong> in the settings menu, or<a href="https://betterseqta.org/privacy" target="_blank" rel="noopener noreferrer" id="privacy-link" style="color: inherit; text-decoration: underline; cursor: pointer; white-space: nowrap;"> click here</a>.
|
||||||
|
</p>
|
||||||
|
<p style="font-weight: bold; margin-top: 15px;">
|
||||||
|
We never collect any information from you, and aim to provide the best features possible.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header) container.append(header);
|
||||||
|
if (text) container.append(text);
|
||||||
|
|
||||||
|
const closeButton = document.createElement("div");
|
||||||
|
closeButton.id = "whatsnewclosebutton";
|
||||||
|
container.append(closeButton);
|
||||||
|
|
||||||
|
popupBackground.append(container);
|
||||||
|
document.getElementById("container")?.append(popupBackground);
|
||||||
|
|
||||||
|
if (settingsState.animations) {
|
||||||
|
animate([popupBackground as HTMLElement], { opacity: [0, 1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
popupBackground.addEventListener("click", (event) => {
|
||||||
|
if (event.target === popupBackground) {
|
||||||
|
popupBackground.remove();
|
||||||
|
// Show what's new if it was waiting
|
||||||
|
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
||||||
|
OpenWhatsNewPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addEventListener("click", () => {
|
||||||
|
popupBackground.remove();
|
||||||
|
// Show what's new if it was waiting
|
||||||
|
if (settingsState.justupdated && !document.getElementById("whatsnewbk")) {
|
||||||
|
OpenWhatsNewPopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle all privacy policy links - ensure they open in new tab
|
||||||
|
setTimeout(() => {
|
||||||
|
// Find all links that point to the privacy policy
|
||||||
|
const allLinks = container.querySelectorAll('a[href*="betterseqta.org/privacy"]');
|
||||||
|
allLinks.forEach((link) => {
|
||||||
|
link.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const href = (link as HTMLAnchorElement).href || "https://betterseqta.org/privacy";
|
||||||
|
window.open(href, "_blank", "noopener,noreferrer");
|
||||||
|
});
|
||||||
|
// Ensure target and rel attributes are set
|
||||||
|
(link as HTMLAnchorElement).target = "_blank";
|
||||||
|
(link as HTMLAnchorElement).rel = "noopener noreferrer";
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy function name for backwards compatibility
|
||||||
|
export function showPrivacyNotification() {
|
||||||
|
checkAndShowPrivacyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import stringToHTML from "../stringToHTML";
|
||||||
|
import { openPopup } from "./PopupManager";
|
||||||
|
|
||||||
|
export function OpenPrivacyStatement() {
|
||||||
|
const header = stringToHTML(
|
||||||
|
/* html */
|
||||||
|
`<div class="whatsnewHeader">
|
||||||
|
<h1>Privacy Statement</h1>
|
||||||
|
<p>Our commitment to your privacy</p>
|
||||||
|
</div>`,
|
||||||
|
).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
const text = stringToHTML(/* html */ `
|
||||||
|
<div class="whatsnewTextContainer" style="overflow-y: auto; max-height: 60vh;">
|
||||||
|
<h2 style="margin-top: 0;">Privacy Policy</h2>
|
||||||
|
<p>At BetterSEQTA+, we take your privacy seriously. We want to be completely transparent about how we handle your data.</p>
|
||||||
|
|
||||||
|
<h3>Data Collection</h3>
|
||||||
|
<p><strong>We never collect any information from you.</strong> BetterSEQTA+ is designed to work entirely on your device. All processing happens locally in your browser, and we do not send any data to external servers.</p>
|
||||||
|
|
||||||
|
<h3>What We Don't Do</h3>
|
||||||
|
<ul style="text-align: left; margin: 10px 0;">
|
||||||
|
<li>We do not track your browsing activity</li>
|
||||||
|
<li>We do not collect personal information</li>
|
||||||
|
<li>We do not store your SEQTA credentials</li>
|
||||||
|
<li>We do not send data to third-party services</li>
|
||||||
|
<li>We do not use analytics or tracking cookies</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Local Storage</h3>
|
||||||
|
<p>BetterSEQTA+ uses your browser's local storage to save your preferences and settings. This data remains on your device and is never transmitted anywhere. You can clear this data at any time through your browser's settings.</p>
|
||||||
|
|
||||||
|
<h3>Open Source</h3>
|
||||||
|
<p>BetterSEQTA+ is an open-source project. You can review our code on <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub</a> to verify our privacy practices. We believe in transparency and encourage you to inspect the code yourself.</p>
|
||||||
|
|
||||||
|
<h3>Our Commitment</h3>
|
||||||
|
<p>We are committed to providing the best features possible while respecting your privacy. We understand that schools and students have concerns about data privacy, and we want to assure you that BetterSEQTA+ is designed with privacy as a core principle.</p>
|
||||||
|
|
||||||
|
<p style="margin-top: 20px; font-weight: bold;">If you have any questions or concerns about our privacy practices, please reach out to us through our <a href="https://github.com/BetterSEQTA/BetterSEQTA-Plus" target="_blank" style="color: inherit; text-decoration: underline;">GitHub repository</a>.</p>
|
||||||
|
</div>
|
||||||
|
`).firstChild as HTMLElement;
|
||||||
|
|
||||||
|
openPopup({
|
||||||
|
header,
|
||||||
|
content: [text],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ export interface SettingsState {
|
|||||||
subjectfilters: Record<string, any>;
|
subjectfilters: Record<string, any>;
|
||||||
transparencyEffects: boolean;
|
transparencyEffects: boolean;
|
||||||
justupdated?: boolean;
|
justupdated?: boolean;
|
||||||
|
privacyStatementShown?: boolean;
|
||||||
|
privacyStatementLastUpdated?: string;
|
||||||
timeFormat?: string;
|
timeFormat?: string;
|
||||||
animations: boolean;
|
animations: boolean;
|
||||||
defaultPage: string;
|
defaultPage: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user