@jungjaehoon/mama-os 0.32.3 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -17
- package/dist/agent/os-agent-capabilities.md +2 -2
- package/dist/api/graph-api.d.ts +4 -4
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +47 -78
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +9 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/operator-handler.d.ts +1 -1
- package/dist/api/operator-handler.js +1 -1
- package/dist/api/runtime-status-handler.d.ts +41 -0
- package/dist/api/runtime-status-handler.d.ts.map +1 -0
- package/dist/api/runtime-status-handler.js +55 -0
- package/dist/api/runtime-status-handler.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +58 -23
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +2 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +62 -1
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/api-server-init.d.ts +12 -1
- package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-server-init.js +54 -0
- package/dist/cli/runtime/api-server-init.js.map +1 -1
- package/dist/cli/runtime/utilities.d.ts +1 -1
- package/dist/cli/runtime/utilities.d.ts.map +1 -1
- package/dist/cli/runtime/utilities.js +3 -1
- package/dist/cli/runtime/utilities.js.map +1 -1
- package/dist/gateways/message-router.d.ts +4 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +62 -13
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/owner-report-inbox.d.ts +40 -0
- package/dist/gateways/owner-report-inbox.d.ts.map +1 -0
- package/dist/gateways/owner-report-inbox.js +128 -0
- package/dist/gateways/owner-report-inbox.js.map +1 -0
- package/dist/gateways/report-projection-v1.d.ts +41 -0
- package/dist/gateways/report-projection-v1.d.ts.map +1 -0
- package/dist/gateways/report-projection-v1.js +160 -0
- package/dist/gateways/report-projection-v1.js.map +1 -0
- package/dist/gateways/session-store.d.ts +22 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +76 -3
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +10 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +23 -2
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram-report-context-store.d.ts +193 -0
- package/dist/gateways/telegram-report-context-store.d.ts.map +1 -0
- package/dist/gateways/telegram-report-context-store.js +612 -0
- package/dist/gateways/telegram-report-context-store.js.map +1 -0
- package/dist/gateways/telegram.d.ts +9 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +100 -0
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +12 -0
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +2 -2
- package/dist/operator/board-slot-instructions.d.ts +1 -1
- package/dist/operator/board-slot-instructions.js +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +17 -1
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +87 -23
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-delivery-coordinator.d.ts +82 -0
- package/dist/operator/report-delivery-coordinator.d.ts.map +1 -0
- package/dist/operator/report-delivery-coordinator.js +123 -0
- package/dist/operator/report-delivery-coordinator.js.map +1 -0
- package/dist/operator/situation-report.d.ts +7 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +12 -0
- package/dist/operator/situation-report.js.map +1 -1
- package/package.json +1 -2
- package/public/viewer/js/modules/system.js +183 -0
- package/public/viewer/js/utils/ui-commands.js +4 -9
- package/public/viewer/log-viewer.html +2 -192
- package/public/viewer/operator/operator.css +1 -0
- package/public/viewer/operator/operator.js +14830 -0
- package/public/viewer/src/modules/system.ts +242 -0
- package/public/viewer/src/utils/ui-commands.ts +4 -8
- package/public/viewer/sw.js +44 -10
- package/public/viewer/viewer.css +338 -331
- package/public/viewer/viewer.html +388 -1472
- package/public/ui/assets/index-BDXDGEV6.js +0 -62
- package/public/ui/assets/index-po2tN0xq.css +0 -1
- package/public/ui/index.html +0 -14
- package/public/ui/theme-init.js +0 -20
- package/public/viewer/js/modules/agents.js +0 -1153
- package/public/viewer/js/modules/chat.js +0 -1993
- package/public/viewer/js/modules/connector-feed.js +0 -352
- package/public/viewer/js/modules/dashboard.js +0 -456
- package/public/viewer/js/modules/entity-audit.js +0 -106
- package/public/viewer/js/modules/entity-review.js +0 -66
- package/public/viewer/js/modules/settings.js +0 -1257
- package/public/viewer/src/modules/agents.ts +0 -1302
- package/public/viewer/src/modules/chat.ts +0 -2426
- package/public/viewer/src/modules/connector-feed.ts +0 -391
- package/public/viewer/src/modules/dashboard.ts +0 -498
- package/public/viewer/src/modules/entity-audit.ts +0 -154
- package/public/viewer/src/modules/entity-review.ts +0 -175
- package/public/viewer/src/modules/settings.ts +0 -1547
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { API } from '../utils/api.js';
|
|
2
|
-
import { DebugLogger } from '../utils/debug-logger.js';
|
|
3
|
-
import { createCollapsible, createResizeHandle } from '../utils/dom.js';
|
|
4
|
-
import { reportPageContext } from '../utils/ui-commands.js';
|
|
5
|
-
const logger = new DebugLogger('ConnectorFeed');
|
|
6
|
-
// ── Mobile helpers ──────────────────────────────────────────────────────────
|
|
7
|
-
const MOBILE_BREAKPOINT = 768;
|
|
8
|
-
function isMobile() {
|
|
9
|
-
return window.innerWidth < MOBILE_BREAKPOINT;
|
|
10
|
-
}
|
|
11
|
-
const backBtnStyle = 'display:flex;align-items:center;gap:6px;' +
|
|
12
|
-
'padding:8px 12px;margin-bottom:8px;' +
|
|
13
|
-
'font-size:13px;color:#6B6560;cursor:pointer;' +
|
|
14
|
-
'border:none;background:none;';
|
|
15
|
-
// ── Style constants ─────────────────────────────────────────────────────────
|
|
16
|
-
const COLOR = {
|
|
17
|
-
primary: '#1A1A1A',
|
|
18
|
-
secondary: '#6B6560',
|
|
19
|
-
tertiary: '#9E9891',
|
|
20
|
-
border: '#EDE9E1',
|
|
21
|
-
bg: '#FAFAF8',
|
|
22
|
-
red: '#D94F4F',
|
|
23
|
-
green: '#3A9E7E',
|
|
24
|
-
yellow: '#F5C518',
|
|
25
|
-
};
|
|
26
|
-
const CONNECTOR_ICON = {
|
|
27
|
-
calendar: '\u{1F4C5}',
|
|
28
|
-
slack: '\u{1F4AC}',
|
|
29
|
-
discord: '\u{1F4AC}',
|
|
30
|
-
telegram: '\u{1F4AC}',
|
|
31
|
-
trello: '\u{1F4CB}',
|
|
32
|
-
kagemusha: '\u{1F977}',
|
|
33
|
-
'claude-code': '\u{1F916}',
|
|
34
|
-
gmail: '\u{1F4E7}',
|
|
35
|
-
notion: '\u{1F4DD}',
|
|
36
|
-
obsidian: '\u{1F4D3}',
|
|
37
|
-
sheets: '\u{1F4CA}',
|
|
38
|
-
drive: '\u{1F4C1}',
|
|
39
|
-
chatwork: '\u{1F4AC}',
|
|
40
|
-
imessage: '\u{1F4AC}',
|
|
41
|
-
};
|
|
42
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
43
|
-
function esc(s) {
|
|
44
|
-
return s
|
|
45
|
-
.replace(/&/g, '&')
|
|
46
|
-
.replace(/</g, '<')
|
|
47
|
-
.replace(/>/g, '>')
|
|
48
|
-
.replace(/"/g, '"');
|
|
49
|
-
}
|
|
50
|
-
function relativeTime(ts) {
|
|
51
|
-
const diffMs = Date.now() - new Date(ts).getTime();
|
|
52
|
-
const diffMin = Math.floor(diffMs / 60_000);
|
|
53
|
-
if (diffMin < 1)
|
|
54
|
-
return 'just now';
|
|
55
|
-
if (diffMin < 60)
|
|
56
|
-
return `${diffMin}m ago`;
|
|
57
|
-
const diffHr = Math.floor(diffMin / 60);
|
|
58
|
-
if (diffHr < 24)
|
|
59
|
-
return `${diffHr}h ago`;
|
|
60
|
-
const diffDays = Math.floor(diffHr / 24);
|
|
61
|
-
return `${diffDays}d ago`;
|
|
62
|
-
}
|
|
63
|
-
// ── Connector Feed Module ───────────────────────────────────────────────────
|
|
64
|
-
export class ConnectorFeedModule {
|
|
65
|
-
container = null;
|
|
66
|
-
selectedConnector = null;
|
|
67
|
-
cachedConnectors = [];
|
|
68
|
-
resizeHandler = null;
|
|
69
|
-
mobileShowingDetail = false;
|
|
70
|
-
init() {
|
|
71
|
-
this.container = document.getElementById('feed-content');
|
|
72
|
-
if (!this.container)
|
|
73
|
-
return;
|
|
74
|
-
this.resizeHandler = () => this.handleResize();
|
|
75
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
76
|
-
this.loadConnectors();
|
|
77
|
-
}
|
|
78
|
-
async loadConnectors() {
|
|
79
|
-
if (!this.container)
|
|
80
|
-
return;
|
|
81
|
-
try {
|
|
82
|
-
const res = await API.getConnectorActivity();
|
|
83
|
-
this.cachedConnectors = res.connectors;
|
|
84
|
-
this.renderConnectorList(res.connectors);
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
logger.error('Failed to load connectors', err);
|
|
88
|
-
this.container.innerHTML =
|
|
89
|
-
'<div style="padding:40px;text-align:center;color:#9E9891;font-size:14px">Failed to load connectors.</div>';
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
renderConnectorList(connectors) {
|
|
93
|
-
if (!this.container)
|
|
94
|
-
return;
|
|
95
|
-
reportPageContext('feed', {
|
|
96
|
-
pageType: 'connector-list',
|
|
97
|
-
selectedConnector: this.selectedConnector,
|
|
98
|
-
connectorCount: connectors.length,
|
|
99
|
-
connectors: connectors.slice(0, 10).map((c) => ({
|
|
100
|
-
connector: c.connector,
|
|
101
|
-
status: c.status,
|
|
102
|
-
channel: c.channel,
|
|
103
|
-
timestamp: c.timestamp,
|
|
104
|
-
})),
|
|
105
|
-
});
|
|
106
|
-
if (connectors.length === 0) {
|
|
107
|
-
this.container.innerHTML =
|
|
108
|
-
'<div style="padding:40px;text-align:center;color:#9E9891;font-size:14px">' +
|
|
109
|
-
'No connectors configured. Add connectors in Settings to see their feeds here.</div>';
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const mobile = isMobile();
|
|
113
|
-
let html = `<div style="display:flex;gap:${mobile ? '0' : '16'}px;height:100%">`;
|
|
114
|
-
// Left: Connector list
|
|
115
|
-
const listStyle = mobile
|
|
116
|
-
? 'width:100%;overflow-y:auto;padding-right:0'
|
|
117
|
-
: `width:280px;min-width:280px;overflow-y:auto;border-right:1px solid ${COLOR.border};padding-right:16px`;
|
|
118
|
-
html += `<div id="connector-list" style="${listStyle}">`;
|
|
119
|
-
html += `<h2 style="font-family:Inter,'Noto Sans KR',sans-serif;font-size:16px;font-weight:600;color:${COLOR.primary};margin:0 0 12px 0">Connectors</h2>`;
|
|
120
|
-
for (const c of connectors) {
|
|
121
|
-
const isActive = this.selectedConnector === c.connector;
|
|
122
|
-
const bgColor = isActive ? '#F5F3EF' : 'transparent';
|
|
123
|
-
const borderLeft = isActive ? `3px solid ${COLOR.primary}` : '3px solid transparent';
|
|
124
|
-
const icon = CONNECTOR_ICON[c.connector] || '\u{1F517}';
|
|
125
|
-
let statusHtml = '';
|
|
126
|
-
if (c.status === 'active') {
|
|
127
|
-
const rel = relativeTime(c.timestamp);
|
|
128
|
-
statusHtml = `<span style="font-size:11px;color:${COLOR.green}">\u25CF active</span> <span style="font-size:10px;color:${COLOR.tertiary}">${esc(rel)}</span>`;
|
|
129
|
-
}
|
|
130
|
-
else if (c.status === 'idle') {
|
|
131
|
-
statusHtml = `<span style="font-size:11px;color:${COLOR.tertiary}">\u25CB idle</span>`;
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
statusHtml = `<span style="font-size:11px;color:${COLOR.red}">\u26A0\uFE0F \uBBF8\uC5F0\uACB0</span>`;
|
|
135
|
-
}
|
|
136
|
-
html +=
|
|
137
|
-
`<div class="connector-item" data-connector="${esc(c.connector)}" ` +
|
|
138
|
-
`style="padding:10px 12px;margin-bottom:4px;border-radius:4px;cursor:pointer;` +
|
|
139
|
-
`background:${bgColor};border-left:${borderLeft};transition:background 0.15s">` +
|
|
140
|
-
`<div style="display:flex;align-items:center;gap:8px">` +
|
|
141
|
-
`<span style="font-size:16px">${icon}</span>` +
|
|
142
|
-
`<span style="font-size:13px;font-weight:600;color:${COLOR.primary}">${esc(c.connector)}</span>` +
|
|
143
|
-
`</div>` +
|
|
144
|
-
`<div style="margin-top:4px;padding-left:24px">${statusHtml}</div>` +
|
|
145
|
-
`</div>`;
|
|
146
|
-
}
|
|
147
|
-
html += '</div>';
|
|
148
|
-
// Right: Detail panel
|
|
149
|
-
const detailDisplay = mobile ? 'display:none;' : '';
|
|
150
|
-
const detailStyle = mobile
|
|
151
|
-
? `flex:1;overflow-y:auto;${detailDisplay}width:100%`
|
|
152
|
-
: `flex:1;overflow-y:auto;padding-left:16px;${detailDisplay}`;
|
|
153
|
-
html += `<div id="connector-detail" style="${detailStyle}">`;
|
|
154
|
-
if (this.selectedConnector) {
|
|
155
|
-
html += `<div style="color:${COLOR.tertiary};font-size:13px">Loading...</div>`;
|
|
156
|
-
}
|
|
157
|
-
else if (!mobile) {
|
|
158
|
-
html +=
|
|
159
|
-
`<div style="padding:40px;text-align:center;color:${COLOR.tertiary};font-size:13px">` +
|
|
160
|
-
'Select a connector to view its feed.</div>';
|
|
161
|
-
}
|
|
162
|
-
html += '</div></div>';
|
|
163
|
-
this.container.innerHTML = html;
|
|
164
|
-
// Attach resize handle to connector list panel (desktop only)
|
|
165
|
-
if (!mobile) {
|
|
166
|
-
const listPanel = document.getElementById('connector-list');
|
|
167
|
-
if (listPanel) {
|
|
168
|
-
createResizeHandle(listPanel, {
|
|
169
|
-
storageKey: 'feed-connector-list-width',
|
|
170
|
-
minWidth: 180,
|
|
171
|
-
maxWidth: 500,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Bind click events
|
|
176
|
-
this.container.querySelectorAll('.connector-item').forEach((el) => {
|
|
177
|
-
el.addEventListener('click', () => {
|
|
178
|
-
const name = el.dataset.connector;
|
|
179
|
-
if (name)
|
|
180
|
-
this.selectConnector(name, connectors);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
// Auto-select first connector if none selected (desktop only)
|
|
184
|
-
if (!this.selectedConnector && connectors.length > 0 && !mobile) {
|
|
185
|
-
this.selectConnector(connectors[0].connector, connectors);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
async selectConnector(name, _connectors) {
|
|
189
|
-
this.selectedConnector = name;
|
|
190
|
-
// Update list selection highlight
|
|
191
|
-
this.container?.querySelectorAll('.connector-item').forEach((el) => {
|
|
192
|
-
const id = el.dataset.connector;
|
|
193
|
-
const isActive = id === name;
|
|
194
|
-
el.style.background = isActive ? '#F5F3EF' : 'transparent';
|
|
195
|
-
el.style.borderLeft = isActive
|
|
196
|
-
? `3px solid ${COLOR.primary}`
|
|
197
|
-
: '3px solid transparent';
|
|
198
|
-
});
|
|
199
|
-
// Mobile: hide list, show detail
|
|
200
|
-
if (isMobile()) {
|
|
201
|
-
this.mobileShowingDetail = true;
|
|
202
|
-
const list = document.getElementById('connector-list');
|
|
203
|
-
const detail = document.getElementById('connector-detail');
|
|
204
|
-
if (list)
|
|
205
|
-
list.style.display = 'none';
|
|
206
|
-
if (detail) {
|
|
207
|
-
detail.style.display = '';
|
|
208
|
-
detail.style.width = '100%';
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const detail = document.getElementById('connector-detail');
|
|
212
|
-
if (!detail)
|
|
213
|
-
return;
|
|
214
|
-
detail.innerHTML = `<div style="color:${COLOR.tertiary};font-size:13px">Loading feed...</div>`;
|
|
215
|
-
try {
|
|
216
|
-
const res = await API.getConnectorFeed(name);
|
|
217
|
-
this.renderFeedDetail(detail, name, res.feed);
|
|
218
|
-
}
|
|
219
|
-
catch (err) {
|
|
220
|
-
logger.error('Failed to load connector feed', err);
|
|
221
|
-
detail.innerHTML = `<div style="color:${COLOR.red};font-size:13px">Failed to load feed.</div>`;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
showMobileList() {
|
|
225
|
-
this.mobileShowingDetail = false;
|
|
226
|
-
this.selectedConnector = null;
|
|
227
|
-
const list = document.getElementById('connector-list');
|
|
228
|
-
const detail = document.getElementById('connector-detail');
|
|
229
|
-
if (list)
|
|
230
|
-
list.style.display = '';
|
|
231
|
-
if (detail)
|
|
232
|
-
detail.style.display = 'none';
|
|
233
|
-
reportPageContext('feed', {
|
|
234
|
-
pageType: 'connector-list',
|
|
235
|
-
selectedConnector: null,
|
|
236
|
-
connectorCount: this.cachedConnectors.length,
|
|
237
|
-
channelCount: 0,
|
|
238
|
-
channels: [],
|
|
239
|
-
connectors: this.cachedConnectors.slice(0, 10).map((c) => ({
|
|
240
|
-
connector: c.connector,
|
|
241
|
-
status: c.status,
|
|
242
|
-
channel: c.channel,
|
|
243
|
-
timestamp: c.timestamp,
|
|
244
|
-
})),
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
handleResize() {
|
|
248
|
-
const list = document.getElementById('connector-list');
|
|
249
|
-
const detail = document.getElementById('connector-detail');
|
|
250
|
-
if (!list || !detail)
|
|
251
|
-
return;
|
|
252
|
-
if (isMobile()) {
|
|
253
|
-
// Mobile: show one panel at a time
|
|
254
|
-
list.style.width = '100%';
|
|
255
|
-
list.style.minWidth = '';
|
|
256
|
-
list.style.borderRight = 'none';
|
|
257
|
-
list.style.paddingRight = '0';
|
|
258
|
-
detail.style.paddingLeft = '0';
|
|
259
|
-
detail.style.width = '100%';
|
|
260
|
-
if (this.mobileShowingDetail) {
|
|
261
|
-
list.style.display = 'none';
|
|
262
|
-
detail.style.display = '';
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
list.style.display = '';
|
|
266
|
-
detail.style.display = 'none';
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
// Desktop: side-by-side
|
|
271
|
-
list.style.display = '';
|
|
272
|
-
list.style.width = '280px';
|
|
273
|
-
list.style.minWidth = '280px';
|
|
274
|
-
list.style.borderRight = `1px solid ${COLOR.border}`;
|
|
275
|
-
list.style.paddingRight = '16px';
|
|
276
|
-
detail.style.display = '';
|
|
277
|
-
detail.style.paddingLeft = '16px';
|
|
278
|
-
detail.style.width = '';
|
|
279
|
-
this.mobileShowingDetail = false;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
renderFeedDetail(container, name, feed) {
|
|
283
|
-
reportPageContext('feed', {
|
|
284
|
-
pageType: 'connector-detail',
|
|
285
|
-
selectedConnector: name,
|
|
286
|
-
channelCount: feed.length,
|
|
287
|
-
channels: feed.slice(0, 10).map((channel) => ({
|
|
288
|
-
channel: channel.channel,
|
|
289
|
-
itemCount: channel.items.length,
|
|
290
|
-
})),
|
|
291
|
-
});
|
|
292
|
-
const icon = CONNECTOR_ICON[name] || '\u{1F517}';
|
|
293
|
-
// Clear and build header
|
|
294
|
-
container.innerHTML = '';
|
|
295
|
-
// Mobile back button
|
|
296
|
-
if (isMobile()) {
|
|
297
|
-
const backBtn = document.createElement('button');
|
|
298
|
-
backBtn.setAttribute('style', backBtnStyle);
|
|
299
|
-
backBtn.innerHTML = '\u2190 Connectors';
|
|
300
|
-
backBtn.addEventListener('click', () => this.showMobileList());
|
|
301
|
-
container.appendChild(backBtn);
|
|
302
|
-
}
|
|
303
|
-
const header = document.createElement('div');
|
|
304
|
-
header.setAttribute('style', 'margin-bottom:16px');
|
|
305
|
-
header.innerHTML =
|
|
306
|
-
`<h2 style="font-family:Inter,'Noto Sans KR',sans-serif;font-size:16px;font-weight:600;color:${COLOR.primary};margin:0">${icon} ${esc(name)}</h2>` +
|
|
307
|
-
`<div style="font-size:11px;color:${COLOR.tertiary};margin-top:4px">${feed.length} channel${feed.length !== 1 ? 's' : ''}</div>`;
|
|
308
|
-
container.appendChild(header);
|
|
309
|
-
if (feed.length === 0) {
|
|
310
|
-
const empty = document.createElement('div');
|
|
311
|
-
empty.setAttribute('style', `padding:20px;text-align:center;color:${COLOR.tertiary};font-size:13px`);
|
|
312
|
-
empty.textContent = 'No feed data available for this connector.';
|
|
313
|
-
container.appendChild(empty);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
// Channel-grouped items with collapsible headers and newest-first sort
|
|
317
|
-
for (const ch of feed) {
|
|
318
|
-
// Sort items newest first by timestamp
|
|
319
|
-
const sorted = [...ch.items].sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
320
|
-
let itemsHtml = '';
|
|
321
|
-
for (const item of sorted) {
|
|
322
|
-
const rel = relativeTime(item.timestamp);
|
|
323
|
-
const preview = item.content.replace(/\n/g, ' ').slice(0, 120);
|
|
324
|
-
itemsHtml += `<div style="padding:8px 12px;border-bottom:1px solid ${COLOR.border}">`;
|
|
325
|
-
itemsHtml += `<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:2px">`;
|
|
326
|
-
itemsHtml += `<span style="font-size:11px;font-weight:600;color:${COLOR.primary}">${esc(item.author)}</span>`;
|
|
327
|
-
itemsHtml += `<span style="font-size:10px;color:${COLOR.tertiary};padding:0 4px;background:${COLOR.bg};border-radius:2px">${esc(item.type)}</span>`;
|
|
328
|
-
itemsHtml += `<span style="font-size:10px;color:${COLOR.tertiary};margin-left:auto;white-space:nowrap">${esc(rel)}</span>`;
|
|
329
|
-
itemsHtml += `</div>`;
|
|
330
|
-
itemsHtml += `<div style="font-size:11px;color:${COLOR.secondary};line-height:1.5">${esc(preview)}</div>`;
|
|
331
|
-
itemsHtml += `</div>`;
|
|
332
|
-
}
|
|
333
|
-
const channelHeading = `#${esc(ch.channel)} (${ch.items.length} items)`;
|
|
334
|
-
const channelGroup = createCollapsible(channelHeading, itemsHtml, {
|
|
335
|
-
storageKey: `feed-channel-${name}-${ch.channel}`,
|
|
336
|
-
defaultOpen: true,
|
|
337
|
-
headingStyle: `font-size:12px;font-weight:600;color:${COLOR.primary};padding:10px 12px;background:${COLOR.bg};`,
|
|
338
|
-
containerStyle: `margin-bottom:16px;border:1px solid ${COLOR.border};border-radius:4px;background:#fff;overflow:hidden;`,
|
|
339
|
-
});
|
|
340
|
-
container.appendChild(channelGroup);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
destroy() {
|
|
344
|
-
this.selectedConnector = null;
|
|
345
|
-
this.cachedConnectors = [];
|
|
346
|
-
this.mobileShowingDetail = false;
|
|
347
|
-
if (this.resizeHandler) {
|
|
348
|
-
window.removeEventListener('resize', this.resizeHandler);
|
|
349
|
-
this.resizeHandler = null;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|