agentgui 1.0.730 → 1.0.731
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/electron/main.js +83 -0
- package/package.json +9 -3
- package/scripts/copy-vendor.js +50 -20
- package/static/index.html +1 -0
- package/static/js/conversations.js +43 -82
- package/static/js/tools-manager-ui.js +25 -20
- package/static/js/tools-manager.js +15 -7
- package/static/lib/webjsx.js +674 -0
package/electron/main.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { app, BrowserWindow, dialog } from 'electron';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const ROOT = path.join(__dirname, '..');
|
|
9
|
+
const PORT = process.env.PORT || 3000;
|
|
10
|
+
const BASE_URL = (process.env.BASE_URL || '/gm').replace(/\/+$/, '');
|
|
11
|
+
const APP_URL = `http://localhost:${PORT}${BASE_URL}/`;
|
|
12
|
+
|
|
13
|
+
let serverProcess = null;
|
|
14
|
+
let mainWindow = null;
|
|
15
|
+
|
|
16
|
+
function startServer() {
|
|
17
|
+
serverProcess = spawn(process.execPath, ['server.js'], {
|
|
18
|
+
cwd: ROOT,
|
|
19
|
+
env: { ...process.env, PORT: String(PORT) },
|
|
20
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
21
|
+
});
|
|
22
|
+
serverProcess.stdout.on('data', d => process.stdout.write(`[server] ${d}`));
|
|
23
|
+
serverProcess.stderr.on('data', d => process.stderr.write(`[server] ${d}`));
|
|
24
|
+
serverProcess.on('exit', code => {
|
|
25
|
+
if (code !== 0 && mainWindow) {
|
|
26
|
+
dialog.showErrorBox('Server exited', `AgentGUI server exited with code ${code}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function pollReady(retries = 40) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
function attempt(n) {
|
|
34
|
+
http.get(APP_URL, res => {
|
|
35
|
+
if (res.statusCode === 200 || res.statusCode === 401) return resolve();
|
|
36
|
+
if (n <= 0) return reject(new Error(`Server not ready after polling (last status: ${res.statusCode})`));
|
|
37
|
+
setTimeout(() => attempt(n - 1), 500);
|
|
38
|
+
}).on('error', err => {
|
|
39
|
+
if (n <= 0) return reject(new Error(`Server not reachable: ${err.message}`));
|
|
40
|
+
setTimeout(() => attempt(n - 1), 500);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
attempt(retries);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createWindow() {
|
|
48
|
+
mainWindow = new BrowserWindow({
|
|
49
|
+
width: 1280,
|
|
50
|
+
height: 800,
|
|
51
|
+
title: 'AgentGUI',
|
|
52
|
+
webPreferences: {
|
|
53
|
+
contextIsolation: true,
|
|
54
|
+
nodeIntegration: false,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
mainWindow.loadURL(APP_URL);
|
|
58
|
+
mainWindow.on('closed', () => { mainWindow = null; });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
app.whenReady().then(async () => {
|
|
62
|
+
startServer();
|
|
63
|
+
try {
|
|
64
|
+
await pollReady();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
dialog.showErrorBox('Startup failed', e.message);
|
|
67
|
+
app.quit();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
createWindow();
|
|
71
|
+
app.on('activate', () => { if (!mainWindow) createWindow(); });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
app.on('window-all-closed', () => {
|
|
75
|
+
if (process.platform !== 'darwin') app.quit();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
app.on('will-quit', () => {
|
|
79
|
+
if (serverProcess) {
|
|
80
|
+
serverProcess.kill('SIGTERM');
|
|
81
|
+
serverProcess = null;
|
|
82
|
+
}
|
|
83
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.731",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "electron/main.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"agentgui": "./bin/gmgui.cjs"
|
|
9
9
|
},
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"start": "node server.js",
|
|
20
20
|
"dev": "node server.js --watch",
|
|
21
|
-
"postinstall": "node scripts/patch-fsbrowse.js && node scripts/copy-vendor.js"
|
|
21
|
+
"postinstall": "node scripts/patch-fsbrowse.js && node scripts/copy-vendor.js",
|
|
22
|
+
"electron": "electron electron/main.js",
|
|
23
|
+
"electron:dev": "PORT=3000 electron electron/main.js"
|
|
22
24
|
},
|
|
23
25
|
"dependencies": {
|
|
24
26
|
"@agentclientprotocol/sdk": "^0.4.1",
|
|
@@ -41,6 +43,7 @@
|
|
|
41
43
|
"p-retry": "^7.1.1",
|
|
42
44
|
"pm2": "^5.4.3",
|
|
43
45
|
"puppeteer-core": "^24.37.5",
|
|
46
|
+
"webjsx": "^0.0.73",
|
|
44
47
|
"webtalk": "^1.0.31",
|
|
45
48
|
"ws": "^8.14.2",
|
|
46
49
|
"xstate": "^5.28.0",
|
|
@@ -54,5 +57,8 @@
|
|
|
54
57
|
"sharp": "npm:empty-npm-package@1.0.0",
|
|
55
58
|
"onnxruntime-common": "1.21.0",
|
|
56
59
|
"onnxruntime-node": "1.21.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"electron": "^35.0.0"
|
|
57
63
|
}
|
|
58
64
|
}
|
package/scripts/copy-vendor.js
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const root = path.join(__dirname, '..');
|
|
8
|
-
|
|
9
|
-
const copies = [
|
|
10
|
-
['node_modules/xstate/dist/xstate.umd.min.js', 'static/lib/xstate.umd.min.js'],
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
for (const [src, dest] of copies) {
|
|
14
|
-
const srcPath = path.join(root, src);
|
|
15
|
-
const destPath = path.join(root, dest);
|
|
16
|
-
if (!fs.existsSync(srcPath)) { console.warn('[copy-vendor] not found:', src); continue; }
|
|
17
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
18
|
-
fs.copyFileSync(srcPath, destPath);
|
|
19
|
-
console.log('[copy-vendor] copied', src, '->', dest);
|
|
20
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root = path.join(__dirname, '..');
|
|
8
|
+
|
|
9
|
+
const copies = [
|
|
10
|
+
['node_modules/xstate/dist/xstate.umd.min.js', 'static/lib/xstate.umd.min.js'],
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
for (const [src, dest] of copies) {
|
|
14
|
+
const srcPath = path.join(root, src);
|
|
15
|
+
const destPath = path.join(root, dest);
|
|
16
|
+
if (!fs.existsSync(srcPath)) { console.warn('[copy-vendor] not found:', src); continue; }
|
|
17
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
18
|
+
fs.copyFileSync(srcPath, destPath);
|
|
19
|
+
console.log('[copy-vendor] copied', src, '->', dest);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Build webjsx IIFE bundle from ESM dist files
|
|
23
|
+
const webjsxDist = path.join(root, 'node_modules/webjsx/dist');
|
|
24
|
+
if (fs.existsSync(webjsxDist)) {
|
|
25
|
+
const ORDER = ['constants', 'elementTags', 'utils', 'attributes', 'createDOMElement', 'createElement', 'applyDiff', 'types'];
|
|
26
|
+
|
|
27
|
+
function stripModule(src) {
|
|
28
|
+
return src
|
|
29
|
+
.replace(/^import\s+.*?from\s+['"][^'"]+['"];?\s*$/gm, '')
|
|
30
|
+
.replace(/^export\s+(const|function|class|async\s+function)\s+/gm, '$1 ')
|
|
31
|
+
.replace(/^export\s+\{[^}]*\}(\s+from\s+['"][^'"]+['"])?\s*;?\s*$/gm, '')
|
|
32
|
+
.replace(/^export\s+\*\s+from\s+['"][^'"]+['"];?\s*$/gm, '')
|
|
33
|
+
.replace(/^\/\/#\s+sourceMappingURL=.*$/gm, '')
|
|
34
|
+
.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const stripped = ORDER.map(name => {
|
|
38
|
+
const src = fs.readFileSync(path.join(webjsxDist, `${name}.js`), 'utf8');
|
|
39
|
+
return `// === ${name}.js ===\n${stripModule(src)}`;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const iife = `(function(window) {\n"use strict";\n\n${stripped.join('\n\n')}\n\nwindow.webjsx = { createElement, applyDiff, createDOMElement, Fragment };\n})(typeof window !== 'undefined' ? window : globalThis);\n`;
|
|
43
|
+
|
|
44
|
+
const dest = path.join(root, 'static/lib/webjsx.js');
|
|
45
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
46
|
+
fs.writeFileSync(dest, iife);
|
|
47
|
+
console.log('[copy-vendor] built webjsx IIFE ->', 'static/lib/webjsx.js', `(${iife.split('\n').length} lines)`);
|
|
48
|
+
} else {
|
|
49
|
+
console.warn('[copy-vendor] webjsx not found in node_modules — run npm install');
|
|
50
|
+
}
|
package/static/index.html
CHANGED
|
@@ -3311,6 +3311,7 @@
|
|
|
3311
3311
|
<script defer src="/gm/js/event-processor.js"></script>
|
|
3312
3312
|
<script defer src="/gm/js/streaming-renderer.js"></script>
|
|
3313
3313
|
<script defer src="/gm/js/image-loader.js"></script>
|
|
3314
|
+
<script defer src="/gm/lib/webjsx.js"></script>
|
|
3314
3315
|
<script defer src="/gm/lib/xstate.umd.min.js"></script>
|
|
3315
3316
|
<script defer src="/gm/js/ws-machine.js"></script>
|
|
3316
3317
|
<script defer src="/gm/js/conv-machine.js"></script>
|
|
@@ -457,46 +457,10 @@ class ConversationManager {
|
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (this.conversations.length === 0) {
|
|
464
|
-
this.showEmpty();
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
this.emptyEl.style.display = 'none';
|
|
469
|
-
|
|
470
|
-
const sorted = [...this.conversations].sort((a, b) =>
|
|
471
|
-
new Date(b.createdAt || 0) - new Date(a.createdAt || 0)
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
const existingMap = {};
|
|
475
|
-
for (const child of Array.from(this.listEl.children)) {
|
|
476
|
-
const cid = child.dataset.convId;
|
|
477
|
-
if (cid) existingMap[cid] = child;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const frag = document.createDocumentFragment();
|
|
481
|
-
for (const conv of sorted) {
|
|
482
|
-
const existing = existingMap[conv.id];
|
|
483
|
-
if (existing) {
|
|
484
|
-
this.updateConversationItem(existing, conv);
|
|
485
|
-
delete existingMap[conv.id];
|
|
486
|
-
frag.appendChild(existing);
|
|
487
|
-
} else {
|
|
488
|
-
frag.appendChild(this.createConversationItem(conv));
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
for (const orphan of Object.values(existingMap)) orphan.remove();
|
|
493
|
-
this.listEl.appendChild(frag);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
updateConversationItem(el, conv) {
|
|
460
|
+
_convVnode(conv) {
|
|
461
|
+
const h = window.webjsx?.createElement;
|
|
462
|
+
if (!h) return null;
|
|
497
463
|
const isActive = conv.id === this.activeId;
|
|
498
|
-
el.classList.toggle('active', isActive);
|
|
499
|
-
|
|
500
464
|
const isStreaming = this.streamingConversations.has(conv.id);
|
|
501
465
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
502
466
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
@@ -505,53 +469,50 @@ class ConversationManager {
|
|
|
505
469
|
const wd = conv.workingDirectory ? pathBasename(conv.workingDirectory) : '';
|
|
506
470
|
const metaParts = [agent + modelLabel, timestamp];
|
|
507
471
|
if (wd) metaParts.push(wd);
|
|
472
|
+
const badge = isStreaming
|
|
473
|
+
? h('span', { class: 'conversation-streaming-badge', title: 'Streaming in progress' }, h('span', { class: 'streaming-dot' }))
|
|
474
|
+
: null;
|
|
475
|
+
return h('li', { class: 'conversation-item' + (isActive ? ' active' : ''), 'data-conv-id': conv.id },
|
|
476
|
+
h('div', { class: 'conversation-item-content' },
|
|
477
|
+
h('div', { class: 'conversation-item-title' }, ...(badge ? [badge, title] : [title])),
|
|
478
|
+
h('div', { class: 'conversation-item-meta' }, metaParts.join(' \u2022 '))
|
|
479
|
+
),
|
|
480
|
+
h('button', { class: 'conversation-item-delete', title: 'Delete conversation', 'data-delete-conv': conv.id },
|
|
481
|
+
h('svg', { width: '14', height: '14', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '2' },
|
|
482
|
+
h('polyline', { points: '3 6 5 6 21 6' }),
|
|
483
|
+
h('path', { d: 'M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' })
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
);
|
|
487
|
+
}
|
|
508
488
|
|
|
509
|
-
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
489
|
+
render() {
|
|
490
|
+
if (!this.listEl) return;
|
|
491
|
+
if (this.conversations.length === 0) { this.showEmpty(); return; }
|
|
492
|
+
this.emptyEl.style.display = 'none';
|
|
493
|
+
const sorted = [...this.conversations].sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0));
|
|
494
|
+
if (window.webjsx?.applyDiff) {
|
|
495
|
+
window.webjsx.applyDiff(this.listEl, sorted.map(conv => this._convVnode(conv)).filter(Boolean));
|
|
496
|
+
} else {
|
|
497
|
+
this._renderFallback(sorted);
|
|
515
498
|
}
|
|
516
|
-
|
|
517
|
-
const metaEl = el.querySelector('.conversation-item-meta');
|
|
518
|
-
if (metaEl) metaEl.textContent = metaParts.join(' \u2022 ');
|
|
519
499
|
}
|
|
520
500
|
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const streamingBadge = isStreaming
|
|
538
|
-
? '<span class="conversation-streaming-badge" title="Streaming in progress"><span class="streaming-dot"></span></span>'
|
|
539
|
-
: '';
|
|
540
|
-
|
|
541
|
-
li.innerHTML = `
|
|
542
|
-
<div class="conversation-item-content">
|
|
543
|
-
<div class="conversation-item-title">${streamingBadge}${this.escapeHtml(title)}</div>
|
|
544
|
-
<div class="conversation-item-meta">${metaParts.join(' • ')}</div>
|
|
545
|
-
</div>
|
|
546
|
-
<button class="conversation-item-delete" title="Delete conversation" data-delete-conv="${conv.id}">
|
|
547
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
548
|
-
<polyline points="3 6 5 6 21 6"></polyline>
|
|
549
|
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
550
|
-
</svg>
|
|
551
|
-
</button>
|
|
552
|
-
`;
|
|
553
|
-
|
|
554
|
-
return li;
|
|
501
|
+
_renderFallback(sorted) {
|
|
502
|
+
const existingMap = {};
|
|
503
|
+
for (const child of Array.from(this.listEl.children)) {
|
|
504
|
+
if (child.dataset.convId) existingMap[child.dataset.convId] = child;
|
|
505
|
+
}
|
|
506
|
+
const frag = document.createDocumentFragment();
|
|
507
|
+
for (const conv of sorted) {
|
|
508
|
+
const el = existingMap[conv.id] || document.createElement('li');
|
|
509
|
+
el.className = 'conversation-item' + (conv.id === this.activeId ? ' active' : '');
|
|
510
|
+
el.dataset.convId = conv.id;
|
|
511
|
+
delete existingMap[conv.id];
|
|
512
|
+
frag.appendChild(el);
|
|
513
|
+
}
|
|
514
|
+
for (const orphan of Object.values(existingMap)) orphan.remove();
|
|
515
|
+
this.listEl.appendChild(frag);
|
|
555
516
|
}
|
|
556
517
|
|
|
557
518
|
async confirmDelete(convId, title) {
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
|
|
44
44
|
renderToolCard: function(tool, isRefreshing) {
|
|
45
|
+
var h = window.webjsx && window.webjsx.createElement;
|
|
46
|
+
if (!h) return null;
|
|
45
47
|
var ui = window.toolsManagerUI;
|
|
46
48
|
var statusClass = ui.getStatusClass(tool);
|
|
47
49
|
var ms = window.toolInstallMachineAPI ? window.toolInstallMachineAPI.getState(tool.id) : null;
|
|
@@ -53,26 +55,29 @@
|
|
|
53
55
|
var canUpdate = ms === 'needs_update' || tool.hasUpdate || tool.status === 'needs_update';
|
|
54
56
|
var iv = (snap && snap.context.installedVersion) || tool.installedVersion;
|
|
55
57
|
var pv = (snap && snap.context.publishedVersion) || tool.publishedVersion;
|
|
56
|
-
var
|
|
57
|
-
if (iv
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
var versionChildren = [];
|
|
59
|
+
if (iv) versionChildren.push(h('span', { class: 'tool-version-item' }, 'v' + iv));
|
|
60
|
+
if (pv && iv !== pv) versionChildren.push(h('span', { class: 'tool-version-item' }, '(v' + pv + ' available)'));
|
|
61
|
+
var actionBtn = canInstall
|
|
62
|
+
? h('button', { class: 'tool-btn tool-btn-primary', disabled: locked, onClick: function() { window.toolsManager.install(tool.id); } }, 'Install')
|
|
63
|
+
: canUpdate
|
|
64
|
+
? h('button', { class: 'tool-btn tool-btn-primary', disabled: locked, onClick: function() { window.toolsManager.update(tool.id); } }, 'Update')
|
|
65
|
+
: h('button', { class: 'tool-btn tool-btn-secondary', disabled: isRefreshing, onClick: function() { window.toolsManager.refresh(); } }, '\u2713');
|
|
66
|
+
return h('div', { class: 'tool-item' },
|
|
67
|
+
h('div', { style: 'display:flex;flex-direction:column;gap:0.3rem;' },
|
|
68
|
+
h('div', { class: 'tool-header' }, h('span', { class: 'tool-name' }, tool.name || tool.id)),
|
|
69
|
+
h('div', { class: 'tool-status-indicator ' + statusClass },
|
|
70
|
+
h('span', { class: 'tool-status-dot' }),
|
|
71
|
+
h('span', {}, ui.getStatusText(tool))
|
|
72
|
+
),
|
|
73
|
+
versionChildren.length ? h('div', { class: 'tool-versions' }, ...versionChildren) : null,
|
|
74
|
+
isInstalling && progress !== undefined
|
|
75
|
+
? h('div', { class: 'tool-progress-container' }, h('div', { class: 'tool-progress-bar' }, h('div', { class: 'tool-progress-fill', style: 'width:' + Math.min(progress, 100) + '%' })))
|
|
76
|
+
: null,
|
|
77
|
+
tool.error_message ? h('div', { class: 'tool-error-message' }, 'Error: ' + tool.error_message.substring(0, 40)) : null
|
|
78
|
+
),
|
|
79
|
+
h('div', { class: 'tool-actions' }, actionBtn)
|
|
80
|
+
);
|
|
76
81
|
},
|
|
77
82
|
|
|
78
83
|
esc: function(s) {
|
|
@@ -124,14 +124,22 @@
|
|
|
124
124
|
function render() {
|
|
125
125
|
var scroll = popup.querySelector('.tools-popup-scroll');
|
|
126
126
|
if (!scroll) return;
|
|
127
|
-
|
|
128
|
-
var
|
|
127
|
+
var h = window.webjsx && window.webjsx.createElement;
|
|
128
|
+
var applyDiff = window.webjsx && window.webjsx.applyDiff;
|
|
129
129
|
var ui = window.toolsManagerUI;
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
if (!h || !applyDiff) { scroll.innerHTML = '<div class="tool-empty-state"><div class="tool-empty-state-text">Loading...</div></div>'; return; }
|
|
131
|
+
if (!tools.length) {
|
|
132
|
+
applyDiff(scroll, [h('div', { class: 'tool-empty-state', style: 'grid-column:1/-1;' }, h('div', { class: 'tool-empty-state-text' }, 'No tools available'))]);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
var cli = tools.filter(t => t.category === 'cli');
|
|
136
|
+
var plugin = tools.filter(t => t.category === 'plugin');
|
|
137
|
+
var other = tools.filter(t => !t.category);
|
|
138
|
+
var vnodes = [];
|
|
139
|
+
if (cli.length) vnodes.push(h('div', { class: 'tool-section-header' }, 'CLI Agents'), ...cli.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
|
|
140
|
+
if (plugin.length) vnodes.push(h('div', { class: 'tool-section-header' }, 'GM Plugins'), ...plugin.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
|
|
141
|
+
if (other.length) vnodes.push(...other.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
|
|
142
|
+
applyDiff(scroll, vnodes);
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
function togglePopup(e) { e.stopPropagation(); if (!popup.classList.contains('open')) { isRefreshing = false; refresh(); } popup.classList.toggle('open'); }
|
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
(function(window) {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// === constants.js ===
|
|
5
|
+
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
|
6
|
+
const MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
|
|
7
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
8
|
+
|
|
9
|
+
// === elementTags.js ===
|
|
10
|
+
// Create a Map for faster lookups
|
|
11
|
+
const KNOWN_ELEMENTS = new Map(Object.entries({
|
|
12
|
+
a: "A",
|
|
13
|
+
abbr: "ABBR",
|
|
14
|
+
address: "ADDRESS",
|
|
15
|
+
area: "AREA",
|
|
16
|
+
article: "ARTICLE",
|
|
17
|
+
aside: "ASIDE",
|
|
18
|
+
audio: "AUDIO",
|
|
19
|
+
b: "B",
|
|
20
|
+
base: "BASE",
|
|
21
|
+
bdi: "BDI",
|
|
22
|
+
bdo: "BDO",
|
|
23
|
+
blockquote: "BLOCKQUOTE",
|
|
24
|
+
body: "BODY",
|
|
25
|
+
br: "BR",
|
|
26
|
+
button: "BUTTON",
|
|
27
|
+
canvas: "CANVAS",
|
|
28
|
+
caption: "CAPTION",
|
|
29
|
+
cite: "CITE",
|
|
30
|
+
code: "CODE",
|
|
31
|
+
col: "COL",
|
|
32
|
+
colgroup: "COLGROUP",
|
|
33
|
+
data: "DATA",
|
|
34
|
+
datalist: "DATALIST",
|
|
35
|
+
dd: "DD",
|
|
36
|
+
del: "DEL",
|
|
37
|
+
details: "DETAILS",
|
|
38
|
+
dfn: "DFN",
|
|
39
|
+
dialog: "DIALOG",
|
|
40
|
+
div: "DIV",
|
|
41
|
+
dl: "DL",
|
|
42
|
+
dt: "DT",
|
|
43
|
+
em: "EM",
|
|
44
|
+
embed: "EMBED",
|
|
45
|
+
fieldset: "FIELDSET",
|
|
46
|
+
figcaption: "FIGCAPTION",
|
|
47
|
+
figure: "FIGURE",
|
|
48
|
+
footer: "FOOTER",
|
|
49
|
+
form: "FORM",
|
|
50
|
+
h1: "H1",
|
|
51
|
+
h2: "H2",
|
|
52
|
+
h3: "H3",
|
|
53
|
+
h4: "H4",
|
|
54
|
+
h5: "H5",
|
|
55
|
+
h6: "H6",
|
|
56
|
+
head: "HEAD",
|
|
57
|
+
header: "HEADER",
|
|
58
|
+
hgroup: "HGROUP",
|
|
59
|
+
hr: "HR",
|
|
60
|
+
html: "HTML",
|
|
61
|
+
i: "I",
|
|
62
|
+
iframe: "IFRAME",
|
|
63
|
+
img: "IMG",
|
|
64
|
+
input: "INPUT",
|
|
65
|
+
ins: "INS",
|
|
66
|
+
kbd: "KBD",
|
|
67
|
+
label: "LABEL",
|
|
68
|
+
legend: "LEGEND",
|
|
69
|
+
li: "LI",
|
|
70
|
+
link: "LINK",
|
|
71
|
+
main: "MAIN",
|
|
72
|
+
map: "MAP",
|
|
73
|
+
mark: "MARK",
|
|
74
|
+
menu: "MENU",
|
|
75
|
+
meta: "META",
|
|
76
|
+
meter: "METER",
|
|
77
|
+
nav: "NAV",
|
|
78
|
+
noscript: "NOSCRIPT",
|
|
79
|
+
object: "OBJECT",
|
|
80
|
+
ol: "OL",
|
|
81
|
+
optgroup: "OPTGROUP",
|
|
82
|
+
option: "OPTION",
|
|
83
|
+
output: "OUTPUT",
|
|
84
|
+
p: "P",
|
|
85
|
+
picture: "PICTURE",
|
|
86
|
+
pre: "PRE",
|
|
87
|
+
progress: "PROGRESS",
|
|
88
|
+
q: "Q",
|
|
89
|
+
rp: "RP",
|
|
90
|
+
rt: "RT",
|
|
91
|
+
ruby: "RUBY",
|
|
92
|
+
s: "S",
|
|
93
|
+
samp: "SAMP",
|
|
94
|
+
script: "SCRIPT",
|
|
95
|
+
section: "SECTION",
|
|
96
|
+
select: "SELECT",
|
|
97
|
+
slot: "SLOT",
|
|
98
|
+
small: "SMALL",
|
|
99
|
+
source: "SOURCE",
|
|
100
|
+
span: "SPAN",
|
|
101
|
+
strong: "STRONG",
|
|
102
|
+
style: "STYLE",
|
|
103
|
+
sub: "SUB",
|
|
104
|
+
summary: "SUMMARY",
|
|
105
|
+
sup: "SUP",
|
|
106
|
+
table: "TABLE",
|
|
107
|
+
tbody: "TBODY",
|
|
108
|
+
td: "TD",
|
|
109
|
+
template: "TEMPLATE",
|
|
110
|
+
textarea: "TEXTAREA",
|
|
111
|
+
tfoot: "TFOOT",
|
|
112
|
+
th: "TH",
|
|
113
|
+
thead: "THEAD",
|
|
114
|
+
time: "TIME",
|
|
115
|
+
title: "TITLE",
|
|
116
|
+
tr: "TR",
|
|
117
|
+
track: "TRACK",
|
|
118
|
+
u: "U",
|
|
119
|
+
ul: "UL",
|
|
120
|
+
var: "VAR",
|
|
121
|
+
video: "VIDEO",
|
|
122
|
+
wbr: "WBR",
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
// === utils.js ===
|
|
126
|
+
/**
|
|
127
|
+
* Flattens nested virtual nodes by replacing Fragments with their children.
|
|
128
|
+
* @param vnodes Virtual nodes to flatten
|
|
129
|
+
* @returns Array of flattened virtual nodes
|
|
130
|
+
*/
|
|
131
|
+
function flattenVNodes(vnodes, result = []) {
|
|
132
|
+
if (Array.isArray(vnodes)) {
|
|
133
|
+
for (const vnode of vnodes) {
|
|
134
|
+
flattenVNodes(vnode, result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (isValidVNode(vnodes)) {
|
|
138
|
+
result.push(vnodes);
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
function isValidVNode(vnode) {
|
|
143
|
+
const typeofVNode = typeof vnode;
|
|
144
|
+
return (vnode !== null &&
|
|
145
|
+
vnode !== undefined &&
|
|
146
|
+
(typeofVNode === "string" ||
|
|
147
|
+
typeofVNode === "object" ||
|
|
148
|
+
typeofVNode === "number" ||
|
|
149
|
+
typeofVNode === "bigint"));
|
|
150
|
+
}
|
|
151
|
+
/* Get Child Nodes Efficiently */
|
|
152
|
+
function getChildNodes(parent) {
|
|
153
|
+
const nodes = [];
|
|
154
|
+
let current = parent.firstChild;
|
|
155
|
+
while (current) {
|
|
156
|
+
nodes.push(current);
|
|
157
|
+
current = current.nextSibling;
|
|
158
|
+
}
|
|
159
|
+
return nodes;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Assigns a ref to a DOM node.
|
|
163
|
+
* @param node Target DOM node
|
|
164
|
+
* @param ref Reference to assign (function or object with current property)
|
|
165
|
+
*/
|
|
166
|
+
function assignRef(node, ref) {
|
|
167
|
+
if (typeof ref === "function") {
|
|
168
|
+
ref(node);
|
|
169
|
+
}
|
|
170
|
+
else if (ref && typeof ref === "object") {
|
|
171
|
+
ref.current = node;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isVElement(vnode) {
|
|
175
|
+
const typeofVNode = typeof vnode;
|
|
176
|
+
return (typeofVNode !== "string" &&
|
|
177
|
+
typeofVNode !== "number" &&
|
|
178
|
+
typeofVNode !== "bigint");
|
|
179
|
+
}
|
|
180
|
+
function isNonBooleanPrimitive(vnode) {
|
|
181
|
+
const typeofVNode = typeof vnode;
|
|
182
|
+
return (typeofVNode === "string" ||
|
|
183
|
+
typeofVNode === "number" ||
|
|
184
|
+
typeofVNode === "bigint");
|
|
185
|
+
}
|
|
186
|
+
function getNamespaceURI(node) {
|
|
187
|
+
return node instanceof Element && node.namespaceURI !== HTML_NAMESPACE
|
|
188
|
+
? node.namespaceURI ?? undefined
|
|
189
|
+
: undefined;
|
|
190
|
+
}
|
|
191
|
+
function setWebJSXProps(element, props) {
|
|
192
|
+
element.__webjsx_props = props;
|
|
193
|
+
}
|
|
194
|
+
function getWebJSXProps(element) {
|
|
195
|
+
let props = element.__webjsx_props;
|
|
196
|
+
if (!props) {
|
|
197
|
+
props = {};
|
|
198
|
+
element.__webjsx_props = props;
|
|
199
|
+
}
|
|
200
|
+
return props;
|
|
201
|
+
}
|
|
202
|
+
function setWebJSXChildNodeCache(element, childNodes) {
|
|
203
|
+
element.__webjsx_childNodes = childNodes;
|
|
204
|
+
}
|
|
205
|
+
function getWebJSXChildNodeCache(element) {
|
|
206
|
+
return element.__webjsx_childNodes;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// === attributes.js ===
|
|
210
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Updates an event listener on an element.
|
|
214
|
+
* @param el Target element
|
|
215
|
+
* @param eventName Name of the event (without 'on' prefix)
|
|
216
|
+
* @param newHandler New event handler function
|
|
217
|
+
* @param oldHandler Previous event handler function
|
|
218
|
+
*/
|
|
219
|
+
function updateEventListener(el, eventName, newHandler, oldHandler) {
|
|
220
|
+
if (oldHandler && oldHandler !== newHandler) {
|
|
221
|
+
el.removeEventListener(eventName, oldHandler);
|
|
222
|
+
}
|
|
223
|
+
if (newHandler && oldHandler !== newHandler) {
|
|
224
|
+
el.addEventListener(eventName, newHandler);
|
|
225
|
+
el.__webjsx_listeners =
|
|
226
|
+
el.__webjsx_listeners ?? {};
|
|
227
|
+
el.__webjsx_listeners[eventName] = newHandler;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Updates a single property or attribute on an element.
|
|
232
|
+
* @param el Target element
|
|
233
|
+
* @param key Property or attribute name
|
|
234
|
+
* @param value New value to set
|
|
235
|
+
*/
|
|
236
|
+
function updatePropOrAttr(el, key, value) {
|
|
237
|
+
if (el instanceof HTMLElement) {
|
|
238
|
+
if (key in el) {
|
|
239
|
+
// Fast path: property exists on HTMLElement
|
|
240
|
+
el[key] = value;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === "string") {
|
|
244
|
+
el.setAttribute(key, value);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Fallback for non-string values on HTMLElement
|
|
248
|
+
el[key] = value;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// SVG/Other namespace elements
|
|
252
|
+
const isSVG = el.namespaceURI === "http://www.w3.org/2000/svg";
|
|
253
|
+
if (isSVG) {
|
|
254
|
+
if (value !== undefined && value !== null) {
|
|
255
|
+
el.setAttribute(key, `${value}`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
el.removeAttribute(key);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// Fallback for other element types
|
|
263
|
+
if (typeof value === "string") {
|
|
264
|
+
el.setAttribute(key, value);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
el[key] = value;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Updates all attributes and properties on a DOM element.
|
|
272
|
+
* @param el Target element
|
|
273
|
+
* @param newProps New properties to apply
|
|
274
|
+
* @param oldProps Previous properties for comparison (default empty object)
|
|
275
|
+
*/
|
|
276
|
+
function updateAttributesCore(el, newProps, oldProps = {}) {
|
|
277
|
+
// Handle new/updated props
|
|
278
|
+
for (const key of Object.keys(newProps)) {
|
|
279
|
+
const value = newProps[key];
|
|
280
|
+
if (key === "children" ||
|
|
281
|
+
key === "key" ||
|
|
282
|
+
key === "dangerouslySetInnerHTML" ||
|
|
283
|
+
key === "nodes")
|
|
284
|
+
continue;
|
|
285
|
+
if (key.startsWith("on") && typeof value === "function") {
|
|
286
|
+
const eventName = key.substring(2).toLowerCase();
|
|
287
|
+
updateEventListener(el, eventName, value, el.__webjsx_listeners?.[eventName]);
|
|
288
|
+
}
|
|
289
|
+
else if (value !== oldProps[key]) {
|
|
290
|
+
updatePropOrAttr(el, key, value);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Handle dangerouslySetInnerHTML
|
|
294
|
+
if (newProps.dangerouslySetInnerHTML) {
|
|
295
|
+
if (!oldProps.dangerouslySetInnerHTML ||
|
|
296
|
+
newProps.dangerouslySetInnerHTML.__html !==
|
|
297
|
+
oldProps.dangerouslySetInnerHTML.__html) {
|
|
298
|
+
const html = newProps.dangerouslySetInnerHTML?.__html || "";
|
|
299
|
+
el.innerHTML = html;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
if (oldProps.dangerouslySetInnerHTML) {
|
|
304
|
+
el.innerHTML = "";
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Remove old props/attributes
|
|
308
|
+
for (const key of Object.keys(oldProps)) {
|
|
309
|
+
if (!(key in newProps) &&
|
|
310
|
+
key !== "children" &&
|
|
311
|
+
key !== "key" &&
|
|
312
|
+
key !== "dangerouslySetInnerHTML" &&
|
|
313
|
+
key !== "nodes") {
|
|
314
|
+
if (key.startsWith("on")) {
|
|
315
|
+
const eventName = key.substring(2).toLowerCase();
|
|
316
|
+
const existingListener = el
|
|
317
|
+
.__webjsx_listeners?.[eventName];
|
|
318
|
+
if (existingListener) {
|
|
319
|
+
el.removeEventListener(eventName, existingListener);
|
|
320
|
+
delete el.__webjsx_listeners[eventName];
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else if (key in el) {
|
|
324
|
+
el[key] = undefined;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
el.removeAttribute(key);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Sets initial attributes and properties on a DOM element.
|
|
334
|
+
* @param el Target element
|
|
335
|
+
* @param props Properties to apply
|
|
336
|
+
*/
|
|
337
|
+
function setAttributes(el, props) {
|
|
338
|
+
if (definesRenderSuspension(el)) {
|
|
339
|
+
withRenderSuspension(el, () => {
|
|
340
|
+
updateAttributesCore(el, props);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
updateAttributesCore(el, props);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Updates existing attributes and properties on a DOM element.
|
|
349
|
+
* @param el Target element
|
|
350
|
+
* @param newProps New properties to apply
|
|
351
|
+
* @param oldProps Previous properties for comparison
|
|
352
|
+
*/
|
|
353
|
+
function updateAttributes(el, newProps, oldProps) {
|
|
354
|
+
if (definesRenderSuspension(el)) {
|
|
355
|
+
withRenderSuspension(el, () => {
|
|
356
|
+
updateAttributesCore(el, newProps, oldProps);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
updateAttributesCore(el, newProps, oldProps);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// === createDOMElement.js ===
|
|
365
|
+
/**
|
|
366
|
+
* Creates a real DOM node from a virtual node representation.
|
|
367
|
+
* @param velement Virtual node to convert
|
|
368
|
+
* @param parentNamespaceURI Namespace URI from parent element, if any
|
|
369
|
+
* @returns Created DOM node
|
|
370
|
+
*/
|
|
371
|
+
function createDOMElement(velement, parentNamespaceURI) {
|
|
372
|
+
const namespaceURI = velement.props.xmlns !== undefined
|
|
373
|
+
? velement.props.xmlns
|
|
374
|
+
: velement.type === "svg"
|
|
375
|
+
? SVG_NAMESPACE
|
|
376
|
+
: parentNamespaceURI ?? undefined;
|
|
377
|
+
const el = velement.props.is !== undefined
|
|
378
|
+
? namespaceURI !== undefined
|
|
379
|
+
? document.createElementNS(namespaceURI, velement.type, {
|
|
380
|
+
is: velement.props.is,
|
|
381
|
+
})
|
|
382
|
+
: document.createElement(velement.type, {
|
|
383
|
+
is: velement.props.is,
|
|
384
|
+
})
|
|
385
|
+
: namespaceURI !== undefined
|
|
386
|
+
? document.createElementNS(namespaceURI, velement.type)
|
|
387
|
+
: document.createElement(velement.type);
|
|
388
|
+
if (velement.props) {
|
|
389
|
+
setAttributes(el, velement.props);
|
|
390
|
+
}
|
|
391
|
+
if (velement.props.key !== undefined) {
|
|
392
|
+
el.__webjsx_key = velement.props.key;
|
|
393
|
+
}
|
|
394
|
+
if (velement.props.ref) {
|
|
395
|
+
assignRef(el, velement.props.ref);
|
|
396
|
+
}
|
|
397
|
+
if (velement.props.children && !velement.props.dangerouslySetInnerHTML) {
|
|
398
|
+
const children = velement.props.children;
|
|
399
|
+
const nodes = [];
|
|
400
|
+
for (let i = 0; i < children.length; i++) {
|
|
401
|
+
const child = children[i];
|
|
402
|
+
const node = isVElement(child)
|
|
403
|
+
? createDOMElement(child, namespaceURI)
|
|
404
|
+
: document.createTextNode(`${child}`);
|
|
405
|
+
nodes.push(node);
|
|
406
|
+
el.appendChild(node);
|
|
407
|
+
}
|
|
408
|
+
setWebJSXProps(el, velement.props);
|
|
409
|
+
setWebJSXChildNodeCache(el, nodes);
|
|
410
|
+
}
|
|
411
|
+
return el;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// === createElement.js ===
|
|
415
|
+
/**
|
|
416
|
+
* Creates a virtual element representing a DOM node or Fragment.
|
|
417
|
+
* @param type Element type (tag name) or Fragment
|
|
418
|
+
* @param props Properties and attributes for the element
|
|
419
|
+
* @param children Child elements or content
|
|
420
|
+
* @returns Virtual element representation
|
|
421
|
+
*/
|
|
422
|
+
function createElement(type, props, ...children) {
|
|
423
|
+
if (typeof type === "string") {
|
|
424
|
+
const normalizedProps = props ? props : {};
|
|
425
|
+
const flatChildren = flattenVNodes(children);
|
|
426
|
+
if (flatChildren.length > 0) {
|
|
427
|
+
// Set children property only if dangerouslySetInnerHTML is not present
|
|
428
|
+
if (!normalizedProps.dangerouslySetInnerHTML) {
|
|
429
|
+
normalizedProps.children = flatChildren;
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
normalizedProps.children = [];
|
|
433
|
+
console.warn("WebJSX: Ignoring children since dangerouslySetInnerHTML is set.");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
normalizedProps.children = [];
|
|
438
|
+
}
|
|
439
|
+
const result = {
|
|
440
|
+
type,
|
|
441
|
+
tagName: KNOWN_ELEMENTS.get(type) ?? type.toUpperCase(),
|
|
442
|
+
props: normalizedProps ?? {},
|
|
443
|
+
};
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
return flattenVNodes(children);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// As called from jsx-runtime.jsx function.
|
|
451
|
+
function createElementJSX(type, props, key) {
|
|
452
|
+
if (typeof type === "string") {
|
|
453
|
+
props = props || {};
|
|
454
|
+
const flatChildren = props
|
|
455
|
+
? flattenVNodes(props.children)
|
|
456
|
+
: [];
|
|
457
|
+
if (key !== undefined) {
|
|
458
|
+
props.key = key;
|
|
459
|
+
}
|
|
460
|
+
if (flatChildren.length > 0) {
|
|
461
|
+
// Set children property only if dangerouslySetInnerHTML is not present
|
|
462
|
+
if (!props.dangerouslySetInnerHTML) {
|
|
463
|
+
props.children = flatChildren;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
props.children = [];
|
|
467
|
+
console.warn("WebJSX: Ignoring children since dangerouslySetInnerHTML is set.");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
props.children = [];
|
|
472
|
+
}
|
|
473
|
+
const result = {
|
|
474
|
+
type,
|
|
475
|
+
tagName: KNOWN_ELEMENTS.get(type) ?? type.toUpperCase(),
|
|
476
|
+
props: props ?? {},
|
|
477
|
+
};
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
const flatChildren = props
|
|
482
|
+
? flattenVNodes(props.children)
|
|
483
|
+
: [];
|
|
484
|
+
return flatChildren;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// === applyDiff.js ===
|
|
489
|
+
function applyDiff(parent, vnodes) {
|
|
490
|
+
const newVNodes = flattenVNodes(vnodes);
|
|
491
|
+
const newNodes = diffChildren(parent, newVNodes);
|
|
492
|
+
const props = getWebJSXProps(parent);
|
|
493
|
+
props.children = newVNodes;
|
|
494
|
+
setWebJSXChildNodeCache(parent, newNodes);
|
|
495
|
+
}
|
|
496
|
+
function diffChildren(parent, newVNodes) {
|
|
497
|
+
const parentProps = getWebJSXProps(parent);
|
|
498
|
+
const oldVNodes = parentProps.children ?? [];
|
|
499
|
+
if (newVNodes.length === 0) {
|
|
500
|
+
if (oldVNodes.length > 0) {
|
|
501
|
+
parent.innerHTML = "";
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// If the parent
|
|
506
|
+
// a) never had any nodes
|
|
507
|
+
// b) OR was managing content via dangerouslySetInnerHTML
|
|
508
|
+
// we must not set parent.innerHTML = "";
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const changes = [];
|
|
513
|
+
let keyedMap = null;
|
|
514
|
+
const originalChildNodes = getWebJSXChildNodeCache(parent) ?? getChildNodes(parent);
|
|
515
|
+
let hasKeyedNodes = false;
|
|
516
|
+
let nodeOrderUnchanged = true;
|
|
517
|
+
for (let i = 0; i < newVNodes.length; i++) {
|
|
518
|
+
const newVNode = newVNodes[i];
|
|
519
|
+
const oldVNode = oldVNodes[i];
|
|
520
|
+
const currentNode = originalChildNodes[i];
|
|
521
|
+
const newKey = isVElement(newVNode) ? newVNode.props.key : undefined;
|
|
522
|
+
if (newKey !== undefined) {
|
|
523
|
+
if (!keyedMap) {
|
|
524
|
+
hasKeyedNodes = true;
|
|
525
|
+
keyedMap = new Map();
|
|
526
|
+
for (let j = 0; j < oldVNodes.length; j++) {
|
|
527
|
+
const matchingVNode = oldVNodes[j];
|
|
528
|
+
const key = matchingVNode.props.key;
|
|
529
|
+
if (key !== undefined) {
|
|
530
|
+
const node = originalChildNodes[j];
|
|
531
|
+
keyedMap.set(key, { node, oldVNode: matchingVNode });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const keyedNode = keyedMap.get(newKey);
|
|
536
|
+
if (keyedNode) {
|
|
537
|
+
if (keyedNode.oldVNode !== oldVNode) {
|
|
538
|
+
nodeOrderUnchanged = false;
|
|
539
|
+
}
|
|
540
|
+
changes.push({
|
|
541
|
+
type: "update",
|
|
542
|
+
node: keyedNode.node,
|
|
543
|
+
newVNode,
|
|
544
|
+
oldVNode: keyedNode.oldVNode,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
nodeOrderUnchanged = false;
|
|
549
|
+
changes.push({ type: "create", vnode: newVNode });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
if (!hasKeyedNodes &&
|
|
554
|
+
canUpdateVNodes(newVNode, oldVNode) &&
|
|
555
|
+
currentNode) {
|
|
556
|
+
changes.push({
|
|
557
|
+
type: "update",
|
|
558
|
+
node: currentNode,
|
|
559
|
+
newVNode,
|
|
560
|
+
oldVNode,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
nodeOrderUnchanged = false;
|
|
565
|
+
changes.push({ type: "create", vnode: newVNode });
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (changes.length) {
|
|
570
|
+
const { nodes, lastNode: lastPlacedNode } = applyChanges(parent, changes, originalChildNodes, nodeOrderUnchanged);
|
|
571
|
+
// Remove any remaining nodes
|
|
572
|
+
while (lastPlacedNode?.nextSibling) {
|
|
573
|
+
parent.removeChild(lastPlacedNode.nextSibling);
|
|
574
|
+
}
|
|
575
|
+
return nodes;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
return originalChildNodes;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function canUpdateVNodes(newVNode, oldVNode) {
|
|
582
|
+
if (oldVNode === undefined)
|
|
583
|
+
return false;
|
|
584
|
+
if (isNonBooleanPrimitive(newVNode) && isNonBooleanPrimitive(oldVNode)) {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
if (isVElement(oldVNode) && isVElement(newVNode)) {
|
|
589
|
+
const oldKey = oldVNode.props.key;
|
|
590
|
+
const newKey = newVNode.props.key;
|
|
591
|
+
return (oldVNode.tagName === newVNode.tagName &&
|
|
592
|
+
((oldKey === undefined && newKey === undefined) ||
|
|
593
|
+
(oldKey !== undefined && newKey !== undefined && oldKey === newKey)));
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function applyChanges(parent, changes, originalNodes, nodeOrderUnchanged) {
|
|
601
|
+
const nodes = [];
|
|
602
|
+
let lastPlacedNode = null;
|
|
603
|
+
for (const change of changes) {
|
|
604
|
+
if (change.type === "create") {
|
|
605
|
+
let node = undefined;
|
|
606
|
+
if (isVElement(change.vnode)) {
|
|
607
|
+
node = createDOMElement(change.vnode, getNamespaceURI(parent));
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
node = document.createTextNode(`${change.vnode}`);
|
|
611
|
+
}
|
|
612
|
+
if (!lastPlacedNode) {
|
|
613
|
+
parent.prepend(node);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
parent.insertBefore(node, lastPlacedNode.nextSibling ?? null);
|
|
617
|
+
}
|
|
618
|
+
lastPlacedNode = node;
|
|
619
|
+
nodes.push(node);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
const { node, newVNode, oldVNode } = change;
|
|
623
|
+
if (isVElement(newVNode)) {
|
|
624
|
+
const oldProps = oldVNode?.props || {};
|
|
625
|
+
const newProps = newVNode.props;
|
|
626
|
+
updateAttributes(node, newProps, oldProps);
|
|
627
|
+
if (newVNode.props.key !== undefined) {
|
|
628
|
+
node.__webjsx_key = newVNode.props.key;
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
if (oldVNode.props?.key) {
|
|
632
|
+
delete node.__webjsx_key;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (newVNode.props.ref) {
|
|
636
|
+
assignRef(node, newVNode.props.ref);
|
|
637
|
+
}
|
|
638
|
+
if (!newProps.dangerouslySetInnerHTML && newProps.children != null) {
|
|
639
|
+
const childNodes = diffChildren(node, newProps.children);
|
|
640
|
+
setWebJSXProps(node, newProps);
|
|
641
|
+
setWebJSXChildNodeCache(node, childNodes);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
if (newVNode !== oldVNode) {
|
|
646
|
+
node.textContent = `${newVNode}`;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (!nodeOrderUnchanged) {
|
|
650
|
+
if (!lastPlacedNode) {
|
|
651
|
+
if (node !== originalNodes[0]) {
|
|
652
|
+
parent.prepend(node);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
if (lastPlacedNode.nextSibling !== node) {
|
|
657
|
+
parent.insertBefore(node, lastPlacedNode.nextSibling ?? null);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
lastPlacedNode = node;
|
|
662
|
+
nodes.push(node);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return { nodes, lastNode: lastPlacedNode };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// === types.js ===
|
|
669
|
+
const Fragment = (props) => {
|
|
670
|
+
return flattenVNodes(props.children);
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
window.webjsx = { createElement, applyDiff, createDOMElement, Fragment };
|
|
674
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|