Skip to content

Commit a56c11a

Browse files
add drag-and-drop functionality to org group sidebar
1 parent d2fa57d commit a56c11a

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

web_src/js/features/group.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import {createSortable} from '../modules/sortable.ts';
2+
import Sortable, {type SortableEvent, type SortableOptions} from 'sortablejs';
3+
import {POST} from '../modules/fetch.ts';
4+
import {toggleElem} from '../utils/dom.ts';
5+
export function initCommonGroup() {
6+
if (!document.querySelectorAll('.group').length) {
7+
return;
8+
}
9+
10+
document.querySelector('.group.settings.options #group_name')?.addEventListener('input', function () {
11+
const nameChanged = this.value.toLowerCase() !== this.getAttribute('data-group-name').toLowerCase();
12+
toggleElem('#group-name-change-prompt', nameChanged);
13+
});
14+
}
15+
16+
async function moveItem({item, from, to, oldIndex}: SortableEvent): Promise<void> {
17+
const closestUl = to.nodeName.toLowerCase() === 'ul' ? to : to.closest('ul');
18+
const sortable = Sortable.get(closestUl);
19+
const isGroup = Boolean(item.getAttribute('data-is-group'));
20+
const strs = sortable.toArray();
21+
const newIndex = Math.max(1, strs.filter((a) => isGroup ?
22+
a.toLocaleLowerCase().startsWith('group') :
23+
a.toLocaleLowerCase().startsWith('repo')).indexOf(item.getAttribute('data-sort-id')) + 1);
24+
const data = {
25+
newParent: parseInt(to.closest('ul').closest('li')?.getAttribute('data-id') || '0'),
26+
id: parseInt(item.getAttribute('data-id')),
27+
newPos: newIndex,
28+
isGroup,
29+
};
30+
try {
31+
await POST(`${to.getAttribute('data-url')}/items/move`, {
32+
data,
33+
});
34+
} catch (error) {
35+
console.error(error);
36+
from.insertBefore(item, from.children[oldIndex]);
37+
}
38+
}
39+
40+
function idSortFn(a: string, b: string): number {
41+
return parseInt(a.split('-')[2]) - parseInt(b.split('-')[2]);
42+
}
43+
44+
function onEnd(ev: SortableEvent) {
45+
const {to} = ev;
46+
const closestUl = to.nodeName.toLowerCase() === 'ul' ? to : (to.closest('li')?.closest('ul') ?? to.closest('ul'));
47+
const sortable = Sortable.get(closestUl);
48+
const strs = sortable.toArray();
49+
const groups = strs.filter((a) => a.toLocaleLowerCase().startsWith('group'));
50+
const repos = strs.filter((a) => a.toLocaleLowerCase().startsWith('repo'));
51+
const newArr = [...groups.toSorted(idSortFn), ...repos.toSorted(idSortFn)];
52+
sortable.sort(newArr, true);
53+
moveItem(ev);
54+
}
55+
56+
const baseSortableOptions: SortableOptions = {
57+
group: {
58+
name: 'group',
59+
put(to, from, drag, ev) {
60+
console.debug('to', to);
61+
console.debug('from', from);
62+
console.debug('drag', drag);
63+
console.debug('ev', ev);
64+
const closestUl = to.el.nodeName.toLowerCase() === 'ul' ? to.el : to.el.closest('ul');
65+
console.debug('put this');
66+
return Boolean(closestUl?.getAttribute('data-is-group') ?? true);
67+
},
68+
pull: true,
69+
},
70+
dataIdAttr: 'data-sort-id',
71+
draggable: '.expandable-menu-item',
72+
delayOnTouchOnly: true,
73+
delay: 500,
74+
// onMove: beforeMove,
75+
onEnd,
76+
emptyInsertThreshold: 25,
77+
};
78+
79+
function initSubgroupSortable(list: Element) {
80+
createSortable(list, {...baseSortableOptions});
81+
for (const el of list.querySelectorAll('.expandable-menu-item')) {
82+
if (!el.getAttribute('data-is-group')) continue;
83+
const sublist = el.querySelector('ul');
84+
initSubgroupSortable(sublist);
85+
}
86+
}
87+
88+
async function initGroupSortable(parentEl: Element): Promise<void> {
89+
initSubgroupSortable(parentEl);
90+
}
91+
92+
export function initGroup(): void {
93+
const mainContainer = document.querySelector('#group-navigation-menu > .sortable');
94+
if (!mainContainer) return;
95+
initGroupSortable(mainContainer);
96+
}

web_src/js/index-domready.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton}
6666
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
6767
import {callInitFunctions} from './modules/init.ts';
6868
import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
69+
import {initCommonGroup, initGroup} from "./features/group.ts";
6970

7071
const initStartTime = performance.now();
7172
const initPerformanceTracer = callInitFunctions([
@@ -164,6 +165,9 @@ const initPerformanceTracer = callInitFunctions([
164165
initOAuth2SettingsDisableCheckbox,
165166

166167
initRepoFileView,
168+
169+
initCommonGroup,
170+
initGroup,
167171
]);
168172

169173
// it must be the last one, then the "querySelectorAll" only needs to be executed once for global init functions.

0 commit comments

Comments
 (0)