pangu
Version:
Opinionated paranoid text spacing: automatically inserts whitespace between CJK (Chinese, Japanese, Korean) and ANS (alphabetical letters, numerical digits and symbols)
921 lines (920 loc) • 41.3 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define([], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.pangu = factory());
})(this, function() {
//#region src/shared/index.ts
var CJK_RADICALS_SUPPLEMENT = "⺀-";
var KANGXI_RADICALS = "⼀-";
var HIRAGANA = "-ゟ";
var KATAKANA_NO_MIDDLE_DOT = "゠-ヺー-ヿ";
var BOPOMOFO = "-ㄯ";
var ENCLOSED_CJK_LETTERS_AND_MONTHS = "㈀-㋿";
var CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A = "㐀-䶿";
var CJK_UNIFIED_IDEOGRAPHS = "一-鿿";
var CJK_COMPATIBILITY_IDEOGRAPHS = "豈-";
var GREEK_AND_COPTIC = "Ͱ-Ͽ";
var LATIN_1_SUPPLEMENT_AFTER_NBSP = "¡-ÿ";
var NUMBER_FORMS = "⅐-";
var LETTERLIKE_SYMBOLS = "℀-⅏";
var DINGBATS = "✀-➿";
var SUPERSCRIPT_SUFFIXES = "®²³¹⁰ⁱ⁴-⁼⁾ⁿ℠™";
var CJK = `${CJK_RADICALS_SUPPLEMENT}${KANGXI_RADICALS}${HIRAGANA}${KATAKANA_NO_MIDDLE_DOT}${BOPOMOFO}${ENCLOSED_CJK_LETTERS_AND_MONTHS}${CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A}${CJK_UNIFIED_IDEOGRAPHS}${CJK_COMPATIBILITY_IDEOGRAPHS}`;
var AN = "A-Za-z0-9";
var A = "A-Za-z";
var UPPER_AN = "A-Z0-9";
var OPERATORS = "\\*=&\\-";
var GRADE_OPERATORS = "\\+\\-\\*";
var QUOTES = "`\"״";
var LEFT_BRACKETS_BASIC = "\\(\\[\\{";
var RIGHT_BRACKETS_BASIC = "\\)\\]\\}";
var LEFT_BRACKETS_EXTENDED = "\\(\\[\\{<>“";
var RIGHT_BRACKETS_EXTENDED = "\\)\\]\\}<>”";
var ANS_CJK_AFTER = `${A}${GREEK_AND_COPTIC}0-9@\\$%\\^&\\*\\-\\+\\\\=${LATIN_1_SUPPLEMENT_AFTER_NBSP}${NUMBER_FORMS}${DINGBATS}${LETTERLIKE_SYMBOLS}`;
var ANS_BEFORE_CJK = `${A}${GREEK_AND_COPTIC}0-9\\$%\\^&\\*\\-\\+\\\\=${LATIN_1_SUPPLEMENT_AFTER_NBSP}${NUMBER_FORMS}${DINGBATS}${LETTERLIKE_SYMBOLS}${SUPERSCRIPT_SUFFIXES}`;
var FILE_PATH_DIRS = "home|root|usr|etc|var|opt|tmp|dev|mnt|proc|sys|bin|boot|lib|media|run|sbin|srv|node_modules|path|project|src|dist|test|tests|docs|templates|assets|public|static|config|scripts|tools|build|out|target|your|\\.claude|\\.git|\\.vscode";
var FILE_PATH_CHARS = "[A-Za-z0-9_\\-\\.@\\+\\*]+";
var UNIX_ABSOLUTE_FILE_PATH = new RegExp(`/(?:\\.?(?:${FILE_PATH_DIRS})|\\.(?:[A-Za-z0-9_\\-]+))(?:/${FILE_PATH_CHARS})*`);
var UNIX_RELATIVE_FILE_PATH = new RegExp(`(?:\\./)?(?:${FILE_PATH_DIRS})(?:/${FILE_PATH_CHARS})+`);
var WINDOWS_FILE_PATH = /[A-Z]:\\(?:[A-Za-z0-9_\-\. ]+\\?)+/;
var ANY_CJK = new RegExp(`[${CJK}]`);
var CJK_PUNCTUATION = new RegExp(`([${CJK}])([!;,\\?:]+)(?=[${CJK}${AN}])`, "g");
var PUNCTUATION_CJK = new RegExp(`([!;,\\?]+)(?=[${CJK}])`, "g");
var CJK_TILDE = new RegExp(`([${CJK}])(~+)(?!=)(?=[${CJK}${AN}])`, "g");
var CJK_TILDE_EQUALS = new RegExp(`([${CJK}])(~=)`, "g");
var CJK_PERIOD = new RegExp(`([${CJK}])(\\.)(?![${AN}\\./])(?=[${CJK}${AN}])`, "g");
var AN_PERIOD_CJK = new RegExp(`([${AN}])(\\.)([${CJK}])`, "g");
var AN_COLON_CJK = new RegExp(`([${AN}])(:)([${CJK}])`, "g");
var FIX_CJK_COLON_ANS = new RegExp(`([${CJK}])\\:([${UPPER_AN}\\(\\)])`, "g");
var DOTS_CJK = new RegExp(`([\\.]{2,}|\u2026)([${CJK}])`, "g");
var CJK_QUOTE = new RegExp(`([${CJK}])([${QUOTES}])`, "g");
var QUOTE_CJK = new RegExp(`([${QUOTES}])([${CJK}])`, "g");
var FIX_QUOTE_ANY_QUOTE = new RegExp(`([${QUOTES}]+)[ ]*([\\s\\S]+?)[ ]*([${QUOTES}]+)`, "g");
var QUOTE_AN = new RegExp(`([\u201d])([${AN}])`, "g");
var CJK_QUOTE_AN = new RegExp(`([${CJK}])(")([${AN}])`, "g");
var CJK_SINGLE_QUOTE_BUT_POSSESSIVE = new RegExp(`([${CJK}])('[^s])`, "g");
var SINGLE_QUOTE_CJK = new RegExp(`(')([${CJK}])`, "g");
var FIX_POSSESSIVE_SINGLE_QUOTE = new RegExp(`([${AN}${CJK}])( )('s)`, "g");
var SINGLE_QUOTE_PURE_CJK = new RegExp(`(')([${CJK}]+)(')`, "g");
var HASH_ANS_CJK_HASH = new RegExp(`([${CJK}])(#)([${CJK}]+)(#)([${CJK}])`, "g");
var CJK_HASH = new RegExp(`([${CJK}])(#([^ \\u00a0]))`, "g");
var HASH_CJK = new RegExp(`(([^ \\u00a0/])#)([${CJK}])`, "g");
var NAME_SUFFIX = `(?:(?<![A-Za-z0-9])(?:(?:Apple TV|CATCHPLAY|[Dd]iscovery|Disney|ESPN|Fitness|iCloud|mo ?店|Paramount|PS|Pro)\\+|(?:(?:tw)?(?:AA|BBB|BB|CCC)|tw[AB]|AB|RhD|Rh)[+-])|(?:公視|影劇館)\\+)`;
var NAME_SUFFIX_AT_END = new RegExp(`${NAME_SUFFIX}$`);
var CLOSING_AFTER_SUFFIX = /[/)\]}\uff09\u3011\u3015\u3009\u300b\u300d\u300f\uff0c\u3002\u3001\uff1b\uff1a\uff01\uff1f]/;
var CJK_OPERATOR_ANS = new RegExp(`([${CJK}])([${OPERATORS}])([${AN}])`, "g");
var ANS_OPERATOR_CJK = new RegExp(`([${AN}${RIGHT_BRACKETS_BASIC}])([${OPERATORS}])(?<!${NAME_SUFFIX})([${CJK}])`, "g");
var PIPE_CJK_CONTACT = new RegExp(`[${CJK}]\\||\\|[${CJK}]`);
var PIPE_SEPARATOR = /([^\s|])[ ]*(\|+)[ ]*(?=[^\s|])/g;
var PLUS_CJK_CONTACT = new RegExp(`[${CJK}]\\+|\\+[${CJK}]`);
var PLUS_SEPARATOR = /(?<=[^\s+\uff0c\u3002\uff1b\uff1a\uff01\uff1f\u3001\uff08\uff09\u300c\u300d\u300e\u300f\u3010\u3011\u300a\u300b])\+(?=[^\s+\uff0c\u3002\uff1b\uff1a\uff01\uff1f\u3001\uff08\uff09\u300c\u300d\u300e\u300f\u3010\u3011\u300a\u300b])/g;
var RIGHT_BRACKET_PLUS_FULL_WIDTH_LEFT_BRACKET = new RegExp(`(?<=[${RIGHT_BRACKETS_BASIC}])\\+(?=[\\uff08\\u300c\\u300e\\u3010\\u300a])`, "g");
var SINGLE_LETTER_GRADE_CJK = new RegExp(`\\b([${A}])([${GRADE_OPERATORS}])([${CJK}])`, "g");
var CJK_SIGN_DIGIT = new RegExp(`([${CJK}])(\\+)([0-9])`, "g");
var CJK_HYPHEN_FLAG = new RegExp(`([${CJK}])(\\-)([a-z])\\b`, "g");
var DIGIT_PLUS_CJK = new RegExp(`\\b([0-9]+)(\\+)([${CJK}])`, "g");
var CJK_LESS_THAN = new RegExp(`([${CJK}])(<)([${AN}])`, "g");
var LESS_THAN_CJK = new RegExp(`([${AN}])(<)([${CJK}])`, "g");
var CJK_GREATER_THAN = new RegExp(`([${CJK}])(>)([${AN}])`, "g");
var GREATER_THAN_CJK = new RegExp(`([${AN}])(>)([${CJK}])`, "g");
var CJK_LEFT_BRACKET = new RegExp(`([${CJK}])([${LEFT_BRACKETS_EXTENDED}])`, "g");
var RIGHT_BRACKET_CJK = new RegExp(`([${RIGHT_BRACKETS_EXTENDED}])([${CJK}])`, "g");
var ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET = new RegExp(`([${AN}${CJK}])[ ]*([\u201c])([${AN}${CJK}\\-_ ]+)([\u201d])`, "g");
var LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK = new RegExp(`([\u201c])([${AN}${CJK}\\-_ ]+)([\u201d])[ ]*([${AN}${CJK}])`, "g");
var ANS_CJK_RIGHT_QUOTE_ANY_RIGHT_QUOTE = new RegExp(`([${AN}${CJK}])[ ]*(?<![\u201c][^\u201c\u201d\n]*)([\u201d])[ ]*([${AN}${CJK}\\-_ ]+?)[ ]*([\u201d])`, "g");
var AN_LEFT_BRACKET = new RegExp(`([${AN}])(?<!\\.[${AN}]*)([${LEFT_BRACKETS_BASIC}])`, "g");
var RIGHT_BRACKET_AN = new RegExp(`([${RIGHT_BRACKETS_BASIC}])([${AN}])`, "g");
var CJK_UNIX_ABSOLUTE_FILE_PATH = new RegExp(`([${CJK}])(${UNIX_ABSOLUTE_FILE_PATH.source})`, "g");
var CJK_UNIX_RELATIVE_FILE_PATH = new RegExp(`([${CJK}])(${UNIX_RELATIVE_FILE_PATH.source})`, "g");
var CJK_WINDOWS_PATH = new RegExp(`([${CJK}])(${WINDOWS_FILE_PATH.source})`, "g");
var UNIX_ABSOLUTE_FILE_PATH_SLASH_CJK = new RegExp(`(${UNIX_ABSOLUTE_FILE_PATH.source}/)([${CJK}])`, "g");
var UNIX_RELATIVE_FILE_PATH_SLASH_CJK = new RegExp(`(${UNIX_RELATIVE_FILE_PATH.source}/)([${CJK}])`, "g");
var CJK_ANS = new RegExp(`([${CJK}])(?![${SUPERSCRIPT_SUFFIXES}])([${ANS_CJK_AFTER}])(?<!${NAME_SUFFIX})`, "g");
var ANS_CJK = new RegExp(`([${ANS_BEFORE_CJK}])([${CJK}])`, "g");
var S_A = new RegExp(`(%)([${A}])`, "g");
var COPYRIGHT_DIGIT = /(\u00a9)([0-9])/g;
var MIDDLE_DOT = /([ ]*)([\u00b7\u2022\u2027])([ ]*)/g;
var VOID_HTML_TAGS = /* @__PURE__ */ new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr"
]);
var BARE_HTML_TAG = /^<([a-zA-Z][a-zA-Z0-9]*)\s*\/?>$/;
var CLOSING_HTML_TAG = /<\/([a-zA-Z][a-zA-Z0-9]*)/g;
var CJK_HTML_TAG_MENTION = new RegExp(`([${CJK}])(?=\uE004)`, "g");
var HTML_TAG_MENTION_CJK = new RegExp(`(?<=\uE005)([${CJK}])`, "g");
var HTTP_URL = /(?<![A-Za-z0-9])https?:\/\/[^\s<>"`\u3000-\u303f\uff00-\uffef\u2018\u2019\u201c\u201d\u2026\ue000-\uf8ff]+/g;
var HTTP_URL_TRAILING_PUNCTUATION = /[.,;:!?'"]+$/;
var CJK_HTTP_URL = new RegExp(`([${CJK}])(?=\uE00A)`, "g");
function trimHttpUrl(url) {
let unbalancedClosingParentheses = (url.match(/\)/g) ?? []).length - (url.match(/\(/g) ?? []).length;
for (;;) {
const trimmed = url.replace(HTTP_URL_TRAILING_PUNCTUATION, "");
if (trimmed.endsWith(")") && unbalancedClosingParentheses > 0) {
unbalancedClosingParentheses--;
url = trimmed.slice(0, -1);
continue;
}
if (trimmed === url) return url;
url = trimmed;
}
}
var BRACKET_PATTERNS = [
{
pattern: /<([^<>]*)>/g,
open: "<",
close: ">"
},
{
pattern: /\(([^()]*)\)/g,
open: "(",
close: ")"
},
{
pattern: /\[([^\[\]]*)\]/g,
open: "[",
close: "]"
},
{
pattern: /\{([^{}]*)\}/g,
open: "{",
close: "}"
}
];
var PlaceholderReplacer = class PlaceholderReplacer {
placeholder;
startDelimiter;
endDelimiter;
static patternCache = /* @__PURE__ */ new Map();
items = [];
index = 0;
pattern;
constructor(placeholder, startDelimiter, endDelimiter) {
this.placeholder = placeholder;
this.startDelimiter = startDelimiter;
this.endDelimiter = endDelimiter;
const cacheKey = `${startDelimiter}${placeholder}${endDelimiter}`;
let pattern = PlaceholderReplacer.patternCache.get(cacheKey);
if (!pattern) {
const escapedStart = this.startDelimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const escapedEnd = this.endDelimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
pattern = new RegExp(`${escapedStart}${this.placeholder}(\\d+)${escapedEnd}`, "g");
PlaceholderReplacer.patternCache.set(cacheKey, pattern);
}
this.pattern = pattern;
}
store(item) {
this.items[this.index] = item;
return `${this.startDelimiter}${this.placeholder}${this.index++}${this.endDelimiter}`;
}
restore(text) {
if (this.index === 0) return text;
return text.replace(this.pattern, (_match, index) => {
return this.items[parseInt(index, 10)] || "";
});
}
};
var Pangu = class {
version;
constructor() {
this.version = "10.2.0";
}
spaceText(text) {
if (typeof text !== "string") {
console.warn(`[pangu] spaceText(text) only accepts string but got ${typeof text}`);
return text;
}
if (text.length <= 1 || !ANY_CJK.test(text)) return text;
let newText = text;
const backtickManager = new PlaceholderReplacer("BACKTICK_CONTENT_", "", "");
newText = newText.replace(/`([^`]+)`/g, (_match, content) => {
return `\`${backtickManager.store(content)}\``;
});
const urlManager = new PlaceholderReplacer("HTTP_URL_PLACEHOLDER_", "", "");
newText = newText.replace(HTTP_URL, (match) => {
const url = trimHttpUrl(match);
return urlManager.store(url) + match.slice(url.length);
});
const htmlTagManager = new PlaceholderReplacer("HTML_TAG_PLACEHOLDER_", "", "");
const mentionedTagManager = new PlaceholderReplacer("HTML_TAG_MENTION_", "", "");
let hasHtmlTags = false;
if (newText.includes("<")) {
hasHtmlTags = true;
const HTML_TAG_PATTERN = /<\/?[a-zA-Z][a-zA-Z0-9]*(?:\s+[^>]*)?>/g;
const closedTagNames = /* @__PURE__ */ new Set();
for (const closingTag of newText.matchAll(CLOSING_HTML_TAG)) closedTagNames.add(closingTag[1].toLowerCase());
newText = newText.replace(HTML_TAG_PATTERN, (match) => {
const bareTag = match.match(BARE_HTML_TAG);
if (bareTag) {
const tagName = bareTag[1].toLowerCase();
if (!VOID_HTML_TAGS.has(tagName) && !closedTagNames.has(tagName)) return mentionedTagManager.store(match);
}
const processedTag = match.replace(/(\w+)="([^"]*)"/g, (_attrMatch, attrName, attrValue) => {
return `${attrName}="${this.spaceText(attrValue)}"`;
});
return htmlTagManager.store(processedTag);
});
}
newText = newText.replace(DOTS_CJK, "$1 $2");
newText = newText.replace(CJK_PUNCTUATION, "$1$2 ");
newText = newText.replace(PUNCTUATION_CJK, "$1 ");
newText = newText.replace(CJK_TILDE, "$1$2 ");
newText = newText.replace(CJK_TILDE_EQUALS, "$1 $2 ");
newText = newText.replace(CJK_PERIOD, "$1$2 ");
newText = newText.replace(AN_PERIOD_CJK, "$1$2 $3");
newText = newText.replace(AN_COLON_CJK, "$1$2 $3");
newText = newText.replace(FIX_CJK_COLON_ANS, "$1:$2");
newText = newText.replace(CJK_QUOTE, "$1 $2");
newText = newText.replace(QUOTE_CJK, "$1 $2");
newText = newText.replace(FIX_QUOTE_ANY_QUOTE, "$1$2$3");
newText = newText.replace(QUOTE_AN, "$1 $2");
newText = newText.replace(CJK_QUOTE_AN, "$1$2 $3");
newText = newText.replace(FIX_POSSESSIVE_SINGLE_QUOTE, "$1's");
const singleQuoteCJKManager = new PlaceholderReplacer("SINGLE_QUOTE_CJK_PLACEHOLDER_", "", "");
newText = newText.replace(SINGLE_QUOTE_PURE_CJK, (match) => {
return singleQuoteCJKManager.store(match);
});
newText = newText.replace(CJK_SINGLE_QUOTE_BUT_POSSESSIVE, "$1 $2");
newText = newText.replace(SINGLE_QUOTE_CJK, "$1 $2");
newText = singleQuoteCJKManager.restore(newText);
if (newText.length >= 5) newText = newText.replace(HASH_ANS_CJK_HASH, "$1 $2$3$4 $5");
newText = newText.replace(CJK_HASH, "$1 $2");
newText = newText.replace(HASH_CJK, "$1 $3");
const compoundWordManager = new PlaceholderReplacer("COMPOUND_WORD_PLACEHOLDER_", "", "");
newText = newText.replace(/\b(?:[A-Za-z0-9]*[a-z][A-Za-z0-9]*-[A-Za-z0-9]+|[A-Za-z0-9]+-[A-Za-z0-9]*[a-z][A-Za-z0-9]*|[A-Za-z]+-[0-9]+|[A-Za-z]+[0-9]+-[A-Za-z0-9]+)(?:-[A-Za-z0-9]+)*\b/g, (match) => {
return compoundWordManager.store(match);
});
newText = newText.replace(SINGLE_LETTER_GRADE_CJK, "$1$2 $3");
newText = newText.replace(CJK_SIGN_DIGIT, "$1 $2$3");
newText = newText.replace(CJK_HYPHEN_FLAG, "$1 $2$3");
newText = newText.replace(DIGIT_PLUS_CJK, "$1$2 $3");
newText = newText.split("\n").map((line) => {
return (PLUS_CJK_CONTACT.test(line) ? line.replace(PLUS_SEPARATOR, (_match, offset) => {
if (!NAME_SUFFIX_AT_END.test(compoundWordManager.restore(line.slice(0, offset + 1)))) return " + ";
return CLOSING_AFTER_SUFFIX.test(line[offset + 1] ?? "") ? "+" : "+ ";
}) : line).replace(RIGHT_BRACKET_PLUS_FULL_WIDTH_LEFT_BRACKET, " +");
}).join("\n");
newText = newText.replace(CJK_OPERATOR_ANS, "$1 $2 $3");
newText = newText.replace(ANS_OPERATOR_CJK, "$1 $2 $3");
newText = newText.replace(CJK_LESS_THAN, "$1 $2 $3");
newText = newText.replace(LESS_THAN_CJK, "$1 $2 $3");
newText = newText.replace(CJK_GREATER_THAN, "$1 $2 $3");
newText = newText.replace(GREATER_THAN_CJK, "$1 $2 $3");
newText = newText.replace(CJK_UNIX_ABSOLUTE_FILE_PATH, "$1 $2");
newText = newText.replace(CJK_UNIX_RELATIVE_FILE_PATH, "$1 $2");
newText = newText.replace(CJK_WINDOWS_PATH, "$1 $2");
newText = newText.replace(UNIX_ABSOLUTE_FILE_PATH_SLASH_CJK, "$1 $2");
newText = newText.replace(UNIX_RELATIVE_FILE_PATH_SLASH_CJK, "$1 $2");
newText = newText.split("\n").map((line) => {
if (!PIPE_CJK_CONTACT.test(line)) return line;
return line.replace(PIPE_SEPARATOR, "$1 $2 ");
}).join("\n");
newText = newText.replace(FIX_QUOTE_ANY_QUOTE, "$1$2$3");
newText = compoundWordManager.restore(newText);
newText = newText.replace(CJK_LEFT_BRACKET, "$1 $2");
newText = newText.replace(RIGHT_BRACKET_CJK, "$1 $2");
newText = newText.replace(ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET, "$1 $2$3$4");
newText = newText.replace(LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK, "$1$2$3 $4");
newText = newText.replace(ANS_CJK_RIGHT_QUOTE_ANY_RIGHT_QUOTE, "$1 $2$3$4");
newText = newText.replace(AN_LEFT_BRACKET, "$1 $2");
newText = newText.replace(RIGHT_BRACKET_AN, "$1 $2");
newText = newText.replace(CJK_ANS, "$1 $2");
newText = newText.replace(ANS_CJK, "$1 $2");
newText = newText.replace(S_A, "$1 $2");
newText = newText.replace(COPYRIGHT_DIGIT, "$1 $2");
newText = newText.replace(MIDDLE_DOT, "・");
newText = this.fixBracketSpacing(newText);
if (hasHtmlTags) {
newText = newText.replace(CJK_HTML_TAG_MENTION, "$1 ");
newText = newText.replace(HTML_TAG_MENTION_CJK, " $1");
newText = mentionedTagManager.restore(newText);
newText = htmlTagManager.restore(newText);
}
newText = newText.replace(CJK_HTTP_URL, "$1 ");
newText = urlManager.restore(newText);
newText = backtickManager.restore(newText);
return newText;
}
hasProperSpacing(text) {
return this.spaceText(text) === text;
}
fixBracketSpacing(text) {
for (const { pattern, open, close } of BRACKET_PATTERNS) text = text.replace(pattern, (_match, innerContent) => {
if (!innerContent) return `${open}${close}`;
const trimmedContent = innerContent.replace(/^ +| +$/g, "");
return `${open}${trimmedContent}${close}`;
});
return text;
}
};
var pangu$1 = new Pangu();
//#endregion
//#region src/browser/dom/boundary-spacing.ts
var QUOTE = /["\u201c\u201d]/;
function decideBoundarySpacing(boundarySpacingContext) {
if (boundarySpacingContext.spaceLikeSiblingAfterCurrent) return "none";
if (boundarySpacingContext.currentEndsWithSpace || boundarySpacingContext.nextStartsWithSpace || boundarySpacingContext.whitespaceBetween) return "none";
if (boundarySpacingContext.contentBetween) return "none";
if (!needsBoundarySpace(boundarySpacingContext.currentTail, boundarySpacingContext.nextFirst)) return "none";
if (boundarySpacingContext.spaceLikeSiblingAfterCurrentBoundary || boundarySpacingContext.currentBoundaryIsBlock) return "none";
if (!boundarySpacingContext.nextBoundaryIsSpaceSensitive) {
if (boundarySpacingContext.nextBoundaryIsIgnored || boundarySpacingContext.nextBoundaryIsBlock || boundarySpacingContext.spaceLikeSiblingBeforeNext || boundarySpacingContext.hiddenBoundaryBefore()) return "none";
return "prepend-next";
}
if (!boundarySpacingContext.currentBoundaryIsSpaceSensitive) {
if (boundarySpacingContext.hiddenBoundaryAfter()) return "none";
return "append-current";
}
if (boundarySpacingContext.spaceLikeSiblingBeforeNextBoundary || boundarySpacingContext.hiddenBoundaryAfter()) return "none";
if (boundarySpacingContext.inGridOrFlexContainer()) return "none";
return "insert-element";
}
function decideTextNodeSpacing(textNodeSpacingContext) {
const textNodeSpacingDecisions = [];
let { text } = textNodeSpacingContext;
if (text.startsWith(" ") && textNodeSpacingContext.hiddenBoundaryBefore()) {
textNodeSpacingDecisions.push("trim-leading-space");
text = text.substring(1);
}
if (isStandaloneQuote(text)) {
if (textNodeSpacingContext.previousElementLastChar !== null && ANY_CJK.test(textNodeSpacingContext.previousElementLastChar)) textNodeSpacingDecisions.push("prepend-space");
} else textNodeSpacingDecisions.push("apply-text-spacing");
return textNodeSpacingDecisions;
}
var spacedJunctionCache = /* @__PURE__ */ new Map();
var SPACED_JUNCTION_CACHE_MAX = 4096;
function spaceJunction(currentTail, nextFirst) {
const junction = `${currentTail}${nextFirst}`;
const cached = spacedJunctionCache.get(junction);
if (cached !== void 0) return cached;
const spacedJunction = pangu$1.spaceText(junction);
if (spacedJunctionCache.size >= SPACED_JUNCTION_CACHE_MAX) spacedJunctionCache.clear();
spacedJunctionCache.set(junction, spacedJunction);
return spacedJunction;
}
function needsBoundarySpace(currentTail, nextFirst) {
return spaceJunction(currentTail, nextFirst).endsWith(` ${nextFirst}`) && !isQuoteNextToCjk(currentTail.slice(-1), nextFirst);
}
function respaceCurrentTail(currentTail, nextFirst) {
const spacedJunction = spaceJunction(currentTail, nextFirst);
if (!spacedJunction.endsWith(` ${nextFirst}`)) return null;
const spacedTail = spacedJunction.slice(0, -2);
return spacedTail === currentTail ? null : spacedTail;
}
function isQuoteNextToCjk(currentLast, nextFirst) {
return QUOTE.test(currentLast) && ANY_CJK.test(nextFirst) || ANY_CJK.test(currentLast) && /["\u201d]/.test(nextFirst);
}
function isStandaloneQuote(text) {
return text.length === 1 && QUOTE.test(text);
}
//#endregion
//#region src/browser/dom/dom-walker.ts
var DomWalker = class {
static blockTags = /^(div|p|h1|h2|h3|h4|h5|h6)$/i;
static ignoredTags = /^(code|pre|script|style|textarea|iframe|input)$/i;
static spaceLikeTags = /^(br|hr|i|img|pangu)$/i;
static spaceSensitiveTags = /^(a|del|pre|s|strike|u)$/i;
static ignoredClass = "no-pangu-spacing";
static collectTextNodes(root, reverse = false) {
const nodes = [];
if (!root || root instanceof DocumentFragment) return nodes;
const walker = this.createTextWalker(root);
while (walker.nextNode()) nodes.push(walker.currentNode);
return reverse ? nodes.reverse() : nodes;
}
static findAdjacentTextNode(textNode, direction) {
let root = textNode;
while (root.parentNode && root !== document.body && !this.blockTags.test(root.nodeName)) root = root.parentNode;
const walker = this.createTextWalker(root);
walker.currentNode = textNode;
return direction === "previous" ? walker.previousNode() : walker.nextNode();
}
static findBoundaryNode(textNode, edge) {
let node = textNode;
while (node.parentNode && !this.spaceSensitiveTags.test(node.nodeName) && (edge === "first" ? this.isFirstTextChild(node.parentNode, node) : this.isLastTextChild(node.parentNode, node))) node = node.parentNode;
return node;
}
static isFirstTextChild(parentNode, targetNode) {
const { childNodes } = parentNode;
for (const childNode of childNodes) if (childNode.nodeType !== Node.COMMENT_NODE && childNode.textContent) return childNode === targetNode;
return false;
}
static isLastTextChild(parentNode, targetNode) {
const { childNodes } = parentNode;
for (let i = childNodes.length - 1; i > -1; i--) {
const childNode = childNodes[i];
if (childNode.nodeType !== Node.COMMENT_NODE && childNode.textContent) return childNode === targetNode;
}
return false;
}
static createTextWalker(root) {
return document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
if (!node.nodeValue || !/\S/.test(node.nodeValue)) return NodeFilter.FILTER_REJECT;
return this.isIgnoredNode(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
} });
}
static isIgnoredNode(node) {
let currentNode = node;
while (currentNode) {
if (currentNode instanceof Element && this.isIgnoredElement(currentNode)) return true;
currentNode = currentNode.parentNode;
}
return false;
}
static isIgnoredElement(element) {
return this.ignoredTags.test(element.nodeName) || this.isContentEditable(element) || element.classList.contains(this.ignoredClass);
}
static isContentEditable(node) {
return node instanceof HTMLElement && (node.isContentEditable || node.getAttribute("g_editable") === "true");
}
};
//#endregion
//#region src/browser/dom/visibility-detector.ts
var VisibilityDetector = class {
cache = /* @__PURE__ */ new WeakMap();
clearCache() {
this.cache = /* @__PURE__ */ new WeakMap();
}
isElementVisuallyHidden(element) {
const style = getComputedStyle(element);
if (style.display === "none") return true;
if (style.visibility === "hidden") return true;
if (parseFloat(style.opacity) === 0) return true;
const clip = style.clip;
if (clip && (clip.includes("rect(1px, 1px, 1px, 1px)") || clip.includes("rect(0px, 0px, 0px, 0px)") || clip.includes("rect(0, 0, 0, 0)"))) return true;
if (style.overflow === "hidden" && style.position === "absolute") {
const height = parseInt(style.height, 10);
const width = parseInt(style.width, 10);
if (height === 1 && width === 1) return true;
}
return false;
}
shouldSkipSpacingAfterNode(node) {
let elementToCheck = null;
if (node instanceof Element) elementToCheck = node;
else if (node.parentElement) elementToCheck = node.parentElement;
if (elementToCheck && this.isElementVisuallyHiddenCached(elementToCheck)) return true;
let currentElement = elementToCheck?.parentElement;
while (currentElement) {
if (this.isElementVisuallyHiddenCached(currentElement)) return true;
currentElement = currentElement.parentElement;
}
return false;
}
shouldSkipSpacingBeforeNode(node) {
let previousNode = node.previousSibling;
if (!previousNode && node.parentElement) {
let parent = node.parentElement;
while (parent && !previousNode) {
previousNode = parent.previousSibling;
if (!previousNode) parent = parent.parentElement;
}
}
if (previousNode) {
if (previousNode instanceof Element && this.isElementVisuallyHiddenCached(previousNode)) return true;
else if (previousNode instanceof Text && previousNode.parentElement && this.isElementVisuallyHiddenCached(previousNode.parentElement)) return true;
}
return false;
}
isElementVisuallyHiddenCached(element) {
const cached = this.cache.get(element);
if (cached !== void 0) return cached;
const hidden = this.isElementVisuallyHidden(element);
this.cache.set(element, hidden);
return hidden;
}
};
//#endregion
//#region src/browser/scheduling/task-scheduler.ts
var TaskQueue = class {
queue = [];
isProcessing = false;
add(task) {
this.queue.push(task);
this.scheduleProcessing();
}
clear() {
this.queue.length = 0;
}
get length() {
return this.queue.length;
}
scheduleProcessing() {
if (!this.isProcessing && this.queue.length > 0) {
this.isProcessing = true;
requestIdleCallback((deadline) => this.process(deadline), { timeout: 5e3 });
}
}
process(deadline) {
try {
while (deadline.timeRemaining() > 0 && this.queue.length > 0) this.queue.shift()?.();
} finally {
this.isProcessing = false;
if (this.queue.length > 0) this.scheduleProcessing();
}
}
};
/**
* Runs queued text spacing work during browser idle time to avoid blocking the UI.
* Tasks execute via requestIdleCallback when the browser has spare time,
* ensuring smooth user experience even when processing large amounts of text.
*/
var TaskScheduler = class {
config = {
enabled: true,
timeout: 2e3
};
taskQueue = new TaskQueue();
get queue() {
return this.taskQueue;
}
};
//#endregion
//#region src/browser/scheduling/timing.ts
function once(func) {
let executed = false;
return function(...args) {
if (executed) return;
executed = true;
return func(...args);
};
}
function debounce(func, delay, mustRunDelay = Infinity) {
let timer = null;
let startTime = null;
return function(...args) {
const currentTime = Date.now();
if (timer) clearTimeout(timer);
if (!startTime) startTime = currentTime;
if (currentTime - startTime >= mustRunDelay) {
func(...args);
startTime = currentTime;
} else timer = window.setTimeout(() => {
func(...args);
}, delay);
};
}
function waitForVideosToLoad(delayMs, onLoaded) {
const videos = Array.from(document.getElementsByTagName("video")).filter((video) => video.getClientRects().length > 0);
if (videos.length === 0) setTimeout(onLoaded, delayMs);
else if (videos.every((video) => video.readyState >= 3)) setTimeout(onLoaded, delayMs);
else {
let loadedCount = 0;
const videoCount = videos.length;
const checkAllLoaded = () => {
loadedCount++;
if (loadedCount >= videoCount) setTimeout(onLoaded, delayMs);
};
for (const video of videos) if (video.readyState >= 3) checkAllLoaded();
else video.addEventListener("loadeddata", checkAllLoaded, { once: true });
setTimeout(onLoaded, delayMs + 5e3);
}
}
//#endregion
//#region src/browser/pangu.ts
var TRAILING_WHITESPACE = /\s$/;
var LEADING_WHITESPACE = /^\s/;
var BrowserPangu = class BrowserPangu extends Pangu {
static maxSyncTextNodes = 256;
autoSpacePageObserver = null;
lastWrittenData = /* @__PURE__ */ new WeakMap();
lateFixedTextNodes = /* @__PURE__ */ new WeakSet();
taskScheduler = new TaskScheduler();
visibilityDetector = new VisibilityDetector();
onTextNodesSettled = null;
autoSpacePage({ pageDelayMs = 1e3, nodeDelayMs = 500, nodeMaxWaitMs = 2e3 } = {}) {
if (!(document.body instanceof Node)) return;
if (this.autoSpacePageObserver) return;
const observer = this.setupAutoSpacePageObserver(nodeDelayMs, nodeMaxWaitMs);
waitForVideosToLoad(pageDelayMs, once(() => {
if (this.autoSpacePageObserver === observer) this.spacePage();
}));
}
spacePage() {
const title = document.querySelector("head > title");
if (title) this.spaceNode(title);
this.spaceNode(document.body);
}
spaceNode(node) {
const textNodes = DomWalker.collectTextNodes(node, true);
this.schedule(() => this.spaceTextNodes(textNodes));
}
stopAutoSpacePage() {
if (this.autoSpacePageObserver) {
this.autoSpacePageObserver.disconnect();
this.autoSpacePageObserver = null;
}
}
applyLateFixes(lateFixes) {
this.schedule(() => {
for (const lateFix of lateFixes) {
if (!lateFix.node.isConnected || lateFix.node.data !== lateFix.settled || DomWalker.isIgnoredNode(lateFix.node)) continue;
lateFix.node.data = lateFix.data;
this.lastWrittenData.set(lateFix.node, lateFix.data);
this.lateFixedTextNodes.add(lateFix.node);
}
});
}
isSpaceLikeSibling(node) {
return !!node && DomWalker.spaceLikeTags.test(node.nodeName);
}
isGridOrFlexContainer(node) {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
const display = window.getComputedStyle(node).display;
return display === "grid" || display === "inline-grid" || display === "flex" || display === "inline-flex";
}
spaceTextNodes(textNodes) {
this.visibilityDetector.clearCache();
const unsettledTextNodes = [];
let nextTextNode = null;
for (const currentTextNode of textNodes) {
if (currentTextNode instanceof Text) if (this.holdsLateFix(currentTextNode)) {
if (this.onTextNodesSettled) unsettledTextNodes.push({
node: currentTextNode,
unspaced: currentTextNode.data
});
} else this.applyTextNodeSpacing(currentTextNode, unsettledTextNodes);
if (nextTextNode) {
if (!(currentTextNode instanceof Text) || !(nextTextNode instanceof Text)) continue;
const nextSuperscript = nextTextNode.parentElement?.closest("sup");
if (nextSuperscript && !nextSuperscript.contains(currentTextNode)) {
nextTextNode = currentTextNode;
continue;
}
const currentBoundaryNode = DomWalker.findBoundaryNode(currentTextNode, "last");
const nextBoundaryNode = DomWalker.findBoundaryNode(nextTextNode, "first");
const { whitespaceBetween, contentBetween } = this.scanBetweenTextNodes(currentBoundaryNode, nextBoundaryNode);
const currentNode = currentTextNode;
const nextNode = nextTextNode;
const currentTail = currentTextNode.data.match(/(?<![A-Za-z0-9])[A-Za-z0-9]{3,}[+-]$/)?.[0] ?? currentTextNode.data.slice(-3);
const nextFirst = nextTextNode.data.slice(0, 1);
const boundarySpacingDecision = decideBoundarySpacing({
currentTail,
nextFirst,
currentEndsWithSpace: TRAILING_WHITESPACE.test(currentTextNode.data),
nextStartsWithSpace: LEADING_WHITESPACE.test(nextTextNode.data),
whitespaceBetween,
contentBetween,
spaceLikeSiblingAfterCurrent: this.isSpaceLikeSibling(currentTextNode.nextSibling),
spaceLikeSiblingAfterCurrentBoundary: this.isSpaceLikeSibling(currentBoundaryNode.nextSibling),
spaceLikeSiblingBeforeNext: this.isSpaceLikeSibling(nextTextNode.previousSibling),
spaceLikeSiblingBeforeNextBoundary: this.isSpaceLikeSibling(nextBoundaryNode.previousSibling),
currentBoundaryIsBlock: DomWalker.blockTags.test(currentBoundaryNode.nodeName),
currentBoundaryIsSpaceSensitive: DomWalker.spaceSensitiveTags.test(currentBoundaryNode.nodeName) || currentTextNode.parentElement?.closest("sup")?.contains(nextTextNode) === false,
nextBoundaryIsBlock: DomWalker.blockTags.test(nextBoundaryNode.nodeName),
nextBoundaryIsIgnored: DomWalker.ignoredTags.test(nextBoundaryNode.nodeName),
nextBoundaryIsSpaceSensitive: DomWalker.spaceSensitiveTags.test(nextBoundaryNode.nodeName),
hiddenBoundaryBefore: () => this.isHiddenBoundaryBefore(nextNode),
hiddenBoundaryAfter: () => this.isHiddenBoundaryAfter(currentNode),
inGridOrFlexContainer: () => !!nextBoundaryNode.parentNode && this.isGridOrFlexContainer(nextBoundaryNode.parentNode)
});
if (boundarySpacingDecision !== "none" && !this.holdsLateFix(currentTextNode) && !NAME_SUFFIX_AT_END.test(currentTextNode.data)) {
const respacedTail = respaceCurrentTail(currentTail, nextFirst);
if (respacedTail !== null) {
currentTextNode.data = currentTextNode.data.slice(0, currentTextNode.data.length - currentTail.length) + respacedTail;
this.lastWrittenData.set(currentTextNode, currentTextNode.data);
}
}
switch (boundarySpacingDecision) {
case "prepend-next":
nextTextNode.data = ` ${nextTextNode.data}`;
this.lastWrittenData.set(nextTextNode, nextTextNode.data);
break;
case "append-current":
currentTextNode.data = `${currentTextNode.data} `;
this.lastWrittenData.set(currentTextNode, currentTextNode.data);
break;
case "insert-element":
this.insertPanguElement(nextBoundaryNode);
break;
case "none": break;
}
}
nextTextNode = currentTextNode;
}
this.emitTextNodesSettled(unsettledTextNodes);
}
holdsLateFix(textNode) {
return this.lateFixedTextNodes.has(textNode) && this.lastWrittenData.get(textNode) === textNode.data;
}
emitTextNodesSettled(unsettledTextNodes) {
if (unsettledTextNodes.length === 0) return;
const settledTextNodes = unsettledTextNodes.map(({ node, unspaced }) => ({
node,
unspaced,
settled: node.data
}));
this.onTextNodesSettled?.(settledTextNodes);
}
applyTextNodeSpacing(textNode, unsettledTextNodes) {
this.lateFixedTextNodes.delete(textNode);
const textNodeSpacingDecisions = decideTextNodeSpacing({
text: textNode.data,
previousElementLastChar: this.findPreviousElementLastChar(textNode),
hiddenBoundaryBefore: () => this.isHiddenBoundaryBefore(textNode)
});
for (const textNodeSpacingDecision of textNodeSpacingDecisions) switch (textNodeSpacingDecision) {
case "trim-leading-space":
textNode.data = textNode.data.substring(1);
this.lastWrittenData.set(textNode, textNode.data);
break;
case "prepend-space":
textNode.data = ` ${textNode.data}`;
this.lastWrittenData.set(textNode, textNode.data);
break;
case "apply-text-spacing": {
if (this.onTextNodesSettled) unsettledTextNodes.push({
node: textNode,
unspaced: textNode.data
});
const newText = this.spaceText(textNode.data);
if (textNode.data !== newText) {
textNode.data = newText;
this.lastWrittenData.set(textNode, textNode.data);
}
break;
}
}
}
spaceNodeSync(node, maxTextNodes) {
const textNodes = DomWalker.collectTextNodes(node);
if (textNodes.length > maxTextNodes) return false;
this.spaceTextNodes(this.withNeighborTextNodes(textNodes).reverse());
return true;
}
withNeighborTextNodes(textNodes) {
const firstTextNode = textNodes[0];
const lastTextNode = textNodes.at(-1);
if (!firstTextNode || !lastTextNode) return textNodes;
const previousTextNode = DomWalker.findAdjacentTextNode(firstTextNode, "previous");
const nextTextNode = DomWalker.findAdjacentTextNode(lastTextNode, "next");
return [
...previousTextNode ? [previousTextNode] : [],
...textNodes,
...nextTextNode ? [nextTextNode] : []
];
}
hasSpacedTextInSubtree(node) {
if (node instanceof Text) return this.lastWrittenData.has(node);
const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT);
while (walker.nextNode()) if (this.lastWrittenData.has(walker.currentNode)) return true;
return false;
}
insertPanguElement(nextBoundaryNode) {
const panguSpace = document.createElement("pangu");
panguSpace.innerHTML = " ";
if (nextBoundaryNode.parentNode) nextBoundaryNode.parentNode.insertBefore(panguSpace, nextBoundaryNode);
if (!panguSpace.previousElementSibling) {
if (panguSpace.parentNode) panguSpace.parentNode.removeChild(panguSpace);
}
}
findPreviousElementLastChar(textNode) {
const previousNode = textNode.previousSibling;
if (previousNode?.nodeType === Node.ELEMENT_NODE && previousNode.textContent) return previousNode.textContent.slice(-1);
return null;
}
scanBetweenTextNodes(currentBoundaryNode, nextBoundaryNode) {
let whitespaceBetween = false;
let contentBetween = false;
const scan = (node) => {
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
if (/\s/.test(node.textContent)) whitespaceBetween = true;
if (/\S/.test(node.textContent)) contentBetween = true;
} else if (node instanceof Element && !DomWalker.isIgnoredElement(node)) for (let child = node.firstChild; child; child = child.nextSibling) scan(child);
};
let containerOfNext = null;
let node = currentBoundaryNode;
while (node && !containerOfNext) {
let sibling = node.nextSibling;
while (sibling && !sibling.contains(nextBoundaryNode)) {
scan(sibling);
sibling = sibling.nextSibling;
}
containerOfNext = sibling;
node = node.parentNode;
}
while (containerOfNext && containerOfNext !== nextBoundaryNode) {
let child = containerOfNext.firstChild;
while (child && !child.contains(nextBoundaryNode)) {
scan(child);
child = child.nextSibling;
}
containerOfNext = child;
}
return {
whitespaceBetween,
contentBetween
};
}
isHiddenBoundaryBefore(node) {
return this.visibilityDetector.shouldSkipSpacingBeforeNode(node);
}
isHiddenBoundaryAfter(node) {
return this.visibilityDetector.shouldSkipSpacingAfterNode(node);
}
schedule(task) {
if (!this.taskScheduler.config.enabled || typeof requestIdleCallback !== "function") {
task();
return;
}
this.taskScheduler.queue.add(task);
}
setupAutoSpacePageObserver(nodeDelayMs, nodeMaxWaitMs) {
const queue = [];
const spaceTitleDebounced = debounce(() => {
if (this.autoSpacePageObserver !== observer) return;
const titleElement = document.querySelector("head > title");
if (titleElement) this.spaceNode(titleElement);
}, nodeDelayMs, nodeMaxWaitMs);
const spaceQueuedNodesDebounced = debounce(() => {
if (this.autoSpacePageObserver !== observer) return;
const nodesToProcess = [...queue];
queue.length = 0;
if (nodesToProcess.length === 0) return;
nodesToProcess.sort((a, b) => {
if (a === b) return 0;
return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
});
const seenTextNodes = /* @__PURE__ */ new Set();
const allTextNodes = [];
for (const node of nodesToProcess) {
const textNodes = DomWalker.collectTextNodes(node);
for (const textNode of this.withNeighborTextNodes(textNodes)) if (!seenTextNodes.has(textNode)) {
seenTextNodes.add(textNode);
allTextNodes.push(textNode);
}
}
allTextNodes.reverse();
this.schedule(() => this.spaceTextNodes(allTextNodes));
}, nodeDelayMs, nodeMaxWaitMs);
const observer = new MutationObserver((mutations) => {
let titleChanged = false;
let removedSpacedContent = false;
for (const mutation of mutations) {
for (const node of mutation.removedNodes) if (this.hasSpacedTextInSubtree(node)) {
removedSpacedContent = true;
break;
}
if (removedSpacedContent) break;
}
for (const mutation of mutations) {
if (mutation.target.parentNode?.nodeName === "TITLE" || mutation.target.nodeName === "TITLE") {
titleChanged = true;
continue;
}
switch (mutation.type) {
case "characterData": {
const { target: node } = mutation;
if (node instanceof Text && node.parentNode) {
const lastWritten = this.lastWrittenData.get(node);
if (lastWritten !== void 0) {
if (node.data === lastWritten) break;
if (this.spaceNodeSync(node.parentNode, BrowserPangu.maxSyncTextNodes)) break;
}
queue.push(node.parentNode);
}
break;
}
case "childList":
for (const node of mutation.addedNodes) if (node.nodeType === Node.ELEMENT_NODE) {
if (removedSpacedContent && this.spaceNodeSync(node, BrowserPangu.maxSyncTextNodes)) continue;
queue.push(node);
} else if (node.nodeType === Node.TEXT_NODE && node.parentNode) {
if (removedSpacedContent && this.spaceNodeSync(node.parentNode, BrowserPangu.maxSyncTextNodes)) continue;
queue.push(node.parentNode);
}
break;
default: break;
}
}
if (titleChanged) spaceTitleDebounced();
spaceQueuedNodesDebounced();
});
this.autoSpacePageObserver = observer;
observer.observe(document.head, {
characterData: true,
childList: true,
subtree: true
});
observer.observe(document.body, {
characterData: true,
childList: true,
subtree: true
});
return observer;
}
};
var pangu = new BrowserPangu();
//#endregion
return Object.assign(pangu, { BrowserPangu });
});
//# sourceMappingURL=pangu.umd.js.map