feat: model inbox capture artifacts
This commit is contained in:
parent
2e86229350
commit
d6ef3a973a
|
|
@ -1,6 +1,12 @@
|
|||
package main
|
||||
|
||||
func (a *App) ListInboxNodes() ([]NodeDTO, error) {
|
||||
type InboxNodeDTO struct {
|
||||
NodeDTO
|
||||
CaptureKind string `json:"captureKind"`
|
||||
CaptureSource string `json:"captureSource"`
|
||||
}
|
||||
|
||||
func (a *App) ListInboxNodes() ([]InboxNodeDTO, error) {
|
||||
if err := a.requireVault(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -8,7 +14,17 @@ func (a *App) ListInboxNodes() ([]NodeDTO, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtos := toNodeDTOs(list)
|
||||
dtos := make([]InboxNodeDTO, 0, len(list))
|
||||
for _, n := range list {
|
||||
dto := InboxNodeDTO{NodeDTO: toNodeDTO(&n)}
|
||||
if kind, ok, err := a.nodes.MetaGet(n.ID, "capture.kind"); err == nil && ok {
|
||||
dto.CaptureKind = kind
|
||||
}
|
||||
if source, ok, err := a.nodes.MetaGet(n.ID, "capture.source"); err == nil && ok {
|
||||
dto.CaptureSource = source
|
||||
}
|
||||
dtos = append(dtos, dto)
|
||||
}
|
||||
for i := range dtos {
|
||||
n, err := a.nodes.CountChildren(dtos[i].ID, "case", "client", "project", "folder", "document", "recipe")
|
||||
if err != nil {
|
||||
|
|
@ -18,3 +34,18 @@ func (a *App) ListInboxNodes() ([]NodeDTO, error) {
|
|||
}
|
||||
return dtos, nil
|
||||
}
|
||||
|
||||
func (a *App) filterInboxCaptureNodes(list []NodeDTO) []NodeDTO {
|
||||
out := make([]NodeDTO, 0, len(list))
|
||||
for _, item := range list {
|
||||
if !a.isInboxCaptureNode(item.ID) {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *App) isInboxCaptureNode(nodeID string) bool {
|
||||
v, ok, err := a.nodes.MetaGet(nodeID, "capture.inbox")
|
||||
return err == nil && ok && v == "true"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func (a *App) ListWorkspaceTree() ([]NodeDTO, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtos := filterContainers(toNodeDTOs(list))
|
||||
dtos := filterContainers(a.filterInboxCaptureNodes(toNodeDTOs(list)))
|
||||
for i := range dtos {
|
||||
n, err := a.nodes.CountChildren(dtos[i].ID, "case", "client", "project", "folder", "document", "recipe")
|
||||
if err != nil {
|
||||
|
|
@ -41,7 +41,7 @@ func (a *App) ListWorkspaceChildren(parentID string) ([]NodeDTO, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dtos := filterContainers(toNodeDTOs(list))
|
||||
dtos := filterContainers(a.filterInboxCaptureNodes(toNodeDTOs(list)))
|
||||
for i := range dtos {
|
||||
n, err := a.nodes.CountChildren(dtos[i].ID, "case", "client", "project", "folder", "document", "recipe")
|
||||
if err != nil {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,7 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-O6a-DCWF.js"></script>
|
||||
<script type="module" crossorigin src="/assets/main-meJOE1Ze.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-es_E5H-H.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ func TestListInboxNodesReturnsOnlyCapturedArtifacts(t *testing.T) {
|
|||
if err := app.nodes.MetaSet(captured.ID, "capture.inbox", "true"); err != nil {
|
||||
t.Fatalf("mark captured: %v", err)
|
||||
}
|
||||
if err := app.nodes.MetaSet(captured.ID, "capture.kind", "text"); err != nil {
|
||||
t.Fatalf("mark capture kind: %v", err)
|
||||
}
|
||||
if err := app.nodes.MetaSet(captured.ID, "capture.source", "clipboard"); err != nil {
|
||||
t.Fatalf("mark capture source: %v", err)
|
||||
}
|
||||
|
||||
list, err := app.ListInboxNodes()
|
||||
if err != nil {
|
||||
|
|
@ -40,6 +46,16 @@ func TestListInboxNodesReturnsOnlyCapturedArtifacts(t *testing.T) {
|
|||
if !got[captured.ID] {
|
||||
t.Fatal("captured artifact missing from inbox")
|
||||
}
|
||||
for _, item := range list {
|
||||
if item.ID == captured.ID {
|
||||
if item.CaptureKind != "text" {
|
||||
t.Fatalf("CaptureKind = %q, want text", item.CaptureKind)
|
||||
}
|
||||
if item.CaptureSource != "clipboard" {
|
||||
t.Fatalf("CaptureSource = %q, want clipboard", item.CaptureSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
if got[manual.ID] {
|
||||
t.Fatal("manual root should not be in inbox")
|
||||
}
|
||||
|
|
@ -49,4 +65,14 @@ func TestListInboxNodesReturnsOnlyCapturedArtifacts(t *testing.T) {
|
|||
if got[child.ID] {
|
||||
t.Fatal("nested child should not be in inbox")
|
||||
}
|
||||
|
||||
workspace, err := app.ListWorkspaceTree()
|
||||
if err != nil {
|
||||
t.Fatalf("ListWorkspaceTree: %v", err)
|
||||
}
|
||||
for _, item := range workspace {
|
||||
if item.ID == captured.ID {
|
||||
t.Fatal("captured artifact should not be shown in workspace tree")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1374,6 +1374,14 @@
|
|||
const labels = { 'project': t('kind.project'), 'client': t('kind.client'), 'document': t('kind.document'), 'recipe': t('kind.recipe'), 'folder': t('kind.folder'), 'note': t('kind.note'), 'file': t('kind.file'), 'archive': t('kind.archive'), 'case': t('kind.case') }
|
||||
return labels[kind] || kind || t('kind.case')
|
||||
}
|
||||
function captureKindLabel(kind) {
|
||||
if (!kind) return ''
|
||||
return t('capture.kind.' + kind)
|
||||
}
|
||||
function captureSourceLabel(source) {
|
||||
if (!source) return ''
|
||||
return t('capture.source.' + source)
|
||||
}
|
||||
function pluralize(n, one, few, many) {
|
||||
n = Math.abs(n) % 100
|
||||
if (n >= 5 && n <= 20) return many
|
||||
|
|
@ -2015,7 +2023,11 @@
|
|||
<div class="inbox-item" role="button" tabindex="0" on:click={() => openNodeById(item.id)} on:keydown={(e) => e.key === 'Enter' && openNodeById(item.id)}>
|
||||
<div class="inbox-item-main">
|
||||
<span class="inbox-item-title">{item.title}</span>
|
||||
<span class="inbox-item-meta">{nodeKindLabel(item.type)} · {formatDate(item.createdAt)}</span>
|
||||
<span class="inbox-item-meta">
|
||||
{#if item.captureKind}{captureKindLabel(item.captureKind)} · {/if}
|
||||
{#if item.captureSource}{captureSourceLabel(item.captureSource)} · {/if}
|
||||
{formatDate(item.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="inbox-item-actions">
|
||||
<button class="btn btn-sm" on:click|stopPropagation={() => openNodeById(item.id)}>{t('common.open')}</button>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ export default {
|
|||
'nav.moveToRoot': 'Move to root',
|
||||
'inbox.subtitle': 'Captured materials that still need to be assigned to cases',
|
||||
'inbox.empty': 'No unprocessed items',
|
||||
'capture.kind.text': 'Text',
|
||||
'capture.kind.url': 'Link',
|
||||
'capture.kind.file': 'File',
|
||||
'capture.kind.folder': 'Folder',
|
||||
'capture.kind.image': 'Image',
|
||||
'capture.source.clipboard': 'Clipboard',
|
||||
'capture.source.drop': 'Drop',
|
||||
'capture.source.browser': 'Browser',
|
||||
'capture.source.manual': 'Manual',
|
||||
'trash.openFolder': 'Open trash folder',
|
||||
'trash.empty': 'Trash is empty',
|
||||
'trash.deletedNodes': 'Deleted items',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,16 @@ export default {
|
|||
'inbox.subtitle': 'Захваченные материалы, которые нужно разложить по делам',
|
||||
'inbox.empty': 'Неразобранных элементов нет',
|
||||
|
||||
'capture.kind.text': 'Текст',
|
||||
'capture.kind.url': 'Ссылка',
|
||||
'capture.kind.file': 'Файл',
|
||||
'capture.kind.folder': 'Папка',
|
||||
'capture.kind.image': 'Изображение',
|
||||
'capture.source.clipboard': 'Буфер обмена',
|
||||
'capture.source.drop': 'Перетаскивание',
|
||||
'capture.source.browser': 'Браузер',
|
||||
'capture.source.manual': 'Вручную',
|
||||
|
||||
'trash.openFolder': 'Открыть папку корзины',
|
||||
'trash.empty': 'Корзина пуста',
|
||||
'trash.deletedNodes': 'Удаленные элементы',
|
||||
|
|
|
|||
|
|
@ -132,10 +132,13 @@ async function runReadyScenario(cdp, url) {
|
|||
await screenshot(cdp, 'settings.png')
|
||||
await click(cdp, '.close-btn')
|
||||
await waitForGone(cdp, '.settings-window')
|
||||
await assertEval(cdp, `![...document.querySelectorAll('.tree-label')].some((el) => el.innerText.includes('Inbox Smoke Item'))`, 'workspace: captured item hidden from tree')
|
||||
|
||||
await clickText(cdp, '.nav-item', 'Неразобранное')
|
||||
await assertText(cdp, 'Неразобранное', 'inbox: system view opens')
|
||||
await assertText(cdp, 'Inbox Smoke Item', 'inbox: captured item visible')
|
||||
await assertText(cdp, 'Текст', 'inbox: capture kind visible')
|
||||
await assertText(cdp, 'Буфер обмена', 'inbox: capture source visible')
|
||||
await assertEval(cdp, `!document.querySelector('.inbox-screen')?.innerText.includes('Manual Root Item')`, 'inbox: manual root is hidden')
|
||||
await screenshot(cdp, 'inbox.png')
|
||||
await clickText(cdp, '.inbox-item-actions .btn', 'Открыть')
|
||||
|
|
@ -578,7 +581,7 @@ function wailsMockSource() {
|
|||
],
|
||||
},
|
||||
{ id: 'node-client', title: 'Smoke Client', type: 'client', section: 'clients', createdAt: now, has_children: false, children: [] },
|
||||
{ id: 'node-inbox', title: 'Inbox Smoke Item', type: 'folder', section: '', captureInbox: true, createdAt: now, has_children: false, children: [] },
|
||||
{ id: 'node-inbox', title: 'Inbox Smoke Item', type: 'folder', section: '', captureInbox: true, captureKind: 'text', captureSource: 'clipboard', createdAt: now, has_children: false, children: [] },
|
||||
{ id: 'node-manual-root', title: 'Manual Root Item', type: 'folder', section: '', createdAt: now, has_children: false, children: [] },
|
||||
],
|
||||
notes: {
|
||||
|
|
@ -685,9 +688,9 @@ function wailsMockSource() {
|
|||
{ id: 'activity', label: 'Активность' },
|
||||
{ id: 'journal', label: 'Журнал' },
|
||||
],
|
||||
ListWorkspaceTree: async () => clone(state.nodes),
|
||||
ListWorkspaceTree: async () => clone(state.nodes.filter((node) => node.captureInbox !== true)),
|
||||
ListWorkspaceChildren: async (id) => clone(childrenOf(id)),
|
||||
ListInboxNodes: async () => clone(state.nodes.filter((node) => !node.parent_id && node.captureInbox === true)),
|
||||
ListInboxNodes: async () => clone(state.nodes.filter((node) => !node.parent_id && node.captureInbox === true).map((node) => ({ ...node, captureKind: node.captureKind || '', captureSource: node.captureSource || '' }))),
|
||||
ListTrash: async () => clone({
|
||||
trashPath: '/tmp/verstak-smoke-vault/.verstak/trash',
|
||||
nodes: [{ id: 'node-trash', title: 'Trash Smoke Folder', type: 'folder', fsPath: 'Trash Smoke Folder', deletedAt: now }],
|
||||
|
|
|
|||
Loading…
Reference in New Issue