agentgui 1.0.823 → 1.0.824
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/CHANGELOG.md +6 -0
- package/lib/jsonl-watcher.js +13 -64
- package/package.json +2 -1
- package/static/index.html +3 -0
- package/static/js/dialogs-types.js +111 -0
- package/static/js/dialogs.js +53 -267
- package/static/js/image-loader-element.js +76 -0
- package/static/js/image-loader.js +0 -74
- package/static/js/syntax-highlighter-render.js +72 -0
- package/static/js/syntax-highlighter.js +0 -84
- package/static/js/event-filter.js +0 -118
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## 2026-04-11
|
|
2
|
+
- refactor: replace jsonl-watcher.js with @lanmower/cc-tail npm package (subclass CCTailWatcher, override _line() to route via JsonlParser); remove custom file-watching logic
|
|
3
|
+
- refactor: split syntax-highlighter.js (216L) into syntax-highlighter.js (132L) + syntax-highlighter-render.js (73L, highlight/createHighlightedElement/highlightElement via prototype extension)
|
|
4
|
+
- refactor: split dialogs.js (268L) into dialogs.js (54L, helpers + window._dialogCore) + dialogs-types.js (112L, window.UIDialog via window._dialogCore)
|
|
5
|
+
- chore: add @lanmower/cc-tail dependency to package.json
|
|
6
|
+
|
|
1
7
|
## [Unreleased]
|
|
2
8
|
|
|
3
9
|
### Refactor
|
package/lib/jsonl-watcher.js
CHANGED
|
@@ -1,36 +1,26 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import
|
|
3
|
-
import os from 'os';
|
|
2
|
+
import { JsonlWatcher as CCTailWatcher } from '@lanmower/cc-tail';
|
|
4
3
|
import { JsonlParser } from './jsonl-parser.js';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
const DEBOUNCE_MS = 16;
|
|
8
|
-
|
|
9
|
-
export class JsonlWatcher {
|
|
5
|
+
export class JsonlWatcher extends CCTailWatcher {
|
|
10
6
|
constructor({ broadcastSync, queries, ownedSessionIds }) {
|
|
7
|
+
super();
|
|
11
8
|
this._parser = new JsonlParser({ broadcastSync, queries, ownedSessionIds });
|
|
12
|
-
this._tails = new Map();
|
|
13
|
-
this._timers = new Map();
|
|
14
|
-
this._watcher = null;
|
|
15
9
|
}
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
this._watcher.on('error', (e) => console.error('[JsonlWatcher]', e.message));
|
|
27
|
-
} catch (e) { console.error('[JsonlWatcher] start failed:', e.message); }
|
|
11
|
+
_line(line) {
|
|
12
|
+
line = line.trim();
|
|
13
|
+
if (!line) return;
|
|
14
|
+
let e;
|
|
15
|
+
try { e = JSON.parse(line); } catch (_) { return; }
|
|
16
|
+
if (!e || !e.sessionId) return;
|
|
17
|
+
const cid = this._parser._conv(e.sessionId, e);
|
|
18
|
+
if (cid) this._parser._route(cid, e.sessionId, e);
|
|
28
19
|
}
|
|
29
20
|
|
|
30
21
|
stop() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
for (const t of this._timers.values()) clearTimeout(t);
|
|
22
|
+
super.stop();
|
|
23
|
+
this._parser.endAllStreaming();
|
|
34
24
|
}
|
|
35
25
|
|
|
36
26
|
removeConversation(conversationId) {
|
|
@@ -54,45 +44,4 @@ export class JsonlWatcher {
|
|
|
54
44
|
this._timers.clear();
|
|
55
45
|
this._parser.clear();
|
|
56
46
|
}
|
|
57
|
-
|
|
58
|
-
_scanDir(dir, depth) {
|
|
59
|
-
if (depth > 4) return;
|
|
60
|
-
try {
|
|
61
|
-
for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
62
|
-
const fp = path.join(dir, d.name);
|
|
63
|
-
if (d.isFile() && d.name.endsWith('.jsonl')) this._debounce(fp);
|
|
64
|
-
else if (d.isDirectory()) this._scanDir(fp, depth + 1);
|
|
65
|
-
}
|
|
66
|
-
} catch (_) {}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
_debounce(fp) {
|
|
70
|
-
const t = this._timers.get(fp);
|
|
71
|
-
if (t) clearTimeout(t);
|
|
72
|
-
this._timers.set(fp, setTimeout(() => { this._timers.delete(fp); this._read(fp); }, DEBOUNCE_MS));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_read(fp) {
|
|
76
|
-
let s = this._tails.get(fp);
|
|
77
|
-
if (!s) { s = { fd: null, offset: 0, partial: '' }; this._tails.set(fp, s); }
|
|
78
|
-
try {
|
|
79
|
-
if (s.fd === null) s.fd = fs.openSync(fp, 'r');
|
|
80
|
-
const stat = fs.fstatSync(s.fd);
|
|
81
|
-
if (stat.size <= s.offset) return;
|
|
82
|
-
const buf = Buffer.allocUnsafe(stat.size - s.offset);
|
|
83
|
-
const n = fs.readSync(s.fd, buf, 0, buf.length, s.offset);
|
|
84
|
-
s.offset += n;
|
|
85
|
-
const text = s.partial + buf.toString('utf8', 0, n);
|
|
86
|
-
const t0 = process.hrtime.bigint();
|
|
87
|
-
const lines = []; let start = 0, idx;
|
|
88
|
-
while ((idx = text.indexOf('\n', start)) !== -1) { lines.push(text.slice(start, idx)); start = idx + 1; }
|
|
89
|
-
s.partial = text.slice(start);
|
|
90
|
-
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
|
|
91
|
-
if (ms > 5) console.warn(`[JsonlWatcher] hot path ${ms.toFixed(1)}ms (${lines.length} lines)`);
|
|
92
|
-
for (const l of lines) this._parser._line(fp, l);
|
|
93
|
-
} catch (e) {
|
|
94
|
-
if (e.code !== 'ENOENT') console.error('[JsonlWatcher] read error:', e.message);
|
|
95
|
-
if (s.fd !== null) { try { fs.closeSync(s.fd); } catch (_) {} s.fd = null; }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
47
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.824",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "electron/main.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@google/gemini-cli": "latest",
|
|
29
29
|
"@huggingface/transformers": "^3.8.1",
|
|
30
30
|
"@kilocode/cli": "latest",
|
|
31
|
+
"@lanmower/cc-tail": "^1.0.2",
|
|
31
32
|
"audio-decode": "^2.2.3",
|
|
32
33
|
"better-sqlite3": "^12.6.2",
|
|
33
34
|
"busboy": "^1.6.0",
|
package/static/index.html
CHANGED
|
@@ -282,6 +282,7 @@
|
|
|
282
282
|
<script defer src="/gm/js/event-processor.js"></script>
|
|
283
283
|
<script defer src="/gm/js/streaming-renderer.js"></script>
|
|
284
284
|
<script defer src="/gm/js/image-loader.js"></script>
|
|
285
|
+
<script defer src="/gm/js/image-loader-element.js"></script>
|
|
285
286
|
<script defer src="/gm/lib/webjsx.js"></script>
|
|
286
287
|
<script defer src="/gm/lib/xstate.umd.min.js"></script>
|
|
287
288
|
<script defer src="/gm/js/ws-machine.js"></script>
|
|
@@ -302,7 +303,9 @@
|
|
|
302
303
|
<script defer src="/gm/js/websocket-manager.js"></script>
|
|
303
304
|
<script defer src="/gm/js/ws-client.js"></script>
|
|
304
305
|
<script defer src="/gm/js/syntax-highlighter.js"></script>
|
|
306
|
+
<script defer src="/gm/js/syntax-highlighter-render.js"></script>
|
|
305
307
|
<script defer src="/gm/js/dialogs.js"></script>
|
|
308
|
+
<script defer src="/gm/js/dialogs-types.js"></script>
|
|
306
309
|
<script defer src="/gm/js/ui-components.js"></script>
|
|
307
310
|
<script defer src="/gm/js/ui-components-rendering.js"></script>
|
|
308
311
|
<script defer src="/gm/js/state-barrier.js"></script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var c = window._dialogCore;
|
|
3
|
+
|
|
4
|
+
window.UIDialog = {
|
|
5
|
+
alert: function(message, title) {
|
|
6
|
+
return new Promise(function(resolve) {
|
|
7
|
+
var overlay = c.createOverlay();
|
|
8
|
+
var dialog = document.createElement('div');
|
|
9
|
+
dialog.className = 'dialog-container';
|
|
10
|
+
dialog.innerHTML =
|
|
11
|
+
'<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Alert') + '</h3></div>' +
|
|
12
|
+
'<div class="dialog-body"><p class="dialog-message">' + c.escapeHtml(message) + '</p></div>' +
|
|
13
|
+
'<div class="dialog-footer"><button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button></div></div>';
|
|
14
|
+
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
15
|
+
okBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
|
|
16
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
|
|
17
|
+
document.addEventListener('keydown', function handler(e) {
|
|
18
|
+
if (e.key === 'Escape' || e.key === 'Enter') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(true); }
|
|
19
|
+
});
|
|
20
|
+
c.showDialog(dialog, overlay);
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
confirm: function(message, title) {
|
|
25
|
+
return new Promise(function(resolve) {
|
|
26
|
+
var overlay = c.createOverlay();
|
|
27
|
+
var dialog = document.createElement('div');
|
|
28
|
+
dialog.className = 'dialog-container';
|
|
29
|
+
dialog.innerHTML =
|
|
30
|
+
'<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Confirm') + '</h3></div>' +
|
|
31
|
+
'<div class="dialog-body"><p class="dialog-message">' + c.escapeHtml(message).replace(/\n/g, '<br>') + '</p></div>' +
|
|
32
|
+
'<div class="dialog-footer"><button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
33
|
+
'<button class="dialog-btn dialog-btn-primary dialog-btn-danger" data-action="confirm">Confirm</button></div></div>';
|
|
34
|
+
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
35
|
+
var confirmBtn = dialog.querySelector('[data-action="confirm"]');
|
|
36
|
+
cancelBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(false); });
|
|
37
|
+
confirmBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
|
|
38
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(false); });
|
|
39
|
+
document.addEventListener('keydown', function handler(e) {
|
|
40
|
+
if (e.key === 'Escape') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(false); }
|
|
41
|
+
else if (e.key === 'Enter') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(true); }
|
|
42
|
+
});
|
|
43
|
+
c.showDialog(dialog, overlay);
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
prompt: function(message, defaultValue, title) {
|
|
48
|
+
return new Promise(function(resolve) {
|
|
49
|
+
var overlay = c.createOverlay();
|
|
50
|
+
var dialog = document.createElement('div');
|
|
51
|
+
dialog.className = 'dialog-container';
|
|
52
|
+
dialog.innerHTML =
|
|
53
|
+
'<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Input') + '</h3></div>' +
|
|
54
|
+
'<div class="dialog-body"><label class="dialog-label">' + c.escapeHtml(message) + '</label>' +
|
|
55
|
+
'<input type="text" class="dialog-input" value="' + c.escapeHtml(defaultValue || '') + '"></div>' +
|
|
56
|
+
'<div class="dialog-footer"><button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
57
|
+
'<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button></div></div>';
|
|
58
|
+
var input = dialog.querySelector('.dialog-input');
|
|
59
|
+
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
60
|
+
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
61
|
+
cancelBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(null); });
|
|
62
|
+
okBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(input.value); });
|
|
63
|
+
input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { c.closeDialog(dialog, overlay); resolve(input.value); } });
|
|
64
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(null); });
|
|
65
|
+
document.addEventListener('keydown', function handler(e) {
|
|
66
|
+
if (e.key === 'Escape') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(null); }
|
|
67
|
+
});
|
|
68
|
+
c.showDialog(dialog, overlay);
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
showProgress: function(config) {
|
|
73
|
+
var overlay = c.createOverlay();
|
|
74
|
+
var dialog = document.createElement('div');
|
|
75
|
+
dialog.className = 'dialog-container';
|
|
76
|
+
dialog.innerHTML =
|
|
77
|
+
'<div class="dialog-box dialog-box-progress"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(config.title || 'Please wait') + '</h3></div>' +
|
|
78
|
+
'<div class="dialog-body"><p class="dialog-message progress-message">' + c.escapeHtml(config.message || 'Loading...') + '</p>' +
|
|
79
|
+
'<div class="dialog-progress-bar"><div class="dialog-progress-fill" style="width: 0%"></div></div>' +
|
|
80
|
+
'<p class="dialog-progress-percent">0%</p></div></div>';
|
|
81
|
+
c.showDialog(dialog, overlay);
|
|
82
|
+
var progressFill = dialog.querySelector('.dialog-progress-fill');
|
|
83
|
+
var progressPercent = dialog.querySelector('.dialog-progress-percent');
|
|
84
|
+
var progressMessage = dialog.querySelector('.progress-message');
|
|
85
|
+
return {
|
|
86
|
+
update: function(percent, message) {
|
|
87
|
+
progressFill.style.width = percent + '%';
|
|
88
|
+
progressPercent.textContent = Math.round(percent) + '%';
|
|
89
|
+
if (message) progressMessage.textContent = message;
|
|
90
|
+
},
|
|
91
|
+
close: function() { c.closeDialog(dialog, overlay); }
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
showToast: function(message, type, duration) {
|
|
96
|
+
var existing = document.querySelector('.toast-notification');
|
|
97
|
+
if (existing) existing.remove();
|
|
98
|
+
var toast = document.createElement('div');
|
|
99
|
+
toast.className = 'toast-notification toast-' + (type || 'info');
|
|
100
|
+
toast.innerHTML = '<span class="toast-message">' + c.escapeHtml(message) + '</span>';
|
|
101
|
+
document.body.appendChild(toast);
|
|
102
|
+
requestAnimationFrame(function() { toast.classList.add('visible'); });
|
|
103
|
+
setTimeout(function() {
|
|
104
|
+
toast.classList.remove('visible');
|
|
105
|
+
setTimeout(function() { if (toast.parentNode) toast.remove(); }, 300);
|
|
106
|
+
}, duration || 3000);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
closeAll: c.closeAllDialogs
|
|
110
|
+
};
|
|
111
|
+
})();
|
package/static/js/dialogs.js
CHANGED
|
@@ -1,267 +1,53 @@
|
|
|
1
|
-
(function() {
|
|
2
|
-
var activeDialogs = [];
|
|
3
|
-
var dialogZIndex = 10000;
|
|
4
|
-
|
|
5
|
-
function escapeHtml(text) {
|
|
6
|
-
if (typeof window._escHtml === 'function') return window._escHtml(text);
|
|
7
|
-
var d = document.createElement('div'); d.textContent = text; return d.innerHTML;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function createOverlay() {
|
|
11
|
-
var overlay = document.createElement('div');
|
|
12
|
-
overlay.className = 'dialog-overlay';
|
|
13
|
-
overlay.innerHTML = '<div class="dialog-backdrop"></div>';
|
|
14
|
-
return overlay;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function showDialog(dialog, overlay) {
|
|
18
|
-
dialogZIndex++;
|
|
19
|
-
if (overlay) {
|
|
20
|
-
overlay.style.zIndex = dialogZIndex;
|
|
21
|
-
document.body.appendChild(overlay);
|
|
22
|
-
}
|
|
23
|
-
dialog.style.zIndex = dialogZIndex + 1;
|
|
24
|
-
document.body.appendChild(dialog);
|
|
25
|
-
activeDialogs.push({ dialog: dialog, overlay: overlay });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
window.UIDialog = {
|
|
58
|
-
alert: function(message, title) {
|
|
59
|
-
return new Promise(function(resolve) {
|
|
60
|
-
var overlay = createOverlay();
|
|
61
|
-
var dialog = document.createElement('div');
|
|
62
|
-
dialog.className = 'dialog-container';
|
|
63
|
-
dialog.innerHTML =
|
|
64
|
-
'<div class="dialog-box">' +
|
|
65
|
-
'<div class="dialog-header">' +
|
|
66
|
-
'<h3 class="dialog-title">' + escapeHtml(title || 'Alert') + '</h3>' +
|
|
67
|
-
'</div>' +
|
|
68
|
-
'<div class="dialog-body">' +
|
|
69
|
-
'<p class="dialog-message">' + escapeHtml(message) + '</p>' +
|
|
70
|
-
'</div>' +
|
|
71
|
-
'<div class="dialog-footer">' +
|
|
72
|
-
'<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
|
|
73
|
-
'</div>' +
|
|
74
|
-
'</div>';
|
|
75
|
-
|
|
76
|
-
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
77
|
-
okBtn.addEventListener('click', function() {
|
|
78
|
-
closeDialog(dialog, overlay);
|
|
79
|
-
resolve(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
83
|
-
closeDialog(dialog, overlay);
|
|
84
|
-
resolve(true);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
document.addEventListener('keydown', function handler(e) {
|
|
88
|
-
if (e.key === 'Escape' || e.key === 'Enter') {
|
|
89
|
-
document.removeEventListener('keydown', handler);
|
|
90
|
-
closeDialog(dialog, overlay);
|
|
91
|
-
resolve(true);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
showDialog(dialog, overlay);
|
|
96
|
-
});
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
confirm: function(message, title) {
|
|
100
|
-
return new Promise(function(resolve) {
|
|
101
|
-
var overlay = createOverlay();
|
|
102
|
-
var dialog = document.createElement('div');
|
|
103
|
-
dialog.className = 'dialog-container';
|
|
104
|
-
dialog.innerHTML =
|
|
105
|
-
'<div class="dialog-box">' +
|
|
106
|
-
'<div class="dialog-header">' +
|
|
107
|
-
'<h3 class="dialog-title">' + escapeHtml(title || 'Confirm') + '</h3>' +
|
|
108
|
-
'</div>' +
|
|
109
|
-
'<div class="dialog-body">' +
|
|
110
|
-
'<p class="dialog-message">' + escapeHtml(message).replace(/\n/g, '<br>') + '</p>' +
|
|
111
|
-
'</div>' +
|
|
112
|
-
'<div class="dialog-footer">' +
|
|
113
|
-
'<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
114
|
-
'<button class="dialog-btn dialog-btn-primary dialog-btn-danger" data-action="confirm">Confirm</button>' +
|
|
115
|
-
'</div>' +
|
|
116
|
-
'</div>';
|
|
117
|
-
|
|
118
|
-
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
119
|
-
var confirmBtn = dialog.querySelector('[data-action="confirm"]');
|
|
120
|
-
|
|
121
|
-
cancelBtn.addEventListener('click', function() {
|
|
122
|
-
closeDialog(dialog, overlay);
|
|
123
|
-
resolve(false);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
confirmBtn.addEventListener('click', function() {
|
|
127
|
-
closeDialog(dialog, overlay);
|
|
128
|
-
resolve(true);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
132
|
-
closeDialog(dialog, overlay);
|
|
133
|
-
resolve(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
document.addEventListener('keydown', function handler(e) {
|
|
137
|
-
if (e.key === 'Escape') {
|
|
138
|
-
document.removeEventListener('keydown', handler);
|
|
139
|
-
closeDialog(dialog, overlay);
|
|
140
|
-
resolve(false);
|
|
141
|
-
} else if (e.key === 'Enter') {
|
|
142
|
-
document.removeEventListener('keydown', handler);
|
|
143
|
-
closeDialog(dialog, overlay);
|
|
144
|
-
resolve(true);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
showDialog(dialog, overlay);
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
prompt: function(message, defaultValue, title) {
|
|
153
|
-
return new Promise(function(resolve) {
|
|
154
|
-
var overlay = createOverlay();
|
|
155
|
-
var dialog = document.createElement('div');
|
|
156
|
-
dialog.className = 'dialog-container';
|
|
157
|
-
dialog.innerHTML =
|
|
158
|
-
'<div class="dialog-box">' +
|
|
159
|
-
'<div class="dialog-header">' +
|
|
160
|
-
'<h3 class="dialog-title">' + escapeHtml(title || 'Input') + '</h3>' +
|
|
161
|
-
'</div>' +
|
|
162
|
-
'<div class="dialog-body">' +
|
|
163
|
-
'<label class="dialog-label">' + escapeHtml(message) + '</label>' +
|
|
164
|
-
'<input type="text" class="dialog-input" value="' + escapeHtml(defaultValue || '') + '">' +
|
|
165
|
-
'</div>' +
|
|
166
|
-
'<div class="dialog-footer">' +
|
|
167
|
-
'<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
168
|
-
'<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
|
|
169
|
-
'</div>' +
|
|
170
|
-
'</div>';
|
|
171
|
-
|
|
172
|
-
var input = dialog.querySelector('.dialog-input');
|
|
173
|
-
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
174
|
-
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
175
|
-
|
|
176
|
-
cancelBtn.addEventListener('click', function() {
|
|
177
|
-
closeDialog(dialog, overlay);
|
|
178
|
-
resolve(null);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
okBtn.addEventListener('click', function() {
|
|
182
|
-
closeDialog(dialog, overlay);
|
|
183
|
-
resolve(input.value);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
input.addEventListener('keydown', function(e) {
|
|
187
|
-
if (e.key === 'Enter') {
|
|
188
|
-
closeDialog(dialog, overlay);
|
|
189
|
-
resolve(input.value);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
194
|
-
closeDialog(dialog, overlay);
|
|
195
|
-
resolve(null);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
document.addEventListener('keydown', function handler(e) {
|
|
199
|
-
if (e.key === 'Escape') {
|
|
200
|
-
document.removeEventListener('keydown', handler);
|
|
201
|
-
closeDialog(dialog, overlay);
|
|
202
|
-
resolve(null);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
showDialog(dialog, overlay);
|
|
207
|
-
});
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
showProgress: function(config) {
|
|
211
|
-
var overlay = createOverlay();
|
|
212
|
-
var dialog = document.createElement('div');
|
|
213
|
-
dialog.className = 'dialog-container';
|
|
214
|
-
dialog.innerHTML =
|
|
215
|
-
'<div class="dialog-box dialog-box-progress">' +
|
|
216
|
-
'<div class="dialog-header">' +
|
|
217
|
-
'<h3 class="dialog-title">' + escapeHtml(config.title || 'Please wait') + '</h3>' +
|
|
218
|
-
'</div>' +
|
|
219
|
-
'<div class="dialog-body">' +
|
|
220
|
-
'<p class="dialog-message progress-message">' + escapeHtml(config.message || 'Loading...') + '</p>' +
|
|
221
|
-
'<div class="dialog-progress-bar">' +
|
|
222
|
-
'<div class="dialog-progress-fill" style="width: 0%"></div>' +
|
|
223
|
-
'</div>' +
|
|
224
|
-
'<p class="dialog-progress-percent">0%</p>' +
|
|
225
|
-
'</div>' +
|
|
226
|
-
'</div>';
|
|
227
|
-
|
|
228
|
-
showDialog(dialog, overlay);
|
|
229
|
-
|
|
230
|
-
var progressFill = dialog.querySelector('.dialog-progress-fill');
|
|
231
|
-
var progressPercent = dialog.querySelector('.dialog-progress-percent');
|
|
232
|
-
var progressMessage = dialog.querySelector('.progress-message');
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
update: function(percent, message) {
|
|
236
|
-
progressFill.style.width = percent + '%';
|
|
237
|
-
progressPercent.textContent = Math.round(percent) + '%';
|
|
238
|
-
if (message) progressMessage.textContent = message;
|
|
239
|
-
},
|
|
240
|
-
close: function() {
|
|
241
|
-
closeDialog(dialog, overlay);
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
showToast: function(message, type, duration) {
|
|
247
|
-
var existing = document.querySelector('.toast-notification');
|
|
248
|
-
if (existing) existing.remove();
|
|
249
|
-
|
|
250
|
-
var toast = document.createElement('div');
|
|
251
|
-
toast.className = 'toast-notification toast-' + (type || 'info');
|
|
252
|
-
toast.innerHTML = '<span class="toast-message">' + escapeHtml(message) + '</span>';
|
|
253
|
-
document.body.appendChild(toast);
|
|
254
|
-
|
|
255
|
-
requestAnimationFrame(function() {
|
|
256
|
-
toast.classList.add('visible');
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
setTimeout(function() {
|
|
260
|
-
toast.classList.remove('visible');
|
|
261
|
-
setTimeout(function() { if (toast.parentNode) toast.remove(); }, 300);
|
|
262
|
-
}, duration || 3000);
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
closeAll: closeAllDialogs
|
|
266
|
-
};
|
|
267
|
-
})();
|
|
1
|
+
(function() {
|
|
2
|
+
var activeDialogs = [];
|
|
3
|
+
var dialogZIndex = 10000;
|
|
4
|
+
|
|
5
|
+
function escapeHtml(text) {
|
|
6
|
+
if (typeof window._escHtml === 'function') return window._escHtml(text);
|
|
7
|
+
var d = document.createElement('div'); d.textContent = text; return d.innerHTML;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createOverlay() {
|
|
11
|
+
var overlay = document.createElement('div');
|
|
12
|
+
overlay.className = 'dialog-overlay';
|
|
13
|
+
overlay.innerHTML = '<div class="dialog-backdrop"></div>';
|
|
14
|
+
return overlay;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function showDialog(dialog, overlay) {
|
|
18
|
+
dialogZIndex++;
|
|
19
|
+
if (overlay) {
|
|
20
|
+
overlay.style.zIndex = dialogZIndex;
|
|
21
|
+
document.body.appendChild(overlay);
|
|
22
|
+
}
|
|
23
|
+
dialog.style.zIndex = dialogZIndex + 1;
|
|
24
|
+
document.body.appendChild(dialog);
|
|
25
|
+
activeDialogs.push({ dialog: dialog, overlay: overlay });
|
|
26
|
+
requestAnimationFrame(function() {
|
|
27
|
+
dialog.classList.add('visible');
|
|
28
|
+
if (overlay) overlay.classList.add('visible');
|
|
29
|
+
var input = dialog.querySelector('input, textarea');
|
|
30
|
+
if (input) input.focus();
|
|
31
|
+
else {
|
|
32
|
+
var btn = dialog.querySelector('.dialog-btn-primary');
|
|
33
|
+
if (btn) btn.focus();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function closeDialog(dialog, overlay) {
|
|
39
|
+
dialog.classList.remove('visible');
|
|
40
|
+
if (overlay) overlay.classList.remove('visible');
|
|
41
|
+
setTimeout(function() {
|
|
42
|
+
if (dialog.parentNode) dialog.remove();
|
|
43
|
+
if (overlay && overlay.parentNode) overlay.remove();
|
|
44
|
+
}, 200);
|
|
45
|
+
activeDialogs = activeDialogs.filter(function(d) { return d.dialog !== dialog; });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function closeAllDialogs() {
|
|
49
|
+
activeDialogs.forEach(function(d) { closeDialog(d.dialog, d.overlay); });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
window._dialogCore = { escapeHtml: escapeHtml, createOverlay: createOverlay, showDialog: showDialog, closeDialog: closeDialog, closeAllDialogs: closeAllDialogs };
|
|
53
|
+
})();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
Object.assign(ImageLoader.prototype, {
|
|
3
|
+
createImageElement(imagePath, options = {}) {
|
|
4
|
+
const container = document.createElement('div');
|
|
5
|
+
container.className = 'image-container';
|
|
6
|
+
container.dataset.imagePath = imagePath;
|
|
7
|
+
container.style.cssText = `
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
gap: 0.5rem;
|
|
11
|
+
padding: 0.75rem;
|
|
12
|
+
border-radius: 0.375rem;
|
|
13
|
+
background: var(--color-bg-secondary);
|
|
14
|
+
border: 1px solid var(--color-border);
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const placeholder = document.createElement('div');
|
|
18
|
+
placeholder.className = 'image-placeholder';
|
|
19
|
+
placeholder.style.cssText = `
|
|
20
|
+
background: linear-gradient(90deg, var(--color-bg-tertiary) 25%, var(--color-bg-secondary) 50%, var(--color-bg-tertiary) 75%);
|
|
21
|
+
background-size: 200% 100%;
|
|
22
|
+
animation: loading 1.5s infinite;
|
|
23
|
+
border-radius: 0.375rem;
|
|
24
|
+
aspect-ratio: 16/9;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
color: var(--color-text-secondary);
|
|
29
|
+
font-size: 0.875rem;
|
|
30
|
+
`;
|
|
31
|
+
placeholder.innerHTML = 'Loading image...';
|
|
32
|
+
placeholder.dataset.path = imagePath;
|
|
33
|
+
|
|
34
|
+
const img = document.createElement('img');
|
|
35
|
+
img.className = 'lazy-image';
|
|
36
|
+
img.alt = imagePath;
|
|
37
|
+
img.style.cssText = `
|
|
38
|
+
max-width: 100%;
|
|
39
|
+
max-height: ${this.config.maxImageDisplaySize};
|
|
40
|
+
border-radius: 0.375rem;
|
|
41
|
+
display: none;
|
|
42
|
+
`;
|
|
43
|
+
img.dataset.src = imagePath;
|
|
44
|
+
|
|
45
|
+
const caption = document.createElement('div');
|
|
46
|
+
caption.className = 'image-caption';
|
|
47
|
+
caption.style.cssText = `
|
|
48
|
+
font-size: 0.75rem;
|
|
49
|
+
color: var(--color-text-secondary);
|
|
50
|
+
word-break: break-all;
|
|
51
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
52
|
+
`;
|
|
53
|
+
caption.textContent = imagePath;
|
|
54
|
+
|
|
55
|
+
container.appendChild(placeholder);
|
|
56
|
+
container.appendChild(img);
|
|
57
|
+
container.appendChild(caption);
|
|
58
|
+
|
|
59
|
+
img.addEventListener('load', () => {
|
|
60
|
+
placeholder.style.display = 'none';
|
|
61
|
+
img.style.display = 'block';
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
img.addEventListener('error', () => {
|
|
65
|
+
placeholder.textContent = 'Failed to load image';
|
|
66
|
+
placeholder.style.background = 'var(--color-bg-error)';
|
|
67
|
+
placeholder.style.color = 'var(--color-text-error)';
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (this.intersectionObserver) {
|
|
71
|
+
this.intersectionObserver.observe(container);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return container;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
@@ -89,80 +89,6 @@ class ImageLoader {
|
|
|
89
89
|
return images;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
createImageElement(imagePath, options = {}) {
|
|
93
|
-
const container = document.createElement('div');
|
|
94
|
-
container.className = 'image-container';
|
|
95
|
-
container.dataset.imagePath = imagePath;
|
|
96
|
-
container.style.cssText = `
|
|
97
|
-
display: flex;
|
|
98
|
-
flex-direction: column;
|
|
99
|
-
gap: 0.5rem;
|
|
100
|
-
padding: 0.75rem;
|
|
101
|
-
border-radius: 0.375rem;
|
|
102
|
-
background: var(--color-bg-secondary);
|
|
103
|
-
border: 1px solid var(--color-border);
|
|
104
|
-
`;
|
|
105
|
-
|
|
106
|
-
const placeholder = document.createElement('div');
|
|
107
|
-
placeholder.className = 'image-placeholder';
|
|
108
|
-
placeholder.style.cssText = `
|
|
109
|
-
background: linear-gradient(90deg, var(--color-bg-tertiary) 25%, var(--color-bg-secondary) 50%, var(--color-bg-tertiary) 75%);
|
|
110
|
-
background-size: 200% 100%;
|
|
111
|
-
animation: loading 1.5s infinite;
|
|
112
|
-
border-radius: 0.375rem;
|
|
113
|
-
aspect-ratio: 16/9;
|
|
114
|
-
display: flex;
|
|
115
|
-
align-items: center;
|
|
116
|
-
justify-content: center;
|
|
117
|
-
color: var(--color-text-secondary);
|
|
118
|
-
font-size: 0.875rem;
|
|
119
|
-
`;
|
|
120
|
-
placeholder.innerHTML = 'Loading image...';
|
|
121
|
-
placeholder.dataset.path = imagePath;
|
|
122
|
-
|
|
123
|
-
const img = document.createElement('img');
|
|
124
|
-
img.className = 'lazy-image';
|
|
125
|
-
img.alt = imagePath;
|
|
126
|
-
img.style.cssText = `
|
|
127
|
-
max-width: 100%;
|
|
128
|
-
max-height: ${this.config.maxImageDisplaySize};
|
|
129
|
-
border-radius: 0.375rem;
|
|
130
|
-
display: none;
|
|
131
|
-
`;
|
|
132
|
-
img.dataset.src = imagePath;
|
|
133
|
-
|
|
134
|
-
const caption = document.createElement('div');
|
|
135
|
-
caption.className = 'image-caption';
|
|
136
|
-
caption.style.cssText = `
|
|
137
|
-
font-size: 0.75rem;
|
|
138
|
-
color: var(--color-text-secondary);
|
|
139
|
-
word-break: break-all;
|
|
140
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
141
|
-
`;
|
|
142
|
-
caption.textContent = imagePath;
|
|
143
|
-
|
|
144
|
-
container.appendChild(placeholder);
|
|
145
|
-
container.appendChild(img);
|
|
146
|
-
container.appendChild(caption);
|
|
147
|
-
|
|
148
|
-
img.addEventListener('load', () => {
|
|
149
|
-
placeholder.style.display = 'none';
|
|
150
|
-
img.style.display = 'block';
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
img.addEventListener('error', () => {
|
|
154
|
-
placeholder.textContent = 'Failed to load image';
|
|
155
|
-
placeholder.style.background = 'var(--color-bg-error)';
|
|
156
|
-
placeholder.style.color = 'var(--color-text-error)';
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (this.intersectionObserver) {
|
|
160
|
-
this.intersectionObserver.observe(container);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return container;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
92
|
initIntersectionObserver() {
|
|
167
93
|
this.intersectionObserver = new IntersectionObserver(
|
|
168
94
|
(entries) => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Object.assign(SyntaxHighlighter.prototype, {
|
|
2
|
+
async highlight(code, language = 'plaintext') {
|
|
3
|
+
if (!code) return '';
|
|
4
|
+
if (this.config.lazyLoad) {
|
|
5
|
+
try {
|
|
6
|
+
await this.ensureLoaded();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.warn('Prism loading failed, returning unformatted code');
|
|
9
|
+
return this.escapeHtml(code);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const cacheKey = `${language}:${code}`;
|
|
13
|
+
if (this.config.enableCache && this.highlightCache.has(cacheKey)) return this.highlightCache.get(cacheKey);
|
|
14
|
+
let highlighted;
|
|
15
|
+
try {
|
|
16
|
+
if (typeof Prism !== 'undefined' && Prism.languages[language]) {
|
|
17
|
+
highlighted = Prism.highlight(code, Prism.languages[language], language);
|
|
18
|
+
} else {
|
|
19
|
+
highlighted = this.escapeHtml(code);
|
|
20
|
+
}
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Highlight error:', error);
|
|
23
|
+
highlighted = this.escapeHtml(code);
|
|
24
|
+
}
|
|
25
|
+
if (this.config.enableCache) {
|
|
26
|
+
this.highlightCache.set(cacheKey, highlighted);
|
|
27
|
+
if (this.highlightCache.size > this.config.maxCacheSize) {
|
|
28
|
+
const firstKey = this.highlightCache.keys().next().value;
|
|
29
|
+
this.highlightCache.delete(firstKey);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return highlighted;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async createHighlightedElement(code, language = 'plaintext') {
|
|
36
|
+
const pre = document.createElement('pre');
|
|
37
|
+
const code_el = document.createElement('code');
|
|
38
|
+
code_el.className = `language-${language}`;
|
|
39
|
+
if (this.config.lazyLoad) {
|
|
40
|
+
try {
|
|
41
|
+
await this.ensureLoaded();
|
|
42
|
+
const highlighted = await this.highlight(code, language);
|
|
43
|
+
code_el.innerHTML = highlighted;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
code_el.textContent = code;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
code_el.textContent = code;
|
|
49
|
+
}
|
|
50
|
+
pre.appendChild(code_el);
|
|
51
|
+
return pre;
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async highlightElement(element) {
|
|
55
|
+
if (!element || !element.querySelector('code')) return;
|
|
56
|
+
if (this.config.lazyLoad) {
|
|
57
|
+
try {
|
|
58
|
+
await this.ensureLoaded();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn('Prism loading failed, skipping highlighting');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (typeof Prism !== 'undefined') {
|
|
65
|
+
try {
|
|
66
|
+
Prism.highlightElement(element.querySelector('code'));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Element highlighting error:', error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -81,90 +81,6 @@ class SyntaxHighlighter {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
async highlight(code, language = 'plaintext') {
|
|
85
|
-
if (!code) return '';
|
|
86
|
-
|
|
87
|
-
if (this.config.lazyLoad) {
|
|
88
|
-
try {
|
|
89
|
-
await this.ensureLoaded();
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.warn('Prism loading failed, returning unformatted code');
|
|
92
|
-
return this.escapeHtml(code);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const cacheKey = `${language}:${code}`;
|
|
97
|
-
if (this.config.enableCache && this.highlightCache.has(cacheKey)) {
|
|
98
|
-
return this.highlightCache.get(cacheKey);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let highlighted;
|
|
102
|
-
try {
|
|
103
|
-
if (typeof Prism !== 'undefined' && Prism.languages[language]) {
|
|
104
|
-
highlighted = Prism.highlight(code, Prism.languages[language], language);
|
|
105
|
-
} else {
|
|
106
|
-
highlighted = this.escapeHtml(code);
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error('Highlight error:', error);
|
|
110
|
-
highlighted = this.escapeHtml(code);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (this.config.enableCache) {
|
|
114
|
-
this.highlightCache.set(cacheKey, highlighted);
|
|
115
|
-
|
|
116
|
-
if (this.highlightCache.size > this.config.maxCacheSize) {
|
|
117
|
-
const firstKey = this.highlightCache.keys().next().value;
|
|
118
|
-
this.highlightCache.delete(firstKey);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return highlighted;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async createHighlightedElement(code, language = 'plaintext') {
|
|
126
|
-
const pre = document.createElement('pre');
|
|
127
|
-
const code_el = document.createElement('code');
|
|
128
|
-
|
|
129
|
-
code_el.className = `language-${language}`;
|
|
130
|
-
|
|
131
|
-
if (this.config.lazyLoad) {
|
|
132
|
-
try {
|
|
133
|
-
await this.ensureLoaded();
|
|
134
|
-
const highlighted = await this.highlight(code, language);
|
|
135
|
-
code_el.innerHTML = highlighted;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
code_el.textContent = code;
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
code_el.textContent = code;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
pre.appendChild(code_el);
|
|
144
|
-
return pre;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async highlightElement(element) {
|
|
148
|
-
if (!element || !element.querySelector('code')) return;
|
|
149
|
-
|
|
150
|
-
if (this.config.lazyLoad) {
|
|
151
|
-
try {
|
|
152
|
-
await this.ensureLoaded();
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.warn('Prism loading failed, skipping highlighting');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (typeof Prism !== 'undefined') {
|
|
160
|
-
try {
|
|
161
|
-
Prism.highlightElement(element.querySelector('code'));
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('Element highlighting error:', error);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
84
|
detectLanguage(code) {
|
|
169
85
|
if (!code) return 'plaintext';
|
|
170
86
|
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
class EventFilter {
|
|
2
|
-
constructor(renderer) {
|
|
3
|
-
this.renderer = renderer;
|
|
4
|
-
this.allEvents = [];
|
|
5
|
-
this.filteredEvents = [];
|
|
6
|
-
this.filterState = {
|
|
7
|
-
types: new Set(),
|
|
8
|
-
searchText: '',
|
|
9
|
-
startTime: null,
|
|
10
|
-
endTime: null,
|
|
11
|
-
isActive: false
|
|
12
|
-
};
|
|
13
|
-
this.replayState = {
|
|
14
|
-
isReplaying: false,
|
|
15
|
-
currentIndex: 0,
|
|
16
|
-
speed: 1
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
trackEvent(event) {
|
|
21
|
-
this.allEvents.push({
|
|
22
|
-
...event,
|
|
23
|
-
trackingId: this.allEvents.length,
|
|
24
|
-
trackedAt: Date.now()
|
|
25
|
-
});
|
|
26
|
-
if (this.allEvents.length > 5000) this.allEvents.shift();
|
|
27
|
-
if (this.filterState.isActive) this.applyFilters();
|
|
28
|
-
return event;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
setTypeFilter(types) { this.filterState.types = new Set(types); this.applyFilters(); }
|
|
32
|
-
|
|
33
|
-
toggleType(type) {
|
|
34
|
-
if (this.filterState.types.has(type)) this.filterState.types.delete(type);
|
|
35
|
-
else this.filterState.types.add(type);
|
|
36
|
-
this.applyFilters();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
setSearchText(text) { this.filterState.searchText = text.toLowerCase(); this.applyFilters(); }
|
|
40
|
-
|
|
41
|
-
setTimeRange(startTime, endTime) { this.filterState.startTime = startTime; this.filterState.endTime = endTime; this.applyFilters(); }
|
|
42
|
-
|
|
43
|
-
applyFilters() {
|
|
44
|
-
this.filterState.isActive =
|
|
45
|
-
this.filterState.types.size > 0 ||
|
|
46
|
-
this.filterState.searchText.length > 0 ||
|
|
47
|
-
this.filterState.startTime !== null ||
|
|
48
|
-
this.filterState.endTime !== null;
|
|
49
|
-
if (!this.filterState.isActive) { this.filteredEvents = [...this.allEvents]; return this.filteredEvents; }
|
|
50
|
-
this.filteredEvents = this.allEvents.filter(event => {
|
|
51
|
-
if (this.filterState.types.size > 0 && !this.filterState.types.has(event.type)) return false;
|
|
52
|
-
if (this.filterState.searchText.length > 0) {
|
|
53
|
-
if (!JSON.stringify(event).toLowerCase().includes(this.filterState.searchText)) return false;
|
|
54
|
-
}
|
|
55
|
-
const eventTime = event.timestamp || event.trackedAt;
|
|
56
|
-
if (this.filterState.startTime && eventTime < this.filterState.startTime) return false;
|
|
57
|
-
if (this.filterState.endTime && eventTime > this.filterState.endTime) return false;
|
|
58
|
-
return true;
|
|
59
|
-
});
|
|
60
|
-
return this.filteredEvents;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
search(query) {
|
|
64
|
-
const lowerQuery = query.toLowerCase();
|
|
65
|
-
return this.allEvents.map((event, i) => {
|
|
66
|
-
const searchable = JSON.stringify(event).toLowerCase();
|
|
67
|
-
if (!searchable.includes(lowerQuery)) return null;
|
|
68
|
-
return { event, index: i, matchCount: (searchable.match(new RegExp(lowerQuery, 'g')) || []).length };
|
|
69
|
-
}).filter(Boolean).sort((a, b) => b.matchCount - a.matchCount);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
getStats() {
|
|
73
|
-
const stats = { total: this.allEvents.length, byType: {}, byTime: { oldest: null, newest: null, span: 0 } };
|
|
74
|
-
for (const event of this.allEvents) {
|
|
75
|
-
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
76
|
-
const time = event.timestamp || event.trackedAt;
|
|
77
|
-
if (!stats.byTime.oldest || time < stats.byTime.oldest) stats.byTime.oldest = time;
|
|
78
|
-
if (!stats.byTime.newest || time > stats.byTime.newest) stats.byTime.newest = time;
|
|
79
|
-
}
|
|
80
|
-
if (stats.byTime.oldest && stats.byTime.newest) stats.byTime.span = stats.byTime.newest - stats.byTime.oldest;
|
|
81
|
-
return stats;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async startReplay(events = null, speed = 1) {
|
|
85
|
-
const replayEvents = events || this.filteredEvents;
|
|
86
|
-
if (replayEvents.length === 0) return;
|
|
87
|
-
this.replayState = { isReplaying: true, currentIndex: 0, speed };
|
|
88
|
-
this.renderer.clear();
|
|
89
|
-
for (const event of replayEvents) {
|
|
90
|
-
if (!this.replayState.isReplaying) break;
|
|
91
|
-
await new Promise(resolve => setTimeout(resolve, 100 / this.replayState.speed));
|
|
92
|
-
this.renderer.queueEvent(event);
|
|
93
|
-
this.replayState.currentIndex++;
|
|
94
|
-
}
|
|
95
|
-
this.replayState.isReplaying = false;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
stopReplay() { this.replayState.isReplaying = false; }
|
|
99
|
-
|
|
100
|
-
getReplayProgress() {
|
|
101
|
-
const total = this.filteredEvents.length;
|
|
102
|
-
const current = this.replayState.currentIndex;
|
|
103
|
-
return { current, total, percentage: total > 0 ? (current / total) * 100 : 0, isReplaying: this.replayState.isReplaying };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export(format = 'json') {
|
|
107
|
-
const events = this.filterState.isActive ? this.filteredEvents : this.allEvents;
|
|
108
|
-
if (format === 'csv') return window.exportEventsAsCSV(events);
|
|
109
|
-
if (format === 'markdown') return window.exportEventsAsMarkdown(events);
|
|
110
|
-
return window.exportEventsAsJSON(events);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
clear() { this.allEvents = []; this.filteredEvents = []; this.stopReplay(); }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
window.EventFilter = EventFilter;
|
|
117
|
-
|
|
118
|
-
if (typeof module !== 'undefined' && module.exports) module.exports = EventFilter;
|