@aryacode/dsh-aryacode-polish 0.17.0 → 0.18.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 +8 -0
- package/client/workspace.js +155 -1
- package/docs/better-workspace.md +9 -0
- package/lib/client.js +156 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.18.0] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Folders that exist before their content.** The workspace menu can declare a folder, an
|
|
7
|
+
empty folder draws its own header with an empty count, accepts a workspace drop, and
|
|
8
|
+
can be removed again. Declared folders are remembered (ryacode-polish:folders.v1).
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
## [0.17.0] - 2026-10-28
|
|
4
12
|
|
|
5
13
|
### Added
|
package/client/workspace.js
CHANGED
|
@@ -386,6 +386,68 @@ const LOOK_KEY = 'aryacode-polish:workspace-appearance.v1'
|
|
|
386
386
|
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
387
387
|
const FOLD_KEY = 'aryacode-polish:folded.v1'
|
|
388
388
|
const DENSITY_KEY = 'aryacode-polish:density.v1'
|
|
389
|
+
const FOLDER_KEY = 'aryacode-polish:folders.v1'
|
|
390
|
+
|
|
391
|
+
const cleanFolder = (name) => {
|
|
392
|
+
const value = String(name || '').trim().replace(/^\/+|\/+$/g, '')
|
|
393
|
+
return value && !value.includes('/') ? value : undefined
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Add a folder name, refusing empties, duplicates and anything nested. */
|
|
397
|
+
export function addFolder(list, name) {
|
|
398
|
+
const folder = cleanFolder(name)
|
|
399
|
+
const names = Array.isArray(list) ? list.slice() : []
|
|
400
|
+
if (!folder || names.includes(folder)) return names
|
|
401
|
+
names.push(folder)
|
|
402
|
+
return names
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Remove a folder name from the declared list. */
|
|
406
|
+
export function removeFolder(list, name) {
|
|
407
|
+
const folder = cleanFolder(name)
|
|
408
|
+
const names = Array.isArray(list) ? list : []
|
|
409
|
+
return folder ? names.filter((entry) => entry !== folder) : names.slice()
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function readFolders(storage) {
|
|
413
|
+
try {
|
|
414
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
415
|
+
if (!store) return []
|
|
416
|
+
const parsed = JSON.parse(store.getItem(FOLDER_KEY) || '[]')
|
|
417
|
+
if (!Array.isArray(parsed)) return []
|
|
418
|
+
return parsed.map(cleanFolder).filter(Boolean)
|
|
419
|
+
} catch {
|
|
420
|
+
return []
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function writeFolders(list, storage) {
|
|
425
|
+
try {
|
|
426
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
427
|
+
if (!store) return
|
|
428
|
+
store.setItem(FOLDER_KEY, JSON.stringify((Array.isArray(list) ? list : []).map(cleanFolder).filter(Boolean)))
|
|
429
|
+
} catch {
|
|
430
|
+
/* folders are a convenience, not a requirement */
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Folder names the tree must draw: the ones its workspaces imply, then any declared but
|
|
436
|
+
* still empty. Declared folders keep their order so an empty folder does not jump around
|
|
437
|
+
* when the first workspace lands in it.
|
|
438
|
+
*/
|
|
439
|
+
export function folderNamesOf(rows, declared) {
|
|
440
|
+
const seen = []
|
|
441
|
+
for (const row of rows || []) {
|
|
442
|
+
const name = folderKey(row.workspace)
|
|
443
|
+
if (name && !seen.includes(name)) seen.push(name)
|
|
444
|
+
}
|
|
445
|
+
for (const name of declared || []) {
|
|
446
|
+
const folder = cleanFolder(name)
|
|
447
|
+
if (folder && !seen.includes(folder)) seen.push(folder)
|
|
448
|
+
}
|
|
449
|
+
return seen
|
|
450
|
+
}
|
|
389
451
|
|
|
390
452
|
export const DENSITIES = ['comfortable', 'compact']
|
|
391
453
|
|
|
@@ -865,10 +927,13 @@ export function BetterWorkspace(props) {
|
|
|
865
927
|
const [card, setCard] = React.useState(null)
|
|
866
928
|
const folded = React.useMemo(() => readFolded(), [])
|
|
867
929
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
930
|
+
const [declared, setDeclared] = React.useState(() => readFolders())
|
|
931
|
+
const [folderDraft, setFolderDraft] = React.useState('')
|
|
868
932
|
const [switcher, setSwitcher] = React.useState(null)
|
|
869
933
|
const [density, setDensity] = React.useState(() => readDensity())
|
|
870
934
|
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
871
935
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
936
|
+
React.useEffect(() => { writeFolders(declared) }, [declared])
|
|
872
937
|
const [renaming, setRenaming] = React.useState(null)
|
|
873
938
|
const commitRename = async () => {
|
|
874
939
|
const draft = renaming
|
|
@@ -920,7 +985,7 @@ export function BetterWorkspace(props) {
|
|
|
920
985
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
921
986
|
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
922
987
|
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
923
|
-
const folderNames =
|
|
988
|
+
const folderNames = folderNamesOf(rows, declared)
|
|
924
989
|
// One click should always do the useful thing: if anything is folded, the button
|
|
925
990
|
// expands everything (folders included); only when nothing is folded does it fold.
|
|
926
991
|
const anyFolded = rows.some(row => expanded[row.workspace.workspaceId] === false)
|
|
@@ -1124,6 +1189,17 @@ export function BetterWorkspace(props) {
|
|
|
1124
1189
|
}
|
|
1125
1190
|
},
|
|
1126
1191
|
}, 'Restore saved order'),
|
|
1192
|
+
React.createElement('button', {
|
|
1193
|
+
type: 'button',
|
|
1194
|
+
'data-polish-folder-new': 'open',
|
|
1195
|
+
style: WS_MENU_ITEM,
|
|
1196
|
+
onClick: () => {
|
|
1197
|
+
const anchor = menu.anchor
|
|
1198
|
+
closeMenu()
|
|
1199
|
+
setFolderDraft('')
|
|
1200
|
+
setMenu({ kind: 'folder', anchor })
|
|
1201
|
+
},
|
|
1202
|
+
}, 'New folder…'),
|
|
1127
1203
|
React.createElement('button', {
|
|
1128
1204
|
type: 'button',
|
|
1129
1205
|
'data-polish-glyph': 'open',
|
|
@@ -1191,6 +1267,30 @@ export function BetterWorkspace(props) {
|
|
|
1191
1267
|
}, 'Unarchive session'))
|
|
1192
1268
|
: null
|
|
1193
1269
|
|
|
1270
|
+
const folderMenu = menu && menu.kind === 'folder'
|
|
1271
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1272
|
+
React.createElement('input', {
|
|
1273
|
+
'data-polish-folder-input': 'true',
|
|
1274
|
+
type: 'text',
|
|
1275
|
+
value: folderDraft,
|
|
1276
|
+
placeholder: 'Folder name',
|
|
1277
|
+
autoFocus: true,
|
|
1278
|
+
style: WS_NOTE_INPUT,
|
|
1279
|
+
onChange: (event) => setFolderDraft(event.target.value),
|
|
1280
|
+
onKeyDown: (event) => {
|
|
1281
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1282
|
+
if (event.key !== 'Enter') return
|
|
1283
|
+
const next = addFolder(declared, folderDraft)
|
|
1284
|
+
closeMenu()
|
|
1285
|
+
if (next.length === declared.length) { setNotice('That folder already exists.'); return }
|
|
1286
|
+
setDeclared(next)
|
|
1287
|
+
setFolders((prev) => Object.assign({}, prev, { [next[next.length - 1]]: false }))
|
|
1288
|
+
setNotice('Folder added.')
|
|
1289
|
+
},
|
|
1290
|
+
}),
|
|
1291
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter creates, Escape cancels'))
|
|
1292
|
+
: null
|
|
1293
|
+
|
|
1194
1294
|
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1195
1295
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1196
1296
|
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
@@ -1506,6 +1606,7 @@ export function BetterWorkspace(props) {
|
|
|
1506
1606
|
const folded = folder !== undefined && folders[folder] === true
|
|
1507
1607
|
|
|
1508
1608
|
if (folder !== undefined && !announced[folder]) {
|
|
1609
|
+
/* handled below for both filled and empty folders */
|
|
1509
1610
|
announced[folder] = true
|
|
1510
1611
|
const inside = rows.filter((entry) => folderOf(entry.workspace) === folder)
|
|
1511
1612
|
children.push(React.createElement('div', {
|
|
@@ -1618,6 +1719,58 @@ export function BetterWorkspace(props) {
|
|
|
1618
1719
|
}
|
|
1619
1720
|
})
|
|
1620
1721
|
|
|
1722
|
+
const filled = new Set(rows.map(row => folderKey(row.workspace)).filter(Boolean))
|
|
1723
|
+
for (const folder of folderNames) {
|
|
1724
|
+
if (filled.has(folder)) continue
|
|
1725
|
+
const empty = folders[folder] === true
|
|
1726
|
+
children.push(React.createElement('div', {
|
|
1727
|
+
key: 'empty-folder:' + folder,
|
|
1728
|
+
'data-polish-ws-empty': folder,
|
|
1729
|
+
role: 'group',
|
|
1730
|
+
'aria-label': folder,
|
|
1731
|
+
title: 'Drop a workspace here to file it in ' + folder,
|
|
1732
|
+
style: Object.assign({}, WS_FOLDER_ROW, {
|
|
1733
|
+
background: dropping === 'folder:' + folder
|
|
1734
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1735
|
+
: undefined,
|
|
1736
|
+
}),
|
|
1737
|
+
onDragOver: (event) => {
|
|
1738
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1739
|
+
event.preventDefault()
|
|
1740
|
+
setDropping('folder:' + folder)
|
|
1741
|
+
},
|
|
1742
|
+
onDragLeave: () => setDropping(null),
|
|
1743
|
+
onDrop: (event) => {
|
|
1744
|
+
event.preventDefault()
|
|
1745
|
+
setDropping(null)
|
|
1746
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1747
|
+
const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
|
|
1748
|
+
if (!moving) return
|
|
1749
|
+
const title = fileUnder(workspaceLabel(moving.workspace), folder)
|
|
1750
|
+
Promise.resolve()
|
|
1751
|
+
.then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
|
|
1752
|
+
.then(() => setNotice('Filed under ' + folder + '.'))
|
|
1753
|
+
.catch(() => setNotice('Could not file that workspace.'))
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1756
|
+
React.createElement('span', { 'aria-hidden': 'true' }, '⌄'),
|
|
1757
|
+
React.createElement('span', { style: WS_TITLE }, folder),
|
|
1758
|
+
React.createElement('span', { style: WS_COUNT_NOTE }, 'empty'),
|
|
1759
|
+
React.createElement('button', {
|
|
1760
|
+
type: 'button',
|
|
1761
|
+
'data-polish-folder-remove': folder,
|
|
1762
|
+
'aria-label': 'Remove the empty folder ' + folder,
|
|
1763
|
+
title: 'Remove this empty folder',
|
|
1764
|
+
style: WS_ROW_ACTION,
|
|
1765
|
+
onClick: (event) => {
|
|
1766
|
+
event.stopPropagation()
|
|
1767
|
+
setDeclared(removeFolder(declared, folder))
|
|
1768
|
+
setNotice('Folder removed.')
|
|
1769
|
+
},
|
|
1770
|
+
}, '×')))
|
|
1771
|
+
if (!empty) continue
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1621
1774
|
if (archivedSessions.length > 0) {
|
|
1622
1775
|
children.push(React.createElement('div', {
|
|
1623
1776
|
key: 'archived-head',
|
|
@@ -1675,6 +1828,7 @@ export function BetterWorkspace(props) {
|
|
|
1675
1828
|
sessionMenu,
|
|
1676
1829
|
glyphMenu,
|
|
1677
1830
|
noteMenu,
|
|
1831
|
+
folderMenu,
|
|
1678
1832
|
noteCard,
|
|
1679
1833
|
archivedMenu,
|
|
1680
1834
|
paintMenu,
|
package/docs/better-workspace.md
CHANGED
|
@@ -201,3 +201,12 @@ drop target, so the gesture cannot rename something onto itself.
|
|
|
201
201
|
A workspace note shows as a small card under the row on hover rather than as a browser
|
|
202
202
|
tooltip: tooltips are slow to appear, unstyleable, and vanish when the pointer leaves. The
|
|
203
203
|
card is portalled, `pointer-events: none`, and only exists while a note does.
|
|
204
|
+
|
|
205
|
+
## Folders without content
|
|
206
|
+
|
|
207
|
+
A folder can be declared before anything lives in it (**New folder…** in a workspace menu).
|
|
208
|
+
It draws its own header with the word `empty` where the count would be, accepts a workspace
|
|
209
|
+
drop like any other folder header, and carries a `×` that removes the declaration. Declared
|
|
210
|
+
names live in `aryacode-polish:folders.v1`; the tree draws the names its workspaces imply
|
|
211
|
+
first and the declared ones after, so an empty folder never jumps ahead of real content and
|
|
212
|
+
disappears on its own the moment the first workspace is filed into it.
|
package/lib/client.js
CHANGED
|
@@ -53,7 +53,7 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
]
|
|
54
54
|
const ROW_ID = "polish"
|
|
55
55
|
const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
|
|
56
|
-
const CLIENT_VERSION = "0.
|
|
56
|
+
const CLIENT_VERSION = "0.18.0"
|
|
57
57
|
|
|
58
58
|
/* html2canvas 1.4.1 — MIT, vendored from vendor/html2canvas.min.js.
|
|
59
59
|
* A UMD build, so it assigns itself to globalThis; face.js reads it lazily. */
|
|
@@ -587,6 +587,68 @@ window.__ModuleLoader__.load({
|
|
|
587
587
|
const ORDER_KEY = 'aryacode-polish:workspace-order.v1'
|
|
588
588
|
const FOLD_KEY = 'aryacode-polish:folded.v1'
|
|
589
589
|
const DENSITY_KEY = 'aryacode-polish:density.v1'
|
|
590
|
+
const FOLDER_KEY = 'aryacode-polish:folders.v1'
|
|
591
|
+
|
|
592
|
+
const cleanFolder = (name) => {
|
|
593
|
+
const value = String(name || '').trim().replace(/^\/+|\/+$/g, '')
|
|
594
|
+
return value && !value.includes('/') ? value : undefined
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Add a folder name, refusing empties, duplicates and anything nested. */
|
|
598
|
+
function addFolder(list, name) {
|
|
599
|
+
const folder = cleanFolder(name)
|
|
600
|
+
const names = Array.isArray(list) ? list.slice() : []
|
|
601
|
+
if (!folder || names.includes(folder)) return names
|
|
602
|
+
names.push(folder)
|
|
603
|
+
return names
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Remove a folder name from the declared list. */
|
|
607
|
+
function removeFolder(list, name) {
|
|
608
|
+
const folder = cleanFolder(name)
|
|
609
|
+
const names = Array.isArray(list) ? list : []
|
|
610
|
+
return folder ? names.filter((entry) => entry !== folder) : names.slice()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function readFolders(storage) {
|
|
614
|
+
try {
|
|
615
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
616
|
+
if (!store) return []
|
|
617
|
+
const parsed = JSON.parse(store.getItem(FOLDER_KEY) || '[]')
|
|
618
|
+
if (!Array.isArray(parsed)) return []
|
|
619
|
+
return parsed.map(cleanFolder).filter(Boolean)
|
|
620
|
+
} catch {
|
|
621
|
+
return []
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function writeFolders(list, storage) {
|
|
626
|
+
try {
|
|
627
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
628
|
+
if (!store) return
|
|
629
|
+
store.setItem(FOLDER_KEY, JSON.stringify((Array.isArray(list) ? list : []).map(cleanFolder).filter(Boolean)))
|
|
630
|
+
} catch {
|
|
631
|
+
/* folders are a convenience, not a requirement */
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Folder names the tree must draw: the ones its workspaces imply, then any declared but
|
|
637
|
+
* still empty. Declared folders keep their order so an empty folder does not jump around
|
|
638
|
+
* when the first workspace lands in it.
|
|
639
|
+
*/
|
|
640
|
+
function folderNamesOf(rows, declared) {
|
|
641
|
+
const seen = []
|
|
642
|
+
for (const row of rows || []) {
|
|
643
|
+
const name = folderKey(row.workspace)
|
|
644
|
+
if (name && !seen.includes(name)) seen.push(name)
|
|
645
|
+
}
|
|
646
|
+
for (const name of declared || []) {
|
|
647
|
+
const folder = cleanFolder(name)
|
|
648
|
+
if (folder && !seen.includes(folder)) seen.push(folder)
|
|
649
|
+
}
|
|
650
|
+
return seen
|
|
651
|
+
}
|
|
590
652
|
|
|
591
653
|
const DENSITIES = ['comfortable', 'compact']
|
|
592
654
|
|
|
@@ -1066,10 +1128,13 @@ window.__ModuleLoader__.load({
|
|
|
1066
1128
|
const [card, setCard] = React.useState(null)
|
|
1067
1129
|
const folded = React.useMemo(() => readFolded(), [])
|
|
1068
1130
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
1131
|
+
const [declared, setDeclared] = React.useState(() => readFolders())
|
|
1132
|
+
const [folderDraft, setFolderDraft] = React.useState('')
|
|
1069
1133
|
const [switcher, setSwitcher] = React.useState(null)
|
|
1070
1134
|
const [density, setDensity] = React.useState(() => readDensity())
|
|
1071
1135
|
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
1072
1136
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
1137
|
+
React.useEffect(() => { writeFolders(declared) }, [declared])
|
|
1073
1138
|
const [renaming, setRenaming] = React.useState(null)
|
|
1074
1139
|
const commitRename = async () => {
|
|
1075
1140
|
const draft = renaming
|
|
@@ -1121,7 +1186,7 @@ window.__ModuleLoader__.load({
|
|
|
1121
1186
|
const entries = React.useMemo(() => rowMenuEntries(slots), [slots, workspaceList.length, sessionList.length])
|
|
1122
1187
|
const totalCount = allRows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
1123
1188
|
const shownCount = rows.reduce((sum, row) => sum + row.sessions.length, 0)
|
|
1124
|
-
const folderNames =
|
|
1189
|
+
const folderNames = folderNamesOf(rows, declared)
|
|
1125
1190
|
// One click should always do the useful thing: if anything is folded, the button
|
|
1126
1191
|
// expands everything (folders included); only when nothing is folded does it fold.
|
|
1127
1192
|
const anyFolded = rows.some(row => expanded[row.workspace.workspaceId] === false)
|
|
@@ -1325,6 +1390,17 @@ window.__ModuleLoader__.load({
|
|
|
1325
1390
|
}
|
|
1326
1391
|
},
|
|
1327
1392
|
}, 'Restore saved order'),
|
|
1393
|
+
React.createElement('button', {
|
|
1394
|
+
type: 'button',
|
|
1395
|
+
'data-polish-folder-new': 'open',
|
|
1396
|
+
style: WS_MENU_ITEM,
|
|
1397
|
+
onClick: () => {
|
|
1398
|
+
const anchor = menu.anchor
|
|
1399
|
+
closeMenu()
|
|
1400
|
+
setFolderDraft('')
|
|
1401
|
+
setMenu({ kind: 'folder', anchor })
|
|
1402
|
+
},
|
|
1403
|
+
}, 'New folder…'),
|
|
1328
1404
|
React.createElement('button', {
|
|
1329
1405
|
type: 'button',
|
|
1330
1406
|
'data-polish-glyph': 'open',
|
|
@@ -1392,6 +1468,30 @@ window.__ModuleLoader__.load({
|
|
|
1392
1468
|
}, 'Unarchive session'))
|
|
1393
1469
|
: null
|
|
1394
1470
|
|
|
1471
|
+
const folderMenu = menu && menu.kind === 'folder'
|
|
1472
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1473
|
+
React.createElement('input', {
|
|
1474
|
+
'data-polish-folder-input': 'true',
|
|
1475
|
+
type: 'text',
|
|
1476
|
+
value: folderDraft,
|
|
1477
|
+
placeholder: 'Folder name',
|
|
1478
|
+
autoFocus: true,
|
|
1479
|
+
style: WS_NOTE_INPUT,
|
|
1480
|
+
onChange: (event) => setFolderDraft(event.target.value),
|
|
1481
|
+
onKeyDown: (event) => {
|
|
1482
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1483
|
+
if (event.key !== 'Enter') return
|
|
1484
|
+
const next = addFolder(declared, folderDraft)
|
|
1485
|
+
closeMenu()
|
|
1486
|
+
if (next.length === declared.length) { setNotice('That folder already exists.'); return }
|
|
1487
|
+
setDeclared(next)
|
|
1488
|
+
setFolders((prev) => Object.assign({}, prev, { [next[next.length - 1]]: false }))
|
|
1489
|
+
setNotice('Folder added.')
|
|
1490
|
+
},
|
|
1491
|
+
}),
|
|
1492
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter creates, Escape cancels'))
|
|
1493
|
+
: null
|
|
1494
|
+
|
|
1395
1495
|
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1396
1496
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1397
1497
|
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
@@ -1707,6 +1807,7 @@ window.__ModuleLoader__.load({
|
|
|
1707
1807
|
const folded = folder !== undefined && folders[folder] === true
|
|
1708
1808
|
|
|
1709
1809
|
if (folder !== undefined && !announced[folder]) {
|
|
1810
|
+
/* handled below for both filled and empty folders */
|
|
1710
1811
|
announced[folder] = true
|
|
1711
1812
|
const inside = rows.filter((entry) => folderOf(entry.workspace) === folder)
|
|
1712
1813
|
children.push(React.createElement('div', {
|
|
@@ -1819,6 +1920,58 @@ window.__ModuleLoader__.load({
|
|
|
1819
1920
|
}
|
|
1820
1921
|
})
|
|
1821
1922
|
|
|
1923
|
+
const filled = new Set(rows.map(row => folderKey(row.workspace)).filter(Boolean))
|
|
1924
|
+
for (const folder of folderNames) {
|
|
1925
|
+
if (filled.has(folder)) continue
|
|
1926
|
+
const empty = folders[folder] === true
|
|
1927
|
+
children.push(React.createElement('div', {
|
|
1928
|
+
key: 'empty-folder:' + folder,
|
|
1929
|
+
'data-polish-ws-empty': folder,
|
|
1930
|
+
role: 'group',
|
|
1931
|
+
'aria-label': folder,
|
|
1932
|
+
title: 'Drop a workspace here to file it in ' + folder,
|
|
1933
|
+
style: Object.assign({}, WS_FOLDER_ROW, {
|
|
1934
|
+
background: dropping === 'folder:' + folder
|
|
1935
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1936
|
+
: undefined,
|
|
1937
|
+
}),
|
|
1938
|
+
onDragOver: (event) => {
|
|
1939
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1940
|
+
event.preventDefault()
|
|
1941
|
+
setDropping('folder:' + folder)
|
|
1942
|
+
},
|
|
1943
|
+
onDragLeave: () => setDropping(null),
|
|
1944
|
+
onDrop: (event) => {
|
|
1945
|
+
event.preventDefault()
|
|
1946
|
+
setDropping(null)
|
|
1947
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1948
|
+
const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
|
|
1949
|
+
if (!moving) return
|
|
1950
|
+
const title = fileUnder(workspaceLabel(moving.workspace), folder)
|
|
1951
|
+
Promise.resolve()
|
|
1952
|
+
.then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
|
|
1953
|
+
.then(() => setNotice('Filed under ' + folder + '.'))
|
|
1954
|
+
.catch(() => setNotice('Could not file that workspace.'))
|
|
1955
|
+
},
|
|
1956
|
+
},
|
|
1957
|
+
React.createElement('span', { 'aria-hidden': 'true' }, '⌄'),
|
|
1958
|
+
React.createElement('span', { style: WS_TITLE }, folder),
|
|
1959
|
+
React.createElement('span', { style: WS_COUNT_NOTE }, 'empty'),
|
|
1960
|
+
React.createElement('button', {
|
|
1961
|
+
type: 'button',
|
|
1962
|
+
'data-polish-folder-remove': folder,
|
|
1963
|
+
'aria-label': 'Remove the empty folder ' + folder,
|
|
1964
|
+
title: 'Remove this empty folder',
|
|
1965
|
+
style: WS_ROW_ACTION,
|
|
1966
|
+
onClick: (event) => {
|
|
1967
|
+
event.stopPropagation()
|
|
1968
|
+
setDeclared(removeFolder(declared, folder))
|
|
1969
|
+
setNotice('Folder removed.')
|
|
1970
|
+
},
|
|
1971
|
+
}, '×')))
|
|
1972
|
+
if (!empty) continue
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1822
1975
|
if (archivedSessions.length > 0) {
|
|
1823
1976
|
children.push(React.createElement('div', {
|
|
1824
1977
|
key: 'archived-head',
|
|
@@ -1876,6 +2029,7 @@ window.__ModuleLoader__.load({
|
|
|
1876
2029
|
sessionMenu,
|
|
1877
2030
|
glyphMenu,
|
|
1878
2031
|
noteMenu,
|
|
2032
|
+
folderMenu,
|
|
1879
2033
|
noteCard,
|
|
1880
2034
|
archivedMenu,
|
|
1881
2035
|
paintMenu,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "AryaCode Polish: one install for the small things that make AryaCode feel finished. One Plugins row, one switch per quality-of-life feature.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|