feat,fix: document observation, popup refinement
* Implement document recheck on text content change * Review extension config * Split text/image popup sections into tabs * Fix popup setting not being properly reactive
This commit is contained in:
parent
f2b8bdca98
commit
b84eaaac82
7 changed files with 366 additions and 284 deletions
|
|
@ -24,14 +24,14 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@tsconfig/svelte": "^5.0.7",
|
"@tsconfig/svelte": "^5.0.8",
|
||||||
"@wxt-dev/module-svelte": "^2.0.4",
|
"@wxt-dev/module-svelte": "^2.0.4",
|
||||||
"daisyui": "^5.5.19",
|
"daisyui": "^5.5.19",
|
||||||
"svelte": "^5.50.0",
|
"svelte": "^5.53.10",
|
||||||
"svelte-check": "^4.3.6",
|
"svelte-check": "^4.4.5",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"wxt": "^0.20.14"
|
"wxt": "^0.20.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,36 +106,34 @@ function setup_inference_ai_text() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function configure_ai_text(config: ExtensionConfigAiText) {
|
async function configure_ai_text(config: ExtensionConfigAiText) {
|
||||||
if (config.enabled) {
|
// if (config.enabled) {
|
||||||
const loader = config.lite ? inference_ai_text_lite : inference_ai_text;
|
// const loader = config.lite ? inference_ai_text_lite : inference_ai_text;
|
||||||
const handler = config.lite ? AiTextLite : AiText;
|
// const handler = config.lite ? AiTextLite : AiText;
|
||||||
const capability = config.lite ? AiTextLiteCapability : AiTextCapability;
|
// const capability = config.lite ? AiTextLiteCapability : AiTextCapability;
|
||||||
const have = config.lite ? AI_TEXT_STATE.have_lite_model : AI_TEXT_STATE.have_normal_model;
|
// const have = config.lite ? AI_TEXT_STATE.have_lite_model : AI_TEXT_STATE.have_normal_model;
|
||||||
const set_have = config.lite
|
// const set_have = config.lite
|
||||||
? () => (AI_TEXT_STATE.have_lite_model = true)
|
// ? () => (AI_TEXT_STATE.have_lite_model = true)
|
||||||
: () => (AI_TEXT_STATE.have_normal_model = true);
|
// : () => (AI_TEXT_STATE.have_normal_model = true);
|
||||||
|
// if (!have) {
|
||||||
if (!have) {
|
// await loader();
|
||||||
await loader();
|
// if (await guess_has_webgpu()) {
|
||||||
if (await guess_has_webgpu()) {
|
// console.warn(
|
||||||
console.warn(
|
// "WebGPU detected, but use is not currently possible due to burn APIs being sync. Check the issue progress in burn and burn-onnx repos. In the mean time will use NdArray anyway.",
|
||||||
"WebGPU detected, but use is not currently possible due to burn APIs being sync. Check the issue progress in burn and burn-onnx repos. In the mean time will use NdArray anyway.",
|
// );
|
||||||
);
|
// //await capability.assert_ndarray();
|
||||||
//await capability.assert_ndarray();
|
// await capability.assert_webgpu();
|
||||||
await capability.assert_webgpu();
|
// } else {
|
||||||
} else {
|
// await capability.assert_ndarray();
|
||||||
await capability.assert_ndarray();
|
// }
|
||||||
}
|
// set_have();
|
||||||
set_have();
|
// }
|
||||||
}
|
// AI_TEXT_STATE.handle = new handler();
|
||||||
|
// } else {
|
||||||
AI_TEXT_STATE.handle = new handler();
|
// AI_TEXT_STATE.handle = null;
|
||||||
} else {
|
// if (AI_TEXT_STATE.queue_inference_timer) {
|
||||||
AI_TEXT_STATE.handle = null;
|
// clearTimeout(AI_TEXT_STATE.queue_inference_timer);
|
||||||
if (AI_TEXT_STATE.queue_inference_timer) {
|
// }
|
||||||
clearTimeout(AI_TEXT_STATE.queue_inference_timer);
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function guess_has_webgpu() {
|
async function guess_has_webgpu() {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,37 @@
|
||||||
import { EXTENSION_CONFIG, ExtensionConfigAiImage, ExtensionConfigAiText } from "@/utils/config";
|
import { EXTENSION_CONFIG, ExtensionConfigAiImage, ExtensionConfigAiText } from "@/utils/config";
|
||||||
import { traverse_text } from "@/utils/domtraverse";
|
import { traverse_text } from "@/utils/domtraverse";
|
||||||
|
|
||||||
|
const console = ExtConsole("content");
|
||||||
|
|
||||||
export default defineContentScript({
|
export default defineContentScript({
|
||||||
matches: ["http://*/*", "https://*/*"],
|
matches: ["http://*/*", "https://*/*"],
|
||||||
main() {
|
main() {
|
||||||
const trigger = setTimeout(() => {
|
DOM_OBSERVATORY.observer = new MutationObserver((_) => {
|
||||||
|
if (DOM_OBSERVATORY.timer) {
|
||||||
|
clearTimeout(DOM_OBSERVATORY.timer);
|
||||||
|
}
|
||||||
|
DOM_OBSERVATORY.timer = setTimeout(() => {
|
||||||
|
console.debug("retraverse, cause: mutation");
|
||||||
text_ai_traverse();
|
text_ai_traverse();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
document.addEventListener("load", () => {
|
|
||||||
clearTimeout(trigger);
|
|
||||||
|
|
||||||
text_ai_traverse();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
text_ai_traverse();
|
||||||
|
|
||||||
|
DOM_OBSERVATORY.observer?.observe(document.body, {
|
||||||
|
subtree: true,
|
||||||
|
characterData: true,
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DOM_OBSERVATORY = {
|
||||||
|
observer: null as MutationObserver | null,
|
||||||
|
timer: null as number | null,
|
||||||
|
};
|
||||||
|
|
||||||
const TRAVERSE_VIEWPORT_OBSERVER = new IntersectionObserver(
|
const TRAVERSE_VIEWPORT_OBSERVER = new IntersectionObserver(
|
||||||
(entries, observer) => {
|
(entries, observer) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
|
|
@ -23,8 +39,7 @@ const TRAVERSE_VIEWPORT_OBSERVER = new IntersectionObserver(
|
||||||
const stack = TRAVERSE_PARENT_TO_STACK.get(entry.target);
|
const stack = TRAVERSE_PARENT_TO_STACK.get(entry.target);
|
||||||
|
|
||||||
if (stack) {
|
if (stack) {
|
||||||
sendMessage("inference_aiText_classify", { text: stack.text })
|
sendMessage("inference_aiText_classify", { text: stack.text }).then((result) => {
|
||||||
.then((result) => {
|
|
||||||
observer.unobserve(entry.target);
|
observer.unobserve(entry.target);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
@ -42,8 +57,7 @@ const TRAVERSE_VIEWPORT_OBSERVER = new IntersectionObserver(
|
||||||
} else {
|
} else {
|
||||||
ai_clear_badge(entry.target);
|
ai_clear_badge(entry.target);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.catch((e) => {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -63,6 +77,7 @@ interface WeakStack {
|
||||||
}
|
}
|
||||||
const TRAVERSE_PARENT_TO_STACK: WeakMap<Element, WeakStack> = new WeakMap();
|
const TRAVERSE_PARENT_TO_STACK: WeakMap<Element, WeakStack> = new WeakMap();
|
||||||
function text_ai_traverse() {
|
function text_ai_traverse() {
|
||||||
|
console.debug("text_ai_traverse: traverse DOM");
|
||||||
for (const stack of traverse_text(600, 20)) {
|
for (const stack of traverse_text(600, 20)) {
|
||||||
let target = stack.first_node.parentElement || stack.root.parentElement;
|
let target = stack.first_node.parentElement || stack.root.parentElement;
|
||||||
if (target) {
|
if (target) {
|
||||||
|
|
@ -77,6 +92,7 @@ const SET_BADGES: WeakMap<Node, Element> = new WeakMap();
|
||||||
function ai_set_badge(node: Node, ai?: number, human?: number) {
|
function ai_set_badge(node: Node, ai?: number, human?: number) {
|
||||||
const badge = document.createElement("span");
|
const badge = document.createElement("span");
|
||||||
|
|
||||||
|
badge.setAttribute(TRAVERSE_BEHAVOUR_ATTR, TRAVERSE_BEHAVOUR_IGNORE_MAGIC);
|
||||||
badge.style.display = "inline";
|
badge.style.display = "inline";
|
||||||
badge.style.borderRadius = "4px";
|
badge.style.borderRadius = "4px";
|
||||||
badge.style.textAlign = "center";
|
badge.style.textAlign = "center";
|
||||||
|
|
@ -84,7 +100,6 @@ function ai_set_badge(node: Node, ai?: number, human?: number) {
|
||||||
badge.style.fontSize = "12px";
|
badge.style.fontSize = "12px";
|
||||||
badge.style.padding = "1px";
|
badge.style.padding = "1px";
|
||||||
badge.style.marginRight = "4px";
|
badge.style.marginRight = "4px";
|
||||||
if (ai || human) {
|
|
||||||
if (ai) {
|
if (ai) {
|
||||||
badge.style.backgroundColor = "#ffc36d";
|
badge.style.backgroundColor = "#ffc36d";
|
||||||
badge.style.color = "#fff";
|
badge.style.color = "#fff";
|
||||||
|
|
@ -93,11 +108,6 @@ function ai_set_badge(node: Node, ai?: number, human?: number) {
|
||||||
badge.style.backgroundColor = "#8fe1a1";
|
badge.style.backgroundColor = "#8fe1a1";
|
||||||
badge.style.color = "#fff";
|
badge.style.color = "#fff";
|
||||||
badge.textContent = `👨💼: ${Math.trunc(human * 100)}%`;
|
badge.textContent = `👨💼: ${Math.trunc(human * 100)}%`;
|
||||||
} else {
|
|
||||||
badge.style.backgroundColor = "#ddd";
|
|
||||||
badge.style.color = "#fff";
|
|
||||||
badge.textContent = "🦊: confused";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
badge.style.backgroundColor = "#ddd";
|
badge.style.backgroundColor = "#ddd";
|
||||||
badge.style.color = "#fff";
|
badge.style.color = "#fff";
|
||||||
|
|
|
||||||
|
|
@ -5,71 +5,65 @@
|
||||||
|
|
||||||
class Config<Key extends keyof typeof extensionConfig> {
|
class Config<Key extends keyof typeof extensionConfig> {
|
||||||
key: Key;
|
key: Key;
|
||||||
value: (typeof extensionConfig)[Key]["fallback"];
|
value: any;
|
||||||
|
insync = false;
|
||||||
|
|
||||||
constructor(key: Key) {
|
constructor(key: Key) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = $state(extensionConfig[key].fallback);
|
this.value = $state((extensionConfig[key] as any).fallback);
|
||||||
this.load();
|
this.load();
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
console.log("RESYNC");
|
const value = $state.snapshot(this.value);
|
||||||
extensionConfig[key].setValue(this.value as any);
|
const timeout = setTimeout(() => {
|
||||||
|
if (!this.insync || this.value == value) return;
|
||||||
|
extensionConfig[this.key].setValue(value).catch((e) => {
|
||||||
|
console.error("Failed to write state back to storage", e);
|
||||||
|
});
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
const config = await extensionConfig[this.key].getValue();
|
const config = await extensionConfig[this.key].getValue().catch((e) => {
|
||||||
|
console.error("Failed to sync state from storage", e);
|
||||||
|
});
|
||||||
|
|
||||||
this.value = config;
|
this.value = config;
|
||||||
|
this.insync = true;
|
||||||
|
|
||||||
extensionConfig[this.key].watch((value) => {
|
(extensionConfig[this.key] as any).watch((value: any) => {
|
||||||
if (value != this.value) this.value = value;
|
if (value != this.value) this.value = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const config_ai_text = new Config("ai_text");
|
const config_ai_text = new Config("ai_text");
|
||||||
$inspect(config_ai_text.value);
|
|
||||||
|
|
||||||
const config_ai_image = new Config("ai_image");
|
const config_ai_image = new Config("ai_image");
|
||||||
$inspect(config_ai_image.value);
|
const domain_whitelist = new Config("domain_whitelist");
|
||||||
|
|
||||||
const config_ai = new Config("ai");
|
|
||||||
$inspect(config_ai.value);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="min-w-115">
|
<main class="min-w-120 max-w-120 w-120 min-h-50 max-h-130">
|
||||||
<div class="tabs tabs-box min-h-50 max-h-max">
|
<div class="tabs tabs-box rounded-none">
|
||||||
<div class="tab pr-4">🦊</div>
|
<div class="tab pr-4">🦊</div>
|
||||||
<input type="radio" name="cfgtabs0" class="tab" aria-label="General" checked={true} />
|
<input type="radio" name="cfgtabs0" class="tab" aria-label="General" checked={true} />
|
||||||
<div class="tab-content bg-base-100 border-base-300 p-2"></div>
|
<div class="tab-content bg-base-100 border-base-300 p-2"></div>
|
||||||
<input type="radio" name="cfgtabs0" class="tab" aria-label="AI detector" />
|
<input type="radio" name="cfgtabs0" class="tab" aria-label="AI text" />
|
||||||
<div class="tab-content bg-base-100 border-base-300 p-2">
|
<div class="tab-content bg-base-100 border-base-300 p-2">
|
||||||
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
<legend class="fieldset-legend">AI Text detection</legend>
|
<legend class="fieldset-legend">General</legend>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox checkbox-sm"
|
class="radio checked:radio-success radio-error"
|
||||||
bind:checked={config_ai_text.value.enabled}
|
bind:checked={config_ai_text.value.enabled}
|
||||||
/>
|
/>
|
||||||
Enabled
|
Enabled
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input type="checkbox" class="radio" bind:checked={config_ai_text.value.show_ai} />
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_text.value.lite}
|
|
||||||
/>
|
|
||||||
Lite version (faster, less accurate)
|
|
||||||
</label>
|
|
||||||
<label class="label">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_text.value.show_ai}
|
|
||||||
/>
|
|
||||||
Show "AI generated" warnings
|
Show "AI generated" warnings
|
||||||
<span
|
<span
|
||||||
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#ffc36d;color:#fff"
|
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#ffc36d;color:#fff"
|
||||||
|
|
@ -78,11 +72,7 @@
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input type="checkbox" class="radio" bind:checked={config_ai_text.value.show_human} />
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_text.value.show_human}
|
|
||||||
/>
|
|
||||||
Show "Human produced" labels
|
Show "Human produced" labels
|
||||||
<span
|
<span
|
||||||
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#8fe1a1;color:#fff"
|
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#8fe1a1;color:#fff"
|
||||||
|
|
@ -90,11 +80,47 @@
|
||||||
👨💼: 99%
|
👨💼: 99%
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label font-bold" for="textaimodelconf"> Report: </label>
|
</fieldset>
|
||||||
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
|
<legend class="fieldset-legend">Mode</legend>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
Any suspicion
|
|
||||||
<input
|
<input
|
||||||
id="textaimodelconf01"
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_text.mode"
|
||||||
|
value="lite"
|
||||||
|
bind:group={config_ai_text.value.mode}
|
||||||
|
/>
|
||||||
|
Lite (the fastest, low RAM usage)
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_text.mode"
|
||||||
|
value="mixed"
|
||||||
|
bind:group={config_ai_text.value.mode}
|
||||||
|
/>
|
||||||
|
Mixed (more accurate, pretty fast, highest RAM usage)
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_text.mode"
|
||||||
|
value="hard"
|
||||||
|
bind:group={config_ai_text.value.mode}
|
||||||
|
/>
|
||||||
|
Hard (most accurate, slow, medium RAM usage)
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
|
<legend class="fieldset-legend">Misc</legend>
|
||||||
|
<label class="label font-bold" for="config_ai_text-value-threshold">Show results: </label>
|
||||||
|
<label class="label">
|
||||||
|
Always
|
||||||
|
<input
|
||||||
|
id="config_ai_text-value-threshold"
|
||||||
type="range"
|
type="range"
|
||||||
class="range range-xs"
|
class="range range-xs"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
|
|
@ -105,30 +131,21 @@
|
||||||
When confident
|
When confident
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<input type="radio" name="cfgtabs0" class="tab" aria-label="AI images" />
|
||||||
|
<div class="tab-content bg-base-100 border-base-300 p-2">
|
||||||
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
<legend class="fieldset-legend">AI Image detection</legend>
|
<legend class="fieldset-legend">General</legend>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox checkbox-sm"
|
class="radio checked:radio-success radio-error"
|
||||||
bind:checked={config_ai_image.value.enabled}
|
bind:checked={config_ai_image.value.enabled}
|
||||||
/>
|
/>
|
||||||
Enabled
|
Enabled
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input type="checkbox" class="radio" bind:checked={config_ai_image.value.show_ai} />
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_image.value.lite}
|
|
||||||
/>
|
|
||||||
Lite version (faster, less accurate)
|
|
||||||
</label>
|
|
||||||
<label class="label">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_image.value.show_ai}
|
|
||||||
/>
|
|
||||||
Show "AI generated" warnings
|
Show "AI generated" warnings
|
||||||
<span
|
<span
|
||||||
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#ffc36d;color:#fff"
|
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#ffc36d;color:#fff"
|
||||||
|
|
@ -137,11 +154,7 @@
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<input
|
<input type="checkbox" class="radio" bind:checked={config_ai_image.value.show_human} />
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
bind:checked={config_ai_image.value.show_human}
|
|
||||||
/>
|
|
||||||
Show "Human produced" labels
|
Show "Human produced" labels
|
||||||
<span
|
<span
|
||||||
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#8fe1a1;color:#fff"
|
style="border-radius:4px;text-align:center;font-family:sans;font-size:10px;background-color:#8fe1a1;color:#fff"
|
||||||
|
|
@ -149,11 +162,47 @@
|
||||||
👨💼: 99%
|
👨💼: 99%
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="label font-bold" for="textaimodelconf"> Report: </label>
|
</fieldset>
|
||||||
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
|
<legend class="fieldset-legend">Mode</legend>
|
||||||
<label class="label">
|
<label class="label">
|
||||||
Any suspicion
|
|
||||||
<input
|
<input
|
||||||
id="textaimodelconf01"
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_image.mode"
|
||||||
|
value="lite"
|
||||||
|
bind:group={config_ai_image.value.mode}
|
||||||
|
/>
|
||||||
|
Lite (the fastest, low RAM usage)
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_image.mode"
|
||||||
|
value="mixed"
|
||||||
|
bind:group={config_ai_image.value.mode}
|
||||||
|
/>
|
||||||
|
Mixed (more accurate, pretty fast, highest RAM usage)
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
class="radio"
|
||||||
|
name="config_ai_image.mode"
|
||||||
|
value="hard"
|
||||||
|
bind:group={config_ai_image.value.mode}
|
||||||
|
/>
|
||||||
|
Hard (most accurate, slow, medium RAM usage)
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
|
<legend class="fieldset-legend">Misc</legend>
|
||||||
|
<label class="label font-bold" for="config_ai_image.threshold">Show results: </label>
|
||||||
|
<label class="label">
|
||||||
|
Always
|
||||||
|
<input
|
||||||
|
name="config_ai_image.threshold"
|
||||||
type="range"
|
type="range"
|
||||||
class="range range-xs"
|
class="range range-xs"
|
||||||
min="0.1"
|
min="0.1"
|
||||||
|
|
@ -164,29 +213,34 @@
|
||||||
When confident
|
When confident
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
<fieldset class="fieldset border-base-300 border rounded-box p-2">
|
||||||
<legend class="fieldset-legend"> Do not check on these sites: </legend>
|
<legend class="fieldset-legend"> Do not check on these sites: </legend>
|
||||||
<div class="overflow-y-auto max-h-30 w-full">
|
<div class="flex gap-2 flex-wrap overflow-y-auto max-h-30 w-full">
|
||||||
{#each Object.entries(config_ai.value.domain_blacklist) as domain (domain[0])}
|
{#each domain_whitelist.value as domain}
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
{domain[0]}
|
{domain}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<input type="radio" name="cfgtabs0" class="tab" aria-label="License, citations" />
|
<input type="radio" name="cfgtabs0" class="tab" aria-label="🛈" />
|
||||||
<div class="tab-content bg-base-100 border-base-300 p-2">
|
<div class="tab-content bg-base-100 border-base-300 p-2">
|
||||||
<p>
|
<p>
|
||||||
The source code of this extension is available <a
|
Firaid-moz webextension v{browser.runtime.getManifest().version}
|
||||||
class="link"
|
by Spushk 🦊
|
||||||
href={browser.runtime.getManifest().homepage_url}>here</a
|
</p>
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
The source code is available <a class="link" href={browser.runtime.getManifest().homepage_url}
|
||||||
|
>here</a
|
||||||
> under terms of Apache-2.0 license
|
> under terms of Apache-2.0 license
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<p>
|
<p>
|
||||||
The following people/organizations made this fun extention possible to create by training and
|
The following people/organizations made it possible to create this by training and providing
|
||||||
providing ML models on <a class="link" href="https://huggingface.co">huggingface</a>:
|
ML models on <a class="link" href="https://huggingface.co">huggingface</a>:
|
||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<div class="csl-bib-body">
|
<div class="csl-bib-body">
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,70 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "daisyui";
|
@plugin "daisyui";
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "spushk-dim";
|
||||||
|
default: false;
|
||||||
|
prefersdark: true;
|
||||||
|
color-scheme: "dark";
|
||||||
|
--color-base-100: #1f273e;
|
||||||
|
--color-base-200: #455474;
|
||||||
|
--color-base-300: #4f658a;
|
||||||
|
--color-base-content: oklch(96% 0.001 286.375);
|
||||||
|
--color-primary: #fe9735;
|
||||||
|
--color-primary-content: oklch(17.226% 0.028 139.549);
|
||||||
|
--color-secondary: oklch(83% 0.128 66.29);
|
||||||
|
--color-secondary-content: oklch(14.675% 0.033 35.353);
|
||||||
|
--color-accent: oklch(74.229% 0.133 311.379);
|
||||||
|
--color-accent-content: oklch(14.845% 0.026 311.379);
|
||||||
|
--color-neutral: oklch(24.731% 0.02 264.094);
|
||||||
|
--color-neutral-content: oklch(87% 0.006 286.286);
|
||||||
|
--color-info: oklch(86.078% 0.142 206.182);
|
||||||
|
--color-info-content: oklch(17.215% 0.028 206.182);
|
||||||
|
--color-success: oklch(86.171% 0.142 166.534);
|
||||||
|
--color-success-content: oklch(17.234% 0.028 166.534);
|
||||||
|
--color-warning: oklch(92% 0.12 95.746);
|
||||||
|
--color-warning-content: oklch(17.232% 0.028 94.818);
|
||||||
|
--color-error: oklch(89% 0.058 10.001);
|
||||||
|
--color-error-content: oklch(16.483% 0.019 33.756);
|
||||||
|
--radius-selector: 0.5rem;
|
||||||
|
--radius-field: 0.5rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.1875rem;
|
||||||
|
--size-field: 0.1875rem;
|
||||||
|
--border: 0.5px;
|
||||||
|
--depth: 0;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "spushk-pastel";
|
||||||
|
default: true;
|
||||||
|
prefersdark: false;
|
||||||
|
color-scheme: "light";
|
||||||
|
--color-base-100: oklch(100% 0 0);
|
||||||
|
--color-base-200: oklch(98.462% 0.001 247.838);
|
||||||
|
--color-base-300: oklch(92.462% 0.001 247.838);
|
||||||
|
--color-base-content: oklch(20% 0 0);
|
||||||
|
--color-primary: #fe9735;
|
||||||
|
--color-primary-content: oklch(14% 0 0);
|
||||||
|
--color-secondary: oklch(83% 0.128 66.29);
|
||||||
|
--color-secondary-content: oklch(14% 0 0);
|
||||||
|
--color-accent: #c792e9;
|
||||||
|
--color-accent-content: oklch(14% 0 0);
|
||||||
|
--color-neutral: #455474;
|
||||||
|
--color-neutral-content: oklch(98% 0.002 247.839);
|
||||||
|
--color-info: oklch(86% 0.127 207.078);
|
||||||
|
--color-info-content: oklch(25% 0.09 281.288);
|
||||||
|
--color-success: oklch(87% 0.15 154.449);
|
||||||
|
--color-success-content: oklch(26% 0.065 152.934);
|
||||||
|
--color-warning: oklch(79% 0.184 86.047);
|
||||||
|
--color-warning-content: oklch(28% 0.066 53.813);
|
||||||
|
--color-error: oklch(51% 0.222 16.935);
|
||||||
|
--color-error-content: oklch(94% 0.03 12.58);
|
||||||
|
--radius-selector: 0.5rem;
|
||||||
|
--radius-field: 0.5rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.1875rem;
|
||||||
|
--size-field: 0.1875rem;
|
||||||
|
--border: 0.5px;
|
||||||
|
--depth: 0;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { ExtensionConfigSchema } from "#imports";
|
|
||||||
import { defineExtensionStorage, ExtensionStorage } from "@webext-core/storage";
|
|
||||||
|
|
||||||
export interface ExtensionConfigAiText {
|
export interface ExtensionConfigAiText {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
lite: boolean;
|
mode: "lite" | "mixed" | "hard";
|
||||||
show_human: boolean;
|
show_human: boolean;
|
||||||
show_ai: boolean;
|
show_ai: boolean;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
|
|
@ -11,53 +8,30 @@ export interface ExtensionConfigAiText {
|
||||||
|
|
||||||
export interface ExtensionConfigAiImage {
|
export interface ExtensionConfigAiImage {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
lite: boolean;
|
mode: "lite" | "mixed" | "hard";
|
||||||
show_human: boolean;
|
show_human: boolean;
|
||||||
show_ai: boolean;
|
show_ai: boolean;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionConfigAi {
|
|
||||||
/// shall contain domain qualified name, separated by dots
|
|
||||||
/// indexed by domain name
|
|
||||||
/// e.g. {
|
|
||||||
/// "github.com": ["github", "com"]
|
|
||||||
/// }
|
|
||||||
domain_blacklist: {
|
|
||||||
[domain: string]: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// export interface ExtensionConfigSchema {
|
|
||||||
// version: string;
|
|
||||||
// ai_text: ExtensionConfigAiText;
|
|
||||||
// ai_image: ExtensionConfigAiImage;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//export const extensionConfig = defineExtensionStorage<ExtensionConfigSchema>(browser.storage.local);
|
|
||||||
|
|
||||||
export const extensionConfig = {
|
export const extensionConfig = {
|
||||||
ai: storage.defineItem<ExtensionConfigAi>("local:config:ai", {
|
domain_whitelist: storage.defineItem("local:config:domain_whitelist", {
|
||||||
fallback: {
|
fallback: [],
|
||||||
domain_blacklist: {},
|
init: () => ["asd.asd", "lkj.asd", "oiua.asd", "oi1u2e.aoisdu", "did0.asdiho"],
|
||||||
},
|
|
||||||
init: () => ({
|
|
||||||
domain_blacklist: {},
|
|
||||||
}),
|
|
||||||
version: 1,
|
version: 1,
|
||||||
migrations: {},
|
migrations: {},
|
||||||
}),
|
}),
|
||||||
ai_text: storage.defineItem<ExtensionConfigAiText>("local:config:ai_text", {
|
ai_text: storage.defineItem<ExtensionConfigAiText>("local:config:ai_text", {
|
||||||
fallback: {
|
fallback: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
lite: true,
|
mode: "lite",
|
||||||
show_ai: true,
|
show_ai: true,
|
||||||
show_human: false,
|
show_human: false,
|
||||||
threshold: 0.6,
|
threshold: 0.6,
|
||||||
},
|
},
|
||||||
init: () => ({
|
init: () => ({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
lite: true,
|
mode: "lite",
|
||||||
show_ai: true,
|
show_ai: true,
|
||||||
show_human: false,
|
show_human: false,
|
||||||
threshold: 0.6,
|
threshold: 0.6,
|
||||||
|
|
@ -67,39 +41,39 @@ export const extensionConfig = {
|
||||||
}),
|
}),
|
||||||
ai_image: storage.defineItem<ExtensionConfigAiImage>("local:config:ai_image", {
|
ai_image: storage.defineItem<ExtensionConfigAiImage>("local:config:ai_image", {
|
||||||
fallback: {
|
fallback: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
lite: true,
|
mode: "lite",
|
||||||
show_ai: true,
|
show_ai: true,
|
||||||
show_human: false,
|
show_human: false,
|
||||||
threshold: 0.6,
|
threshold: 0.6,
|
||||||
},
|
},
|
||||||
init: () => ({
|
init: () => ({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
lite: true,
|
mode: "lite",
|
||||||
show_ai: true,
|
show_ai: true,
|
||||||
show_human: true,
|
show_human: false,
|
||||||
threshold: 0.6,
|
threshold: 0.6,
|
||||||
}),
|
}),
|
||||||
version: 1,
|
version: 1,
|
||||||
migrations: {},
|
migrations: {},
|
||||||
}),
|
}),
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
export const EXTENSION_CONFIG: {
|
export const EXTENSION_CONFIG: {
|
||||||
ai: ExtensionConfigAi;
|
domain_whitelist: Set<string>;
|
||||||
ai_text: ExtensionConfigAiText;
|
ai_text: ExtensionConfigAiText;
|
||||||
ai_image: ExtensionConfigAiImage;
|
ai_image: ExtensionConfigAiImage;
|
||||||
} = {
|
} = {
|
||||||
ai: extensionConfig.ai.fallback,
|
domain_whitelist: new Set(extensionConfig.domain_whitelist.fallback),
|
||||||
ai_text: extensionConfig.ai_text.fallback,
|
ai_text: extensionConfig.ai_text.fallback,
|
||||||
ai_image: extensionConfig.ai_image.fallback,
|
ai_image: extensionConfig.ai_image.fallback,
|
||||||
};
|
};
|
||||||
|
|
||||||
extensionConfig.ai.getValue().then((config) => {
|
extensionConfig.domain_whitelist.getValue().then((config) => {
|
||||||
EXTENSION_CONFIG.ai = config;
|
EXTENSION_CONFIG.domain_whitelist = new Set(config);
|
||||||
|
|
||||||
extensionConfig.ai.watch((config) => {
|
extensionConfig.domain_whitelist.watch((config) => {
|
||||||
EXTENSION_CONFIG.ai = config;
|
EXTENSION_CONFIG.domain_whitelist = new Set(config);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -118,41 +92,3 @@ extensionConfig.ai_image.getValue().then((config) => {
|
||||||
EXTENSION_CONFIG.ai_image = config;
|
EXTENSION_CONFIG.ai_image = config;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// extensionConfig.getItem("version").then(async (v) => {
|
|
||||||
// const current_version = browser.runtime.getManifest().version;
|
|
||||||
// if (v != current_version) {
|
|
||||||
// switch (v) {
|
|
||||||
// default:
|
|
||||||
// await config_migrate_fresh();
|
|
||||||
// }
|
|
||||||
// await extensionConfig.setItem("version", current_version);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// async function config_migrate_fresh() {
|
|
||||||
// await extensionConfig.clear();
|
|
||||||
// await extensionConfig.setItem("ai_text", {
|
|
||||||
// enabled: true,
|
|
||||||
// lite: true,
|
|
||||||
// show_ai: true,
|
|
||||||
// show_human: false,
|
|
||||||
// threshold: 0.6,
|
|
||||||
// });
|
|
||||||
// await extensionConfig.setItem("ai_image", {
|
|
||||||
// enabled: true,
|
|
||||||
// lite: true,
|
|
||||||
// show_ai: true,
|
|
||||||
// show_human: false,
|
|
||||||
// threshold: 0.6,
|
|
||||||
// });
|
|
||||||
// await extensionConfig.setItem("fun_githief", {
|
|
||||||
// enabled: false,
|
|
||||||
// });
|
|
||||||
// await extensionConfig.setItem("fun_microslop", {
|
|
||||||
// enabled: false,
|
|
||||||
// });
|
|
||||||
// await extensionConfig.setItem("fun_huyandex", {
|
|
||||||
// enabled: false,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,8 @@
|
||||||
/// Extracts meaningful portions of text from DOM, yielding the top matched nodes
|
|
||||||
///
|
|
||||||
/// e.g.
|
|
||||||
///
|
|
||||||
/// ```html
|
|
||||||
/// <div>
|
|
||||||
/// my hair are NOT
|
|
||||||
/// <span>
|
|
||||||
/// blue!
|
|
||||||
/// </span>
|
|
||||||
/// </div>
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Shall yield a reference to the `div` with text "my hair are NOT blue!"
|
|
||||||
|
|
||||||
const IS_DEV = import.meta.env.MODE == "development";
|
const IS_DEV = import.meta.env.MODE == "development";
|
||||||
|
const console = ExtConsole("domtraverse");
|
||||||
|
|
||||||
|
export const TRAVERSE_BEHAVOUR_IGNORE_MAGIC = Math.floor(Math.random() * 1000000).toString();
|
||||||
|
export const TRAVERSE_BEHAVOUR_ATTR = "data-firaid-moz-traverse-behavour";
|
||||||
|
|
||||||
const TRAVERSE_STACK_BANS: Set<string | null | undefined> = new Set([
|
const TRAVERSE_STACK_BANS: Set<string | null | undefined> = new Set([
|
||||||
"IMG",
|
"IMG",
|
||||||
|
|
@ -50,28 +39,56 @@ const TRAVERSE_ALLOWED_ELEMENT_TAGS: Set<string | null | undefined> = new Set([
|
||||||
"I",
|
"I",
|
||||||
"STRONG",
|
"STRONG",
|
||||||
"EM",
|
"EM",
|
||||||
|
"DEL",
|
||||||
]);
|
]);
|
||||||
|
const KNOWN_STACK_ROOTS: WeakMap<
|
||||||
|
Node,
|
||||||
|
{
|
||||||
|
csum: number;
|
||||||
|
}
|
||||||
|
> = new WeakMap();
|
||||||
|
|
||||||
export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
||||||
acceptNode: (node) => {
|
acceptNode: (node) => {
|
||||||
// sanity check
|
// sanity check
|
||||||
if (!node.isConnected) {
|
if (node.parentNode?.nodeType != Node.ELEMENT_NODE || !node.parentElement) {
|
||||||
if (IS_DEV) {
|
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!IS_CONNECTED");
|
|
||||||
}
|
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
if (node.parentNode?.nodeType != Node.ELEMENT_NODE) {
|
if (!node.isConnected) {
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!ELEMENT_NODE");
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:disconnected");
|
||||||
}
|
}
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter elements
|
// filter elements
|
||||||
if (!TRAVERSE_ALLOWED_ELEMENT_TAGS.has(node.parentElement?.tagName)) {
|
if (!TRAVERSE_ALLOWED_ELEMENT_TAGS.has(node.parentElement.tagName)) {
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-!ALLOWED_TAG");
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:tag_not_allowed");
|
||||||
|
}
|
||||||
|
return NodeFilter.FILTER_REJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignored elements
|
||||||
|
if (node.parentElement.getAttribute(TRAVERSE_BEHAVOUR_ATTR) == TRAVERSE_BEHAVOUR_IGNORE_MAGIC) {
|
||||||
|
if (IS_DEV) {
|
||||||
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:behavour_magic");
|
||||||
|
}
|
||||||
|
return NodeFilter.FILTER_REJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check visibility (my best effort, still mistakes sometimes)
|
||||||
|
if (
|
||||||
|
!node.parentElement.checkVisibility({
|
||||||
|
visibilityProperty: true,
|
||||||
|
}) ||
|
||||||
|
!node.parentElement.offsetParent ||
|
||||||
|
!node.parentElement.getBoundingClientRect().height
|
||||||
|
) {
|
||||||
|
// check visibility
|
||||||
|
if (IS_DEV) {
|
||||||
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:invisible");
|
||||||
}
|
}
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
|
|
@ -80,17 +97,14 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
// Ignore anything inside of banned elements.
|
// Ignore anything inside of banned elements.
|
||||||
//
|
//
|
||||||
// For now I'll just limit the depth to 16
|
// For now I'll just limit the depth to 16
|
||||||
let checking = node.parentElement;
|
let checking: Element | null = node.parentElement;
|
||||||
for (let i = 0; i < 16; i++) {
|
for (let i = 0; i < 16; i++) {
|
||||||
if (!checking || checking.tagName == "BODY") {
|
if (!checking || checking.tagName == "BODY") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (TRAVERSE_STACK_BANS.has(checking.tagName)) {
|
if (TRAVERSE_STACK_BANS.has(checking.tagName)) {
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
node.parentElement?.setAttribute(
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "REJECT:banned_ancestor");
|
||||||
"data-firaid-moz-traverse",
|
|
||||||
"REJECT-BANNED_ANCESTOR",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
|
|
@ -98,16 +112,16 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop with nonsense content
|
// drop with nonsense content
|
||||||
if ((node.textContent?.trim()?.length || 0) < 8) {
|
// if (!node.textContent?.trim()) {
|
||||||
if (IS_DEV) {
|
// if (IS_DEV) {
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-SHORT_TEXT");
|
// node.parentElement?.setAttribute("data-firaid-moz-traverse", "REJECT-NO_TEXT");
|
||||||
}
|
// }
|
||||||
return NodeFilter.FILTER_REJECT;
|
// return NodeFilter.FILTER_REJECT;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// finally good ones
|
// finally good ones
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-traverse", "ACCEPT");
|
node.parentElement.setAttribute("data-firaid-moz-traverse", "ACCEPT");
|
||||||
}
|
}
|
||||||
return NodeFilter.FILTER_ACCEPT;
|
return NodeFilter.FILTER_ACCEPT;
|
||||||
},
|
},
|
||||||
|
|
@ -127,25 +141,15 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
rootable_parent = rootable_parent.parentElement;
|
rootable_parent = rootable_parent.parentElement;
|
||||||
}
|
}
|
||||||
if (!rootable_parent) {
|
if (!rootable_parent) {
|
||||||
if (IS_DEV) {
|
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "REJECTED-!ROOTABLE_PARENT");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeText = node.textContent?.trim();
|
let nodeText = node.textContent?.trim();
|
||||||
if (!nodeText) {
|
if (!nodeText) {
|
||||||
if (IS_DEV) {
|
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "REJECTED-!TEXT");
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let nodeTextWords = count_words_naive(nodeText);
|
let nodeTextWords = count_words_naive(nodeText);
|
||||||
|
|
||||||
if (IS_DEV) {
|
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "VISITED");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stack) {
|
if (!stack) {
|
||||||
stack = {
|
stack = {
|
||||||
root: rootable_parent,
|
root: rootable_parent,
|
||||||
|
|
@ -155,20 +159,19 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
words: nodeTextWords,
|
words: nodeTextWords,
|
||||||
};
|
};
|
||||||
} else if (stack.root.contains(rootable_parent)) {
|
} else if (stack.root.contains(rootable_parent)) {
|
||||||
if (IS_DEV) {
|
|
||||||
node.parentElement?.setAttribute("data-firaid-moz-stack", "MEMBER");
|
|
||||||
}
|
|
||||||
stack.text += " " + nodeText;
|
stack.text += " " + nodeText;
|
||||||
stack.words += nodeTextWords;
|
stack.words += nodeTextWords;
|
||||||
stack.last_node = node;
|
stack.last_node = node;
|
||||||
} else {
|
} else {
|
||||||
if (stack.words >= hard_min_words) {
|
if (stack.words >= hard_min_words) {
|
||||||
|
const known = KNOWN_STACK_ROOTS.get(stack.root);
|
||||||
|
const csum = text_csum(stack.text);
|
||||||
|
if (csum !== known?.csum) {
|
||||||
|
KNOWN_STACK_ROOTS.set(stack.root, { csum, ...known });
|
||||||
yield stack;
|
yield stack;
|
||||||
if (IS_DEV) {
|
} else {
|
||||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "ROOT");
|
console.debug("Refuse to yield stack as matching csum exists");
|
||||||
}
|
}
|
||||||
} else if (IS_DEV) {
|
|
||||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "SKIP-SHORT_TEXT");
|
|
||||||
}
|
}
|
||||||
stack = {
|
stack = {
|
||||||
root: rootable_parent,
|
root: rootable_parent,
|
||||||
|
|
@ -180,13 +183,16 @@ export function* traverse_text(soft_max_words: number, hard_min_words: number) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stack.words > soft_max_words) {
|
if (stack && stack.words > soft_max_words) {
|
||||||
if (stack.words >= hard_min_words) {
|
if (stack.words >= hard_min_words) {
|
||||||
if (IS_DEV) {
|
const known = KNOWN_STACK_ROOTS.get(stack.root);
|
||||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "ROOT");
|
const csum = text_csum(stack.text);
|
||||||
|
if (csum !== known?.csum) {
|
||||||
|
KNOWN_STACK_ROOTS.set(stack.root, { csum, ...known });
|
||||||
|
yield stack;
|
||||||
|
} else {
|
||||||
|
console.debug("Refuse to yield stack as matching csum exists");
|
||||||
}
|
}
|
||||||
} else if (IS_DEV) {
|
|
||||||
stack.root.parentElement?.setAttribute("data-firaid-moz-stack", "SKIP-SHORT_TEXT");
|
|
||||||
}
|
}
|
||||||
stack = null;
|
stack = null;
|
||||||
}
|
}
|
||||||
|
|
@ -209,3 +215,13 @@ function count_words_naive(text: string): number {
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function text_csum(text: string): number {
|
||||||
|
let csum = 5381;
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
csum += (csum << 5) + text.charCodeAt(i);
|
||||||
|
csum &= csum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return csum >>> 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue