kingkont 0.17.4 → 0.17.6
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/package.json +1 -1
- package/renderer/board.js +15 -0
- package/renderer/generate.js +7 -1
- package/renderer/notifyPanel.js +25 -6
- package/renderer/state.js +6 -2
- package/server.js +2 -2
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -454,7 +454,22 @@ function fmtRelativeTime(ts) {
|
|
|
454
454
|
return new Date(ts).toLocaleDateString();
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
// Дедуп: на Cmd+R / WS-event burst несколько вызовов могли race'иться
|
|
458
|
+
// (clear grid → await getRecents → append → второй call в это же время →
|
|
459
|
+
// дубли карточек / наезжают). Если уже рендерим — отметим что нужен
|
|
460
|
+
// follow-up render и выйдем; запустим его после текущего.
|
|
461
|
+
let _wrInFlight = false;
|
|
462
|
+
let _wrPending = false;
|
|
457
463
|
async function renderWelcomeRecents() {
|
|
464
|
+
if (_wrInFlight) { _wrPending = true; return; }
|
|
465
|
+
_wrInFlight = true;
|
|
466
|
+
try { await _renderWelcomeRecentsInner(); }
|
|
467
|
+
finally {
|
|
468
|
+
_wrInFlight = false;
|
|
469
|
+
if (_wrPending) { _wrPending = false; renderWelcomeRecents().catch(() => {}); }
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function _renderWelcomeRecentsInner() {
|
|
458
473
|
const grid = $('welcomeRecentGrid');
|
|
459
474
|
const wrap = $('welcomeRecent');
|
|
460
475
|
const titleEl = $('welcomeRecentTitle');
|
package/renderer/generate.js
CHANGED
|
@@ -2008,7 +2008,13 @@ async function pollJob(job, nodeId, bKey, boardHandle, kind) {
|
|
|
2008
2008
|
const isCurrent = state.currentBoard?.key === bKey;
|
|
2009
2009
|
if (typeof showToast === 'function') {
|
|
2010
2010
|
const where = isCurrent ? '' : ` в ${bKey}`;
|
|
2011
|
-
|
|
2011
|
+
// target — для click-навигации в notify-панели (откроется доска +
|
|
2012
|
+
// подсветится нода).
|
|
2013
|
+
showToast(
|
|
2014
|
+
`✓ ${kind} «${nodeName || nodeId.slice(0,6)}» готов${where}`,
|
|
2015
|
+
isCurrent ? 'ok' : 'info',
|
|
2016
|
+
{ target: { projectKey: projectKeyAtCompletion, boardKey: bKey, nodeId } },
|
|
2017
|
+
);
|
|
2012
2018
|
}
|
|
2013
2019
|
// System notification — только когда генерация завершилась в фоне
|
|
2014
2020
|
// (не на активной доске или когда окно скрыто). На активном UI
|
package/renderer/notifyPanel.js
CHANGED
|
@@ -89,18 +89,36 @@
|
|
|
89
89
|
const dt = new Date(e.ts);
|
|
90
90
|
const time = `${String(dt.getHours()).padStart(2,'0')}:${String(dt.getMinutes()).padStart(2,'0')}:${String(dt.getSeconds()).padStart(2,'0')}`;
|
|
91
91
|
const clickable = !!e.target;
|
|
92
|
-
row.style.cssText = `padding:8px 10px; border-radius:4px; margin-bottom:4px; background:${c}22; border-left:3px solid ${c}; cursor:${clickable ? 'pointer' : 'default'}; transition:background 0.1s;`;
|
|
92
|
+
row.style.cssText = `position:relative; padding:8px 10px 8px 10px; border-radius:4px; margin-bottom:4px; background:${c}22; border-left:3px solid ${c}; cursor:${clickable ? 'pointer' : 'default'}; transition:background 0.1s;`;
|
|
93
93
|
if (clickable) row.title = 'Перейти к объекту';
|
|
94
94
|
const targetHint = clickable ? `<span style="color:#9ab; font-size:10px; margin-left:6px;">↗</span>` : '';
|
|
95
|
-
row.innerHTML = `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start;">
|
|
95
|
+
row.innerHTML = `<div style="display:flex; justify-content:space-between; gap:8px; align-items:flex-start; padding-right:18px;">
|
|
96
96
|
<div style="color:#e0e0e0; font-size:12px; line-height:1.4; word-break:break-word; flex:1;"></div>
|
|
97
97
|
<span style="color:#777; font-size:10px; font-family:ui-monospace,monospace; flex-shrink:0;">${time}${targetHint}</span>
|
|
98
98
|
</div>`;
|
|
99
99
|
row.querySelector('div > div').textContent = e.text;
|
|
100
|
+
// Кнопка × для удаления конкретного event'a (не блокирует клик по row).
|
|
101
|
+
const delBtn = document.createElement('button');
|
|
102
|
+
delBtn.textContent = '×';
|
|
103
|
+
delBtn.title = 'Удалить';
|
|
104
|
+
delBtn.style.cssText = 'position:absolute; top:2px; right:4px; background:transparent; border:none; color:#888; cursor:pointer; font-size:14px; line-height:1; padding:2px 6px; opacity:0.5; transition:opacity 0.1s;';
|
|
105
|
+
delBtn.addEventListener('mouseenter', () => delBtn.style.opacity = '1');
|
|
106
|
+
delBtn.addEventListener('mouseleave', () => delBtn.style.opacity = '0.5');
|
|
107
|
+
delBtn.addEventListener('click', (ev) => {
|
|
108
|
+
ev.stopPropagation();
|
|
109
|
+
// Find by ts (events shifted by render, индекс i может измениться к click'у).
|
|
110
|
+
const idx = events.findIndex(x => x.ts === e.ts && x.text === e.text);
|
|
111
|
+
if (idx >= 0) events.splice(idx, 1);
|
|
112
|
+
render();
|
|
113
|
+
});
|
|
114
|
+
row.appendChild(delBtn);
|
|
100
115
|
if (clickable) {
|
|
101
116
|
row.addEventListener('mouseenter', () => row.style.background = `${c}44`);
|
|
102
117
|
row.addEventListener('mouseleave', () => row.style.background = `${c}22`);
|
|
103
|
-
row.addEventListener('click', () =>
|
|
118
|
+
row.addEventListener('click', (ev) => {
|
|
119
|
+
if (ev.target === delBtn) return;
|
|
120
|
+
navigateToTarget(e.target);
|
|
121
|
+
});
|
|
104
122
|
}
|
|
105
123
|
list.appendChild(row);
|
|
106
124
|
}
|
|
@@ -188,11 +206,12 @@
|
|
|
188
206
|
// Init: ensure UI exists ASAP, hook into showToast и WS-events.
|
|
189
207
|
document.addEventListener('DOMContentLoaded', () => {
|
|
190
208
|
_ensureUI();
|
|
191
|
-
// Wrap showToast чтобы дублировать в
|
|
209
|
+
// Wrap showToast чтобы дублировать в панель + forward target для
|
|
210
|
+
// click-навигации (showToast(text, kind, {target: {...}})).
|
|
192
211
|
if (typeof window.showToast === 'function') {
|
|
193
212
|
const orig = window.showToast;
|
|
194
|
-
window.showToast = function (text, kind) {
|
|
195
|
-
try { addEvent({ kind: kind || 'info', text }); } catch {}
|
|
213
|
+
window.showToast = function (text, kind, opts) {
|
|
214
|
+
try { addEvent({ kind: kind || 'info', text, target: opts?.target || null }); } catch {}
|
|
196
215
|
return orig.apply(this, arguments);
|
|
197
216
|
};
|
|
198
217
|
}
|
package/renderer/state.js
CHANGED
|
@@ -201,12 +201,16 @@ window.systemNotify = systemNotify;
|
|
|
201
201
|
// resume сработал, ...). Стек справа сверху. Auto-dismiss:
|
|
202
202
|
// - ok/info — 10s (юзер должен заметить успех)
|
|
203
203
|
// - error — 30s (важно, не пропустить)
|
|
204
|
-
function showToast(text, kind) {
|
|
204
|
+
function showToast(text, kind, opts) {
|
|
205
|
+
// opts.target: {projectKey, boardKey, nodeId} — toast становится кликабельным.
|
|
205
206
|
let host = document.getElementById('toastHost');
|
|
206
207
|
if (!host) {
|
|
207
208
|
host = document.createElement('div');
|
|
208
209
|
host.id = 'toastHost';
|
|
209
|
-
|
|
210
|
+
// Bottom-left стек — рядом с 🔔 кнопкой нотификаций. Новые toast'ы
|
|
211
|
+
// появляются СНИЗУ (flex-direction: column-reverse), так что они
|
|
212
|
+
// «всплывают» вверх от кнопки.
|
|
213
|
+
host.style.cssText = 'position:fixed; left:52px; bottom:12px; z-index:9999; display:flex; flex-direction:column-reverse; gap:8px; pointer-events:none; max-width:340px;';
|
|
210
214
|
document.body.appendChild(host);
|
|
211
215
|
}
|
|
212
216
|
const t = document.createElement('div');
|
package/server.js
CHANGED
|
@@ -356,9 +356,9 @@ async function handleChatClear(req, res) {
|
|
|
356
356
|
async function handleJobsTrack(req, res) {
|
|
357
357
|
try {
|
|
358
358
|
const body = await readJson(req);
|
|
359
|
-
const { action, projectKey, jobId, kind, name, type, taskId } = body || {};
|
|
359
|
+
const { action, projectKey, jobId, kind, name, type, taskId, boardKey } = body || {};
|
|
360
360
|
if (!projectKey || !jobId) return send(res, 400, { error: 'projectKey + jobId обязательны' });
|
|
361
|
-
if (action === 'start') jobsHub.start({ projectKey, jobId, kind, name, type, taskId });
|
|
361
|
+
if (action === 'start') jobsHub.start({ projectKey, jobId, kind, name, type, taskId, boardKey });
|
|
362
362
|
else if (action === 'end') jobsHub.end({ projectKey, jobId });
|
|
363
363
|
else return send(res, 400, { error: 'action: start|end' });
|
|
364
364
|
send(res, 200, { ok: true });
|