@rohal12/spindle 0.52.3 → 0.52.5
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/dist/pkg/format.js +1 -1
- package/dist/pkg/headless.js +1105 -1588
- package/dist/pkg/story-variables.js +155 -469
- package/package.json +1 -1
- package/src/class-registry.ts +6 -182
- package/src/components/macros/Widget.tsx +18 -7
- package/src/execute-mutation.ts +17 -97
- package/src/index.tsx +5 -8
- package/src/interpolation.ts +16 -22
- package/src/js-lexer.ts +20 -9
- package/src/markup/ast.ts +108 -147
- package/src/markup/code-attributes.ts +4 -6
- package/src/markup/tokenizer.ts +218 -574
- package/src/saves/save-manager.ts +190 -259
- package/src/saves/storage.ts +229 -402
- package/src/store.ts +244 -310
- package/src/structural.ts +388 -0
|
@@ -101,13 +101,13 @@ function scanRegex(src, start, cache) {
|
|
|
101
101
|
};
|
|
102
102
|
let i = start + 1;
|
|
103
103
|
while (i < src.length) {
|
|
104
|
+
const lineEnd = regexLineEnd(src, i);
|
|
105
|
+
if (lineEnd !== -1) return done(lineEnd, false);
|
|
104
106
|
const c = src.charAt(i);
|
|
105
107
|
if (c === "\\") {
|
|
106
|
-
if (LINE_TERMINATOR_RE.test(src.charAt(i + 1))) return done(i + 1, false);
|
|
107
108
|
i += 2;
|
|
108
109
|
continue;
|
|
109
110
|
}
|
|
110
|
-
if (LINE_TERMINATOR_RE.test(c)) return done(i, false);
|
|
111
111
|
if (c === "[") {
|
|
112
112
|
i = scanRegexClass(src, i, cache);
|
|
113
113
|
if (src.charAt(i) !== "]") return done(i, false);
|
|
@@ -138,16 +138,17 @@ function scanRegexClass(src, open, cache) {
|
|
|
138
138
|
const opens = [open];
|
|
139
139
|
let i = open + 1;
|
|
140
140
|
while (i < src.length) {
|
|
141
|
+
const lineEnd = regexLineEnd(src, i);
|
|
142
|
+
if (lineEnd !== -1) {
|
|
143
|
+
i = lineEnd;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
141
146
|
const c = src.charAt(i);
|
|
142
147
|
if (c === "\\") {
|
|
143
|
-
if (LINE_TERMINATOR_RE.test(src.charAt(i + 1))) {
|
|
144
|
-
i++;
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
148
|
i += 2;
|
|
148
149
|
continue;
|
|
149
150
|
}
|
|
150
|
-
if (c === "]"
|
|
151
|
+
if (c === "]") break;
|
|
151
152
|
if (c === "[" && cache) opens.push(i);
|
|
152
153
|
i++;
|
|
153
154
|
}
|
|
@@ -156,6 +157,16 @@ function scanRegexClass(src, open, cache) {
|
|
|
156
157
|
return i;
|
|
157
158
|
}
|
|
158
159
|
/**
|
|
160
|
+
* Index of the line break at `i`, escaped (`\` then a line break) or not,
|
|
161
|
+
* where a regex literal or class ends unterminated; -1 if there is none.
|
|
162
|
+
*/
|
|
163
|
+
function regexLineEnd(src, i) {
|
|
164
|
+
const c = src.charAt(i);
|
|
165
|
+
if (LINE_TERMINATOR_RE.test(c)) return i;
|
|
166
|
+
if (c === "\\" && LINE_TERMINATOR_RE.test(src.charAt(i + 1))) return i + 1;
|
|
167
|
+
return -1;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
159
170
|
* The next index from `from` on where `find` matches (-1 for none), for
|
|
160
171
|
* searches from increasing positions: a search from within the stretch the
|
|
161
172
|
* last one covered has the same answer.
|
|
@@ -1105,8 +1116,7 @@ function splitSigilTemplate(template) {
|
|
|
1105
1116
|
}
|
|
1106
1117
|
text += template.slice(i, brace);
|
|
1107
1118
|
i = brace + 1;
|
|
1108
|
-
const
|
|
1109
|
-
const end = sigil === "$" || sigil === "_" || sigil === "@" || sigil === "%" ? scanBalancedBrace(template, i, memo) : -1;
|
|
1119
|
+
const end = isSigil(template[i]) ? scanBalancedBrace(template, i, memo) : -1;
|
|
1110
1120
|
if (end === -1) {
|
|
1111
1121
|
text += "{";
|
|
1112
1122
|
continue;
|
|
@@ -1122,6 +1132,25 @@ function splitSigilTemplate(template) {
|
|
|
1122
1132
|
}
|
|
1123
1133
|
//#endregion
|
|
1124
1134
|
//#region src/markup/tokenizer.ts
|
|
1135
|
+
/** Variable sigils: story ($), temporary (_), local (@), transient (%). */
|
|
1136
|
+
var SIGIL_SCOPES = {
|
|
1137
|
+
$: "variable",
|
|
1138
|
+
_: "temporary",
|
|
1139
|
+
"@": "local",
|
|
1140
|
+
"%": "transient"
|
|
1141
|
+
};
|
|
1142
|
+
Object.fromEntries(Object.entries(SIGIL_SCOPES).map(([sigil, scope]) => [scope, sigil]));
|
|
1143
|
+
var SIGIL_CHARS = new Set(Object.keys(SIGIL_SCOPES));
|
|
1144
|
+
/** Whether `c` is a variable sigil. */
|
|
1145
|
+
function isSigil(c) {
|
|
1146
|
+
return c !== void 0 && SIGIL_CHARS.has(c);
|
|
1147
|
+
}
|
|
1148
|
+
/** Copy the selectors set in `from` onto `target`, and return it. */
|
|
1149
|
+
function withSelectors(target, from) {
|
|
1150
|
+
if (from.className) target.className = from.className;
|
|
1151
|
+
if (from.id) target.id = from.id;
|
|
1152
|
+
return target;
|
|
1153
|
+
}
|
|
1125
1154
|
/** Tag name must start with a letter (covers standard and custom elements). */
|
|
1126
1155
|
var VALID_TAG_START = /[a-zA-Z]/;
|
|
1127
1156
|
/** HTML void elements: never have children or a closing tag. */
|
|
@@ -1141,13 +1170,6 @@ var HTML_VOID_TAGS = new Set([
|
|
|
1141
1170
|
"track",
|
|
1142
1171
|
"wbr"
|
|
1143
1172
|
]);
|
|
1144
|
-
/** Variable sigils: story ($), temporary (_), local (@), transient (%). */
|
|
1145
|
-
var SIGIL_CHARS = new Set([
|
|
1146
|
-
"$",
|
|
1147
|
-
"_",
|
|
1148
|
-
"@",
|
|
1149
|
-
"%"
|
|
1150
|
-
]);
|
|
1151
1173
|
/**
|
|
1152
1174
|
* Characters other than a sigil that open an expression after `{`:
|
|
1153
1175
|
* `{(Math.max($a, 0))}`, `{!$done}`. Other characters that can start an
|
|
@@ -1158,25 +1180,32 @@ var EXPRESSION_START = new Set(["(", "!"]);
|
|
|
1158
1180
|
/** Macros whose body is JavaScript source, kept verbatim instead of tokenized. */
|
|
1159
1181
|
var RAW_BODY_MACROS = new Set(["do"]);
|
|
1160
1182
|
/**
|
|
1183
|
+
* Link separators, in the order they are tried, and whether the target
|
|
1184
|
+
* comes first: display|target, display->target, target<-display.
|
|
1185
|
+
*/
|
|
1186
|
+
var LINK_SEPARATORS = [
|
|
1187
|
+
["|", false],
|
|
1188
|
+
["->", false],
|
|
1189
|
+
["<-", true]
|
|
1190
|
+
];
|
|
1191
|
+
/**
|
|
1161
1192
|
* Parse a Twine link interior into display and target.
|
|
1162
1193
|
* Supports: display|target, display->target, target<-display, plain
|
|
1163
1194
|
*/
|
|
1164
1195
|
function parseLink(inner) {
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
display: inner.slice(revIdx + 2).trim()
|
|
1179
|
-
};
|
|
1196
|
+
for (const [separator, targetFirst] of LINK_SEPARATORS) {
|
|
1197
|
+
const idx = inner.indexOf(separator);
|
|
1198
|
+
if (idx === -1) continue;
|
|
1199
|
+
const before = inner.slice(0, idx).trim();
|
|
1200
|
+
const after = inner.slice(idx + separator.length).trim();
|
|
1201
|
+
return targetFirst ? {
|
|
1202
|
+
display: after,
|
|
1203
|
+
target: before
|
|
1204
|
+
} : {
|
|
1205
|
+
display: before,
|
|
1206
|
+
target: after
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1180
1209
|
const trimmed = inner.trim();
|
|
1181
1210
|
return {
|
|
1182
1211
|
display: trimmed,
|
|
@@ -1207,8 +1236,10 @@ function parseMacroContent(content) {
|
|
|
1207
1236
|
}
|
|
1208
1237
|
/**
|
|
1209
1238
|
* Parse CSS selectors: .foo.bar#baz → { className: "foo bar", id: "baz" }
|
|
1210
|
-
* Scans .[a-zA-Z0-9_-]+ and #[a-zA-Z0-9_-]+ segments in any order
|
|
1211
|
-
* Returns space-joined class string, last id wins
|
|
1239
|
+
* Scans .[a-zA-Z0-9_-]+ and #[a-zA-Z0-9_-]+ segments in any order, and one
|
|
1240
|
+
* space after them. Returns space-joined class string, last id wins (each
|
|
1241
|
+
* left out if empty), and the position after them: `startIdx` if there are
|
|
1242
|
+
* none.
|
|
1212
1243
|
*/
|
|
1213
1244
|
function parseSelectors(input, startIdx) {
|
|
1214
1245
|
const classes = [];
|
|
@@ -1221,7 +1252,7 @@ function parseSelectors(input, startIdx) {
|
|
|
1221
1252
|
while (i < input.length) if (/[a-zA-Z0-9_-]/.test(input[i])) {
|
|
1222
1253
|
name += input[i];
|
|
1223
1254
|
i++;
|
|
1224
|
-
} else if (input[i] === "{" &&
|
|
1255
|
+
} else if (input[i] === "{" && isSigil(input[i + 1])) {
|
|
1225
1256
|
const braceStart = i;
|
|
1226
1257
|
i += 2;
|
|
1227
1258
|
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
@@ -1236,10 +1267,13 @@ function parseSelectors(input, startIdx) {
|
|
|
1236
1267
|
if (name) if (prefix === ".") classes.push(name);
|
|
1237
1268
|
else id = name;
|
|
1238
1269
|
}
|
|
1270
|
+
if (i > startIdx && input[i] === " ") i++;
|
|
1239
1271
|
return {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1272
|
+
selectors: withSelectors({}, {
|
|
1273
|
+
className: classes.join(" "),
|
|
1274
|
+
id
|
|
1275
|
+
}),
|
|
1276
|
+
end: i
|
|
1243
1277
|
};
|
|
1244
1278
|
}
|
|
1245
1279
|
/**
|
|
@@ -1354,7 +1388,7 @@ function scanQuotedValue(input, j, quote, code, memo) {
|
|
|
1354
1388
|
continue;
|
|
1355
1389
|
}
|
|
1356
1390
|
if (input[j] === "{") {
|
|
1357
|
-
const closeIdx = !code ? scanBlockClose(input, j, memo) :
|
|
1391
|
+
const closeIdx = !code ? scanBlockClose(input, j, memo) : isSigil(input[j + 1]) ? scanBalancedBrace(input, j + 1, memo) : -1;
|
|
1358
1392
|
if (closeIdx !== -1) {
|
|
1359
1393
|
j = closeIdx + 1;
|
|
1360
1394
|
checkpoint = true;
|
|
@@ -1430,12 +1464,7 @@ function scanMacroClose(input, contentStart, memo) {
|
|
|
1430
1464
|
* code from just past the {. Returns -1 if it is unclosed.
|
|
1431
1465
|
*/
|
|
1432
1466
|
function scanBlockClose(input, open, memo) {
|
|
1433
|
-
|
|
1434
|
-
const c = input[at];
|
|
1435
|
-
if (c === "." || c === "#") {
|
|
1436
|
-
at = parseSelectors(input, at).endIdx;
|
|
1437
|
-
if (input[at] === " ") at++;
|
|
1438
|
-
}
|
|
1467
|
+
const at = parseSelectors(input, open + 1).end;
|
|
1439
1468
|
const first = input[at];
|
|
1440
1469
|
if (first !== void 0 && (first === "/" || /[a-zA-Z]/.test(first))) return scanMacroClose(input, at, memo);
|
|
1441
1470
|
return scanBalancedBrace(input, at, memo);
|
|
@@ -1627,13 +1656,22 @@ function tokenize(input, options = {}) {
|
|
|
1627
1656
|
const memo = createScanMemo();
|
|
1628
1657
|
let i = 0;
|
|
1629
1658
|
let textStart = 0;
|
|
1659
|
+
/** Push a text token for the text not yet tokenized before `end`. */
|
|
1630
1660
|
function flushText(end) {
|
|
1631
|
-
if (end > textStart)
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1661
|
+
if (end > textStart) {
|
|
1662
|
+
tokens.push({
|
|
1663
|
+
type: "text",
|
|
1664
|
+
value: input.slice(textStart, end),
|
|
1665
|
+
start: textStart,
|
|
1666
|
+
end
|
|
1667
|
+
});
|
|
1668
|
+
textStart = end;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
/** Push `token` and go on after it. */
|
|
1672
|
+
function emit(token) {
|
|
1673
|
+
tokens.push(token);
|
|
1674
|
+
i = textStart = token.end;
|
|
1637
1675
|
}
|
|
1638
1676
|
/**
|
|
1639
1677
|
* After an opening raw-body macro ({do}), emit everything up to its
|
|
@@ -1675,34 +1713,66 @@ function tokenize(input, options = {}) {
|
|
|
1675
1713
|
atRe.lastIndex = closeStart;
|
|
1676
1714
|
const closeEnd = closeStart + atRe.exec(input)[0].length;
|
|
1677
1715
|
flushText(closeStart);
|
|
1678
|
-
|
|
1716
|
+
emit({
|
|
1679
1717
|
type: "macro",
|
|
1680
1718
|
...parseMacroContent(input.slice(closeStart + 1, closeEnd - 1)),
|
|
1681
1719
|
start: closeStart,
|
|
1682
1720
|
end: closeEnd
|
|
1683
1721
|
});
|
|
1684
|
-
i = closeEnd;
|
|
1685
|
-
textStart = closeEnd;
|
|
1686
1722
|
}
|
|
1687
1723
|
/**
|
|
1688
1724
|
* Push an expression token for the `{…}` block opened at `start` whose
|
|
1689
|
-
* expression starts at `exprStart`, flushing the text before it first
|
|
1690
|
-
*
|
|
1725
|
+
* expression starts at `exprStart`, flushing the text before it first.
|
|
1726
|
+
* Returns false, consuming nothing, if the block is unclosed.
|
|
1691
1727
|
*/
|
|
1692
|
-
function pushExpression(exprStart, start,
|
|
1728
|
+
function pushExpression(exprStart, start, selectors) {
|
|
1693
1729
|
const closeIdx = scanBalancedBrace(input, exprStart, memo);
|
|
1694
1730
|
if (closeIdx === -1) return false;
|
|
1695
|
-
|
|
1696
|
-
|
|
1731
|
+
flushText(start);
|
|
1732
|
+
emit(withSelectors({
|
|
1697
1733
|
type: "expression",
|
|
1698
1734
|
expression: input.slice(exprStart, closeIdx),
|
|
1699
1735
|
start,
|
|
1700
1736
|
end: closeIdx + 1
|
|
1701
|
-
};
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1737
|
+
}, selectors));
|
|
1738
|
+
return true;
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Push a variable token for the `{…}` block opened at `start` whose sigil
|
|
1742
|
+
* is at `sigilAt`: `{$name}`, `{_name.field.subfield}`. Anything else
|
|
1743
|
+
* after the name makes the block an expression from the sigil on
|
|
1744
|
+
* (`{$expr[...]}`). Returns false, consuming nothing, if it is unclosed.
|
|
1745
|
+
*/
|
|
1746
|
+
function pushVariable(sigilAt, start, selectors) {
|
|
1747
|
+
let nameEnd = sigilAt + 1;
|
|
1748
|
+
while (nameEnd < input.length && /[\w.]/.test(input[nameEnd])) nameEnd++;
|
|
1749
|
+
if (input[nameEnd] !== "}") return pushExpression(sigilAt, start, selectors);
|
|
1750
|
+
emit(withSelectors({
|
|
1751
|
+
type: "variable",
|
|
1752
|
+
name: input.slice(sigilAt + 1, nameEnd),
|
|
1753
|
+
scope: SIGIL_SCOPES[input[sigilAt]],
|
|
1754
|
+
start,
|
|
1755
|
+
end: nameEnd + 1
|
|
1756
|
+
}, selectors));
|
|
1757
|
+
return true;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Push a macro token for the `{…}` block opened at `start` whose content
|
|
1761
|
+
* (name, then arguments) starts at `contentStart`, then consume the body
|
|
1762
|
+
* of a raw-body macro. Returns false, consuming nothing, if the block is
|
|
1763
|
+
* unclosed.
|
|
1764
|
+
*/
|
|
1765
|
+
function pushMacro(contentStart, start, selectors) {
|
|
1766
|
+
const closeIdx = scanMacroClose(input, contentStart, memo);
|
|
1767
|
+
if (closeIdx === -1) return false;
|
|
1768
|
+
const token = withSelectors({
|
|
1769
|
+
type: "macro",
|
|
1770
|
+
...parseMacroContent(input.slice(contentStart, closeIdx)),
|
|
1771
|
+
start,
|
|
1772
|
+
end: closeIdx + 1
|
|
1773
|
+
}, selectors);
|
|
1774
|
+
emit(token);
|
|
1775
|
+
consumeRawBody(token.name, token.isClose);
|
|
1706
1776
|
return true;
|
|
1707
1777
|
}
|
|
1708
1778
|
while (i < input.length) {
|
|
@@ -1726,14 +1796,12 @@ function tokenize(input, options = {}) {
|
|
|
1726
1796
|
}
|
|
1727
1797
|
if ((next === "{" || next === "}") && (k - i) % 2 === 1) {
|
|
1728
1798
|
flushText(k - 1);
|
|
1729
|
-
|
|
1799
|
+
emit({
|
|
1730
1800
|
type: "text",
|
|
1731
1801
|
value: next,
|
|
1732
1802
|
start: k - 1,
|
|
1733
1803
|
end: k + 1
|
|
1734
1804
|
});
|
|
1735
|
-
i = k + 1;
|
|
1736
|
-
textStart = i;
|
|
1737
1805
|
continue;
|
|
1738
1806
|
}
|
|
1739
1807
|
i = k;
|
|
@@ -1742,413 +1810,36 @@ function tokenize(input, options = {}) {
|
|
|
1742
1810
|
if (!textMode && input[i] === "[" && input[i + 1] === "[") {
|
|
1743
1811
|
flushText(i);
|
|
1744
1812
|
const start = i;
|
|
1745
|
-
i
|
|
1746
|
-
let className;
|
|
1747
|
-
let id;
|
|
1748
|
-
if (input[i] === "." || input[i] === "#") {
|
|
1749
|
-
const parsed = parseSelectors(input, i);
|
|
1750
|
-
className = parsed.className || void 0;
|
|
1751
|
-
id = parsed.id || void 0;
|
|
1752
|
-
i = parsed.endIdx;
|
|
1753
|
-
if (input[i] === " ") i++;
|
|
1754
|
-
}
|
|
1755
|
-
const innerStart = i;
|
|
1813
|
+
const { selectors, end: innerStart } = parseSelectors(input, i + 2);
|
|
1756
1814
|
const closeIdx = scanLinkClose(input, innerStart, memo);
|
|
1757
1815
|
if (closeIdx === -1) {
|
|
1758
1816
|
i = start + 2;
|
|
1759
|
-
textStart = start;
|
|
1760
1817
|
continue;
|
|
1761
1818
|
}
|
|
1762
|
-
|
|
1763
|
-
i = closeIdx + 2;
|
|
1764
|
-
const { display, target } = parseLink(inner);
|
|
1765
|
-
const linkToken = {
|
|
1819
|
+
emit(withSelectors({
|
|
1766
1820
|
type: "link",
|
|
1767
|
-
|
|
1768
|
-
target,
|
|
1821
|
+
...parseLink(input.slice(innerStart, closeIdx)),
|
|
1769
1822
|
start,
|
|
1770
|
-
end:
|
|
1771
|
-
};
|
|
1772
|
-
if (className) linkToken.className = className;
|
|
1773
|
-
if (id) linkToken.id = id;
|
|
1774
|
-
tokens.push(linkToken);
|
|
1775
|
-
textStart = i;
|
|
1823
|
+
end: closeIdx + 2
|
|
1824
|
+
}, selectors));
|
|
1776
1825
|
continue;
|
|
1777
1826
|
}
|
|
1778
1827
|
if (input[i] === "{") {
|
|
1779
1828
|
const start = i;
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (
|
|
1792
|
-
i = afterSelectors + 1;
|
|
1793
|
-
const nameStart = i;
|
|
1794
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
1795
|
-
const name = input.slice(nameStart, i);
|
|
1796
|
-
if (input[i] === "}") {
|
|
1797
|
-
i++;
|
|
1798
|
-
const token = {
|
|
1799
|
-
type: "variable",
|
|
1800
|
-
name,
|
|
1801
|
-
scope: "variable",
|
|
1802
|
-
start,
|
|
1803
|
-
end: i
|
|
1804
|
-
};
|
|
1805
|
-
if (className) token.className = className;
|
|
1806
|
-
if (id) token.id = id;
|
|
1807
|
-
tokens.push(token);
|
|
1808
|
-
textStart = i;
|
|
1809
|
-
continue;
|
|
1810
|
-
}
|
|
1811
|
-
const closeIdx$ = scanBalancedBrace(input, nameStart - 1, memo);
|
|
1812
|
-
if (closeIdx$ !== -1) {
|
|
1813
|
-
const expression = input.slice(afterSelectors, closeIdx$);
|
|
1814
|
-
i = closeIdx$ + 1;
|
|
1815
|
-
const token = {
|
|
1816
|
-
type: "expression",
|
|
1817
|
-
expression,
|
|
1818
|
-
start,
|
|
1819
|
-
end: i
|
|
1820
|
-
};
|
|
1821
|
-
if (className) token.className = className;
|
|
1822
|
-
if (id) token.id = id;
|
|
1823
|
-
tokens.push(token);
|
|
1824
|
-
textStart = i;
|
|
1825
|
-
continue;
|
|
1826
|
-
}
|
|
1827
|
-
i = start + 1;
|
|
1828
|
-
textStart = start;
|
|
1829
|
-
continue;
|
|
1830
|
-
}
|
|
1831
|
-
if (charAfter === "_") {
|
|
1832
|
-
i = afterSelectors + 1;
|
|
1833
|
-
const nameStart = i;
|
|
1834
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
1835
|
-
const name = input.slice(nameStart, i);
|
|
1836
|
-
if (input[i] === "}") {
|
|
1837
|
-
i++;
|
|
1838
|
-
const token = {
|
|
1839
|
-
type: "variable",
|
|
1840
|
-
name,
|
|
1841
|
-
scope: "temporary",
|
|
1842
|
-
start,
|
|
1843
|
-
end: i
|
|
1844
|
-
};
|
|
1845
|
-
if (className) token.className = className;
|
|
1846
|
-
if (id) token.id = id;
|
|
1847
|
-
tokens.push(token);
|
|
1848
|
-
textStart = i;
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
const closeIdx_ = scanBalancedBrace(input, nameStart - 1, memo);
|
|
1852
|
-
if (closeIdx_ !== -1) {
|
|
1853
|
-
const expression = input.slice(afterSelectors, closeIdx_);
|
|
1854
|
-
i = closeIdx_ + 1;
|
|
1855
|
-
const token = {
|
|
1856
|
-
type: "expression",
|
|
1857
|
-
expression,
|
|
1858
|
-
start,
|
|
1859
|
-
end: i
|
|
1860
|
-
};
|
|
1861
|
-
if (className) token.className = className;
|
|
1862
|
-
if (id) token.id = id;
|
|
1863
|
-
tokens.push(token);
|
|
1864
|
-
textStart = i;
|
|
1865
|
-
continue;
|
|
1866
|
-
}
|
|
1867
|
-
i = start + 1;
|
|
1868
|
-
textStart = start;
|
|
1869
|
-
continue;
|
|
1870
|
-
}
|
|
1871
|
-
if (charAfter === "@") {
|
|
1872
|
-
i = afterSelectors + 1;
|
|
1873
|
-
const nameStart = i;
|
|
1874
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
1875
|
-
const name = input.slice(nameStart, i);
|
|
1876
|
-
if (input[i] === "}") {
|
|
1877
|
-
i++;
|
|
1878
|
-
const token = {
|
|
1879
|
-
type: "variable",
|
|
1880
|
-
name,
|
|
1881
|
-
scope: "local",
|
|
1882
|
-
start,
|
|
1883
|
-
end: i
|
|
1884
|
-
};
|
|
1885
|
-
if (className) token.className = className;
|
|
1886
|
-
if (id) token.id = id;
|
|
1887
|
-
tokens.push(token);
|
|
1888
|
-
textStart = i;
|
|
1889
|
-
continue;
|
|
1890
|
-
}
|
|
1891
|
-
const closeIdx_at = scanBalancedBrace(input, nameStart - 1, memo);
|
|
1892
|
-
if (closeIdx_at !== -1) {
|
|
1893
|
-
const expression = input.slice(afterSelectors, closeIdx_at);
|
|
1894
|
-
i = closeIdx_at + 1;
|
|
1895
|
-
const token = {
|
|
1896
|
-
type: "expression",
|
|
1897
|
-
expression,
|
|
1898
|
-
start,
|
|
1899
|
-
end: i
|
|
1900
|
-
};
|
|
1901
|
-
if (className) token.className = className;
|
|
1902
|
-
if (id) token.id = id;
|
|
1903
|
-
tokens.push(token);
|
|
1904
|
-
textStart = i;
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
i = start + 1;
|
|
1908
|
-
textStart = start;
|
|
1909
|
-
continue;
|
|
1910
|
-
}
|
|
1911
|
-
if (charAfter === "%") {
|
|
1912
|
-
i = afterSelectors + 1;
|
|
1913
|
-
const nameStart = i;
|
|
1914
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
1915
|
-
const name = input.slice(nameStart, i);
|
|
1916
|
-
if (input[i] === "}") {
|
|
1917
|
-
i++;
|
|
1918
|
-
const token = {
|
|
1919
|
-
type: "variable",
|
|
1920
|
-
name,
|
|
1921
|
-
scope: "transient",
|
|
1922
|
-
start,
|
|
1923
|
-
end: i
|
|
1924
|
-
};
|
|
1925
|
-
if (className) token.className = className;
|
|
1926
|
-
if (id) token.id = id;
|
|
1927
|
-
tokens.push(token);
|
|
1928
|
-
textStart = i;
|
|
1929
|
-
continue;
|
|
1930
|
-
}
|
|
1931
|
-
const closeIdx_pct = scanBalancedBrace(input, nameStart - 1, memo);
|
|
1932
|
-
if (closeIdx_pct !== -1) {
|
|
1933
|
-
const expression = input.slice(afterSelectors, closeIdx_pct);
|
|
1934
|
-
i = closeIdx_pct + 1;
|
|
1935
|
-
const token = {
|
|
1936
|
-
type: "expression",
|
|
1937
|
-
expression,
|
|
1938
|
-
start,
|
|
1939
|
-
end: i
|
|
1940
|
-
};
|
|
1941
|
-
if (className) token.className = className;
|
|
1942
|
-
if (id) token.id = id;
|
|
1943
|
-
tokens.push(token);
|
|
1944
|
-
textStart = i;
|
|
1945
|
-
continue;
|
|
1946
|
-
}
|
|
1947
|
-
i = start + 1;
|
|
1948
|
-
textStart = start;
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
if (EXPRESSION_START.has(charAfter) && pushExpression(afterSelectors, start, false, className, id)) continue;
|
|
1952
|
-
if (charAfter !== void 0 && /[a-zA-Z]/.test(charAfter)) {
|
|
1953
|
-
const contentStart = afterSelectors;
|
|
1954
|
-
const closeIdx = scanMacroClose(input, contentStart, memo);
|
|
1955
|
-
if (closeIdx === -1) {
|
|
1956
|
-
i = start + 1;
|
|
1957
|
-
textStart = start;
|
|
1958
|
-
continue;
|
|
1959
|
-
}
|
|
1960
|
-
const content = input.slice(contentStart, closeIdx);
|
|
1961
|
-
i = closeIdx + 1;
|
|
1962
|
-
const { name, rawArgs, isClose } = parseMacroContent(content);
|
|
1963
|
-
const token = {
|
|
1964
|
-
type: "macro",
|
|
1965
|
-
name,
|
|
1966
|
-
rawArgs,
|
|
1967
|
-
isClose,
|
|
1968
|
-
start,
|
|
1969
|
-
end: i
|
|
1970
|
-
};
|
|
1971
|
-
if (className) token.className = className;
|
|
1972
|
-
if (id) token.id = id;
|
|
1973
|
-
tokens.push(token);
|
|
1974
|
-
textStart = i;
|
|
1975
|
-
consumeRawBody(name, isClose);
|
|
1976
|
-
continue;
|
|
1977
|
-
}
|
|
1978
|
-
i = start + 1;
|
|
1979
|
-
textStart = start;
|
|
1980
|
-
continue;
|
|
1981
|
-
}
|
|
1982
|
-
if (nextChar === "$") {
|
|
1983
|
-
flushText(i);
|
|
1984
|
-
i += 2;
|
|
1985
|
-
const nameStart = i;
|
|
1986
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
1987
|
-
const name = input.slice(nameStart, i);
|
|
1988
|
-
if (input[i] === "}") {
|
|
1989
|
-
i++;
|
|
1990
|
-
tokens.push({
|
|
1991
|
-
type: "variable",
|
|
1992
|
-
name,
|
|
1993
|
-
scope: "variable",
|
|
1994
|
-
start,
|
|
1995
|
-
end: i
|
|
1996
|
-
});
|
|
1997
|
-
textStart = i;
|
|
1998
|
-
continue;
|
|
1999
|
-
}
|
|
2000
|
-
const closeIdx = scanBalancedBrace(input, nameStart - 1, memo);
|
|
2001
|
-
if (closeIdx !== -1) {
|
|
2002
|
-
const expression = input.slice(start + 1, closeIdx);
|
|
2003
|
-
i = closeIdx + 1;
|
|
2004
|
-
tokens.push({
|
|
2005
|
-
type: "expression",
|
|
2006
|
-
expression,
|
|
2007
|
-
start,
|
|
2008
|
-
end: i
|
|
2009
|
-
});
|
|
2010
|
-
textStart = i;
|
|
2011
|
-
continue;
|
|
2012
|
-
}
|
|
2013
|
-
i = start + 1;
|
|
2014
|
-
textStart = start;
|
|
2015
|
-
continue;
|
|
2016
|
-
}
|
|
2017
|
-
if (nextChar === "_") {
|
|
2018
|
-
flushText(i);
|
|
2019
|
-
i += 2;
|
|
2020
|
-
const nameStart = i;
|
|
2021
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
2022
|
-
const name = input.slice(nameStart, i);
|
|
2023
|
-
if (input[i] === "}") {
|
|
2024
|
-
i++;
|
|
2025
|
-
tokens.push({
|
|
2026
|
-
type: "variable",
|
|
2027
|
-
name,
|
|
2028
|
-
scope: "temporary",
|
|
2029
|
-
start,
|
|
2030
|
-
end: i
|
|
2031
|
-
});
|
|
2032
|
-
textStart = i;
|
|
2033
|
-
continue;
|
|
2034
|
-
}
|
|
2035
|
-
const closeIdx = scanBalancedBrace(input, nameStart - 1, memo);
|
|
2036
|
-
if (closeIdx !== -1) {
|
|
2037
|
-
const expression = input.slice(start + 1, closeIdx);
|
|
2038
|
-
i = closeIdx + 1;
|
|
2039
|
-
tokens.push({
|
|
2040
|
-
type: "expression",
|
|
2041
|
-
expression,
|
|
2042
|
-
start,
|
|
2043
|
-
end: i
|
|
2044
|
-
});
|
|
2045
|
-
textStart = i;
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
2048
|
-
i = start + 1;
|
|
2049
|
-
textStart = start;
|
|
2050
|
-
continue;
|
|
1829
|
+
const { selectors, end: at } = parseSelectors(input, i + 1);
|
|
1830
|
+
const prefixed = at > i + 1;
|
|
1831
|
+
if (prefixed) flushText(start);
|
|
1832
|
+
const c = input[at];
|
|
1833
|
+
if (isSigil(c)) {
|
|
1834
|
+
flushText(start);
|
|
1835
|
+
if (pushVariable(at, start, selectors)) continue;
|
|
1836
|
+
} else if (EXPRESSION_START.has(c)) {
|
|
1837
|
+
if (pushExpression(at, start, selectors)) continue;
|
|
1838
|
+
} else if (c !== void 0 && (/[a-zA-Z]/.test(c) || c === "/" && !prefixed)) {
|
|
1839
|
+
flushText(start);
|
|
1840
|
+
if (pushMacro(at, start, selectors)) continue;
|
|
2051
1841
|
}
|
|
2052
|
-
|
|
2053
|
-
flushText(i);
|
|
2054
|
-
i += 2;
|
|
2055
|
-
const nameStart = i;
|
|
2056
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
2057
|
-
const name = input.slice(nameStart, i);
|
|
2058
|
-
if (input[i] === "}") {
|
|
2059
|
-
i++;
|
|
2060
|
-
tokens.push({
|
|
2061
|
-
type: "variable",
|
|
2062
|
-
name,
|
|
2063
|
-
scope: "local",
|
|
2064
|
-
start,
|
|
2065
|
-
end: i
|
|
2066
|
-
});
|
|
2067
|
-
textStart = i;
|
|
2068
|
-
continue;
|
|
2069
|
-
}
|
|
2070
|
-
const closeIdx = scanBalancedBrace(input, nameStart - 1, memo);
|
|
2071
|
-
if (closeIdx !== -1) {
|
|
2072
|
-
const expression = input.slice(start + 1, closeIdx);
|
|
2073
|
-
i = closeIdx + 1;
|
|
2074
|
-
tokens.push({
|
|
2075
|
-
type: "expression",
|
|
2076
|
-
expression,
|
|
2077
|
-
start,
|
|
2078
|
-
end: i
|
|
2079
|
-
});
|
|
2080
|
-
textStart = i;
|
|
2081
|
-
continue;
|
|
2082
|
-
}
|
|
2083
|
-
i = start + 1;
|
|
2084
|
-
textStart = start;
|
|
2085
|
-
continue;
|
|
2086
|
-
}
|
|
2087
|
-
if (nextChar === "%") {
|
|
2088
|
-
flushText(i);
|
|
2089
|
-
i += 2;
|
|
2090
|
-
const nameStart = i;
|
|
2091
|
-
while (i < input.length && /[\w.]/.test(input[i])) i++;
|
|
2092
|
-
const name = input.slice(nameStart, i);
|
|
2093
|
-
if (input[i] === "}") {
|
|
2094
|
-
i++;
|
|
2095
|
-
tokens.push({
|
|
2096
|
-
type: "variable",
|
|
2097
|
-
name,
|
|
2098
|
-
scope: "transient",
|
|
2099
|
-
start,
|
|
2100
|
-
end: i
|
|
2101
|
-
});
|
|
2102
|
-
textStart = i;
|
|
2103
|
-
continue;
|
|
2104
|
-
}
|
|
2105
|
-
const closeIdx = scanBalancedBrace(input, nameStart - 1, memo);
|
|
2106
|
-
if (closeIdx !== -1) {
|
|
2107
|
-
const expression = input.slice(start + 1, closeIdx);
|
|
2108
|
-
i = closeIdx + 1;
|
|
2109
|
-
tokens.push({
|
|
2110
|
-
type: "expression",
|
|
2111
|
-
expression,
|
|
2112
|
-
start,
|
|
2113
|
-
end: i
|
|
2114
|
-
});
|
|
2115
|
-
textStart = i;
|
|
2116
|
-
continue;
|
|
2117
|
-
}
|
|
2118
|
-
i = start + 1;
|
|
2119
|
-
textStart = start;
|
|
2120
|
-
continue;
|
|
2121
|
-
}
|
|
2122
|
-
if (EXPRESSION_START.has(nextChar)) {
|
|
2123
|
-
if (pushExpression(i + 1, start, true)) continue;
|
|
2124
|
-
i++;
|
|
2125
|
-
continue;
|
|
2126
|
-
}
|
|
2127
|
-
if (nextChar !== void 0 && (nextChar === "/" || /[a-zA-Z]/.test(nextChar))) {
|
|
2128
|
-
flushText(i);
|
|
2129
|
-
const contentStart = i + 1;
|
|
2130
|
-
const closeIdx = scanMacroClose(input, contentStart, memo);
|
|
2131
|
-
if (closeIdx === -1) {
|
|
2132
|
-
i = start + 1;
|
|
2133
|
-
textStart = start;
|
|
2134
|
-
continue;
|
|
2135
|
-
}
|
|
2136
|
-
const content = input.slice(contentStart, closeIdx);
|
|
2137
|
-
i = closeIdx + 1;
|
|
2138
|
-
const { name, rawArgs, isClose } = parseMacroContent(content);
|
|
2139
|
-
tokens.push({
|
|
2140
|
-
type: "macro",
|
|
2141
|
-
name,
|
|
2142
|
-
rawArgs,
|
|
2143
|
-
isClose,
|
|
2144
|
-
start,
|
|
2145
|
-
end: i
|
|
2146
|
-
});
|
|
2147
|
-
textStart = i;
|
|
2148
|
-
consumeRawBody(name, isClose);
|
|
2149
|
-
continue;
|
|
2150
|
-
}
|
|
2151
|
-
i++;
|
|
1842
|
+
i = start + 1;
|
|
2152
1843
|
continue;
|
|
2153
1844
|
}
|
|
2154
1845
|
if (!textMode && input[i] === "<") {
|
|
@@ -2166,11 +1857,10 @@ function tokenize(input, options = {}) {
|
|
|
2166
1857
|
j++;
|
|
2167
1858
|
flushText(start);
|
|
2168
1859
|
if (HTML_VOID_TAGS.has(tagLower)) {
|
|
2169
|
-
textStart = j;
|
|
2170
|
-
i = j;
|
|
1860
|
+
i = textStart = j;
|
|
2171
1861
|
continue;
|
|
2172
1862
|
}
|
|
2173
|
-
|
|
1863
|
+
emit({
|
|
2174
1864
|
type: "html",
|
|
2175
1865
|
tag,
|
|
2176
1866
|
attributes: {},
|
|
@@ -2179,8 +1869,6 @@ function tokenize(input, options = {}) {
|
|
|
2179
1869
|
start,
|
|
2180
1870
|
end: j
|
|
2181
1871
|
});
|
|
2182
|
-
textStart = j;
|
|
2183
|
-
i = j;
|
|
2184
1872
|
continue;
|
|
2185
1873
|
}
|
|
2186
1874
|
} else {
|
|
@@ -2194,7 +1882,7 @@ function tokenize(input, options = {}) {
|
|
|
2194
1882
|
if (input[j] === ">") {
|
|
2195
1883
|
j++;
|
|
2196
1884
|
flushText(start);
|
|
2197
|
-
|
|
1885
|
+
emit({
|
|
2198
1886
|
type: "html",
|
|
2199
1887
|
tag,
|
|
2200
1888
|
attributes: parseHtmlAttributes(input, attrsStart, memo).attributes,
|
|
@@ -2203,8 +1891,6 @@ function tokenize(input, options = {}) {
|
|
|
2203
1891
|
start,
|
|
2204
1892
|
end: j
|
|
2205
1893
|
});
|
|
2206
|
-
textStart = j;
|
|
2207
|
-
i = j;
|
|
2208
1894
|
continue;
|
|
2209
1895
|
}
|
|
2210
1896
|
}
|