local-kanban 1.9.0 → 1.10.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/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/public/js/board.js +4 -1
- package/public/js/chaos.js +3 -6
- package/public/js/core.js +10 -1
- package/public/js/project.js +7 -1
- package/public/js/sidebar.js +6 -9
- package/public/style.css +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
3
3
|
Notable changes, newest first. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
4
4
|
and the project uses [semantic versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.10.0]
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **The deploy skill list only offers deploy skills.** On the project settings page it listed
|
|
11
|
+
everything in your skills folder — on a real board that is ten entries when one of them is an
|
|
12
|
+
actual deploy skill, and the board's own skill sits among them, offered as a way to deploy your
|
|
13
|
+
project. The wizard already filtered this; the project page did not, because the rule lived in
|
|
14
|
+
the wizard's file. Now both ask the same function. A skill your project already points at stays
|
|
15
|
+
in the list even if it falls outside the filter, so saving never quietly clears the field.
|
|
16
|
+
|
|
17
|
+
- **A dropdown opens where there is room.** The custom select always opened downward with a fixed
|
|
18
|
+
height, so near the bottom of a dialog its list ran past the dialog and under the buttons. It now
|
|
19
|
+
measures the space first — against the dialog, not just the window — and flips up when it has to.
|
|
20
|
+
The rule already existed for the picker menus; there is one copy of it now instead of one and a
|
|
21
|
+
half.
|
|
22
|
+
|
|
23
|
+
- **The Add project form is not cut off.** Its body was capped at 62% of the window height while
|
|
24
|
+
the dialog itself used far less, so on a 800px-tall window the last field lost 37 pixels and the
|
|
25
|
+
hint under it never showed at all. The cap now follows the space actually available.
|
|
26
|
+
|
|
27
|
+
- **Picking the project folder starts with the folder, not with a list.** The step opened with
|
|
28
|
+
every unregistered folder in your projects directory — 31 entries on a real machine, eight of
|
|
29
|
+
them visible — and the path field and "Choose…" button were hidden behind an entry near the
|
|
30
|
+
bottom of that same list. The list now holds only the three modes (a folder, no folder, clone
|
|
31
|
+
from git), and the folder one — the default — shows the path field and the button straight away.
|
|
32
|
+
The system dialog no longer opens by itself just because you reached the step. Your project
|
|
33
|
+
folders are still one click away: the built-in browser opens on that very directory.
|
|
34
|
+
|
|
6
35
|
## [1.9.0]
|
|
7
36
|
|
|
8
37
|
### Added
|
package/package.json
CHANGED
package/public/js/board.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { $, ALL, ALL_STATUSES, DONE_LIMIT,
|
|
2
|
+
import { $, ALL, ALL_STATUSES, api, DONE_LIMIT, esc, ic, LABEL_COLORS, LABEL_SELECTABLE, noteUnsaved, popoverPlacement, PRI_ICON, PRI_LEVELS, seg, state, tr } from './core.js';
|
|
3
3
|
import { markKbCursor, openCardMenu, openDrawer, openDrawerNew, setKbCursor, statusOptionList } from './drawer.js';
|
|
4
4
|
import { getSetting, plural } from './settings.js';
|
|
5
5
|
import { styledAlert, styledConfirm } from './sidebar.js';
|
|
@@ -676,6 +676,9 @@ export function buildSelect(host, opts) {
|
|
|
676
676
|
if (host.classList.contains('open')) { close(); return; }
|
|
677
677
|
document.querySelectorAll('.kbsel.open').forEach((el) => { if (el !== host) el.classList.remove('open'); });
|
|
678
678
|
host.classList.add('open');
|
|
679
|
+
const place = popoverPlacement(host.querySelector('.kbsel-btn'));
|
|
680
|
+
list.classList.toggle('up', place.openUp);
|
|
681
|
+
list.style.maxHeight = `${place.maxHeight}px`;
|
|
679
682
|
if (opts.search) { const si = host.querySelector('.kbsel-search-input'); si.value = ''; filterOpts(''); setTimeout(() => si.focus(), 0); }
|
|
680
683
|
setTimeout(() => document.addEventListener('click', outside), 0);
|
|
681
684
|
};
|
package/public/js/chaos.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { buildSelect, selVal } from './board.js';
|
|
3
|
-
import { $,
|
|
3
|
+
import { $, api, esc, ic, LABEL_COLORS, LABEL_SELECTABLE, popoverPlacement, PRI_ICON, PRI_LEVELS, state, tr } from './core.js';
|
|
4
4
|
import { openDrawer, openLightbox, uploadAttachment } from './drawer.js';
|
|
5
5
|
import { loadProjects } from './sidebar.js';
|
|
6
6
|
import { autoGrow, refresh, titleOr } from './sse.js';
|
|
@@ -159,11 +159,8 @@ function chaosPopover(anchor, items, onPick) {
|
|
|
159
159
|
pop.innerHTML = items.map((o) => `<div class="chaos-pop-opt${o.active ? ' active' : ''}" data-v="${esc(String(o.v))}">${o.html}</div>`).join('');
|
|
160
160
|
document.body.appendChild(pop);
|
|
161
161
|
const r = anchor.getBoundingClientRect();
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const above = r.top - margin;
|
|
165
|
-
const openUp = below < 200 && above > below;
|
|
166
|
-
pop.style.maxHeight = `${Math.max(120, (openUp ? above : below) - 5)}px`;
|
|
162
|
+
const { openUp, maxHeight } = popoverPlacement(anchor);
|
|
163
|
+
pop.style.maxHeight = `${maxHeight}px`;
|
|
167
164
|
let left = r.left;
|
|
168
165
|
if (left + pop.offsetWidth > innerWidth - 8) left = innerWidth - pop.offsetWidth - 8;
|
|
169
166
|
const top = openUp ? (r.top - pop.offsetHeight - 5) : (r.bottom + 5);
|
package/public/js/core.js
CHANGED
|
@@ -83,7 +83,7 @@ const I18N_RU = {
|
|
|
83
83
|
'All projects': 'Все проекты',
|
|
84
84
|
'all self-healed': 'всё починилось само',
|
|
85
85
|
'All set!': 'Готово!',
|
|
86
|
-
'
|
|
86
|
+
'a folder on this computer': 'папка на этом компьютере',
|
|
87
87
|
'Appearance': 'Оформление',
|
|
88
88
|
'applies after page reload': 'применится после перезагрузки страницы',
|
|
89
89
|
'Archive': 'Архивировать',
|
|
@@ -758,6 +758,15 @@ export const state = {
|
|
|
758
758
|
|
|
759
759
|
export function $(id) { return document.getElementById(id); }
|
|
760
760
|
|
|
761
|
+
export function popoverPlacement(anchor, { flipBelow = 200, floor = 120, gap = 10 } = {}) {
|
|
762
|
+
const r = anchor.getBoundingClientRect();
|
|
763
|
+
const box = anchor.closest?.('.modal')?.getBoundingClientRect();
|
|
764
|
+
const below = Math.min(window.innerHeight, box ? box.bottom : Infinity) - r.bottom - gap;
|
|
765
|
+
const above = r.top - Math.max(0, box ? box.top : 0) - gap;
|
|
766
|
+
const openUp = below < flipBelow && above > below;
|
|
767
|
+
return { openUp, maxHeight: Math.max(floor, (openUp ? above : below) - 5) };
|
|
768
|
+
}
|
|
769
|
+
|
|
761
770
|
export const SETUP_PROMPT = {
|
|
762
771
|
ru: `Запусти скилл kanban.
|
|
763
772
|
|
package/public/js/project.js
CHANGED
|
@@ -124,6 +124,12 @@ export async function renderProjectSettings(force) {
|
|
|
124
124
|
renderDocs(p);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
export function deploySkills(info) {
|
|
128
|
+
const generic = info?.generic_deploy_skill || 'deploy';
|
|
129
|
+
const own = new Set(info?.own_skills || []);
|
|
130
|
+
return (info?.items || []).filter((s) => s.name === generic || s.used_by?.length || own.has(s.name));
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
export function skillOptions(cur, list) {
|
|
128
134
|
const names = list ? list.map((s) => s.name) : (cur ? [cur] : []);
|
|
129
135
|
if (cur && !names.includes(cur)) names.unshift(cur);
|
|
@@ -145,7 +151,7 @@ async function loadSkillOptions(p) {
|
|
|
145
151
|
try { info = await api('GET', '/api/skills', null, { quiet: true }); }
|
|
146
152
|
catch { return; }
|
|
147
153
|
if (!$('ps-skill') || $('projset').dataset.slug !== p.slug) return;
|
|
148
|
-
buildSkillSelect(p, info
|
|
154
|
+
buildSkillSelect(p, deploySkills(info));
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
async function renderDocs(p) {
|
package/public/js/sidebar.js
CHANGED
|
@@ -6,7 +6,7 @@ import { renderChaos } from './chaos.js';
|
|
|
6
6
|
import { $, ALL, CALENDAR, CHAOS, DASH, HIDDEN_SECTIONS, HORIZON, LANG, SETTINGS, SIDEBAR_SECTIONS, api, esc, ic, seg, setupPrompt, state, tr } from './core.js';
|
|
7
7
|
import { renderDashboard } from './dash.js';
|
|
8
8
|
import { renderCalendar, renderHorizon } from './horizon.js';
|
|
9
|
-
import { openProjectSettings, renderProjectSettings, skillOptions } from './project.js';
|
|
9
|
+
import { openProjectSettings, renderProjectSettings, skillOptions, deploySkills } from './project.js';
|
|
10
10
|
import { openSettingsModal, plural, setSetting, syncLangToServer } from './settings.js';
|
|
11
11
|
import { copyText, refresh, showView } from './sse.js';
|
|
12
12
|
|
|
@@ -442,17 +442,17 @@ function openProjectWizard() {
|
|
|
442
442
|
if (folderSel) {
|
|
443
443
|
const options = [
|
|
444
444
|
...(type === 'local' ? [{ value: '', label: tr('no folder — just a task list') }] : []),
|
|
445
|
-
|
|
446
|
-
{ value: '__manual__', label: tr('another folder on this computer…') },
|
|
445
|
+
{ value: '__manual__', label: tr('a folder on this computer') },
|
|
447
446
|
...(type === 'localserver' ? [{ value: '__clone__', label: tr('clone from a git URL…') }] : []),
|
|
448
447
|
];
|
|
449
448
|
const onFolderChange = (v) => {
|
|
450
449
|
$('wiz-path-row').classList.toggle('hidden', v !== '__manual__');
|
|
451
450
|
$('wiz-browser').classList.add('hidden');
|
|
452
451
|
$('wiz-git').classList.toggle('hidden', v !== '__clone__');
|
|
453
|
-
if (v === '__manual__') openFolderPicker();
|
|
454
452
|
};
|
|
455
|
-
|
|
453
|
+
const start = type === 'local' ? '' : '__manual__';
|
|
454
|
+
buildSelect(folderSel, { value: start, options, onChange: onFolderChange });
|
|
455
|
+
onFolderChange(start);
|
|
456
456
|
$('wiz-browse').onclick = openFolderPicker;
|
|
457
457
|
}
|
|
458
458
|
const skillHost = $('wiz-skill');
|
|
@@ -462,8 +462,7 @@ function openProjectWizard() {
|
|
|
462
462
|
.then((info) => {
|
|
463
463
|
if (!$('wiz-skill')) return;
|
|
464
464
|
const generic = info.generic_deploy_skill || GENERIC_DEPLOY;
|
|
465
|
-
const
|
|
466
|
-
const deployish = (info.items || []).filter((s) => s.name === generic || s.used_by?.length || own.has(s.name));
|
|
465
|
+
const deployish = deploySkills(info);
|
|
467
466
|
const has = deployish.some((s) => s.name === generic);
|
|
468
467
|
buildSelect($('wiz-skill'), { value: has ? generic : '', options: skillOptions(generic, deployish) });
|
|
469
468
|
})
|
|
@@ -498,8 +497,6 @@ function openProjectWizard() {
|
|
|
498
497
|
btn.textContent = tr('cloning…');
|
|
499
498
|
const r = await api('POST', '/api/projects/clone', { url, name: slug });
|
|
500
499
|
body.path = r.path;
|
|
501
|
-
} else if (mode) {
|
|
502
|
-
body.path = `${state.folders.root}/${mode}`;
|
|
503
500
|
}
|
|
504
501
|
if (type === 'local') body.category = 'Local';
|
|
505
502
|
}
|
package/public/style.css
CHANGED
|
@@ -614,7 +614,7 @@ body.dragging-board .col-add-ghost { display: none; }
|
|
|
614
614
|
.modal-actions { display: flex; gap: 8px; }
|
|
615
615
|
|
|
616
616
|
.modal.wiz { width: 400px; max-width: calc(100vw - 32px); }
|
|
617
|
-
.wiz-body { display: flex; flex-direction: column; gap: 9px; max-height:
|
|
617
|
+
.wiz-body { display: flex; flex-direction: column; gap: 9px; max-height: calc(100vh - 190px); overflow-y: auto; }
|
|
618
618
|
.wiz-type {
|
|
619
619
|
display: flex; flex-direction: column; gap: 3px; text-align: left; padding: 10px 12px;
|
|
620
620
|
border: 1px solid var(--border); border-radius: 9px; background: var(--panel); cursor: pointer; font: inherit; color: var(--text);
|
|
@@ -1037,6 +1037,7 @@ body.dragging-board .col-add-ghost { display: none; }
|
|
|
1037
1037
|
box-shadow: 0 10px 28px var(--shadow); padding: 4px; max-height: 280px; overflow-y: auto;
|
|
1038
1038
|
}
|
|
1039
1039
|
.kbsel.open .kbsel-list { display: block; }
|
|
1040
|
+
.kbsel-list.up { top: auto; bottom: calc(100% + 4px); }
|
|
1040
1041
|
.kbsel-search { position: sticky; top: -4px; background: var(--panel); padding: 2px 2px 5px; margin: -4px -4px 2px; z-index: 1; }
|
|
1041
1042
|
.kbsel-search-input {
|
|
1042
1043
|
width: 100%; box-sizing: border-box; padding: 6px 9px; font: inherit; font-size: 12.5px;
|