@aryacode/dsh-aryacode-polish 0.19.0 → 0.20.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 +4 -0
- package/client/workspace.js +152 -4
- package/docs/better-workspace.md +14 -0
- package/lib/client.js +153 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/client/workspace.js
CHANGED
|
@@ -387,6 +387,79 @@ 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
389
|
const FOLDER_KEY = 'aryacode-polish:folders.v1'
|
|
390
|
+
const MARK_KEY = 'aryacode-polish:session-marks.v1'
|
|
391
|
+
|
|
392
|
+
export function markOf(map, sessionId) {
|
|
393
|
+
const entry = (map && map[String(sessionId)]) || {}
|
|
394
|
+
return {
|
|
395
|
+
flag: entry.flag === true,
|
|
396
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function withMark(map, sessionId, patch) {
|
|
401
|
+
const next = Object.assign({}, map || {})
|
|
402
|
+
const key = String(sessionId)
|
|
403
|
+
const merged = Object.assign({}, markOf(next, key), patch || {})
|
|
404
|
+
const entry = {}
|
|
405
|
+
if (merged.flag === true) entry.flag = true
|
|
406
|
+
if (merged.note) entry.note = merged.note
|
|
407
|
+
if (Object.keys(entry).length === 0) delete next[key]
|
|
408
|
+
else next[key] = entry
|
|
409
|
+
return next
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function readMarks(storage) {
|
|
413
|
+
try {
|
|
414
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
415
|
+
if (!store) return {}
|
|
416
|
+
const parsed = JSON.parse(store.getItem(MARK_KEY) || '{}')
|
|
417
|
+
return parsed && typeof parsed === 'object' ? parsed : {}
|
|
418
|
+
} catch {
|
|
419
|
+
return {}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function writeMarks(map, storage) {
|
|
424
|
+
try {
|
|
425
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
426
|
+
if (!store) return
|
|
427
|
+
store.setItem(MARK_KEY, JSON.stringify(map || {}))
|
|
428
|
+
} catch {
|
|
429
|
+
/* a mark is a nicety */
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** The whole tree as indented text, for pasting into a prompt or an issue. */
|
|
434
|
+
export function treeText(rows, folderNames, markOfSession) {
|
|
435
|
+
const lines = []
|
|
436
|
+
const seen = new Set()
|
|
437
|
+
const pushGroup = (folder) => {
|
|
438
|
+
if (seen.has(folder)) return
|
|
439
|
+
seen.add(folder)
|
|
440
|
+
lines.push(folder)
|
|
441
|
+
for (const row of rows || []) {
|
|
442
|
+
const name = folderKey(row.workspace)
|
|
443
|
+
if (name !== folder) continue
|
|
444
|
+
lines.push(' ' + workspaceLabel(row.workspace))
|
|
445
|
+
for (const session of row.sessions || []) {
|
|
446
|
+
const mark = typeof markOfSession === 'function' ? markOfSession(session.id) : { flag: false }
|
|
447
|
+
lines.push(' ' + (mark && mark.flag ? '⚑ ' : '') + String(session.displayTitle || session.title || session.id))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
for (const folder of folderNames || []) pushGroup(folder)
|
|
452
|
+
for (const row of rows || []) {
|
|
453
|
+
const name = folderKey(row.workspace)
|
|
454
|
+
if (name !== undefined) continue
|
|
455
|
+
lines.push(workspaceLabel(row.workspace))
|
|
456
|
+
for (const session of row.sessions || []) {
|
|
457
|
+
const mark = typeof markOfSession === 'function' ? markOfSession(session.id) : { flag: false }
|
|
458
|
+
lines.push(' ' + (mark && mark.flag ? '⚑ ' : '') + String(session.displayTitle || session.title || session.id))
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return lines.join('\n')
|
|
462
|
+
}
|
|
390
463
|
|
|
391
464
|
const cleanFolder = (name) => {
|
|
392
465
|
const value = String(name || '').trim().replace(/^\/+|\/+$/g, '')
|
|
@@ -937,11 +1010,14 @@ export function BetterWorkspace(props) {
|
|
|
937
1010
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
938
1011
|
const [declared, setDeclared] = React.useState(() => readFolders())
|
|
939
1012
|
const [folderDraft, setFolderDraft] = React.useState('')
|
|
1013
|
+
const [marks, setMarks] = React.useState(() => readMarks())
|
|
1014
|
+
const [markDraft, setMarkDraft] = React.useState('')
|
|
940
1015
|
const [switcher, setSwitcher] = React.useState(null)
|
|
941
1016
|
const [density, setDensity] = React.useState(() => readDensity())
|
|
942
1017
|
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
943
1018
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
944
1019
|
React.useEffect(() => { writeFolders(declared) }, [declared])
|
|
1020
|
+
React.useEffect(() => { writeMarks(marks) }, [marks])
|
|
945
1021
|
const [renaming, setRenaming] = React.useState(null)
|
|
946
1022
|
const commitRename = async () => {
|
|
947
1023
|
const draft = renaming
|
|
@@ -1355,6 +1431,28 @@ export function BetterWorkspace(props) {
|
|
|
1355
1431
|
React.createElement('div', { style: WS_MENU_HINT }, 'Renames every workspace inside it'))
|
|
1356
1432
|
: null
|
|
1357
1433
|
|
|
1434
|
+
const markMenu = menu && menu.kind === 'mark'
|
|
1435
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1436
|
+
React.createElement('input', {
|
|
1437
|
+
'data-polish-mark-input': 'true',
|
|
1438
|
+
type: 'text',
|
|
1439
|
+
value: markDraft,
|
|
1440
|
+
placeholder: 'A note for this session',
|
|
1441
|
+
autoFocus: true,
|
|
1442
|
+
style: WS_NOTE_INPUT,
|
|
1443
|
+
onChange: (event) => setMarkDraft(event.target.value),
|
|
1444
|
+
onKeyDown: (event) => {
|
|
1445
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1446
|
+
if (event.key !== 'Enter') return
|
|
1447
|
+
const id = menu.row.id
|
|
1448
|
+
const value = markDraft.trim()
|
|
1449
|
+
closeMenu()
|
|
1450
|
+
setMarks((prev) => withMark(prev, id, { note: value }))
|
|
1451
|
+
},
|
|
1452
|
+
}),
|
|
1453
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1454
|
+
: null
|
|
1455
|
+
|
|
1358
1456
|
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1359
1457
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1360
1458
|
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
@@ -1463,7 +1561,29 @@ export function BetterWorkspace(props) {
|
|
|
1463
1561
|
workspacesSnapshot: workspaceState,
|
|
1464
1562
|
session: { sessionId: menu.row.id, displayTitle: menu.row.displayTitle, session: menu.row },
|
|
1465
1563
|
}))),
|
|
1466
|
-
|
|
1564
|
+
React.createElement('button', {
|
|
1565
|
+
type: 'button',
|
|
1566
|
+
'data-polish-session-flag': menu.row.id,
|
|
1567
|
+
style: WS_MENU_ITEM,
|
|
1568
|
+
onClick: () => {
|
|
1569
|
+
const row = menu.row
|
|
1570
|
+
const current = markOf(marks, row.id)
|
|
1571
|
+
closeMenu()
|
|
1572
|
+
setMarks((prev) => withMark(prev, row.id, { flag: !current.flag }))
|
|
1573
|
+
},
|
|
1574
|
+
}, markOf(marks, menu.row.id).flag ? 'Remove flag' : 'Flag this session'),
|
|
1575
|
+
React.createElement('button', {
|
|
1576
|
+
type: 'button',
|
|
1577
|
+
'data-polish-session-note': menu.row.id,
|
|
1578
|
+
style: WS_MENU_ITEM,
|
|
1579
|
+
onClick: () => {
|
|
1580
|
+
const row = menu.row
|
|
1581
|
+
const anchor = menu.anchor
|
|
1582
|
+
closeMenu()
|
|
1583
|
+
setMarkDraft(markOf(marks, row.id).note || '')
|
|
1584
|
+
setMenu({ kind: 'mark', row, anchor })
|
|
1585
|
+
},
|
|
1586
|
+
}, 'Session note…'))
|
|
1467
1587
|
: null
|
|
1468
1588
|
|
|
1469
1589
|
const switcherCard = switcher
|
|
@@ -1529,6 +1649,20 @@ export function BetterWorkspace(props) {
|
|
|
1529
1649
|
style: WS_ADD,
|
|
1530
1650
|
onClick: foldAll,
|
|
1531
1651
|
}, allFolded ? '⌄' : '⌃'),
|
|
1652
|
+
React.createElement('button', {
|
|
1653
|
+
key: 'copy',
|
|
1654
|
+
type: 'button',
|
|
1655
|
+
'data-polish-ws-copy': 'tree',
|
|
1656
|
+
title: 'Copy the tree as text',
|
|
1657
|
+
'aria-label': 'Copy the tree as text',
|
|
1658
|
+
style: WS_ADD,
|
|
1659
|
+
onClick: () => {
|
|
1660
|
+
const text = treeText(rows, folderNames, (id) => markOf(marks, id))
|
|
1661
|
+
Promise.resolve(navigator.clipboard.writeText(text))
|
|
1662
|
+
.then(() => setNotice('Tree copied.'))
|
|
1663
|
+
.catch(() => setNotice('Could not copy the tree.'))
|
|
1664
|
+
},
|
|
1665
|
+
}, '⧉'),
|
|
1532
1666
|
React.createElement('button', {
|
|
1533
1667
|
key: 'density',
|
|
1534
1668
|
type: 'button',
|
|
@@ -1638,8 +1772,14 @@ export function BetterWorkspace(props) {
|
|
|
1638
1772
|
}),
|
|
1639
1773
|
onClick: () => services.openSession(row.id),
|
|
1640
1774
|
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
1641
|
-
onMouseEnter: () =>
|
|
1642
|
-
|
|
1775
|
+
onMouseEnter: (event) => {
|
|
1776
|
+
setHover('session:' + row.id)
|
|
1777
|
+
const note = markOf(marks, row.id).note
|
|
1778
|
+
if (!note) return
|
|
1779
|
+
const box = event.currentTarget.getBoundingClientRect()
|
|
1780
|
+
setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
|
|
1781
|
+
},
|
|
1782
|
+
onMouseLeave: () => { setHover(null); setCard(null) },
|
|
1643
1783
|
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
1644
1784
|
onDragOver: (event) => {
|
|
1645
1785
|
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
@@ -1651,7 +1791,14 @@ export function BetterWorkspace(props) {
|
|
|
1651
1791
|
},
|
|
1652
1792
|
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
1653
1793
|
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
1654
|
-
|
|
1794
|
+
markOf(marks, row.id).flag
|
|
1795
|
+
? React.createElement('span', { 'data-polish-mark-flag': row.id, style: WS_PIN, title: 'Flagged' }, '⚑')
|
|
1796
|
+
: null,
|
|
1797
|
+
React.createElement('span', {
|
|
1798
|
+
style: Object.assign({}, WS_TITLE, {
|
|
1799
|
+
fontStyle: markOf(marks, row.id).note ? 'italic' : undefined,
|
|
1800
|
+
}),
|
|
1801
|
+
}, row.displayTitle || row.id),
|
|
1655
1802
|
rowHovered || rowFocused
|
|
1656
1803
|
? React.createElement('button', {
|
|
1657
1804
|
type: 'button',
|
|
@@ -1907,6 +2054,7 @@ export function BetterWorkspace(props) {
|
|
|
1907
2054
|
noteMenu,
|
|
1908
2055
|
folderMenu,
|
|
1909
2056
|
renameFolderMenu,
|
|
2057
|
+
markMenu,
|
|
1910
2058
|
noteCard,
|
|
1911
2059
|
archivedMenu,
|
|
1912
2060
|
paintMenu,
|
package/docs/better-workspace.md
CHANGED
|
@@ -225,3 +225,17 @@ a renamed folder does not suddenly spring open.
|
|
|
225
225
|
and does the inverse: `unfiled('Work/Alpha')` is `Alpha`, renamed through the Host like any
|
|
226
226
|
other title change. A folder left with nothing in it simply stops being drawn, unless it was
|
|
227
227
|
declared empty on purpose.
|
|
228
|
+
|
|
229
|
+
## Flags and notes on sessions
|
|
230
|
+
|
|
231
|
+
A session can carry a flag (`⚑`, shown before its title) and a note (shown on hover, in the same
|
|
232
|
+
card the workspace notes use). Both live in `aryacode-polish:session-marks.v1`, keyed by session
|
|
233
|
+
id, and both are set from the session's own menu: **Flag this session** / **Remove flag** and
|
|
234
|
+
**Session note…** (Enter saves, Escape cancels). The tree never invents a session, so a mark for a
|
|
235
|
+
session that no longer exists is simply never drawn.
|
|
236
|
+
|
|
237
|
+
## Copy the tree
|
|
238
|
+
|
|
239
|
+
The header's `⧉` copies the whole tree as indented text — folders, workspaces, sessions, with
|
|
240
|
+
`⚑` before a flagged session — through `navigator.clipboard.writeText`. It is meant for pasting
|
|
241
|
+
into a prompt or an issue: "here is what I am looking at", without screenshots.
|
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.20.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. */
|
|
@@ -588,6 +588,79 @@ window.__ModuleLoader__.load({
|
|
|
588
588
|
const FOLD_KEY = 'aryacode-polish:folded.v1'
|
|
589
589
|
const DENSITY_KEY = 'aryacode-polish:density.v1'
|
|
590
590
|
const FOLDER_KEY = 'aryacode-polish:folders.v1'
|
|
591
|
+
const MARK_KEY = 'aryacode-polish:session-marks.v1'
|
|
592
|
+
|
|
593
|
+
function markOf(map, sessionId) {
|
|
594
|
+
const entry = (map && map[String(sessionId)]) || {}
|
|
595
|
+
return {
|
|
596
|
+
flag: entry.flag === true,
|
|
597
|
+
note: typeof entry.note === 'string' && entry.note.length > 0 ? entry.note : undefined,
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function withMark(map, sessionId, patch) {
|
|
602
|
+
const next = Object.assign({}, map || {})
|
|
603
|
+
const key = String(sessionId)
|
|
604
|
+
const merged = Object.assign({}, markOf(next, key), patch || {})
|
|
605
|
+
const entry = {}
|
|
606
|
+
if (merged.flag === true) entry.flag = true
|
|
607
|
+
if (merged.note) entry.note = merged.note
|
|
608
|
+
if (Object.keys(entry).length === 0) delete next[key]
|
|
609
|
+
else next[key] = entry
|
|
610
|
+
return next
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function readMarks(storage) {
|
|
614
|
+
try {
|
|
615
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
616
|
+
if (!store) return {}
|
|
617
|
+
const parsed = JSON.parse(store.getItem(MARK_KEY) || '{}')
|
|
618
|
+
return parsed && typeof parsed === 'object' ? parsed : {}
|
|
619
|
+
} catch {
|
|
620
|
+
return {}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function writeMarks(map, storage) {
|
|
625
|
+
try {
|
|
626
|
+
const store = storage || (typeof localStorage === 'undefined' ? undefined : localStorage)
|
|
627
|
+
if (!store) return
|
|
628
|
+
store.setItem(MARK_KEY, JSON.stringify(map || {}))
|
|
629
|
+
} catch {
|
|
630
|
+
/* a mark is a nicety */
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** The whole tree as indented text, for pasting into a prompt or an issue. */
|
|
635
|
+
function treeText(rows, folderNames, markOfSession) {
|
|
636
|
+
const lines = []
|
|
637
|
+
const seen = new Set()
|
|
638
|
+
const pushGroup = (folder) => {
|
|
639
|
+
if (seen.has(folder)) return
|
|
640
|
+
seen.add(folder)
|
|
641
|
+
lines.push(folder)
|
|
642
|
+
for (const row of rows || []) {
|
|
643
|
+
const name = folderKey(row.workspace)
|
|
644
|
+
if (name !== folder) continue
|
|
645
|
+
lines.push(' ' + workspaceLabel(row.workspace))
|
|
646
|
+
for (const session of row.sessions || []) {
|
|
647
|
+
const mark = typeof markOfSession === 'function' ? markOfSession(session.id) : { flag: false }
|
|
648
|
+
lines.push(' ' + (mark && mark.flag ? '⚑ ' : '') + String(session.displayTitle || session.title || session.id))
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
for (const folder of folderNames || []) pushGroup(folder)
|
|
653
|
+
for (const row of rows || []) {
|
|
654
|
+
const name = folderKey(row.workspace)
|
|
655
|
+
if (name !== undefined) continue
|
|
656
|
+
lines.push(workspaceLabel(row.workspace))
|
|
657
|
+
for (const session of row.sessions || []) {
|
|
658
|
+
const mark = typeof markOfSession === 'function' ? markOfSession(session.id) : { flag: false }
|
|
659
|
+
lines.push(' ' + (mark && mark.flag ? '⚑ ' : '') + String(session.displayTitle || session.title || session.id))
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return lines.join('\n')
|
|
663
|
+
}
|
|
591
664
|
|
|
592
665
|
const cleanFolder = (name) => {
|
|
593
666
|
const value = String(name || '').trim().replace(/^\/+|\/+$/g, '')
|
|
@@ -1138,11 +1211,14 @@ window.__ModuleLoader__.load({
|
|
|
1138
1211
|
const [folders, setFolders] = React.useState(folded.folders)
|
|
1139
1212
|
const [declared, setDeclared] = React.useState(() => readFolders())
|
|
1140
1213
|
const [folderDraft, setFolderDraft] = React.useState('')
|
|
1214
|
+
const [marks, setMarks] = React.useState(() => readMarks())
|
|
1215
|
+
const [markDraft, setMarkDraft] = React.useState('')
|
|
1141
1216
|
const [switcher, setSwitcher] = React.useState(null)
|
|
1142
1217
|
const [density, setDensity] = React.useState(() => readDensity())
|
|
1143
1218
|
const [archiveOpen, setArchiveOpen] = React.useState(false)
|
|
1144
1219
|
React.useEffect(() => { writeFolded({ workspaces: expanded, folders }) }, [expanded, folders])
|
|
1145
1220
|
React.useEffect(() => { writeFolders(declared) }, [declared])
|
|
1221
|
+
React.useEffect(() => { writeMarks(marks) }, [marks])
|
|
1146
1222
|
const [renaming, setRenaming] = React.useState(null)
|
|
1147
1223
|
const commitRename = async () => {
|
|
1148
1224
|
const draft = renaming
|
|
@@ -1556,6 +1632,28 @@ window.__ModuleLoader__.load({
|
|
|
1556
1632
|
React.createElement('div', { style: WS_MENU_HINT }, 'Renames every workspace inside it'))
|
|
1557
1633
|
: null
|
|
1558
1634
|
|
|
1635
|
+
const markMenu = menu && menu.kind === 'mark'
|
|
1636
|
+
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1637
|
+
React.createElement('input', {
|
|
1638
|
+
'data-polish-mark-input': 'true',
|
|
1639
|
+
type: 'text',
|
|
1640
|
+
value: markDraft,
|
|
1641
|
+
placeholder: 'A note for this session',
|
|
1642
|
+
autoFocus: true,
|
|
1643
|
+
style: WS_NOTE_INPUT,
|
|
1644
|
+
onChange: (event) => setMarkDraft(event.target.value),
|
|
1645
|
+
onKeyDown: (event) => {
|
|
1646
|
+
if (event.key === 'Escape') { event.stopPropagation(); closeMenu(); return }
|
|
1647
|
+
if (event.key !== 'Enter') return
|
|
1648
|
+
const id = menu.row.id
|
|
1649
|
+
const value = markDraft.trim()
|
|
1650
|
+
closeMenu()
|
|
1651
|
+
setMarks((prev) => withMark(prev, id, { note: value }))
|
|
1652
|
+
},
|
|
1653
|
+
}),
|
|
1654
|
+
React.createElement('div', { style: WS_MENU_HINT }, 'Enter saves, Escape cancels'))
|
|
1655
|
+
: null
|
|
1656
|
+
|
|
1559
1657
|
const glyphMenu = menu && menu.kind === 'glyph'
|
|
1560
1658
|
? React.createElement(RowMenu, { anchor: menu.anchor, onClose: closeMenu },
|
|
1561
1659
|
React.createElement('div', { style: WS_GLYPH_GRID },
|
|
@@ -1664,7 +1762,29 @@ window.__ModuleLoader__.load({
|
|
|
1664
1762
|
workspacesSnapshot: workspaceState,
|
|
1665
1763
|
session: { sessionId: menu.row.id, displayTitle: menu.row.displayTitle, session: menu.row },
|
|
1666
1764
|
}))),
|
|
1667
|
-
|
|
1765
|
+
React.createElement('button', {
|
|
1766
|
+
type: 'button',
|
|
1767
|
+
'data-polish-session-flag': menu.row.id,
|
|
1768
|
+
style: WS_MENU_ITEM,
|
|
1769
|
+
onClick: () => {
|
|
1770
|
+
const row = menu.row
|
|
1771
|
+
const current = markOf(marks, row.id)
|
|
1772
|
+
closeMenu()
|
|
1773
|
+
setMarks((prev) => withMark(prev, row.id, { flag: !current.flag }))
|
|
1774
|
+
},
|
|
1775
|
+
}, markOf(marks, menu.row.id).flag ? 'Remove flag' : 'Flag this session'),
|
|
1776
|
+
React.createElement('button', {
|
|
1777
|
+
type: 'button',
|
|
1778
|
+
'data-polish-session-note': menu.row.id,
|
|
1779
|
+
style: WS_MENU_ITEM,
|
|
1780
|
+
onClick: () => {
|
|
1781
|
+
const row = menu.row
|
|
1782
|
+
const anchor = menu.anchor
|
|
1783
|
+
closeMenu()
|
|
1784
|
+
setMarkDraft(markOf(marks, row.id).note || '')
|
|
1785
|
+
setMenu({ kind: 'mark', row, anchor })
|
|
1786
|
+
},
|
|
1787
|
+
}, 'Session note…'))
|
|
1668
1788
|
: null
|
|
1669
1789
|
|
|
1670
1790
|
const switcherCard = switcher
|
|
@@ -1730,6 +1850,20 @@ window.__ModuleLoader__.load({
|
|
|
1730
1850
|
style: WS_ADD,
|
|
1731
1851
|
onClick: foldAll,
|
|
1732
1852
|
}, allFolded ? '⌄' : '⌃'),
|
|
1853
|
+
React.createElement('button', {
|
|
1854
|
+
key: 'copy',
|
|
1855
|
+
type: 'button',
|
|
1856
|
+
'data-polish-ws-copy': 'tree',
|
|
1857
|
+
title: 'Copy the tree as text',
|
|
1858
|
+
'aria-label': 'Copy the tree as text',
|
|
1859
|
+
style: WS_ADD,
|
|
1860
|
+
onClick: () => {
|
|
1861
|
+
const text = treeText(rows, folderNames, (id) => markOf(marks, id))
|
|
1862
|
+
Promise.resolve(navigator.clipboard.writeText(text))
|
|
1863
|
+
.then(() => setNotice('Tree copied.'))
|
|
1864
|
+
.catch(() => setNotice('Could not copy the tree.'))
|
|
1865
|
+
},
|
|
1866
|
+
}, '⧉'),
|
|
1733
1867
|
React.createElement('button', {
|
|
1734
1868
|
key: 'density',
|
|
1735
1869
|
type: 'button',
|
|
@@ -1839,8 +1973,14 @@ window.__ModuleLoader__.load({
|
|
|
1839
1973
|
}),
|
|
1840
1974
|
onClick: () => services.openSession(row.id),
|
|
1841
1975
|
onContextMenu: (event) => openMenu(event, row, workspace),
|
|
1842
|
-
onMouseEnter: () =>
|
|
1843
|
-
|
|
1976
|
+
onMouseEnter: (event) => {
|
|
1977
|
+
setHover('session:' + row.id)
|
|
1978
|
+
const note = markOf(marks, row.id).note
|
|
1979
|
+
if (!note) return
|
|
1980
|
+
const box = event.currentTarget.getBoundingClientRect()
|
|
1981
|
+
setCard({ text: note, top: box.bottom + 6, left: Math.max(8, box.left) })
|
|
1982
|
+
},
|
|
1983
|
+
onMouseLeave: () => { setHover(null); setCard(null) },
|
|
1844
1984
|
onDragStart: () => setDrag({ kind: 'session', id: row.id, workspaceId: workspace.workspaceId }),
|
|
1845
1985
|
onDragOver: (event) => {
|
|
1846
1986
|
if (!drag || drag.kind !== 'session' || drag.id === row.id) return
|
|
@@ -1852,7 +1992,14 @@ window.__ModuleLoader__.load({
|
|
|
1852
1992
|
},
|
|
1853
1993
|
row.running ? React.createElement('span', { style: WS_DOT, title: 'Running' }) : null,
|
|
1854
1994
|
pinned.has(row.id) ? React.createElement('span', { style: WS_PIN, title: 'Pinned' }, '\u2605') : null,
|
|
1855
|
-
|
|
1995
|
+
markOf(marks, row.id).flag
|
|
1996
|
+
? React.createElement('span', { 'data-polish-mark-flag': row.id, style: WS_PIN, title: 'Flagged' }, '⚑')
|
|
1997
|
+
: null,
|
|
1998
|
+
React.createElement('span', {
|
|
1999
|
+
style: Object.assign({}, WS_TITLE, {
|
|
2000
|
+
fontStyle: markOf(marks, row.id).note ? 'italic' : undefined,
|
|
2001
|
+
}),
|
|
2002
|
+
}, row.displayTitle || row.id),
|
|
1856
2003
|
rowHovered || rowFocused
|
|
1857
2004
|
? React.createElement('button', {
|
|
1858
2005
|
type: 'button',
|
|
@@ -2108,6 +2255,7 @@ window.__ModuleLoader__.load({
|
|
|
2108
2255
|
noteMenu,
|
|
2109
2256
|
folderMenu,
|
|
2110
2257
|
renameFolderMenu,
|
|
2258
|
+
markMenu,
|
|
2111
2259
|
noteCard,
|
|
2112
2260
|
archivedMenu,
|
|
2113
2261
|
paintMenu,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|