agentgui 1.0.823 → 1.0.825
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 +16 -44
- 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,45 +1,17 @@
|
|
|
1
|
-
## [Unreleased]
|
|
2
|
-
|
|
3
|
-
### Refactor
|
|
4
|
-
- Split `conversations.js` (737L) into 4 files: conversations.js (117L), conv-sidebar-actions.js (185L), conv-sidebar-clone.js (92L), conv-list-renderer.js (198L)
|
|
5
|
-
- Split `websocket-manager.js` (643L) into 3 files: ws-core.js (163L), ws-latency.js (89L), websocket-manager.js (108L)
|
|
6
|
-
- Split `ui-components.js` (370L) into 2 files: ui-components.js (89L), ui-components-rendering.js (63L)
|
|
7
|
-
- Split `agent-auth.js` into agent-auth.js (147L) and agent-auth-oauth.js (160L)
|
|
8
|
-
- Deleted dead code: event-filter.js (zero external references)
|
|
9
|
-
- Updated index.html script load order for all new split files
|
|
10
|
-
|
|
11
1
|
## 2026-04-11
|
|
12
|
-
- refactor:
|
|
13
|
-
- refactor: split
|
|
14
|
-
- refactor: split
|
|
15
|
-
- refactor: split
|
|
16
|
-
- refactor:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- refactor: split
|
|
20
|
-
- refactor: split
|
|
21
|
-
- refactor: split
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
### Changed
|
|
30
|
-
- Remove GMGUIApp dead code from app.js; strip 900+ lines, keep 5 live IIFEs
|
|
31
|
-
- Add app.js and app-shortcuts.js to index.html script loading (previously unreferenced)
|
|
32
|
-
- Remove unused BASE_URL declaration from app-shortcuts.js
|
|
33
|
-
- Extend window.__debug.getSyncState() with promptMachineState, toolInstallMachineStates, voiceMachineState, convListMachineState
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
### Fixed
|
|
37
|
-
- Thinking blocks now use theme-aware CSS vars for light/dark backgrounds (`--color-thinking-bg`)
|
|
38
|
-
- All code blocks (renderBlockCode, renderCodeWithHighlight, renderFileRead, renderCommand) now use CSS vars instead of hardcoded dark hex colors
|
|
39
|
-
- Removed hardcoded `#1e293b`, `#e2e8f0`, `#d1d5db` from all inline styles in streaming-renderer.js
|
|
40
|
-
- tool-result-pre text color now adapts to theme via `--color-code-text`
|
|
41
|
-
- Added CSS vars to :root (light) and html.dark for --color-bg-code, --color-code-text, --color-code-border, --color-thinking-bg
|
|
42
|
-
- Thinking block content now renders full markdown (headers, lists, blockquotes, bold, italic, inline code, links)
|
|
43
|
-
- parseAndRenderMarkdown() extended with headers (##/###), ul/ol lists, blockquotes (>), horizontal rules (---)
|
|
44
|
-
- Thinking content dark mode text color set to #c4b5fd (readable purple)
|
|
45
|
-
|
|
2
|
+
- refactor: replace jsonl-watcher.js with @lanmower/cc-tail npm package (subclass CCTailWatcher, override _line() via JsonlParser); remove custom file-watching logic
|
|
3
|
+
- refactor: split syntax-highlighter.js (216L) into syntax-highlighter.js (132L) + syntax-highlighter-render.js (73L)
|
|
4
|
+
- refactor: split dialogs.js (268L) into dialogs.js (54L) + dialogs-types.js (112L, window.UIDialog)
|
|
5
|
+
- refactor: split image-loader.js (221L) into image-loader.js (147L) + image-loader-element.js (77L)
|
|
6
|
+
- refactor: split conversations.js (742L) into conversations.js (116L) + conv-sidebar-actions.js (185L) + conv-list-renderer.js (198L) + conv-sidebar-clone.js (92L)
|
|
7
|
+
- refactor: split websocket-manager.js (643L) into ws-core.js (163L) + websocket-manager.js (108L) + ws-latency.js (89L)
|
|
8
|
+
- refactor: split ui-components.js into ui-components.js (89L) + ui-components-rendering.js (63L)
|
|
9
|
+
- refactor: split agent-auth.js into agent-auth.js (147L) + agent-auth-oauth.js (160L)
|
|
10
|
+
- refactor: split event-filter.js into event-filter-config.js (37L); delete event-filter.js dead code (zero external refs)
|
|
11
|
+
- refactor: split jsonl-watcher.js parse logic into jsonl-parser.js (180L); watcher retains file watching only
|
|
12
|
+
- refactor: split tool-version.js into tool-version-check.js + tool-version-fetch.js
|
|
13
|
+
- refactor: fix index.html script load order for all new split files
|
|
14
|
+
- fix: thinking blocks use theme-aware CSS vars for light/dark backgrounds
|
|
15
|
+
- fix: all code blocks use CSS vars instead of hardcoded dark hex colors
|
|
16
|
+
- fix: remove GMGUIApp dead code from app.js; add app.js and app-shortcuts.js to index.html
|
|
17
|
+
- fix: extend window.__debug.getSyncState() with all XState machine snapshots
|
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.825",
|
|
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;
|