@miliastry/quasar 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/Visuals/lyne.css +257 -3
- package/dist/Visuals/osu.css +357 -6
- package/dist/index.js +850 -98
- package/dist/index.mjs +846 -99
- package/dist/types/BBCode/BBCodeDocumentModel.d.ts +11 -1
- package/dist/types/Visitors/DOMMorpher.d.ts +6 -0
- package/dist/types/Visitors/HTMLRenderer.d.ts +120 -8
- package/dist/types/Visitors/index.d.ts +1 -1
- package/dist/types/Visuals/BoxDrawer.d.ts +10 -5
- package/dist/types/Visuals/LyneAudio.d.ts +8 -0
- package/dist/types/Visuals/index.d.ts +1 -0
- package/dist/types/index.d.ts +4 -3
- package/package.json +1 -1
- package/src/BBCode/BBCodeDocumentModel.ts +36 -1
- package/src/HTML/HTMLToGreenNode.ts +8 -0
- package/src/Semantic/SemanticAnalyzer.ts +43 -0
- package/src/Visitors/BBCodeExporter.ts +51 -5
- package/src/Visitors/BlockPatcher.ts +36 -11
- package/src/Visitors/DOMMorpher.ts +44 -38
- package/src/Visitors/HTMLRenderer.ts +402 -59
- package/src/Visitors/index.ts +1 -1
- package/src/Visuals/BoxDrawer.ts +56 -10
- package/src/Visuals/LyneAudio.ts +346 -0
- package/src/Visuals/index.ts +1 -0
- package/src/Visuals/lyne.css +257 -3
- package/src/Visuals/osu.css +357 -6
- package/src/index.ts +6 -3
package/dist/index.mjs
CHANGED
|
@@ -6232,6 +6232,42 @@ var SemanticAnalyzer = class {
|
|
|
6232
6232
|
return diagnostics.length > 0 ? diagnostics : null;
|
|
6233
6233
|
}
|
|
6234
6234
|
});
|
|
6235
|
+
this.register({
|
|
6236
|
+
code: "box-missing-equals",
|
|
6237
|
+
severity: "warning",
|
|
6238
|
+
kinds: ["box"],
|
|
6239
|
+
validate: (node, ctx) => {
|
|
6240
|
+
if (node.text.startsWith("=")) return null;
|
|
6241
|
+
const openEnd = node.innerStart > node.range.start ? node.innerStart : node.range.start + 5;
|
|
6242
|
+
if (node.range.start < 0 || openEnd > ctx.source.length) return null;
|
|
6243
|
+
const openTag = ctx.source.slice(node.range.start, openEnd);
|
|
6244
|
+
if (openTag.includes("=")) return null;
|
|
6245
|
+
const fixes = [
|
|
6246
|
+
{
|
|
6247
|
+
description: "Add '=' to [box]",
|
|
6248
|
+
isAutomatic: true,
|
|
6249
|
+
operations: [
|
|
6250
|
+
{
|
|
6251
|
+
kind: "replace_text",
|
|
6252
|
+
range: { start: node.range.start, end: openEnd },
|
|
6253
|
+
newText: "[box=]"
|
|
6254
|
+
}
|
|
6255
|
+
]
|
|
6256
|
+
}
|
|
6257
|
+
];
|
|
6258
|
+
return createDiagnostic(
|
|
6259
|
+
"box-missing-equals",
|
|
6260
|
+
"[box] without '=' can be previewed in Miliastry, but osu! requires [box=] to parse it correctly",
|
|
6261
|
+
"warning",
|
|
6262
|
+
{
|
|
6263
|
+
nodeId: node.id,
|
|
6264
|
+
nodeKind: node.kind,
|
|
6265
|
+
range: { start: node.range.start, end: openEnd },
|
|
6266
|
+
fixes
|
|
6267
|
+
}
|
|
6268
|
+
);
|
|
6269
|
+
}
|
|
6270
|
+
});
|
|
6235
6271
|
}
|
|
6236
6272
|
/**
|
|
6237
6273
|
* Create a validator for a specific tag/kind.
|
|
@@ -8329,10 +8365,29 @@ var KIND_TO_TAG_NAME = {
|
|
|
8329
8365
|
function hasAttrValue(value) {
|
|
8330
8366
|
return value !== void 0 && value !== null && String(value) !== "";
|
|
8331
8367
|
}
|
|
8332
|
-
function
|
|
8368
|
+
function expandHexForOsu(body) {
|
|
8369
|
+
if (!/^[0-9a-fA-F]+$/.test(body)) return null;
|
|
8370
|
+
switch (body.length) {
|
|
8371
|
+
case 3:
|
|
8372
|
+
return body[0] + body[0] + body[1] + body[1] + body[2] + body[2];
|
|
8373
|
+
case 4:
|
|
8374
|
+
return body[0] + body[0] + body[1] + body[1] + body[2] + body[2];
|
|
8375
|
+
case 6:
|
|
8376
|
+
return body;
|
|
8377
|
+
case 8:
|
|
8378
|
+
return body.slice(0, 6);
|
|
8379
|
+
default:
|
|
8380
|
+
return null;
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
8383
|
+
function normalizeColorToHex(color, target = "miliastry") {
|
|
8333
8384
|
if (!color) return color;
|
|
8334
8385
|
const trimmed = color.trim();
|
|
8335
|
-
if (trimmed.startsWith("#"))
|
|
8386
|
+
if (trimmed.startsWith("#")) {
|
|
8387
|
+
if (target !== "osu") return trimmed;
|
|
8388
|
+
const expanded = expandHexForOsu(trimmed.slice(1));
|
|
8389
|
+
return expanded === null ? trimmed : `#${expanded}`;
|
|
8390
|
+
}
|
|
8336
8391
|
const rgbMatch = trimmed.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i);
|
|
8337
8392
|
if (rgbMatch) {
|
|
8338
8393
|
const r = parseInt(rgbMatch[1], 10).toString(16).padStart(2, "0");
|
|
@@ -8340,6 +8395,10 @@ function normalizeColorToHex(color) {
|
|
|
8340
8395
|
const b = parseInt(rgbMatch[3], 10).toString(16).padStart(2, "0");
|
|
8341
8396
|
return `#${r}${g}${b}`.toLowerCase();
|
|
8342
8397
|
}
|
|
8398
|
+
if (target === "osu" && /\d/.test(trimmed)) {
|
|
8399
|
+
const expanded = expandHexForOsu(trimmed);
|
|
8400
|
+
if (expanded !== null) return `#${expanded}`;
|
|
8401
|
+
}
|
|
8343
8402
|
return trimmed;
|
|
8344
8403
|
}
|
|
8345
8404
|
var BBCodeExporter = class extends Visitor {
|
|
@@ -8464,7 +8523,7 @@ var BBCodeExporter = class extends Visitor {
|
|
|
8464
8523
|
if (this.shouldResolveTokens() && col.startsWith("$")) {
|
|
8465
8524
|
col = resolveTokenValue(col, this.tokenResolver);
|
|
8466
8525
|
}
|
|
8467
|
-
out = `[color=${normalizeColorToHex(col)}]${out}[/color]`;
|
|
8526
|
+
out = `[color=${normalizeColorToHex(col, this.target)}]${out}[/color]`;
|
|
8468
8527
|
}
|
|
8469
8528
|
if (style.fontSize) {
|
|
8470
8529
|
let size = style.fontSize;
|
|
@@ -8547,7 +8606,7 @@ var BBCodeExporter = class extends Visitor {
|
|
|
8547
8606
|
if (this.shouldResolveTokens() && color.startsWith("$")) {
|
|
8548
8607
|
color = resolveTokenValue(color, this.tokenResolver);
|
|
8549
8608
|
}
|
|
8550
|
-
return `=${normalizeColorToHex(color)}`;
|
|
8609
|
+
return `=${normalizeColorToHex(color, this.target)}`;
|
|
8551
8610
|
} else if (node.kind === "font" && hasAttrValue(node.metadata.font)) {
|
|
8552
8611
|
let font = String(node.metadata.font);
|
|
8553
8612
|
if (this.shouldResolveTokens() && font.startsWith("$")) {
|
|
@@ -8650,7 +8709,7 @@ var BBCodeExporter = class extends Visitor {
|
|
|
8650
8709
|
if (this.shouldResolveTokens() && col.startsWith("$")) {
|
|
8651
8710
|
col = resolveTokenValue(col, this.tokenResolver);
|
|
8652
8711
|
}
|
|
8653
|
-
return `=${normalizeColorToHex(col)}`;
|
|
8712
|
+
return `=${normalizeColorToHex(col, this.target)}`;
|
|
8654
8713
|
}
|
|
8655
8714
|
if (this.shouldResolveTokens() && text.startsWith("=")) {
|
|
8656
8715
|
let val = text.slice(1);
|
|
@@ -8975,6 +9034,23 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
8975
9034
|
* LYNE's forum renderer applies.
|
|
8976
9035
|
*/
|
|
8977
9036
|
tableDepth = 0;
|
|
9037
|
+
/**
|
|
9038
|
+
* ¿Estamos emitiendo el vocabulario de clases de osu!?
|
|
9039
|
+
*
|
|
9040
|
+
* osu estiliza box, spoilerbox, notice, imagemap, youtube, los alineados y
|
|
9041
|
+
* los perfiles POR NOMBRE DE CLASE, no por estilo inline. Sobre una userpage
|
|
9042
|
+
* real el HTML de Quasar salía sin estilo porque emitía su propio
|
|
9043
|
+
* vocabulario (`<details>`, `.notice`, `.imagemap-container`…). Bajo
|
|
9044
|
+
* `dialect: 'osu'` se emiten las clases y la estructura de osu; el resto de
|
|
9045
|
+
* dialectos conserva la suya, que es la que sus hojas de estilo esperan.
|
|
9046
|
+
*/
|
|
9047
|
+
isOsu() {
|
|
9048
|
+
return this.options.dialect === "osu";
|
|
9049
|
+
}
|
|
9050
|
+
/** osu recorta los saltos pegados a la apertura y al cierre de box/notice. */
|
|
9051
|
+
static trimOsuEdges(html) {
|
|
9052
|
+
return html.replace(/^[\t ]*\r?\n/, "").replace(/\r?\n[\t ]*$/, "");
|
|
9053
|
+
}
|
|
8978
9054
|
idAttr(node) {
|
|
8979
9055
|
if (_HTMLRenderer.idMode === "none") return "";
|
|
8980
9056
|
if (_HTMLRenderer.idMode === "all") return ` data-node-id="${node.id}"`;
|
|
@@ -9105,7 +9181,7 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9105
9181
|
case "spoiler":
|
|
9106
9182
|
return this.wrapInline("span", node, 'class="spoiler"');
|
|
9107
9183
|
case "color":
|
|
9108
|
-
return this.
|
|
9184
|
+
return this.renderColor(node);
|
|
9109
9185
|
case "font_size":
|
|
9110
9186
|
return this.wrapInline("span", node, this.fontSizeStyle(node));
|
|
9111
9187
|
case "font":
|
|
@@ -9123,11 +9199,11 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9123
9199
|
case "audio":
|
|
9124
9200
|
return this.renderAudio(node);
|
|
9125
9201
|
case "center":
|
|
9126
|
-
return this.
|
|
9202
|
+
return this.renderAlignAs(node, "center");
|
|
9127
9203
|
case "right":
|
|
9128
|
-
return this.
|
|
9204
|
+
return this.renderAlignAs(node, "right");
|
|
9129
9205
|
case "left":
|
|
9130
|
-
return this.
|
|
9206
|
+
return this.renderAlignAs(node, "left");
|
|
9131
9207
|
// Sigue siendo siempre `h2`, como antes: el nivel de BBCode no mapea al
|
|
9132
9208
|
// de HTML y un `[heading=9]` daría un `<h9>` inválido. `data-bare-level`
|
|
9133
9209
|
// sólo anota que el 2 lo puso el renderer, para que el camino de vuelta
|
|
@@ -9260,12 +9336,10 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9260
9336
|
case "paint":
|
|
9261
9337
|
return this.renderEffectSegments(node, "paint");
|
|
9262
9338
|
case "spacing":
|
|
9263
|
-
if (this.
|
|
9264
|
-
|
|
9265
|
-
return this.isPrevBlockBoundary(node) ? "\n" : `<br${this.idAttr(node)}>`;
|
|
9339
|
+
if (this.isNewlineSwallowed(node)) return "\n";
|
|
9340
|
+
return `<br${this.idAttr(node)}>`;
|
|
9266
9341
|
case "empty_line":
|
|
9267
|
-
if (this.
|
|
9268
|
-
if (this.isTrailingBlockBoundary(node)) return "\n";
|
|
9342
|
+
if (this.isNewlineSwallowed(node)) return "\n";
|
|
9269
9343
|
return `<div class="bb-empty-line"${this.idAttr(node)}><br></div>`;
|
|
9270
9344
|
case "group":
|
|
9271
9345
|
return this.wrapInline("span", node, 'class="group"');
|
|
@@ -9301,58 +9375,241 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9301
9375
|
return this.escapeHtml(node.text || "");
|
|
9302
9376
|
}
|
|
9303
9377
|
}
|
|
9304
|
-
// ───
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9378
|
+
// ─── Newline swallowing ─────────────────────────────────
|
|
9379
|
+
//
|
|
9380
|
+
// osu! turns newlines into `<br />` with one flat rule at the very end of
|
|
9381
|
+
// `BBCodeFromDB::toHTML` — `str_replace("\n", '<br />')`. Every subtlety
|
|
9382
|
+
// lives BEFORE that line: each block pass is a regex that eats the newlines
|
|
9383
|
+
// touching its own tags, so those newlines are simply gone by the time the
|
|
9384
|
+
// flat rule runs. The amount eaten differs per tag, and the asymmetries are
|
|
9385
|
+
// not decorative:
|
|
9386
|
+
//
|
|
9387
|
+
// parseBox `\[box=…\]\n*` `\n*\[/box\]\n?`
|
|
9388
|
+
// parseCode `\[code\]\n*` `\n*\[/code\]\n?`
|
|
9389
|
+
// parseNotice `\[notice\]\n*` `\n*\[/notice\]\n?`
|
|
9390
|
+
// parseList `\s*\[\*\]` `\s*\[/list\]\n?\n?`
|
|
9391
|
+
// parseQuote `\[quote…\]\s*` `\s*\[/quote\]\n?\n?`
|
|
9392
|
+
// parseHeading — `\[/heading\]\n?`
|
|
9393
|
+
// parseImagemap — `\[/imagemap\]\n?`
|
|
9394
|
+
// parseAlignment strtr of `[centre]\n` and `[/centre]\n` — exactly one
|
|
9395
|
+
//
|
|
9396
|
+
// Quasar used to approximate all of that with two neighbourhood heuristics
|
|
9397
|
+
// (`isPrevBlockBoundary` / `isTrailingBlockBoundary`) that treated every
|
|
9398
|
+
// block alike, so they over-ate at `[centre]`/`[/imagemap]` and under-ate at
|
|
9399
|
+
// `[/list]`/`[/quote]`. This models the real rules instead.
|
|
9400
|
+
//
|
|
9401
|
+
// Deliberately NOT gated on the dialect: Miliastry is "osu with steroids"
|
|
9402
|
+
// and has to break lines the same way. Blocks that only exist in Miliastry
|
|
9403
|
+
// (tables, gallery, columns, scroll, …) have no osu counterpart to copy, so
|
|
9404
|
+
// they keep the legacy behaviour via {@link HTMLRenderer.LEGACY_BLOCK_RULE}.
|
|
9405
|
+
/** How many newlines a construct swallows around its own tags. */
|
|
9406
|
+
static NEWLINE_RULES = {
|
|
9407
|
+
// `\n*` inside both edges, one newline after the close.
|
|
9408
|
+
box: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9409
|
+
boxw: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9410
|
+
spoilerbox: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9411
|
+
notice: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9412
|
+
wnotice: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9413
|
+
code: { afterOpen: "all", beforeClose: "all", beforeOpen: "none", afterClose: 1 },
|
|
9414
|
+
// `\s*` — not just newlines — and TWO newlines after the close.
|
|
9415
|
+
quote: { afterOpen: "whitespace", beforeClose: "whitespace", beforeOpen: "none", afterClose: 2 },
|
|
9416
|
+
// `[list]` itself eats nothing after its opening tag: the pass that eats
|
|
9417
|
+
// is `\s*\[\*\]`, which needs an item to follow. `[list]\n\nloose text`
|
|
9418
|
+
// keeps both newlines; `[list]\n[*]a` loses one to the item, not the list.
|
|
9419
|
+
list: { afterOpen: "none", beforeClose: "whitespace", beforeOpen: "none", afterClose: 2 },
|
|
9420
|
+
// `\s*\[\*\]`. The matching `[/*]` of the table exists only in legacy
|
|
9421
|
+
// phpBB rows — `BBCodeForDB` never emits one — so the item's close is
|
|
9422
|
+
// width-less here and its two-newline budget is unreachable by design;
|
|
9423
|
+
// `[*]a\n\n[*]b` loses both newlines to the NEXT item's `\s*`, which is
|
|
9424
|
+
// the same output by a different route.
|
|
9425
|
+
list_item: { afterOpen: "none", beforeClose: "none", beforeOpen: "whitespace", afterClose: 0 },
|
|
9426
|
+
// strtr with `[centre]\n` / `[/centre]\n`: exactly one on each outer edge,
|
|
9427
|
+
// and nothing before the close — `x\n[/centre]` really does keep its `<br>`.
|
|
9428
|
+
center: { afterOpen: "one", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9429
|
+
left: { afterOpen: "one", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9430
|
+
right: { afterOpen: "one", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9431
|
+
align: { afterOpen: "one", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9432
|
+
heading: { afterOpen: "none", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9433
|
+
imagemap: { afterOpen: "none", beforeClose: "none", beforeOpen: "none", afterClose: 1 },
|
|
9434
|
+
// `[img]` is inline in osu and swallows nothing at all.
|
|
9435
|
+
image: { afterOpen: "none", beforeClose: "none", beforeOpen: "none", afterClose: 0 },
|
|
9436
|
+
document: { afterOpen: "none", beforeClose: "none", beforeOpen: "none", afterClose: 0 }
|
|
9437
|
+
};
|
|
9438
|
+
/**
|
|
9439
|
+
* What a Miliastry-only block does. This is what the old
|
|
9440
|
+
* `isPrevBlockBoundary` / `isTrailingBlockBoundary` pair did for every block:
|
|
9441
|
+
* eat the first newline after the open, every newline before the close, and
|
|
9442
|
+
* the first newline after the close.
|
|
9443
|
+
*/
|
|
9444
|
+
static LEGACY_BLOCK_RULE = {
|
|
9445
|
+
afterOpen: "one",
|
|
9446
|
+
beforeClose: "all",
|
|
9447
|
+
beforeOpen: "none",
|
|
9448
|
+
afterClose: 1
|
|
9449
|
+
};
|
|
9450
|
+
/**
|
|
9451
|
+
* Containers whose opening tag occupies no source text, so a backwards scan
|
|
9452
|
+
* has to walk straight through them.
|
|
9453
|
+
*/
|
|
9454
|
+
static WIDTHLESS_OPEN = /* @__PURE__ */ new Set(["paragraph", "group"]);
|
|
9455
|
+
/**
|
|
9456
|
+
* Same for the closing side. `list_item` is here because `[/*]` is never
|
|
9457
|
+
* written: an item ends where the next `[*]` or the `[/list]` begins, so
|
|
9458
|
+
* `\s*\[/list\]` sees the newline that Quasar stores inside the item.
|
|
9459
|
+
*/
|
|
9460
|
+
static WIDTHLESS_CLOSE = /* @__PURE__ */ new Set(["paragraph", "group", "list_item"]);
|
|
9461
|
+
newlineRule(kind) {
|
|
9462
|
+
const rule = _HTMLRenderer.NEWLINE_RULES[kind];
|
|
9463
|
+
if (rule) return rule;
|
|
9464
|
+
return this.BLOCK_TAGS.has(kind) ? _HTMLRenderer.LEGACY_BLOCK_RULE : null;
|
|
9465
|
+
}
|
|
9466
|
+
static isNewlineNode(node) {
|
|
9467
|
+
return node.kind === "spacing" || node.kind === "empty_line";
|
|
9468
|
+
}
|
|
9469
|
+
static isBlankText(node) {
|
|
9470
|
+
return node.kind === "text" && node.children.length === 0 && node.text.trim() === "";
|
|
9471
|
+
}
|
|
9472
|
+
/**
|
|
9473
|
+
* Whether this `spacing` / `empty_line` leaf is eaten by a neighbouring tag
|
|
9474
|
+
* and therefore renders nothing.
|
|
9475
|
+
*
|
|
9476
|
+
* Each leaf is exactly ONE source newline (the parser splits a run into one
|
|
9477
|
+
* node per `\n`), so the four scans below can be read straight off the
|
|
9478
|
+
* regexes they mirror. A newline eaten by any of them is eaten: osu's passes
|
|
9479
|
+
* run in a fixed order, but since a consumed newline is consumed whichever
|
|
9480
|
+
* pass claimed it, the union is enough — the per-pass order only matters for
|
|
9481
|
+
* a budget that could be spent elsewhere, and budgets here are counted from
|
|
9482
|
+
* the tag outwards, exactly as `\n?\n?` counts.
|
|
9483
|
+
*/
|
|
9484
|
+
isNewlineSwallowed(node) {
|
|
9485
|
+
return this.eatenByOpeningTag(node) || this.eatenByClosingTag(node) || this.eatenAfterClosingTag(node) || this.eatenBeforeOpeningTag(node);
|
|
9486
|
+
}
|
|
9487
|
+
/** `\[box\]\n*`, `\[quote\]\s*`, `[centre]\n`. */
|
|
9488
|
+
eatenByOpeningTag(node) {
|
|
9489
|
+
let cur = node;
|
|
9490
|
+
let newlinesBetween = 0;
|
|
9491
|
+
let blankBetween = false;
|
|
9492
|
+
for (; ; ) {
|
|
9493
|
+
const prev = cur.previousSibling;
|
|
9494
|
+
if (prev) {
|
|
9495
|
+
if (_HTMLRenderer.isNewlineNode(prev)) {
|
|
9496
|
+
newlinesBetween++;
|
|
9497
|
+
cur = prev;
|
|
9498
|
+
continue;
|
|
9499
|
+
}
|
|
9500
|
+
if (_HTMLRenderer.isBlankText(prev)) {
|
|
9501
|
+
blankBetween = true;
|
|
9502
|
+
cur = prev;
|
|
9503
|
+
continue;
|
|
9504
|
+
}
|
|
9505
|
+
return false;
|
|
9327
9506
|
}
|
|
9328
|
-
|
|
9329
|
-
|
|
9507
|
+
const parent = cur.parent;
|
|
9508
|
+
if (!parent) return false;
|
|
9509
|
+
if (_HTMLRenderer.WIDTHLESS_OPEN.has(parent.kind)) {
|
|
9510
|
+
cur = parent;
|
|
9330
9511
|
continue;
|
|
9331
9512
|
}
|
|
9332
|
-
|
|
9513
|
+
const rule = this.newlineRule(parent.kind);
|
|
9514
|
+
if (!rule) return false;
|
|
9515
|
+
switch (rule.afterOpen) {
|
|
9516
|
+
case "whitespace":
|
|
9517
|
+
return true;
|
|
9518
|
+
// `\n*` matches newlines only: a stray space breaks the run.
|
|
9519
|
+
case "all":
|
|
9520
|
+
return !blankBetween;
|
|
9521
|
+
case "one":
|
|
9522
|
+
return !blankBetween && newlinesBetween === 0;
|
|
9523
|
+
default:
|
|
9524
|
+
return false;
|
|
9525
|
+
}
|
|
9333
9526
|
}
|
|
9334
|
-
if (prev && this.BLOCK_TAGS.has(prev.kind) && prev.kind !== "image" && prev.kind !== "imagemap") return true;
|
|
9335
|
-
if (!prev && node.parent && this.BLOCK_TAGS.has(node.parent.kind) && node.parent.kind !== "image" && node.parent.kind !== "imagemap") return true;
|
|
9336
|
-
return false;
|
|
9337
9527
|
}
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9528
|
+
/** `\n*\[/box\]`, `\s*\[/quote\]`, `\s*\[/list\]`. */
|
|
9529
|
+
eatenByClosingTag(node) {
|
|
9530
|
+
let cur = node;
|
|
9531
|
+
let blankBetween = false;
|
|
9532
|
+
for (; ; ) {
|
|
9533
|
+
const next = cur.nextSibling;
|
|
9534
|
+
if (next) {
|
|
9535
|
+
if (_HTMLRenderer.isNewlineNode(next)) {
|
|
9536
|
+
cur = next;
|
|
9537
|
+
continue;
|
|
9538
|
+
}
|
|
9539
|
+
if (_HTMLRenderer.isBlankText(next)) {
|
|
9540
|
+
blankBetween = true;
|
|
9541
|
+
cur = next;
|
|
9542
|
+
continue;
|
|
9543
|
+
}
|
|
9544
|
+
return false;
|
|
9545
|
+
}
|
|
9546
|
+
const parent = cur.parent;
|
|
9547
|
+
if (!parent) return false;
|
|
9548
|
+
if (_HTMLRenderer.WIDTHLESS_CLOSE.has(parent.kind)) {
|
|
9549
|
+
cur = parent;
|
|
9343
9550
|
continue;
|
|
9344
9551
|
}
|
|
9345
|
-
|
|
9346
|
-
|
|
9552
|
+
const rule = this.newlineRule(parent.kind);
|
|
9553
|
+
if (!rule) return false;
|
|
9554
|
+
switch (rule.beforeClose) {
|
|
9555
|
+
case "whitespace":
|
|
9556
|
+
return true;
|
|
9557
|
+
case "all":
|
|
9558
|
+
return !blankBetween;
|
|
9559
|
+
default:
|
|
9560
|
+
return false;
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
}
|
|
9564
|
+
/** `\[/box\]\n?`, `\[/list\]\n?\n?`. */
|
|
9565
|
+
eatenAfterClosingTag(node) {
|
|
9566
|
+
let cur = node;
|
|
9567
|
+
let newlinesBetween = 0;
|
|
9568
|
+
for (; ; ) {
|
|
9569
|
+
const prev = cur.previousSibling;
|
|
9570
|
+
if (!prev) {
|
|
9571
|
+
const parent = cur.parent;
|
|
9572
|
+
if (parent && _HTMLRenderer.WIDTHLESS_OPEN.has(parent.kind)) {
|
|
9573
|
+
cur = parent;
|
|
9574
|
+
continue;
|
|
9575
|
+
}
|
|
9576
|
+
return false;
|
|
9577
|
+
}
|
|
9578
|
+
if (_HTMLRenderer.isNewlineNode(prev)) {
|
|
9579
|
+
newlinesBetween++;
|
|
9580
|
+
cur = prev;
|
|
9347
9581
|
continue;
|
|
9348
9582
|
}
|
|
9349
|
-
|
|
9583
|
+
let closer = prev;
|
|
9584
|
+
while (_HTMLRenderer.WIDTHLESS_CLOSE.has(closer.kind) && closer.children.length > 0) {
|
|
9585
|
+
closer = closer.children[closer.children.length - 1];
|
|
9586
|
+
}
|
|
9587
|
+
const rule = this.newlineRule(closer.kind);
|
|
9588
|
+
return rule !== null && newlinesBetween < rule.afterClose;
|
|
9350
9589
|
}
|
|
9351
|
-
|
|
9352
|
-
|
|
9590
|
+
}
|
|
9591
|
+
/** `\s*\[\*\]` — the only pass that eats whitespace BEFORE an opening tag. */
|
|
9592
|
+
eatenBeforeOpeningTag(node) {
|
|
9593
|
+
let cur = node;
|
|
9594
|
+
for (; ; ) {
|
|
9595
|
+
const next = cur.nextSibling;
|
|
9596
|
+
if (next) {
|
|
9597
|
+
if (_HTMLRenderer.isNewlineNode(next) || _HTMLRenderer.isBlankText(next)) {
|
|
9598
|
+
cur = next;
|
|
9599
|
+
continue;
|
|
9600
|
+
}
|
|
9601
|
+
return this.newlineRule(next.kind)?.beforeOpen === "whitespace";
|
|
9602
|
+
}
|
|
9603
|
+
const parent = cur.parent;
|
|
9604
|
+
if (!parent) return false;
|
|
9605
|
+
if (_HTMLRenderer.WIDTHLESS_CLOSE.has(parent.kind)) {
|
|
9606
|
+
cur = parent;
|
|
9607
|
+
continue;
|
|
9608
|
+
}
|
|
9609
|
+
return false;
|
|
9353
9610
|
}
|
|
9354
|
-
return false;
|
|
9355
9611
|
}
|
|
9612
|
+
// ─── Render Helpers ─────────────────────────────────────
|
|
9356
9613
|
renderError(node) {
|
|
9357
9614
|
const errorMsg = this.escapeHtml(node.metadata?.message || node.text || "Syntax Error");
|
|
9358
9615
|
const content = this.renderChildren(node) || this.escapeHtml(node.text || "");
|
|
@@ -9404,6 +9661,41 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9404
9661
|
return !!raw && /^[a-z]{2,20}(?: [a-z]{2,20})?$|^[1-9]00$/i.test(raw.trim());
|
|
9405
9662
|
}
|
|
9406
9663
|
/** Read a metadata field, falling back to the raw tag attribute. */
|
|
9664
|
+
/**
|
|
9665
|
+
* Lo único que osu! acepta en `[color=…]`.
|
|
9666
|
+
*
|
|
9667
|
+
* Su `BBCodeForDB::parseColour` sella el tag con un uid sólo si el valor
|
|
9668
|
+
* matchea `#[[:xdigit:]]{6}` o `[[:alpha:]]+` — nada más. No valida que el
|
|
9669
|
+
* nombre sea un color CSS de verdad (`banana` pasa), pero `#fff`, `#ffffffff`,
|
|
9670
|
+
* `rgb(...)`, `$token` o un hex sin `#` no pasan. Sin uid, la segunda pasada
|
|
9671
|
+
* no ve el tag y el opener *y* el closer quedan como texto en la página.
|
|
9672
|
+
*/
|
|
9673
|
+
static OSU_COLOR_RE = /^(?:#[0-9a-fA-F]{6}|[a-zA-Z]+)$/;
|
|
9674
|
+
/**
|
|
9675
|
+
* `[color]` con el vocabulario de cada dialecto.
|
|
9676
|
+
*
|
|
9677
|
+
* Miliastry (y Lyne) aceptan a propósito más que osu: `#RGB`, `#RGBA`, un
|
|
9678
|
+
* `$token` de diseño, nombres propios. Bajo `dialect: 'osu'` eso es una
|
|
9679
|
+
* mentira: el editor pintaría color donde la página publicada muestra el
|
|
9680
|
+
* BBCode crudo. Así que replicamos lo que hace osu — literal el opener,
|
|
9681
|
+
* literal el closer, y los hijos renderizados normalmente en el medio.
|
|
9682
|
+
*
|
|
9683
|
+
* El chequeo mira el texto crudo del atributo, no el valor saneado: osu
|
|
9684
|
+
* matchea sobre la fuente, así que `[color="#ffffff"]` (con comillas) también
|
|
9685
|
+
* se le escapa.
|
|
9686
|
+
*/
|
|
9687
|
+
renderColor(node) {
|
|
9688
|
+
if (this.options.dialect === "osu") {
|
|
9689
|
+
const text = node.text || "";
|
|
9690
|
+
const eq = text.indexOf("=");
|
|
9691
|
+
const raw = eq >= 0 ? text.slice(eq + 1) : text ? "" : nodeAttrValue(node, "color");
|
|
9692
|
+
if (!_HTMLRenderer.OSU_COLOR_RE.test(raw)) {
|
|
9693
|
+
const opener = eq >= 0 ? `[color${text}]` : `[${text || "color"}]`;
|
|
9694
|
+
return this.escapeHtml(opener) + this.renderChildren(node) + this.escapeHtml("[/color]");
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
return this.wrapInline("span", node, this.colorStyle(node));
|
|
9698
|
+
}
|
|
9407
9699
|
colorStyle(node) {
|
|
9408
9700
|
const color = sanitizeColor(nodeAttrValue(node, "color"), this.tokenResolver);
|
|
9409
9701
|
return color ? `style="color:${color};"` : "";
|
|
@@ -9445,6 +9737,11 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9445
9737
|
return `<strong${entity}><a${this.idAttr(node)} href="${this.escapeHtml(link.href)}"${ext}>${content}</a></strong>`;
|
|
9446
9738
|
}
|
|
9447
9739
|
}
|
|
9740
|
+
if (type === "profile" && this.isOsu()) {
|
|
9741
|
+
const key2 = val || `@${this.collectNodeText(node)}`;
|
|
9742
|
+
const href = `https://osu.ppy.sh/users/${encodeURIComponent(key2)}`;
|
|
9743
|
+
return `<a${this.idAttr(node)}${entity} class="user-name js-usercard" data-user-id="${this.escapeHtml(key2)}" href="${href}">${content}</a>`;
|
|
9744
|
+
}
|
|
9448
9745
|
if (type === "profile") {
|
|
9449
9746
|
const url = this.options.theme === "lyne" || this.options.dialect === "lyne" ? `/u/${encodeURIComponent(val || content)}` : `https://osu.ppy.sh/users/${this.escapeHtml(val || content)}`;
|
|
9450
9747
|
return `<strong${entity}><a${this.idAttr(node)} href="${url}" target="_blank" rel="noopener">${content}</a></strong>`;
|
|
@@ -9499,13 +9796,25 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9499
9796
|
if (!id) return this.mediaError("youtube", "[youtube] missing video ID");
|
|
9500
9797
|
const ytMatch = /(?:youtu\.be\/|v=|\/embed\/|\/shorts\/)([\w-]{11})/.exec(id);
|
|
9501
9798
|
if (ytMatch) id = ytMatch[1];
|
|
9502
|
-
|
|
9799
|
+
const cls = this.isOsu() ? "u-embed-wide u-embed-wide--bbcode" : "bb-youtube";
|
|
9800
|
+
const rel = this.isOsu() ? "?rel=0" : "";
|
|
9801
|
+
return `<iframe${this.idAttr(node)} class="${cls}" data-youtube="${this.escapeHtml(id)}" src="https://www.youtube.com/embed/${this.escapeHtml(id)}${rel}" frameborder="0" allowfullscreen></iframe>`;
|
|
9503
9802
|
}
|
|
9504
9803
|
renderAudio(node) {
|
|
9505
9804
|
let src = String(node.metadata?.src ?? "") || node.text || "";
|
|
9506
9805
|
if (this.options.mediaProxy && src) {
|
|
9507
9806
|
src = this.options.mediaProxy(src);
|
|
9508
9807
|
}
|
|
9808
|
+
const isLyne = this.options.theme === "lyne" || this.options.dialect === "lyne";
|
|
9809
|
+
if (isLyne) {
|
|
9810
|
+
const rawName = src.split("?")[0].split("#")[0].split("/").filter(Boolean).pop() || "audio_track.mp3";
|
|
9811
|
+
let fileName = rawName;
|
|
9812
|
+
try {
|
|
9813
|
+
fileName = decodeURIComponent(rawName);
|
|
9814
|
+
} catch {
|
|
9815
|
+
}
|
|
9816
|
+
return `<div${this.idAttr(node)} class="lx-audio bb-audio" data-src="${this.escapeHtml(src)}"><audio preload="metadata" src="${this.escapeHtml(src)}"></audio><div class="lx-track" role="progressbar" aria-label="Audio progress"><div class="fill"></div></div><div class="lx-row"><button type="button" class="lx-btn" aria-label="Play"><svg viewBox="0 0 16 16"><path d="M3 1.5 14 8 3 14.5z"/></svg></button><div class="lx-title"><div class="name">${this.escapeHtml(fileName)}</div><div class="label"><span class="dot"></span> <span class="status-text">audio</span></div></div><div class="lx-vol"><button type="button" class="lx-vol-btn" aria-label="Mute"><svg viewBox="0 0 16 16"><path d="M10.707 11.182A4.5 4.5 0 0 0 12.025 8a4.5 4.5 0 0 0-1.318-3.182L10 5.525A3.5 3.5 0 0 1 11.025 8 3.5 3.5 0 0 1 10 10.475zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06"/></svg></button><input type="range" class="lx-vol-slider" min="0" max="1" step="0.01" value="0.2" aria-label="Volume" style="background:linear-gradient(to right, var(--color-accent, #2EE6E2) 20%, var(--color-inset-well, #080D20) 20%);" /></div><button type="button" class="lx-speed" aria-label="Playback speed">1.0\xD7</button><div class="lx-time"><span class="cur">0:00</span> / <span class="total">\u2013:\u2013\u2013</span></div></div></div>`;
|
|
9817
|
+
}
|
|
9509
9818
|
return `<audio${this.idAttr(node)} controls src="${this.escapeHtml(src)}" class="bb-audio"></audio>`;
|
|
9510
9819
|
}
|
|
9511
9820
|
hexToRgba(hex, alpha) {
|
|
@@ -9531,6 +9840,10 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9531
9840
|
const warningIcon = warning ? `<span aria-hidden class="bb-notice-mark"${markStyle}>\u26A0</span>` : "";
|
|
9532
9841
|
return `<div${this.idAttr(node)} class="notice bb-cut-panel bb-notice${warning ? " bb-wnotice" : ""}" role="note"${styleAttr}>${warningIcon}<div class="bb-notice-body">${content}</div></div>`;
|
|
9533
9842
|
}
|
|
9843
|
+
if (this.isOsu()) {
|
|
9844
|
+
const content = _HTMLRenderer.trimOsuEdges(this.renderChildren(node));
|
|
9845
|
+
return `<div${this.idAttr(node)} class="well">${content}</div>`;
|
|
9846
|
+
}
|
|
9534
9847
|
return this.wrapBlock("div", node, 'class="notice"');
|
|
9535
9848
|
}
|
|
9536
9849
|
renderTables(node) {
|
|
@@ -9636,7 +9949,21 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9636
9949
|
renderAlign(node) {
|
|
9637
9950
|
const alignVal = (String(node.metadata?.align ?? "") || nodeAttrValue(node) || "center").trim().toLowerCase();
|
|
9638
9951
|
const validAlign = alignVal === "left" || alignVal === "right" ? alignVal : "center";
|
|
9639
|
-
return this.
|
|
9952
|
+
return this.renderAlignAs(node, validAlign);
|
|
9953
|
+
}
|
|
9954
|
+
/**
|
|
9955
|
+
* `[centre]` / `[left]` / `[right]` (y `[align=…]`).
|
|
9956
|
+
*
|
|
9957
|
+
* osu! no usa `text-align` inline: estiliza el bloque por nombre de clase,
|
|
9958
|
+
* con la grafía británica `centre`. Fuera del dialecto osu el estilo inline
|
|
9959
|
+
* se mantiene, porque ni Miliastry ni Lyne traen esas reglas.
|
|
9960
|
+
*/
|
|
9961
|
+
renderAlignAs(node, align) {
|
|
9962
|
+
if (this.isOsu()) {
|
|
9963
|
+
const name = align === "center" ? "centre" : align;
|
|
9964
|
+
return this.wrapBlock("div", node, `class="bbcode__align-${name}"`);
|
|
9965
|
+
}
|
|
9966
|
+
return this.wrapBlock("div", node, `style="text-align:${align};"`);
|
|
9640
9967
|
}
|
|
9641
9968
|
renderEffect(node) {
|
|
9642
9969
|
const raw = (String(node.metadata?.effectType ?? "") || nodeAttrValue(node) || "glow").toLowerCase().trim();
|
|
@@ -9825,7 +10152,25 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9825
10152
|
}
|
|
9826
10153
|
return `<blockquote${this.idAttr(node)}>${content}</blockquote>`;
|
|
9827
10154
|
}
|
|
10155
|
+
/**
|
|
10156
|
+
* La estructura exacta que `bbcode-spoilerbox` de osu-web espera.
|
|
10157
|
+
*
|
|
10158
|
+
* El toggle de osu es JS: `js-spoilerbox__link` es el gancho del click y
|
|
10159
|
+
* `js-spoilerbox__body` el panel que abre. Si falta cualquiera de las dos
|
|
10160
|
+
* clases el box queda mudo, así que la estructura no es decorativa.
|
|
10161
|
+
*/
|
|
10162
|
+
renderOsuSpoilerbox(node, title, extra) {
|
|
10163
|
+
const content = _HTMLRenderer.trimOsuEdges(this.renderChildren(node));
|
|
10164
|
+
return `<div${this.idAttr(node)} class="js-spoilerbox bbcode-spoilerbox"${extra}><a class="js-spoilerbox__link bbcode-spoilerbox__link" href="#"><span class="bbcode-spoilerbox__link-icon"></span><span class="bbcode-spoilerbox__link-text">${title}</span></a><div class="js-spoilerbox__body bbcode-spoilerbox__body">${content}</div></div>`;
|
|
10165
|
+
}
|
|
10166
|
+
/** El rótulo de un box bajo osu: el del autor, o `SPOILER` en mayúsculas. */
|
|
10167
|
+
osuBoxTitle(node) {
|
|
10168
|
+
return this.hasOwnTitle(node) ? this.renderTitle(node, "SPOILER") : "SPOILER";
|
|
10169
|
+
}
|
|
9828
10170
|
renderSpoilerbox(node) {
|
|
10171
|
+
if (this.isOsu()) {
|
|
10172
|
+
return this.renderOsuSpoilerbox(node, this.osuBoxTitle(node), this.bareTitleAttr(node));
|
|
10173
|
+
}
|
|
9829
10174
|
const title = this.renderTitle(node, "Spoiler");
|
|
9830
10175
|
const bare = this.bareTitleAttr(node);
|
|
9831
10176
|
const content = this.renderChildren(node);
|
|
@@ -9835,6 +10180,9 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9835
10180
|
return `<details${this.idAttr(node)}${bare}${accent}><summary><span class="bb-box-heading">${title}</span></summary><div class="${bodyCls}">${content}</div></details>`;
|
|
9836
10181
|
}
|
|
9837
10182
|
renderBox(node) {
|
|
10183
|
+
if (this.isOsu()) {
|
|
10184
|
+
return this.renderOsuSpoilerbox(node, this.osuBoxTitle(node), this.bareTitleAttr(node));
|
|
10185
|
+
}
|
|
9838
10186
|
const title = this.renderTitle(node, "Box");
|
|
9839
10187
|
const bare = this.bareTitleAttr(node);
|
|
9840
10188
|
const content = this.renderChildren(node);
|
|
@@ -9852,9 +10200,18 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9852
10200
|
* escrito y devolvía `[box=Box]`.
|
|
9853
10201
|
*/
|
|
9854
10202
|
bareTitleAttr(node) {
|
|
10203
|
+
return this.hasOwnTitle(node) ? "" : ' data-bare-title="1"';
|
|
10204
|
+
}
|
|
10205
|
+
/**
|
|
10206
|
+
* ¿El título del box lo escribió el autor, o es el relleno del parser?
|
|
10207
|
+
*
|
|
10208
|
+
* `BBCodeToGreenNode` ya deja `metadata.title = 'Box'`/`'Spoiler'` para un
|
|
10209
|
+
* tag pelado, así que el `fallback` de `renderTitle` nunca llega a usarse:
|
|
10210
|
+
* quien quiera otro rótulo por defecto tiene que preguntar por aquí.
|
|
10211
|
+
*/
|
|
10212
|
+
hasOwnTitle(node) {
|
|
9855
10213
|
const raw = node.metadata?.rawTitle;
|
|
9856
|
-
|
|
9857
|
-
return hasOwnTitle ? "" : ' data-bare-title="1"';
|
|
10214
|
+
return raw !== void 0 ? String(raw) !== "" : node.metadata?.title !== void 0;
|
|
9858
10215
|
}
|
|
9859
10216
|
renderTitle(node, fallback) {
|
|
9860
10217
|
const titleNodes = node.metadata?.titleNodes;
|
|
@@ -9953,8 +10310,17 @@ var HTMLRenderer = class _HTMLRenderer extends Visitor {
|
|
|
9953
10310
|
if (areaUrl && !areaUrl.startsWith("http://") && !areaUrl.startsWith("https://") && !areaUrl.startsWith("mailto:")) {
|
|
9954
10311
|
areaUrl = "https://" + areaUrl;
|
|
9955
10312
|
}
|
|
10313
|
+
if (this.isOsu()) {
|
|
10314
|
+
const pos = `left:${x}%;top:${y}%;width:${w}%;height:${h}%;`;
|
|
10315
|
+
const title = ` title="${this.escapeHtml(label)}"`;
|
|
10316
|
+
areas += url === "#" ? `<span class="imagemap__link" style="${pos}"${title}></span>` : `<a class="imagemap__link" href="${this.escapeHtml(areaUrl)}" style="${pos}"${title}></a>`;
|
|
10317
|
+
continue;
|
|
10318
|
+
}
|
|
9956
10319
|
areas += `<a${this.idAttr(node)} href="${this.escapeHtml(areaUrl)}" target="_blank" rel="noopener" class="imagemap-area bbcode-imap-area" style="position:absolute;left:${x}%;top:${y}%;width:${w}%;height:${h}%;" title="${this.escapeHtml(label || "Link")}"></a>`;
|
|
9957
10320
|
}
|
|
10321
|
+
if (this.isOsu()) {
|
|
10322
|
+
return `<div${this.idAttr(node)} class="imagemap"><img class="imagemap__image" loading="lazy" src="${this.escapeHtml(imageUrl)}" alt="">${areas}</div>`;
|
|
10323
|
+
}
|
|
9958
10324
|
return `<div${this.idAttr(node)} class="imagemap-container bbcode-imagemap" style="position:relative;display:inline-block;"><img src="${this.escapeHtml(imageUrl)}" alt="imagemap" style="max-width:100%;height:auto;display:block;">${areas}</div>`;
|
|
9959
10325
|
}
|
|
9960
10326
|
collectNodeText(node) {
|
|
@@ -10239,38 +10605,39 @@ function morphNodes(parent, newParent) {
|
|
|
10239
10605
|
oldNode.nodeValue = newNode.nodeValue;
|
|
10240
10606
|
}
|
|
10241
10607
|
} else if (oldNode.nodeType === 1 && newNode.nodeType === 1 && oldNode.tagName === newNode.tagName) {
|
|
10242
|
-
|
|
10243
|
-
const newEl = newNode;
|
|
10244
|
-
if (oldEl.tagName === "DETAILS" && oldEl.hasAttribute("open")) {
|
|
10245
|
-
newEl.setAttribute("open", "");
|
|
10246
|
-
}
|
|
10247
|
-
if (oldEl.classList.contains("open")) {
|
|
10248
|
-
newEl.classList.add("open");
|
|
10249
|
-
}
|
|
10250
|
-
if (oldEl.classList.contains("is-open")) {
|
|
10251
|
-
newEl.classList.add("is-open");
|
|
10252
|
-
}
|
|
10253
|
-
const newAttrs = newEl.attributes;
|
|
10254
|
-
for (let i2 = 0; i2 < newAttrs.length; i2++) {
|
|
10255
|
-
const attr = newAttrs[i2];
|
|
10256
|
-
if (oldEl.getAttribute(attr.name) !== attr.value) {
|
|
10257
|
-
oldEl.setAttribute(attr.name, attr.value);
|
|
10258
|
-
}
|
|
10259
|
-
}
|
|
10260
|
-
const oldAttrs = oldEl.attributes;
|
|
10261
|
-
for (let i2 = oldAttrs.length - 1; i2 >= 0; i2--) {
|
|
10262
|
-
const name = oldAttrs[i2].name;
|
|
10263
|
-
if (!newEl.hasAttribute(name)) {
|
|
10264
|
-
oldEl.removeAttribute(name);
|
|
10265
|
-
}
|
|
10266
|
-
}
|
|
10267
|
-
morphNodes(oldEl, newEl);
|
|
10608
|
+
morphElement(oldNode, newNode);
|
|
10268
10609
|
} else {
|
|
10269
10610
|
oldNode.parentNode.replaceChild(newNode.cloneNode(true), oldNode);
|
|
10270
10611
|
}
|
|
10271
10612
|
}
|
|
10272
10613
|
}
|
|
10273
10614
|
}
|
|
10615
|
+
function morphElement(oldEl, newEl) {
|
|
10616
|
+
if (oldEl.tagName === "DETAILS" && oldEl.hasAttribute("open")) {
|
|
10617
|
+
newEl.setAttribute("open", "");
|
|
10618
|
+
}
|
|
10619
|
+
if (oldEl.classList.contains("open")) {
|
|
10620
|
+
newEl.classList.add("open");
|
|
10621
|
+
}
|
|
10622
|
+
if (oldEl.classList.contains("is-open")) {
|
|
10623
|
+
newEl.classList.add("is-open");
|
|
10624
|
+
}
|
|
10625
|
+
const newAttrs = newEl.attributes;
|
|
10626
|
+
for (let i = 0; i < newAttrs.length; i++) {
|
|
10627
|
+
const attr = newAttrs[i];
|
|
10628
|
+
if (oldEl.getAttribute(attr.name) !== attr.value) {
|
|
10629
|
+
oldEl.setAttribute(attr.name, attr.value);
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
const oldAttrs = oldEl.attributes;
|
|
10633
|
+
for (let i = oldAttrs.length - 1; i >= 0; i--) {
|
|
10634
|
+
const name = oldAttrs[i].name;
|
|
10635
|
+
if (!newEl.hasAttribute(name)) {
|
|
10636
|
+
oldEl.removeAttribute(name);
|
|
10637
|
+
}
|
|
10638
|
+
}
|
|
10639
|
+
morphNodes(oldEl, newEl);
|
|
10640
|
+
}
|
|
10274
10641
|
|
|
10275
10642
|
// src/Visitors/BlockPatcher.ts
|
|
10276
10643
|
var caches = /* @__PURE__ */ new WeakMap();
|
|
@@ -10308,6 +10675,9 @@ function getCache(container) {
|
|
|
10308
10675
|
function blockKey(node, index) {
|
|
10309
10676
|
return node.id ?? `__block_${index}`;
|
|
10310
10677
|
}
|
|
10678
|
+
function isContextSensitive(node) {
|
|
10679
|
+
return node.kind === "spacing" || node.kind === "empty_line";
|
|
10680
|
+
}
|
|
10311
10681
|
function nodeFromHtml(html) {
|
|
10312
10682
|
const t = document.createElement("template");
|
|
10313
10683
|
t.innerHTML = html;
|
|
@@ -10326,7 +10696,7 @@ function renderedTag(html) {
|
|
|
10326
10696
|
return m ? m[1].toUpperCase() : null;
|
|
10327
10697
|
}
|
|
10328
10698
|
function shouldMorphInPlace(element, run) {
|
|
10329
|
-
return run.kind === "element" && !!run.node.id &&
|
|
10699
|
+
return run.kind === "element" && !!run.node.id && canMorphInPlace(run.node) && renderedTag(run.html) === element.tagName;
|
|
10330
10700
|
}
|
|
10331
10701
|
function buildRuns(blocks, keys, getHtml, baseStart) {
|
|
10332
10702
|
const runs = [];
|
|
@@ -10392,7 +10762,7 @@ function reconcileKeyed(container, rootNode, keys, renderer, cache, options) {
|
|
|
10392
10762
|
let patched = 0;
|
|
10393
10763
|
try {
|
|
10394
10764
|
runs = buildRuns(blocks, keys, (node, key) => {
|
|
10395
|
-
if (cache.lastNode.get(key) === node) {
|
|
10765
|
+
if (cache.lastNode.get(key) === node && !isContextSensitive(node)) {
|
|
10396
10766
|
return { html: cache.lastHtml.get(key) ?? "", kind: cache.lastClass.get(key) ?? "none" };
|
|
10397
10767
|
}
|
|
10398
10768
|
const html = renderer.render(node);
|
|
@@ -10433,7 +10803,12 @@ function reconcileKeyed(container, rootNode, keys, renderer, cache, options) {
|
|
|
10433
10803
|
continue;
|
|
10434
10804
|
}
|
|
10435
10805
|
if (element.nodeType === 1 && shouldMorphInPlace(element, run)) {
|
|
10436
|
-
|
|
10806
|
+
const newEl = nodeFromHtml(run.html);
|
|
10807
|
+
if (newEl && newEl.nodeType === 1 && newEl.tagName === element.tagName) {
|
|
10808
|
+
morphElement(element, newEl);
|
|
10809
|
+
} else {
|
|
10810
|
+
container.replaceChild(newEl, element);
|
|
10811
|
+
}
|
|
10437
10812
|
} else {
|
|
10438
10813
|
container.replaceChild(nodeFromHtml(run.html), element);
|
|
10439
10814
|
}
|
|
@@ -10500,7 +10875,7 @@ function reconcileWindowed(container, rootNode, change, renderer, cache, options
|
|
|
10500
10875
|
let runs;
|
|
10501
10876
|
try {
|
|
10502
10877
|
runs = buildRuns(blocks, keys, (node2, key) => {
|
|
10503
|
-
if (cache.lastNode.get(key) === node2) {
|
|
10878
|
+
if (cache.lastNode.get(key) === node2 && !isContextSensitive(node2)) {
|
|
10504
10879
|
return { html: cache.lastHtml.get(key) ?? "", kind: cache.lastClass.get(key) ?? "none" };
|
|
10505
10880
|
}
|
|
10506
10881
|
const html = renderer.render(node2);
|
|
@@ -10659,7 +11034,12 @@ function reconcileWindowed(container, rootNode, change, renderer, cache, options
|
|
|
10659
11034
|
continue;
|
|
10660
11035
|
}
|
|
10661
11036
|
if (element.nodeType === 1 && shouldMorphInPlace(element, run)) {
|
|
10662
|
-
|
|
11037
|
+
const newEl = nodeFromHtml(run.html);
|
|
11038
|
+
if (newEl && newEl.nodeType === 1 && newEl.tagName === element.tagName) {
|
|
11039
|
+
morphElement(element, newEl);
|
|
11040
|
+
} else {
|
|
11041
|
+
container.replaceChild(newEl, element);
|
|
11042
|
+
}
|
|
10663
11043
|
} else {
|
|
10664
11044
|
container.replaceChild(nodeFromHtml(run.html), element);
|
|
10665
11045
|
}
|
|
@@ -11881,6 +12261,13 @@ function domToGreenTree(root) {
|
|
|
11881
12261
|
children.push(greenLeaf("spacing", ""));
|
|
11882
12262
|
}
|
|
11883
12263
|
});
|
|
12264
|
+
} else if (el.classList.contains("lx-audio")) {
|
|
12265
|
+
kind = "audio";
|
|
12266
|
+
const audioSrc = el.getAttribute("data-src") || el.querySelector("audio")?.getAttribute("src") || "";
|
|
12267
|
+
if (audioSrc) {
|
|
12268
|
+
children.push(greenLeaf("text", audioSrc));
|
|
12269
|
+
currentOffset += audioSrc.length;
|
|
12270
|
+
}
|
|
11884
12271
|
} else if (el.style.textAlign === "center") kind = "center";
|
|
11885
12272
|
else if (el.style.textAlign === "right") kind = "right";
|
|
11886
12273
|
else if (el.style.textAlign === "left") kind = "left";
|
|
@@ -12614,7 +13001,7 @@ var GreenNodePool = class _GreenNodePool {
|
|
|
12614
13001
|
};
|
|
12615
13002
|
|
|
12616
13003
|
// src/BBCode/BBCodeDocumentModel.ts
|
|
12617
|
-
var BBCodeDocumentModel = class extends DocumentModel {
|
|
13004
|
+
var BBCodeDocumentModel = class _BBCodeDocumentModel extends DocumentModel {
|
|
12618
13005
|
_strictMode;
|
|
12619
13006
|
_dialect;
|
|
12620
13007
|
/** Per-document interner, or null when interning is off. */
|
|
@@ -12650,6 +13037,35 @@ var BBCodeDocumentModel = class extends DocumentModel {
|
|
|
12650
13037
|
const r = renderer ?? new HTMLRenderer({ dialect: this._dialect, theme: this._dialect === "lyne" ? "lyne" : "osu" });
|
|
12651
13038
|
return r.render(this.redRoot);
|
|
12652
13039
|
}
|
|
13040
|
+
/**
|
|
13041
|
+
* Fast-path read-only rendering for forum posts, comments, and static views.
|
|
13042
|
+
*
|
|
13043
|
+
* Completely bypasses semantic analysis (linter), undo stack allocations, and
|
|
13044
|
+
* editor node ids (idMode: 'none') to maximize throughput and minimize memory.
|
|
13045
|
+
*/
|
|
13046
|
+
static renderForum(source, options = {}) {
|
|
13047
|
+
const previousIdMode = HTMLRenderer.idMode;
|
|
13048
|
+
HTMLRenderer.idMode = "none";
|
|
13049
|
+
try {
|
|
13050
|
+
const dialect = options.dialect ?? (options.theme === "lyne" ? "lyne" : "miliastry");
|
|
13051
|
+
const doc = new _BBCodeDocumentModel({
|
|
13052
|
+
source: source || " ",
|
|
13053
|
+
dialect,
|
|
13054
|
+
autoAnalyze: false,
|
|
13055
|
+
maxUndo: 0,
|
|
13056
|
+
incremental: false
|
|
13057
|
+
});
|
|
13058
|
+
if (!doc.redRoot) return "";
|
|
13059
|
+
const renderer = new HTMLRenderer({
|
|
13060
|
+
...options,
|
|
13061
|
+
dialect,
|
|
13062
|
+
registry: options.registry ?? doc.tagRegistry
|
|
13063
|
+
});
|
|
13064
|
+
return renderer.render(doc.redRoot);
|
|
13065
|
+
} finally {
|
|
13066
|
+
HTMLRenderer.idMode = previousIdMode;
|
|
13067
|
+
}
|
|
13068
|
+
}
|
|
12653
13069
|
/**
|
|
12654
13070
|
* Parse BBCode text directly to a GreenNode tree using the
|
|
12655
13071
|
* DocumentEngine's built-in BBCode Lexer + Parser.
|
|
@@ -12700,6 +13116,7 @@ var BBCodeDocumentModel = class extends DocumentModel {
|
|
|
12700
13116
|
return new BBCodeExporter(this.tagRegistry, exportTarget).export(root);
|
|
12701
13117
|
}
|
|
12702
13118
|
};
|
|
13119
|
+
var renderForumBBCode = BBCodeDocumentModel.renderForum;
|
|
12703
13120
|
|
|
12704
13121
|
// src/Edits/Rules/Rule.ts
|
|
12705
13122
|
function endOf(p) {
|
|
@@ -13262,31 +13679,32 @@ function transformRange(range3, changes) {
|
|
|
13262
13679
|
// src/Visuals/BoxDrawer.ts
|
|
13263
13680
|
var DEFAULT_DURATION_MS = 400;
|
|
13264
13681
|
var MIN_DURATION_MS = 120;
|
|
13682
|
+
var OSU_OPEN_CLASS = "js-spoilerbox--open";
|
|
13265
13683
|
var DEFAULT_EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
|
|
13266
13684
|
var running = /* @__PURE__ */ new WeakMap();
|
|
13267
13685
|
function prefersReducedMotion() {
|
|
13268
13686
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
13269
13687
|
}
|
|
13270
|
-
function
|
|
13688
|
+
function animateDrawer(details, isOpen, setOpen, options = {}) {
|
|
13271
13689
|
if (typeof details.animate !== "function" || prefersReducedMotion()) {
|
|
13272
|
-
|
|
13690
|
+
setOpen(!isOpen());
|
|
13273
13691
|
return;
|
|
13274
13692
|
}
|
|
13275
13693
|
const from = details.getBoundingClientRect().height;
|
|
13276
13694
|
const previous = running.get(details);
|
|
13277
13695
|
const restoreOverflow = previous ? previous.restoreOverflow : details.style.overflow;
|
|
13278
|
-
const opening = previous ? !previous.opening : !
|
|
13696
|
+
const opening = previous ? !previous.opening : !isOpen();
|
|
13279
13697
|
previous?.animation.cancel();
|
|
13280
13698
|
let collapsed;
|
|
13281
13699
|
let expanded;
|
|
13282
|
-
if (
|
|
13700
|
+
if (isOpen()) {
|
|
13283
13701
|
expanded = details.getBoundingClientRect().height;
|
|
13284
|
-
|
|
13702
|
+
setOpen(false);
|
|
13285
13703
|
collapsed = details.getBoundingClientRect().height;
|
|
13286
|
-
|
|
13704
|
+
setOpen(true);
|
|
13287
13705
|
} else {
|
|
13288
13706
|
collapsed = details.getBoundingClientRect().height;
|
|
13289
|
-
|
|
13707
|
+
setOpen(true);
|
|
13290
13708
|
expanded = details.getBoundingClientRect().height;
|
|
13291
13709
|
}
|
|
13292
13710
|
const to = opening ? expanded : collapsed;
|
|
@@ -13305,15 +13723,36 @@ function toggleBoxWithDrawer(details, options = {}) {
|
|
|
13305
13723
|
const settle = (finished) => {
|
|
13306
13724
|
if (running.get(details)?.animation !== animation) return;
|
|
13307
13725
|
running.delete(details);
|
|
13308
|
-
if (finished)
|
|
13726
|
+
if (finished) setOpen(opening);
|
|
13309
13727
|
details.style.overflow = restoreOverflow;
|
|
13310
13728
|
};
|
|
13311
13729
|
animation.addEventListener("finish", () => settle(true));
|
|
13312
13730
|
animation.addEventListener("cancel", () => settle(false));
|
|
13313
13731
|
}
|
|
13732
|
+
function toggleBoxWithDrawer(details, options = {}) {
|
|
13733
|
+
animateDrawer(details, () => details.open, (v) => {
|
|
13734
|
+
details.open = v;
|
|
13735
|
+
}, options);
|
|
13736
|
+
}
|
|
13737
|
+
function toggleSpoilerboxWithDrawer(box, options = {}) {
|
|
13738
|
+
animateDrawer(
|
|
13739
|
+
box,
|
|
13740
|
+
() => box.classList.contains(OSU_OPEN_CLASS),
|
|
13741
|
+
(v) => box.classList.toggle(OSU_OPEN_CLASS, v),
|
|
13742
|
+
options
|
|
13743
|
+
);
|
|
13744
|
+
}
|
|
13314
13745
|
function bindBoxDrawer(root, options = {}) {
|
|
13315
13746
|
const handleClick = (e) => {
|
|
13316
13747
|
const target = e.target;
|
|
13748
|
+
const osuLink = target?.closest(".js-spoilerbox__link");
|
|
13749
|
+
if (osuLink) {
|
|
13750
|
+
const box = osuLink.closest(".js-spoilerbox");
|
|
13751
|
+
if (!(box instanceof HTMLElement) || !root.contains(box)) return;
|
|
13752
|
+
e.preventDefault();
|
|
13753
|
+
toggleSpoilerboxWithDrawer(box, options);
|
|
13754
|
+
return;
|
|
13755
|
+
}
|
|
13317
13756
|
const summary = target?.closest("summary");
|
|
13318
13757
|
if (!summary) return;
|
|
13319
13758
|
const details = summary.parentElement;
|
|
@@ -13326,6 +13765,314 @@ function bindBoxDrawer(root, options = {}) {
|
|
|
13326
13765
|
return () => root.removeEventListener("click", handleClick);
|
|
13327
13766
|
}
|
|
13328
13767
|
|
|
13768
|
+
// src/Visuals/LyneAudio.ts
|
|
13769
|
+
function bindLyneAudio(root = typeof document !== "undefined" ? document.documentElement : void 0) {
|
|
13770
|
+
if (!root || typeof root.addEventListener !== "function") {
|
|
13771
|
+
return () => {
|
|
13772
|
+
};
|
|
13773
|
+
}
|
|
13774
|
+
const formatTime = (seconds) => {
|
|
13775
|
+
if (!isFinite(seconds) || isNaN(seconds) || seconds < 0) return "\u2013:\u2013\u2013";
|
|
13776
|
+
const m = Math.floor(seconds / 60);
|
|
13777
|
+
const s = Math.floor(seconds % 60);
|
|
13778
|
+
return `${m}:${s < 10 ? "0" : ""}${s}`;
|
|
13779
|
+
};
|
|
13780
|
+
const PLAY_SVG = '<svg viewBox="0 0 16 16"><path d="M3 1.5 14 8 3 14.5z"/></svg>';
|
|
13781
|
+
const PAUSE_SVG = '<svg viewBox="0 0 16 16"><path d="M3 2h3.6v12H3zM9.4 2H13v12H9.4z"/></svg>';
|
|
13782
|
+
const RETRY_SVG = '<svg viewBox="0 0 16 16"><path d="M8 2a6 6 0 1 0 6 6h-2a4 4 0 1 1-4-4v3l5-4-5-4z"/></svg>';
|
|
13783
|
+
const VOL_LOW_SVG = '<svg viewBox="0 0 16 16"><path d="M10.707 11.182A4.5 4.5 0 0 0 12.025 8a4.5 4.5 0 0 0-1.318-3.182L10 5.525A3.5 3.5 0 0 1 11.025 8 3.5 3.5 0 0 1 10 10.475zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06"/></svg>';
|
|
13784
|
+
const VOL_HIGH_SVG = '<svg viewBox="0 0 16 16"><path d="M11.536 14.01A8.47 8.47 0 0 0 14.026 8a8.47 8.47 0 0 0-2.49-6.01l-.708.707A7.48 7.48 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303z"/><path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.48 5.48 0 0 1 11.025 8a5.48 5.48 0 0 1-1.61 3.89z"/><path d="M8.707 11.182A4.5 4.5 0 0 0 10.025 8a4.5 4.5 0 0 0-1.318-3.182L8 5.525A3.5 3.5 0 0 1 9.025 8 3.5 3.5 0 0 1 8 10.475zM6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06"/></svg>';
|
|
13785
|
+
const VOL_MUTE_SVG = '<svg viewBox="0 0 16 16"><path d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06m7.137 2.096a.5.5 0 0 1 0 .708L12.207 8l1.647 1.646a.5.5 0 0 1-.708.708L11.5 8.707l-1.646 1.647a.5.5 0 0 1-.708-.708L10.793 8 9.146 6.354a.5.5 0 1 1 .708-.708L11.5 7.293l1.646-1.647a.5.5 0 0 1 .708 0"/></svg>';
|
|
13786
|
+
const DEFAULT_VOLUME = 0.2;
|
|
13787
|
+
const applyDefaultVolume = (audio) => {
|
|
13788
|
+
if (audio && audio.dataset.volSet !== "true") {
|
|
13789
|
+
audio.volume = DEFAULT_VOLUME;
|
|
13790
|
+
audio.dataset.volSet = "true";
|
|
13791
|
+
}
|
|
13792
|
+
};
|
|
13793
|
+
const updateVolUI = (player, vol) => {
|
|
13794
|
+
const btn = player.querySelector(".lx-vol-btn");
|
|
13795
|
+
const slider = player.querySelector(".lx-vol-slider");
|
|
13796
|
+
if (slider) {
|
|
13797
|
+
if (Math.abs(parseFloat(slider.value) - vol) > 5e-3) {
|
|
13798
|
+
slider.value = String(vol);
|
|
13799
|
+
}
|
|
13800
|
+
const pct = (vol * 100).toFixed(1);
|
|
13801
|
+
slider.style.background = `linear-gradient(to right, var(--color-accent, #2EE6E2) ${pct}%, var(--color-inset-well, #080D20) ${pct}%)`;
|
|
13802
|
+
}
|
|
13803
|
+
if (btn) {
|
|
13804
|
+
if (vol <= 1e-3) {
|
|
13805
|
+
btn.innerHTML = VOL_MUTE_SVG;
|
|
13806
|
+
btn.setAttribute("aria-label", "Unmute");
|
|
13807
|
+
} else if (vol > 0.5) {
|
|
13808
|
+
btn.innerHTML = VOL_HIGH_SVG;
|
|
13809
|
+
btn.setAttribute("aria-label", "Mute");
|
|
13810
|
+
} else {
|
|
13811
|
+
btn.innerHTML = VOL_LOW_SVG;
|
|
13812
|
+
btn.setAttribute("aria-label", "Mute");
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
};
|
|
13816
|
+
const onClick = (e) => {
|
|
13817
|
+
const target = e.target;
|
|
13818
|
+
if (!target) return;
|
|
13819
|
+
const btn = target.closest(".lx-btn");
|
|
13820
|
+
if (btn) {
|
|
13821
|
+
const player = btn.closest(".lx-audio");
|
|
13822
|
+
const audio = player?.querySelector("audio");
|
|
13823
|
+
if (audio && player) {
|
|
13824
|
+
e.preventDefault();
|
|
13825
|
+
e.stopPropagation();
|
|
13826
|
+
if (player.classList.contains("is-error")) {
|
|
13827
|
+
player.classList.remove("is-error");
|
|
13828
|
+
player.classList.add("is-loading");
|
|
13829
|
+
audio.load();
|
|
13830
|
+
const p = audio.play();
|
|
13831
|
+
if (p !== void 0) {
|
|
13832
|
+
p.catch((err) => {
|
|
13833
|
+
console.warn("[LyneAudio] retry playback failed:", err);
|
|
13834
|
+
player.classList.remove("is-loading");
|
|
13835
|
+
player.classList.add("is-error");
|
|
13836
|
+
});
|
|
13837
|
+
}
|
|
13838
|
+
} else if (audio.paused) {
|
|
13839
|
+
const doc = root.ownerDocument || document;
|
|
13840
|
+
doc.querySelectorAll(".lx-audio.is-playing").forEach((other) => {
|
|
13841
|
+
if (other !== player) {
|
|
13842
|
+
const otherAudio = other.querySelector("audio");
|
|
13843
|
+
if (otherAudio && !otherAudio.paused) {
|
|
13844
|
+
otherAudio.pause();
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
13847
|
+
});
|
|
13848
|
+
player.classList.add("is-loading");
|
|
13849
|
+
const p = audio.play();
|
|
13850
|
+
if (p !== void 0) {
|
|
13851
|
+
p.then(() => {
|
|
13852
|
+
player.classList.remove("is-loading");
|
|
13853
|
+
}).catch((err) => {
|
|
13854
|
+
console.warn("[LyneAudio] play failed:", err);
|
|
13855
|
+
player.classList.remove("is-loading");
|
|
13856
|
+
player.classList.add("is-error");
|
|
13857
|
+
});
|
|
13858
|
+
}
|
|
13859
|
+
} else {
|
|
13860
|
+
audio.pause();
|
|
13861
|
+
}
|
|
13862
|
+
}
|
|
13863
|
+
return;
|
|
13864
|
+
}
|
|
13865
|
+
const track = target.closest(".lx-track");
|
|
13866
|
+
if (track) {
|
|
13867
|
+
const player = track.closest(".lx-audio");
|
|
13868
|
+
const audio = player?.querySelector("audio");
|
|
13869
|
+
if (audio) {
|
|
13870
|
+
e.preventDefault();
|
|
13871
|
+
e.stopPropagation();
|
|
13872
|
+
const rect = track.getBoundingClientRect();
|
|
13873
|
+
const pct = Math.min(Math.max((e.clientX - rect.left) / rect.width, 0), 1);
|
|
13874
|
+
if (audio.duration && isFinite(audio.duration)) {
|
|
13875
|
+
audio.currentTime = pct * audio.duration;
|
|
13876
|
+
}
|
|
13877
|
+
const fill = track.querySelector(".fill");
|
|
13878
|
+
if (fill) fill.style.width = `${pct * 100}%`;
|
|
13879
|
+
}
|
|
13880
|
+
return;
|
|
13881
|
+
}
|
|
13882
|
+
const speed = target.closest(".lx-speed");
|
|
13883
|
+
if (speed) {
|
|
13884
|
+
const player = speed.closest(".lx-audio");
|
|
13885
|
+
const audio = player?.querySelector("audio");
|
|
13886
|
+
if (audio) {
|
|
13887
|
+
e.preventDefault();
|
|
13888
|
+
e.stopPropagation();
|
|
13889
|
+
const rates = [1, 1.25, 1.5, 0.75];
|
|
13890
|
+
const curRate = audio.playbackRate || 1;
|
|
13891
|
+
const curIdx = rates.indexOf(curRate);
|
|
13892
|
+
const nextRate = rates[(curIdx + 1) % rates.length];
|
|
13893
|
+
audio.playbackRate = nextRate;
|
|
13894
|
+
speed.textContent = `${nextRate}\xD7`;
|
|
13895
|
+
speed.classList.toggle("active", nextRate !== 1);
|
|
13896
|
+
}
|
|
13897
|
+
return;
|
|
13898
|
+
}
|
|
13899
|
+
const volBtn = target.closest(".lx-vol-btn");
|
|
13900
|
+
if (volBtn) {
|
|
13901
|
+
const player = volBtn.closest(".lx-audio");
|
|
13902
|
+
const audio = player?.querySelector("audio");
|
|
13903
|
+
if (audio && player) {
|
|
13904
|
+
e.preventDefault();
|
|
13905
|
+
e.stopPropagation();
|
|
13906
|
+
audio.dataset.volSet = "true";
|
|
13907
|
+
if (audio.muted || audio.volume <= 1e-3) {
|
|
13908
|
+
const prev = parseFloat(audio.dataset.prevVol || String(DEFAULT_VOLUME)) || DEFAULT_VOLUME;
|
|
13909
|
+
audio.muted = false;
|
|
13910
|
+
audio.volume = prev;
|
|
13911
|
+
updateVolUI(player, prev);
|
|
13912
|
+
} else {
|
|
13913
|
+
audio.dataset.prevVol = String(audio.volume);
|
|
13914
|
+
audio.muted = true;
|
|
13915
|
+
updateVolUI(player, 0);
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
return;
|
|
13919
|
+
}
|
|
13920
|
+
};
|
|
13921
|
+
const onInput = (e) => {
|
|
13922
|
+
const target = e.target;
|
|
13923
|
+
if (!target) return;
|
|
13924
|
+
const slider = target.closest(".lx-vol-slider");
|
|
13925
|
+
if (slider) {
|
|
13926
|
+
const player = slider.closest(".lx-audio");
|
|
13927
|
+
const audio = player?.querySelector("audio");
|
|
13928
|
+
if (audio) {
|
|
13929
|
+
const val = parseFloat(slider.value);
|
|
13930
|
+
const clamped = isNaN(val) ? DEFAULT_VOLUME : Math.max(0, Math.min(1, val));
|
|
13931
|
+
audio.dataset.volSet = "true";
|
|
13932
|
+
audio.muted = clamped === 0;
|
|
13933
|
+
audio.volume = clamped;
|
|
13934
|
+
if (player) {
|
|
13935
|
+
updateVolUI(player, clamped);
|
|
13936
|
+
}
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
13939
|
+
};
|
|
13940
|
+
const onPlay = (e) => {
|
|
13941
|
+
const audio = e.target;
|
|
13942
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
13943
|
+
applyDefaultVolume(audio);
|
|
13944
|
+
const player = audio.closest(".lx-audio");
|
|
13945
|
+
if (!player) return;
|
|
13946
|
+
player.classList.remove("is-loading");
|
|
13947
|
+
player.classList.add("is-playing");
|
|
13948
|
+
const btn = player.querySelector(".lx-btn");
|
|
13949
|
+
if (btn) {
|
|
13950
|
+
btn.innerHTML = PAUSE_SVG;
|
|
13951
|
+
btn.setAttribute("aria-label", "Pause");
|
|
13952
|
+
}
|
|
13953
|
+
const label = player.querySelector(".lx-title .label .status-text");
|
|
13954
|
+
if (label) label.textContent = "playing";
|
|
13955
|
+
updateVolUI(player, audio.muted ? 0 : audio.volume);
|
|
13956
|
+
};
|
|
13957
|
+
const onPause = (e) => {
|
|
13958
|
+
const audio = e.target;
|
|
13959
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
13960
|
+
const player = audio.closest(".lx-audio");
|
|
13961
|
+
if (!player) return;
|
|
13962
|
+
player.classList.remove("is-playing", "is-loading");
|
|
13963
|
+
const btn = player.querySelector(".lx-btn");
|
|
13964
|
+
if (btn) {
|
|
13965
|
+
btn.innerHTML = PLAY_SVG;
|
|
13966
|
+
btn.setAttribute("aria-label", "Play");
|
|
13967
|
+
}
|
|
13968
|
+
const label = player.querySelector(".lx-title .label .status-text");
|
|
13969
|
+
if (label) label.textContent = "audio";
|
|
13970
|
+
};
|
|
13971
|
+
const onTimeUpdate = (e) => {
|
|
13972
|
+
const audio = e.target;
|
|
13973
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
13974
|
+
const player = audio.closest(".lx-audio");
|
|
13975
|
+
if (!player) return;
|
|
13976
|
+
const cur = audio.currentTime || 0;
|
|
13977
|
+
const dur = audio.duration || 0;
|
|
13978
|
+
const fill = player.querySelector(".lx-track .fill");
|
|
13979
|
+
const curTime = player.querySelector(".lx-time .cur");
|
|
13980
|
+
if (fill && dur > 0 && isFinite(dur)) {
|
|
13981
|
+
fill.style.width = `${cur / dur * 100}%`;
|
|
13982
|
+
}
|
|
13983
|
+
if (curTime) {
|
|
13984
|
+
curTime.textContent = formatTime(cur);
|
|
13985
|
+
}
|
|
13986
|
+
};
|
|
13987
|
+
const onLoadedMetadata = (e) => {
|
|
13988
|
+
const audio = e.target;
|
|
13989
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
13990
|
+
applyDefaultVolume(audio);
|
|
13991
|
+
const player = audio.closest(".lx-audio");
|
|
13992
|
+
if (!player) return;
|
|
13993
|
+
const dur = audio.duration || 0;
|
|
13994
|
+
const totalTime = player.querySelector(".lx-time .total");
|
|
13995
|
+
if (totalTime && dur > 0 && isFinite(dur)) {
|
|
13996
|
+
totalTime.textContent = formatTime(dur);
|
|
13997
|
+
}
|
|
13998
|
+
updateVolUI(player, audio.muted ? 0 : audio.volume);
|
|
13999
|
+
};
|
|
14000
|
+
const onWaiting = (e) => {
|
|
14001
|
+
const audio = e.target;
|
|
14002
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
14003
|
+
const player = audio.closest(".lx-audio");
|
|
14004
|
+
if (!player) return;
|
|
14005
|
+
player.classList.add("is-loading");
|
|
14006
|
+
const label = player.querySelector(".lx-title .label .status-text");
|
|
14007
|
+
if (label) label.textContent = "buffering\u2026";
|
|
14008
|
+
};
|
|
14009
|
+
const onError = (e) => {
|
|
14010
|
+
const audio = e.target;
|
|
14011
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
14012
|
+
const player = audio.closest(".lx-audio");
|
|
14013
|
+
if (!player) return;
|
|
14014
|
+
player.classList.remove("is-playing", "is-loading");
|
|
14015
|
+
player.classList.add("is-error");
|
|
14016
|
+
const label = player.querySelector(".lx-title .label .status-text");
|
|
14017
|
+
if (label) label.textContent = "unavailable \u2014 file error";
|
|
14018
|
+
const btn = player.querySelector(".lx-btn");
|
|
14019
|
+
if (btn) {
|
|
14020
|
+
btn.innerHTML = RETRY_SVG;
|
|
14021
|
+
btn.setAttribute("aria-label", "Retry");
|
|
14022
|
+
btn.style.background = "var(--color-danger, #FF3B5C)";
|
|
14023
|
+
}
|
|
14024
|
+
const curTime = player.querySelector(".lx-time .cur");
|
|
14025
|
+
const totalTime = player.querySelector(".lx-time .total");
|
|
14026
|
+
if (curTime) curTime.textContent = "\u2013:\u2013\u2013";
|
|
14027
|
+
if (totalTime) totalTime.textContent = "\u2013:\u2013\u2013";
|
|
14028
|
+
};
|
|
14029
|
+
const onVolumeChange = (e) => {
|
|
14030
|
+
const audio = e.target;
|
|
14031
|
+
if (audio?.tagName !== "AUDIO") return;
|
|
14032
|
+
const player = audio.closest(".lx-audio");
|
|
14033
|
+
if (!player) return;
|
|
14034
|
+
updateVolUI(player, audio.muted ? 0 : audio.volume);
|
|
14035
|
+
};
|
|
14036
|
+
try {
|
|
14037
|
+
root.querySelectorAll("audio").forEach((audio) => {
|
|
14038
|
+
applyDefaultVolume(audio);
|
|
14039
|
+
const player = audio.closest(".lx-audio");
|
|
14040
|
+
if (player) {
|
|
14041
|
+
updateVolUI(player, audio.muted ? 0 : audio.volume);
|
|
14042
|
+
}
|
|
14043
|
+
});
|
|
14044
|
+
} catch {
|
|
14045
|
+
}
|
|
14046
|
+
root.addEventListener("click", onClick, true);
|
|
14047
|
+
root.addEventListener("input", onInput, true);
|
|
14048
|
+
root.addEventListener("change", onInput, true);
|
|
14049
|
+
root.addEventListener("play", onPlay, true);
|
|
14050
|
+
root.addEventListener("pause", onPause, true);
|
|
14051
|
+
root.addEventListener("timeupdate", onTimeUpdate, true);
|
|
14052
|
+
root.addEventListener("loadedmetadata", onLoadedMetadata, true);
|
|
14053
|
+
root.addEventListener("durationchange", onLoadedMetadata, true);
|
|
14054
|
+
root.addEventListener("waiting", onWaiting, true);
|
|
14055
|
+
root.addEventListener("error", onError, true);
|
|
14056
|
+
root.addEventListener("volumechange", onVolumeChange, true);
|
|
14057
|
+
return () => {
|
|
14058
|
+
root.removeEventListener("click", onClick, true);
|
|
14059
|
+
root.removeEventListener("input", onInput, true);
|
|
14060
|
+
root.removeEventListener("change", onInput, true);
|
|
14061
|
+
root.removeEventListener("play", onPlay, true);
|
|
14062
|
+
root.removeEventListener("pause", onPause, true);
|
|
14063
|
+
root.removeEventListener("timeupdate", onTimeUpdate, true);
|
|
14064
|
+
root.removeEventListener("loadedmetadata", onLoadedMetadata, true);
|
|
14065
|
+
root.removeEventListener("durationchange", onLoadedMetadata, true);
|
|
14066
|
+
root.removeEventListener("waiting", onWaiting, true);
|
|
14067
|
+
root.removeEventListener("error", onError, true);
|
|
14068
|
+
root.removeEventListener("volumechange", onVolumeChange, true);
|
|
14069
|
+
};
|
|
14070
|
+
}
|
|
14071
|
+
var setupLyneAudioRuntime = bindLyneAudio;
|
|
14072
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
14073
|
+
bindLyneAudio(document.documentElement);
|
|
14074
|
+
}
|
|
14075
|
+
|
|
13329
14076
|
// src/Visuals/index.ts
|
|
13330
14077
|
var visualThemes = [
|
|
13331
14078
|
{
|
|
@@ -17636,4 +18383,4 @@ var MergeColorsTransform = class {
|
|
|
17636
18383
|
}
|
|
17637
18384
|
};
|
|
17638
18385
|
|
|
17639
|
-
export { ANIM_TYPES, ASTOptimizer, AXES, BBBlocksExporter, BBCODE_RAW_TAGS, BBCODE_TAG_NAMES, BBCodeDocumentModel, BBCodeExporter, BLEND_MODES, BRIDGES_WHITESPACE, CONTAINER_TYPES, ChangeTracker, CharacterCountAnalyzer, CollapseGradientTransform, ColorUsageAnalyzer, CommandRegistry, ContributionKind, DEFAULT_CELL_ASPECT, DEFAULT_MASK, DEFAULT_SPATIAL, DEFAULT_WAVE_OPTIONS, DROPPABLE_WHEN_EMPTY, DROP_EMPTY_PRIORITY, DROP_REDUNDANT_NESTING_PRIORITY, DefaultDecision, DeleteNode, DocumentEventBus, DocumentModel, DropEmptyTagsRule, DropRedundantNestingRule, EFFECT_PARAM_KEYS, EFFECT_TYPES, EFFECT_UNITS, EXPRESSION_VARS, Formatter, GRADIENT_DEFAULTS, GROW_DEFAULTS, GradientAnalyzer, GradientTransformer, GreenNode, GrowTransformer, HTMLDocumentModel, HTMLRenderer, IMG_MODIFIERS, IncrementalParser, InsertText, JSONExporter, LYNE_ONLY_TAGS, Lexer, Linter, MASK_SHAPES, MERGEABLE_INLINE, MERGE_ADJACENT_PRIORITY, MILIASTRY_ONLY_TAGS, MarkdownDocumentModel, MarkdownExporter, MergeAdjacentRule, MergeColorsTransform, MergeNode, MergeableColorAnalyzer, MilHibriDocumentModel, NEUTRAL_SIZE, NodeFactory, NodeMatcher, PAINT_DEFAULTS, PAINT_MAX_COLORS, PaletteRemapDecision, Pipeline, PipelineBuilder, PipelineMode, PipelineStage, PluginAPI, PluginRegistry, QueryEngine, RAINBOW_DEFAULTS, REORDER_WRAPPERS_PRIORITY, RainbowAnalyzer, RainbowCollapseTransform, RainbowTransformer, RedNode, RedNodeStore, RenderPipeline, RenderTree, ReorderWrappersRule, SEPARATOR_VARIANTS, SHORTEN_HEX_PRIORITY, SPATIAL_AXES, SPATIAL_DEFAULTS, SVGRenderer, SemanticAnalyzer, ShortenHexRule, SineWaveTransformer, SplitNode, SymbolAnalyzer, SymbolTable, TABLE_FLAGS, TagRegistry, TiptapExporter, Transaction, TreeBuilder, TreeDiffer, UNWRAP_INVISIBLE_COLOR_PRIORITY, UndoManager, UnwrapInvisibleColorRule, Visitor, WAVE_KINDS, WaveAnalyzer, WaveCollapseTransform, WrapInTag, adjustHsl, allAttributeVocabularies, allRules, applyCentralGradient, applyEditsToSource, applyEffect, applyGradient, applyGrow, applyMultiGradient, applyRainbow, attributeValue, attributeVocabularyFor, axisValue, bbBlockToGreenNode, bbBlocksToGreenTree, bbBlocksToRedTree, bindBoxDrawer, blendHex, buildRangeScope, buildSampleTable, clamp012 as clamp01, clampRange, classifyOverlap, clearPatchCache, closeRange, coalesceDeletions, compareEditPriority, compileExpression, computeTextDelta, countTextLength, defaultRules, deletion, documentScope, domToSVG, domToSVGResult, ease, editsConflict, effectiveAxis, endOf, evaluateEffect, expressionVars, extractTextContent, fbm, findCollapsibleGradients, formatGradientTag, getBBCodeTagNames, greenLeaf, greenNode, greenToRedNode, gridFromParams, hasBothDelimiters, hashSeed, hexToHsl, hexToOklab, hexToRgb, hslToHex, htmlStringToGreenTree, isBlockKind, isHexColor, isInvisibleWhitespace, markdownAstToGreenTree, markdownAstToRedTree, maskDistance, maskFromParams, maskValue, mergeIdentity, mergeStyledSegments, mixHex, mixHexOklab, mixMultiple, mixMultipleStops, morphHTML, nodeAttrValue, nodeKindToTag, normalizeColorValue, normalizeHex, openRange, optimizeBBCode, optimizeTree, parseColorStops, parseEffectParams, parseImgAttr, parsePaintGrid, patchBlocksInto, perceptualDistance, positionedChildren, posterizeHex, randAt, reconcileVisualDOMToBBCode, repairNesting, resolveEditConflicts, resolveTokenValue, rgbToHex, samplePaintGrid, sanitizeColor, sanitizeFontFamily, sanitizeFontSize, shortenableHex, solveCubicBezierY, spatialFromParams, spatialPoint, stringifyColorStops, stringifyEffectParams, stringifyPaintCells, stringifyPaintPalette, tagToNodeKind, toTokenResolver, toggleBoxWithDrawer, transformOffset, transformRange, validateExpression, valueNoise, visualThemes, waveform };
|
|
18386
|
+
export { ANIM_TYPES, ASTOptimizer, AXES, BBBlocksExporter, BBCODE_RAW_TAGS, BBCODE_TAG_NAMES, BBCodeDocumentModel, BBCodeExporter, BLEND_MODES, BRIDGES_WHITESPACE, CONTAINER_TYPES, ChangeTracker, CharacterCountAnalyzer, CollapseGradientTransform, ColorUsageAnalyzer, CommandRegistry, ContributionKind, DEFAULT_CELL_ASPECT, DEFAULT_MASK, DEFAULT_SPATIAL, DEFAULT_WAVE_OPTIONS, DROPPABLE_WHEN_EMPTY, DROP_EMPTY_PRIORITY, DROP_REDUNDANT_NESTING_PRIORITY, DefaultDecision, DeleteNode, DocumentEventBus, DocumentModel, DropEmptyTagsRule, DropRedundantNestingRule, EFFECT_PARAM_KEYS, EFFECT_TYPES, EFFECT_UNITS, EXPRESSION_VARS, Formatter, GRADIENT_DEFAULTS, GROW_DEFAULTS, GradientAnalyzer, GradientTransformer, GreenNode, GrowTransformer, HTMLDocumentModel, HTMLRenderer, IMG_MODIFIERS, IncrementalParser, InsertText, JSONExporter, LYNE_ONLY_TAGS, Lexer, Linter, MASK_SHAPES, MERGEABLE_INLINE, MERGE_ADJACENT_PRIORITY, MILIASTRY_ONLY_TAGS, MarkdownDocumentModel, MarkdownExporter, MergeAdjacentRule, MergeColorsTransform, MergeNode, MergeableColorAnalyzer, MilHibriDocumentModel, NEUTRAL_SIZE, NodeFactory, NodeMatcher, PAINT_DEFAULTS, PAINT_MAX_COLORS, PaletteRemapDecision, Pipeline, PipelineBuilder, PipelineMode, PipelineStage, PluginAPI, PluginRegistry, QueryEngine, RAINBOW_DEFAULTS, REORDER_WRAPPERS_PRIORITY, RainbowAnalyzer, RainbowCollapseTransform, RainbowTransformer, RedNode, RedNodeStore, RenderPipeline, RenderTree, ReorderWrappersRule, SEPARATOR_VARIANTS, SHORTEN_HEX_PRIORITY, SPATIAL_AXES, SPATIAL_DEFAULTS, SVGRenderer, SemanticAnalyzer, ShortenHexRule, SineWaveTransformer, SplitNode, SymbolAnalyzer, SymbolTable, TABLE_FLAGS, TagRegistry, TiptapExporter, Transaction, TreeBuilder, TreeDiffer, UNWRAP_INVISIBLE_COLOR_PRIORITY, UndoManager, UnwrapInvisibleColorRule, Visitor, WAVE_KINDS, WaveAnalyzer, WaveCollapseTransform, WrapInTag, adjustHsl, allAttributeVocabularies, allRules, applyCentralGradient, applyEditsToSource, applyEffect, applyGradient, applyGrow, applyMultiGradient, applyRainbow, attributeValue, attributeVocabularyFor, axisValue, bbBlockToGreenNode, bbBlocksToGreenTree, bbBlocksToRedTree, bindBoxDrawer, bindLyneAudio, blendHex, buildRangeScope, buildSampleTable, clamp012 as clamp01, clampRange, classifyOverlap, clearPatchCache, closeRange, coalesceDeletions, compareEditPriority, compileExpression, computeTextDelta, countTextLength, defaultRules, deletion, documentScope, domToSVG, domToSVGResult, ease, editsConflict, effectiveAxis, endOf, evaluateEffect, expressionVars, extractTextContent, fbm, findCollapsibleGradients, formatGradientTag, getBBCodeTagNames, greenLeaf, greenNode, greenToRedNode, gridFromParams, hasBothDelimiters, hashSeed, hexToHsl, hexToOklab, hexToRgb, hslToHex, htmlStringToGreenTree, isBlockKind, isHexColor, isInvisibleWhitespace, markdownAstToGreenTree, markdownAstToRedTree, maskDistance, maskFromParams, maskValue, mergeIdentity, mergeStyledSegments, mixHex, mixHexOklab, mixMultiple, mixMultipleStops, morphElement, morphHTML, nodeAttrValue, nodeKindToTag, normalizeColorValue, normalizeHex, openRange, optimizeBBCode, optimizeTree, parseColorStops, parseEffectParams, parseImgAttr, parsePaintGrid, patchBlocksInto, perceptualDistance, positionedChildren, posterizeHex, randAt, reconcileVisualDOMToBBCode, renderForumBBCode, repairNesting, resolveEditConflicts, resolveTokenValue, rgbToHex, samplePaintGrid, sanitizeColor, sanitizeFontFamily, sanitizeFontSize, setupLyneAudioRuntime, shortenableHex, solveCubicBezierY, spatialFromParams, spatialPoint, stringifyColorStops, stringifyEffectParams, stringifyPaintCells, stringifyPaintPalette, tagToNodeKind, toTokenResolver, toggleBoxWithDrawer, toggleSpoilerboxWithDrawer, transformOffset, transformRange, validateExpression, valueNoise, visualThemes, waveform };
|