@hirohsu/user-web-feedback 2.8.12 → 2.8.13
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/README.md +953 -953
- package/dist/cli.cjs +33267 -46031
- package/dist/index.cjs +35731 -48497
- package/dist/static/app.js +385 -385
- package/dist/static/components/navbar.css +406 -406
- package/dist/static/components/navbar.html +49 -49
- package/dist/static/components/navbar.js +211 -211
- package/dist/static/dashboard.css +495 -495
- package/dist/static/dashboard.html +95 -95
- package/dist/static/dashboard.js +540 -540
- package/dist/static/favicon.svg +27 -27
- package/dist/static/index.html +541 -541
- package/dist/static/logs.html +376 -376
- package/dist/static/logs.js +442 -442
- package/dist/static/mcp-settings.html +797 -797
- package/dist/static/mcp-settings.js +884 -884
- package/dist/static/modules/app-core.js +124 -124
- package/dist/static/modules/conversation-panel.js +247 -247
- package/dist/static/modules/feedback-handler.js +1420 -1420
- package/dist/static/modules/image-handler.js +155 -155
- package/dist/static/modules/log-viewer.js +296 -296
- package/dist/static/modules/mcp-manager.js +474 -474
- package/dist/static/modules/prompt-manager.js +364 -364
- package/dist/static/modules/settings-manager.js +299 -299
- package/dist/static/modules/socket-manager.js +170 -170
- package/dist/static/modules/state-manager.js +352 -352
- package/dist/static/modules/timer-controller.js +243 -243
- package/dist/static/modules/ui-helpers.js +246 -246
- package/dist/static/settings.html +426 -426
- package/dist/static/settings.js +767 -767
- package/dist/static/style.css +2156 -2156
- package/dist/static/terminals.html +357 -357
- package/dist/static/terminals.js +321 -321
- package/package.json +95 -95
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
<nav class="navbar" role="navigation" aria-label="主導覽">
|
|
2
|
-
<div class="navbar-container">
|
|
3
|
-
<div class="navbar-brand">
|
|
4
|
-
<span class="navbar-logo">🎯</span>
|
|
5
|
-
<span class="navbar-title">User Feedback</span>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<button class="navbar-toggle" aria-label="展開選單" aria-expanded="false">
|
|
9
|
-
<span class="toggle-icon"></span>
|
|
10
|
-
</button>
|
|
11
|
-
|
|
12
|
-
<div class="navbar-menu">
|
|
13
|
-
<div class="navbar-links">
|
|
14
|
-
<a href="/dashboard" class="navbar-link" data-page="dashboard">
|
|
15
|
-
<span class="link-icon">📊</span>
|
|
16
|
-
<span class="link-text">Dashboard</span>
|
|
17
|
-
</a>
|
|
18
|
-
<a href="/" class="navbar-link" data-page="feedback">
|
|
19
|
-
<span class="link-icon">💬</span>
|
|
20
|
-
<span class="link-text">回覆</span>
|
|
21
|
-
</a>
|
|
22
|
-
<a href="/mcp-settings" class="navbar-link" data-page="mcp-settings">
|
|
23
|
-
<span class="link-icon">🔧</span>
|
|
24
|
-
<span class="link-text">MCP</span>
|
|
25
|
-
</a>
|
|
26
|
-
<a href="/logs" class="navbar-link" data-page="logs">
|
|
27
|
-
<span class="link-icon">📋</span>
|
|
28
|
-
<span class="link-text">日誌</span>
|
|
29
|
-
</a>
|
|
30
|
-
<a href="/terminals" class="navbar-link" data-page="terminals">
|
|
31
|
-
<span class="link-icon">💻</span>
|
|
32
|
-
<span class="link-text">終端機</span>
|
|
33
|
-
</a>
|
|
34
|
-
<a href="/settings" class="navbar-link" data-page="settings">
|
|
35
|
-
<span class="link-icon">⚙️</span>
|
|
36
|
-
<span class="link-text">設定</span>
|
|
37
|
-
</a>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
<div class="navbar-status">
|
|
41
|
-
<div class="connection-status" id="navbar-connection-status">
|
|
42
|
-
<span class="status-dot"></span>
|
|
43
|
-
<span class="status-text">連接中...</span>
|
|
44
|
-
</div>
|
|
45
|
-
<span class="version-badge" id="navbar-version">v--</span>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</nav>
|
|
1
|
+
<nav class="navbar" role="navigation" aria-label="主導覽">
|
|
2
|
+
<div class="navbar-container">
|
|
3
|
+
<div class="navbar-brand">
|
|
4
|
+
<span class="navbar-logo">🎯</span>
|
|
5
|
+
<span class="navbar-title">User Feedback</span>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<button class="navbar-toggle" aria-label="展開選單" aria-expanded="false">
|
|
9
|
+
<span class="toggle-icon"></span>
|
|
10
|
+
</button>
|
|
11
|
+
|
|
12
|
+
<div class="navbar-menu">
|
|
13
|
+
<div class="navbar-links">
|
|
14
|
+
<a href="/dashboard" class="navbar-link" data-page="dashboard">
|
|
15
|
+
<span class="link-icon">📊</span>
|
|
16
|
+
<span class="link-text">Dashboard</span>
|
|
17
|
+
</a>
|
|
18
|
+
<a href="/" class="navbar-link" data-page="feedback">
|
|
19
|
+
<span class="link-icon">💬</span>
|
|
20
|
+
<span class="link-text">回覆</span>
|
|
21
|
+
</a>
|
|
22
|
+
<a href="/mcp-settings" class="navbar-link" data-page="mcp-settings">
|
|
23
|
+
<span class="link-icon">🔧</span>
|
|
24
|
+
<span class="link-text">MCP</span>
|
|
25
|
+
</a>
|
|
26
|
+
<a href="/logs" class="navbar-link" data-page="logs">
|
|
27
|
+
<span class="link-icon">📋</span>
|
|
28
|
+
<span class="link-text">日誌</span>
|
|
29
|
+
</a>
|
|
30
|
+
<a href="/terminals" class="navbar-link" data-page="terminals">
|
|
31
|
+
<span class="link-icon">💻</span>
|
|
32
|
+
<span class="link-text">終端機</span>
|
|
33
|
+
</a>
|
|
34
|
+
<a href="/settings" class="navbar-link" data-page="settings">
|
|
35
|
+
<span class="link-icon">⚙️</span>
|
|
36
|
+
<span class="link-text">設定</span>
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="navbar-status">
|
|
41
|
+
<div class="connection-status" id="navbar-connection-status">
|
|
42
|
+
<span class="status-dot"></span>
|
|
43
|
+
<span class="status-text">連接中...</span>
|
|
44
|
+
</div>
|
|
45
|
+
<span class="version-badge" id="navbar-version">v--</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</nav>
|
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified Navigation Bar Component
|
|
3
|
-
* 可重用的導覽列組件,注入到所有頁面
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
(function () {
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
const NAVBAR_HTML_PATH = "/components/navbar.html";
|
|
10
|
-
|
|
11
|
-
class Navbar {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.container = null;
|
|
14
|
-
this.socket = null;
|
|
15
|
-
this.currentPage = this.detectCurrentPage();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 初始化導覽列
|
|
20
|
-
*/
|
|
21
|
-
async init() {
|
|
22
|
-
try {
|
|
23
|
-
await this.loadAndInject();
|
|
24
|
-
this.setupEventListeners();
|
|
25
|
-
this.highlightActivePage();
|
|
26
|
-
this.loadVersion();
|
|
27
|
-
|
|
28
|
-
// 如果頁面有 Socket.IO,連接狀態同步
|
|
29
|
-
if (window.io) {
|
|
30
|
-
this.connectSocket();
|
|
31
|
-
}
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error("[Navbar] Initialization failed:", error);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 載入並注入 HTML
|
|
39
|
-
*/
|
|
40
|
-
async loadAndInject() {
|
|
41
|
-
const response = await fetch(NAVBAR_HTML_PATH);
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`Failed to load navbar: ${response.status}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const html = await response.text();
|
|
47
|
-
const tempContainer = document.createElement("div");
|
|
48
|
-
tempContainer.innerHTML = html;
|
|
49
|
-
|
|
50
|
-
const navbar = tempContainer.querySelector(".navbar");
|
|
51
|
-
if (!navbar) {
|
|
52
|
-
throw new Error("Navbar element not found in HTML");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 注入到 body 的最前面
|
|
56
|
-
document.body.insertBefore(navbar, document.body.firstChild);
|
|
57
|
-
this.container = navbar;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 偵測當前頁面
|
|
62
|
-
*/
|
|
63
|
-
detectCurrentPage() {
|
|
64
|
-
const path = window.location.pathname;
|
|
65
|
-
|
|
66
|
-
if (path === "/" || path === "/index.html") {
|
|
67
|
-
return "feedback";
|
|
68
|
-
} else if (path.startsWith("/dashboard")) {
|
|
69
|
-
return "dashboard";
|
|
70
|
-
} else if (path.startsWith("/mcp-settings")) {
|
|
71
|
-
return "mcp-settings";
|
|
72
|
-
} else if (path.startsWith("/logs")) {
|
|
73
|
-
return "logs";
|
|
74
|
-
} else if (path.startsWith("/terminals")) {
|
|
75
|
-
return "terminals";
|
|
76
|
-
} else if (path.startsWith("/settings")) {
|
|
77
|
-
return "settings";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 高亮當前頁面連結
|
|
85
|
-
*/
|
|
86
|
-
highlightActivePage() {
|
|
87
|
-
if (!this.container || !this.currentPage) return;
|
|
88
|
-
|
|
89
|
-
const links = this.container.querySelectorAll(".navbar-link");
|
|
90
|
-
links.forEach((link) => {
|
|
91
|
-
if (link.dataset.page === this.currentPage) {
|
|
92
|
-
link.classList.add("active");
|
|
93
|
-
link.setAttribute("aria-current", "page");
|
|
94
|
-
} else {
|
|
95
|
-
link.classList.remove("active");
|
|
96
|
-
link.removeAttribute("aria-current");
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 設置事件監聽器
|
|
103
|
-
*/
|
|
104
|
-
setupEventListeners() {
|
|
105
|
-
if (!this.container) return;
|
|
106
|
-
|
|
107
|
-
// 移動端選單切換
|
|
108
|
-
const toggle = this.container.querySelector(".navbar-toggle");
|
|
109
|
-
const menu = this.container.querySelector(".navbar-menu");
|
|
110
|
-
|
|
111
|
-
if (toggle && menu) {
|
|
112
|
-
toggle.addEventListener("click", () => {
|
|
113
|
-
const isExpanded = menu.classList.toggle("active");
|
|
114
|
-
toggle.setAttribute("aria-expanded", String(isExpanded));
|
|
115
|
-
toggle.classList.toggle("active");
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 點擊連結時關閉移動端選單
|
|
120
|
-
const links = this.container.querySelectorAll(".navbar-link");
|
|
121
|
-
links.forEach((link) => {
|
|
122
|
-
link.addEventListener("click", () => {
|
|
123
|
-
if (menu.classList.contains("active")) {
|
|
124
|
-
menu.classList.remove("active");
|
|
125
|
-
toggle.classList.remove("active");
|
|
126
|
-
toggle.setAttribute("aria-expanded", "false");
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 載入版本資訊
|
|
134
|
-
*/
|
|
135
|
-
async loadVersion() {
|
|
136
|
-
try {
|
|
137
|
-
const response = await fetch("/api/version");
|
|
138
|
-
const data = await response.json();
|
|
139
|
-
|
|
140
|
-
const versionEl = this.container.querySelector("#navbar-version");
|
|
141
|
-
if (versionEl && data.version) {
|
|
142
|
-
versionEl.textContent = `v${data.version}`;
|
|
143
|
-
}
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.warn("[Navbar] Failed to load version:", error);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* 連接 Socket.IO 並同步狀態
|
|
151
|
-
*/
|
|
152
|
-
connectSocket() {
|
|
153
|
-
// 尋找全局的 socket 實例
|
|
154
|
-
if (window.socket) {
|
|
155
|
-
this.socket = window.socket;
|
|
156
|
-
this.syncSocketStatus();
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 如果還沒有 socket,創建一個用於狀態監聽
|
|
161
|
-
if (window.io) {
|
|
162
|
-
this.socket = io();
|
|
163
|
-
this.syncSocketStatus();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* 同步 Socket.IO 連接狀態
|
|
169
|
-
*/
|
|
170
|
-
syncSocketStatus() {
|
|
171
|
-
if (!this.socket || !this.container) return;
|
|
172
|
-
|
|
173
|
-
const statusEl = this.container.querySelector(
|
|
174
|
-
"#navbar-connection-status"
|
|
175
|
-
);
|
|
176
|
-
if (!statusEl) return;
|
|
177
|
-
|
|
178
|
-
// 更新狀態顯示
|
|
179
|
-
const updateStatus = (connected) => {
|
|
180
|
-
statusEl.classList.toggle("connected", connected);
|
|
181
|
-
statusEl.classList.toggle("disconnected", !connected);
|
|
182
|
-
|
|
183
|
-
const textEl = statusEl.querySelector(".status-text");
|
|
184
|
-
if (textEl) {
|
|
185
|
-
textEl.textContent = connected ? "已連接" : "已斷開";
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// 監聽連接事件
|
|
190
|
-
this.socket.on("connect", () => updateStatus(true));
|
|
191
|
-
this.socket.on("disconnect", () => updateStatus(false));
|
|
192
|
-
|
|
193
|
-
// 初始狀態
|
|
194
|
-
updateStatus(this.socket.connected);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 頁面載入時自動初始化
|
|
199
|
-
if (document.readyState === "loading") {
|
|
200
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
201
|
-
const navbar = new Navbar();
|
|
202
|
-
navbar.init();
|
|
203
|
-
});
|
|
204
|
-
} else {
|
|
205
|
-
const navbar = new Navbar();
|
|
206
|
-
navbar.init();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// 將 Navbar 暴露為全局對象(可選)
|
|
210
|
-
window.Navbar = Navbar;
|
|
211
|
-
})();
|
|
1
|
+
/**
|
|
2
|
+
* Unified Navigation Bar Component
|
|
3
|
+
* 可重用的導覽列組件,注入到所有頁面
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const NAVBAR_HTML_PATH = "/components/navbar.html";
|
|
10
|
+
|
|
11
|
+
class Navbar {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.container = null;
|
|
14
|
+
this.socket = null;
|
|
15
|
+
this.currentPage = this.detectCurrentPage();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 初始化導覽列
|
|
20
|
+
*/
|
|
21
|
+
async init() {
|
|
22
|
+
try {
|
|
23
|
+
await this.loadAndInject();
|
|
24
|
+
this.setupEventListeners();
|
|
25
|
+
this.highlightActivePage();
|
|
26
|
+
this.loadVersion();
|
|
27
|
+
|
|
28
|
+
// 如果頁面有 Socket.IO,連接狀態同步
|
|
29
|
+
if (window.io) {
|
|
30
|
+
this.connectSocket();
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error("[Navbar] Initialization failed:", error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 載入並注入 HTML
|
|
39
|
+
*/
|
|
40
|
+
async loadAndInject() {
|
|
41
|
+
const response = await fetch(NAVBAR_HTML_PATH);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`Failed to load navbar: ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const html = await response.text();
|
|
47
|
+
const tempContainer = document.createElement("div");
|
|
48
|
+
tempContainer.innerHTML = html;
|
|
49
|
+
|
|
50
|
+
const navbar = tempContainer.querySelector(".navbar");
|
|
51
|
+
if (!navbar) {
|
|
52
|
+
throw new Error("Navbar element not found in HTML");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 注入到 body 的最前面
|
|
56
|
+
document.body.insertBefore(navbar, document.body.firstChild);
|
|
57
|
+
this.container = navbar;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 偵測當前頁面
|
|
62
|
+
*/
|
|
63
|
+
detectCurrentPage() {
|
|
64
|
+
const path = window.location.pathname;
|
|
65
|
+
|
|
66
|
+
if (path === "/" || path === "/index.html") {
|
|
67
|
+
return "feedback";
|
|
68
|
+
} else if (path.startsWith("/dashboard")) {
|
|
69
|
+
return "dashboard";
|
|
70
|
+
} else if (path.startsWith("/mcp-settings")) {
|
|
71
|
+
return "mcp-settings";
|
|
72
|
+
} else if (path.startsWith("/logs")) {
|
|
73
|
+
return "logs";
|
|
74
|
+
} else if (path.startsWith("/terminals")) {
|
|
75
|
+
return "terminals";
|
|
76
|
+
} else if (path.startsWith("/settings")) {
|
|
77
|
+
return "settings";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 高亮當前頁面連結
|
|
85
|
+
*/
|
|
86
|
+
highlightActivePage() {
|
|
87
|
+
if (!this.container || !this.currentPage) return;
|
|
88
|
+
|
|
89
|
+
const links = this.container.querySelectorAll(".navbar-link");
|
|
90
|
+
links.forEach((link) => {
|
|
91
|
+
if (link.dataset.page === this.currentPage) {
|
|
92
|
+
link.classList.add("active");
|
|
93
|
+
link.setAttribute("aria-current", "page");
|
|
94
|
+
} else {
|
|
95
|
+
link.classList.remove("active");
|
|
96
|
+
link.removeAttribute("aria-current");
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 設置事件監聽器
|
|
103
|
+
*/
|
|
104
|
+
setupEventListeners() {
|
|
105
|
+
if (!this.container) return;
|
|
106
|
+
|
|
107
|
+
// 移動端選單切換
|
|
108
|
+
const toggle = this.container.querySelector(".navbar-toggle");
|
|
109
|
+
const menu = this.container.querySelector(".navbar-menu");
|
|
110
|
+
|
|
111
|
+
if (toggle && menu) {
|
|
112
|
+
toggle.addEventListener("click", () => {
|
|
113
|
+
const isExpanded = menu.classList.toggle("active");
|
|
114
|
+
toggle.setAttribute("aria-expanded", String(isExpanded));
|
|
115
|
+
toggle.classList.toggle("active");
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 點擊連結時關閉移動端選單
|
|
120
|
+
const links = this.container.querySelectorAll(".navbar-link");
|
|
121
|
+
links.forEach((link) => {
|
|
122
|
+
link.addEventListener("click", () => {
|
|
123
|
+
if (menu.classList.contains("active")) {
|
|
124
|
+
menu.classList.remove("active");
|
|
125
|
+
toggle.classList.remove("active");
|
|
126
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 載入版本資訊
|
|
134
|
+
*/
|
|
135
|
+
async loadVersion() {
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch("/api/version");
|
|
138
|
+
const data = await response.json();
|
|
139
|
+
|
|
140
|
+
const versionEl = this.container.querySelector("#navbar-version");
|
|
141
|
+
if (versionEl && data.version) {
|
|
142
|
+
versionEl.textContent = `v${data.version}`;
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn("[Navbar] Failed to load version:", error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 連接 Socket.IO 並同步狀態
|
|
151
|
+
*/
|
|
152
|
+
connectSocket() {
|
|
153
|
+
// 尋找全局的 socket 實例
|
|
154
|
+
if (window.socket) {
|
|
155
|
+
this.socket = window.socket;
|
|
156
|
+
this.syncSocketStatus();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 如果還沒有 socket,創建一個用於狀態監聽
|
|
161
|
+
if (window.io) {
|
|
162
|
+
this.socket = io();
|
|
163
|
+
this.syncSocketStatus();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 同步 Socket.IO 連接狀態
|
|
169
|
+
*/
|
|
170
|
+
syncSocketStatus() {
|
|
171
|
+
if (!this.socket || !this.container) return;
|
|
172
|
+
|
|
173
|
+
const statusEl = this.container.querySelector(
|
|
174
|
+
"#navbar-connection-status"
|
|
175
|
+
);
|
|
176
|
+
if (!statusEl) return;
|
|
177
|
+
|
|
178
|
+
// 更新狀態顯示
|
|
179
|
+
const updateStatus = (connected) => {
|
|
180
|
+
statusEl.classList.toggle("connected", connected);
|
|
181
|
+
statusEl.classList.toggle("disconnected", !connected);
|
|
182
|
+
|
|
183
|
+
const textEl = statusEl.querySelector(".status-text");
|
|
184
|
+
if (textEl) {
|
|
185
|
+
textEl.textContent = connected ? "已連接" : "已斷開";
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// 監聽連接事件
|
|
190
|
+
this.socket.on("connect", () => updateStatus(true));
|
|
191
|
+
this.socket.on("disconnect", () => updateStatus(false));
|
|
192
|
+
|
|
193
|
+
// 初始狀態
|
|
194
|
+
updateStatus(this.socket.connected);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 頁面載入時自動初始化
|
|
199
|
+
if (document.readyState === "loading") {
|
|
200
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
201
|
+
const navbar = new Navbar();
|
|
202
|
+
navbar.init();
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
const navbar = new Navbar();
|
|
206
|
+
navbar.init();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 將 Navbar 暴露為全局對象(可選)
|
|
210
|
+
window.Navbar = Navbar;
|
|
211
|
+
})();
|