feat: add local inbox and links tabs
This commit is contained in:
parent
c1dfc456ec
commit
f112e9a2d0
|
|
@ -92,8 +92,15 @@
|
||||||
let suggestions = []
|
let suggestions = []
|
||||||
let suggestionCount = 0
|
let suggestionCount = 0
|
||||||
let inboxNodes = []
|
let inboxNodes = []
|
||||||
|
let localInboxNodes = []
|
||||||
let inboxCaptureBusy = false
|
let inboxCaptureBusy = false
|
||||||
let inboxCaptureStatus = ''
|
let inboxCaptureStatus = ''
|
||||||
|
let links = []
|
||||||
|
let editingLink = null
|
||||||
|
let linkTitle = ''
|
||||||
|
let linkURL = ''
|
||||||
|
let linkNote = ''
|
||||||
|
let linkStatus = ''
|
||||||
let trashInfo = null
|
let trashInfo = null
|
||||||
let showCreateNode = false
|
let showCreateNode = false
|
||||||
let newNodeTitle = ''
|
let newNodeTitle = ''
|
||||||
|
|
@ -170,6 +177,8 @@
|
||||||
{ id: 'overview', label: t('tab.overview') },
|
{ id: 'overview', label: t('tab.overview') },
|
||||||
{ id: 'notes', label: t('tab.notes') },
|
{ id: 'notes', label: t('tab.notes') },
|
||||||
{ id: 'files', label: t('tab.files') },
|
{ id: 'files', label: t('tab.files') },
|
||||||
|
{ id: 'inbox', label: t('tab.inbox') },
|
||||||
|
{ id: 'links', label: t('tab.links') },
|
||||||
{ id: 'actions', label: t('tab.actions') },
|
{ id: 'actions', label: t('tab.actions') },
|
||||||
{ id: 'worklog', label: t('tab.worklog') },
|
{ id: 'worklog', label: t('tab.worklog') },
|
||||||
{ id: 'activity', label: t('tab.activity') },
|
{ id: 'activity', label: t('tab.activity') },
|
||||||
|
|
@ -248,6 +257,8 @@
|
||||||
files = []
|
files = []
|
||||||
actions = []
|
actions = []
|
||||||
worklog = []
|
worklog = []
|
||||||
|
localInboxNodes = []
|
||||||
|
links = []
|
||||||
suggestions = []
|
suggestions = []
|
||||||
inboxNodes = []
|
inboxNodes = []
|
||||||
trashInfo = null
|
trashInfo = null
|
||||||
|
|
@ -312,6 +323,8 @@
|
||||||
try { files = await wailsCall('ListFiles', nodeID) || [] } catch(e) {}
|
try { files = await wailsCall('ListFiles', nodeID) || [] } catch(e) {}
|
||||||
try { actions = await wailsCall('ListActions', nodeID) || [] } catch(e) {}
|
try { actions = await wailsCall('ListActions', nodeID) || [] } catch(e) {}
|
||||||
try { worklog = initWorklogEntries(await wailsCall('ListWorklog', nodeID)) } catch(e) {}
|
try { worklog = initWorklogEntries(await wailsCall('ListWorklog', nodeID)) } catch(e) {}
|
||||||
|
try { localInboxNodes = await wailsCall('ListInboxNodesForTarget', nodeID) || [] } catch(e) { localInboxNodes = [] }
|
||||||
|
try { links = await wailsCall('ListLinks', nodeID) || [] } catch(e) { links = [] }
|
||||||
try {
|
try {
|
||||||
suggestions = await wailsCall('GetSuggestions') || []
|
suggestions = await wailsCall('GetSuggestions') || []
|
||||||
suggestionCount = suggestions.length
|
suggestionCount = suggestions.length
|
||||||
|
|
@ -1395,7 +1408,7 @@
|
||||||
try { return new Date(str).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) } catch (e) { return str }
|
try { return new Date(str).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) } catch (e) { return str }
|
||||||
}
|
}
|
||||||
function nodeKindLabel(kind) {
|
function nodeKindLabel(kind) {
|
||||||
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') }
|
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'), 'link': t('kind.link') }
|
||||||
return labels[kind] || kind || t('kind.case')
|
return labels[kind] || kind || t('kind.case')
|
||||||
}
|
}
|
||||||
function captureKindLabel(kind) {
|
function captureKindLabel(kind) {
|
||||||
|
|
@ -1409,6 +1422,9 @@
|
||||||
function addInboxCapture(item) {
|
function addInboxCapture(item) {
|
||||||
if (!item || !item.id) return
|
if (!item || !item.id) return
|
||||||
inboxNodes = [item, ...inboxNodes.filter(existing => existing.id !== item.id)]
|
inboxNodes = [item, ...inboxNodes.filter(existing => existing.id !== item.id)]
|
||||||
|
if (selectedNode && (item.captureContextNodeId === selectedNode.id || item.suggestedTargetNodeId === selectedNode.id)) {
|
||||||
|
localInboxNodes = [item, ...localInboxNodes.filter(existing => existing.id !== item.id)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function looksLikeURL(value) {
|
function looksLikeURL(value) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -1689,14 +1705,31 @@
|
||||||
async function submitAssignInbox() {
|
async function submitAssignInbox() {
|
||||||
if (!assignInboxItem || !inboxAssignTarget) return
|
if (!assignInboxItem || !inboxAssignTarget) return
|
||||||
try {
|
try {
|
||||||
await wailsCall('AssignInboxNode', assignInboxItem.id, inboxAssignTarget.id)
|
await resolveInboxToTarget(assignInboxItem, inboxAssignTarget.id)
|
||||||
inboxNodes = inboxNodes.filter(item => item.id !== assignInboxItem.id)
|
|
||||||
await reloadTreePreservingExpanded()
|
|
||||||
closeAssignInbox()
|
closeAssignInbox()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = String(e)
|
error = String(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function resolveInboxToTarget(item, targetNodeId) {
|
||||||
|
if (!item || !targetNodeId) return
|
||||||
|
await wailsCall('ResolveInboxNode', item.id, targetNodeId)
|
||||||
|
inboxNodes = inboxNodes.filter(existing => existing.id !== item.id)
|
||||||
|
localInboxNodes = localInboxNodes.filter(existing => existing.id !== item.id)
|
||||||
|
await reloadTreePreservingExpanded()
|
||||||
|
if (selectedNode) {
|
||||||
|
await loadTabData(selectedNode.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function resolveInboxHere(item) {
|
||||||
|
const targetId = item?.suggestedTargetNodeId || selectedNode?.id || ''
|
||||||
|
if (!targetId) return
|
||||||
|
try {
|
||||||
|
await resolveInboxToTarget(item, targetId)
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
function confirmDeleteInbox(item) {
|
function confirmDeleteInbox(item) {
|
||||||
openConfirm({
|
openConfirm({
|
||||||
title: t('inbox.deleteTitle'),
|
title: t('inbox.deleteTitle'),
|
||||||
|
|
@ -1707,12 +1740,68 @@
|
||||||
try {
|
try {
|
||||||
await wailsCall('DeleteInboxNode', item.id)
|
await wailsCall('DeleteInboxNode', item.id)
|
||||||
inboxNodes = inboxNodes.filter(existing => existing.id !== item.id)
|
inboxNodes = inboxNodes.filter(existing => existing.id !== item.id)
|
||||||
|
localInboxNodes = localInboxNodes.filter(existing => existing.id !== item.id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = String(e)
|
error = String(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function openEditLink(link) {
|
||||||
|
editingLink = link
|
||||||
|
linkTitle = link.title || ''
|
||||||
|
linkURL = link.url || ''
|
||||||
|
linkNote = link.note || ''
|
||||||
|
linkStatus = ''
|
||||||
|
}
|
||||||
|
function closeEditLink() {
|
||||||
|
editingLink = null
|
||||||
|
linkTitle = ''
|
||||||
|
linkURL = ''
|
||||||
|
linkNote = ''
|
||||||
|
linkStatus = ''
|
||||||
|
}
|
||||||
|
async function submitEditLink() {
|
||||||
|
if (!editingLink || !linkURL.trim()) return
|
||||||
|
try {
|
||||||
|
const updated = await wailsCall('UpdateLink', editingLink.id, linkTitle.trim(), linkURL.trim(), linkNote)
|
||||||
|
links = links.map(link => link.id === updated.id ? updated : link)
|
||||||
|
closeEditLink()
|
||||||
|
} catch (e) {
|
||||||
|
linkStatus = String(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function deleteLink(link) {
|
||||||
|
openConfirm({
|
||||||
|
title: t('links.deleteTitle'),
|
||||||
|
message: t('links.deleteConfirm', { title: link.title }),
|
||||||
|
confirmText: t('common.delete'),
|
||||||
|
danger: true,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await wailsCall('DeleteLink', link.id)
|
||||||
|
links = links.filter(item => item.id !== link.id)
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function openLink(link) {
|
||||||
|
try {
|
||||||
|
await wailsCall('OpenLink', link.id)
|
||||||
|
} catch (e) {
|
||||||
|
error = String(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function copyLinkURL(link) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard?.writeText?.(link.url)
|
||||||
|
linkStatus = t('links.copied')
|
||||||
|
} catch (e) {
|
||||||
|
linkStatus = t('links.copyUnavailable')
|
||||||
|
}
|
||||||
|
}
|
||||||
function pluralize(n, one, few, many) {
|
function pluralize(n, one, few, many) {
|
||||||
n = Math.abs(n) % 100
|
n = Math.abs(n) % 100
|
||||||
if (n >= 5 && n <= 20) return many
|
if (n >= 5 && n <= 20) return many
|
||||||
|
|
@ -2188,6 +2277,63 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{:else if activeTab === 'inbox'}
|
||||||
|
<div class="inbox-tab">
|
||||||
|
{#if localInboxNodes.length === 0}
|
||||||
|
<div class="empty-state"><p>{t('inbox.localEmpty')}</p></div>
|
||||||
|
{:else}
|
||||||
|
<div class="inbox-list">
|
||||||
|
{#each localInboxNodes as item}
|
||||||
|
<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">
|
||||||
|
{#if item.captureKind}{captureKindLabel(item.captureKind)} · {/if}
|
||||||
|
{#if item.captureSource}{captureSourceLabel(item.captureSource)} · {/if}
|
||||||
|
{#if item.captureContextLabel}{t('inbox.capturedIn')}: {item.captureContextLabel} · {/if}
|
||||||
|
{formatDate(item.capturedAt || item.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="inbox-item-actions">
|
||||||
|
<button class="btn btn-sm btn-primary" on:click|stopPropagation={() => resolveInboxHere(item)}>{t('inbox.keepHere')}</button>
|
||||||
|
<button class="btn btn-sm" on:click|stopPropagation={() => openAssignInbox(item)}>{t('inbox.assign')}</button>
|
||||||
|
<button class="btn btn-sm btn-danger" on:click|stopPropagation={() => confirmDeleteInbox(item)}>{t('common.delete')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{:else if activeTab === 'links'}
|
||||||
|
<div class="links-tab">
|
||||||
|
{#if links.length === 0}
|
||||||
|
<div class="empty-state"><p>{t('links.empty')}</p></div>
|
||||||
|
{:else}
|
||||||
|
<div class="links-list">
|
||||||
|
{#each links as link}
|
||||||
|
<div class="link-card">
|
||||||
|
<div class="link-main">
|
||||||
|
<span class="link-title">{link.title}</span>
|
||||||
|
<span class="link-url" title={link.url}>{link.hostname || link.url}</span>
|
||||||
|
<span class="link-date">{formatDate(link.createdAt)}</span>
|
||||||
|
{#if link.note}<span class="link-note">{link.note}</span>{/if}
|
||||||
|
</div>
|
||||||
|
<div class="link-actions">
|
||||||
|
<button class="btn btn-sm btn-primary" on:click={() => openLink(link)}>{t('common.open')}</button>
|
||||||
|
<button class="btn btn-sm" on:click={() => copyLinkURL(link)}>{t('links.copyUrl')}</button>
|
||||||
|
<button class="btn btn-sm" on:click={() => openEditLink(link)}>{t('common.rename')}</button>
|
||||||
|
<button class="btn btn-sm btn-danger" on:click={() => deleteLink(link)}>{t('common.delete')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if linkStatus}
|
||||||
|
<div class="link-status">{linkStatus}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{:else if activeTab === 'actions'}
|
{:else if activeTab === 'actions'}
|
||||||
<div class="actions-tab">
|
<div class="actions-tab">
|
||||||
<div class="tab-toolbar">
|
<div class="tab-toolbar">
|
||||||
|
|
@ -2380,6 +2526,9 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="inbox-item-actions">
|
<div class="inbox-item-actions">
|
||||||
|
{#if item.suggestedTargetNodeId}
|
||||||
|
<button class="btn btn-sm btn-primary" on:click|stopPropagation={() => resolveInboxHere(item)}>{t('inbox.keepHere')}</button>
|
||||||
|
{/if}
|
||||||
<button class="btn btn-sm btn-primary" on:click|stopPropagation={() => openAssignInbox(item)}>{t('inbox.assign')}</button>
|
<button class="btn btn-sm btn-primary" on:click|stopPropagation={() => openAssignInbox(item)}>{t('inbox.assign')}</button>
|
||||||
<button class="btn btn-sm" on:click|stopPropagation={() => openNodeById(item.id)}>{t('common.open')}</button>
|
<button class="btn btn-sm" on:click|stopPropagation={() => openNodeById(item.id)}>{t('common.open')}</button>
|
||||||
<button class="btn btn-sm" on:click|stopPropagation={() => openNodeFolder(item)}>{t('file.showInExplorer')}</button>
|
<button class="btn btn-sm" on:click|stopPropagation={() => openNodeFolder(item)}>{t('file.showInExplorer')}</button>
|
||||||
|
|
@ -3032,6 +3181,36 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if editingLink}
|
||||||
|
<div class="modal-overlay" role="button" tabindex="0" on:click|self={closeEditLink} on:keydown={onKeyActivate(closeEditLink)}>
|
||||||
|
<div class="modal">
|
||||||
|
<h3>{t('links.editTitle')}</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label><span class="label-text">{t('common.name')}</span>
|
||||||
|
<input type="text" bind:value={linkTitle} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label><span class="label-text">URL</span>
|
||||||
|
<input type="url" bind:value={linkURL} />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label><span class="label-text">{t('links.note')}</span>
|
||||||
|
<textarea bind:value={linkNote}></textarea>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{#if linkStatus}
|
||||||
|
<div class="rename-error">{linkStatus}</div>
|
||||||
|
{/if}
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn btn-primary" on:click={submitEditLink} disabled={!linkURL.trim()}>{t('common.save')}</button>
|
||||||
|
<button class="btn" on:click={closeEditLink}>{t('common.cancel')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if showConfirm}
|
{#if showConfirm}
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
title={confirmTitle}
|
title={confirmTitle}
|
||||||
|
|
@ -3198,6 +3377,20 @@
|
||||||
.inbox-item-title { color: #e4e4ef; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.inbox-item-title { color: #e4e4ef; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.inbox-item-meta { color: #8888a0; font-size: 12px; }
|
.inbox-item-meta { color: #8888a0; font-size: 12px; }
|
||||||
.inbox-item-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
.inbox-item-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
||||||
|
.inbox-tab { padding: 24px; }
|
||||||
|
|
||||||
|
/* Links tab */
|
||||||
|
.links-tab { padding: 24px; }
|
||||||
|
.links-list { display: flex; flex-direction: column; gap: 8px; }
|
||||||
|
.link-card { display: flex; align-items: center; gap: 12px; padding: 12px; border: 1px solid #2a2a3c; border-radius: 8px; background: #1a1a28; }
|
||||||
|
.link-card:hover { border-color: #3a3a5c; background: #1e1e32; }
|
||||||
|
.link-main { flex: 1; min-width: 0; display: grid; grid-template-columns: minmax(160px, 1fr) minmax(120px, 220px) auto; gap: 8px 12px; align-items: baseline; }
|
||||||
|
.link-title { color: #e4e4ef; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.link-url { color: #a5b4fc; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.link-date { color: #8888a0; font-size: 12px; white-space: nowrap; }
|
||||||
|
.link-note { grid-column: 1 / -1; color: #8888a0; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.link-actions { display: flex; gap: 8px; flex-shrink: 0; }
|
||||||
|
.link-status { margin-top: 12px; color: #8ee6b1; font-size: 12px; }
|
||||||
|
|
||||||
/* Trash screen */
|
/* Trash screen */
|
||||||
.trash-screen { padding: 24px; overflow-y: auto; flex: 1; }
|
.trash-screen { padding: 24px; overflow-y: auto; flex: 1; }
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ export default {
|
||||||
'inbox.clipboardEmpty': 'Clipboard is empty',
|
'inbox.clipboardEmpty': 'Clipboard is empty',
|
||||||
'inbox.clipboardUnavailable': 'Clipboard is unavailable',
|
'inbox.clipboardUnavailable': 'Clipboard is unavailable',
|
||||||
'inbox.assign': 'Assign',
|
'inbox.assign': 'Assign',
|
||||||
|
'inbox.keepHere': 'Keep here',
|
||||||
|
'inbox.localEmpty': 'No unprocessed materials for this case',
|
||||||
|
'inbox.capturedIn': 'Captured in',
|
||||||
'inbox.assignTitle': 'Assign material',
|
'inbox.assignTitle': 'Assign material',
|
||||||
'inbox.assignTarget': 'Case',
|
'inbox.assignTarget': 'Case',
|
||||||
'inbox.assignSearchPlaceholder': 'Find case',
|
'inbox.assignSearchPlaceholder': 'Find case',
|
||||||
|
|
@ -56,6 +59,8 @@ export default {
|
||||||
'tab.overview': 'Overview',
|
'tab.overview': 'Overview',
|
||||||
'tab.notes': 'Notes',
|
'tab.notes': 'Notes',
|
||||||
'tab.files': 'Files',
|
'tab.files': 'Files',
|
||||||
|
'tab.inbox': 'Inbox',
|
||||||
|
'tab.links': 'Links',
|
||||||
'tab.actions': 'Actions',
|
'tab.actions': 'Actions',
|
||||||
'tab.worklog': 'Work Log',
|
'tab.worklog': 'Work Log',
|
||||||
'tab.activity': 'Activity',
|
'tab.activity': 'Activity',
|
||||||
|
|
@ -118,6 +123,15 @@ export default {
|
||||||
'kind.file': 'File',
|
'kind.file': 'File',
|
||||||
'kind.archive': 'Archive',
|
'kind.archive': 'Archive',
|
||||||
'kind.case': 'Case',
|
'kind.case': 'Case',
|
||||||
|
'kind.link': 'Link',
|
||||||
|
'links.empty': 'No links yet',
|
||||||
|
'links.editTitle': 'Edit link',
|
||||||
|
'links.note': 'Note',
|
||||||
|
'links.copyUrl': 'Copy URL',
|
||||||
|
'links.copied': 'URL copied',
|
||||||
|
'links.copyUnavailable': 'Could not copy URL',
|
||||||
|
'links.deleteTitle': 'Delete link',
|
||||||
|
'links.deleteConfirm': 'Delete link "{title}"?',
|
||||||
'action.openUrl': 'Open URL',
|
'action.openUrl': 'Open URL',
|
||||||
'action.openFile': 'Open file',
|
'action.openFile': 'Open file',
|
||||||
'action.openFolder': 'Open folder',
|
'action.openFolder': 'Open folder',
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ export default {
|
||||||
'inbox.clipboardEmpty': 'Буфер обмена пуст',
|
'inbox.clipboardEmpty': 'Буфер обмена пуст',
|
||||||
'inbox.clipboardUnavailable': 'Буфер обмена недоступен',
|
'inbox.clipboardUnavailable': 'Буфер обмена недоступен',
|
||||||
'inbox.assign': 'Разложить',
|
'inbox.assign': 'Разложить',
|
||||||
|
'inbox.keepHere': 'Оставить здесь',
|
||||||
|
'inbox.localEmpty': 'Для этого дела неразобранных материалов нет',
|
||||||
|
'inbox.capturedIn': 'Захвачено в',
|
||||||
'inbox.assignTitle': 'Разложить материал',
|
'inbox.assignTitle': 'Разложить материал',
|
||||||
'inbox.assignTarget': 'Дело',
|
'inbox.assignTarget': 'Дело',
|
||||||
'inbox.assignSearchPlaceholder': 'Найти дело',
|
'inbox.assignSearchPlaceholder': 'Найти дело',
|
||||||
|
|
@ -60,6 +63,8 @@ export default {
|
||||||
'tab.overview': 'Обзор',
|
'tab.overview': 'Обзор',
|
||||||
'tab.notes': 'Заметки',
|
'tab.notes': 'Заметки',
|
||||||
'tab.files': 'Файлы',
|
'tab.files': 'Файлы',
|
||||||
|
'tab.inbox': 'Неразобранное',
|
||||||
|
'tab.links': 'Ссылки',
|
||||||
'tab.actions': 'Действия',
|
'tab.actions': 'Действия',
|
||||||
'tab.worklog': 'Журнал',
|
'tab.worklog': 'Журнал',
|
||||||
'tab.activity': 'Активность',
|
'tab.activity': 'Активность',
|
||||||
|
|
@ -126,6 +131,16 @@ export default {
|
||||||
'kind.file': 'Файл',
|
'kind.file': 'Файл',
|
||||||
'kind.archive': 'Архив',
|
'kind.archive': 'Архив',
|
||||||
'kind.case': 'Дело',
|
'kind.case': 'Дело',
|
||||||
|
'kind.link': 'Ссылка',
|
||||||
|
|
||||||
|
'links.empty': 'Ссылок пока нет',
|
||||||
|
'links.editTitle': 'Редактировать ссылку',
|
||||||
|
'links.note': 'Заметка',
|
||||||
|
'links.copyUrl': 'Копировать URL',
|
||||||
|
'links.copied': 'URL скопирован',
|
||||||
|
'links.copyUnavailable': 'Не удалось скопировать URL',
|
||||||
|
'links.deleteTitle': 'Удалить ссылку',
|
||||||
|
'links.deleteConfirm': 'Удалить ссылку «{title}»?',
|
||||||
|
|
||||||
'action.openUrl': 'Открыть URL',
|
'action.openUrl': 'Открыть URL',
|
||||||
'action.openFile': 'Открыть файл',
|
'action.openFile': 'Открыть файл',
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,14 @@ async function runReadyScenario(cdp, url) {
|
||||||
await clickText(cdp, '.inbox-header .btn', 'Вставить из буфера')
|
await clickText(cdp, '.inbox-header .btn', 'Вставить из буфера')
|
||||||
await assertText(cdp, 'example.test', 'inbox: clipboard URL captured')
|
await assertText(cdp, 'example.test', 'inbox: clipboard URL captured')
|
||||||
await assertText(cdp, 'Ссылка', 'inbox: clipboard URL kind visible')
|
await assertText(cdp, 'Ссылка', 'inbox: clipboard URL kind visible')
|
||||||
|
await clickInboxItemButton(cdp, 'example.test', 'Разложить')
|
||||||
|
await waitForSelector(cdp, '.modal input[type="text"]')
|
||||||
|
await setInputValue(cdp, '.modal input[type="text"]', 'Smoke')
|
||||||
|
await waitForSelector(cdp, '.assign-search-result')
|
||||||
|
await clickText(cdp, '.assign-search-result', 'Smoke Project')
|
||||||
|
await clickText(cdp, '.modal-actions .btn', 'Разложить')
|
||||||
|
await waitForGone(cdp, '.modal-overlay')
|
||||||
|
await assertEval(cdp, `!document.querySelector('.inbox-screen')?.innerText.includes('example.test')`, 'inbox: assigned link leaves inbox')
|
||||||
await emitDroppedFiles(cdp, ['/tmp/smoke-drop-folder'])
|
await emitDroppedFiles(cdp, ['/tmp/smoke-drop-folder'])
|
||||||
await assertText(cdp, 'smoke-drop-folder', 'inbox: dropped folder captured')
|
await assertText(cdp, 'smoke-drop-folder', 'inbox: dropped folder captured')
|
||||||
await assertText(cdp, 'Перетаскивание', 'inbox: dropped source visible')
|
await assertText(cdp, 'Перетаскивание', 'inbox: dropped source visible')
|
||||||
|
|
@ -174,7 +182,7 @@ async function runReadyScenario(cdp, url) {
|
||||||
await clickText(cdp, '.tree-label', 'Smoke Project')
|
await clickText(cdp, '.tree-label', 'Smoke Project')
|
||||||
await waitForSelector(cdp, '.tabs')
|
await waitForSelector(cdp, '.tabs')
|
||||||
await assertText(cdp, 'Smoke Project', 'node: selected project visible')
|
await assertText(cdp, 'Smoke Project', 'node: selected project visible')
|
||||||
await assertEval(cdp, `document.querySelectorAll('.tab').length === 6`, 'node: all primary tabs rendered')
|
await assertEval(cdp, `document.querySelectorAll('.tab').length === 8`, 'node: all primary tabs rendered')
|
||||||
await screenshot(cdp, 'node-overview.png')
|
await screenshot(cdp, 'node-overview.png')
|
||||||
|
|
||||||
await clickText(cdp, '.tab', 'Заметки')
|
await clickText(cdp, '.tab', 'Заметки')
|
||||||
|
|
@ -195,6 +203,9 @@ async function runReadyScenario(cdp, url) {
|
||||||
await click(cdp, '.back-btn')
|
await click(cdp, '.back-btn')
|
||||||
await assertText(cdp, 'brief.md', 'files: back button returns to parent folder')
|
await assertText(cdp, 'brief.md', 'files: back button returns to parent folder')
|
||||||
|
|
||||||
|
await clickText(cdp, '.tab', 'Ссылки')
|
||||||
|
await assertText(cdp, 'example.test', 'links: resolved link visible')
|
||||||
|
|
||||||
await clickText(cdp, '.tab', 'Действия')
|
await clickText(cdp, '.tab', 'Действия')
|
||||||
await assertText(cdp, 'Deploy smoke', 'actions: action card visible')
|
await assertText(cdp, 'Deploy smoke', 'actions: action card visible')
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue