A modular, Webpack-powered TypeScript version of the WTR Lab Term Replacer userscript.
// ==UserScript==
// @name WTR Lab Term Replacer
// @description A modular, Webpack-powered TypeScript version of the WTR Lab Term Replacer userscript.
// @version 5.7.0
// @author MasuRii
// @homepage https://github.com/MasuRii/wtr-lab-term-replacer-webpack#readme
// @supportURL https://github.com/MasuRii/wtr-lab-term-replacer-webpack/issues
// @match https://wtr-lab.com/en/novel/*/*/*
// @connect fonts.googleapis.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @icon https://www.google.com/s2/favicons?sz=64&domain=wtr-lab.com
// @license MIT
// @namespace https://github.com/MasuRii/wtr-lab-term-replacer-webpack
// @run-at document-idle
// ==/UserScript==
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ 387
(module, __unused_webpack_exports, __webpack_require__) {
// config/versions.js
// Centralized version management for WTR Lab Term Replacer
// Environment variable overrides with fallbacks
const envVersion = ({ env: {} }).env.WTR_VERSION || ({ env: {} }).env.APP_VERSION;
const buildEnv = ({ env: {} }).env.WTR_BUILD_ENV || ({ env: {} }).env.BUILD_ENV || "production";
const buildDate = ({ env: {} }).env.WTR_BUILD_DATE || ({ env: {} }).env.BUILD_DATE || new Date().toISOString().split("T")[0];
// Derive base version from package.json at runtime so only package.json is edited manually
const pkg = __webpack_require__(330);
const BASE_VERSION = pkg.version;
const VERSION_INFO = {
SEMANTIC: envVersion || BASE_VERSION, // Semantic version
DISPLAY: `v${envVersion || BASE_VERSION}`, // Display version
BUILD_ENV: buildEnv || "production", // Build environment
BUILD_DATE: buildDate, // Build date
GREASYFORK: envVersion || BASE_VERSION, // GreasyFork version
NPM: envVersion || BASE_VERSION, // NPM version
BADGE: envVersion || BASE_VERSION, // Badge version
CHANGELOG: envVersion || BASE_VERSION, // Changelog version
};
// Export version info and utility functions
module.exports = {
VERSION_INFO,
// Utility functions
getVersion: (type = "semantic") => {
switch (type.toLowerCase()) {
case "semantic":
case "semver":
return VERSION_INFO.SEMANTIC;
case "display":
return VERSION_INFO.DISPLAY;
case "build":
return `${VERSION_INFO.SEMANTIC}-${VERSION_INFO.BUILD_ENV}`;
case "dev":
return `${VERSION_INFO.SEMANTIC}-dev.${Date.now()}`;
default:
return VERSION_INFO.SEMANTIC;
}
},
getBuildTime: () => new Date().toISOString(),
getBuildDate: () => VERSION_INFO.BUILD_DATE,
isProduction: () => VERSION_INFO.BUILD_ENV === "production",
isDevelopment: () => VERSION_INFO.BUILD_ENV === "development",
getDisplayVersion: () => VERSION_INFO.DISPLAY
};
/***/ },
/***/ 333
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Ft: () => (/* binding */ SETTINGS_STORAGE_KEY_PREFIX),
/* harmony export */ Qp: () => (/* binding */ CURRENT_LOCATION_KEY),
/* harmony export */ fW: () => (/* binding */ TERMS_STORAGE_KEY_PREFIX),
/* harmony export */ re: () => (/* binding */ ITEMS_PER_PAGE),
/* harmony export */ sI: () => (/* binding */ GLOBAL_SETTINGS_KEY),
/* harmony export */ tA: () => (/* binding */ CHAPTER_BODY_SELECTOR)
/* harmony export */ });
// Configuration constants and selectors
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const TERMS_STORAGE_KEY_PREFIX = "wtr_lab_terms_";
const SETTINGS_STORAGE_KEY_PREFIX = "wtr_lab_settings_";
const GLOBAL_SETTINGS_KEY = "wtr_lab_global_settings";
const CURRENT_LOCATION_KEY = "wtr_lab_term_list_location";
const ITEMS_PER_PAGE = 100;
/***/ },
/***/ 201
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Cs: () => (/* binding */ updateDupModeAfterChange),
/* harmony export */ DP: () => (/* binding */ changeDupGroup),
/* harmony export */ bj: () => (/* binding */ exitDupMode),
/* harmony export */ r_: () => (/* binding */ computeDupGroups)
/* harmony export */ });
/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(654);
/* harmony import */ var _ui__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(141);
// Duplicate detection logic for WTR Lab Term Replacer
/**
* Normalizes a term's original text into an array of its core components.
* @param {object} term - The term object.
* @returns {string[]} An array of normalized string components.
*/
function getNormalizedTermComponents(term) {
const components = [];
const original = term.original;
const isSimpleAlternation = term.isRegex && !/[\\^$.*+?()[\]{}()]/.test(original);
if (isSimpleAlternation) {
components.push(...original.split("|"));
}
else {
components.push(original);
}
return components
.map((comp) => {
let normalized = comp.trim().replace(/\s+/g, " ");
if (!term.caseSensitive) {
normalized = normalized.toLowerCase();
}
return normalized;
})
.filter((comp) => comp.length > 0);
}
/**
* Computes duplicate groups from a given array of terms.
* @param {object[]} termsToScan - The array of terms to check for duplicates.
*/
function computeDupGroups(termsToScan) {
const componentMap = new Map();
const replacementDupGroups = new Map();
const allDupGroups = new Map();
termsToScan.forEach((term) => {
const components = getNormalizedTermComponents(term);
if (components.length > 1) {
const uniqueComponents = new Set(components);
if (uniqueComponents.size < components.length) {
const originalTextSnippet = term.original.length > 50 ? term.original.substring(0, 47) + "..." : term.original;
const key = `Internal duplicate in: "${originalTextSnippet}"`;
allDupGroups.set(key, [term]);
}
}
components.forEach((comp) => {
if (!componentMap.has(comp)) {
componentMap.set(comp, []);
}
const group = componentMap.get(comp);
if (!group.some((t) => t.id === term.id)) {
group.push(term);
}
});
if (term.replacement) {
const key = term.replacement;
if (!replacementDupGroups.has(key)) {
replacementDupGroups.set(key, []);
}
replacementDupGroups.get(key).push(term);
}
});
for (const [component, group] of componentMap.entries()) {
if (group.length > 1) {
allDupGroups.set(`Original component: "${component}"`, group);
}
}
for (const [key, group] of replacementDupGroups.entries()) {
if (group.length > 1) {
const displayKey = `Replacement: "${key}"`;
if (allDupGroups.has(displayKey)) {
const existingGroup = allDupGroups.get(displayKey);
const newTerms = group.filter((t) => !existingGroup.some((et) => et.id === t.id));
allDupGroups.set(displayKey, [...existingGroup, ...newTerms]);
}
else {
allDupGroups.set(displayKey, group);
}
}
}
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupGroups = allDupGroups;
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys = Array.from(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupGroups.keys()).sort();
}
function exitDupMode() {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.isDupMode = false;
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex = 0;
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupGroups = new Map();
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys = [];
(0,_ui__WEBPACK_IMPORTED_MODULE_1__/* .renderTermList */ .FP)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue);
}
function changeDupGroup(delta) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex = Math.max(0, Math.min(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length - 1, _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex + delta));
(0,_ui__WEBPACK_IMPORTED_MODULE_1__/* .renderTermList */ .FP)();
}
function updateDupModeAfterChange() {
computeDupGroups(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms); // Use the current in-memory `state.terms` array which has just been updated
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length === 0) {
alert("All duplicates resolved.");
exitDupMode();
return;
}
const oldKey = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys[_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex];
const newIndex = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.indexOf(oldKey);
if (newIndex !== -1) {
const group = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupGroups.get(oldKey);
if (group && (group.length > 1 || oldKey.startsWith("Internal duplicate"))) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex = newIndex;
}
else {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex = Math.min(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex, _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length - 1);
}
}
else {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex = Math.min(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex, _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length - 1);
}
(0,_ui__WEBPACK_IMPORTED_MODULE_1__/* .renderTermList */ .FP)();
}
/***/ },
/***/ 9
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ performReplacements: () => (/* binding */ performReplacements),
/* harmony export */ revertAllReplacements: () => (/* binding */ revertAllReplacements)
/* harmony export */ });
/* unused harmony exports executeReplacementLogic, traverseAndRevert */
/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(654);
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(158);
/* harmony import */ var _ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(141);
/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333);
// Core replacement engine for WTR Lab Term Replacer
async function performReplacements(targetElement) {
if (!targetElement) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: performReplacements called with null target element");
return;
}
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Starting performReplacements");
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(true);
await new Promise((resolve) => setTimeout(resolve, 10));
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings.isDisabled || _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.length === 0) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Skipping replacements - disabled: ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings.isDisabled}, terms: ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.length}`);
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(false);
return;
}
try {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Found ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.length} terms to process, beginning replacement with retry mechanism`);
// Enhanced replacement with error handling and retry
await performReplacementsWithRetry(targetElement, 3);
}
catch (error) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Failed to perform replacements after retries:`, error);
console.error("WTR Term Replacer: Replacement failed, but original content preserved");
}
finally {
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(false);
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: performReplacements completed");
}
}
async function performReplacementsWithRetry(targetElement, maxRetries) {
let lastError = null;
let elementStabilityCounter = 0;
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Starting replacement process with ${maxRetries} retries`);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Replacement attempt ${attempt}/${maxRetries}`);
// Enhanced element stability validation
if (!validateElementStability(targetElement, elementStabilityCounter)) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Element stability check failed, attempt ${elementStabilityCounter}`);
// Re-acquire element reference for better stability
const chapterId = (0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .getChapterIdFromUrl */ .Ug)(window.location.href);
if (chapterId) {
const chapterSelector = `#${chapterId} ${_config__WEBPACK_IMPORTED_MODULE_3__/* .CHAPTER_BODY_SELECTOR */ .tA}`;
targetElement = document.querySelector(chapterSelector);
if (!targetElement) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Unable to re-acquire target element for chapter ${chapterId}`);
throw new Error("Unable to re-acquire target element");
}
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Re-acquired target element for chapter ${chapterId}`);
elementStabilityCounter++;
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Cannot determine chapter ID for element recovery");
throw new Error("Cannot determine chapter ID for element recovery");
}
}
// Additional DOM validation before proceeding
if (!document.contains(targetElement) || !targetElement.parentNode) {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Target element validation failed - not in stable DOM");
throw new Error("Target element not in stable DOM");
}
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Element validation passed, executing replacement logic");
// Perform the actual replacement logic
await executeReplacementLogic(targetElement);
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Replacement attempt ${attempt} successful`);
return; // Success, exit retry loop
}
catch (error) {
lastError = error;
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Replacement attempt ${attempt} failed:`, error.message);
if (attempt < maxRetries) {
// Progressive backoff with stability checks
const delay = Math.min(100 * Math.pow(2, attempt - 1), 2000);
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
// Pre-retry stability check
if (error.message && error.message.includes("DOM")) {
elementStabilityCounter++;
}
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: All ${maxRetries} attempts failed, giving up`);
}
}
}
// All retries failed
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: All replacement attempts failed, throwing error: ${lastError?.message || "Unknown error"}`);
throw lastError || new Error("Unknown replacement error");
}
// Enhanced element stability validation
function validateElementStability(element, stabilityCounter) {
if (!element || !document.contains(element)) {
return false;
}
// Check if element is visible and attached to DOM
const rect = element.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return false;
}
// Check parent chain is stable
let parent = element.parentNode;
let stabilityCheckCount = 0;
while (parent && stabilityCheckCount < 10) {
if (!document.contains(parent)) {
return false;
}
parent = parent.parentNode;
stabilityCheckCount++;
}
// Allow limited DOM recreation (stabilityCounter < 3)
return stabilityCounter < 3;
}
async function executeReplacementLogic(targetElement) {
// Validate target element state before processing
if (!targetElement || targetElement.nodeType !== Node.ELEMENT_NODE) {
throw new Error("Invalid target element");
}
// Additional DOM stability validation
if (!validateElementStability(targetElement, 0)) {
throw new Error("Target element DOM stability check failed");
}
// Check if element has meaningful content to process
const textContent = targetElement.textContent?.trim() || "";
if (textContent.length === 0) {
const chapterId = (0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .getChapterIdFromUrl */ .Ug)(window.location.href) || "unknown";
const contentLength = targetElement.textContent?.length || 0;
(0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Target element has no text content in chapter ${chapterId} (${contentLength} chars), skipping`);
return;
}
// Collect all text nodes and aggregate into a single string.
const walker = document.createTreeWalker(targetElement, NodeFilter.SHOW_TEXT);
const textNodes = [];
let node;
while ((node = walker.nextNode())) {
if (!node.parentElement.closest(".wtr-replacer-ui, script, style")) {
textNodes.push(node);
}
}
const nodeValues = new Map();
const nodeMap = [];
let fullText = "";
let currentPos = 0;
textNodes.forEach((n) => {
if (!_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.originalTextNodes.has(n)) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.originalTextNodes.set(n, n.nodeValue);
}
const originalValue = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.originalTextNodes.get(n);
nodeValues.set(n, originalValue);
if (originalValue.length > 0) {
nodeMap.push({ node: n, start: currentPos, end: currentPos + originalValue.length });
}
fullText += originalValue;
currentPos += originalValue.length;
});
if (!fullText.trim()) {
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(false);
return;
}
// Categorize terms
const simple_cs_partial = new Map();
const simple_cs_whole = new Map();
const simple_ci_partial = new Map();
const simple_ci_whole = new Map();
const regex_terms = [];
for (const term of _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms) {
if (!term.original) {
continue;
}
if (term.isRegex) {
try {
const flags = term.caseSensitive ? "g" : "gi";
regex_terms.push({
pattern: new RegExp(term.original, flags),
replacement: term.replacement,
});
}
catch (e) {
console.error(`Skipping invalid regex for term "${term.original}":`, e);
}
}
else {
const key = term.caseSensitive ? term.original : term.original.toLowerCase();
const value = term.replacement;
if (term.caseSensitive) {
if (term.wholeWord) {
simple_cs_whole.set(key, value);
}
else {
simple_cs_partial.set(key, value);
}
}
else {
if (term.wholeWord) {
simple_ci_whole.set(key, value);
}
else {
simple_ci_partial.set(key, value);
}
}
}
}
// Compile categorized terms into combined patterns.
const compiledTerms = [...regex_terms];
const addSimpleGroup = (map, flags, wholeWord, caseSensitive) => {
if (map.size > 0) {
const sortedKeys = [...map.keys()].sort((a, b) => b.length - a.length);
const patterns = sortedKeys.map((k) => {
const escaped = (0,_utils__WEBPACK_IMPORTED_MODULE_1__/* .escapeRegExp */ .Nt)(k);
return wholeWord ? `\\b${escaped}\\b` : escaped;
});
const combined = patterns.join("|");
compiledTerms.push({
pattern: new RegExp(combined, flags),
replacement_map: map,
is_simple: true,
case_sensitive: caseSensitive,
});
}
};
addSimpleGroup(simple_cs_partial, "g", false, true);
addSimpleGroup(simple_cs_whole, "g", true, true);
addSimpleGroup(simple_ci_partial, "gi", false, false);
addSimpleGroup(simple_ci_whole, "gi", true, false);
// Find ALL possible matches from all compiled terms.
const allMatches = [];
for (const comp of compiledTerms) {
for (const match of fullText.matchAll(comp.pattern)) {
if (match.index === match.index + match[0].length) {
continue;
} // Skip zero-length matches
let replacementText;
if (comp.is_simple) {
const key = comp.case_sensitive ? match[0] : match[0].toLowerCase();
replacementText = comp.replacement_map.get(key);
}
else {
replacementText = comp.replacement;
}
if (replacementText !== undefined) {
allMatches.push({
start: match.index,
end: match.index + match[0].length,
replacement: replacementText,
});
}
}
}
// Resolve overlaps: Sort by start index, then by end index descending (longest match wins).
allMatches.sort((a, b) => {
if (a.start !== b.start) {
return a.start - b.start;
}
return b.end - a.end;
});
// Select the non-overlapping "winning" matches.
const winningMatches = [];
let lastEnd = -1;
for (const match of allMatches) {
if (match.start >= lastEnd) {
winningMatches.push(match);
lastEnd = match.end;
}
}
// Apply winning matches to the nodeValues map, from last to first.
for (const match of winningMatches.reverse()) {
const { start: matchStart, end: matchEnd, replacement: replacementString } = match;
const affectedNodesInfo = [];
for (const info of nodeMap) {
if (info.start < matchEnd && info.end > matchStart) {
affectedNodesInfo.push(info);
}
}
if (affectedNodesInfo.length === 0) {
continue;
}
const firstNodeInfo = affectedNodesInfo[0];
const lastNodeInfo = affectedNodesInfo[affectedNodesInfo.length - 1];
const startNode = firstNodeInfo.node;
const lastNode = lastNodeInfo.node;
const startOffset = matchStart - firstNodeInfo.start;
const endOffset = matchEnd - lastNodeInfo.start;
if (startNode === lastNode) {
const currentVal = nodeValues.get(startNode);
nodeValues.set(startNode, currentVal.substring(0, startOffset) + replacementString + currentVal.substring(endOffset));
}
else {
const lastVal = nodeValues.get(lastNode);
nodeValues.set(lastNode, lastVal.substring(endOffset));
for (let i = 1; i < affectedNodesInfo.length - 1; i++) {
nodeValues.set(affectedNodesInfo[i].node, "");
}
const firstVal = nodeValues.get(startNode);
nodeValues.set(startNode, firstVal.substring(0, startOffset) + replacementString);
}
}
// After all processing, apply the final values to the DOM.
for (const n of textNodes) {
const finalValue = nodeValues.get(n);
if (n.nodeValue !== finalValue) {
n.nodeValue = finalValue;
}
}
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(false);
}
function traverseAndRevert(node) {
if (node.nodeType === Node.TEXT_NODE) {
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.originalTextNodes.has(node)) {
node.nodeValue = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.originalTextNodes.get(node);
}
return;
}
if (node.nodeType === Node.ELEMENT_NODE &&
node.tagName.toLowerCase() !== "script" &&
node.tagName.toLowerCase() !== "style") {
if (node.classList.contains("wtr-replacer-ui")) {
return;
}
for (const child of node.childNodes) {
traverseAndRevert(child);
}
}
}
async function revertAllReplacements(targetElement) {
if (!targetElement) {
return;
}
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(true);
await new Promise((resolve) => setTimeout(resolve, 10));
traverseAndRevert(targetElement);
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .showProcessingIndicator */ .gn)(false);
}
/***/ },
/***/ 359
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
IY: () => (/* binding */ addTermProgrammatically),
nS: () => (/* binding */ clearDiscoveryFormState),
mA: () => (/* binding */ enhanceWtrTermPopovers),
az: () => (/* binding */ handleAddTermFromSelection),
Jm: () => (/* binding */ handleDeleteSelected),
ts: () => (/* binding */ handleDisableToggle),
ym: () => (/* binding */ handleExportAll),
ow: () => (/* binding */ handleExportCombined),
b7: () => (/* binding */ handleExportNovel),
kF: () => (/* binding */ handleFileImport),
y$: () => (/* binding */ handleFindDuplicates),
VM: () => (/* binding */ handleListInteraction),
Zw: () => (/* binding */ handleRefreshSuggestionsClick),
JB: () => (/* binding */ handleReplacementSuggestionClick),
l9: () => (/* binding */ handleReplacementSuggestionInput),
s7: () => (/* binding */ handleSaveTerm),
RX: () => (/* binding */ handleSearch),
U8: () => (/* binding */ handleSuggestionTargetFocus),
Qk: () => (/* binding */ handleTabSwitch),
Me: () => (/* binding */ handleTextSelection),
cD: () => (/* binding */ handleWtrPopoverAddTermClick),
Xq: () => (/* binding */ handleWtrTextPatchClick),
X: () => (/* binding */ hideUIPanel),
AO: () => (/* binding */ normalizeOriginalRegexField),
s3: () => (/* binding */ restoreTermListLocation),
R6: () => (/* reexport */ storage.saveTermListLocation),
o6: () => (/* binding */ toggleLogging),
fA: () => (/* binding */ validateRegexSilent)
});
// UNUSED EXPORTS: downloadJSON, setSearchFieldValue, validateRegex
// EXTERNAL MODULE: ./src/modules/state.ts
var state = __webpack_require__(654);
// EXTERNAL MODULE: ./src/modules/storage.ts
var storage = __webpack_require__(694);
// EXTERNAL MODULE: ./src/modules/ui.ts
var ui = __webpack_require__(141);
// EXTERNAL MODULE: ./src/modules/observer.ts
var observer = __webpack_require__(405);
// EXTERNAL MODULE: ./src/modules/duplicates.ts
var duplicates = __webpack_require__(201);
// EXTERNAL MODULE: ./src/modules/utils.ts
var utils = __webpack_require__(158);
// EXTERNAL MODULE: ./src/modules/engine.ts
var engine = __webpack_require__(9);
;// ./src/modules/termDiscoveryHelpers.ts
const MAX_TERM_LENGTH = 80;
const MAX_REPLACEMENT_LENGTH = 120;
const MAX_RESULTS = 100;
const CONTROL_CHARS_PATTERN = /[\u0000-\u001f\u007f]/g;
const HTML_TAG_PATTERN = /<[^>]*>/g;
const HTML_ENTITY_PATTERN = /&(?:nbsp|amp|lt|gt|quot|#39);/gi;
const SAFE_IDENTIFIER_PATTERN = /^[A-Za-z0-9_.:-]{1,80}$/;
const WTR_SOURCE_ID_PREFIX = "id.";
const COMMON_WORDS = new Set([
"A",
"An",
"And",
"As",
"At",
"But",
"By",
"For",
"From",
"He",
"Her",
"His",
"I",
"In",
"Into",
"It",
"Of",
"On",
"Or",
"She",
"The",
"They",
"This",
"To",
"Was",
"With",
]);
function sanitizeApiText(value, maxLength = MAX_TERM_LENGTH) {
if (typeof value !== "string" && typeof value !== "number") {
return null;
}
const normalized = String(value)
.replace(CONTROL_CHARS_PATTERN, " ")
.replace(/\s+/g, " ")
.trim();
if (!normalized) {
return null;
}
return normalized.slice(0, maxLength);
}
function stripHtml(value) {
return value
.replace(HTML_TAG_PATTERN, " ")
.replace(HTML_ENTITY_PATTERN, (entity) => {
const lower = entity.toLowerCase();
if (lower === " ") {
return " ";
}
if (lower === "&") {
return "&";
}
if (lower === "<") {
return "<";
}
if (lower === ">") {
return ">";
}
if (lower === """) {
return '"';
}
return "'";
});
}
function sanitizeIdentifier(value) {
const sanitized = sanitizeApiText(value, 80);
if (!sanitized || !SAFE_IDENTIFIER_PATTERN.test(sanitized)) {
return undefined;
}
return sanitized;
}
function sanitizePreferenceHash(value) {
return sanitizeApiText(value, MAX_TERM_LENGTH) || undefined;
}
function sanitizeLang(value, fallback = "en") {
const sanitized = sanitizeApiText(value, 12);
if (sanitized && /^[a-z]{2}(?:-[A-Z]{2})?$/.test(sanitized)) {
return sanitized;
}
return fallback;
}
function parsePositiveInteger(value, pattern = /^(\d+)$/) {
const sanitized = sanitizeApiText(value, 80);
const match = sanitized?.match(pattern);
if (!match) {
return null;
}
const parsed = Number(match[1]);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
}
function sanitizeTranslateService(value) {
const sanitized = sanitizeApiText(value, 20);
return sanitized && /^[a-z][a-z0-9_-]{0,19}$/i.test(sanitized) ? sanitized : "ai";
}
function buildReaderGetPayload(context, translateService = "ai") {
const rawId = parsePositiveInteger(context.rawId);
const chapterNoSource = context.chapterNo ?? context.chapterSlug;
const chapterNo = parsePositiveInteger(chapterNoSource, /^(?:chapter-)?(\d+)$/);
if (!rawId || !chapterNo) {
return null;
}
const payload = {
translate: sanitizeTranslateService(translateService),
language: sanitizeLang(context.lang),
raw_id: rawId,
chapter_no: chapterNo,
retry: false,
force_retry: false,
};
const chapterId = parsePositiveInteger(context.chapterId);
if (chapterId) {
payload.chapter_id = chapterId;
}
return payload;
}
function firstTextValue(entry, keys, maxLength = MAX_TERM_LENGTH) {
for (const fieldName of keys) {
const value = sanitizeApiText(entry[fieldName], maxLength);
if (value) {
return value;
}
}
return null;
}
function firstNumberValue(entry, keys) {
for (const fieldName of keys) {
const value = entry[fieldName];
if (typeof value === "number" && Number.isFinite(value)) {
return Math.max(0, value);
}
if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
return Math.max(0, Number(value));
}
}
return 0;
}
function getArrayPayload(payload) {
if (Array.isArray(payload)) {
return payload;
}
if (!payload || typeof payload !== "object") {
return [];
}
const objectPayload = payload;
for (const fieldName of ["data", "terms", "items", "results", "preferences", "sources", "glossaries"]) {
const value = objectPayload[fieldName];
if (Array.isArray(value)) {
return value;
}
if (value && typeof value === "object") {
const nested = getArrayPayload(value);
if (nested.length > 0) {
return nested;
}
}
}
return [];
}
function isRecord(value) {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function isWtrTermTuple(value) {
return Array.isArray(value) && Array.isArray(value[0]) && (typeof value[1] === "string" || typeof value[1] === "number");
}
function getGlossarySourceId(entry) {
const explicitSourceId = sanitizeIdentifier(entry.source_id ?? entry.sourceId);
if (explicitSourceId) {
return explicitSourceId;
}
const data = isRecord(entry.data) ? entry.data : null;
const sourceType = sanitizeIdentifier(data?.type ?? entry.type);
const sourceId = sanitizeIdentifier(data?.id ?? entry.id ?? entry.raw_id ?? entry.rawId);
if (!sourceType || !sourceId) {
return undefined;
}
return `${WTR_SOURCE_ID_PREFIX}${sourceType}.${sourceId}`;
}
function collectNovelTermEntries(payload, fallbackLang, sourceId) {
if (isWtrTermTuple(payload)) {
return [{ item: payload, sourceId, lang: fallbackLang }];
}
if (Array.isArray(payload)) {
return payload.flatMap((item) => collectNovelTermEntries(item, fallbackLang, sourceId));
}
if (!isRecord(payload)) {
return [];
}
const localSourceId = getGlossarySourceId(payload) || sourceId;
const localLang = sanitizeLang(payload.lang ?? payload.language, fallbackLang);
const directTerm = firstTextValue(payload, ["source", "original", "source_text", "sourceText", "from", "term", "raw", "name"]);
if (directTerm) {
return [{ item: payload, sourceId: localSourceId, lang: localLang }];
}
const nestedEntries = [];
for (const fieldName of ["glossaries", "data", "terms", "items", "results", "sources"]) {
const value = payload[fieldName];
if (value && (Array.isArray(value) || typeof value === "object")) {
nestedEntries.push(...collectNovelTermEntries(value, localLang, localSourceId));
}
}
return nestedEntries;
}
function textValuesFromArray(values, maxLength = MAX_TERM_LENGTH) {
const deduped = new Map();
for (const value of values) {
const text = sanitizeApiText(value, maxLength);
if (text) {
deduped.set(text, text);
}
}
return Array.from(deduped.values());
}
function firstTextFromArray(values, maxLength = MAX_TERM_LENGTH) {
return textValuesFromArray(values, maxLength)[0] || null;
}
function maxNumberValue(values) {
return values.reduce((maxValue, value) => {
if (typeof value === "number" && Number.isFinite(value)) {
return Math.max(maxValue, value);
}
if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) {
return Math.max(maxValue, Number(value));
}
return maxValue;
}, 0);
}
function collectReaderText(payload, depth = 0) {
if (depth > 6 || payload == null) {
return [];
}
if (typeof payload === "string") {
return payload.length > 40 ? [payload] : [];
}
if (Array.isArray(payload)) {
return payload.flatMap((item) => collectReaderText(item, depth + 1));
}
if (typeof payload !== "object") {
return [];
}
const objectPayload = payload;
const textKeys = ["body", "content", "chapter", "chapter_body", "chapterBody", "html", "text"];
const directText = textKeys.flatMap((fieldName) => collectReaderText(objectPayload[fieldName], depth + 1));
if (directText.length > 0) {
return directText;
}
return Object.values(objectPayload).flatMap((value) => collectReaderText(value, depth + 1));
}
function rankChapterTermCandidates(text, existingTerms = new Set(), limit = 50) {
const strippedText = stripHtml(text);
const counts = new Map();
const addTerm = (rawTerm) => {
const term = sanitizeApiText(rawTerm);
if (!term || term.length < 2 || existingTerms.has(term) || COMMON_WORDS.has(term)) {
return;
}
counts.set(term, (counts.get(term) || 0) + 1);
};
const latinMatches = strippedText.matchAll(/\b[A-Z][\p{L}\p{M}'-]*(?:\s+[A-Z][\p{L}\p{M}'-]*){0,3}\b/gu);
for (const match of latinMatches) {
addTerm(match[0]);
}
const cjkMatches = strippedText.matchAll(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\uAC00-\uD7AF]{2,12}/gu);
for (const match of cjkMatches) {
addTerm(match[0]);
}
return Array.from(counts.entries())
.map(([term, count]) => ({ term, count, source: "chapter" }))
.sort((a, b) => b.count - a.count || a.term.localeCompare(b.term))
.slice(0, limit);
}
function extractCurrentChapterCandidates(readerPayload, existingTerms = new Set(), limit = 50) {
const text = collectReaderText(readerPayload).join(" ");
if (!text) {
return [];
}
return rankChapterTermCandidates(text, existingTerms, limit);
}
function parseNovelTermEntries(payload, lang = "en", limit = MAX_RESULTS) {
const deduped = new Map();
const entries = collectNovelTermEntries(payload, sanitizeLang(lang));
for (const { item, sourceId: inheritedSourceId, lang: entryLang } of entries) {
let term = null;
let replacement = null;
let replacementSuggestions = [];
let count = 0;
let sourceId = inheritedSourceId;
let hash;
let candidateLang = entryLang;
if (Array.isArray(item)) {
const replacements = Array.isArray(item[0]) ? item[0] : [];
term = sanitizeApiText(item[1]);
replacementSuggestions = textValuesFromArray(replacements, MAX_REPLACEMENT_LENGTH);
replacement = replacementSuggestions[0] || null;
count = maxNumberValue(item.slice(2));
hash = sanitizePreferenceHash(item[1]);
}
else if (isRecord(item)) {
term = firstTextValue(item, ["source", "original", "source_text", "sourceText", "from", "term", "raw", "name"]);
replacement = firstTextValue(item, ["target", "replacement", "target_text", "targetText", "translation", "value", "to"], MAX_REPLACEMENT_LENGTH);
count = firstNumberValue(item, [
"popularity",
"count",
"uses",
"usage",
"frequency",
"preference_count",
"userCount",
"user_count",
]);
sourceId = sanitizeIdentifier(item.source_id ?? item.sourceId ?? item.id) || sourceId;
hash = sanitizePreferenceHash(item.hash ?? item.source_hash ?? item.sourceHash ?? item.from ?? item.source ?? item.original ?? term);
candidateLang = sanitizeLang(item.lang ?? item.language, candidateLang);
}
if (!term) {
continue;
}
const candidate = {
term,
replacement: replacement || undefined,
...(replacementSuggestions.length > 1 ? { replacementSuggestions } : {}),
source: "novel",
count,
sourceId,
hash,
lang: candidateLang,
};
const mapKey = term.toLocaleLowerCase();
const previous = deduped.get(mapKey);
if (!previous || candidate.count > previous.count || (!previous.hash && candidate.hash)) {
deduped.set(mapKey, candidate);
}
}
return Array.from(deduped.values())
.sort((a, b) => b.count - a.count || a.term.localeCompare(b.term))
.slice(0, limit);
}
function getDiscoveryCandidateKey(candidate) {
if (!candidate) {
return "";
}
return [candidate.term, candidate.replacement || "", candidate.source, candidate.sourceId || "", candidate.hash || "", candidate.lang || ""].join("\u001f");
}
function isReplacementSuggestionRequestCurrent(requestId, latestRequestId, candidateKey, selectedCandidateKey, inputValue, currentInputValue) {
return requestId === latestRequestId && candidateKey === selectedCandidateKey && inputValue === currentInputValue;
}
function parseReplacementPreferences(payload, limit = 20) {
const deduped = new Map();
const entries = getArrayPayload(payload);
for (const item of entries) {
if (!item || typeof item !== "object") {
continue;
}
const entry = item;
const replacement = firstTextValue(entry, ["target", "replacement", "target_text", "targetText", "translation", "value", "to"], MAX_REPLACEMENT_LENGTH);
if (!replacement) {
continue;
}
const count = firstNumberValue(entry, [
"count",
"score",
"votes",
"uses",
"usage",
"preference_count",
"users_count",
"userCount",
"user_count",
]);
const mapKey = replacement.toLocaleLowerCase();
const previous = deduped.get(mapKey);
if (!previous || count > previous.count) {
deduped.set(mapKey, { replacement, count });
}
}
return Array.from(deduped.values())
.sort((a, b) => b.count - a.count || a.replacement.localeCompare(b.replacement))
.slice(0, limit);
}
;// ./src/modules/termDiscovery.ts
const TERM_SUGGESTION_CACHE_PREFIX = "wtr_lab_term_suggestion_cache_v1_";
const NOVEL_TERMS_CACHE_TTL_MS = 60 * 60 * 1000;
const PREFERENCES_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
function getReaderContext() {
return (0,utils/* getReaderContextFromPath */.o7)(window.location.pathname);
}
function getCsrfToken() {
const meta = document.querySelector('meta[name="csrf-token"], meta[name="csrf_token"]');
return meta?.getAttribute("content") || null;
}
function getCacheKey(type, identifiers) {
return `${TERM_SUGGESTION_CACHE_PREFIX}${type}_${identifiers.filter(Boolean).join("_")}`;
}
async function readCache(key, ttlMs) {
try {
const entry = (await GM_getValue(key, null));
if (!entry || typeof entry.fetchedAt !== "number" || Date.now() - entry.fetchedAt > ttlMs) {
return null;
}
return entry.data;
}
catch (_error) {
return null;
}
}
async function writeCache(key, data) {
try {
await GM_setValue(key, { fetchedAt: Date.now(), data });
}
catch (_error) {
// Suggestion cache is optional and must never block the main term workflow.
}
}
async function fetchJson(url, init = {}) {
const headers = new Headers(init.headers || {});
headers.set("Accept", "application/json");
const csrfToken = getCsrfToken();
if (csrfToken) {
headers.set("X-CSRF-TOKEN", csrfToken);
}
const response = await fetch(url, {
...init,
credentials: "same-origin",
headers,
});
if (!response.ok) {
throw new Error(`WTR API returned ${response.status}`);
}
return response.json();
}
function hasPreferenceIdentifiers(candidate) {
return Boolean(candidate?.sourceId && candidate?.hash && candidate?.lang);
}
async function loadNovelTermEntries(forceRefresh = false) {
const context = getReaderContext();
if (!context.rawId) {
return [];
}
const cacheKey = getCacheKey("novel", [context.lang, context.rawId]);
if (!forceRefresh) {
const cached = await readCache(cacheKey, NOVEL_TERMS_CACHE_TTL_MS);
if (cached) {
return cached;
}
}
const apiPayload = await fetchJson(`/api/v2/reader/terms/${encodeURIComponent(context.rawId)}.json`);
const candidates = parseNovelTermEntries(apiPayload, context.lang, 200);
await writeCache(cacheKey, candidates);
return candidates;
}
async function loadReplacementSuggestions(candidate, forceRefresh = false) {
if (!hasPreferenceIdentifiers(candidate)) {
return [];
}
const sourceId = candidate.sourceId;
const hash = candidate.hash;
const lang = candidate.lang;
const cacheKey = getCacheKey("preferences", [lang, sourceId, hash]);
if (!forceRefresh) {
const cached = await readCache(cacheKey, PREFERENCES_CACHE_TTL_MS);
if (cached) {
return cached;
}
}
const params = new URLSearchParams({ source_id: sourceId, hash, lang });
const apiPayload = await fetchJson(`/api/v2/term-preferences?${params.toString()}`);
const suggestions = parseReplacementPreferences(apiPayload, 12);
await writeCache(cacheKey, suggestions);
return suggestions;
}
// EXTERNAL MODULE: ./config/versions.js
var versions = __webpack_require__(387);
;// ./src/modules/handlers.ts
// Event handler functions for WTR Lab Term Replacer
// Re-export saveTermListLocation for UI module
// Export hideUIPanel function that can be called from UI
function hideUIPanel() {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: UI panel hide requested");
(0,ui/* hideUIPanel */.X)();
}
function validateRegex(pattern) {
try {
new RegExp(pattern);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Valid regex pattern: ${pattern}`);
return true;
}
catch (e) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Invalid regex pattern: ${pattern} - ${e.message}`);
return false;
}
}
// Silent validation for real-time visual feedback
function validateRegexSilent(pattern) {
try {
new RegExp(pattern);
return { isValid: true, error: null };
}
catch (e) {
return { isValid: false, error: e.message };
}
}
function ensureDiscoveryState() {
if (!state/* state */.wk.termDiscovery) {
state/* state */.wk.termDiscovery = {
novelTerms: [],
replacementSuggestions: [],
selectedCandidate: null,
};
}
return state/* state */.wk.termDiscovery;
}
function getFieldSuggestionTokens(fieldId) {
const field = document.getElementById(fieldId);
return getExistingSuggestionTokens(field?.value || "");
}
function getSuggestionPresenceLabels(suggestion) {
const labels = [];
const originalTokens = getFieldSuggestionTokens("wtr-original");
const replacementTokens = getFieldSuggestionTokens("wtr-replacement");
if (originalTokens.has(suggestion)) {
labels.push("Original");
}
if (replacementTokens.has(suggestion)) {
labels.push("Replacement");
}
return labels;
}
function renderReplacementSuggestions(suggestions, message = "") {
const container = document.getElementById("wtr-replacement-suggestions");
if (!container) {
return;
}
container.textContent = "";
if (message) {
const messageEl = document.createElement("small");
messageEl.textContent = message;
container.appendChild(messageEl);
return;
}
if (suggestions.length === 0) {
return;
}
const label = document.createElement("small");
label.textContent = "Suggestions:";
container.appendChild(label);
const buttonWrap = document.createElement("div");
buttonWrap.className = "wtr-replacement-suggestion-buttons";
suggestions.forEach((suggestion) => {
const presenceLabels = getSuggestionPresenceLabels(suggestion.replacement);
const button = document.createElement("button");
button.type = "button";
button.className = `btn btn-secondary btn-sm wtr-replacement-suggestion-btn${presenceLabels.length ? " wtr-suggestion-existing" : ""}`;
button.dataset.replacement = suggestion.replacement;
const replacementLabel = document.createElement("span");
replacementLabel.textContent = suggestion.replacement;
button.appendChild(replacementLabel);
const metaParts = [suggestion.sourceLabel, suggestion.count > 0 ? String(suggestion.count) : "", ...presenceLabels].filter(Boolean);
if (metaParts.length > 0) {
const sourceLabel = document.createElement("small");
sourceLabel.className = "wtr-replacement-suggestion-source";
sourceLabel.textContent = metaParts.join(" • ");
button.appendChild(sourceLabel);
}
buttonWrap.appendChild(button);
});
container.appendChild(buttonWrap);
}
function normalizeReplacementSuggestion(replacement, count = 0, sourceLabel = "", sourceRank = 50) {
const normalizedReplacement = replacement.replace(/\s+/g, " ").trim();
if (!normalizedReplacement) {
return null;
}
return { replacement: normalizedReplacement, count, sourceLabel, sourceRank };
}
function mergeSuggestionLabels(existingLabel = "", newLabel = "") {
const labels = new Set([...existingLabel.split(" + "), ...newLabel.split(" + ")].map((label) => label.trim()).filter(Boolean));
return Array.from(labels).join(" + ");
}
function dedupeReplacementSuggestions(suggestions) {
const deduped = new Map();
suggestions.forEach((suggestion) => {
const normalized = normalizeReplacementSuggestion(suggestion.replacement, suggestion.count, suggestion.sourceLabel, suggestion.sourceRank);
if (!normalized) {
return;
}
const key = normalized.replacement;
const existing = deduped.get(key);
if (!existing) {
deduped.set(key, normalized);
return;
}
existing.count = Math.max(existing.count, normalized.count);
existing.sourceRank = Math.min(existing.sourceRank ?? 50, normalized.sourceRank ?? 50);
existing.sourceLabel = mergeSuggestionLabels(existing.sourceLabel, normalized.sourceLabel);
});
return Array.from(deduped.values()).sort((a, b) => (a.sourceRank ?? 50) - (b.sourceRank ?? 50) ||
b.count - a.count ||
a.replacement.localeCompare(b.replacement));
}
function replacementSuggestionsFromValues(values, sourceLabel = "WTR", sourceRank = 50) {
return dedupeReplacementSuggestions(values
.map((value) => normalizeReplacementSuggestion(value, 0, sourceLabel, sourceRank))
.filter((suggestion) => Boolean(suggestion)));
}
function getOriginalInputOptions() {
const originalInput = document.getElementById("wtr-original");
const regexCheckbox = document.getElementById("wtr-is-regex");
const caseSensitiveCheckbox = document.getElementById("wtr-case-sensitive");
return {
value: originalInput ? originalInput.value.trim() : "",
isRegex: Boolean(regexCheckbox?.checked),
caseSensitive: Boolean(caseSensitiveCheckbox?.checked),
};
}
function stripRegexSyntax(fragment) {
return fragment
.replace(/\\[bBAZzG]/g, " ")
.replace(/\\s[+*?]?/g, " ")
.replace(/\\[dDwW][+*?]?/g, " ")
.replace(/\[([^\]\\]+)\][+*?]?/g, (_match, chars) => chars.match(/[\p{L}\p{M}\p{N}'-]/u)?.[0] || " ")
.replace(/\(\?[:=!<][^)]*\)/g, " ")
.replace(/[()]/g, " ")
.replace(/[{}+*?^$]/g, " ")
.replace(/\\(.)/g, "$1")
.replace(/\s+/g, " ")
.trim();
}
function splitTopLevelAlternatives(value) {
const parts = [];
let current = "";
let isEscaped = false;
let characterClassDepth = 0;
let groupDepth = 0;
for (const char of value) {
if (isEscaped) {
current += char;
isEscaped = false;
continue;
}
if (char === "\\") {
current += char;
isEscaped = true;
continue;
}
if (char === "[") {
characterClassDepth++;
current += char;
continue;
}
if (char === "]" && characterClassDepth > 0) {
characterClassDepth--;
current += char;
continue;
}
if (characterClassDepth === 0 && char === "(") {
groupDepth++;
current += char;
continue;
}
if (characterClassDepth === 0 && char === ")" && groupDepth > 0) {
groupDepth--;
current += char;
continue;
}
if (characterClassDepth === 0 && groupDepth === 0 && char === "|") {
parts.push(current);
current = "";
continue;
}
current += char;
}
parts.push(current);
return parts;
}
function getSuggestionLookupTerms(original, isRegex) {
const terms = new Map();
const addTerm = (value) => {
const term = value.replace(/\s+/g, " ").trim();
if (term.length >= 2) {
terms.set(term.toLocaleLowerCase(), term);
}
};
addTerm(original);
const splitPattern = isRegex ? /[\n,;]+/ : /[\n,;|/]+/;
original.split(splitPattern).forEach(addTerm);
if (isRegex) {
for (const fragment of splitTopLevelAlternatives(original)) {
addTerm(stripRegexSyntax(fragment));
stripRegexSyntax(fragment)
.split(/[|,;\n]+/)
.forEach(addTerm);
}
stripRegexSyntax(original)
.split(/[|,;\n]+/)
.forEach(addTerm);
}
return Array.from(terms.values());
}
function findNovelCandidatesByOriginalInput(original, isRegex, caseSensitive) {
const discovery = ensureDiscoveryState();
const novelTerms = discovery.novelTerms;
const deduped = new Map();
const lookupTerms = getSuggestionLookupTerms(original, isRegex);
const lookupSet = new Set(lookupTerms.map((term) => (caseSensitive ? term : term.toLocaleLowerCase())));
const addCandidate = (candidate) => {
deduped.set(candidate.term.toLocaleLowerCase(), candidate);
};
const getCandidateLookupValues = (candidate) => [
candidate.term,
...(candidate.replacement ? [candidate.replacement] : []),
...(candidate.replacementSuggestions || []),
];
for (const candidate of novelTerms) {
const candidateValues = getCandidateLookupValues(candidate).map((value) => (caseSensitive ? value : value.toLocaleLowerCase()));
if (candidateValues.some((candidateValue) => lookupSet.has(candidateValue))) {
addCandidate(candidate);
}
}
if (isRegex) {
try {
const regex = new RegExp(original, caseSensitive ? "" : "i");
for (const candidate of novelTerms) {
if (getCandidateLookupValues(candidate).some((value) => regex.test(value))) {
addCandidate(candidate);
}
}
}
catch (_error) {
// Invalid in-progress regex input still gets literal/variant suggestions from extracted terms.
}
}
return Array.from(deduped.values()).slice(0, 10);
}
function mergeReplacementSuggestions(candidates, suggestions) {
const mergedSuggestions = [];
for (const candidate of candidates) {
mergedSuggestions.push({
replacement: candidate.term,
count: candidate.count || 0,
sourceLabel: "Source",
sourceRank: -10,
});
const candidateReplacements = candidate.replacementSuggestions?.length
? candidate.replacementSuggestions
: candidate.replacement
? [candidate.replacement]
: [];
candidateReplacements.forEach((replacement) => {
mergedSuggestions.push({
replacement,
count: candidate.count || 0,
sourceLabel: "WTR",
sourceRank: 30,
});
});
}
mergedSuggestions.push(...suggestions.map((suggestion) => ({
...suggestion,
sourceLabel: suggestion.sourceLabel || "API",
sourceRank: suggestion.sourceRank ?? 40,
})));
return dedupeReplacementSuggestions(mergedSuggestions);
}
let replacementSuggestionRequestId = 0;
let replacementSuggestionTimeout = null;
let suppressNextReplacementSuggestionInput = false;
let activeSuggestionTarget = "replacement";
function isActiveReplacementSuggestionRequest(requestId, inputValue) {
return requestId === replacementSuggestionRequestId && getOriginalInputOptions().value === inputValue;
}
function getOriginalInputFieldSuggestions(value, isRegex) {
if (!isRegex) {
return [];
}
return normalizeOriginalRegexPattern(value)
.split("|")
.map((part) => normalizeReplacementSuggestion(part, 0, "Field", 5))
.filter((suggestion) => Boolean(suggestion));
}
async function updateReplacementSuggestionsForCandidates(candidates, inputValue, mergeExisting = false, seedSuggestions = []) {
const discovery = ensureDiscoveryState();
const existingSuggestions = mergeExisting ? [...(discovery.replacementSuggestions || [])] : [];
discovery.selectedCandidate = candidates[0] || null;
const requestId = ++replacementSuggestionRequestId;
if (candidates.length === 0) {
const mergedExistingSuggestions = dedupeReplacementSuggestions([...existingSuggestions, ...seedSuggestions]);
discovery.replacementSuggestions = mergedExistingSuggestions;
renderReplacementSuggestions(mergedExistingSuggestions);
return;
}
const loadedSuggestions = await Promise.all(candidates.map(async (candidate) => {
if (!hasPreferenceIdentifiers(candidate)) {
return [];
}
try {
return await loadReplacementSuggestions(candidate);
}
catch (error) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Replacement suggestions unavailable", error);
return [];
}
}));
if (!isActiveReplacementSuggestionRequest(requestId, inputValue)) {
return;
}
const mergedSuggestions = dedupeReplacementSuggestions([
...existingSuggestions,
...seedSuggestions,
...mergeReplacementSuggestions(candidates, loadedSuggestions.flat()),
]);
discovery.replacementSuggestions = mergedSuggestions;
renderReplacementSuggestions(mergedSuggestions, mergedSuggestions.length ? "" : "No replacement suggestions found for this original text.");
}
function clearDiscoveryFormState() {
if (replacementSuggestionTimeout) {
clearTimeout(replacementSuggestionTimeout);
replacementSuggestionTimeout = null;
}
const discovery = ensureDiscoveryState();
discovery.replacementSuggestions = [];
discovery.selectedCandidate = null;
replacementSuggestionRequestId++;
const suggestionsContainer = document.getElementById("wtr-replacement-suggestions");
if (suggestionsContainer) {
suggestionsContainer.textContent = "";
}
}
function handleReplacementSuggestionInput(event) {
if (suppressNextReplacementSuggestionInput) {
suppressNextReplacementSuggestionInput = false;
return;
}
if (replacementSuggestionTimeout) {
clearTimeout(replacementSuggestionTimeout);
}
const mergeExisting = Boolean(event?.mergeExisting);
replacementSuggestionTimeout = setTimeout(async () => {
const sourceValue = event?.target?.value;
const options = getOriginalInputOptions();
const query = typeof sourceValue === "string" ? sourceValue.trim() : options.value;
const discovery = ensureDiscoveryState();
const fieldSuggestions = getOriginalInputFieldSuggestions(query, options.isRegex);
if (!query) {
updateReplacementSuggestionsForCandidates([], query, mergeExisting, fieldSuggestions);
return;
}
if (discovery.novelTerms.length === 0) {
try {
discovery.novelTerms = await loadNovelTermEntries(false);
}
catch (error) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Replacement suggestions unavailable", error);
}
}
updateReplacementSuggestionsForCandidates(findNovelCandidatesByOriginalInput(query, options.isRegex, options.caseSensitive), query, mergeExisting, fieldSuggestions);
}, 250);
}
async function handleRefreshSuggestionsClick() {
if (replacementSuggestionTimeout) {
clearTimeout(replacementSuggestionTimeout);
replacementSuggestionTimeout = null;
}
const options = getOriginalInputOptions();
if (!options.value) {
renderReplacementSuggestions([], "Enter Original Text first, then refresh suggestions.");
return;
}
renderReplacementSuggestions([], "Refreshing suggestions...");
try {
const discovery = ensureDiscoveryState();
discovery.novelTerms = await loadNovelTermEntries(true);
await updateReplacementSuggestionsForCandidates(findNovelCandidatesByOriginalInput(options.value, options.isRegex, options.caseSensitive), options.value, true, getOriginalInputFieldSuggestions(options.value, options.isRegex));
}
catch (error) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Replacement suggestion refresh failed", error);
renderReplacementSuggestions([], "Suggestions are unavailable right now.");
}
}
function handleSuggestionTargetFocus(event) {
const fieldId = event?.target?.id;
if (fieldId === "wtr-original") {
activeSuggestionTarget = "original";
}
else if (fieldId === "wtr-replacement") {
activeSuggestionTarget = "replacement";
}
}
function getExistingSuggestionTokens(value) {
return new Set(value
.split(/\s*(?:\||\/|,|;|\n)\s*/)
.map((token) => token.trim())
.filter(Boolean));
}
function getReplacementAppendSeparator(value, isRegex) {
if (value.includes("\n")) {
return "\n";
}
if (value.includes(" | ")) {
return " | ";
}
if (value.includes("|")) {
return "|";
}
if (value.includes(" / ") || value.includes("/")) {
return " / ";
}
if (value.includes(";")) {
return "; ";
}
if (value.includes(",")) {
return ", ";
}
return "|";
}
function normalizeOriginalRegexPattern(value) {
const deduped = new Map();
value
.trim()
.replace(/\s*\|\s*/g, "|")
.replace(/\s*\/\s*/g, "|")
.split("|")
.map((part) => part.trim())
.filter(Boolean)
.forEach((part) => deduped.set(part, part));
return Array.from(deduped.values())
.sort((a, b) => b.length - a.length || a.localeCompare(b))
.join("|");
}
function normalizeOriginalRegexField() {
const regexCheckbox = document.getElementById("wtr-is-regex");
const originalInput = document.getElementById("wtr-original");
if (!regexCheckbox?.checked || !originalInput) {
return;
}
const normalized = normalizeOriginalRegexPattern(originalInput.value);
if (normalized && normalized !== originalInput.value) {
originalInput.value = normalized;
suppressNextReplacementSuggestionInput = true;
originalInput.dispatchEvent(new Event("input", { bubbles: true }));
}
}
function getSuggestionAppendSeparator(value, isRegex, targetField) {
if (targetField === "original") {
return "|";
}
return getReplacementAppendSeparator(value, isRegex);
}
function mergeSuggestionInputValue(currentValue, suggestion, isRegex, selectionStart, selectionEnd, targetField) {
const replacement = suggestion.trim();
if (!replacement) {
return currentValue;
}
if (selectionStart !== null && selectionEnd !== null && selectionEnd > selectionStart) {
return `${currentValue.slice(0, selectionStart)}${replacement}${currentValue.slice(selectionEnd)}`;
}
const trimmedValue = currentValue.trim();
if (!trimmedValue) {
return replacement;
}
if (getExistingSuggestionTokens(trimmedValue).has(replacement)) {
return currentValue;
}
return `${currentValue.trimEnd()}${getSuggestionAppendSeparator(trimmedValue, isRegex, targetField)}${replacement}`;
}
function normalizePopoverText(value) {
return (value || "").replace(/\s+/g, " ").trim();
}
function getElementClassText(element) {
if (!element) {
return "";
}
return typeof element.className === "string" ? element.className : element.getAttribute("class") || "";
}
function getPopoverBadgeCount(badge) {
const countText = normalizePopoverText(badge.closest(".user-term")?.querySelector(".badge-segment span:last-child")?.textContent);
if (!countText) {
return 0;
}
if (countText.endsWith("+")) {
return Number.parseInt(countText, 10) || 0;
}
return Number(countText) || 0;
}
function getPopoverBadgeSuggestion(badge) {
const label = badge.querySelector("span:last-child") || badge;
const replacement = normalizePopoverText(label.textContent);
if (!replacement) {
return null;
}
const userTerm = badge.closest(".user-term");
const iconHref = badge.querySelector("use")?.getAttribute("href") || "";
const classSource = `${getElementClassText(badge)} ${getElementClassText(userTerm)} ${iconHref}`;
if (/google|g_translate|bg-primary/i.test(classSource)) {
return normalizeReplacementSuggestion(replacement, getPopoverBadgeCount(badge), "Google", 90);
}
if (/dictionary|bg-success/i.test(classSource)) {
return normalizeReplacementSuggestion(replacement, getPopoverBadgeCount(badge), "WTR", 30);
}
if (userTerm) {
return normalizeReplacementSuggestion(replacement, getPopoverBadgeCount(badge), "WTR", 40);
}
return normalizeReplacementSuggestion(replacement, getPopoverBadgeCount(badge), "WTR", 50);
}
function getWtrPopoverContextFromElement(element) {
const sourceTerm = normalizePopoverText(element.querySelector(".text-underscore")?.textContent);
const popoverSuggestions = Array.from(element.querySelectorAll(".user-term .badge, .badge.bg-success, .badge.bg-primary"))
.map(getPopoverBadgeSuggestion)
.filter((suggestion) => Boolean(suggestion));
const recentContext = state/* state */.wk.wtrPopoverTermContext || {};
const recentSuggestions = Array.isArray(recentContext.suggestions) ? recentContext.suggestions : [];
const original = normalizePopoverText(recentContext.original) || popoverSuggestions[0]?.replacement || sourceTerm;
const resolvedSourceTerm = sourceTerm || normalizePopoverText(recentContext.sourceTerm);
const sourceSuggestion = normalizeReplacementSuggestion(resolvedSourceTerm, 0, "Source", -10);
const currentSuggestion = normalizeReplacementSuggestion(original, 0, "Current", 0);
const resolvedSuggestions = dedupeReplacementSuggestions([
...(sourceSuggestion ? [sourceSuggestion] : []),
...(currentSuggestion ? [currentSuggestion] : []),
...popoverSuggestions,
...recentSuggestions,
]);
if (!original && !resolvedSourceTerm && resolvedSuggestions.length === 0) {
return null;
}
return {
original,
sourceTerm: resolvedSourceTerm,
suggestions: resolvedSuggestions,
};
}
function encodePopoverContext(context) {
return JSON.stringify(context);
}
function decodePopoverContext(value) {
if (!value) {
return null;
}
try {
return JSON.parse(value);
}
catch (_error) {
return null;
}
}
function openWtrPopoverTermContext(context) {
(0,ui/* showUIPanel */.E1)();
suppressNextReplacementSuggestionInput = true;
(0,ui/* showFormView */.BD)();
const originalInput = document.getElementById("wtr-original");
const replacementInput = document.getElementById("wtr-replacement");
if (!originalInput || !replacementInput) {
return;
}
originalInput.value = context.original || context.suggestions[0]?.replacement || context.sourceTerm || "";
originalInput.rows = Math.max(1, Math.ceil(originalInput.value.length / 40));
replacementInput.value = "";
ensureDiscoveryState().replacementSuggestions = dedupeReplacementSuggestions(context.suggestions);
renderReplacementSuggestions(ensureDiscoveryState().replacementSuggestions);
activeSuggestionTarget = "replacement";
replacementInput.focus();
}
function handleWtrTextPatchClick(event) {
const patch = event.target?.closest?.(".text-patch[data-hash]");
if (!patch) {
return;
}
const visibleText = normalizePopoverText(patch.textContent);
const sourceTerm = normalizePopoverText(patch.getAttribute("data-hash"));
const sourceSuggestion = normalizeReplacementSuggestion(sourceTerm, 0, "Source", -10);
const currentSuggestion = normalizeReplacementSuggestion(visibleText, 0, "Current", 0);
state/* state */.wk.wtrPopoverTermContext = {
original: visibleText,
sourceTerm,
suggestions: [sourceSuggestion, currentSuggestion].filter((suggestion) => Boolean(suggestion)),
};
}
function enhanceWtrTermPopovers(root = document) {
const editors = [
...(root instanceof Element && root.matches(".mini-term-editor") ? [root] : []),
...Array.from(root.querySelectorAll(".mini-term-editor")),
];
editors.forEach((editor) => {
if (editor.querySelector(".wtr-replacer-popover-actions")) {
return;
}
const popoverRoot = editor.closest(".popover-body") || editor;
const context = getWtrPopoverContextFromElement(popoverRoot);
if (!context) {
return;
}
const encodedContext = encodePopoverContext(context);
const actionColumn = editor.querySelector(".d-flex.flex-column.ms-2") || editor;
const buttonGroup = document.createElement("div");
buttonGroup.className = "wtr-replacer-popover-actions";
const openButton = document.createElement("button");
openButton.type = "button";
openButton.className = "mt-1 btn btn-outline-primary btn-sm wtr-replacer-popover-add-btn";
openButton.textContent = "+ Replacer Term";
openButton.dataset.wtrContext = encodedContext;
buttonGroup.appendChild(openButton);
actionColumn.appendChild(buttonGroup);
});
}
function handleWtrPopoverAddTermClick(event) {
const button = event.target?.closest?.(".wtr-replacer-popover-add-btn");
if (!button) {
return;
}
event.preventDefault();
event.stopPropagation();
const context = decodePopoverContext(button.dataset.wtrContext) || getWtrPopoverContextFromElement(button.closest(".popover-body") || button);
if (!context) {
return;
}
openWtrPopoverTermContext(context);
}
function handleReplacementSuggestionClick(event) {
const button = event.target.closest(".wtr-replacement-suggestion-btn");
if (!button) {
return;
}
const originalInput = document.getElementById("wtr-original");
const replacementInput = document.getElementById("wtr-replacement");
const regexCheckbox = document.getElementById("wtr-is-regex");
const targetField = activeSuggestionTarget;
const targetInput = targetField === "original" ? originalInput : replacementInput;
if (targetInput) {
targetInput.value = mergeSuggestionInputValue(targetInput.value, button.dataset.replacement || "", Boolean(regexCheckbox?.checked), targetInput.selectionStart, targetInput.selectionEnd, targetField);
if (targetField === "original") {
suppressNextReplacementSuggestionInput = true;
}
targetInput.dispatchEvent(new Event("input", { bubbles: true }));
targetInput.focus();
}
}
async function handleSaveTerm() {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Handle save term started");
const id = document.getElementById("wtr-term-id").value;
const originalInput = document.getElementById("wtr-original");
const replacementInput = document.getElementById("wtr-replacement");
const isRegex = document.getElementById("wtr-is-regex").checked;
let original = originalInput.value.trim();
if (isRegex) {
original = normalizeOriginalRegexPattern(original);
originalInput.value = original;
}
const wholeWord = document.getElementById("wtr-whole-word").checked;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Saving term - original: "${original}", replacement: "${replacementInput.value}", isRegex: ${isRegex}, wholeWord: ${wholeWord}, caseSensitive: ${document.getElementById("wtr-case-sensitive").checked}`);
if (!original) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Save term failed - empty original text");
return; // No error message shown, rely on disabled save button
}
if (isRegex && !validateRegex(original)) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Save term failed - invalid regex pattern");
return; // No error message shown, rely on visual feedback
}
const newTerm = {
id: id || `term_${Date.now()}`,
original,
replacement: replacementInput.value,
caseSensitive: document.getElementById("wtr-case-sensitive").checked,
isRegex,
wholeWord: isRegex ? false : wholeWord,
};
const existingIndex = state/* state */.wk.terms.findIndex((t) => t.id === newTerm.id);
if (existingIndex > -1) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Updating existing term ${newTerm.id}`);
state/* state */.wk.terms[existingIndex] = newTerm;
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Adding new term ${newTerm.id}`);
state/* state */.wk.terms.push(newTerm);
}
await (0,storage.saveTerms)(state/* state */.wk.terms);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Term saved successfully, total terms: ${state/* state */.wk.terms.length}`);
(0,observer/* reprocessCurrentChapter */.J)();
// Clear form fields
originalInput.value = "";
replacementInput.value = "";
document.getElementById("wtr-term-id").value = "";
document.getElementById("wtr-case-sensitive").checked = false;
document.getElementById("wtr-is-regex").checked = false;
document.getElementById("wtr-whole-word").checked = false;
document.getElementById("wtr-save-btn").textContent = "Save Term";
clearDiscoveryFormState();
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
if (id) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Switching to terms tab after update");
(0,ui/* switchTab */.OG)("terms");
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Focusing on original input for next term");
originalInput.focus();
}
if (state/* state */.wk.isDupMode) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Updating duplicate mode after term change");
(0,duplicates/* updateDupModeAfterChange */.Cs)();
}
}
function handleListInteraction(e) {
const termId = e.target.closest("li")?.dataset.id;
if (!termId) {
return;
}
if (e.target.classList.contains("wtr-edit-btn")) {
const term = state/* state */.wk.terms.find((t) => t.id === termId);
if (term) {
(0,ui/* showFormView */.BD)(term);
}
}
}
async function handleDeleteSelected() {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Delete selected terms started");
(0,ui/* showUILoader */.Xt)();
try {
const selectedIds = [...document.querySelectorAll(".wtr-replacer-term-select:checked")].map((cb) => cb.dataset.id);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Found ${selectedIds.length} terms selected for deletion: ${selectedIds.join(", ")}`);
if (selectedIds.length === 0) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Delete cancelled - no terms selected");
alert("No terms selected.");
return;
}
if (confirm(`Delete ${selectedIds.length} term(s)?`)) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: User confirmed deletion, proceeding...");
const filteredTerms = state/* state */.wk.terms.filter((t) => !selectedIds.includes(t.id));
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Deleting ${state/* state */.wk.terms.length - filteredTerms.length} terms, ${filteredTerms.length} remaining`);
await (0,storage.saveTerms)(filteredTerms);
await (0,storage.loadData)();
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Terms deleted and data reloaded");
(0,observer/* reprocessCurrentChapter */.J)();
if (state/* state */.wk.isDupMode) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Updating duplicate mode after deletion");
(0,duplicates/* updateDupModeAfterChange */.Cs)();
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Refreshing term list display");
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
}
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Delete cancelled by user");
}
}
catch (error) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Error during term deletion: ${error.message}`);
console.error("Error during term deletion:", error);
}
finally {
(0,ui/* hideUILoader */.W4)();
}
}
function handleTextSelection(e) {
const CHAPTER_BODY_SELECTOR = ".chapter-body";
if (!e.target.closest(CHAPTER_BODY_SELECTOR)) {
return;
}
const selection = window.getSelection().toString().trim();
const floatBtn = document.querySelector(".wtr-add-term-float-btn");
if (selection && selection.length > 0 && selection.length < 100) {
floatBtn.style.display = "block";
}
else {
floatBtn.style.display = "none";
}
}
function handleAddTermFromSelection() {
const selection = window.getSelection().toString().trim();
if (selection) {
(0,ui/* showUIPanel */.E1)();
(0,ui/* showFormView */.BD)();
document.getElementById("wtr-original").value = selection;
document.getElementById("wtr-replacement").focus();
}
document.querySelector(".wtr-add-term-float-btn").style.display = "none";
}
function handleSearch(e) {
if (state/* state */.wk.isDupMode) {
return;
}
state/* state */.wk.currentSearchValue = e.target.value;
state/* state */.wk.currentPage = 1;
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
// Immediately save the search field value for reactive behavior
(0,storage.saveSearchFieldValue)();
}
async function handleDisableToggle(e) {
state/* state */.wk.settings.isDisabled = e.target.checked;
await (0,storage.saveSettings)(state/* state */.wk.settings);
const getChapterIdFromUrl = (url) => {
const match = url.match(/(chapter-\d+)/);
return match ? match[1] : null;
};
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterId = getChapterIdFromUrl(window.location.href);
if (!chapterId) {
return;
}
const chapterSelector = `#${chapterId} ${CHAPTER_BODY_SELECTOR}`;
const chapterBody = document.querySelector(chapterSelector);
if (chapterBody) {
if (state/* state */.wk.settings.isDisabled) {
(0,engine.revertAllReplacements)(chapterBody);
}
else {
(0,engine.performReplacements)(chapterBody);
}
}
}
function downloadJSON(data, filename) {
return new Promise((resolve) => {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
resolve();
});
}
// Enhanced Export Functions
async function handleExportNovel() {
const exportData = {
formatVersion: (0,versions.getVersion)(),
settings: { [state/* state */.wk.novelSlug]: state/* state */.wk.settings },
terms: { [state/* state */.wk.novelSlug]: state/* state */.wk.terms },
};
downloadJSON(exportData, `${state/* state */.wk.novelSlug}-terms.json`);
}
async function handleExportAll() {
(0,ui/* showUILoader */.Xt)();
try {
const allKeys = await GM_listValues();
const TERMS_STORAGE_KEY_PREFIX = "wtr_lab_terms_";
const SETTINGS_STORAGE_KEY_PREFIX = "wtr_lab_settings_";
const termKeys = allKeys.filter((k) => k.startsWith(TERMS_STORAGE_KEY_PREFIX));
const settingKeys = allKeys.filter((k) => k.startsWith(SETTINGS_STORAGE_KEY_PREFIX));
const exportData = { formatVersion: (0,versions.getVersion)(), settings: {}, terms: {} };
for (const key of termKeys) {
const slug = key.replace(TERMS_STORAGE_KEY_PREFIX, "");
exportData.terms[slug] = await GM_getValue(key);
}
for (const key of settingKeys) {
const slug = key.replace(SETTINGS_STORAGE_KEY_PREFIX, "");
exportData.settings[slug] = await GM_getValue(key);
}
downloadJSON(exportData, "wtr-lab-all-terms-backup.json");
}
catch (e) {
console.error("Error exporting all terms:", e);
alert("Failed to export all terms.");
}
finally {
(0,ui/* hideUILoader */.W4)();
}
}
// Enhanced dual export functionality with sequential downloads
async function handleExportCombined() {
(0,ui/* showUILoader */.Xt)();
try {
// Step 1: Export novel terms first
const novelExportData = {
formatVersion: (0,versions.getVersion)(),
settings: { [state/* state */.wk.novelSlug]: state/* state */.wk.settings },
terms: { [state/* state */.wk.novelSlug]: state/* state */.wk.terms },
};
await downloadJSON(novelExportData, `${state/* state */.wk.novelSlug}-terms.json`);
// Step 2: Ask user for confirmation before proceeding to second download
const userConfirmed = confirm('The first file (Novel Terms) has been downloaded. Please check if the download completed successfully. Click "OK" to proceed with the second download (All Terms backup), or "Cancel" to skip.');
if (userConfirmed) {
// Step 3: Export all terms only after user confirmation
const allKeys = await GM_listValues();
const TERMS_STORAGE_KEY_PREFIX = "wtr_lab_terms_";
const SETTINGS_STORAGE_KEY_PREFIX = "wtr_lab_settings_";
const termKeys = allKeys.filter((k) => k.startsWith(TERMS_STORAGE_KEY_PREFIX));
const settingKeys = allKeys.filter((k) => k.startsWith(SETTINGS_STORAGE_KEY_PREFIX));
const allExportData = { formatVersion: (0,versions.getVersion)(), settings: {}, terms: {} };
for (const key of termKeys) {
const slug = key.replace(TERMS_STORAGE_KEY_PREFIX, "");
allExportData.terms[slug] = await GM_getValue(key);
}
for (const key of settingKeys) {
const slug = key.replace(SETTINGS_STORAGE_KEY_PREFIX, "");
allExportData.settings[slug] = await GM_getValue(key);
}
await downloadJSON(allExportData, "wtr-lab-all-terms-backup.json");
alert("Both files have been successfully exported!");
}
else {
alert("Second export cancelled. Only the novel terms file was downloaded.");
}
}
catch (e) {
console.error("Error exporting combined terms:", e);
alert("Failed to export combined terms. Please try again.");
}
finally {
(0,ui/* hideUILoader */.W4)();
}
}
async function handleFileImport(event) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: File import started, import type: ${state/* state */.wk.importType}`);
(0,ui/* showUILoader */.Xt)();
try {
const file = event.target.files[0];
if (!file) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: No file selected for import");
return;
}
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Importing file: ${file.name}, size: ${file.size} bytes, type: ${file.type}`);
const reader = new FileReader();
reader.onload = async (e) => {
const content = String(e.target.result ?? "");
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: File content loaded, length: ${content.length} characters`);
let importedData;
try {
importedData = JSON.parse(content);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: JSON parsed successfully");
}
catch (err) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Import failed - invalid JSON: ${err.message}`);
alert("Import failed. Invalid JSON data. Error: " + err.message);
return;
}
const isNewFormat = Boolean(importedData.formatVersion);
let termsData;
let settingsData;
const isArrayData = Array.isArray(importedData);
const isOldGlobal = !isNewFormat && !isArrayData && typeof importedData === "object";
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Detected format - isNewFormat: ${isNewFormat}, isArrayData: ${isArrayData}, isOldGlobal: ${isOldGlobal}`);
if (isArrayData) {
termsData = { [state/* state */.wk.novelSlug]: importedData };
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Array format detected, mapping to current novel");
}
else if (isOldGlobal) {
termsData = importedData;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Old global format detected");
}
else if (isNewFormat) {
termsData = importedData.terms || {};
settingsData = importedData.settings || {};
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: New format detected - terms: ${Object.keys(termsData).length} slugs, settings: ${Object.keys(settingsData).length} slugs`);
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Import failed - unrecognized data format");
alert("Import failed. Unrecognized data format.");
return;
}
let slugs = Object.keys(termsData);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Found data for ${slugs.length} slugs: ${slugs.join(", ")}`);
if (state/* state */.wk.importType === "novel" && slugs.length > 1) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Novel import with multiple slugs - warning user");
alert("Warning: File contains data for multiple novels, but importing to current novel only. Use Global Import for all.");
termsData = { [state/* state */.wk.novelSlug]: termsData[Object.keys(termsData)[0]] || [] };
if (settingsData) {
settingsData = { [state/* state */.wk.novelSlug]: settingsData[Object.keys(settingsData)[0]] || {} };
}
slugs = [state/* state */.wk.novelSlug];
}
let shouldImportSettings = false;
if (settingsData && Object.keys(settingsData).length > 0) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Settings detected in import, asking user for confirmation");
shouldImportSettings = confirm("This file contains settings. Would you like to import and overwrite your current settings?");
}
let totalAdded = 0, totalSkipped = 0, totalConflicts = 0, invalidCount = 0, validCount = 0;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Starting term import process...");
for (const slug of slugs) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Processing import for slug: ${slug}`);
const existingTerms = await GM_getValue(`wtr_lab_terms_${slug}`, []);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Existing terms for ${slug}: ${existingTerms.length}`);
let overwrite = true;
if (existingTerms.length > 0) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Existing terms found for ${slug}, asking user about merge vs overwrite`);
overwrite = !confirm(`An existing term list was found for ${slug}. Would you like to merge? (OK = Merge, Cancel = Overwrite)`);
if (!overwrite) {
if (!confirm("Are you sure you want to overwrite?")) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: User cancelled overwrite for ${slug}`);
continue;
}
overwrite = true;
}
}
const rawTerms = termsData[slug] || [];
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Raw terms for ${slug}: ${rawTerms.length}`);
if (!Array.isArray(rawTerms)) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Skipping ${slug} - not an array`);
continue;
}
const validatedTerms = rawTerms.filter((term) => {
term.wholeWord = term.wholeWord ?? false;
if (term.isRegex) {
try {
new RegExp(term.original);
validCount++;
return true;
}
catch (err) {
invalidCount++;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Skipping invalid regex term: "${term.original}" - ${err.message}`);
console.warn(`Skipping invalid regex term on import: "${term.original}"`);
return false;
}
}
validCount++;
return true;
});
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Validated terms for ${slug}: ${validatedTerms.length} valid, ${invalidCount} invalid`);
const { added, skipped, conflicts } = await (0,storage.processAndSaveTerms)(slug, validatedTerms, overwrite);
totalAdded += added;
totalSkipped += skipped;
totalConflicts += conflicts;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Import results for ${slug} - added: ${added}, skipped: ${skipped}, conflicts: ${conflicts}`);
}
if (shouldImportSettings) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Importing settings data...");
await (0,storage.processAndSaveSettings)(settingsData);
}
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Reloading data and reprocessing chapters...");
await (0,storage.loadData)();
(0,observer/* reprocessCurrentChapter */.J)();
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
if (state/* state */.wk.isDupMode) {
(0,duplicates/* updateDupModeAfterChange */.Cs)();
}
let summary = "Import successful!";
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Import completed - totalAdded: ${totalAdded}, totalSkipped: ${totalSkipped}, totalConflicts: ${totalConflicts}, invalidCount: ${invalidCount}, validCount: ${validCount}`);
if (totalAdded > 0 || totalSkipped > 0 || totalConflicts > 0) {
summary += `\n${totalAdded} new terms added. ${totalSkipped} duplicates skipped. ${totalConflicts} conflicts skipped.`;
}
if (invalidCount > 0) {
summary += `\n${validCount} terms imported. ${invalidCount} terms skipped due to invalid regex.`;
}
alert(summary);
};
reader.readAsText(file);
event.target.value = "";
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: File import process initiated");
}
catch (e) {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Import error: ${e.message}`);
alert("An error occurred during import.");
console.error(e);
}
finally {
(0,ui/* hideUILoader */.W4)();
}
}
function handleTabSwitch(e) {
const targetTab = e.target.dataset.tab;
// Save current state before switching (if on terms tab)
const currentTab = document.querySelector(".wtr-replacer-tab-btn.active").dataset.tab;
if (currentTab === "terms") {
// Save the full scroll position when leaving terms tab
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Saving scroll position before switching from terms to ${targetTab}`);
(0,storage.saveTermListLocation)();
}
document.querySelectorAll(".wtr-replacer-tab-btn").forEach((btn) => btn.classList.remove("active"));
e.target.classList.add("active");
document.querySelectorAll(".wtr-replacer-tab-content").forEach((content) => content.classList.remove("active"));
document.getElementById(`wtr-tab-${targetTab}`).classList.add("active");
if (targetTab === "terms") {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, "WTR Term Replacer: Restoring scroll position after switching to terms tab");
restoreTermListLocation();
}
else {
(0,ui/* clearTermList */.kH)();
}
}
async function handleFindDuplicates() {
(0,ui/* showUILoader */.Xt)();
try {
const TERMS_KEY = `wtr_lab_terms_${state/* state */.wk.novelSlug}`;
const currentNovelTerms = await GM_getValue(TERMS_KEY, []);
(0,duplicates/* computeDupGroups */.r_)(currentNovelTerms);
if (state/* state */.wk.dupKeys.length === 0) {
alert("No duplicates found.");
return;
}
state/* state */.wk.isDupMode = true;
state/* state */.wk.currentDupIndex = 0;
state/* state */.wk.currentSearchValue = "";
setSearchFieldValue("");
}
finally {
(0,ui/* hideUILoader */.W4)();
}
}
// Use duplicate functions from duplicates module (imported above)
// Helper function to set search field value programmatically with reactive save
function setSearchFieldValue(value) {
const searchBar = document.getElementById("wtr-search-bar");
if (searchBar) {
searchBar.value = value;
state/* state */.wk.currentSearchValue = value;
state/* state */.wk.currentPage = 1;
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
(0,storage.saveSearchFieldValue)();
}
}
async function restoreTermListLocation() {
try {
const saved = await GM_getValue(`wtr_lab_term_list_location_${state/* state */.wk.novelSlug}`, null);
if (saved) {
state/* state */.wk.savedTermListLocation = saved;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Restoring scroll position - top: ${saved.scrollTop}, page: ${saved.page}`);
}
state/* state */.wk.currentPage = state/* state */.wk.savedTermListLocation.page || 1;
state/* state */.wk.currentSearchValue = state/* state */.wk.savedTermListLocation.searchValue || "";
// Apply the saved state to the UI
const searchBar = document.getElementById("wtr-search-bar");
if (searchBar && state/* state */.wk.currentSearchValue) {
searchBar.value = state/* state */.wk.currentSearchValue;
}
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
// Restore scroll position after a short delay to ensure rendering is complete
setTimeout(() => {
const termListContainer = document.querySelector(".wtr-replacer-content");
if (termListContainer && state/* state */.wk.savedTermListLocation.scrollTop) {
termListContainer.scrollTop = state/* state */.wk.savedTermListLocation.scrollTop;
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Scroll position restored to ${state/* state */.wk.savedTermListLocation.scrollTop}`);
}
}, 100);
}
catch (e) {
console.error("Error restoring term list location:", e);
}
}
function toggleLogging() {
state/* state */.wk.globalSettings.isLoggingEnabled = !state/* state */.wk.globalSettings.isLoggingEnabled;
(0,storage.saveGlobalSettings)();
alert(`Logging ${state/* state */.wk.globalSettings.isLoggingEnabled ? "enabled" : "disabled"}.`);
}
// Additional functions needed for index.js integration
async function addTermProgrammatically(original, replacement, isRegex = false) {
if (!original) {
return;
}
const newTerm = {
id: `term_${Date.now()}`,
original: original.trim(),
replacement: replacement.trim(),
caseSensitive: false,
isRegex: isRegex,
wholeWord: isRegex ? false : false,
};
const isDuplicate = state/* state */.wk.terms.some((t) => t.original === newTerm.original && t.replacement === newTerm.replacement && t.isRegex === newTerm.isRegex);
if (!isDuplicate) {
state/* state */.wk.terms.push(newTerm);
await (0,storage.saveTerms)(state/* state */.wk.terms);
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Programmatically added term (Regex: ${isRegex}): ${newTerm.original} -> ${newTerm.replacement}`);
if (document.querySelector(".wtr-replacer-ui").style.display === "flex") {
(0,ui/* renderTermList */.FP)(state/* state */.wk.currentSearchValue);
}
}
else {
(0,utils/* log */.Rm)(state/* state */.wk.globalSettings, `WTR Term Replacer: Skipped adding duplicate term: ${newTerm.original}`);
}
}
/***/ },
/***/ 405
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ J: () => (/* binding */ reprocessCurrentChapter),
/* harmony export */ _: () => (/* binding */ waitForInitialContent),
/* harmony export */ processVisibleChapter: () => (/* binding */ processVisibleChapter)
/* harmony export */ });
/* unused harmony import specifier */ var state;
/* unused harmony import specifier */ var getChapterIdFromUrl;
/* unused harmony import specifier */ var log;
/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(654);
/* harmony import */ var _engine__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9);
/* harmony import */ var _ui__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(141);
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(158);
// MutationObserver and content handling for WTR Lab Term Replacer
function waitForInitialContent() {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Starting robust content detection for slow-loading websites...");
// Set up mutation observer for dynamic content loading
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Setting up content change observer");
setupContentObserver();
// Set up additional fallback mechanisms
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Setting up fallback detection mechanisms");
setupFallbackDetection();
}
function _detectContentWithMultipleStrategies() {
const detectionStrategies = [
// Strategy 1: Standard chapter ID detection
() => {
const chapterId = getChapterIdFromUrl(window.location.href);
const contentContainer = chapterId ? document.querySelector(`#${chapterId}`) : null;
return contentContainer ? { container: contentContainer, strategy: "chapter-id" } : null;
},
// Strategy 2: Look for chapter body directly
() => {
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterBody = document.querySelector(CHAPTER_BODY_SELECTOR);
return chapterBody ? { container: chapterBody.closest('[id*="chapter"]'), strategy: "chapter-body" } : null;
},
// Strategy 3: Look for any container with substantial content
() => {
const contentAreas = document.querySelectorAll('main, article, .content, .chapter, [role="main"]');
for (const area of contentAreas) {
if (area.textContent?.trim().length > 200) {
return { container: area, strategy: "content-area" };
}
}
return null;
},
// Strategy 4: Last resort - any substantial text content
() => {
const bodyText = document.body.textContent?.trim() || "";
if (bodyText.length > 500 && !bodyText.includes("loading")) {
return { container: document.body, strategy: "body-fallback" };
}
return null;
},
];
let currentStrategy = 0;
const maxRetriesPerStrategy = 20;
let retries = 0;
const enhancedPoll = setInterval(async () => {
const result = detectionStrategies[currentStrategy]();
if (result && result.container) {
clearInterval(enhancedPoll);
console.log(`WTR Term Replacer: Content detected using strategy: ${result.strategy}`);
// Progressive processing based on content readiness
await progressiveContentProcessing(result.container, result.strategy);
monitorURLChanges();
return;
}
retries++;
if (retries >= maxRetriesPerStrategy) {
currentStrategy++;
retries = 0;
if (currentStrategy >= detectionStrategies.length) {
clearInterval(enhancedPoll);
console.warn("WTR Term Replacer: All detection strategies exhausted. Will retry on content changes.");
// Keep fallback detection active
}
else {
log(`WTR Term Replacer: Strategy ${currentStrategy} failed, trying next strategy...`);
}
}
}, 500); // Increased interval for slower polling
}
async function progressiveContentProcessing(container, strategy) {
log(`WTR Term Replacer: Starting progressive processing with strategy: ${strategy}`);
// Give the page more time to fully load, especially for slow connections
const settlingDelays = [200, 500, 1000, 2000]; // Progressive delays
let currentDelayIndex = 0;
const attemptProcessing = async () => {
try {
// Check content readiness with multiple criteria
if (isContentReadyForProcessing(container)) {
log("WTR Term Replacer: Content ready for processing, proceeding...");
processVisibleChapter();
return true;
}
else if (currentDelayIndex < settlingDelays.length - 1) {
currentDelayIndex++;
log(`WTR Term Replacer: Content not ready, waiting ${settlingDelays[currentDelayIndex]}ms more...`);
setTimeout(attemptProcessing, settlingDelays[currentDelayIndex]);
return false;
}
else {
log("WTR Term Replacer: Final attempt with current content state");
processVisibleChapter(); // Force processing
return true;
}
}
catch (error) {
log("WTR Term Replacer: Error during progressive processing:", error);
return false;
}
};
await attemptProcessing();
}
function isContentReadyForProcessing(container) {
// Multiple readiness criteria for robust detection
const hasSubstantialContent = container.textContent?.trim().length > 100;
const hasNoActiveLoaders = !container.querySelector('.loading, .spinner, [style*="loading"], .skeleton');
const isVisible = container.offsetWidth > 0 && container.offsetHeight > 0;
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const hasChapterContent = container.querySelector(CHAPTER_BODY_SELECTOR) || container.querySelector("p, h1, h2, h3, h4, h5, h6");
return hasSubstantialContent && hasNoActiveLoaders && isVisible && hasChapterContent;
}
function setupContentObserver() {
// Watch for dynamic content loading with enhanced coordination
let observerTimeout;
let lastCheckTime = 0;
let isContentChangeInProgress = false;
let potentialMultiScriptConflicts = 0;
const observer = new MutationObserver((mutations) => {
// Prevent excessive triggering with timing constraints
const now = Date.now();
if (now - lastCheckTime < 2000) {
// Minimum 2 seconds between checks
return;
}
let shouldCheckForContent = false;
const detectedScriptChanges = [];
for (const mutation of mutations) {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
// Check if substantial content was added
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
const textContent = node.textContent?.trim() || "";
// Detect multi-script data attributes being added
if (node.hasAttribute?.("data-smart-quotes-processed")) {
detectedScriptChanges.push("Smart Quotes");
shouldCheckForContent = true;
}
if (node.hasAttribute?.("data-uncensor-processed")) {
detectedScriptChanges.push("Uncensor");
shouldCheckForContent = true;
}
if (node.hasAttribute?.("data-auto-scroll") || node.hasAttribute?.("data-reader-enhanced")) {
detectedScriptChanges.push("Reader Enhancer");
shouldCheckForContent = true;
}
// More strict content validation to reduce false positives
if (textContent.length > 100 &&
!textContent.includes("loading") &&
!textContent.includes("...") &&
(node.id?.includes("chapter") ||
node.className?.includes("chapter") ||
node.querySelector(".chapter-body"))) {
shouldCheckForContent = true;
break;
}
}
}
}
}
if (shouldCheckForContent && !isContentChangeInProgress) {
isContentChangeInProgress = true;
lastCheckTime = now;
if (detectedScriptChanges.length > 0) {
potentialMultiScriptConflicts++;
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Multi-script activity detected from: ${detectedScriptChanges.join(", ")} (conflict ${potentialMultiScriptConflicts})`);
// Update our detected scripts
detectedScriptChanges.forEach((script) => _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.add(script));
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)("WTR Term Replacer: Content changes detected, checking for chapter content...");
}
// Debounced check to avoid excessive processing with enhanced delay for multi-script
const baseDelay = 1500;
const multiScriptDelay = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size > 0 ? 2500 : baseDelay;
clearTimeout(observerTimeout);
observerTimeout = setTimeout(() => {
const queuedForProcessing = document.querySelector("[data-wtr-processed]") || _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.size > 0;
if (!queuedForProcessing) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Initiating content processing (${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size} other scripts active, ${multiScriptDelay}ms coordination delay)`);
processVisibleChapter();
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Skipping content processing - already in progress or completed (queue: ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.size})`);
}
isContentChangeInProgress = false;
}, multiScriptDelay); // Increased delay to coordinate with other processes
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["style", "class", "id"],
});
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)("WTR Term Replacer: Enhanced content observer activated with multi-script coordination");
}
function setupFallbackDetection() {
// Periodic fallback check for stubborn slow-loading pages
let fallbackAttempts = 0;
const maxFallbackAttempts = 10;
const fallbackInterval = setInterval(() => {
if (document.querySelector("[data-wtr-processed]")) {
clearInterval(fallbackInterval);
return;
}
fallbackAttempts++;
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Fallback attempt ${fallbackAttempts}/${maxFallbackAttempts}`);
// Try processing if we have any chapter-like content
const chapterId = (0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .getChapterIdFromUrl */ .Ug)(window.location.href);
if (chapterId) {
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterSelector = `#${chapterId} ${CHAPTER_BODY_SELECTOR}`;
const chapterBody = document.querySelector(chapterSelector);
if (chapterBody) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)("WTR Term Replacer: Fallback processing successful");
processVisibleChapter();
clearInterval(fallbackInterval);
return;
}
}
// Check for any substantial content that might be chapter content
const potentialContent = document.querySelector("main, article, .content, .chapter");
if (potentialContent && potentialContent.textContent?.trim().length > 200) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)("WTR Term Replacer: Fallback processing with detected content");
processVisibleChapter();
clearInterval(fallbackInterval);
}
if (fallbackAttempts >= maxFallbackAttempts) {
clearInterval(fallbackInterval);
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)("WTR Term Replacer: Fallback detection exhausted");
}
}, 3000); // Check every 3 seconds
// Clear fallback interval after 5 minutes to prevent infinite polling
setTimeout(() => {
clearInterval(fallbackInterval);
}, 300000);
}
function processVisibleChapter() {
const chapterId = (0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .getChapterIdFromUrl */ .Ug)(window.location.href);
if (!chapterId) {
return;
}
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterSelector = `#${chapterId} ${CHAPTER_BODY_SELECTOR}`;
const chapterBody = document.querySelector(chapterSelector);
if (!chapterBody) {
return;
}
if (chapterBody.dataset.wtrProcessed === "true") {
return;
}
// Use queue-based processing to avoid race conditions
scheduleChapterProcessing(chapterId, chapterBody);
}
function scheduleChapterProcessing(chapterId, _chapterBody) {
const processingKey = `${chapterId}_${Date.now()}`;
// Enhanced queue management with proper synchronization
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.has(chapterId)) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Chapter ${chapterId} already queued for processing ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.size} queued`);
return;
}
// Add with unique identifier to prevent race conditions
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.add(processingKey);
// Progressive retry with exponential backoff for slow-loading content
const retryAttempts = [
{ delay: 100, maxContentLoad: 0.3 }, // Fast retry for quick loads
{ delay: 500, maxContentLoad: 0.5 }, // Medium retry for normal loads
{ delay: 1000, maxContentLoad: 0.7 }, // Slower retry for slow loads
{ delay: 2000, maxContentLoad: 0.9 }, // Very slow retry for very slow loads
{ delay: 5000, maxContentLoad: 1.0 }, // Final attempt with any content
];
executeProcessingWithRetry(chapterId, retryAttempts, 0, processingKey);
}
async function executeProcessingWithRetry(chapterId, retryAttempts, attemptIndex, processingKey) {
const attempt = retryAttempts[attemptIndex];
try {
// Wait for the specified delay
await new Promise((resolve) => setTimeout(resolve, attempt.delay));
// Verify queue entry still exists (prevent race conditions)
if (!_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.has(processingKey)) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Chapter ${chapterId} processing cancelled (no longer in queue)`);
return;
}
// Re-acquire chapter body element dynamically to avoid stale references
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterSelector = `#${chapterId} ${CHAPTER_BODY_SELECTOR}`;
const chapterBody = document.querySelector(chapterSelector);
if (!chapterBody) {
throw new Error("Chapter body element not found");
}
// Additional DOM stability validation
if (!document.contains(chapterBody) || chapterBody.nodeType !== Node.ELEMENT_NODE) {
throw new Error("Chapter body element no longer in DOM");
}
// Check if content is sufficiently loaded
const contentLoadLevel = estimateContentLoadLevel(chapterBody);
if (contentLoadLevel >= attempt.maxContentLoad) {
// Proceed with processing
await performRobustReplacements(chapterBody, chapterId);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.delete(processingKey);
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Successfully processed chapter ${chapterId} on attempt ${attemptIndex + 1}`);
}
else if (attemptIndex < retryAttempts.length - 1) {
// Retry with next attempt
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Chapter ${chapterId} content not ready (load level: ${contentLoadLevel.toFixed(2)}), retrying...`);
executeProcessingWithRetry(chapterId, retryAttempts, attemptIndex + 1, processingKey);
}
else {
// Final attempt with any available content
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Final attempt for chapter ${chapterId} with available content`);
await performRobustReplacements(chapterBody, chapterId, true); // force processing
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.delete(processingKey);
}
}
catch (error) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Error processing chapter ${chapterId} on attempt ${attemptIndex + 1}:`, error);
if (attemptIndex < retryAttempts.length - 1) {
executeProcessingWithRetry(chapterId, retryAttempts, attemptIndex + 1, processingKey);
}
else {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.delete(processingKey);
console.error(`WTR Term Replacer: Failed to process chapter ${chapterId} after all retries`);
}
}
}
function estimateContentLoadLevel(chapterBody) {
// Estimate how much content is loaded based on text density and structure
const textNodes = chapterBody.querySelectorAll("p, h1, h2, h3, h4, h5, h6, div, span");
const totalTextLength = Array.from(textNodes).reduce((total, node) => total + (node.textContent?.trim().length || 0), 0);
// Check for loading indicators or placeholder content
const hasLoadingIndicators = chapterBody.querySelector('.loading, .spinner, [style*="loading"], [class*="loading"]');
const hasPlaceholderContent = chapterBody.textContent?.includes("Loading...") ||
chapterBody.textContent?.includes("loading") ||
chapterBody.textContent?.includes("...");
// Calculate load level based on content density and absence of loading indicators
let loadLevel = Math.min(totalTextLength / 1000, 1.0); // Normalize to 0-1 based on 1000 chars
if (hasLoadingIndicators || hasPlaceholderContent) {
loadLevel *= 0.3; // Reduce load level if loading indicators present
}
// Ensure minimum threshold for processing
return Math.max(loadLevel, totalTextLength > 100 ? 0.5 : 0.1);
}
function detectOtherWTRScripts() {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Scanning for other WTR Lab scripts...");
// Detect other WTR Lab scripts by their data attributes or specific patterns
const scripts = document.querySelectorAll("[data-smart-quotes-processed], [data-uncensor-processed], [data-auto-scroll], [data-reader-enhanced]");
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Found ${scripts.length} elements with WTR script attributes`);
scripts.forEach((el) => {
if (el.hasAttribute("data-smart-quotes-processed")) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.add("Smart Quotes");
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Detected Smart Quotes script");
}
if (el.hasAttribute("data-uncensor-processed")) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.add("Uncensor");
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Detected Uncensor script");
}
if (el.hasAttribute("data-auto-scroll") || el.hasAttribute("data-reader-enhanced")) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.add("Reader Enhancer");
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Detected Reader Enhancer script");
}
});
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size > 0) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Multi-script environment detected - Active scripts: ${Array.from(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts).join(", ")}`);
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: No other WTR scripts detected, running in single-script mode");
}
}
function startProcessingTimer(operation) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Starting processing timer for ${operation}`);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingStartTime.set(operation, Date.now());
}
function endProcessingTimer(operation, chapterId) {
const startTime = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingStartTime.get(operation);
if (startTime) {
const processingTime = Date.now() - startTime;
const isMultiScript = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size > 0;
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Processing timer ended for ${operation}, took ${processingTime}ms`);
logProcessingWithMultiScriptContext(chapterId, processingTime, isMultiScript);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingStartTime.delete(operation);
return processingTime;
}
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Warning - processing timer for ${operation} not found`);
return 0;
}
function logProcessingWithMultiScriptContext(chapterId, processingTime, isMultiScript = false) {
const context = {
chapterId,
processingTime: `${processingTime}ms`,
multiScriptEnvironment: isMultiScript,
activeScripts: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size,
queueSize: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.size,
timestamp: new Date().toISOString(),
};
if (isMultiScript && _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size > 0) {
context.activeScripts = Array.from(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts);
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Multi-script enhanced processing completed`, context);
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Standard processing completed`, context);
}
}
async function performRobustReplacements(chapterBody, chapterId, forceProcess = false) {
try {
// Additional readiness checks before processing
if (!forceProcess && !isElementReadyForProcessing(chapterBody)) {
throw new Error("Element not ready for processing");
}
startProcessingTimer(`chapter_${chapterId}`);
// Detect other WTR scripts if not already done
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size === 0) {
detectOtherWTRScripts();
}
const isMultiScript = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts.size > 0;
if (isMultiScript) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Multi-script processing starting for chapter ${chapterId} with active scripts: ${Array.from(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.otherWTRScripts).join(", ")}`);
}
else {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Processing chapter ${chapterId} with robust method`);
}
(0,_engine__WEBPACK_IMPORTED_MODULE_1__.performReplacements)(chapterBody);
chapterBody.dataset.wtrProcessed = "true";
(0,_ui__WEBPACK_IMPORTED_MODULE_2__/* .addMenuButton */ .L_)();
const processingTime = endProcessingTimer(`chapter_${chapterId}`, chapterId);
if (isMultiScript) {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Successfully completed multi-script processing for chapter ${chapterId} in ${processingTime}ms`);
}
}
catch (error) {
const processingTime = endProcessingTimer(`chapter_${chapterId}`, chapterId) || 0;
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(`WTR Term Replacer: Robust processing failed for chapter ${chapterId} after ${processingTime}ms:`, error);
throw error;
}
}
function isElementReadyForProcessing(element) {
// Check if element is visible and has substantial content
const rect = element.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0;
const hasSubstantialContent = element.textContent?.trim().length > 50;
const hasNoLoadingStates = !element.querySelector('.loading, .spinner, [style*="display: none"]');
return isVisible && hasSubstantialContent && hasNoLoadingStates;
}
function reprocessCurrentChapter() {
const chapterId = (0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .getChapterIdFromUrl */ .Ug)(window.location.href);
if (!chapterId) {
return;
}
const CHAPTER_BODY_SELECTOR = ".chapter-body";
const chapterSelector = `#${chapterId} ${CHAPTER_BODY_SELECTOR}`;
const chapterBody = document.querySelector(chapterSelector);
if (chapterBody) {
// Reset processing state to allow reprocessing
chapterBody.dataset.wtrProcessed = "false";
// Clear any existing processing entries for this chapter
const existingKeys = Array.from(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue).filter((key) => key.startsWith(chapterId));
existingKeys.forEach((key) => _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.processingQueue.delete(key));
// Use robust reprocessing with retry mechanism
scheduleChapterProcessing(chapterId, chapterBody);
}
}
function monitorURLChanges() {
setInterval(() => {
if (window.location.href !== state.currentURL) {
log(`WTR Term Replacer: URL changed to ${window.location.href}.`);
state.currentURL = window.location.href;
setTimeout(processVisibleChapter, 250);
}
}, 500);
}
/***/ },
/***/ 654
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ oP: () => (/* binding */ setNovelSlug),
/* harmony export */ wk: () => (/* binding */ state)
/* harmony export */ });
/* unused harmony export initializeState */
// State management for WTR Lab Term Replacer
const state = {
novelSlug: null,
terms: [],
settings: { isDisabled: false },
globalSettings: { isLoggingEnabled: false },
importType: "novel",
currentSearchValue: "",
isDupMode: false,
dupGroups: new Map(),
dupKeys: [],
currentDupIndex: 0,
currentPage: 1,
savedTermListLocation: { page: 1, scrollTop: 0, searchValue: "" },
originalTextNodes: new WeakMap(),
otherWTRScripts: new Set(),
processingStartTime: new Map(),
domConflictDetected: false,
multiScriptPerformanceImpact: new Map(),
currentURL: window.location.href,
processingQueue: new Set(),
isProcessingInProgress: false,
observedMenuContainers: new WeakSet(),
termDiscovery: {
novelTerms: [],
replacementSuggestions: [],
selectedCandidate: null,
},
};
// Function to initialize novel slug - should be called after utils is loaded
function initializeState() {
if (!state.novelSlug) {
// Import getNovelSlug function dynamically to avoid circular dependencies
Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 158)).then(({ getNovelSlug }) => {
state.novelSlug = getNovelSlug();
});
}
return state.novelSlug;
}
// Set novel slug (for synchronous initialization)
function setNovelSlug(slug) {
state.novelSlug = slug;
}
/***/ },
/***/ 694
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ getTermKey: () => (/* binding */ getTermKey),
/* harmony export */ getTermsForSlug: () => (/* binding */ getTermsForSlug),
/* harmony export */ loadData: () => (/* binding */ loadData),
/* harmony export */ loadGlobalSettings: () => (/* binding */ loadGlobalSettings),
/* harmony export */ loadTermListLocation: () => (/* binding */ loadTermListLocation),
/* harmony export */ processAndSaveSettings: () => (/* binding */ processAndSaveSettings),
/* harmony export */ processAndSaveTerms: () => (/* binding */ processAndSaveTerms),
/* harmony export */ saveGlobalSettings: () => (/* binding */ saveGlobalSettings),
/* harmony export */ saveSearchFieldValue: () => (/* binding */ saveSearchFieldValue),
/* harmony export */ saveSettings: () => (/* binding */ saveSettings),
/* harmony export */ saveTermListLocation: () => (/* binding */ saveTermListLocation),
/* harmony export */ saveTerms: () => (/* binding */ saveTerms)
/* harmony export */ });
/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(654);
/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(333);
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(158);
// Storage functions using GM_* API for WTR Lab Term Replacer
async function loadGlobalSettings() {
try {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings = await GM_getValue(_config__WEBPACK_IMPORTED_MODULE_1__/* .GLOBAL_SETTINGS_KEY */ .sI, { isLoggingEnabled: false });
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Global settings loaded");
}
catch (e) {
console.error("Error loading global settings:", e);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings = { isLoggingEnabled: false };
}
}
async function saveGlobalSettings() {
try {
await GM_setValue(_config__WEBPACK_IMPORTED_MODULE_1__/* .GLOBAL_SETTINGS_KEY */ .sI, _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings);
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Global settings saved");
}
catch (e) {
console.error("Error saving global settings:", e);
}
}
async function loadTermListLocation() {
try {
const saved = await GM_getValue(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .CURRENT_LOCATION_KEY */ .Qp}_${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`, null);
if (saved) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.savedTermListLocation = saved;
}
}
catch (e) {
console.error("Error loading term list location:", e);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.savedTermListLocation = { page: 1, scrollTop: 0, searchValue: "" };
}
}
async function saveTermListLocation() {
try {
const termListContainer = document.querySelector(".wtr-replacer-content");
if (termListContainer) {
// Capture more detailed location information for better preservation
const locationData = {
page: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage,
scrollTop: termListContainer.scrollTop,
scrollHeight: termListContainer.scrollHeight,
clientHeight: termListContainer.clientHeight,
searchValue: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue,
timestamp: Date.now(), // Add timestamp for better tracking
};
await GM_setValue(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .CURRENT_LOCATION_KEY */ .Qp}_${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`, locationData);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.savedTermListLocation = locationData;
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Saved scroll position - top: ${locationData.scrollTop}, page: ${locationData.page}`);
}
}
catch (e) {
console.error("Error saving term list location:", e);
}
}
// Helper function to save search field value immediately
async function saveSearchFieldValue() {
try {
const locationData = {
page: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage,
scrollTop: 0,
searchValue: _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue,
};
await GM_setValue(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .CURRENT_LOCATION_KEY */ .Qp}_${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`, locationData);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.savedTermListLocation = locationData;
}
catch (e) {
console.error("Error saving search field value:", e);
}
}
async function getTermsForSlug(slug) {
if (!slug) {
return [];
}
const terms = await GM_getValue(`${_config__WEBPACK_IMPORTED_MODULE_1__/* .TERMS_STORAGE_KEY_PREFIX */ .fW}${slug}`, []);
if (!Array.isArray(terms)) {
return [];
}
return terms.map((term) => ({
...term,
wholeWord: term.wholeWord ?? false,
}));
}
async function loadData() {
try {
const SETTINGS_KEY = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .SETTINGS_STORAGE_KEY_PREFIX */ .Ft}${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`;
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms = await getTermsForSlug(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug);
const savedSettings = await GM_getValue(SETTINGS_KEY, {});
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings = { isDisabled: false, ...savedSettings };
}
catch (e) {
console.error("Error loading data:", e);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms = [];
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings = { isDisabled: false };
}
}
async function saveTerms(termsToSave) {
try {
const TERMS_KEY = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .TERMS_STORAGE_KEY_PREFIX */ .fW}${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`;
await GM_setValue(TERMS_KEY, termsToSave);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms = termsToSave;
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Saved ${termsToSave.length} terms for novel ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`);
}
catch (e) {
console.error("Error saving terms:", e);
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Failed to save terms: ${e.message}`);
alert("Failed to save terms. Storage might be full.");
}
}
async function saveSettings(settingsToSave) {
try {
const SETTINGS_KEY = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .SETTINGS_STORAGE_KEY_PREFIX */ .Ft}${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug}`;
await GM_setValue(SETTINGS_KEY, settingsToSave);
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings = settingsToSave;
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Settings saved successfully");
}
catch (e) {
console.error("Error saving settings:", e);
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Failed to save settings: ${e.message}`);
alert("Failed to save settings. Storage might be full.");
}
}
function getTermKey(term) {
return `${term.original}|${term.caseSensitive}|${term.isRegex}`;
}
async function processAndSaveTerms(slug, importedTerms, overwrite = true) {
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Processing import for slug ${slug}, overwrite: ${overwrite}`);
const TERMS_KEY = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .TERMS_STORAGE_KEY_PREFIX */ .fW}${slug}`;
const existingTerms = await GM_getValue(TERMS_KEY, []);
existingTerms.forEach((t) => {
t.wholeWord = t.wholeWord ?? false;
});
let newTerms = [];
let added = 0;
let skipped = 0;
let conflicts = 0;
if (overwrite) {
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Overwrite mode - importing ${importedTerms.length} terms`);
newTerms = importedTerms;
}
else {
const existingMap = new Map();
existingTerms.forEach((t) => existingMap.set(getTermKey(t), t));
importedTerms.forEach((imp) => {
const key = getTermKey(imp);
if (!existingMap.has(key)) {
newTerms.push(imp);
added++;
}
else {
const ext = existingMap.get(key);
if (ext.replacement !== imp.replacement || ext.wholeWord !== imp.wholeWord) {
conflicts++;
}
else {
skipped++;
}
}
});
newTerms = [...existingTerms, ...newTerms];
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Merge mode - added: ${added}, skipped: ${skipped}, conflicts: ${conflicts}`);
}
await GM_setValue(TERMS_KEY, newTerms);
if (slug === _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms = newTerms;
}
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Import complete for ${slug} - total terms: ${newTerms.length}`);
return { added, skipped, conflicts };
}
async function processAndSaveSettings(importedSettings) {
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Processing settings import for ${Object.keys(importedSettings).length} slugs`);
for (const slug in importedSettings) {
const SETTINGS_KEY = `${_config__WEBPACK_IMPORTED_MODULE_1__/* .SETTINGS_STORAGE_KEY_PREFIX */ .Ft}${slug}`;
const existing = await GM_getValue(SETTINGS_KEY, { isDisabled: false });
const newSettings = { ...existing, ...importedSettings[slug] };
await GM_setValue(SETTINGS_KEY, newSettings);
if (slug === _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.novelSlug) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings = newSettings;
}
(0,_utils__WEBPACK_IMPORTED_MODULE_2__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, `WTR Term Replacer: Settings updated for slug ${slug}`);
}
}
/***/ },
/***/ 141
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ BD: () => (/* binding */ showFormView),
/* harmony export */ E1: () => (/* binding */ showUIPanel),
/* harmony export */ FP: () => (/* binding */ renderTermList),
/* harmony export */ L_: () => (/* binding */ addMenuButton),
/* harmony export */ OG: () => (/* binding */ switchTab),
/* harmony export */ RD: () => (/* binding */ createUI),
/* harmony export */ W4: () => (/* binding */ hideUILoader),
/* harmony export */ X: () => (/* binding */ hideUIPanel),
/* harmony export */ Xt: () => (/* binding */ showUILoader),
/* harmony export */ gn: () => (/* binding */ showProcessingIndicator),
/* harmony export */ kH: () => (/* binding */ clearTermList)
/* harmony export */ });
/* harmony import */ var _state__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(654);
/* harmony import */ var _handlers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(359);
/* harmony import */ var _duplicates__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(201);
/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(158);
/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333);
/* harmony import */ var _config_versions__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(387);
/* harmony import */ var _config_versions__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_config_versions__WEBPACK_IMPORTED_MODULE_5__);
const UI_HTML = `
<div class="wtr-replacer-header">
<h2>Term Replacer ${(0,_config_versions__WEBPACK_IMPORTED_MODULE_5__.getDisplayVersion)()}</h2>
<div class="wtr-replacer-header-controls">
<div class="wtr-replacer-disable-toggle">
<label><input type="checkbox" id="wtr-disable-all"> Disable All</label>
</div>
<button class="wtr-replacer-close-btn">×</button>
</div>
</div>
<div class="wtr-replacer-tabs">
<button class="wtr-replacer-tab-btn active" data-tab="terms">Terms List</button>
<button class="wtr-replacer-tab-btn" data-tab="add">Add/Edit Term</button>
<button class="wtr-replacer-tab-btn" data-tab="io">Import/Export</button>
</div>
<div class="wtr-replacer-content">
<div class="wtr-ui-loader" id="wtr-ui-loader">Loading...</div>
<div id="wtr-tab-terms" class="wtr-replacer-tab-content active">
<div id="wtr-dup-message" class="wtr-dup-message" style="display:none;"></div>
<div id="wtr-dup-controls" class="wtr-dup-controls" style="display:none;">
<button id="wtr-prev-dup-btn" class="btn btn-secondary">Previous</button>
<button id="wtr-next-dup-btn" class="btn btn-secondary">Next</button>
<button id="wtr-exit-dup-btn" class="btn btn-secondary">Exit Duplicate Mode</button>
</div>
<div class="wtr-replacer-list-controls">
<input type="text" id="wtr-search-bar" class="wtr-replacer-search-bar" placeholder="Search terms...">
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button id="wtr-find-duplicates-btn" class="btn btn-secondary">Find Duplicates</button>
<button id="wtr-delete-selected-btn" class="btn btn-secondary">Delete Selected</button>
</div>
</div>
<ul class="wtr-replacer-term-list"></ul>
<div class="wtr-pagination-controls">
<button id="wtr-first-page-btn" class="btn btn-secondary" title="First Page">««</button>
<button id="wtr-prev-page-btn" class="btn btn-secondary" title="Previous Page">«</button>
<span id="wtr-page-indicator"></span>
<button id="wtr-next-page-btn" class="btn btn-secondary" title="Next Page">»</button>
<button id="wtr-last-page-btn" class="btn btn-secondary" title="Last Page">»»</button>
</div>
</div>
<div id="wtr-tab-add" class="wtr-replacer-tab-content">
<input type="hidden" id="wtr-term-id">
<div class="wtr-replacer-form-group">
<label for="wtr-original">Original Text</label>
<textarea id="wtr-original" rows="1"></textarea>
<small id="wtr-regex-disabled-warning" class="wtr-regex-disabled-warning" style="display:none;">This looks like regex syntax, but Use Regex is off. It will be saved as plain text unless you enable Use Regex.</small>
<button type="button" id="wtr-refresh-suggestions-btn" class="btn btn-secondary btn-sm wtr-refresh-suggestions-btn">Refresh Suggestions</button>
</div>
<div class="wtr-replacer-form-group">
<label for="wtr-replacement">Replacement Text</label>
<input type="text" id="wtr-replacement">
<div id="wtr-replacement-suggestions" class="wtr-replacement-suggestions" aria-live="polite"></div>
</div>
<div class="wtr-replacer-form-group">
<label><input type="checkbox" id="wtr-case-sensitive"> Case Sensitive</label>
<label><input type="checkbox" id="wtr-is-regex"> Use Regex</label>
<label><input type="checkbox" id="wtr-whole-word" disabled> Whole Word Only</label>
</div>
<button id="wtr-save-btn" class="btn btn-primary">Save Term</button>
</div>
<div id="wtr-tab-io" class="wtr-replacer-tab-content">
<input type="file" id="wtr-file-input" accept=".json" style="display: none;">
<div class="wtr-replacer-io-section">
<h3>Export</h3>
<div class="wtr-replacer-io-actions" style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap;">
<button id="wtr-export-novel-btn" class="btn btn-success">Export Novel Terms</button>
<button id="wtr-export-all-btn" class="btn btn-success">Export All Terms</button>
<button id="wtr-export-combined-btn" class="btn btn-info wtr-export-combined">Export Both (Novel + All)</button>
</div>
</div>
<div class="wtr-replacer-io-section">
<h3>Import</h3>
<div class="wtr-replacer-io-actions" style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap;">
<button id="wtr-import-novel-btn" class="btn btn-warning">Import to This Novel</button>
<button id="wtr-import-all-btn" class="btn btn-warning">Import All (Global)</button>
</div>
</div>
</div>
</div>
`;
const UI_CSS = `
/* --- Google Material Symbols --- */
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0');
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
font-size: 16px;
vertical-align: middle;
}
/* --- Main UI Container --- */
.wtr-replacer-ui {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 90%; max-width: 650px; max-height: 80vh;
background-color: var(--bs-body-bg); color: var(--bs-body-color);
border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius-lg);
box-shadow: var(--bs-box-shadow-lg); z-index: 99999;
display: none; flex-direction: column; font-family: var(--bs-body-font-family);
}
.wtr-replacer-ui * { box-sizing: border-box; }
/* --- Header --- */
.wtr-replacer-header {
padding: 0.75rem 1rem; background-color: var(--bs-tertiary-bg);
border-bottom: 1px solid var(--bs-border-color);
display: flex; justify-content: space-between; align-items: center;
border-radius: var(--bs-border-radius-lg) var(--bs-border-radius-lg) 0 0;
}
.wtr-replacer-header h2 { margin: 0; font-size: 1.25rem; }
.wtr-replacer-header-controls { display: flex; align-items: center; gap: 1rem; }
.wtr-replacer-disable-toggle label { display: flex; align-items: center; cursor: pointer; font-size: 0.9rem; }
.wtr-replacer-disable-toggle input { margin-right: 0.5rem; }
.wtr-replacer-close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; line-height: 1; color: inherit; padding: 0; }
/* --- Tabs --- */
.wtr-replacer-tabs { display: flex; padding: 0 0.5rem; border-bottom: 1px solid var(--bs-border-color); background-color: var(--bs-tertiary-bg); }
.wtr-replacer-tab-btn {
background: none; border: none; padding: 0.75rem 1rem; cursor: pointer;
font-size: 0.9rem; color: var(--bs-secondary-color);
border-bottom: 3px solid transparent; margin-bottom: -1px;
}
.wtr-replacer-tab-btn.active { color: var(--bs-primary); border-bottom-color: var(--bs-primary); font-weight: bold; }
/* --- Content & Forms --- */
.wtr-replacer-content { padding: 1rem; overflow-y: auto; flex-grow: 1; position: relative; }
.wtr-replacer-tab-content { display: none; }
.wtr-replacer-tab-content.active { display: block; }
.wtr-replacer-form-group { margin-bottom: 1rem; }
.wtr-replacer-form-group label { display: block; margin-bottom: 0.5rem; font-weight: bold; font-size: 0.9rem; }
.wtr-replacer-form-group input[type="text"], .wtr-replacer-search-bar {
width: 100%; padding: 0.5rem 0.75rem;
background-color: var(--bs-body-bg); color: var(--bs-body-color);
border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius);
}
.wtr-replacer-form-group textarea {
width: 100%; padding: 0.5rem 0.75rem;
background-color: var(--bs-body-bg); color: var(--bs-body-color);
border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius);
resize: none;
min-height: 2.5rem; max-height: 10rem;
line-height: 1.5; font-family: inherit;
word-wrap: break-word; white-space: pre-wrap;
}
.wtr-replacer-form-group input[type="checkbox"] { margin-right: 0.5rem; }
/* --- Visual Validation States --- */
.wtr-replacer-form-group .wtr-field-invalid {
border-color: var(--bs-danger) !important;
background-color: rgba(var(--bs-danger-rgb), 0.1) !important;
box-shadow: 0 0 0 0.2rem rgba(var(--bs-danger-rgb), 0.25);
}
.wtr-replacer-form-group .wtr-field-valid {
border-color: var(--bs-success) !important;
background-color: rgba(var(--bs-success-rgb), 0.1) !important;
}
.wtr-replacer-form-group .wtr-field-warning {
border-color: var(--bs-warning) !important;
background-color: rgba(var(--bs-warning-rgb), 0.12) !important;
box-shadow: 0 0 0 0.2rem rgba(var(--bs-warning-rgb), 0.2);
}
.wtr-regex-disabled-warning {
display: block;
margin-top: 0.35rem;
color: var(--bs-warning-text-emphasis, var(--bs-warning));
}
.wtr-save-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: var(--bs-secondary);
border-color: var(--bs-secondary);
}
/* --- Buttons (Scoped to UI) --- */
.wtr-replacer-ui .btn {
display: inline-block; font-weight: 400; line-height: 1.5; color: var(--bs-body-color);
text-align: center; vertical-align: middle; cursor: pointer; user-select: none;
background-color: transparent; border: 1px solid transparent;
padding: 0.375rem 0.75rem; font-size: 1rem; border-radius: var(--bs-border-radius);
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.wtr-replacer-ui .btn:disabled { opacity: 0.65; cursor: not-allowed; }
.wtr-replacer-ui .btn-primary { color: #fff; background-color: var(--bs-primary); border-color: var(--bs-primary); }
.wtr-replacer-ui .btn-secondary { color: #fff; background-color: var(--bs-secondary); border-color: var(--bs-secondary); }
.wtr-replacer-ui .btn-success { color: #fff; background-color: var(--bs-success); border-color: var(--bs-success); }
.wtr-replacer-ui .btn-warning { color: #000; background-color: var(--bs-warning); border-color: var(--bs-warning); }
.wtr-replacer-ui .btn-info { color: #fff; background-color: var(--bs-info); border-color: var(--bs-info); }
.wtr-replacer-ui .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
.wtr-refresh-suggestions-btn { margin-top: 0.35rem; }
.wtr-replacement-suggestions { margin-top: 0.35rem; }
.wtr-replacement-suggestion-buttons { display: flex; gap: 0.35rem; flex-wrap: wrap; margin-top: 0.35rem; }
.wtr-replacement-suggestion-btn { display: inline-flex !important; align-items: center; gap: 0.35rem; }
.wtr-replacement-suggestion-btn.wtr-suggestion-existing {
background-color: var(--bs-success) !important;
border-color: var(--bs-success) !important;
color: #fff !important;
}
.wtr-replacement-suggestion-source { opacity: 0.85; font-size: 0.72rem; }
.wtr-replacer-popover-actions { display: flex; flex-direction: column; gap: 0.25rem; }
.wtr-replacer-popover-add-btn { white-space: nowrap; }
/* --- Term List --- */
.wtr-replacer-list-controls {
display: flex; justify-content: space-between; align-items: center;
gap: 0.75rem; position: sticky; top: -1rem;
background-color: var(--bs-body-bg); padding: 0.75rem 0; z-index: 10;
flex-wrap: wrap;
}
.wtr-replacer-term-list { list-style: none; padding: 0; margin: 0; }
.wtr-replacer-term-item {
padding: 0.75rem; border: 1px solid var(--bs-border-color);
border-radius: var(--bs-border-radius); margin-bottom: 0.5rem;
display: flex; align-items: center; gap: 0.75rem;
background-color: var(--bs-secondary-bg-subtle);
}
.wtr-replacer-term-details { flex-grow: 1; overflow: hidden; }
.wtr-replacer-term-text { font-family: var(--bs-font-monospace); font-size: 0.9rem; word-wrap: break-word; }
.wtr-term-original { color: var(--bs-danger) !important; font-weight: bold; }
.wtr-term-replacement { color: var(--bs-success) !important; font-weight: bold; }
/* --- Floating Button --- */
.wtr-add-term-float-btn {
position: fixed; bottom: 20px; right: 20px;
background-color: var(--bs-primary); color: white;
padding: 0.75rem 1.25rem; border-radius: 50rem; border: none;
box-shadow: var(--bs-box-shadow); cursor: pointer; font-size: 1rem; z-index: 99998; display: none;
}
/* --- Overlays & Loaders --- */
.wtr-processing-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); color: white;
display: flex; justify-content: center; align-items: center;
font-size: 1.5rem; z-index: 100000; display: none;
}
.wtr-ui-loader {
display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(var(--bs-body-bg-rgb), 0.7); color: var(--bs-body-color);
justify-content: center; align-items: center; z-index: 20;
}
/* --- Duplicate Mode & Pagination --- */
.wtr-dup-message { margin-bottom: 0.75rem; font-weight: bold; }
.wtr-dup-controls { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
.wtr-pagination-controls {
display: flex; justify-content: center; align-items: center; margin-top: 1rem; gap: 0.25rem;
flex-wrap: wrap; padding: 0.5rem 0;
}
.wtr-pagination-controls .btn {
padding: 0.25rem 0.5rem; font-size: 0.875rem; min-width: 2.5rem;
}
#wtr-page-indicator {
white-space: nowrap; margin: 0 0.5rem; font-size: 0.875rem;
padding: 0.25rem 0.5rem; background-color: var(--bs-secondary-bg-subtle);
border-radius: var(--bs-border-radius);
}
/* --- Enhanced Export Button Styling --- */
.wtr-export-combined {
background: linear-gradient(45deg, var(--bs-success), #28a745);
border: none;
color: white;
font-weight: bold;
position: relative;
overflow: hidden;
}
.wtr-export-combined:hover {
background: linear-gradient(45deg, #28a745, var(--bs-success));
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
.wtr-export-combined:active {
transform: translateY(0);
}
/* --- Global Menu Button Alignment Fix --- */
/* Fix menu button alignment - remove margin-right from Term Settings for all devices */
.bottom-reader-nav .menu-button.small > span {
margin-right: 0 !important;
}
/* --- Responsive Design (Mobile First) --- */
#wtr-tab-terms.active {
display: flex;
flex-direction: column;
}
.wtr-replacer-list-controls { order: 1; }
.wtr-pagination-controls { order: 2; margin-top: 0.5rem; margin-bottom: 0.5rem; }
.wtr-replacer-term-list { order: 3; }
/* --- Mobile Specific Improvements --- */
@media (max-width: 768px) {
.wtr-replacer-ui {
width: 95%; max-height: 85vh;
top: 2.5%; left: 2.5%; transform: none;
}
.wtr-replacer-content {
padding: 0.5rem;
}
.wtr-replacer-list-controls {
flex-direction: column; align-items: stretch; gap: 0.5rem;
}
.wtr-replacer-list-controls input[type="text"] {
order: 1;
}
.wtr-replacer-list-controls .btn {
order: 2; margin: 0;
}
.wtr-dup-controls {
justify-content: center;
}
.wtr-pagination-controls {
justify-content: center;
gap: 0.125rem;
}
.wtr-pagination-controls .btn {
padding: 0.375rem 0.5rem;
font-size: 0.8rem;
min-width: 2rem;
}
#wtr-page-indicator {
font-size: 0.8rem;
margin: 0 0.25rem;
padding: 0.25rem;
}
.wtr-replacer-term-item {
padding: 0.5rem;
gap: 0.5rem;
}
.wtr-replacer-term-text {
font-size: 0.8rem;
}
}
@media (min-width: 769px) {
.wtr-replacer-list-controls { order: 1; }
.wtr-replacer-term-list { order: 2; }
.wtr-pagination-controls { order: 3; margin-top: 1rem; margin-bottom: 0; }
}
`;
function createUI() {
if (document.querySelector(".wtr-replacer-ui")) {
return;
}
GM_addStyle(UI_CSS);
const uiContainer = document.createElement("div");
uiContainer.className = "wtr-replacer-ui";
uiContainer.innerHTML = UI_HTML;
document.body.appendChild(uiContainer);
const processingOverlay = document.createElement("div");
processingOverlay.className = "wtr-processing-overlay";
processingOverlay.textContent = "Processing...";
document.body.appendChild(processingOverlay);
// Event Listeners
uiContainer.querySelector(".wtr-replacer-close-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .hideUIPanel */ .X);
uiContainer.querySelector("#wtr-disable-all").addEventListener("change", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleDisableToggle */ .ts);
uiContainer.querySelector("#wtr-save-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleSaveTerm */ .s7);
uiContainer.querySelector("#wtr-refresh-suggestions-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleRefreshSuggestionsClick */ .Zw);
uiContainer.querySelector("#wtr-replacement-suggestions").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleReplacementSuggestionClick */ .JB);
uiContainer.querySelector("#wtr-delete-selected-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleDeleteSelected */ .Jm);
uiContainer.querySelector("#wtr-search-bar").addEventListener("input", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleSearch */ .RX);
uiContainer.querySelector(".wtr-replacer-term-list").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleListInteraction */ .VM);
uiContainer
.querySelectorAll(".wtr-replacer-tab-btn")
.forEach((btn) => btn.addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleTabSwitch */ .Qk));
uiContainer.querySelector("#wtr-export-novel-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleExportNovel */ .b7);
uiContainer.querySelector("#wtr-export-all-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleExportAll */ .ym);
uiContainer.querySelector("#wtr-export-combined-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleExportCombined */ .ow);
uiContainer.querySelector("#wtr-import-novel-btn").addEventListener("click", () => {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.importType = "novel";
document.getElementById("wtr-file-input").click();
});
uiContainer.querySelector("#wtr-import-all-btn").addEventListener("click", () => {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.importType = "all";
document.getElementById("wtr-file-input").click();
});
uiContainer.querySelector("#wtr-file-input").addEventListener("change", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleFileImport */ .kF);
uiContainer.querySelector("#wtr-find-duplicates-btn").addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleFindDuplicates */ .y$);
uiContainer.querySelector("#wtr-prev-dup-btn").addEventListener("click", () => (0,_duplicates__WEBPACK_IMPORTED_MODULE_2__/* .changeDupGroup */ .DP)(-1));
uiContainer.querySelector("#wtr-next-dup-btn").addEventListener("click", () => (0,_duplicates__WEBPACK_IMPORTED_MODULE_2__/* .changeDupGroup */ .DP)(1));
uiContainer.querySelector("#wtr-exit-dup-btn").addEventListener("click", _duplicates__WEBPACK_IMPORTED_MODULE_2__/* .exitDupMode */ .bj);
document.addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleWtrTextPatchClick */ .Xq, true);
document.addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleWtrPopoverAddTermClick */ .cD);
const wtrPopoverObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof Element) {
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .enhanceWtrTermPopovers */ .mA(node);
}
});
});
});
wtrPopoverObserver.observe(document.body, { childList: true, subtree: true });
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .enhanceWtrTermPopovers */ .mA(document);
// Add scroll event listener to save term list location
const contentArea = uiContainer.querySelector(".wtr-replacer-content");
if (contentArea) {
let scrollTimeout;
contentArea.addEventListener("scroll", () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
if (document.querySelector(".wtr-replacer-tab-btn.active").dataset.tab === "terms") {
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .saveTermListLocation */ .R6();
}
}, 1000); // Save after 1 second of inactivity
});
}
// Character-based auto-resize for original text field
const regexCheckbox = uiContainer.querySelector("#wtr-is-regex");
const caseSensitiveCheckbox = uiContainer.querySelector("#wtr-case-sensitive");
const wholeWordCheckbox = uiContainer.querySelector("#wtr-whole-word");
regexCheckbox.addEventListener("change", (e) => {
wholeWordCheckbox.disabled = e.target.checked;
if (e.target.checked) {
wholeWordCheckbox.checked = false;
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .normalizeOriginalRegexField */ .AO();
}
});
const originalTextarea = uiContainer.querySelector("#wtr-original");
function autoResizeTextarea() {
if (!originalTextarea) {
return;
}
const text = originalTextarea.value;
const charCount = text.length;
const lines = Math.ceil(charCount / 40);
const maxLines = Infinity;
const finalLines = Math.min(lines, maxLines);
originalTextarea.rows = Math.max(1, finalLines);
}
originalTextarea.addEventListener("input", autoResizeTextarea);
originalTextarea.addEventListener("input", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleReplacementSuggestionInput */ .l9);
originalTextarea.addEventListener("focus", autoResizeTextarea);
originalTextarea.addEventListener("focus", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleSuggestionTargetFocus */ .U8);
regexCheckbox.addEventListener("change", () => _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleReplacementSuggestionInput */ .l9({ target: originalTextarea, mergeExisting: true }));
caseSensitiveCheckbox.addEventListener("change", () => _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleReplacementSuggestionInput */ .l9({ target: originalTextarea, mergeExisting: true }));
// Real-time regex validation system
const saveButton = uiContainer.querySelector("#wtr-save-btn");
const replacementInput = uiContainer.querySelector("#wtr-replacement");
replacementInput.addEventListener("focus", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleSuggestionTargetFocus */ .U8);
function updateValidationVisual(state) {
// Remove all validation classes
originalTextarea.classList.remove("wtr-field-invalid", "wtr-field-valid", "wtr-field-warning");
if (state === "invalid") {
originalTextarea.classList.add("wtr-field-invalid");
}
else if (state === "valid") {
originalTextarea.classList.add("wtr-field-valid");
}
else if (state === "warning") {
originalTextarea.classList.add("wtr-field-warning");
}
}
function looksLikeRegexSyntax(value) {
return /(^|[^\\])\|/.test(value) || /\\[bBdDsSwW]/.test(value) || /\[[^\]]+\]/.test(value) || /\([^)]*\|[^)]*\)/.test(value) || /\.\*/.test(value) || /[+*?{}^$]/.test(value);
}
function validateAndUpdateUI() {
const isRegexEnabled = regexCheckbox.checked;
const originalText = originalTextarea.value.trim();
const replacementText = replacementInput.value.trim();
const isValidInput = originalText.length > 0 && replacementText.length > 0;
const regexWarning = document.getElementById("wtr-regex-disabled-warning");
const shouldWarnRegexDisabled = !isRegexEnabled && originalText.length > 0 && looksLikeRegexSyntax(originalText);
if (regexWarning) {
regexWarning.style.display = shouldWarnRegexDisabled ? "block" : "none";
}
if (!isRegexEnabled || originalText.length === 0) {
// Not a regex or empty field, clear validation state unless the text looks like regex syntax.
updateValidationVisual(shouldWarnRegexDisabled ? "warning" : null);
saveButton.disabled = !isValidInput;
return;
}
// Validate regex pattern
const validation = _handlers__WEBPACK_IMPORTED_MODULE_1__/* .validateRegexSilent */ .fA(originalText);
if (validation.isValid) {
updateValidationVisual("valid");
saveButton.disabled = !isValidInput;
}
else {
updateValidationVisual("invalid");
saveButton.disabled = true;
}
}
// Add real-time validation listeners
originalTextarea.addEventListener("input", validateAndUpdateUI);
replacementInput.addEventListener("input", validateAndUpdateUI);
regexCheckbox.addEventListener("change", validateAndUpdateUI);
// Initial validation state
validateAndUpdateUI();
// Create floating action button
const addTermFloatBtn = document.createElement("button");
addTermFloatBtn.className = "wtr-add-term-float-btn";
addTermFloatBtn.textContent = "Add Term";
document.body.appendChild(addTermFloatBtn);
addTermFloatBtn.addEventListener("click", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleAddTermFromSelection */ .az);
document.addEventListener("mouseup", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleTextSelection */ .Me);
document.addEventListener("touchend", _handlers__WEBPACK_IMPORTED_MODULE_1__/* .handleTextSelection */ .Me);
// Pagination Listeners
uiContainer.querySelector("#wtr-first-page-btn").addEventListener("click", () => {
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage > 1) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage = 1;
renderTermList(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue);
}
});
uiContainer.querySelector("#wtr-prev-page-btn").addEventListener("click", () => {
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage > 1) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage--;
renderTermList(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue);
}
});
uiContainer.querySelector("#wtr-next-page-btn").addEventListener("click", () => {
const filteredTerms = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.filter((t) => t.original.toLowerCase().includes(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue.toLowerCase()) ||
t.replacement.toLowerCase().includes(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue.toLowerCase()));
const totalPages = Math.ceil(filteredTerms.length / _config__WEBPACK_IMPORTED_MODULE_4__/* .ITEMS_PER_PAGE */ .re) || 1;
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage < totalPages) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage++;
renderTermList(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue);
}
});
uiContainer.querySelector("#wtr-last-page-btn").addEventListener("click", () => {
const filteredTerms = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.filter((t) => t.original.toLowerCase().includes(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue.toLowerCase()) ||
t.replacement.toLowerCase().includes(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue.toLowerCase()));
const totalPages = Math.ceil(filteredTerms.length / _config__WEBPACK_IMPORTED_MODULE_4__/* .ITEMS_PER_PAGE */ .re) || 1;
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage < totalPages) {
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage = totalPages;
renderTermList(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue);
}
});
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: UI created successfully");
}
function showProcessingIndicator(show) {
const overlay = document.querySelector(".wtr-processing-overlay");
if (overlay) {
overlay.style.display = show ? "flex" : "none";
}
}
function showUILoader() {
const loader = document.getElementById("wtr-ui-loader");
if (loader) {
loader.style.display = "flex";
}
const content = document.querySelector(".wtr-replacer-content");
if (content) {
content.style.pointerEvents = "none";
}
}
function hideUILoader() {
const loader = document.getElementById("wtr-ui-loader");
if (loader) {
loader.style.display = "none";
}
const content = document.querySelector(".wtr-replacer-content");
if (content) {
content.style.pointerEvents = "auto";
}
}
function renderTermList(filter = "") {
const listEl = document.querySelector(".wtr-replacer-term-list");
const paginationControls = document.querySelector(".wtr-pagination-controls");
const pageIndicator = document.getElementById("wtr-page-indicator");
const firstBtn = document.getElementById("wtr-first-page-btn");
const prevBtn = document.getElementById("wtr-prev-page-btn");
const nextBtn = document.getElementById("wtr-next-page-btn");
const lastBtn = document.getElementById("wtr-last-page-btn");
const contentArea = document.querySelector(".wtr-replacer-content");
if (!listEl || !paginationControls || !pageIndicator || !prevBtn || !nextBtn || !firstBtn || !lastBtn) {
return;
}
// Capture current scroll position before re-rendering
const previousScrollTop = contentArea ? contentArea.scrollTop : 0;
const shouldRestoreScroll = previousScrollTop > 0 && !_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.isDupMode && filter === _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentSearchValue;
listEl.innerHTML = "";
let filteredTerms;
let termsToRender;
if (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.isDupMode) {
const currentKey = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys[_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex];
filteredTerms = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupGroups.get(currentKey) || [];
document.getElementById("wtr-dup-message").textContent = `Duplicate group ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex + 1} of ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length} — ${currentKey}`;
document.getElementById("wtr-dup-message").style.display = "block";
document.getElementById("wtr-dup-controls").style.display = "flex";
document.getElementById("wtr-prev-dup-btn").disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex === 0;
document.getElementById("wtr-next-dup-btn").disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentDupIndex === _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.dupKeys.length - 1;
document.getElementById("wtr-search-bar").disabled = true;
paginationControls.style.display = "none";
termsToRender = filteredTerms;
}
else {
const filterLower = filter.toLowerCase();
filteredTerms = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.filter((t) => t.original.toLowerCase().includes(filterLower) || t.replacement.toLowerCase().includes(filterLower));
document.getElementById("wtr-dup-message").style.display = "none";
document.getElementById("wtr-dup-controls").style.display = "none";
document.getElementById("wtr-search-bar").disabled = false;
const totalPages = Math.ceil(filteredTerms.length / _config__WEBPACK_IMPORTED_MODULE_4__/* .ITEMS_PER_PAGE */ .re) || 1;
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage = Math.max(1, Math.min(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage, totalPages));
const start = (_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage - 1) * _config__WEBPACK_IMPORTED_MODULE_4__/* .ITEMS_PER_PAGE */ .re;
const end = start + _config__WEBPACK_IMPORTED_MODULE_4__/* .ITEMS_PER_PAGE */ .re;
termsToRender = filteredTerms.slice(start, end);
if (totalPages > 1) {
paginationControls.style.display = "flex";
pageIndicator.textContent = `Page ${_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage} of ${totalPages}`;
firstBtn.disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage === 1;
prevBtn.disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage === 1;
nextBtn.disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage === totalPages;
lastBtn.disabled = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.currentPage === totalPages;
}
else {
paginationControls.style.display = "none";
}
}
if (termsToRender.length === 0) {
listEl.innerHTML = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.terms.length === 0 ? "<li>No terms defined.</li>" : "<li>No terms match search.</li>";
}
else {
const fragment = document.createDocumentFragment();
termsToRender.forEach((term) => {
const li = document.createElement("li");
li.className = "wtr-replacer-term-item";
li.dataset.id = term.id;
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.className = "wtr-replacer-term-select";
checkbox.dataset.id = term.id;
li.appendChild(checkbox);
const details = document.createElement("div");
details.className = "wtr-replacer-term-details";
const termText = document.createElement("div");
termText.className = "wtr-replacer-term-text";
const originalSpan = document.createElement("span");
originalSpan.className = "wtr-term-original";
originalSpan.textContent = term.original;
const replacementSpan = document.createElement("span");
replacementSpan.className = "wtr-term-replacement";
replacementSpan.textContent = term.replacement;
termText.appendChild(originalSpan);
termText.appendChild(document.createTextNode(" → "));
termText.appendChild(replacementSpan);
details.appendChild(termText);
const flags = document.createElement("div");
if (term.caseSensitive) {
const badge = document.createElement("small");
badge.textContent = "CS";
flags.appendChild(badge);
flags.appendChild(document.createTextNode(" "));
}
if (term.isRegex) {
const badge = document.createElement("small");
badge.textContent = "RX";
flags.appendChild(badge);
flags.appendChild(document.createTextNode(" "));
}
if (term.wholeWord) {
const badge = document.createElement("small");
badge.textContent = "WW";
flags.appendChild(badge);
}
details.appendChild(flags);
li.appendChild(details);
const actionWrap = document.createElement("div");
const editButton = document.createElement("button");
editButton.type = "button";
editButton.className = "btn btn-secondary btn-sm wtr-edit-btn";
editButton.dataset.id = term.id;
editButton.textContent = "Edit";
actionWrap.appendChild(editButton);
li.appendChild(actionWrap);
fragment.appendChild(li);
});
listEl.appendChild(fragment);
}
// Restore scroll position after DOM update if it was captured
if (shouldRestoreScroll && contentArea) {
// Use requestAnimationFrame to ensure DOM has been updated
requestAnimationFrame(() => {
contentArea.scrollTop = previousScrollTop;
});
}
}
function showUIPanel() {
const ui = document.querySelector(".wtr-replacer-ui");
ui.style.display = "flex";
document.getElementById("wtr-disable-all").checked = _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.settings.isDisabled;
// Restore saved location when showing the terms tab
if (document.querySelector(".wtr-replacer-tab-btn.active").dataset.tab === "terms") {
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .restoreTermListLocation */ .s3();
}
else {
renderTermList();
}
}
function hideUIPanel() {
// Save current location before hiding
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .saveTermListLocation */ .R6();
document.querySelector(".wtr-replacer-ui").style.display = "none";
clearTermList();
}
function clearTermList() {
const listEl = document.querySelector(".wtr-replacer-term-list");
if (listEl) {
listEl.innerHTML = "";
}
}
function showFormView(term = null) {
if (!term) {
_handlers__WEBPACK_IMPORTED_MODULE_1__/* .clearDiscoveryFormState */ .nS();
}
document.getElementById("wtr-term-id").value = term ? term.id : "";
document.getElementById("wtr-original").value = term ? term.original : "";
document.getElementById("wtr-replacement").value = term ? term.replacement : "";
document.getElementById("wtr-case-sensitive").checked = term ? term.caseSensitive : false;
document.getElementById("wtr-is-regex").checked = term ? term.isRegex : false;
document.getElementById("wtr-whole-word").checked = term ? term.wholeWord : false;
document.getElementById("wtr-whole-word").disabled = term ? term.isRegex : false;
document.getElementById("wtr-save-btn").textContent = term ? "Update Term" : "Save Term";
switchTab("add");
// Initialize auto-resize after form is populated
setTimeout(() => {
const originalTextarea = document.getElementById("wtr-original");
if (originalTextarea) {
const text = originalTextarea.value;
const charCount = text.length;
const lines = Math.ceil(charCount / 40);
originalTextarea.rows = Math.max(1, lines);
}
// Re-initialize validation state for the form
const regexCheckbox = document.getElementById("wtr-is-regex");
if (regexCheckbox) {
const validationEvent = new Event("input", { bubbles: true });
originalTextarea.dispatchEvent(validationEvent);
}
}, 10);
}
function switchTab(tabName) {
document.querySelector(`.wtr-replacer-tab-btn[data-tab="${tabName}"]`).click();
}
// Simple function to create menu buttons with inline SVG icons
function createSimpleMenuButton(options) {
const { text = "Settings", onClick = null, className = "", tooltip = "" } = options;
const button = document.createElement("button");
button.className = `replacer-settings-btn ${className}`;
if (tooltip) {
button.title = tooltip;
}
// Create settings icon using the specified SVG
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svg.setAttribute("height", "24px");
svg.setAttribute("viewBox", "0 -960 960 960");
svg.setAttribute("width", "24px");
svg.setAttribute("fill", "#1f1f1f");
svg.style.marginRight = "4px";
svg.style.verticalAlign = "middle";
svg.innerHTML =
'<path d="M700-120h40v-100h100v-40H740v-100h-40v100H600v40h100v100Zm20 80q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM280-600h400v-80H280v80Zm187 480H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v268q-29-14-58.5-21t-61.5-7q-11 0-20.5.5T680-517v-3H280v80h245q-18 17-32.5 37T467-360H280v80h163q-2 10-2.5 19.5T440-240q0 33 6 61.5t21 58.5Z"/>';
button.appendChild(svg);
// Add text
const textSpan = document.createElement("span");
textSpan.textContent = text;
button.appendChild(textSpan);
// Add click handler
if (onClick) {
button.addEventListener("click", onClick);
}
return button;
}
function addMenuButton() {
const container = document.querySelector("div.col-6:has(button.term-edit-btn)");
if (!container || _state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.observedMenuContainers.has(container)) {
return;
}
const ensureButtonState = () => {
let settingsButton = container.querySelector(".replacer-settings-btn");
const originalButton = container.querySelector(".term-edit-btn:not(.replacer-settings-btn)");
// 1. Create the button if it doesn't exist
if (!settingsButton) {
if (!originalButton) {
return;
} // Can't create if the original doesn't exist yet
// Create button with simple inline SVG icon
settingsButton = createSimpleMenuButton({
text: "Term Settings",
onClick: showUIPanel,
className: originalButton.className, // Copy classes for styling
tooltip: "Open WTR Term Settings",
});
container.appendChild(settingsButton);
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Settings button created with simple icon system.");
}
// 2. Enforce the correct order (our button should be last)
if (container.lastChild !== settingsButton) {
container.appendChild(settingsButton);
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Settings button order corrected.");
}
// 3. Apply consistent styling
if (originalButton && settingsButton) {
const desiredFlexStyle = "1 1 0%";
container.style.display = "flex";
container.style.gap = "5px";
originalButton.style.flex = desiredFlexStyle;
settingsButton.style.flex = desiredFlexStyle;
}
};
// Run once immediately
ensureButtonState();
// Observe for any changes and re-run to correct the state
const observer = new MutationObserver(() => {
(0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .log */ .Rm)(_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.globalSettings, "WTR Term Replacer: Detected change in menu container, ensuring button state.");
ensureButtonState();
});
observer.observe(container, { childList: true });
// Mark this container as observed to prevent re-attaching observers
_state__WEBPACK_IMPORTED_MODULE_0__/* .state */ .wk.observedMenuContainers.add(container);
}
/***/ },
/***/ 158
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ Nt: () => (/* binding */ escapeRegExp),
/* harmony export */ Rm: () => (/* binding */ log),
/* harmony export */ Ug: () => (/* binding */ getChapterIdFromUrl),
/* harmony export */ getNovelSlug: () => (/* binding */ getNovelSlug),
/* harmony export */ o7: () => (/* binding */ getReaderContextFromPath)
/* harmony export */ });
/* unused harmony exports debounce, detectOtherWTRScripts, logDOMConflict, logProcessingWithMultiScriptContext, startProcessingTimer, endProcessingTimer, isContentReadyForProcessing, isElementReadyForProcessing, estimateContentLoadLevel */
/* harmony import */ var _config_versions__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(387);
/* harmony import */ var _config_versions__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_config_versions__WEBPACK_IMPORTED_MODULE_0__);
// Utility functions for WTR Lab Term Replacer
function getReaderContextFromPath(pathname = window.location.pathname) {
const parts = pathname.split("/").filter(Boolean);
const novelIndex = parts.indexOf("novel");
if (novelIndex >= 0) {
return {
lang: novelIndex > 0 ? parts[0] || "en" : "en",
rawId: parts[novelIndex + 1] || null,
novelSlug: parts[novelIndex + 2] || null,
chapterSlug: parts[novelIndex + 3] || null,
};
}
const serieIndex = parts.findIndex((part) => /^serie-\d+$/.test(part));
if (serieIndex >= 0) {
const rawId = parts[serieIndex].replace(/^serie-/, "") || null;
return {
lang: serieIndex > 0 ? parts[0] || "en" : "en",
rawId,
novelSlug: parts[serieIndex + 1] || null,
chapterSlug: parts[serieIndex + 2] || null,
};
}
return {
lang: parts[0] || "en",
rawId: null,
novelSlug: null,
chapterSlug: null,
};
}
function getNovelSlug() {
return getReaderContextFromPath().novelSlug;
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
}
function debounce(func, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
function getChapterIdFromUrl(url) {
const match = url.match(/(chapter-\d+)/);
return match ? match[1] : null;
}
function log(globalSettings, ...args) {
if (globalSettings && globalSettings.isLoggingEnabled) {
console.log(...args);
}
}
const _CURRENT_VERSION = (0,_config_versions__WEBPACK_IMPORTED_MODULE_0__.getVersion)();
// --- [ENHANCED ${CURRENT_VERSION}] MULTI-SCRIPT COORDINATION FUNCTIONS ---
function detectOtherWTRScripts() {
// Detect other WTR Lab scripts by their data attributes or specific patterns
const scripts = document.querySelectorAll("[data-smart-quotes-processed], [data-uncensor-processed], [data-auto-scroll], [data-reader-enhanced]");
const otherWTRScripts = new Set();
scripts.forEach((el) => {
if (el.hasAttribute("data-smart-quotes-processed")) {
otherWTRScripts.add("Smart Quotes");
}
if (el.hasAttribute("data-uncensor-processed")) {
otherWTRScripts.add("Uncensor");
}
if (el.hasAttribute("data-auto-scroll") || el.hasAttribute("data-reader-enhanced")) {
otherWTRScripts.add("Reader Enhancer");
}
});
if (otherWTRScripts.size > 0) {
log(null, `WTR Term Replacer: Multi-script environment detected - Active scripts: ${Array.from(otherWTRScripts).join(", ")}`);
}
return otherWTRScripts;
}
function logDOMConflict(sourceScript, element, processingQueue, chapterId) {
const timestamp = new Date().toISOString();
const conflictInfo = {
timestamp,
sourceScript,
element: element.tagName,
elementId: element.id || "no-id",
processingQueueSize: processingQueue ? processingQueue.size : 0,
chapterId: chapterId,
};
log(null, `WTR Term Replacer: DOM conflict detected with ${sourceScript} script`, conflictInfo);
}
function logProcessingWithMultiScriptContext(chapterId, processingTime, isMultiScript = false, otherWTRScripts, processingQueue) {
const context = {
chapterId,
processingTime: `${processingTime}ms`,
multiScriptEnvironment: isMultiScript,
activeScripts: otherWTRScripts ? otherWTRScripts.size : 0,
queueSize: processingQueue ? processingQueue.size : 0,
timestamp: new Date().toISOString(),
};
if (isMultiScript && otherWTRScripts && otherWTRScripts.size > 0) {
context.activeScripts = Array.from(otherWTRScripts);
log(null, `WTR Term Replacer: Multi-script enhanced processing completed`, context);
}
else {
log(null, `WTR Term Replacer: Standard processing completed`, context);
}
}
function startProcessingTimer(operation, processingStartTime) {
processingStartTime.set(operation, Date.now());
}
function endProcessingTimer(operation, chapterId, processingStartTime, otherWTRScripts, processingQueue) {
const startTime = processingStartTime.get(operation);
if (startTime) {
const processingTime = Date.now() - startTime;
const isMultiScript = otherWTRScripts && otherWTRScripts.size > 0;
log(null, `WTR Term Replacer: Processing timer ended for ${operation}, took ${processingTime}ms`);
logProcessingWithMultiScriptContext(chapterId, processingTime, isMultiScript, otherWTRScripts, processingQueue);
processingStartTime.delete(operation);
return processingTime;
}
log(null, `WTR Term Replacer: Warning - processing timer for ${operation} not found`);
return 0;
}
// Content readiness check for enhanced content processing
function isContentReadyForProcessing(container) {
// Multiple readiness criteria for robust detection
const hasSubstantialContent = container.textContent?.trim().length > 100;
const hasNoActiveLoaders = !container.querySelector('.loading, .spinner, [style*="loading"], .skeleton');
const isVisible = container.offsetWidth > 0 && container.offsetHeight > 0;
const hasChapterContent = container.querySelector(".chapter-body") || container.querySelector("p, h1, h2, h3, h4, h5, h6");
return hasSubstantialContent && hasNoActiveLoaders && isVisible && hasChapterContent;
}
// Element readiness check for robust processing
function isElementReadyForProcessing(element) {
// Check if element is visible and has substantial content
const rect = element.getBoundingClientRect();
const isVisible = rect.width > 0 && rect.height > 0;
const hasSubstantialContent = element.textContent?.trim().length > 50;
const hasNoLoadingStates = !element.querySelector('.loading, .spinner, [style*="display: none"]');
return isVisible && hasSubstantialContent && hasNoLoadingStates;
}
// Enhanced content load level estimation for retry mechanisms
function estimateContentLoadLevel(chapterBody) {
// Estimate how much content is loaded based on text density and structure
const textNodes = chapterBody.querySelectorAll("p, h1, h2, h3, h4, h5, h6, div, span");
const totalTextLength = Array.from(textNodes).reduce((total, node) => total + (node.textContent?.trim().length || 0), 0);
// Check for loading indicators or placeholder content
const hasLoadingIndicators = chapterBody.querySelector('.loading, .spinner, [style*="loading"], [class*="loading"]');
const hasPlaceholderContent = chapterBody.textContent?.includes("Loading...") ||
chapterBody.textContent?.includes("loading") ||
chapterBody.textContent?.includes("...");
// Calculate load level based on content density and absence of loading indicators
let loadLevel = Math.min(totalTextLength / 1000, 1.0); // Normalize to 0-1 based on 1000 chars
if (hasLoadingIndicators || hasPlaceholderContent) {
loadLevel *= 0.3; // Reduce load level if loading indicators present
}
// Ensure minimum threshold for processing
return Math.max(loadLevel, totalTextLength > 100 ? 0.5 : 0.1);
}
/***/ },
/***/ 330
(module) {
"use strict";
module.exports = /*#__PURE__*/JSON.parse('{"name":"wtr-lab-term-replacer-webpack","version":"5.7.0","description":"A modular, Webpack-powered TypeScript version of the WTR Lab Term Replacer userscript.","author":"MasuRii","license":"MIT","private":true,"main":"dist/wtr-lab-term-replacer-webpack.user.js","repository":{"type":"git","url":"https://github.com/MasuRii/wtr-lab-term-replacer-webpack.git"},"bugs":{"url":"https://github.com/MasuRii/wtr-lab-term-replacer-webpack/issues"},"keywords":["term","replacement","wtr-lab","userscript","modular","webpack"],"files":["dist/","src/"],"scripts":{"build":"npm run version:update && npm run typecheck && webpack --mode=production","build:performance":"npm run typecheck && webpack --config webpack.config.js --config-name performance --mode=production","build:greasyfork":"npm run typecheck && webpack --config webpack.config.js --config-name greasyfork --mode=production","build:devbundle":"npm run typecheck && webpack --config webpack.config.js --config-name dev --mode=development","dev":"webpack serve --config webpack.config.js --config-name dev --mode=development","typecheck":"tsc --noEmit","test":"node scripts/run-tests.js","version:update":"node scripts/update-versions.js update","version:check":"node scripts/update-versions.js check"},"devDependencies":{"@types/tampermonkey":"^5.0.5","ts-loader":"^9.5.7","typescript":"^6.0.3","webpack":"^5.106.2","webpack-cli":"^7.0.2","webpack-dev-server":"^5.2.3","webpack-userscript":"^3.2.3"}}');
/***/ }
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
(() => {
"use strict";
/* harmony import */ var _modules_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(141);
/* harmony import */ var _modules_storage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(694);
/* harmony import */ var _modules_observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(405);
/* harmony import */ var _modules_state__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(654);
/* harmony import */ var _modules_handlers__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(359);
/* harmony import */ var _modules_utils__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(158);
// Main entry point for WTR Lab Term Replacer
// Import all handlers
// Function to get chapter ID from URL (for module compatibility)
function getChapterIdFromUrl(url) {
const match = url.match(/(chapter-\d+)/);
return match ? match[1] : null;
}
// Enhanced error handling setup
function setupEnhancedErrorHandling() {
// Global error handler to catch and log any issues
window.addEventListener("error", (event) => {
if (event.error && event.error.message && event.error.message.includes("WTR")) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Caught error:", event.error);
}
});
// Handle unhandled promise rejections
window.addEventListener("unhandledrejection", (event) => {
if (event.reason && event.reason.message && event.reason.message.includes("WTR")) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Unhandled promise rejection:", event.reason);
}
});
// Cleanup function for when page unloads
window.addEventListener("beforeunload", () => {
_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.processingQueue.clear();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Cleanup on page unload");
});
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Enhanced error handling activated");
}
// Enhanced navigation handling setup
function setupEnhancedNavigationHandling() {
// Enhanced URL change detection with proper debouncing and coordination
let isNavigationInProgress = false;
const processNavigationSafely = () => {
if (isNavigationInProgress) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Navigation already in progress, skipping");
return;
}
isNavigationInProgress = true;
// Clear processing queue for new chapter
_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.processingQueue.clear();
// Wait for content to load with enhanced detection
setTimeout(() => {
Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 405)).then(({ processVisibleChapter }) => {
processVisibleChapter();
});
isNavigationInProgress = false;
}, 500); // Increased delay to allow DOM updates and prevent conflicts
};
// Set up navigation event listeners for SPA-style navigation
window.addEventListener("popstate", () => {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Popstate event detected");
processNavigationSafely();
});
// Handle pushState/replaceState (SPA navigation)
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function (...args) {
const result = originalPushState.apply(this, args);
processNavigationSafely();
return result;
};
history.replaceState = function (...args) {
const result = originalReplaceState.apply(this, args);
processNavigationSafely();
return result;
};
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Enhanced navigation handling activated");
}
// Enhanced disable functionality that works reliably
function addDisableAllRobustness() {
// Enhanced disable functionality with proper error handling
const handleDisableToggleRobust = async function (e) {
const wasDisabled = _modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.settings.isDisabled;
const shouldDisable = e.target.checked;
// Update settings immediately
_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.settings.isDisabled = shouldDisable;
await (await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 694))).saveSettings(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.settings);
// Perform robust disable/enable operation
const chapterId = getChapterIdFromUrl(window.location.href);
if (!chapterId) {
return;
}
const chapterSelector = `#${chapterId} .chapter-body`;
const chapterBody = document.querySelector(chapterSelector);
if (chapterBody) {
try {
const { performReplacements, revertAllReplacements } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, 9));
if (shouldDisable) {
// Disable: revert all replacements
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Robust disable - reverting all replacements");
await revertAllReplacements(chapterBody);
chapterBody.dataset.wtrProcessed = "false";
}
else {
// Enable: perform replacements with retry
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Robust enable - performing replacements");
await performReplacements(chapterBody);
chapterBody.dataset.wtrProcessed = "true";
}
}
catch (error) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, `WTR Term Replacer: Error during ${shouldDisable ? "disable" : "enable"} operation:`, error);
// Reset checkbox on error
e.target.checked = wasDisabled;
_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.settings.isDisabled = wasDisabled;
}
}
};
// Replace the existing event listener with our robust version
const uiContainer = document.querySelector(".wtr-replacer-ui");
if (uiContainer) {
const disableCheckbox = uiContainer.querySelector("#wtr-disable-all");
if (disableCheckbox) {
// Remove existing listener and add our enhanced one
const newDisableCheckbox = disableCheckbox.cloneNode(true);
disableCheckbox.parentNode.replaceChild(newDisableCheckbox, disableCheckbox);
newDisableCheckbox.addEventListener("change", handleDisableToggleRobust);
}
}
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Enhanced disable functionality activated");
}
function registerExternalIntegrationBridge() {
if (window.__WTR_TERM_REPLACER_BRIDGE_REGISTERED__) {
const existingApi = window.WTR_LAB_TERM_REPLACER || {};
window.WTR_LAB_TERM_REPLACER = {
...existingApi,
ready: true,
bridgeVersion: 1,
};
return;
}
window.__WTR_TERM_REPLACER_BRIDGE_REGISTERED__ = true;
const existingApi = window.WTR_LAB_TERM_REPLACER || {};
window.WTR_LAB_TERM_REPLACER = {
...existingApi,
ready: true,
bridgeVersion: 1,
};
window.addEventListener("wtr:addTerm", (event) => {
const { original, replacement, isRegex } = event?.detail || {};
_modules_handlers__WEBPACK_IMPORTED_MODULE_4__/* .addTermProgrammatically */ .IY(original, replacement, isRegex);
});
window.addEventListener("wtr:requestTerms", async (event) => {
const requestId = event?.detail?.requestId;
const requestedSlug = event?.detail?.novelSlug || _modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.novelSlug || (0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__.getNovelSlug)();
if (!requestId) {
return;
}
try {
const terms = requestedSlug === _modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.novelSlug ? [..._modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.terms] : await (0,_modules_storage__WEBPACK_IMPORTED_MODULE_1__.getTermsForSlug)(requestedSlug);
window.dispatchEvent(new CustomEvent("wtr:termsResponse", {
detail: {
requestId,
novelSlug: requestedSlug,
terms,
source: "wtr-term-replacer",
success: true,
},
}));
}
catch (error) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Failed to provide terms to external requester", error);
window.dispatchEvent(new CustomEvent("wtr:termsResponse", {
detail: {
requestId,
novelSlug: requestedSlug,
terms: [],
source: "wtr-term-replacer",
success: false,
error: error instanceof Error ? error.message : String(error),
},
}));
}
});
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: External integration bridge registered");
}
async function main() {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Main function starting initialization...");
// Initialize state and validate novel slug
const novelSlug = (0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__.getNovelSlug)();
if (!novelSlug) {
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Critical error - could not determine novel slug");
console.error("WTR Term Replacer: Could not determine novel slug.");
return;
}
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, `WTR Term Replacer: Novel slug determined: ${novelSlug}`);
(0,_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .setNovelSlug */ .oP)(novelSlug);
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Loading global settings and data...");
await (0,_modules_storage__WEBPACK_IMPORTED_MODULE_1__.loadGlobalSettings)();
await (0,_modules_storage__WEBPACK_IMPORTED_MODULE_1__.loadData)();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, `WTR Term Replacer: Data loaded - terms: ${_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.terms.length}, settings disabled: ${_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.settings.isDisabled}`);
// Enhanced initialization with robustness features
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Setting up error handling...");
setupEnhancedErrorHandling();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Setting up disable functionality...");
addDisableAllRobustness();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Setting up navigation handling...");
setupEnhancedNavigationHandling();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Creating UI and menu commands...");
(0,_modules_ui__WEBPACK_IMPORTED_MODULE_0__/* .createUI */ .RD)(); // This will also set up the initial event listeners
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Registering menu commands...");
GM_registerMenuCommand("Term Replacer Settings", _modules_ui__WEBPACK_IMPORTED_MODULE_0__/* .showUIPanel */ .E1);
GM_registerMenuCommand("Toggle Logging", _modules_handlers__WEBPACK_IMPORTED_MODULE_4__/* .toggleLogging */ .o6);
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Starting initial content detection...");
(0,_modules_observer__WEBPACK_IMPORTED_MODULE_2__/* .waitForInitialContent */ ._)();
(0,_modules_utils__WEBPACK_IMPORTED_MODULE_5__/* .log */ .Rm)(_modules_state__WEBPACK_IMPORTED_MODULE_3__/* .state */ .wk.globalSettings, "WTR Term Replacer: Initialization completed successfully");
}
registerExternalIntegrationBridge();
// Start the script
main().catch((err) => console.error("WTR Term Replacer failed to start:", err));
})();
/******/ })()
;