@aryacode/dsh-aryacode-polish 0.15.2 โ 0.17.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 +17 -0
- package/client/workspace.js +214 -6
- package/docs/better-workspace.md +25 -0
- package/lib/client.js +215 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.17.0] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Drop a workspace on a folder header to file it there.** The title is rewritten to
|
|
7
|
+
Folder/Name (any previous prefix is replaced, not stacked) through the Host rename.
|
|
8
|
+
- **A workspace note shows as a card on hover**, not just as a tooltip title.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [0.16.0] - 2026-10-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **A glyph per workspace.** The workspace menu can put an emoji badge in front of a
|
|
15
|
+
workspace instead of the folder icon, and the choice is remembered with the colour.
|
|
16
|
+
- **A note per workspace.** The note shows as the row tooltip and is edited from the same
|
|
17
|
+
menu; Enter saves, Escape cancels.
|
|
18
|
+
|
|
19
|
+
|
|
3
20
|
## [0.15.2] - 2026-10-28
|
|
4
21
|
|
|
5
22
|
### Added
|
package/client/workspace.js
CHANGED
|
@@ -435,6 +435,70 @@ export function splitArchived(sessions, archivedIds) {
|
|
|
435
435
|
return { active, archived }
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
const WS_GLYPH_GRID = {
|
|
439
|
+
display: 'grid',
|
|
440
|
+
gridTemplateColumns: 'repeat(6, 26px)',
|
|
441
|
+
gap: '2px',
|
|
442
|
+
padding: '2px',
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const WS_GLYPH_BUTTON = {
|
|
446
|
+
width: '26px',
|
|
447
|
+
height: '26px',
|
|
448
|
+
display: 'inline-flex',
|
|
449
|
+
alignItems: 'center',
|
|
450
|
+
justifyContent: 'center',
|
|
451
|
+
padding: 0,
|
|
452
|
+
border: 'none',
|
|
453
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
454
|
+
background: 'transparent',
|
|
455
|
+
fontSize: '15px',
|
|
456
|
+
lineHeight: 1,
|
|
457
|
+
cursor: 'pointer',
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const WS_NOTE_INPUT = {
|
|
461
|
+
width: '220px',
|
|
462
|
+
boxSizing: 'border-box',
|
|
463
|
+
padding: '5px 8px',
|
|
464
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
465
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
466
|
+
background: 'transparent',
|
|
467
|
+
color: 'inherit',
|
|
468
|
+
font: 'inherit',
|
|
469
|
+
outline: 'none',
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const WS_MENU_HINT = {
|
|
473
|
+
padding: '4px 8px 2px',
|
|
474
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
475
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const WS_NOTE_CARD = {
|
|
479
|
+
position: 'fixed',
|
|
480
|
+
zIndex: 1250,
|
|
481
|
+
maxWidth: '260px',
|
|
482
|
+
padding: '6px 9px',
|
|
483
|
+
borderRadius: 'var(--dsw-radius-md, 8px)',
|
|
484
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
485
|
+
background: 'Canvas',
|
|
486
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
487
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
|
|
488
|
+
lineHeight: 1.35,
|
|
489
|
+
boxShadow: 'var(--dsw-elevation-prominent, 0 2px 8px rgba(0, 0, 0, 0.08))',
|
|
490
|
+
pointerEvents: 'none',
|
|
491
|
+
whiteSpace: 'pre-wrap',
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const WS_GLYPH = {
|
|
495
|
+
flex: 'none',
|
|
496
|
+
width: '16px',
|
|
497
|
+
textAlign: 'center',
|
|
498
|
+
fontSize: '13px',
|
|
499
|
+
lineHeight: 1,
|
|
500
|
+
}
|
|
501
|
+
|
|
438
502
|
const WS_SECTION = {
|
|
439
503
|
display: 'flex',
|
|
440
504
|
alignItems: 'center',
|
|
@@ -568,6 +632,19 @@ const SWITCHER_HINT = {
|
|
|
568
632
|
* written to the Host and renaming a workspace is enough to move it between
|
|
569
633
|
* folders โ the same idea as the reference plugin, without its "/" toggle.
|
|
570
634
|
*/
|
|
635
|
+
/**
|
|
636
|
+
* The title a workspace needs to sit under `folder`: its own name moved behind the
|
|
637
|
+
* folder prefix, with any previous prefix replaced rather than stacked.
|
|
638
|
+
*/
|
|
639
|
+
export function fileUnder(label, folder) {
|
|
640
|
+
const name = String(label || '').trim()
|
|
641
|
+
const target = String(folder || '').trim()
|
|
642
|
+
if (!name || !target) return name
|
|
643
|
+
const cut = name.lastIndexOf('/')
|
|
644
|
+
const bare = (cut >= 0 ? name.slice(cut + 1) : name).trim()
|
|
645
|
+
return bare ? target + '/' + bare : target
|
|
646
|
+
}
|
|
647
|
+
|
|
571
648
|
export function folderKey(workspace) {
|
|
572
649
|
const label = workspaceLabel(workspace)
|
|
573
650
|
const cut = label.lastIndexOf('/')
|
|
@@ -645,19 +722,29 @@ export const TONE_COLOR = {
|
|
|
645
722
|
violet: '#7a5cc4',
|
|
646
723
|
}
|
|
647
724
|
|
|
725
|
+
/** Glyphs a workspace can wear instead of the folder icon. */
|
|
726
|
+
export const GLYPHS = ['๐', '๐', '๐งช', '๐', 'โ๏ธ', '๐ฆ', '๐งญ', 'โจ', '๐ง ', '๐๏ธ']
|
|
727
|
+
|
|
648
728
|
export function appearanceOf(map, workspaceId) {
|
|
649
729
|
const entry = (map && map[workspaceId]) || {}
|
|
650
730
|
return {
|
|
651
731
|
tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
|
|
652
732
|
strong: entry.strong === true,
|
|
733
|
+
glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
|
|
734
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
653
735
|
}
|
|
654
736
|
}
|
|
655
737
|
|
|
656
738
|
export function withAppearance(map, workspaceId, patch) {
|
|
657
739
|
const next = Object.assign({}, map || {})
|
|
658
740
|
const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
|
|
659
|
-
|
|
660
|
-
|
|
741
|
+
const entry = {}
|
|
742
|
+
if (merged.tone) entry.tone = merged.tone
|
|
743
|
+
if (merged.strong === true) entry.strong = true
|
|
744
|
+
if (merged.glyph) entry.glyph = merged.glyph
|
|
745
|
+
if (merged.note) entry.note = merged.note
|
|
746
|
+
if (Object.keys(entry).length === 0) delete next[workspaceId]
|
|
747
|
+
else next[workspaceId] = entry
|
|
661
748
|
return next
|
|
662
749
|
}
|
|
663
750
|
|
|
@@ -774,6 +861,8 @@ export function BetterWorkspace(props) {
|
|
|
774
861
|
const [drag, setDrag] = React.useState(null)
|
|
775
862
|
const [dropping, setDropping] = React.useState(null)
|
|
776
863
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
864
|
+
const [noteDraft, setNoteDraft] = React.useState('')
|
|
865
|
+
const [card, setCard] = React.useState(null)
|
|
777
866
|
const folded = React.useMemo(() => readFolded(), [])
|
|
778
867
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
779
868
|
const [switcher, setSwitcher] = React.useState(null)
|
|
@@ -1035,6 +1124,29 @@ export function BetterWorkspace(props) {
|
|
|
1035
1124
|
}
|
|
1036
1125
|
},
|
|
1037
1126
|
}, 'Restore saved order'),
|
|
1127
|
+
React.createElement('button', {
|
|
1128
|
+
type: 'button',
|
|
1129
|
+
'data-polish-glyph': 'open',
|
|
1130
|
+
style: WS_MENU_ITEM,
|
|
1131
|
+
onClick: () => {
|
|
1132
|
+
const target = menu.workspace
|
|
1133
|
+
const anchor = menu.anchor
|
|
1134
|
+
closeMenu()
|
|
1135
|
+
setMenu({ kind: 'glyph', workspace: target, anchor })
|
|
1136
|
+
},
|
|
1137
|
+
}, 'Glyphโฆ'),
|
|
1138
|
+
React.createElement('button', {
|
|
1139
|
+
type: 'button',
|
|
1140
|
+
'data-polish-note': 'open',
|
|
1141
|
+
style: WS_MENU_ITEM,
|
|
1142
|
+
onClick: () => {
|
|
1143
|
+
const target = menu.workspace
|
|
1144
|
+
const anchor = menu.anchor
|
|
1145
|
+
closeMenu()
|
|
1146
|
+
setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
|
|
1147
|
+
setMenu({ kind: 'note', workspace: target, anchor })
|
|
1148
|
+
},
|
|
1149
|
+
}, 'Noteโฆ'),
|
|
1038
1150
|
React.createElement('button', {
|
|
1039
1151
|
type: 'button',
|
|
1040
1152
|
'data-polish-ws-appearance': 'open',
|
|
@@ -1079,6 +1191,67 @@ export function BetterWorkspace(props) {
|
|
|
1079
1191
|
}, 'Unarchive session'))
|
|
1080
1192
|
: null
|
|
1081
1193
|
|
|
1194
|
+
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1195
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1196
|
+
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
1197
|
+
GLYPHS.map((glyph) => React.createElement('button', {
|
|
1198
|
+
key: 'glyph:' + glyph,
|
|
1199
|
+
type: 'button',
|
|
1200
|
+
'data-polish-glyph-pick': glyph,
|
|
1201
|
+
title: 'Use ' + glyph,
|
|
1202
|
+
style: Object.assign({}, WS_GLYPH_BUTTON, {
|
|
1203
|
+
background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
|
|
1204
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
|
|
1205
|
+
: undefined,
|
|
1206
|
+
}),
|
|
1207
|
+
onClick: () => {
|
|
1208
|
+
const target = menu.workspace
|
|
1209
|
+
closeMenu()
|
|
1210
|
+
paintLook(target.workspaceId, { glyph })
|
|
1211
|
+
},
|
|
1212
|
+
}, glyph)),
|
|
1213
|
+
React.createElement('button', {
|
|
1214
|
+
key: 'glyph:none',
|
|
1215
|
+
type: 'button',
|
|
1216
|
+
'data-polish-glyph-pick': 'none',
|
|
1217
|
+
style: WS_GLYPH_BUTTON,
|
|
1218
|
+
onClick: () => {
|
|
1219
|
+
const target = menu.workspace
|
|
1220
|
+
closeMenu()
|
|
1221
|
+
paintLook(target.workspaceId, { glyph: '' })
|
|
1222
|
+
},
|
|
1223
|
+
}, 'โซ')))
|
|
1224
|
+
: null
|
|
1225
|
+
|
|
1226
|
+
const noteMenu = menu && menu.kind === 'note'
|
|
1227
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1228
|
+
React.createElement('input', {
|
|
1229
|
+
'data-polish-note-input': 'true',
|
|
1230
|
+
type: 'text',
|
|
1231
|
+
value: noteDraft,
|
|
1232
|
+
placeholder: 'A note for this workspace',
|
|
1233
|
+
autoFocus: true,
|
|
1234
|
+
style: WS_NOTE_INPUT,
|
|
1235
|
+
onChange: (event) => setNoteDraft(event.target.value),
|
|
1236
|
+
onKeyDown: (event) => {
|
|
1237
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1238
|
+
if (event.key !== 'Enter') return
|
|
1239
|
+
const target = menu.workspace
|
|
1240
|
+
const value = noteDraft.trim()
|
|
1241
|
+
closeMenu()
|
|
1242
|
+
paintLook(target.workspaceId, { note: value })
|
|
1243
|
+
},
|
|
1244
|
+
}),
|
|
1245
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1246
|
+
: null
|
|
1247
|
+
|
|
1248
|
+
const noteCard = card
|
|
1249
|
+
? ReactDOM.createPortal(React.createElement('div', {
|
|
1250
|
+
'data-polish-note-card': 'true',
|
|
1251
|
+
style: Object.assign({}, WS_NOTE_CARD, { top: card.top, left: card.left }),
|
|
1252
|
+
}, card.text), document.body)
|
|
1253
|
+
: null
|
|
1254
|
+
|
|
1082
1255
|
const paintMenu = menu && menu.kind === 'paint'
|
|
1083
1256
|
? (() => {
|
|
1084
1257
|
const workspace = menu.workspace
|
|
@@ -1341,8 +1514,31 @@ export function BetterWorkspace(props) {
|
|
|
1341
1514
|
role: 'group',
|
|
1342
1515
|
'aria-label': folder,
|
|
1343
1516
|
title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
|
|
1344
|
-
style: WS_FOLDER_ROW,
|
|
1517
|
+
style: Object.assign({}, WS_FOLDER_ROW, {
|
|
1518
|
+
background: dropping === 'folder:' + folder
|
|
1519
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1520
|
+
: undefined,
|
|
1521
|
+
}),
|
|
1345
1522
|
onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
|
|
1523
|
+
onDragOver: (event) => {
|
|
1524
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1525
|
+
if (folderOf(rows.find((entry) => entry.workspace.workspaceId === drag.id)?.workspace) === folder) return
|
|
1526
|
+
event.preventDefault()
|
|
1527
|
+
setDropping('folder:' + folder)
|
|
1528
|
+
},
|
|
1529
|
+
onDragLeave: () => setDropping(null),
|
|
1530
|
+
onDrop: (event) => {
|
|
1531
|
+
event.preventDefault()
|
|
1532
|
+
setDropping(null)
|
|
1533
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1534
|
+
const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
|
|
1535
|
+
if (!moving || folderOf(moving.workspace) === folder) return
|
|
1536
|
+
const title = fileUnder(workspaceLabel(moving.workspace), folder)
|
|
1537
|
+
Promise.resolve()
|
|
1538
|
+
.then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
|
|
1539
|
+
.then(() => setNotice('Filed under ' + folder + '.'))
|
|
1540
|
+
.catch(() => setNotice('Could not file that workspace.'))
|
|
1541
|
+
},
|
|
1346
1542
|
},
|
|
1347
1543
|
React.createElement('span', { 'aria-hidden': 'true' }, folded ? 'โบ' : 'โ'),
|
|
1348
1544
|
React.createElement('span', { style: WS_TITLE }, folder),
|
|
@@ -1375,8 +1571,15 @@ export function BetterWorkspace(props) {
|
|
|
1375
1571
|
}),
|
|
1376
1572
|
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
1377
1573
|
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
1378
|
-
|
|
1379
|
-
|
|
1574
|
+
title: looks.note || undefined,
|
|
1575
|
+
onMouseEnter: (event) => {
|
|
1576
|
+
setHover('workspace:' + workspace.workspaceId)
|
|
1577
|
+
const note = appearanceOf(look, workspace.workspaceId).note
|
|
1578
|
+
if (!note) return
|
|
1579
|
+
const box = event.currentTarget.getBoundingClientRect()
|
|
1580
|
+
setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
|
|
1581
|
+
},
|
|
1582
|
+
onMouseLeave: () => { setHover(null); setCard(null) },
|
|
1380
1583
|
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
1381
1584
|
onDragOver: (event) => {
|
|
1382
1585
|
if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
|
|
@@ -1387,7 +1590,9 @@ export function BetterWorkspace(props) {
|
|
|
1387
1590
|
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
1388
1591
|
},
|
|
1389
1592
|
React.createElement(CaretGlyph, { open }),
|
|
1390
|
-
|
|
1593
|
+
looks.glyph
|
|
1594
|
+
? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
|
|
1595
|
+
: React.createElement(FolderGlyph),
|
|
1391
1596
|
looks.tone
|
|
1392
1597
|
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
1393
1598
|
: null,
|
|
@@ -1468,6 +1673,9 @@ export function BetterWorkspace(props) {
|
|
|
1468
1673
|
: null,
|
|
1469
1674
|
switcherCard,
|
|
1470
1675
|
sessionMenu,
|
|
1676
|
+
glyphMenu,
|
|
1677
|
+
noteMenu,
|
|
1678
|
+
noteCard,
|
|
1471
1679
|
archivedMenu,
|
|
1472
1680
|
paintMenu,
|
|
1473
1681
|
workspaceMenu)
|
package/docs/better-workspace.md
CHANGED
|
@@ -176,3 +176,28 @@ were being rendered inline in their workspace as if they were live.
|
|
|
176
176
|
The header's fold control expands when anything is folded (folders included) and folds
|
|
177
177
|
only when nothing is. An earlier version only looked at workspace rows, so a folded
|
|
178
178
|
folder left the button saying "Expand all" while its workspaces stayed hidden.
|
|
179
|
+
|
|
180
|
+
## Glyphs and notes
|
|
181
|
+
|
|
182
|
+
A workspace can wear an emoji badge instead of the folder icon (**Glyphโฆ** in its menu, ten
|
|
183
|
+
badges plus a clear button) and carry a short note (**Noteโฆ**, Enter saves, Escape cancels).
|
|
184
|
+
Both live in the same appearance record as the colour โ one localStorage key, one shape โ
|
|
185
|
+
and the note surfaces as the row's tooltip, so it costs no vertical space in the tree.
|
|
186
|
+
|
|
187
|
+
Glyph validation is deliberately narrow (one to four characters): the field is a badge in a
|
|
188
|
+
16 px slot, not a place to paste a sentence.
|
|
189
|
+
|
|
190
|
+
## Filing by drag
|
|
191
|
+
|
|
192
|
+
Dropping a workspace on a folder header files it there: the title is rewritten to
|
|
193
|
+
`Folder/Name` through the Host's own rename, so the move is durable and the shipped
|
|
194
|
+
browser (when this switch is off) sees exactly the same structure. `fileUnder(label, folder)`
|
|
195
|
+
replaces any previous prefix instead of stacking it โ dragging `A/B/Gamma` onto `C` gives
|
|
196
|
+
`C/Gamma`, not `C/A/B/Gamma` โ and a workspace already inside the target folder is not a
|
|
197
|
+
drop target, so the gesture cannot rename something onto itself.
|
|
198
|
+
|
|
199
|
+
## The note card
|
|
200
|
+
|
|
201
|
+
A workspace note shows as a small card under the row on hover rather than as a browser
|
|
202
|
+
tooltip: tooltips are slow to appear, unstyleable, and vanish when the pointer leaves. The
|
|
203
|
+
card is portalled, `pointer-events: none`, and only exists while a note does.
|
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.17.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. */
|
|
@@ -636,6 +636,70 @@ window.__ModuleLoader__.load({
|
|
|
636
636
|
return { active, archived }
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
+
const WS_GLYPH_GRID = {
|
|
640
|
+
display: 'grid',
|
|
641
|
+
gridTemplateColumns: 'repeat(6, 26px)',
|
|
642
|
+
gap: '2px',
|
|
643
|
+
padding: '2px',
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const WS_GLYPH_BUTTON = {
|
|
647
|
+
width: '26px',
|
|
648
|
+
height: '26px',
|
|
649
|
+
display: 'inline-flex',
|
|
650
|
+
alignItems: 'center',
|
|
651
|
+
justifyContent: 'center',
|
|
652
|
+
padding: 0,
|
|
653
|
+
border: 'none',
|
|
654
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
655
|
+
background: 'transparent',
|
|
656
|
+
fontSize: '15px',
|
|
657
|
+
lineHeight: 1,
|
|
658
|
+
cursor: 'pointer',
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const WS_NOTE_INPUT = {
|
|
662
|
+
width: '220px',
|
|
663
|
+
boxSizing: 'border-box',
|
|
664
|
+
padding: '5px 8px',
|
|
665
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
666
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
667
|
+
background: 'transparent',
|
|
668
|
+
color: 'inherit',
|
|
669
|
+
font: 'inherit',
|
|
670
|
+
outline: 'none',
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const WS_MENU_HINT = {
|
|
674
|
+
padding: '4px 8px 2px',
|
|
675
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 2px)',
|
|
676
|
+
color: 'var(--dsw-alias-label-quaternary, #8a8a8a)',
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const WS_NOTE_CARD = {
|
|
680
|
+
position: 'fixed',
|
|
681
|
+
zIndex: 1250,
|
|
682
|
+
maxWidth: '260px',
|
|
683
|
+
padding: '6px 9px',
|
|
684
|
+
borderRadius: 'var(--dsw-radius-md, 8px)',
|
|
685
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12))',
|
|
686
|
+
background: 'Canvas',
|
|
687
|
+
color: 'var(--dsw-alias-label-secondary, #61666b)',
|
|
688
|
+
fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
|
|
689
|
+
lineHeight: 1.35,
|
|
690
|
+
boxShadow: 'var(--dsw-elevation-prominent, 0 2px 8px rgba(0, 0, 0, 0.08))',
|
|
691
|
+
pointerEvents: 'none',
|
|
692
|
+
whiteSpace: 'pre-wrap',
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const WS_GLYPH = {
|
|
696
|
+
flex: 'none',
|
|
697
|
+
width: '16px',
|
|
698
|
+
textAlign: 'center',
|
|
699
|
+
fontSize: '13px',
|
|
700
|
+
lineHeight: 1,
|
|
701
|
+
}
|
|
702
|
+
|
|
639
703
|
const WS_SECTION = {
|
|
640
704
|
display: 'flex',
|
|
641
705
|
alignItems: 'center',
|
|
@@ -769,6 +833,19 @@ window.__ModuleLoader__.load({
|
|
|
769
833
|
* written to the Host and renaming a workspace is enough to move it between
|
|
770
834
|
* folders โ the same idea as the reference plugin, without its "/" toggle.
|
|
771
835
|
*/
|
|
836
|
+
/**
|
|
837
|
+
* The title a workspace needs to sit under `folder`: its own name moved behind the
|
|
838
|
+
* folder prefix, with any previous prefix replaced rather than stacked.
|
|
839
|
+
*/
|
|
840
|
+
function fileUnder(label, folder) {
|
|
841
|
+
const name = String(label || '').trim()
|
|
842
|
+
const target = String(folder || '').trim()
|
|
843
|
+
if (!name || !target) return name
|
|
844
|
+
const cut = name.lastIndexOf('/')
|
|
845
|
+
const bare = (cut >= 0 ? name.slice(cut + 1) : name).trim()
|
|
846
|
+
return bare ? target + '/' + bare : target
|
|
847
|
+
}
|
|
848
|
+
|
|
772
849
|
function folderKey(workspace) {
|
|
773
850
|
const label = workspaceLabel(workspace)
|
|
774
851
|
const cut = label.lastIndexOf('/')
|
|
@@ -846,19 +923,29 @@ window.__ModuleLoader__.load({
|
|
|
846
923
|
violet: '#7a5cc4',
|
|
847
924
|
}
|
|
848
925
|
|
|
926
|
+
/** Glyphs a workspace can wear instead of the folder icon. */
|
|
927
|
+
const GLYPHS = ['๐', '๐', '๐งช', '๐', 'โ๏ธ', '๐ฆ', '๐งญ', 'โจ', '๐ง ', '๐๏ธ']
|
|
928
|
+
|
|
849
929
|
function appearanceOf(map, workspaceId) {
|
|
850
930
|
const entry = (map && map[workspaceId]) || {}
|
|
851
931
|
return {
|
|
852
932
|
tone: TONE_COLOR[entry.tone] ? entry.tone : undefined,
|
|
853
933
|
strong: entry.strong === true,
|
|
934
|
+
glyph: typeof entry.glyph === 'string' && entry.glyph.length > 0 && entry.glyph.length <= 4 ? entry.glyph : undefined,
|
|
935
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
854
936
|
}
|
|
855
937
|
}
|
|
856
938
|
|
|
857
939
|
function withAppearance(map, workspaceId, patch) {
|
|
858
940
|
const next = Object.assign({}, map || {})
|
|
859
941
|
const merged = Object.assign({}, appearanceOf(next, workspaceId), patch || {})
|
|
860
|
-
|
|
861
|
-
|
|
942
|
+
const entry = {}
|
|
943
|
+
if (merged.tone) entry.tone = merged.tone
|
|
944
|
+
if (merged.strong === true) entry.strong = true
|
|
945
|
+
if (merged.glyph) entry.glyph = merged.glyph
|
|
946
|
+
if (merged.note) entry.note = merged.note
|
|
947
|
+
if (Object.keys(entry).length === 0) delete next[workspaceId]
|
|
948
|
+
else next[workspaceId] = entry
|
|
862
949
|
return next
|
|
863
950
|
}
|
|
864
951
|
|
|
@@ -975,6 +1062,8 @@ window.__ModuleLoader__.load({
|
|
|
975
1062
|
const [drag, setDrag] = React.useState(null)
|
|
976
1063
|
const [dropping, setDropping] = React.useState(null)
|
|
977
1064
|
const [look, setLook] = React.useState(() => readAppearance())
|
|
1065
|
+
const [noteDraft, setNoteDraft] = React.useState('')
|
|
1066
|
+
const [card, setCard] = React.useState(null)
|
|
978
1067
|
const folded = React.useMemo(() => readFolded(), [])
|
|
979
1068
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
980
1069
|
const [switcher, setSwitcher] = React.useState(null)
|
|
@@ -1236,6 +1325,29 @@ window.__ModuleLoader__.load({
|
|
|
1236
1325
|
}
|
|
1237
1326
|
},
|
|
1238
1327
|
}, 'Restore saved order'),
|
|
1328
|
+
React.createElement('button', {
|
|
1329
|
+
type: 'button',
|
|
1330
|
+
'data-polish-glyph': 'open',
|
|
1331
|
+
style: WS_MENU_ITEM,
|
|
1332
|
+
onClick: () => {
|
|
1333
|
+
const target = menu.workspace
|
|
1334
|
+
const anchor = menu.anchor
|
|
1335
|
+
closeMenu()
|
|
1336
|
+
setMenu({ kind: 'glyph', workspace: target, anchor })
|
|
1337
|
+
},
|
|
1338
|
+
}, 'Glyphโฆ'),
|
|
1339
|
+
React.createElement('button', {
|
|
1340
|
+
type: 'button',
|
|
1341
|
+
'data-polish-note': 'open',
|
|
1342
|
+
style: WS_MENU_ITEM,
|
|
1343
|
+
onClick: () => {
|
|
1344
|
+
const target = menu.workspace
|
|
1345
|
+
const anchor = menu.anchor
|
|
1346
|
+
closeMenu()
|
|
1347
|
+
setNoteDraft(appearanceOf(look, target.workspaceId).note || '')
|
|
1348
|
+
setMenu({ kind: 'note', workspace: target, anchor })
|
|
1349
|
+
},
|
|
1350
|
+
}, 'Noteโฆ'),
|
|
1239
1351
|
React.createElement('button', {
|
|
1240
1352
|
type: 'button',
|
|
1241
1353
|
'data-polish-ws-appearance': 'open',
|
|
@@ -1280,6 +1392,67 @@ window.__ModuleLoader__.load({
|
|
|
1280
1392
|
}, 'Unarchive session'))
|
|
1281
1393
|
: null
|
|
1282
1394
|
|
|
1395
|
+
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1396
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1397
|
+
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
1398
|
+
GLYPHS.map((glyph) => React.createElement('button', {
|
|
1399
|
+
key: 'glyph:' + glyph,
|
|
1400
|
+
type: 'button',
|
|
1401
|
+
'data-polish-glyph-pick': glyph,
|
|
1402
|
+
title: 'Use ' + glyph,
|
|
1403
|
+
style: Object.assign({}, WS_GLYPH_BUTTON, {
|
|
1404
|
+
background: appearanceOf(look, menu.workspace.workspaceId).glyph === glyph
|
|
1405
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06))'
|
|
1406
|
+
: undefined,
|
|
1407
|
+
}),
|
|
1408
|
+
onClick: () => {
|
|
1409
|
+
const target = menu.workspace
|
|
1410
|
+
closeMenu()
|
|
1411
|
+
paintLook(target.workspaceId, { glyph })
|
|
1412
|
+
},
|
|
1413
|
+
}, glyph)),
|
|
1414
|
+
React.createElement('button', {
|
|
1415
|
+
key: 'glyph:none',
|
|
1416
|
+
type: 'button',
|
|
1417
|
+
'data-polish-glyph-pick': 'none',
|
|
1418
|
+
style: WS_GLYPH_BUTTON,
|
|
1419
|
+
onClick: () => {
|
|
1420
|
+
const target = menu.workspace
|
|
1421
|
+
closeMenu()
|
|
1422
|
+
paintLook(target.workspaceId, { glyph: '' })
|
|
1423
|
+
},
|
|
1424
|
+
}, 'โซ')))
|
|
1425
|
+
: null
|
|
1426
|
+
|
|
1427
|
+
const noteMenu = menu && menu.kind === 'note'
|
|
1428
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1429
|
+
React.createElement('input', {
|
|
1430
|
+
'data-polish-note-input': 'true',
|
|
1431
|
+
type: 'text',
|
|
1432
|
+
value: noteDraft,
|
|
1433
|
+
placeholder: 'A note for this workspace',
|
|
1434
|
+
autoFocus: true,
|
|
1435
|
+
style: WS_NOTE_INPUT,
|
|
1436
|
+
onChange: (event) => setNoteDraft(event.target.value),
|
|
1437
|
+
onKeyDown: (event) => {
|
|
1438
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1439
|
+
if (event.key !== 'Enter') return
|
|
1440
|
+
const target = menu.workspace
|
|
1441
|
+
const value = noteDraft.trim()
|
|
1442
|
+
closeMenu()
|
|
1443
|
+
paintLook(target.workspaceId, { note: value })
|
|
1444
|
+
},
|
|
1445
|
+
}),
|
|
1446
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1447
|
+
: null
|
|
1448
|
+
|
|
1449
|
+
const noteCard = card
|
|
1450
|
+
? ReactDOM.createPortal(React.createElement('div', {
|
|
1451
|
+
'data-polish-note-card': 'true',
|
|
1452
|
+
style: Object.assign({}, WS_NOTE_CARD, { top: card.top, left: card.left }),
|
|
1453
|
+
}, card.text), document.body)
|
|
1454
|
+
: null
|
|
1455
|
+
|
|
1283
1456
|
const paintMenu = menu && menu.kind === 'paint'
|
|
1284
1457
|
? (() => {
|
|
1285
1458
|
const workspace = menu.workspace
|
|
@@ -1542,8 +1715,31 @@ window.__ModuleLoader__.load({
|
|
|
1542
1715
|
role: 'group',
|
|
1543
1716
|
'aria-label': folder,
|
|
1544
1717
|
title: folded ? 'Expand ' + folder : 'Collapse ' + folder,
|
|
1545
|
-
style: WS_FOLDER_ROW,
|
|
1718
|
+
style: Object.assign({}, WS_FOLDER_ROW, {
|
|
1719
|
+
background: dropping === 'folder:' + folder
|
|
1720
|
+
? 'var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05))'
|
|
1721
|
+
: undefined,
|
|
1722
|
+
}),
|
|
1546
1723
|
onClick: () => setFolders((prev) => Object.assign({}, prev, { [folder]: prev[folder] !== true })),
|
|
1724
|
+
onDragOver: (event) => {
|
|
1725
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1726
|
+
if (folderOf(rows.find((entry) => entry.workspace.workspaceId === drag.id)?.workspace) === folder) return
|
|
1727
|
+
event.preventDefault()
|
|
1728
|
+
setDropping('folder:' + folder)
|
|
1729
|
+
},
|
|
1730
|
+
onDragLeave: () => setDropping(null),
|
|
1731
|
+
onDrop: (event) => {
|
|
1732
|
+
event.preventDefault()
|
|
1733
|
+
setDropping(null)
|
|
1734
|
+
if (!drag || drag.kind !== 'workspace') return
|
|
1735
|
+
const moving = rows.find((entry) => entry.workspace.workspaceId === drag.id)
|
|
1736
|
+
if (!moving || folderOf(moving.workspace) === folder) return
|
|
1737
|
+
const title = fileUnder(workspaceLabel(moving.workspace), folder)
|
|
1738
|
+
Promise.resolve()
|
|
1739
|
+
.then(() => services.renameWorkspace(moving.workspace.workspaceId, title))
|
|
1740
|
+
.then(() => setNotice('Filed under ' + folder + '.'))
|
|
1741
|
+
.catch(() => setNotice('Could not file that workspace.'))
|
|
1742
|
+
},
|
|
1547
1743
|
},
|
|
1548
1744
|
React.createElement('span', { 'aria-hidden': 'true' }, folded ? 'โบ' : 'โ'),
|
|
1549
1745
|
React.createElement('span', { style: WS_TITLE }, folder),
|
|
@@ -1576,8 +1772,15 @@ window.__ModuleLoader__.load({
|
|
|
1576
1772
|
}),
|
|
1577
1773
|
onClick: () => toggleWorkspace(workspace.workspaceId),
|
|
1578
1774
|
onContextMenu: (event) => openWorkspaceMenu(event, workspace),
|
|
1579
|
-
|
|
1580
|
-
|
|
1775
|
+
title: looks.note || undefined,
|
|
1776
|
+
onMouseEnter: (event) => {
|
|
1777
|
+
setHover('workspace:' + workspace.workspaceId)
|
|
1778
|
+
const note = appearanceOf(look, workspace.workspaceId).note
|
|
1779
|
+
if (!note) return
|
|
1780
|
+
const box = event.currentTarget.getBoundingClientRect()
|
|
1781
|
+
setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
|
|
1782
|
+
},
|
|
1783
|
+
onMouseLeave: () => { setHover(null); setCard(null) },
|
|
1581
1784
|
onDragStart: () => setDrag({ kind: 'workspace', id: workspace.workspaceId }),
|
|
1582
1785
|
onDragOver: (event) => {
|
|
1583
1786
|
if (!drag || drag.kind !== 'workspace' || drag.id === workspace.workspaceId) return
|
|
@@ -1588,7 +1791,9 @@ window.__ModuleLoader__.load({
|
|
|
1588
1791
|
onDrop: (event) => { event.preventDefault(); dropOnWorkspace(workspace.workspaceId) },
|
|
1589
1792
|
},
|
|
1590
1793
|
React.createElement(CaretGlyph, { open }),
|
|
1591
|
-
|
|
1794
|
+
looks.glyph
|
|
1795
|
+
? React.createElement('span', { 'data-polish-ws-glyph': looks.glyph, style: WS_GLYPH }, looks.glyph)
|
|
1796
|
+
: React.createElement(FolderGlyph),
|
|
1592
1797
|
looks.tone
|
|
1593
1798
|
? React.createElement('span', { style: Object.assign({}, WS_TONE, { background: TONE_COLOR[looks.tone] }) })
|
|
1594
1799
|
: null,
|
|
@@ -1669,6 +1874,9 @@ window.__ModuleLoader__.load({
|
|
|
1669
1874
|
: null,
|
|
1670
1875
|
switcherCard,
|
|
1671
1876
|
sessionMenu,
|
|
1877
|
+
glyphMenu,
|
|
1878
|
+
noteMenu,
|
|
1879
|
+
noteCard,
|
|
1672
1880
|
archivedMenu,
|
|
1673
1881
|
paintMenu,
|
|
1674
1882
|
workspaceMenu)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|