binja 0.6.0 → 0.7.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/dist/cli.js +0 -105
- package/dist/debug/index.js +427 -384
- package/dist/debug/panel.d.ts +10 -8
- package/dist/debug/panel.d.ts.map +1 -1
- package/dist/index.js +969 -493
- package/dist/lexer/index.d.ts +0 -4
- package/dist/lexer/index.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/package.json +7 -19
- package/native/darwin-arm64/libbinja.dylib +0 -0
- package/native/darwin-x64/libbinja.dylib +0 -0
- package/native/linux-arm64/libbinja.so +0 -0
- package/native/linux-x64/libbinja.so +0 -0
package/dist/index.js
CHANGED
|
@@ -56,103 +56,6 @@ var KEYWORDS = {
|
|
|
56
56
|
in: "NAME" /* NAME */
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
// src/lexer/hybrid.ts
|
|
60
|
-
var _tokenizeBatchFn = null;
|
|
61
|
-
function checkNative() {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
var NATIVE_TO_TS = {
|
|
65
|
-
0: "TEXT" /* TEXT */,
|
|
66
|
-
1: "VARIABLE_START" /* VARIABLE_START */,
|
|
67
|
-
2: "VARIABLE_END" /* VARIABLE_END */,
|
|
68
|
-
3: "BLOCK_START" /* BLOCK_START */,
|
|
69
|
-
4: "BLOCK_END" /* BLOCK_END */,
|
|
70
|
-
5: "COMMENT_START" /* COMMENT_START */,
|
|
71
|
-
6: "COMMENT_END" /* COMMENT_END */,
|
|
72
|
-
7: "NAME" /* NAME */,
|
|
73
|
-
8: "STRING" /* STRING */,
|
|
74
|
-
9: "NUMBER" /* NUMBER */,
|
|
75
|
-
10: "NAME" /* NAME */,
|
|
76
|
-
11: "DOT" /* DOT */,
|
|
77
|
-
12: "COMMA" /* COMMA */,
|
|
78
|
-
13: "PIPE" /* PIPE */,
|
|
79
|
-
14: "COLON" /* COLON */,
|
|
80
|
-
15: "LPAREN" /* LPAREN */,
|
|
81
|
-
16: "RPAREN" /* RPAREN */,
|
|
82
|
-
17: "LBRACKET" /* LBRACKET */,
|
|
83
|
-
18: "RBRACKET" /* RBRACKET */,
|
|
84
|
-
19: "LBRACE" /* LBRACE */,
|
|
85
|
-
20: "RBRACE" /* RBRACE */,
|
|
86
|
-
21: "ASSIGN" /* ASSIGN */,
|
|
87
|
-
22: "EOF" /* EOF */
|
|
88
|
-
};
|
|
89
|
-
var OPERATOR_TO_TYPE = {
|
|
90
|
-
"==": "EQ" /* EQ */,
|
|
91
|
-
"!=": "NE" /* NE */,
|
|
92
|
-
"<": "LT" /* LT */,
|
|
93
|
-
">": "GT" /* GT */,
|
|
94
|
-
"<=": "LE" /* LE */,
|
|
95
|
-
">=": "GE" /* GE */,
|
|
96
|
-
"+": "ADD" /* ADD */,
|
|
97
|
-
"-": "SUB" /* SUB */,
|
|
98
|
-
"*": "MUL" /* MUL */,
|
|
99
|
-
"/": "DIV" /* DIV */,
|
|
100
|
-
"%": "MOD" /* MOD */,
|
|
101
|
-
"~": "TILDE" /* TILDE */
|
|
102
|
-
};
|
|
103
|
-
var KEYWORD_TO_TYPE = {
|
|
104
|
-
and: "AND" /* AND */,
|
|
105
|
-
or: "OR" /* OR */,
|
|
106
|
-
not: "NOT" /* NOT */
|
|
107
|
-
};
|
|
108
|
-
function isNativeAccelerated() {
|
|
109
|
-
return checkNative();
|
|
110
|
-
}
|
|
111
|
-
function tokenizeNative(source) {
|
|
112
|
-
if (!checkNative() || !_tokenizeBatchFn)
|
|
113
|
-
return null;
|
|
114
|
-
if (source.length === 0) {
|
|
115
|
-
return [{ type: "EOF" /* EOF */, value: "", line: 1, column: 1 }];
|
|
116
|
-
}
|
|
117
|
-
const rawTokens = _tokenizeBatchFn(source);
|
|
118
|
-
const lineStarts = [0];
|
|
119
|
-
for (let i = 0;i < source.length; i++) {
|
|
120
|
-
if (source[i] === `
|
|
121
|
-
`)
|
|
122
|
-
lineStarts.push(i + 1);
|
|
123
|
-
}
|
|
124
|
-
const tokens = new Array(rawTokens.length);
|
|
125
|
-
for (let i = 0;i < rawTokens.length; i++) {
|
|
126
|
-
const [nativeType, start, end] = rawTokens[i];
|
|
127
|
-
let value = source.slice(start, end);
|
|
128
|
-
let lo = 0, hi = lineStarts.length - 1;
|
|
129
|
-
while (lo < hi) {
|
|
130
|
-
const mid = lo + hi + 1 >> 1;
|
|
131
|
-
if (lineStarts[mid] <= start)
|
|
132
|
-
lo = mid;
|
|
133
|
-
else
|
|
134
|
-
hi = mid - 1;
|
|
135
|
-
}
|
|
136
|
-
const line = lo + 1;
|
|
137
|
-
const column = start - lineStarts[lo] + 1;
|
|
138
|
-
let type = NATIVE_TO_TS[nativeType] ?? "NAME" /* NAME */;
|
|
139
|
-
if (nativeType === 10 && OPERATOR_TO_TYPE[value]) {
|
|
140
|
-
type = OPERATOR_TO_TYPE[value];
|
|
141
|
-
} else if (type === "NAME" /* NAME */ && KEYWORD_TO_TYPE[value]) {
|
|
142
|
-
type = KEYWORD_TO_TYPE[value];
|
|
143
|
-
}
|
|
144
|
-
if (type === "STRING" /* STRING */ && value.length >= 2) {
|
|
145
|
-
const first = value[0];
|
|
146
|
-
const last = value[value.length - 1];
|
|
147
|
-
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
148
|
-
value = value.slice(1, -1);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
tokens[i] = { type, value, line, column };
|
|
152
|
-
}
|
|
153
|
-
return tokens;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
59
|
// src/errors/index.ts
|
|
157
60
|
var colors = {
|
|
158
61
|
red: "\x1B[31m",
|
|
@@ -315,7 +218,6 @@ class Lexer {
|
|
|
315
218
|
blockEnd;
|
|
316
219
|
commentStart;
|
|
317
220
|
commentEnd;
|
|
318
|
-
useNative;
|
|
319
221
|
constructor(source, options = {}) {
|
|
320
222
|
this.state = {
|
|
321
223
|
source,
|
|
@@ -330,15 +232,8 @@ class Lexer {
|
|
|
330
232
|
this.blockEnd = options.blockEnd ?? "%}";
|
|
331
233
|
this.commentStart = options.commentStart ?? "{#";
|
|
332
234
|
this.commentEnd = options.commentEnd ?? "#}";
|
|
333
|
-
const hasCustomDelimiters = options.variableStart !== undefined || options.variableEnd !== undefined || options.blockStart !== undefined || options.blockEnd !== undefined || options.commentStart !== undefined || options.commentEnd !== undefined;
|
|
334
|
-
this.useNative = !hasCustomDelimiters && isNativeAccelerated();
|
|
335
235
|
}
|
|
336
236
|
tokenize() {
|
|
337
|
-
if (this.useNative) {
|
|
338
|
-
const nativeTokens = tokenizeNative(this.state.source);
|
|
339
|
-
if (nativeTokens)
|
|
340
|
-
return nativeTokens;
|
|
341
|
-
}
|
|
342
237
|
while (!this.isAtEnd()) {
|
|
343
238
|
this.scanToken();
|
|
344
239
|
}
|
|
@@ -3863,18 +3758,556 @@ class Runtime {
|
|
|
3863
3758
|
return;
|
|
3864
3759
|
return obj[index];
|
|
3865
3760
|
}
|
|
3761
|
+
static TITLE_REGEX = /\b\w/g;
|
|
3762
|
+
static STRIPTAGS_REGEX = /<[^>]*>/g;
|
|
3763
|
+
static SLUGIFY_REGEX1 = /[^\w\s-]/g;
|
|
3764
|
+
static SLUGIFY_REGEX2 = /[\s_-]+/g;
|
|
3765
|
+
static SLUGIFY_REGEX3 = /^-+|-+$/g;
|
|
3766
|
+
static WHITESPACE_REGEX = /\s+/;
|
|
3767
|
+
static URLIZE_REGEX = /(https?:\/\/[^\s]+)/g;
|
|
3768
|
+
static NEWLINE_REGEX = /\n/g;
|
|
3769
|
+
static DOUBLE_NEWLINE_REGEX = /\n\n+/;
|
|
3866
3770
|
evalFilter(node, ctx) {
|
|
3867
3771
|
const value = this.eval(node.node, ctx);
|
|
3772
|
+
const filterName = node.filter;
|
|
3773
|
+
const argsLen = node.args.length;
|
|
3774
|
+
if (argsLen === 0) {
|
|
3775
|
+
switch (filterName) {
|
|
3776
|
+
case "upper":
|
|
3777
|
+
return String(value ?? "").toUpperCase();
|
|
3778
|
+
case "lower":
|
|
3779
|
+
return String(value ?? "").toLowerCase();
|
|
3780
|
+
case "trim":
|
|
3781
|
+
return String(value ?? "").trim();
|
|
3782
|
+
case "capitalize": {
|
|
3783
|
+
const s = String(value ?? "");
|
|
3784
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
3785
|
+
}
|
|
3786
|
+
case "capfirst": {
|
|
3787
|
+
const s = String(value ?? "");
|
|
3788
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3789
|
+
}
|
|
3790
|
+
case "title":
|
|
3791
|
+
return String(value ?? "").replace(Runtime.TITLE_REGEX, (c2) => c2.toUpperCase());
|
|
3792
|
+
case "striptags":
|
|
3793
|
+
return String(value ?? "").replace(Runtime.STRIPTAGS_REGEX, "");
|
|
3794
|
+
case "slugify":
|
|
3795
|
+
return String(value ?? "").toLowerCase().replace(Runtime.SLUGIFY_REGEX1, "").replace(Runtime.SLUGIFY_REGEX2, "-").replace(Runtime.SLUGIFY_REGEX3, "");
|
|
3796
|
+
case "wordcount": {
|
|
3797
|
+
const str = String(value ?? "").trim();
|
|
3798
|
+
return str ? str.split(Runtime.WHITESPACE_REGEX).length : 0;
|
|
3799
|
+
}
|
|
3800
|
+
case "escapejs":
|
|
3801
|
+
return JSON.stringify(String(value ?? "")).slice(1, -1);
|
|
3802
|
+
case "linebreaksbr": {
|
|
3803
|
+
const html = String(value ?? "").replace(Runtime.NEWLINE_REGEX, "<br>");
|
|
3804
|
+
const safe2 = new String(html);
|
|
3805
|
+
safe2.__safe__ = true;
|
|
3806
|
+
return safe2;
|
|
3807
|
+
}
|
|
3808
|
+
case "linebreaks": {
|
|
3809
|
+
const paragraphs = String(value ?? "").split(Runtime.DOUBLE_NEWLINE_REGEX);
|
|
3810
|
+
let html = "";
|
|
3811
|
+
for (let i = 0;i < paragraphs.length; i++) {
|
|
3812
|
+
if (i > 0)
|
|
3813
|
+
html += `
|
|
3814
|
+
`;
|
|
3815
|
+
html += `<p>${paragraphs[i].replace(Runtime.NEWLINE_REGEX, "<br>")}</p>`;
|
|
3816
|
+
}
|
|
3817
|
+
const safe2 = new String(html);
|
|
3818
|
+
safe2.__safe__ = true;
|
|
3819
|
+
return safe2;
|
|
3820
|
+
}
|
|
3821
|
+
case "urlencode":
|
|
3822
|
+
return encodeURIComponent(String(value ?? ""));
|
|
3823
|
+
case "safe": {
|
|
3824
|
+
const safeVal = new String(value ?? "");
|
|
3825
|
+
safeVal.__safe__ = true;
|
|
3826
|
+
return safeVal;
|
|
3827
|
+
}
|
|
3828
|
+
case "escape":
|
|
3829
|
+
case "e": {
|
|
3830
|
+
if (value?.__safe__)
|
|
3831
|
+
return value;
|
|
3832
|
+
const escaped = new String(Bun.escapeHTML(String(value ?? "")));
|
|
3833
|
+
escaped.__safe__ = true;
|
|
3834
|
+
return escaped;
|
|
3835
|
+
}
|
|
3836
|
+
case "forceescape": {
|
|
3837
|
+
const escaped = new String(Bun.escapeHTML(String(value ?? "")));
|
|
3838
|
+
escaped.__safe__ = true;
|
|
3839
|
+
return escaped;
|
|
3840
|
+
}
|
|
3841
|
+
case "int":
|
|
3842
|
+
return parseInt(String(value), 10) || 0;
|
|
3843
|
+
case "float":
|
|
3844
|
+
return parseFloat(String(value)) || 0;
|
|
3845
|
+
case "abs":
|
|
3846
|
+
return Math.abs(Number(value) || 0);
|
|
3847
|
+
case "filesizeformat": {
|
|
3848
|
+
const bytes = Number(value) || 0;
|
|
3849
|
+
const units = ["bytes", "KB", "MB", "GB", "TB", "PB"];
|
|
3850
|
+
let i = 0, size = bytes;
|
|
3851
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
3852
|
+
size /= 1024;
|
|
3853
|
+
i++;
|
|
3854
|
+
}
|
|
3855
|
+
return `${size.toFixed(1)} ${units[i]}`;
|
|
3856
|
+
}
|
|
3857
|
+
case "length":
|
|
3858
|
+
if (value == null)
|
|
3859
|
+
return 0;
|
|
3860
|
+
if (typeof value === "string" || Array.isArray(value))
|
|
3861
|
+
return value.length;
|
|
3862
|
+
if (typeof value === "object") {
|
|
3863
|
+
let count = 0;
|
|
3864
|
+
for (const _ in value)
|
|
3865
|
+
count++;
|
|
3866
|
+
return count;
|
|
3867
|
+
}
|
|
3868
|
+
return 0;
|
|
3869
|
+
case "first":
|
|
3870
|
+
if (Array.isArray(value))
|
|
3871
|
+
return value[0];
|
|
3872
|
+
if (typeof value === "string")
|
|
3873
|
+
return value[0];
|
|
3874
|
+
return value;
|
|
3875
|
+
case "last":
|
|
3876
|
+
if (Array.isArray(value))
|
|
3877
|
+
return value[value.length - 1];
|
|
3878
|
+
if (typeof value === "string")
|
|
3879
|
+
return value[value.length - 1];
|
|
3880
|
+
return value;
|
|
3881
|
+
case "reverse":
|
|
3882
|
+
if (Array.isArray(value))
|
|
3883
|
+
return [...value].reverse();
|
|
3884
|
+
if (typeof value === "string")
|
|
3885
|
+
return value.split("").reverse().join("");
|
|
3886
|
+
return value;
|
|
3887
|
+
case "sort":
|
|
3888
|
+
if (Array.isArray(value))
|
|
3889
|
+
return [...value].sort();
|
|
3890
|
+
return value;
|
|
3891
|
+
case "unique":
|
|
3892
|
+
if (Array.isArray(value))
|
|
3893
|
+
return [...new Set(value)];
|
|
3894
|
+
return value;
|
|
3895
|
+
case "list":
|
|
3896
|
+
case "make_list":
|
|
3897
|
+
if (Array.isArray(value))
|
|
3898
|
+
return value;
|
|
3899
|
+
if (typeof value === "string")
|
|
3900
|
+
return value.split("");
|
|
3901
|
+
if (value && typeof value[Symbol.iterator] === "function")
|
|
3902
|
+
return [...value];
|
|
3903
|
+
if (typeof value === "object" && value !== null)
|
|
3904
|
+
return Object.values(value);
|
|
3905
|
+
return [value];
|
|
3906
|
+
case "random":
|
|
3907
|
+
if (Array.isArray(value))
|
|
3908
|
+
return value[Math.floor(Math.random() * value.length)];
|
|
3909
|
+
return value;
|
|
3910
|
+
case "items":
|
|
3911
|
+
if (value == null || typeof value !== "object")
|
|
3912
|
+
return [];
|
|
3913
|
+
return Object.entries(value);
|
|
3914
|
+
case "string":
|
|
3915
|
+
return String(value ?? "");
|
|
3916
|
+
case "json":
|
|
3917
|
+
case "tojson": {
|
|
3918
|
+
try {
|
|
3919
|
+
const jsonStr = new String(JSON.stringify(value));
|
|
3920
|
+
jsonStr.__safe__ = true;
|
|
3921
|
+
return jsonStr;
|
|
3922
|
+
} catch {
|
|
3923
|
+
return "";
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
case "pprint": {
|
|
3927
|
+
try {
|
|
3928
|
+
const result = new String(JSON.stringify(value, null, 2));
|
|
3929
|
+
result.__safe__ = true;
|
|
3930
|
+
return result;
|
|
3931
|
+
} catch {
|
|
3932
|
+
return String(value);
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
case "urlize": {
|
|
3936
|
+
const html = String(value ?? "").replace(Runtime.URLIZE_REGEX, '<a href="$1">$1</a>');
|
|
3937
|
+
const safe2 = new String(html);
|
|
3938
|
+
safe2.__safe__ = true;
|
|
3939
|
+
return safe2;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
if (argsLen === 1) {
|
|
3944
|
+
const arg = this.eval(node.args[0], ctx);
|
|
3945
|
+
switch (filterName) {
|
|
3946
|
+
case "default":
|
|
3947
|
+
case "d":
|
|
3948
|
+
if (value === undefined || value === null || value === "" || value === false) {
|
|
3949
|
+
return arg;
|
|
3950
|
+
}
|
|
3951
|
+
return value;
|
|
3952
|
+
case "default_if_none":
|
|
3953
|
+
return value === null || value === undefined ? arg : value;
|
|
3954
|
+
case "truncatechars": {
|
|
3955
|
+
const str = String(value ?? "");
|
|
3956
|
+
const len = Number(arg) || 30;
|
|
3957
|
+
if (str.length <= len)
|
|
3958
|
+
return str;
|
|
3959
|
+
return str.slice(0, len - 3) + "...";
|
|
3960
|
+
}
|
|
3961
|
+
case "truncatewords": {
|
|
3962
|
+
const words = String(value ?? "").split(Runtime.WHITESPACE_REGEX);
|
|
3963
|
+
const count = Number(arg) || 15;
|
|
3964
|
+
if (words.length <= count)
|
|
3965
|
+
return value;
|
|
3966
|
+
return words.slice(0, count).join(" ") + "...";
|
|
3967
|
+
}
|
|
3968
|
+
case "center": {
|
|
3969
|
+
const str = String(value ?? "");
|
|
3970
|
+
const width = Number(arg) || 80;
|
|
3971
|
+
const padding = Math.max(0, width - str.length);
|
|
3972
|
+
const left = Math.floor(padding / 2);
|
|
3973
|
+
return " ".repeat(left) + str + " ".repeat(padding - left);
|
|
3974
|
+
}
|
|
3975
|
+
case "ljust":
|
|
3976
|
+
return String(value ?? "").padEnd(Number(arg) || 80);
|
|
3977
|
+
case "rjust":
|
|
3978
|
+
return String(value ?? "").padStart(Number(arg) || 80);
|
|
3979
|
+
case "cut":
|
|
3980
|
+
return String(value ?? "").split(String(arg)).join("");
|
|
3981
|
+
case "add": {
|
|
3982
|
+
const numVal = Number(value);
|
|
3983
|
+
const numArg = Number(arg);
|
|
3984
|
+
if (!isNaN(numVal) && !isNaN(numArg))
|
|
3985
|
+
return numVal + numArg;
|
|
3986
|
+
return String(value) + String(arg);
|
|
3987
|
+
}
|
|
3988
|
+
case "divisibleby":
|
|
3989
|
+
return Number(value) % Number(arg) === 0;
|
|
3990
|
+
case "round":
|
|
3991
|
+
return Number(Number(value).toFixed(Number(arg) || 0));
|
|
3992
|
+
case "floatformat": {
|
|
3993
|
+
const num = parseFloat(String(value));
|
|
3994
|
+
if (isNaN(num))
|
|
3995
|
+
return "";
|
|
3996
|
+
const decimals = Number(arg);
|
|
3997
|
+
if (decimals === -1) {
|
|
3998
|
+
const formatted = num.toFixed(1);
|
|
3999
|
+
return formatted.endsWith(".0") ? Math.round(num).toString() : formatted;
|
|
4000
|
+
}
|
|
4001
|
+
return num.toFixed(Math.abs(decimals));
|
|
4002
|
+
}
|
|
4003
|
+
case "get_digit": {
|
|
4004
|
+
const num = parseInt(String(value), 10);
|
|
4005
|
+
if (isNaN(num))
|
|
4006
|
+
return value;
|
|
4007
|
+
const str = String(Math.abs(num));
|
|
4008
|
+
const pos = Number(arg) || 1;
|
|
4009
|
+
if (pos < 1 || pos > str.length)
|
|
4010
|
+
return value;
|
|
4011
|
+
return parseInt(str[str.length - pos], 10);
|
|
4012
|
+
}
|
|
4013
|
+
case "join":
|
|
4014
|
+
if (Array.isArray(value))
|
|
4015
|
+
return value.join(String(arg ?? ""));
|
|
4016
|
+
return String(value);
|
|
4017
|
+
case "slice": {
|
|
4018
|
+
if (!value)
|
|
4019
|
+
return value;
|
|
4020
|
+
const [startStr, endStr] = String(arg).split(":");
|
|
4021
|
+
const start = startStr ? parseInt(startStr, 10) : 0;
|
|
4022
|
+
const end = endStr ? parseInt(endStr, 10) : undefined;
|
|
4023
|
+
if (Array.isArray(value) || typeof value === "string")
|
|
4024
|
+
return value.slice(start, end);
|
|
4025
|
+
return value;
|
|
4026
|
+
}
|
|
4027
|
+
case "batch": {
|
|
4028
|
+
if (!Array.isArray(value))
|
|
4029
|
+
return [[value]];
|
|
4030
|
+
const size = Number(arg) || 1;
|
|
4031
|
+
const result = [];
|
|
4032
|
+
for (let i = 0;i < value.length; i += size) {
|
|
4033
|
+
result.push(value.slice(i, i + size));
|
|
4034
|
+
}
|
|
4035
|
+
return result;
|
|
4036
|
+
}
|
|
4037
|
+
case "columns": {
|
|
4038
|
+
if (!Array.isArray(value))
|
|
4039
|
+
return [[value]];
|
|
4040
|
+
const cols = Number(arg) || 2;
|
|
4041
|
+
const result = [];
|
|
4042
|
+
for (let i = 0;i < value.length; i += cols) {
|
|
4043
|
+
result.push(value.slice(i, i + cols));
|
|
4044
|
+
}
|
|
4045
|
+
return result;
|
|
4046
|
+
}
|
|
4047
|
+
case "length_is":
|
|
4048
|
+
return (value == null ? 0 : value.length ?? Object.keys(value).length ?? 0) === Number(arg);
|
|
4049
|
+
case "attr":
|
|
4050
|
+
return value == null ? undefined : value[arg];
|
|
4051
|
+
case "dictsort":
|
|
4052
|
+
if (!Array.isArray(value))
|
|
4053
|
+
return value;
|
|
4054
|
+
return [...value].sort((a, b) => {
|
|
4055
|
+
const aVal = arg ? a[arg] : a;
|
|
4056
|
+
const bVal = arg ? b[arg] : b;
|
|
4057
|
+
return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
4058
|
+
});
|
|
4059
|
+
case "dictsortreversed":
|
|
4060
|
+
if (!Array.isArray(value))
|
|
4061
|
+
return value;
|
|
4062
|
+
return [...value].sort((a, b) => {
|
|
4063
|
+
const aVal = arg ? a[arg] : a;
|
|
4064
|
+
const bVal = arg ? b[arg] : b;
|
|
4065
|
+
return aVal > bVal ? -1 : aVal < bVal ? 1 : 0;
|
|
4066
|
+
});
|
|
4067
|
+
case "map":
|
|
4068
|
+
if (!Array.isArray(value))
|
|
4069
|
+
return [];
|
|
4070
|
+
if (typeof arg === "string")
|
|
4071
|
+
return value.map((item) => item?.[arg]);
|
|
4072
|
+
return value;
|
|
4073
|
+
case "select":
|
|
4074
|
+
if (!Array.isArray(value))
|
|
4075
|
+
return [];
|
|
4076
|
+
if (arg === undefined)
|
|
4077
|
+
return value.filter((item) => !!item);
|
|
4078
|
+
return value.filter((item) => !!item?.[arg]);
|
|
4079
|
+
case "reject":
|
|
4080
|
+
if (!Array.isArray(value))
|
|
4081
|
+
return [];
|
|
4082
|
+
if (arg === undefined)
|
|
4083
|
+
return value.filter((item) => !item);
|
|
4084
|
+
return value.filter((item) => !item?.[arg]);
|
|
4085
|
+
case "groupby": {
|
|
4086
|
+
if (!Array.isArray(value))
|
|
4087
|
+
return [];
|
|
4088
|
+
const groups = {};
|
|
4089
|
+
for (const item of value) {
|
|
4090
|
+
const key = String(arg ? item[arg] : item);
|
|
4091
|
+
if (!(key in groups))
|
|
4092
|
+
groups[key] = [];
|
|
4093
|
+
groups[key].push(item);
|
|
4094
|
+
}
|
|
4095
|
+
const result = [];
|
|
4096
|
+
for (const key in groups) {
|
|
4097
|
+
result.push({ grouper: key, list: groups[key] });
|
|
4098
|
+
}
|
|
4099
|
+
return result;
|
|
4100
|
+
}
|
|
4101
|
+
case "date":
|
|
4102
|
+
case "time": {
|
|
4103
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
4104
|
+
if (isNaN(d.getTime()))
|
|
4105
|
+
return "";
|
|
4106
|
+
return this.formatDate(d, String(arg || (filterName === "time" ? "H:i" : "N j, Y")));
|
|
4107
|
+
}
|
|
4108
|
+
case "pluralize": {
|
|
4109
|
+
const argStr = String(arg ?? "s");
|
|
4110
|
+
const [singular, plural] = argStr.includes(",") ? argStr.split(",") : ["", argStr];
|
|
4111
|
+
return Number(value) === 1 ? singular : plural;
|
|
4112
|
+
}
|
|
4113
|
+
case "yesno": {
|
|
4114
|
+
const [yes, no, maybe] = String(arg ?? "yes,no,maybe").split(",");
|
|
4115
|
+
if (value === true)
|
|
4116
|
+
return yes;
|
|
4117
|
+
if (value === false)
|
|
4118
|
+
return no;
|
|
4119
|
+
return maybe ?? no;
|
|
4120
|
+
}
|
|
4121
|
+
case "json":
|
|
4122
|
+
case "tojson": {
|
|
4123
|
+
try {
|
|
4124
|
+
const jsonStr = new String(JSON.stringify(value, null, arg));
|
|
4125
|
+
jsonStr.__safe__ = true;
|
|
4126
|
+
return jsonStr;
|
|
4127
|
+
} catch {
|
|
4128
|
+
return "";
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
case "urlizetrunc": {
|
|
4132
|
+
const maxLen = Number(arg) || 15;
|
|
4133
|
+
const html = String(value ?? "").replace(Runtime.URLIZE_REGEX, (url) => {
|
|
4134
|
+
const displayUrl = url.length > maxLen ? url.slice(0, maxLen) + "..." : url;
|
|
4135
|
+
return `<a href="${url}">${displayUrl}</a>`;
|
|
4136
|
+
});
|
|
4137
|
+
const safe2 = new String(html);
|
|
4138
|
+
safe2.__safe__ = true;
|
|
4139
|
+
return safe2;
|
|
4140
|
+
}
|
|
4141
|
+
case "indent": {
|
|
4142
|
+
const str = String(value ?? "");
|
|
4143
|
+
const indentStr = typeof arg === "string" ? arg : " ".repeat(Number(arg) || 4);
|
|
4144
|
+
const lines = str.split(`
|
|
4145
|
+
`);
|
|
4146
|
+
let result = lines[0];
|
|
4147
|
+
for (let i = 1;i < lines.length; i++) {
|
|
4148
|
+
result += `
|
|
4149
|
+
` + (lines[i].trim() === "" ? lines[i] : indentStr + lines[i]);
|
|
4150
|
+
}
|
|
4151
|
+
return result;
|
|
4152
|
+
}
|
|
4153
|
+
case "stringformat": {
|
|
4154
|
+
const fmt = String(arg);
|
|
4155
|
+
const val = value;
|
|
4156
|
+
if (fmt === "s")
|
|
4157
|
+
return String(val);
|
|
4158
|
+
if (fmt === "d" || fmt === "i")
|
|
4159
|
+
return String(parseInt(String(val), 10) || 0);
|
|
4160
|
+
if (fmt === "f")
|
|
4161
|
+
return String(parseFloat(String(val)) || 0);
|
|
4162
|
+
if (fmt === "x")
|
|
4163
|
+
return (parseInt(String(val), 10) || 0).toString(16);
|
|
4164
|
+
if (fmt === "X")
|
|
4165
|
+
return (parseInt(String(val), 10) || 0).toString(16).toUpperCase();
|
|
4166
|
+
if (fmt === "o")
|
|
4167
|
+
return (parseInt(String(val), 10) || 0).toString(8);
|
|
4168
|
+
if (fmt === "b")
|
|
4169
|
+
return (parseInt(String(val), 10) || 0).toString(2);
|
|
4170
|
+
if (fmt === "e")
|
|
4171
|
+
return (parseFloat(String(val)) || 0).toExponential();
|
|
4172
|
+
const precMatch = fmt.match(/^\.?(\d+)f$/);
|
|
4173
|
+
if (precMatch)
|
|
4174
|
+
return (parseFloat(String(val)) || 0).toFixed(parseInt(precMatch[1], 10));
|
|
4175
|
+
const widthMatch = fmt.match(/^(0?)(\d+)([sd])$/);
|
|
4176
|
+
if (widthMatch) {
|
|
4177
|
+
const [, zero, widthStr, type] = widthMatch;
|
|
4178
|
+
const width = parseInt(widthStr, 10);
|
|
4179
|
+
const strVal = type === "d" ? String(parseInt(String(val), 10) || 0) : String(val);
|
|
4180
|
+
return strVal.padStart(width, zero ? "0" : " ");
|
|
4181
|
+
}
|
|
4182
|
+
return String(val);
|
|
4183
|
+
}
|
|
4184
|
+
case "json_script": {
|
|
4185
|
+
const jsonStr = JSON.stringify(value).replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026");
|
|
4186
|
+
const id = arg ? ` id="${String(arg)}"` : "";
|
|
4187
|
+
const html = `<script${id} type="application/json">${jsonStr}</script>`;
|
|
4188
|
+
const safe2 = new String(html);
|
|
4189
|
+
safe2.__safe__ = true;
|
|
4190
|
+
return safe2;
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
if (argsLen === 2) {
|
|
4195
|
+
const arg1 = this.eval(node.args[0], ctx);
|
|
4196
|
+
const arg2 = this.eval(node.args[1], ctx);
|
|
4197
|
+
switch (filterName) {
|
|
4198
|
+
case "replace": {
|
|
4199
|
+
const str = String(value ?? "");
|
|
4200
|
+
return str.replaceAll(String(arg1), String(arg2));
|
|
4201
|
+
}
|
|
4202
|
+
case "batch": {
|
|
4203
|
+
if (!Array.isArray(value))
|
|
4204
|
+
return [[value]];
|
|
4205
|
+
const size = Number(arg1) || 1;
|
|
4206
|
+
const fillWith = arg2;
|
|
4207
|
+
const result = [];
|
|
4208
|
+
for (let i = 0;i < value.length; i += size) {
|
|
4209
|
+
const batch2 = value.slice(i, i + size);
|
|
4210
|
+
while (fillWith !== null && batch2.length < size)
|
|
4211
|
+
batch2.push(fillWith);
|
|
4212
|
+
result.push(batch2);
|
|
4213
|
+
}
|
|
4214
|
+
return result;
|
|
4215
|
+
}
|
|
4216
|
+
case "sum": {
|
|
4217
|
+
if (!Array.isArray(value))
|
|
4218
|
+
return Number(arg2) || 0;
|
|
4219
|
+
let total = Number(arg2) || 0;
|
|
4220
|
+
for (let i = 0;i < value.length; i++) {
|
|
4221
|
+
total += Number(arg1 ? value[i][arg1] : value[i]) || 0;
|
|
4222
|
+
}
|
|
4223
|
+
return total;
|
|
4224
|
+
}
|
|
4225
|
+
case "max": {
|
|
4226
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
4227
|
+
return arg2;
|
|
4228
|
+
if (arg1) {
|
|
4229
|
+
let maxItem = value[0];
|
|
4230
|
+
for (let i = 1;i < value.length; i++) {
|
|
4231
|
+
if (value[i][arg1] > maxItem[arg1])
|
|
4232
|
+
maxItem = value[i];
|
|
4233
|
+
}
|
|
4234
|
+
return maxItem;
|
|
4235
|
+
}
|
|
4236
|
+
let maxVal = value[0];
|
|
4237
|
+
for (let i = 1;i < value.length; i++) {
|
|
4238
|
+
if (value[i] > maxVal)
|
|
4239
|
+
maxVal = value[i];
|
|
4240
|
+
}
|
|
4241
|
+
return maxVal;
|
|
4242
|
+
}
|
|
4243
|
+
case "min": {
|
|
4244
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
4245
|
+
return arg2;
|
|
4246
|
+
if (arg1) {
|
|
4247
|
+
let minItem = value[0];
|
|
4248
|
+
for (let i = 1;i < value.length; i++) {
|
|
4249
|
+
if (value[i][arg1] < minItem[arg1])
|
|
4250
|
+
minItem = value[i];
|
|
4251
|
+
}
|
|
4252
|
+
return minItem;
|
|
4253
|
+
}
|
|
4254
|
+
let minVal = value[0];
|
|
4255
|
+
for (let i = 1;i < value.length; i++) {
|
|
4256
|
+
if (value[i] < minVal)
|
|
4257
|
+
minVal = value[i];
|
|
4258
|
+
}
|
|
4259
|
+
return minVal;
|
|
4260
|
+
}
|
|
4261
|
+
case "timesince": {
|
|
4262
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
4263
|
+
const now = arg1 instanceof Date ? arg1 : new Date(arg1 ?? Date.now());
|
|
4264
|
+
const diff = (now.getTime() - d.getTime()) / 1000;
|
|
4265
|
+
const intervals = [
|
|
4266
|
+
[31536000, "year", "years"],
|
|
4267
|
+
[2592000, "month", "months"],
|
|
4268
|
+
[604800, "week", "weeks"],
|
|
4269
|
+
[86400, "day", "days"],
|
|
4270
|
+
[3600, "hour", "hours"],
|
|
4271
|
+
[60, "minute", "minutes"]
|
|
4272
|
+
];
|
|
4273
|
+
for (const [secs, sing, plur] of intervals) {
|
|
4274
|
+
const count = Math.floor(diff / secs);
|
|
4275
|
+
if (count >= 1)
|
|
4276
|
+
return `${count} ${count === 1 ? sing : plur}`;
|
|
4277
|
+
}
|
|
4278
|
+
return "just now";
|
|
4279
|
+
}
|
|
4280
|
+
case "timeuntil": {
|
|
4281
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
4282
|
+
const now = arg1 instanceof Date ? arg1 : new Date(arg1 ?? Date.now());
|
|
4283
|
+
const diff = (d.getTime() - now.getTime()) / 1000;
|
|
4284
|
+
const intervals = [
|
|
4285
|
+
[31536000, "year", "years"],
|
|
4286
|
+
[2592000, "month", "months"],
|
|
4287
|
+
[604800, "week", "weeks"],
|
|
4288
|
+
[86400, "day", "days"],
|
|
4289
|
+
[3600, "hour", "hours"],
|
|
4290
|
+
[60, "minute", "minutes"]
|
|
4291
|
+
];
|
|
4292
|
+
for (const [secs, sing, plur] of intervals) {
|
|
4293
|
+
const count = Math.floor(diff / secs);
|
|
4294
|
+
if (count >= 1)
|
|
4295
|
+
return `${count} ${count === 1 ? sing : plur}`;
|
|
4296
|
+
}
|
|
4297
|
+
return "now";
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
3868
4301
|
const args = [];
|
|
3869
|
-
for (let i = 0;i <
|
|
4302
|
+
for (let i = 0;i < argsLen; i++) {
|
|
3870
4303
|
args.push(this.eval(node.args[i], ctx));
|
|
3871
4304
|
}
|
|
3872
4305
|
const kwargs = this.evalObjectSync(node.kwargs, ctx);
|
|
3873
|
-
const filter = this.filters[
|
|
4306
|
+
const filter = this.filters[filterName];
|
|
3874
4307
|
if (!filter) {
|
|
3875
4308
|
const available = Object.keys(this.filters);
|
|
3876
|
-
const suggestion = findSimilar(
|
|
3877
|
-
throw new TemplateRuntimeError(`Unknown filter '${
|
|
4309
|
+
const suggestion = findSimilar(filterName, available);
|
|
4310
|
+
throw new TemplateRuntimeError(`Unknown filter '${filterName}'`, {
|
|
3878
4311
|
line: node.line,
|
|
3879
4312
|
column: node.column,
|
|
3880
4313
|
source: this.source,
|
|
@@ -5284,473 +5717,516 @@ function endDebugCollection() {
|
|
|
5284
5717
|
|
|
5285
5718
|
// src/debug/panel.ts
|
|
5286
5719
|
var DEFAULT_OPTIONS = {
|
|
5287
|
-
position: "bottom
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5720
|
+
position: "bottom",
|
|
5721
|
+
height: 300,
|
|
5722
|
+
width: 400,
|
|
5723
|
+
open: false,
|
|
5724
|
+
dark: true
|
|
5291
5725
|
};
|
|
5292
5726
|
function generateDebugPanel(data, options = {}) {
|
|
5293
5727
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
5294
|
-
const id = `binja-
|
|
5295
|
-
const
|
|
5728
|
+
const id = `binja-dbg-${Date.now()}`;
|
|
5729
|
+
const c2 = opts.dark ? darkTheme : lightTheme;
|
|
5296
5730
|
return `
|
|
5297
5731
|
<!-- Binja Debug Panel -->
|
|
5298
|
-
<div id="${id}" class="binja-
|
|
5299
|
-
<style>${generateStyles(id,
|
|
5300
|
-
${
|
|
5301
|
-
|
|
5302
|
-
<script>${generateScript(id)}</script>
|
|
5732
|
+
<div id="${id}" class="binja-devtools" data-position="${opts.position}" data-open="${opts.open}">
|
|
5733
|
+
<style>${generateStyles(id, c2, opts)}</style>
|
|
5734
|
+
${generateHTML(id, data, c2, opts)}
|
|
5735
|
+
<script>${generateScript(id, data, opts)}</script>
|
|
5303
5736
|
</div>
|
|
5304
5737
|
<!-- /Binja Debug Panel -->
|
|
5305
5738
|
`;
|
|
5306
5739
|
}
|
|
5307
5740
|
var darkTheme = {
|
|
5308
|
-
bg: "#
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
text: "#
|
|
5314
|
-
textSecondary: "#
|
|
5315
|
-
textMuted: "#
|
|
5316
|
-
accent: "#
|
|
5317
|
-
accentHover: "#
|
|
5318
|
-
success: "#
|
|
5319
|
-
warning: "#
|
|
5320
|
-
error: "#
|
|
5321
|
-
info: "#
|
|
5741
|
+
bg: "#1e1e1e",
|
|
5742
|
+
bgPanel: "#252526",
|
|
5743
|
+
bgHover: "#2a2d2e",
|
|
5744
|
+
bgActive: "#37373d",
|
|
5745
|
+
border: "#3c3c3c",
|
|
5746
|
+
text: "#cccccc",
|
|
5747
|
+
textSecondary: "#969696",
|
|
5748
|
+
textMuted: "#6e6e6e",
|
|
5749
|
+
accent: "#0078d4",
|
|
5750
|
+
accentHover: "#1c86d8",
|
|
5751
|
+
success: "#4ec9b0",
|
|
5752
|
+
warning: "#dcdcaa",
|
|
5753
|
+
error: "#f14c4c",
|
|
5754
|
+
info: "#75beff",
|
|
5755
|
+
string: "#ce9178",
|
|
5756
|
+
number: "#b5cea8",
|
|
5757
|
+
keyword: "#569cd6"
|
|
5322
5758
|
};
|
|
5323
5759
|
var lightTheme = {
|
|
5324
|
-
bg: "#
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
text: "#
|
|
5330
|
-
textSecondary: "#
|
|
5331
|
-
textMuted: "#
|
|
5332
|
-
accent: "#
|
|
5333
|
-
accentHover: "#
|
|
5334
|
-
success: "#
|
|
5335
|
-
warning: "#
|
|
5336
|
-
error: "#
|
|
5337
|
-
info: "#
|
|
5760
|
+
bg: "#f3f3f3",
|
|
5761
|
+
bgPanel: "#ffffff",
|
|
5762
|
+
bgHover: "#e8e8e8",
|
|
5763
|
+
bgActive: "#d4d4d4",
|
|
5764
|
+
border: "#d4d4d4",
|
|
5765
|
+
text: "#1e1e1e",
|
|
5766
|
+
textSecondary: "#616161",
|
|
5767
|
+
textMuted: "#a0a0a0",
|
|
5768
|
+
accent: "#0078d4",
|
|
5769
|
+
accentHover: "#106ebe",
|
|
5770
|
+
success: "#16825d",
|
|
5771
|
+
warning: "#bf8803",
|
|
5772
|
+
error: "#cd3131",
|
|
5773
|
+
info: "#0078d4",
|
|
5774
|
+
string: "#a31515",
|
|
5775
|
+
number: "#098658",
|
|
5776
|
+
keyword: "#0000ff"
|
|
5338
5777
|
};
|
|
5339
5778
|
function generateStyles(id, c2, opts) {
|
|
5340
|
-
const pos = getPosition(opts.position);
|
|
5341
5779
|
return `
|
|
5342
|
-
#${id} { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
5780
|
+
#${id} { --bg: ${c2.bg}; --bg-panel: ${c2.bgPanel}; --bg-hover: ${c2.bgHover}; --bg-active: ${c2.bgActive}; --border: ${c2.border}; --text: ${c2.text}; --text-secondary: ${c2.textSecondary}; --text-muted: ${c2.textMuted}; --accent: ${c2.accent}; --success: ${c2.success}; --warning: ${c2.warning}; --error: ${c2.error}; --string: ${c2.string}; --number: ${c2.number}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; font-size: 12px; line-height: 1.4; color: var(--text); }
|
|
5343
5781
|
#${id} * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
#${id} .
|
|
5347
|
-
#${id} .
|
|
5348
|
-
#${id} .
|
|
5349
|
-
#${id} .
|
|
5350
|
-
#${id} .
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
#${id} .
|
|
5354
|
-
#${id} .
|
|
5355
|
-
#${id} .
|
|
5356
|
-
#${id} .
|
|
5357
|
-
#${id} .
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
#${id} .
|
|
5361
|
-
#${id} .
|
|
5362
|
-
#${id} .
|
|
5363
|
-
#${id} .
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
#${id} .
|
|
5367
|
-
#${id} .
|
|
5368
|
-
#${id} .
|
|
5369
|
-
#${id} .
|
|
5370
|
-
#${id} .
|
|
5371
|
-
#${id} .
|
|
5372
|
-
#${id} .
|
|
5373
|
-
#${id} .
|
|
5374
|
-
#${id} .
|
|
5375
|
-
#${id} .
|
|
5376
|
-
#${id} .
|
|
5377
|
-
#${id} .
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
#${id} .
|
|
5381
|
-
#${id} .
|
|
5382
|
-
#${id} .
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
#${id} .
|
|
5386
|
-
#${id} .
|
|
5387
|
-
#${id} .
|
|
5388
|
-
#${id} .
|
|
5389
|
-
#${id} .
|
|
5390
|
-
#${id} .
|
|
5391
|
-
#${id} .
|
|
5392
|
-
#${id} .
|
|
5393
|
-
#${id} .
|
|
5394
|
-
#${id} .
|
|
5395
|
-
#${id} .
|
|
5396
|
-
#${id} .
|
|
5397
|
-
#${id} .
|
|
5398
|
-
#${id} .
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
#${id} .
|
|
5402
|
-
#${id} .
|
|
5403
|
-
#${id} .
|
|
5404
|
-
#${id} .
|
|
5405
|
-
#${id} .
|
|
5406
|
-
#${id} .
|
|
5407
|
-
#${id} .
|
|
5408
|
-
#${id} .
|
|
5409
|
-
#${id} .
|
|
5410
|
-
#${id} .
|
|
5411
|
-
#${id} .
|
|
5412
|
-
#${id} .
|
|
5413
|
-
#${id} .
|
|
5414
|
-
#${id} .
|
|
5415
|
-
#${id} .
|
|
5416
|
-
#${id} .
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
#${id} .
|
|
5420
|
-
#${id} .
|
|
5421
|
-
#${id} .
|
|
5422
|
-
#${id} .
|
|
5423
|
-
#${id} .
|
|
5424
|
-
#${id} .
|
|
5425
|
-
#${id} .
|
|
5426
|
-
#${id} .
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
#${id} .
|
|
5430
|
-
#${id} .
|
|
5431
|
-
#${id} .
|
|
5432
|
-
#${id} .
|
|
5433
|
-
#${id} .
|
|
5434
|
-
#${id} .
|
|
5435
|
-
#${id} .
|
|
5782
|
+
|
|
5783
|
+
/* Toggle Button */
|
|
5784
|
+
#${id} .devtools-toggle { position: fixed; bottom: 10px; right: 10px; z-index: 2147483646; display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); cursor: pointer; font-size: 11px; font-weight: 500; box-shadow: 0 2px 8px rgba(0,0,0,0.2); transition: all 0.15s; }
|
|
5785
|
+
#${id} .devtools-toggle:hover { background: var(--bg-hover); border-color: var(--accent); }
|
|
5786
|
+
#${id} .devtools-toggle svg { width: 14px; height: 14px; color: var(--accent); }
|
|
5787
|
+
#${id} .devtools-toggle .badge { padding: 2px 6px; background: var(--accent); color: #fff; border-radius: 3px; font-size: 10px; }
|
|
5788
|
+
#${id}[data-open="true"] .devtools-toggle { display: none; }
|
|
5789
|
+
|
|
5790
|
+
/* Panel Container */
|
|
5791
|
+
#${id} .devtools-panel { display: none; position: fixed; z-index: 2147483647; background: var(--bg); border: 1px solid var(--border); box-shadow: 0 -2px 12px rgba(0,0,0,0.3); }
|
|
5792
|
+
#${id}[data-open="true"] .devtools-panel { display: flex; flex-direction: column; }
|
|
5793
|
+
#${id}[data-position="bottom"] .devtools-panel { left: 0; right: 0; bottom: 0; height: ${opts.height}px; border-left: none; border-right: none; border-bottom: none; }
|
|
5794
|
+
#${id}[data-position="right"] .devtools-panel { top: 0; right: 0; bottom: 0; width: ${opts.width}px; border-top: none; border-right: none; border-bottom: none; }
|
|
5795
|
+
#${id}[data-position="popup"] .devtools-panel { bottom: 50px; right: 20px; width: 700px; height: 500px; border-radius: 8px; }
|
|
5796
|
+
|
|
5797
|
+
/* Resize Handle */
|
|
5798
|
+
#${id} .devtools-resize { position: absolute; background: transparent; }
|
|
5799
|
+
#${id}[data-position="bottom"] .devtools-resize { top: 0; left: 0; right: 0; height: 4px; cursor: ns-resize; }
|
|
5800
|
+
#${id}[data-position="right"] .devtools-resize { top: 0; left: 0; bottom: 0; width: 4px; cursor: ew-resize; }
|
|
5801
|
+
#${id} .devtools-resize:hover { background: var(--accent); }
|
|
5802
|
+
|
|
5803
|
+
/* Toolbar */
|
|
5804
|
+
#${id} .devtools-toolbar { display: flex; align-items: center; justify-content: space-between; height: 30px; padding: 0 8px; background: var(--bg-panel); border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
5805
|
+
#${id} .devtools-tabs { display: flex; height: 100%; }
|
|
5806
|
+
#${id} .devtools-tab { display: flex; align-items: center; gap: 4px; padding: 0 12px; border: none; background: none; color: var(--text-secondary); font-size: 11px; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.1s; }
|
|
5807
|
+
#${id} .devtools-tab:hover { color: var(--text); background: var(--bg-hover); }
|
|
5808
|
+
#${id} .devtools-tab.active { color: var(--text); border-bottom-color: var(--accent); }
|
|
5809
|
+
#${id} .devtools-tab svg { width: 12px; height: 12px; opacity: 0.7; }
|
|
5810
|
+
#${id} .devtools-tab .count { margin-left: 4px; padding: 1px 5px; background: var(--bg-active); border-radius: 8px; font-size: 10px; }
|
|
5811
|
+
#${id} .devtools-tab .count.warn { background: rgba(241,76,76,0.2); color: var(--error); }
|
|
5812
|
+
#${id} .devtools-actions { display: flex; align-items: center; gap: 4px; }
|
|
5813
|
+
#${id} .devtools-btn { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: none; background: none; color: var(--text-secondary); cursor: pointer; border-radius: 3px; }
|
|
5814
|
+
#${id} .devtools-btn:hover { background: var(--bg-hover); color: var(--text); }
|
|
5815
|
+
#${id} .devtools-btn svg { width: 14px; height: 14px; }
|
|
5816
|
+
|
|
5817
|
+
/* Content Area */
|
|
5818
|
+
#${id} .devtools-content { flex: 1; overflow: hidden; }
|
|
5819
|
+
#${id} .devtools-pane { display: none; height: 100%; overflow: auto; padding: 8px; }
|
|
5820
|
+
#${id} .devtools-pane.active { display: block; }
|
|
5821
|
+
|
|
5822
|
+
/* Performance Tab */
|
|
5823
|
+
#${id} .perf-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 12px; }
|
|
5824
|
+
#${id} .perf-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; padding: 10px; text-align: center; }
|
|
5825
|
+
#${id} .perf-card-value { font-size: 20px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; color: var(--success); }
|
|
5826
|
+
#${id} .perf-card-label { font-size: 10px; color: var(--text-muted); margin-top: 4px; text-transform: uppercase; }
|
|
5827
|
+
#${id} .perf-breakdown { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
|
|
5828
|
+
#${id} .perf-row { display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
|
5829
|
+
#${id} .perf-row:last-child { border-bottom: none; }
|
|
5830
|
+
#${id} .perf-row-label { flex: 1; font-size: 11px; color: var(--text-secondary); }
|
|
5831
|
+
#${id} .perf-row-value { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; min-width: 60px; text-align: right; }
|
|
5832
|
+
#${id} .perf-row-bar { flex: 2; height: 4px; background: var(--bg-active); border-radius: 2px; margin: 0 12px; overflow: hidden; }
|
|
5833
|
+
#${id} .perf-row-fill { height: 100%; border-radius: 2px; }
|
|
5834
|
+
#${id} .perf-row-fill.lexer { background: ${c2.info}; }
|
|
5835
|
+
#${id} .perf-row-fill.parser { background: ${c2.warning}; }
|
|
5836
|
+
#${id} .perf-row-fill.render { background: ${c2.success}; }
|
|
5837
|
+
|
|
5838
|
+
/* Context Tab - Tree View */
|
|
5839
|
+
#${id} .tree { font-family: 'SF Mono', Monaco, Consolas, monospace; font-size: 11px; }
|
|
5840
|
+
#${id} .tree-item { }
|
|
5841
|
+
#${id} .tree-row { display: flex; align-items: center; padding: 2px 4px; cursor: default; border-radius: 2px; }
|
|
5842
|
+
#${id} .tree-row:hover { background: var(--bg-hover); }
|
|
5843
|
+
#${id} .tree-row.expandable { cursor: pointer; }
|
|
5844
|
+
#${id} .tree-arrow { width: 12px; height: 12px; color: var(--text-muted); transition: transform 0.1s; flex-shrink: 0; }
|
|
5845
|
+
#${id} .tree-item.open > .tree-row .tree-arrow { transform: rotate(90deg); }
|
|
5846
|
+
#${id} .tree-key { color: var(--keyword); margin-right: 4px; }
|
|
5847
|
+
#${id} .tree-colon { color: var(--text-muted); margin-right: 6px; }
|
|
5848
|
+
#${id} .tree-value { color: var(--text); }
|
|
5849
|
+
#${id} .tree-value.string { color: var(--string); }
|
|
5850
|
+
#${id} .tree-value.number { color: var(--number); }
|
|
5851
|
+
#${id} .tree-value.null { color: var(--text-muted); font-style: italic; }
|
|
5852
|
+
#${id} .tree-type { color: var(--text-muted); margin-left: 6px; font-size: 10px; }
|
|
5853
|
+
#${id} .tree-children { display: none; padding-left: 16px; }
|
|
5854
|
+
#${id} .tree-item.open > .tree-children { display: block; }
|
|
5855
|
+
|
|
5856
|
+
/* Templates Tab */
|
|
5857
|
+
#${id} .template-list { display: flex; flex-direction: column; gap: 4px; }
|
|
5858
|
+
#${id} .template-item { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; }
|
|
5859
|
+
#${id} .template-icon { width: 14px; height: 14px; color: var(--text-muted); }
|
|
5860
|
+
#${id} .template-name { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; }
|
|
5861
|
+
#${id} .template-badge { font-size: 9px; padding: 2px 6px; border-radius: 3px; text-transform: uppercase; font-weight: 500; }
|
|
5862
|
+
#${id} .template-badge.root { background: rgba(0,120,212,0.15); color: var(--accent); }
|
|
5863
|
+
#${id} .template-badge.extends { background: rgba(156,86,246,0.15); color: #9c56f6; }
|
|
5864
|
+
#${id} .template-badge.include { background: rgba(78,201,176,0.15); color: var(--success); }
|
|
5865
|
+
|
|
5866
|
+
/* Queries Tab */
|
|
5867
|
+
#${id} .queries-stats { display: flex; gap: 16px; padding: 8px 12px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 8px; }
|
|
5868
|
+
#${id} .queries-stat { text-align: center; }
|
|
5869
|
+
#${id} .queries-stat-value { font-size: 16px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
|
|
5870
|
+
#${id} .queries-stat-value.warn { color: var(--warning); }
|
|
5871
|
+
#${id} .queries-stat-value.error { color: var(--error); }
|
|
5872
|
+
#${id} .queries-stat-label { font-size: 9px; color: var(--text-muted); text-transform: uppercase; }
|
|
5873
|
+
#${id} .query-list { display: flex; flex-direction: column; gap: 4px; }
|
|
5874
|
+
#${id} .query-item { background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; }
|
|
5875
|
+
#${id} .query-item.n1 { border-left: 3px solid var(--error); }
|
|
5876
|
+
#${id} .query-item.slow { border-left: 3px solid var(--warning); }
|
|
5877
|
+
#${id} .query-header { display: flex; align-items: center; gap: 8px; padding: 6px 10px; }
|
|
5878
|
+
#${id} .query-sql { flex: 1; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
|
|
5879
|
+
#${id} .query-meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
|
|
5880
|
+
#${id} .query-badge { font-size: 9px; padding: 2px 5px; border-radius: 3px; font-weight: 600; }
|
|
5881
|
+
#${id} .query-badge.n1 { background: rgba(241,76,76,0.15); color: var(--error); }
|
|
5882
|
+
#${id} .query-source { font-size: 9px; padding: 2px 5px; border-radius: 3px; background: var(--bg-active); color: var(--text-muted); text-transform: uppercase; }
|
|
5883
|
+
#${id} .query-time { font-family: 'SF Mono', Monaco, monospace; font-size: 10px; color: var(--text-secondary); }
|
|
5884
|
+
#${id} .query-time.slow { color: var(--warning); }
|
|
5885
|
+
#${id} .query-rows { font-size: 10px; color: var(--text-muted); }
|
|
5886
|
+
|
|
5887
|
+
/* Filters Tab */
|
|
5888
|
+
#${id} .filter-grid { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
5889
|
+
#${id} .filter-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; }
|
|
5890
|
+
#${id} .filter-count { color: var(--accent); font-weight: 600; font-size: 10px; }
|
|
5891
|
+
|
|
5892
|
+
/* Cache Tab */
|
|
5893
|
+
#${id} .cache-stats { display: flex; gap: 20px; }
|
|
5894
|
+
#${id} .cache-stat { flex: 1; background: var(--bg-panel); border: 1px solid var(--border); border-radius: 4px; padding: 16px; text-align: center; }
|
|
5895
|
+
#${id} .cache-value { font-size: 32px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
|
|
5896
|
+
#${id} .cache-value.hit { color: var(--success); }
|
|
5897
|
+
#${id} .cache-value.miss { color: var(--error); }
|
|
5898
|
+
#${id} .cache-label { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
|
5899
|
+
|
|
5900
|
+
/* Warnings Tab */
|
|
5901
|
+
#${id} .warning-list { display: flex; flex-direction: column; gap: 6px; }
|
|
5902
|
+
#${id} .warning-item { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(241,76,76,0.08); border: 1px solid rgba(241,76,76,0.2); border-radius: 4px; }
|
|
5903
|
+
#${id} .warning-icon { color: var(--error); flex-shrink: 0; width: 14px; height: 14px; }
|
|
5904
|
+
#${id} .warning-text { font-size: 11px; color: var(--text); }
|
|
5905
|
+
|
|
5906
|
+
/* Empty State */
|
|
5907
|
+
#${id} .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--text-muted); font-size: 12px; }
|
|
5908
|
+
#${id} .empty-state svg { width: 32px; height: 32px; margin-bottom: 8px; opacity: 0.5; }
|
|
5436
5909
|
`;
|
|
5437
5910
|
}
|
|
5438
|
-
function getPosition(pos) {
|
|
5439
|
-
switch (pos) {
|
|
5440
|
-
case "bottom-left":
|
|
5441
|
-
return "bottom: 16px; left: 16px;";
|
|
5442
|
-
case "top-right":
|
|
5443
|
-
return "top: 16px; right: 16px;";
|
|
5444
|
-
case "top-left":
|
|
5445
|
-
return "top: 16px; left: 16px;";
|
|
5446
|
-
default:
|
|
5447
|
-
return "bottom: 16px; right: 16px;";
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
5911
|
var icons = {
|
|
5451
5912
|
logo: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`,
|
|
5452
5913
|
close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`,
|
|
5453
|
-
|
|
5914
|
+
minimize: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/></svg>`,
|
|
5915
|
+
dock: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 15h18"/></svg>`,
|
|
5916
|
+
dockRight: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M15 3v18"/></svg>`,
|
|
5917
|
+
popup: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v6h6"/></svg>`,
|
|
5918
|
+
arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>`,
|
|
5454
5919
|
perf: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>`,
|
|
5455
|
-
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>`,
|
|
5456
5920
|
context: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z"/></svg>`,
|
|
5921
|
+
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><path d="M14 2v6h6"/></svg>`,
|
|
5457
5922
|
filter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>`,
|
|
5923
|
+
database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`,
|
|
5458
5924
|
cache: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1010 10H12V2z"/><path d="M12 2a10 10 0 00-8.66 15"/></svg>`,
|
|
5459
|
-
warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg
|
|
5460
|
-
file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`,
|
|
5461
|
-
database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`
|
|
5925
|
+
warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`
|
|
5462
5926
|
};
|
|
5463
|
-
function
|
|
5927
|
+
function generateHTML(id, data, c2, opts) {
|
|
5464
5928
|
const time2 = (data.totalTime || 0).toFixed(1);
|
|
5929
|
+
const queryCount = data.queries?.length || 0;
|
|
5930
|
+
const hasWarnings = data.warnings.length > 0 || data.queryStats?.n1Count > 0;
|
|
5931
|
+
const tabs = [
|
|
5932
|
+
{ id: "perf", icon: icons.perf, label: "Performance", count: `${time2}ms` },
|
|
5933
|
+
{ id: "context", icon: icons.context, label: "Context", count: Object.keys(data.contextSnapshot).length || null },
|
|
5934
|
+
{ id: "templates", icon: icons.template, label: "Templates", count: data.templateChain.length || null },
|
|
5935
|
+
{ id: "filters", icon: icons.filter, label: "Filters", count: data.filtersUsed.size || null },
|
|
5936
|
+
{ id: "queries", icon: icons.database, label: "Queries", count: queryCount || null, warn: data.queryStats?.n1Count > 0 },
|
|
5937
|
+
{ id: "cache", icon: icons.cache, label: "Cache", count: data.cacheHits + data.cacheMisses || null },
|
|
5938
|
+
{ id: "warnings", icon: icons.warning, label: "Warnings", count: data.warnings.length || null, warn: hasWarnings }
|
|
5939
|
+
];
|
|
5940
|
+
const tabsHtml = tabs.map((t, i) => {
|
|
5941
|
+
const countHtml = t.count !== null ? `<span class="count${t.warn ? " warn" : ""}">${t.count}</span>` : "";
|
|
5942
|
+
return `<button class="devtools-tab${i === 0 ? " active" : ""}" data-tab="${t.id}">${t.icon}${t.label}${countHtml}</button>`;
|
|
5943
|
+
}).join("");
|
|
5465
5944
|
return `
|
|
5466
|
-
<button class="
|
|
5945
|
+
<button class="devtools-toggle" onclick="document.getElementById('${id}').dataset.open='true'">
|
|
5467
5946
|
${icons.logo}
|
|
5468
5947
|
<span>Binja</span>
|
|
5469
|
-
<span class="
|
|
5470
|
-
</button
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
<div class="
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
<
|
|
5481
|
-
<span class="dbg-badge time">${time2}ms</span>
|
|
5482
|
-
<button class="dbg-close" onclick="document.querySelector('#${id} .dbg-panel').classList.remove('open');document.querySelector('#${id} .dbg-toggle').style.display='inline-flex'">${icons.close}</button>
|
|
5948
|
+
<span class="badge">${time2}ms</span>
|
|
5949
|
+
</button>
|
|
5950
|
+
|
|
5951
|
+
<div class="devtools-panel">
|
|
5952
|
+
<div class="devtools-resize"></div>
|
|
5953
|
+
<div class="devtools-toolbar">
|
|
5954
|
+
<div class="devtools-tabs">${tabsHtml}</div>
|
|
5955
|
+
<div class="devtools-actions">
|
|
5956
|
+
<button class="devtools-btn" title="Dock to bottom" data-dock="bottom">${icons.dock}</button>
|
|
5957
|
+
<button class="devtools-btn" title="Dock to right" data-dock="right">${icons.dockRight}</button>
|
|
5958
|
+
<button class="devtools-btn" title="Popup" data-dock="popup">${icons.popup}</button>
|
|
5959
|
+
<button class="devtools-btn" title="Close" onclick="document.getElementById('${id}').dataset.open='false'">${icons.close}</button>
|
|
5483
5960
|
</div>
|
|
5484
5961
|
</div>
|
|
5485
|
-
<div class="
|
|
5486
|
-
${
|
|
5487
|
-
${
|
|
5488
|
-
${
|
|
5489
|
-
${
|
|
5490
|
-
${
|
|
5491
|
-
${
|
|
5492
|
-
${
|
|
5962
|
+
<div class="devtools-content">
|
|
5963
|
+
${generatePerfPane(data)}
|
|
5964
|
+
${generateContextPane(data)}
|
|
5965
|
+
${generateTemplatesPane(data)}
|
|
5966
|
+
${generateFiltersPane(data)}
|
|
5967
|
+
${generateQueriesPane(data)}
|
|
5968
|
+
${generateCachePane(data)}
|
|
5969
|
+
${generateWarningsPane(data)}
|
|
5493
5970
|
</div>
|
|
5494
5971
|
</div>`;
|
|
5495
5972
|
}
|
|
5496
|
-
function
|
|
5973
|
+
function generatePerfPane(data) {
|
|
5497
5974
|
const total = data.totalTime || 0.01;
|
|
5498
5975
|
const lexer = data.lexerTime || 0;
|
|
5499
5976
|
const parser = data.parserTime || 0;
|
|
5500
5977
|
const render = data.renderTime || 0;
|
|
5978
|
+
const mode = data.mode === "aot" ? "AOT" : "Runtime";
|
|
5501
5979
|
return `
|
|
5502
|
-
<div class="
|
|
5503
|
-
<div class="
|
|
5504
|
-
<
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
<div class="dbg-section-content">
|
|
5508
|
-
<div class="dbg-row">
|
|
5509
|
-
<span class="dbg-label">Lexer</span>
|
|
5510
|
-
<span class="dbg-value">${lexer.toFixed(2)}ms</span>
|
|
5980
|
+
<div class="devtools-pane active" data-pane="perf">
|
|
5981
|
+
<div class="perf-grid">
|
|
5982
|
+
<div class="perf-card">
|
|
5983
|
+
<div class="perf-card-value">${total.toFixed(2)}ms</div>
|
|
5984
|
+
<div class="perf-card-label">Total Time</div>
|
|
5511
5985
|
</div>
|
|
5512
|
-
<div class="
|
|
5513
|
-
|
|
5514
|
-
<
|
|
5515
|
-
<span class="dbg-value">${parser.toFixed(2)}ms</span>
|
|
5986
|
+
<div class="perf-card">
|
|
5987
|
+
<div class="perf-card-value" style="color:var(--accent)">${mode}</div>
|
|
5988
|
+
<div class="perf-card-label">Mode</div>
|
|
5516
5989
|
</div>
|
|
5517
|
-
<div class="
|
|
5518
|
-
|
|
5519
|
-
<
|
|
5520
|
-
<span class="dbg-value">${render.toFixed(2)}ms</span>
|
|
5990
|
+
<div class="perf-card">
|
|
5991
|
+
<div class="perf-card-value">${data.templateChain.length}</div>
|
|
5992
|
+
<div class="perf-card-label">Templates</div>
|
|
5521
5993
|
</div>
|
|
5522
|
-
<div class="
|
|
5523
|
-
|
|
5524
|
-
<
|
|
5525
|
-
<span class="dbg-value" style="font-weight:600">${total.toFixed(2)}ms</span>
|
|
5994
|
+
<div class="perf-card">
|
|
5995
|
+
<div class="perf-card-value">${data.queries?.length || 0}</div>
|
|
5996
|
+
<div class="perf-card-label">Queries</div>
|
|
5526
5997
|
</div>
|
|
5527
5998
|
</div>
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
<div class="
|
|
5535
|
-
|
|
5536
|
-
<
|
|
5537
|
-
<span class="
|
|
5999
|
+
<div class="perf-breakdown">
|
|
6000
|
+
<div class="perf-row">
|
|
6001
|
+
<span class="perf-row-label">Lexer</span>
|
|
6002
|
+
<div class="perf-row-bar"><div class="perf-row-fill lexer" style="width:${lexer / total * 100}%"></div></div>
|
|
6003
|
+
<span class="perf-row-value">${lexer.toFixed(2)}ms</span>
|
|
6004
|
+
</div>
|
|
6005
|
+
<div class="perf-row">
|
|
6006
|
+
<span class="perf-row-label">Parser</span>
|
|
6007
|
+
<div class="perf-row-bar"><div class="perf-row-fill parser" style="width:${parser / total * 100}%"></div></div>
|
|
6008
|
+
<span class="perf-row-value">${parser.toFixed(2)}ms</span>
|
|
6009
|
+
</div>
|
|
6010
|
+
<div class="perf-row">
|
|
6011
|
+
<span class="perf-row-label">Render</span>
|
|
6012
|
+
<div class="perf-row-bar"><div class="perf-row-fill render" style="width:${render / total * 100}%"></div></div>
|
|
6013
|
+
<span class="perf-row-value">${render.toFixed(2)}ms</span>
|
|
5538
6014
|
</div>
|
|
5539
|
-
`).join("");
|
|
5540
|
-
return `
|
|
5541
|
-
<div class="dbg-section">
|
|
5542
|
-
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
5543
|
-
<span class="dbg-section-title">${icons.template} Templates</span>
|
|
5544
|
-
<span class="dbg-section-meta">${data.templateChain.length}</span>
|
|
5545
|
-
<span class="dbg-chevron">${icons.chevron}</span>
|
|
5546
|
-
</div>
|
|
5547
|
-
<div class="dbg-section-content">
|
|
5548
|
-
<div class="dbg-templates">${templates}</div>
|
|
5549
6015
|
</div>
|
|
5550
6016
|
</div>`;
|
|
5551
6017
|
}
|
|
5552
|
-
function
|
|
6018
|
+
function generateContextPane(data) {
|
|
5553
6019
|
const keys = Object.keys(data.contextSnapshot);
|
|
5554
|
-
if (keys.length === 0)
|
|
5555
|
-
return ""
|
|
5556
|
-
const items2 = keys.map((key) => renderContextValue(key, data.contextSnapshot[key])).join("");
|
|
5557
|
-
return `
|
|
5558
|
-
<div class="dbg-section">
|
|
5559
|
-
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
5560
|
-
<span class="dbg-section-title">${icons.context} Context</span>
|
|
5561
|
-
<span class="dbg-section-meta">${keys.length} vars</span>
|
|
5562
|
-
<span class="dbg-chevron">${icons.chevron}</span>
|
|
5563
|
-
</div>
|
|
5564
|
-
<div class="dbg-section-content">
|
|
5565
|
-
<div class="dbg-ctx-grid">${items2}</div>
|
|
5566
|
-
</div>
|
|
5567
|
-
</div>`;
|
|
5568
|
-
}
|
|
5569
|
-
function renderContextValue(key, ctx) {
|
|
5570
|
-
const arrow = ctx.expandable ? `<svg class="dbg-ctx-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>` : "";
|
|
5571
|
-
const expandableClass = ctx.expandable ? "expandable" : "";
|
|
5572
|
-
const onClick = ctx.expandable ? `onclick="this.parentElement.classList.toggle('open')"` : "";
|
|
5573
|
-
let children = "";
|
|
5574
|
-
if (ctx.expandable && ctx.children) {
|
|
5575
|
-
const childItems = Object.entries(ctx.children).map(([k, v]) => renderContextValue(k, v)).join("");
|
|
5576
|
-
children = `<div class="dbg-ctx-children">${childItems}</div>`;
|
|
6020
|
+
if (keys.length === 0) {
|
|
6021
|
+
return `<div class="devtools-pane" data-pane="context"><div class="empty-state">${icons.context}<span>No context variables</span></div></div>`;
|
|
5577
6022
|
}
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
<div class="dbg-ctx-row ${expandableClass}" ${onClick}>
|
|
5581
|
-
<div class="dbg-ctx-key">
|
|
5582
|
-
${arrow}
|
|
5583
|
-
<span class="dbg-ctx-name">${escapeHtml(key)}</span>
|
|
5584
|
-
<span class="dbg-ctx-type">${ctx.type}</span>
|
|
5585
|
-
</div>
|
|
5586
|
-
<span class="dbg-ctx-preview">${escapeHtml(ctx.preview)}</span>
|
|
5587
|
-
</div>
|
|
5588
|
-
${children}
|
|
5589
|
-
</div>`;
|
|
6023
|
+
const items2 = keys.map((key) => renderTreeItem(key, data.contextSnapshot[key])).join("");
|
|
6024
|
+
return `<div class="devtools-pane" data-pane="context"><div class="tree">${items2}</div></div>`;
|
|
5590
6025
|
}
|
|
5591
|
-
function
|
|
5592
|
-
const
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
const
|
|
6026
|
+
function renderTreeItem(key, ctx) {
|
|
6027
|
+
const hasChildren = ctx.expandable && ctx.children && Object.keys(ctx.children).length > 0;
|
|
6028
|
+
const arrowHtml = hasChildren ? `<span class="tree-arrow">${icons.arrow}</span>` : '<span class="tree-arrow" style="visibility:hidden">${icons.arrow}</span>';
|
|
6029
|
+
const expandableClass = hasChildren ? "expandable" : "";
|
|
6030
|
+
const valueClass = getValueClass(ctx.type);
|
|
6031
|
+
let childrenHtml = "";
|
|
6032
|
+
if (hasChildren && ctx.children) {
|
|
6033
|
+
childrenHtml = `<div class="tree-children">${Object.entries(ctx.children).map(([k, v]) => renderTreeItem(k, v)).join("")}</div>`;
|
|
6034
|
+
}
|
|
5596
6035
|
return `
|
|
5597
|
-
<div class="
|
|
5598
|
-
<div class="
|
|
5599
|
-
|
|
5600
|
-
<span class="
|
|
5601
|
-
<span class="
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
<div class="dbg-filters">${items2}</div>
|
|
6036
|
+
<div class="tree-item">
|
|
6037
|
+
<div class="tree-row ${expandableClass}">
|
|
6038
|
+
${arrowHtml}
|
|
6039
|
+
<span class="tree-key">${escapeHtml(key)}</span>
|
|
6040
|
+
<span class="tree-colon">:</span>
|
|
6041
|
+
<span class="tree-value ${valueClass}">${escapeHtml(ctx.preview)}</span>
|
|
6042
|
+
<span class="tree-type">${ctx.type}</span>
|
|
5605
6043
|
</div>
|
|
6044
|
+
${childrenHtml}
|
|
5606
6045
|
</div>`;
|
|
5607
6046
|
}
|
|
5608
|
-
function
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
<span class="dbg-section-meta">${(data.cacheHits / total * 100).toFixed(0)}% hit</span>
|
|
5617
|
-
<span class="dbg-chevron">${icons.chevron}</span>
|
|
5618
|
-
</div>
|
|
5619
|
-
<div class="dbg-section-content">
|
|
5620
|
-
<div class="dbg-cache">
|
|
5621
|
-
<div class="dbg-cache-stat">
|
|
5622
|
-
<div class="dbg-cache-num hit">${data.cacheHits}</div>
|
|
5623
|
-
<div class="dbg-cache-label">Cache Hits</div>
|
|
5624
|
-
</div>
|
|
5625
|
-
<div class="dbg-cache-stat">
|
|
5626
|
-
<div class="dbg-cache-num miss">${data.cacheMisses}</div>
|
|
5627
|
-
<div class="dbg-cache-label">Cache Misses</div>
|
|
5628
|
-
</div>
|
|
5629
|
-
</div>
|
|
5630
|
-
</div>
|
|
5631
|
-
</div>`;
|
|
6047
|
+
function getValueClass(type) {
|
|
6048
|
+
if (type === "string")
|
|
6049
|
+
return "string";
|
|
6050
|
+
if (type === "number" || type === "integer" || type === "float")
|
|
6051
|
+
return "number";
|
|
6052
|
+
if (type === "null" || type === "undefined")
|
|
6053
|
+
return "null";
|
|
6054
|
+
return "";
|
|
5632
6055
|
}
|
|
5633
|
-
function
|
|
5634
|
-
if (data.
|
|
5635
|
-
return ""
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
<span class="
|
|
6056
|
+
function generateTemplatesPane(data) {
|
|
6057
|
+
if (data.templateChain.length === 0) {
|
|
6058
|
+
return `<div class="devtools-pane" data-pane="templates"><div class="empty-state">${icons.template}<span>No templates loaded</span></div></div>`;
|
|
6059
|
+
}
|
|
6060
|
+
const items2 = data.templateChain.map((t) => `
|
|
6061
|
+
<div class="template-item">
|
|
6062
|
+
<span class="template-icon">${icons.template}</span>
|
|
6063
|
+
<span class="template-name">${escapeHtml(t.name)}</span>
|
|
6064
|
+
<span class="template-badge ${t.type}">${t.type}</span>
|
|
5640
6065
|
</div>
|
|
5641
6066
|
`).join("");
|
|
5642
|
-
return
|
|
5643
|
-
<div class="dbg-section open">
|
|
5644
|
-
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
5645
|
-
<span class="dbg-section-title">${icons.warning} Warnings</span>
|
|
5646
|
-
<span class="dbg-section-meta" style="color:#eab308">${data.warnings.length}</span>
|
|
5647
|
-
<span class="dbg-chevron">${icons.chevron}</span>
|
|
5648
|
-
</div>
|
|
5649
|
-
<div class="dbg-section-content">
|
|
5650
|
-
<div class="dbg-warnings">${items2}</div>
|
|
5651
|
-
</div>
|
|
5652
|
-
</div>`;
|
|
6067
|
+
return `<div class="devtools-pane" data-pane="templates"><div class="template-list">${items2}</div></div>`;
|
|
5653
6068
|
}
|
|
5654
|
-
function
|
|
5655
|
-
|
|
5656
|
-
|
|
6069
|
+
function generateFiltersPane(data) {
|
|
6070
|
+
const filters = Array.from(data.filtersUsed.entries());
|
|
6071
|
+
if (filters.length === 0) {
|
|
6072
|
+
return `<div class="devtools-pane" data-pane="filters"><div class="empty-state">${icons.filter}<span>No filters used</span></div></div>`;
|
|
6073
|
+
}
|
|
6074
|
+
const items2 = filters.map(([name, count]) => `
|
|
6075
|
+
<span class="filter-chip">${escapeHtml(name)}<span class="filter-count">\xD7${count}</span></span>
|
|
6076
|
+
`).join("");
|
|
6077
|
+
return `<div class="devtools-pane" data-pane="filters"><div class="filter-grid">${items2}</div></div>`;
|
|
6078
|
+
}
|
|
6079
|
+
function generateQueriesPane(data) {
|
|
6080
|
+
if (!data.queries || data.queries.length === 0) {
|
|
6081
|
+
return `<div class="devtools-pane" data-pane="queries"><div class="empty-state">${icons.database}<span>No queries recorded</span></div></div>`;
|
|
6082
|
+
}
|
|
5657
6083
|
const stats = data.queryStats;
|
|
5658
|
-
const hasIssues = stats.slowCount > 0 || stats.n1Count > 0;
|
|
5659
6084
|
const statsHtml = `
|
|
5660
|
-
<div class="
|
|
5661
|
-
<div class="
|
|
5662
|
-
<div class="
|
|
5663
|
-
<div class="
|
|
6085
|
+
<div class="queries-stats">
|
|
6086
|
+
<div class="queries-stat">
|
|
6087
|
+
<div class="queries-stat-value">${stats.count}</div>
|
|
6088
|
+
<div class="queries-stat-label">Queries</div>
|
|
5664
6089
|
</div>
|
|
5665
|
-
<div class="
|
|
5666
|
-
<div class="
|
|
5667
|
-
<div class="
|
|
6090
|
+
<div class="queries-stat">
|
|
6091
|
+
<div class="queries-stat-value">${stats.totalDuration.toFixed(1)}ms</div>
|
|
6092
|
+
<div class="queries-stat-label">Total</div>
|
|
5668
6093
|
</div>
|
|
5669
|
-
<div class="
|
|
5670
|
-
<div class="
|
|
5671
|
-
<div class="
|
|
6094
|
+
<div class="queries-stat">
|
|
6095
|
+
<div class="queries-stat-value ${stats.slowCount > 0 ? "warn" : ""}">${stats.slowCount}</div>
|
|
6096
|
+
<div class="queries-stat-label">Slow</div>
|
|
5672
6097
|
</div>
|
|
5673
|
-
<div class="
|
|
5674
|
-
<div class="
|
|
5675
|
-
<div class="
|
|
6098
|
+
<div class="queries-stat">
|
|
6099
|
+
<div class="queries-stat-value ${stats.n1Count > 0 ? "error" : ""}">${stats.n1Count}</div>
|
|
6100
|
+
<div class="queries-stat-label">N+1</div>
|
|
5676
6101
|
</div>
|
|
5677
6102
|
</div>`;
|
|
5678
6103
|
const queries = data.queries.map((q) => {
|
|
5679
6104
|
const isSlow = q.duration > 100;
|
|
5680
|
-
const classes = [
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
].filter(Boolean).join(" ");
|
|
5685
|
-
const badges = [];
|
|
5686
|
-
if (q.isN1) {
|
|
5687
|
-
badges.push('<span class="dbg-query-badge n1">N+1</span>');
|
|
5688
|
-
}
|
|
5689
|
-
const rowsText = q.rows !== undefined ? `<span class="dbg-query-rows">${q.rows} rows</span>` : "";
|
|
5690
|
-
const sourceText = q.source ? `<span class="dbg-query-source">${escapeHtml(q.source)}</span>` : "";
|
|
6105
|
+
const classes = ["query-item", q.isN1 ? "n1" : "", isSlow ? "slow" : ""].filter(Boolean).join(" ");
|
|
6106
|
+
const badge = q.isN1 ? '<span class="query-badge n1">N+1</span>' : "";
|
|
6107
|
+
const source = q.source ? `<span class="query-source">${escapeHtml(q.source)}</span>` : "";
|
|
6108
|
+
const rows = q.rows !== undefined ? `<span class="query-rows">${q.rows} rows</span>` : "";
|
|
5691
6109
|
return `
|
|
5692
6110
|
<div class="${classes}">
|
|
5693
|
-
<div class="
|
|
5694
|
-
<span class="
|
|
5695
|
-
<div class="
|
|
5696
|
-
${
|
|
5697
|
-
${
|
|
5698
|
-
${rowsText}
|
|
5699
|
-
<span class="dbg-query-time ${isSlow ? "slow" : ""}">${q.duration.toFixed(1)}ms</span>
|
|
6111
|
+
<div class="query-header">
|
|
6112
|
+
<span class="query-sql" title="${escapeHtml(q.sql)}">${escapeHtml(q.sql)}</span>
|
|
6113
|
+
<div class="query-meta">
|
|
6114
|
+
${badge}${source}${rows}
|
|
6115
|
+
<span class="query-time ${isSlow ? "slow" : ""}">${q.duration.toFixed(1)}ms</span>
|
|
5700
6116
|
</div>
|
|
5701
6117
|
</div>
|
|
5702
6118
|
</div>`;
|
|
5703
6119
|
}).join("");
|
|
5704
|
-
|
|
6120
|
+
return `<div class="devtools-pane" data-pane="queries">${statsHtml}<div class="query-list">${queries}</div></div>`;
|
|
6121
|
+
}
|
|
6122
|
+
function generateCachePane(data) {
|
|
6123
|
+
const total = data.cacheHits + data.cacheMisses;
|
|
6124
|
+
if (total === 0) {
|
|
6125
|
+
return `<div class="devtools-pane" data-pane="cache"><div class="empty-state">${icons.cache}<span>No cache activity</span></div></div>`;
|
|
6126
|
+
}
|
|
6127
|
+
const hitRate = (data.cacheHits / total * 100).toFixed(0);
|
|
5705
6128
|
return `
|
|
5706
|
-
<div class="
|
|
5707
|
-
<div class="
|
|
5708
|
-
<
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
6129
|
+
<div class="devtools-pane" data-pane="cache">
|
|
6130
|
+
<div class="cache-stats">
|
|
6131
|
+
<div class="cache-stat">
|
|
6132
|
+
<div class="cache-value hit">${data.cacheHits}</div>
|
|
6133
|
+
<div class="cache-label">Cache Hits</div>
|
|
6134
|
+
</div>
|
|
6135
|
+
<div class="cache-stat">
|
|
6136
|
+
<div class="cache-value miss">${data.cacheMisses}</div>
|
|
6137
|
+
<div class="cache-label">Cache Misses</div>
|
|
6138
|
+
</div>
|
|
6139
|
+
<div class="cache-stat">
|
|
6140
|
+
<div class="cache-value">${hitRate}%</div>
|
|
6141
|
+
<div class="cache-label">Hit Rate</div>
|
|
6142
|
+
</div>
|
|
5715
6143
|
</div>
|
|
5716
6144
|
</div>`;
|
|
5717
6145
|
}
|
|
5718
|
-
function
|
|
6146
|
+
function generateWarningsPane(data) {
|
|
6147
|
+
if (data.warnings.length === 0) {
|
|
6148
|
+
return `<div class="devtools-pane" data-pane="warnings"><div class="empty-state">${icons.warning}<span>No warnings</span></div></div>`;
|
|
6149
|
+
}
|
|
6150
|
+
const items2 = data.warnings.map((w) => `
|
|
6151
|
+
<div class="warning-item">
|
|
6152
|
+
<span class="warning-icon">${icons.warning}</span>
|
|
6153
|
+
<span class="warning-text">${escapeHtml(w)}</span>
|
|
6154
|
+
</div>
|
|
6155
|
+
`).join("");
|
|
6156
|
+
return `<div class="devtools-pane" data-pane="warnings"><div class="warning-list">${items2}</div></div>`;
|
|
6157
|
+
}
|
|
6158
|
+
function generateScript(id, data, opts) {
|
|
5719
6159
|
return `
|
|
5720
|
-
(function(){
|
|
5721
|
-
var
|
|
5722
|
-
if (!
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
};
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
6160
|
+
(function() {
|
|
6161
|
+
var root = document.getElementById('${id}');
|
|
6162
|
+
if (!root) return;
|
|
6163
|
+
|
|
6164
|
+
// Tab switching
|
|
6165
|
+
root.querySelectorAll('.devtools-tab').forEach(function(tab) {
|
|
6166
|
+
tab.addEventListener('click', function() {
|
|
6167
|
+
root.querySelectorAll('.devtools-tab').forEach(function(t) { t.classList.remove('active'); });
|
|
6168
|
+
root.querySelectorAll('.devtools-pane').forEach(function(p) { p.classList.remove('active'); });
|
|
6169
|
+
tab.classList.add('active');
|
|
6170
|
+
var pane = root.querySelector('[data-pane="' + tab.dataset.tab + '"]');
|
|
6171
|
+
if (pane) pane.classList.add('active');
|
|
6172
|
+
});
|
|
6173
|
+
});
|
|
6174
|
+
|
|
6175
|
+
// Tree expand/collapse
|
|
6176
|
+
root.querySelectorAll('.tree-row.expandable').forEach(function(row) {
|
|
6177
|
+
row.addEventListener('click', function() {
|
|
6178
|
+
row.parentElement.classList.toggle('open');
|
|
6179
|
+
});
|
|
6180
|
+
});
|
|
6181
|
+
|
|
6182
|
+
// Dock position switching
|
|
6183
|
+
root.querySelectorAll('[data-dock]').forEach(function(btn) {
|
|
6184
|
+
btn.addEventListener('click', function() {
|
|
6185
|
+
root.dataset.position = btn.dataset.dock;
|
|
6186
|
+
});
|
|
6187
|
+
});
|
|
6188
|
+
|
|
6189
|
+
// Resize functionality
|
|
6190
|
+
var resize = root.querySelector('.devtools-resize');
|
|
6191
|
+
var panel = root.querySelector('.devtools-panel');
|
|
6192
|
+
if (resize && panel) {
|
|
6193
|
+
var isResizing = false;
|
|
6194
|
+
var startY, startX, startHeight, startWidth;
|
|
6195
|
+
|
|
6196
|
+
resize.addEventListener('mousedown', function(e) {
|
|
6197
|
+
isResizing = true;
|
|
6198
|
+
startY = e.clientY;
|
|
6199
|
+
startX = e.clientX;
|
|
6200
|
+
startHeight = panel.offsetHeight;
|
|
6201
|
+
startWidth = panel.offsetWidth;
|
|
6202
|
+
document.body.style.cursor = root.dataset.position === 'right' ? 'ew-resize' : 'ns-resize';
|
|
6203
|
+
e.preventDefault();
|
|
6204
|
+
});
|
|
6205
|
+
|
|
6206
|
+
document.addEventListener('mousemove', function(e) {
|
|
6207
|
+
if (!isResizing) return;
|
|
6208
|
+
if (root.dataset.position === 'bottom') {
|
|
6209
|
+
var newHeight = startHeight - (e.clientY - startY);
|
|
6210
|
+
if (newHeight > 100 && newHeight < window.innerHeight * 0.8) {
|
|
6211
|
+
panel.style.height = newHeight + 'px';
|
|
6212
|
+
}
|
|
6213
|
+
} else if (root.dataset.position === 'right') {
|
|
6214
|
+
var newWidth = startWidth - (e.clientX - startX);
|
|
6215
|
+
if (newWidth > 200 && newWidth < window.innerWidth * 0.6) {
|
|
6216
|
+
panel.style.width = newWidth + 'px';
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
|
|
6221
|
+
document.addEventListener('mouseup', function() {
|
|
6222
|
+
isResizing = false;
|
|
6223
|
+
document.body.style.cursor = '';
|
|
6224
|
+
});
|
|
6225
|
+
}
|
|
5750
6226
|
})();`;
|
|
5751
6227
|
}
|
|
5752
6228
|
function escapeHtml(str) {
|
|
5753
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6229
|
+
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5754
6230
|
}
|
|
5755
6231
|
|
|
5756
6232
|
// src/debug/index.ts
|