fix: localize Browser materials workflow

This commit is contained in:
mirivlad 2026-07-14 22:28:53 +08:00
parent a1b3c31d0d
commit 99172944a1
5 changed files with 235 additions and 95 deletions

View File

@ -1,5 +1,5 @@
/* =========================================================== /* ===========================================================
Browser Inbox Plugin Verstak v2 Frontend Bundle Browser Plugin Verstak v2 Frontend Bundle
Contract: window.VerstakPluginRegister(id, { components }) Contract: window.VerstakPluginRegister(id, { components })
=========================================================== */ =========================================================== */
@ -146,7 +146,7 @@
function scopeFromProps(props) { function scopeFromProps(props) {
var workspaceRoot = workspaceFromProps(props); var workspaceRoot = workspaceFromProps(props);
if (!workspaceRoot) { if (!workspaceRoot) {
return { mode: 'global', key: GLOBAL_KEY, label: 'All workspaces', workspaceRoot: '' }; return { mode: 'global', key: GLOBAL_KEY, label: '', workspaceRoot: '' };
} }
return { return {
mode: 'workspace', mode: 'workspace',
@ -161,13 +161,13 @@
return value === 'selection' || value === 'link' || value === 'file' || value === 'page' ? value : 'page'; return value === 'selection' || value === 'link' || value === 'file' || value === 'page' ? value : 'page';
} }
function displayTitle(capture) { function displayTitle(capture, fallbackTitle) {
if (capture && capture.kind === 'file' && capture.fileName) return capture.fileName; if (capture && capture.kind === 'file' && capture.fileName) return capture.fileName;
return capture.title || capture.url || capture.captureId || 'Untitled capture'; return capture.title || capture.url || capture.captureId || fallbackTitle || '';
} }
function noteTitle(capture) { function noteTitle(capture, fallbackTitle) {
return text((capture && (capture.title || capture.domain || capture.captureId)) || 'Browser Capture').trim() || 'Browser Capture'; return text((capture && (capture.title || capture.domain || capture.captureId)) || fallbackTitle).trim() || text(fallbackTitle).trim();
} }
function safeNoteFilename(title) { function safeNoteFilename(title) {
@ -199,8 +199,8 @@
return base; return base;
} }
function captureToMarkdown(capture) { function captureToMarkdown(capture, fallbackTitle) {
var title = noteTitle(capture); var title = noteTitle(capture, fallbackTitle);
var lines = ['# ' + title, '']; var lines = ['# ' + title, ''];
if (capture && capture.url) lines.push('Source: ' + capture.url); if (capture && capture.url) lines.push('Source: ' + capture.url);
if (capture && capture.capturedAt) lines.push('Captured: ' + capture.capturedAt); if (capture && capture.capturedAt) lines.push('Captured: ' + capture.capturedAt);
@ -363,7 +363,7 @@
var scope = scopeFromProps(props || {}); var scope = scopeFromProps(props || {});
var captures = []; var captures = [];
var selectedId = ''; var selectedId = '';
var statusText = 'Connecting to receiver events...'; var statusText = '';
var statusClass = ''; var statusClass = '';
var disposed = false; var disposed = false;
var unsubscribers = []; var unsubscribers = [];
@ -377,6 +377,18 @@
return fallback || key; return fallback || key;
} }
function localizedItemCount(count) {
var locale = api && api.i18n && typeof api.i18n.getLocale === 'function' ? api.i18n.getLocale() : 'en';
if (locale === 'ru') {
var mod10 = count % 10;
var mod100 = count % 100;
if (mod10 === 1 && mod100 !== 11) return tr('ui.items.one', { count: count }, count + ' материал');
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return tr('ui.items.few', { count: count }, count + ' материала');
return tr('ui.items.many', { count: count }, count + ' материалов');
}
return tr(count === 1 ? 'ui.items.one' : 'ui.items.other', { count: count }, count + (count === 1 ? ' item' : ' items'));
}
function reportError(key, fallback, err) { function reportError(key, fallback, err) {
if (typeof console !== 'undefined' && typeof console.warn === 'function') { if (typeof console !== 'undefined' && typeof console.warn === 'function') {
console.warn('[verstak.browser-inbox] ' + key, err); console.warn('[verstak.browser-inbox] ' + key, err);
@ -389,14 +401,14 @@
statusText = tr('ui.connecting', null, 'Connecting to receiver events...'); statusText = tr('ui.connecting', null, 'Connecting to receiver events...');
var toolbar = el('div', { className: 'browser-inbox-toolbar' }); var toolbar = el('div', { className: 'browser-inbox-toolbar' });
var titleEl = el('span', { className: 'browser-inbox-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Browser Inbox') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Browser Inbox · ' + scope.label) }); var titleEl = el('span', { className: 'browser-inbox-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Browser') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Browser · ' + scope.label) });
var countEl = el('span', { className: 'browser-inbox-count' }); var countEl = el('span', { className: 'browser-inbox-count' });
var statusEl = el('span', { className: 'browser-inbox-status' }); var statusEl = el('span', { className: 'browser-inbox-status' });
var filtersEl = el('div', { className: 'browser-inbox-filters' }); var filtersEl = el('div', { className: 'browser-inbox-filters' });
var statusFilterEl = el('select', { var statusFilterEl = el('select', {
className: 'browser-inbox-select', className: 'browser-inbox-select',
'data-browser-inbox-filter': 'status', 'data-browser-inbox-filter': 'status',
'aria-label': 'Capture status filter', 'aria-label': tr('ui.statusFilter', null, 'Material status filter'),
onChange: function (event) { onChange: function (event) {
statusFilter = text(event && event.target && event.target.value) || 'all'; statusFilter = text(event && event.target && event.target.value) || 'all';
selectedId = ''; selectedId = '';
@ -412,7 +424,7 @@
var workspaceFilterEl = el('select', { var workspaceFilterEl = el('select', {
className: 'browser-inbox-select', className: 'browser-inbox-select',
'data-browser-inbox-filter': 'workspace', 'data-browser-inbox-filter': 'workspace',
'aria-label': 'Workspace filter', 'aria-label': tr('ui.workspaceFilter', null, 'Deal filter'),
onChange: function (event) { onChange: function (event) {
workspaceFilter = cleanWorkspace(event && event.target && event.target.value); workspaceFilter = cleanWorkspace(event && event.target && event.target.value);
selectedId = ''; selectedId = '';
@ -424,7 +436,7 @@
type: 'search', type: 'search',
placeholder: tr('ui.search', null, 'Search captures'), placeholder: tr('ui.search', null, 'Search captures'),
'data-browser-inbox-filter': 'search', 'data-browser-inbox-filter': 'search',
'aria-label': 'Search captures', 'aria-label': tr('ui.search', null, 'Search captures'),
onInput: function (event) { onInput: function (event) {
searchQuery = text(event && event.target && event.target.value).trim().toLowerCase(); searchQuery = text(event && event.target && event.target.value).trim().toLowerCase();
selectedId = ''; selectedId = '';
@ -482,7 +494,7 @@
function renderWorkspaceFilterOptions() { function renderWorkspaceFilterOptions() {
if (scope.mode !== 'global') return; if (scope.mode !== 'global') return;
workspaceFilterEl.innerHTML = ''; workspaceFilterEl.innerHTML = '';
workspaceFilterEl.appendChild(option('', 'All workspaces')); workspaceFilterEl.appendChild(option('', tr('ui.allDeals', null, 'All Deals')));
workspaceRoots().forEach(function (root) { workspaceRoots().forEach(function (root) {
workspaceFilterEl.appendChild(option(root, root)); workspaceFilterEl.appendChild(option(root, root));
}); });
@ -506,7 +518,7 @@
function publishMutation(action, payload, verify, verifySettings) { function publishMutation(action, payload, verify, verifySettings) {
if (!api || !api.events || typeof api.events.publish !== 'function') { if (!api || !api.events || typeof api.events.publish !== 'function') {
reportError('ui.saveError', 'Could not update the browser inbox. Please try again.'); reportError('ui.saveError', 'Could not update browser materials. Please try again.');
return Promise.resolve(false); return Promise.resolve(false);
} }
return api.events.publish(MUTATION_EVENT, Object.assign({ action: action }, payload || {})).then(function () { return api.events.publish(MUTATION_EVENT, Object.assign({ action: action }, payload || {})).then(function () {
@ -524,7 +536,7 @@
} }
return true; return true;
}).catch(function (err) { }).catch(function (err) {
reportError('ui.saveError', 'Could not update the browser inbox. Please try again.', err); reportError('ui.saveError', 'Could not update browser materials. Please try again.', err);
return false; return false;
}); });
} }
@ -551,7 +563,9 @@
var ids = scope.mode === 'global' var ids = scope.mode === 'global'
? captures.map(function (capture) { return capture.captureId; }) ? captures.map(function (capture) { return capture.captureId; })
: captures.filter(function (capture) { return capture.workspaceRootPath === scope.workspaceRoot; }).map(function (capture) { return capture.captureId; }); : captures.filter(function (capture) { return capture.workspaceRootPath === scope.workspaceRoot; }).map(function (capture) { return capture.captureId; });
return archiveCaptures(ids, scope.mode === 'global' ? 'Inbox archived' : 'Workspace captures archived'); return archiveCaptures(ids, scope.mode === 'global'
? tr('ui.inboxArchived', null, 'Inbox archived')
: tr('ui.workspaceCapturesArchived', null, 'Deal materials archived'));
} }
function selectedCapture() { function selectedCapture() {
@ -569,10 +583,8 @@
}); });
if (existing) return Promise.resolve(); if (existing) return Promise.resolve();
selectedId = capture.captureId; selectedId = capture.captureId;
statusText = 'Capture received'; statusText = tr('ui.captureReceived', null, 'Material received');
statusClass = ''; statusClass = '';
statusText = 'Could not load received capture from storage';
statusClass = 'error';
render(); render();
return Promise.resolve(); return Promise.resolve();
} }
@ -598,14 +610,16 @@
}).then(function (saved) { }).then(function (saved) {
if (!saved) return; if (!saved) return;
if (workspaceRoot && workspaceOptions.indexOf(workspaceRoot) === -1) workspaceOptions.push(workspaceRoot); if (workspaceRoot && workspaceOptions.indexOf(workspaceRoot) === -1) workspaceOptions.push(workspaceRoot);
statusText = workspaceRoot ? 'Capture assigned to ' + workspaceRoot : 'Capture is unassigned'; statusText = workspaceRoot
? tr('ui.assignedToWorkspace', { workspace: workspaceRoot }, 'Material assigned to ' + workspaceRoot)
: tr('ui.captureUnassigned', null, 'Material is unassigned');
statusClass = ''; statusClass = '';
render(); render();
}); });
} }
function archiveCapture(captureId) { function archiveCapture(captureId) {
return archiveCaptures([captureId], 'Capture archived'); return archiveCaptures([captureId], tr('ui.captureArchived', null, 'Material archived'));
} }
function restoreCapture(captureId) { function restoreCapture(captureId) {
@ -615,7 +629,7 @@
}); });
}).then(function (saved) { }).then(function (saved) {
if (!saved) return; if (!saved) return;
statusText = 'Capture restored to Inbox'; statusText = tr('ui.captureRestored', null, 'Material restored to Inbox');
statusClass = ''; statusClass = '';
render(); render();
}); });
@ -627,7 +641,7 @@
}).then(function (saved) { }).then(function (saved) {
if (!saved) return; if (!saved) return;
if (selectedId === captureId) selectedId = ''; if (selectedId === captureId) selectedId = '';
statusText = 'Capture permanently deleted'; statusText = tr('ui.captureDeleted', null, 'Material permanently deleted');
statusClass = ''; statusClass = '';
render(); render();
}); });
@ -643,7 +657,9 @@
}); });
}).then(function (saved) { }).then(function (saved) {
if (!saved) return; if (!saved) return;
statusText = processed ? 'Capture marked processed' : 'Capture marked unprocessed'; statusText = processed
? tr('ui.captureProcessed', null, 'Material marked processed')
: tr('ui.captureUnprocessed', null, 'Material marked unprocessed');
statusClass = ''; statusClass = '';
render(); render();
}); });
@ -655,12 +671,12 @@
reportError('ui.createNoteError', 'Could not create the note. Please try again.'); reportError('ui.createNoteError', 'Could not create the note. Please try again.');
return Promise.resolve(); return Promise.resolve();
} }
var title = noteTitle(capture); var title = noteTitle(capture, tr('ui.untitledCapture', null, 'Untitled material'));
var notePath = capture.workspaceRootPath + '/Notes/' + safeNoteFilename(title); var notePath = capture.workspaceRootPath + '/Notes/' + safeNoteFilename(title);
statusText = 'Creating note...'; statusText = tr('ui.creatingNote', null, 'Creating note...');
statusClass = ''; statusClass = '';
render(); render();
return api.files.writeText(notePath, captureToMarkdown(capture), { return api.files.writeText(notePath, captureToMarkdown(capture, tr('ui.untitledCapture', null, 'Untitled material')), {
createIfMissing: true, createIfMissing: true,
overwrite: false overwrite: false
}).then(function () { }).then(function () {
@ -677,7 +693,7 @@
} }
return undefined; return undefined;
}).then(function () { }).then(function () {
statusText = 'Created note: ' + notePath; statusText = tr('ui.noteCreated', { path: notePath }, 'Note created: ' + notePath);
statusClass = ''; statusClass = '';
return archiveCapture(capture.captureId); return archiveCapture(capture.captureId);
}).catch(function (err) { }).catch(function (err) {
@ -691,8 +707,8 @@
reportError('ui.createLinkError', 'Could not create the link. Please try again.'); reportError('ui.createLinkError', 'Could not create the link. Please try again.');
return Promise.resolve(); return Promise.resolve();
} }
var title = noteTitle(capture); var title = noteTitle(capture, tr('ui.untitledCapture', null, 'Untitled material'));
statusText = 'Creating link...'; statusText = tr('ui.creatingLink', null, 'Creating link...');
statusClass = ''; statusClass = '';
render(); render();
function writeLink(number) { function writeLink(number) {
@ -725,7 +741,7 @@
} }
return linkPath; return linkPath;
}).then(function () { }).then(function () {
statusText = 'Created link'; statusText = tr('ui.linkCreated', null, 'Link created');
statusClass = ''; statusClass = '';
return archiveCapture(capture.captureId); return archiveCapture(capture.captureId);
}).catch(function (err) { }).catch(function (err) {
@ -748,7 +764,7 @@
} }
var fileName = safeFileFilename(capture.fileName); var fileName = safeFileFilename(capture.fileName);
var filePath = capture.workspaceRootPath + '/Files/' + fileName; var filePath = capture.workspaceRootPath + '/Files/' + fileName;
statusText = 'Creating file...'; statusText = tr('ui.creatingFile', null, 'Creating file...');
statusClass = ''; statusClass = '';
render(); render();
var writeOptions = { var writeOptions = {
@ -775,7 +791,7 @@
} }
return undefined; return undefined;
}).then(function () { }).then(function () {
statusText = 'Created file: ' + filePath; statusText = tr('ui.fileCreated', { path: filePath }, 'File created: ' + filePath);
statusClass = ''; statusClass = '';
return archiveCapture(capture.captureId); return archiveCapture(capture.captureId);
}).catch(function (err) { }).catch(function (err) {
@ -788,8 +804,8 @@
var visible = visibleCaptures(); var visible = visibleCaptures();
if (visible.length === 0) { if (visible.length === 0) {
var emptyText = captures.length === 0 var emptyText = captures.length === 0
? 'No browser captures yet. Keep this view open, then send a page, selection, or link from the extension.' ? tr('ui.empty', null, 'No browser materials yet. Send a page, selection, or link from the extension.')
: 'No captures match the current filters.'; : tr('ui.emptyFiltered', null, 'No materials match the current filters.');
listEl.appendChild(el('div', { className: 'browser-inbox-empty', textContent: emptyText })); listEl.appendChild(el('div', { className: 'browser-inbox-empty', textContent: emptyText }));
return; return;
} }
@ -805,20 +821,22 @@
}, [ }, [
el('div', { className: 'browser-inbox-row-head' }, [ el('div', { className: 'browser-inbox-row-head' }, [
el('span', { className: 'browser-inbox-kind', textContent: capture.kind }), el('span', { className: 'browser-inbox-kind', textContent: capture.kind }),
el('span', { className: 'browser-inbox-row-title', textContent: displayTitle(capture) }) el('span', { className: 'browser-inbox-row-title', textContent: displayTitle(capture, tr('ui.untitledCapture', null, 'Untitled material')) })
]), ]),
el('div', { className: 'browser-inbox-row-url', textContent: capture.url || capture.domain || capture.captureId }) el('div', { className: 'browser-inbox-row-url', textContent: capture.url || capture.domain || capture.captureId })
]); ]);
row.appendChild(el('div', { className: 'browser-inbox-row-meta' }, [ row.appendChild(el('div', { className: 'browser-inbox-row-meta' }, [
el('span', { el('span', {
className: 'browser-inbox-badge' + (workspaceRoot ? '' : ' unassigned'), className: 'browser-inbox-badge' + (workspaceRoot ? '' : ' unassigned'),
textContent: workspaceRoot || 'Unassigned' textContent: workspaceRoot || tr('ui.unassigned', null, 'Unassigned')
}), }),
el('span', { el('span', {
className: 'browser-inbox-badge' + (capture.processed ? ' processed' : ''), className: 'browser-inbox-badge' + (capture.processed ? ' processed' : ''),
textContent: capture.processed ? 'Processed' : 'Unprocessed' textContent: capture.processed
? tr('ui.processed', null, 'Processed')
: tr('ui.unprocessed', null, 'Unprocessed')
}), }),
capture.globalState === 'archived' ? el('span', { className: 'browser-inbox-badge', textContent: 'Archived' }) : null capture.globalState === 'archived' ? el('span', { className: 'browser-inbox-badge', textContent: tr('ui.archive', null, 'Archive') }) : null
])); ]));
if (capture.text) { if (capture.text) {
row.appendChild(el('div', { className: 'browser-inbox-row-text', textContent: capture.text })); row.appendChild(el('div', { className: 'browser-inbox-row-text', textContent: capture.text }));
@ -835,45 +853,47 @@
return; return;
} }
selectedId = capture.captureId; selectedId = capture.captureId;
detailEl.appendChild(el('div', { className: 'browser-inbox-detail-title', textContent: displayTitle(capture) })); detailEl.appendChild(el('div', { className: 'browser-inbox-detail-title', textContent: displayTitle(capture, tr('ui.untitledCapture', null, 'Untitled material')) }));
detailEl.appendChild(el('div', { className: 'browser-inbox-meta' }, [ detailEl.appendChild(el('div', { className: 'browser-inbox-meta' }, [
el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.kind', null, 'Kind') }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.kind', null, 'Kind') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.kind }), el('div', { className: 'browser-inbox-meta-value', textContent: capture.kind }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'URL' }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.url', null, 'URL') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.url || '-' }), el('div', { className: 'browser-inbox-meta-value', textContent: capture.url || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.domain', null, 'Domain') }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.domain', null, 'Domain') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.domain || '-' }), el('div', { className: 'browser-inbox-meta-value', textContent: capture.domain || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.captured', null, 'Captured') }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.captured', null, 'Captured') }),
el('div', { className: 'browser-inbox-meta-value', textContent: formatDate(capture.capturedAt) || '-' }), el('div', { className: 'browser-inbox-meta-value', textContent: formatDate(capture.capturedAt) || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: 'Browser' }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.browser', null, 'Browser') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.browserName || capture.source || '-' }), el('div', { className: 'browser-inbox-meta-value', textContent: capture.browserName || capture.source || '-' }),
el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.workspace', null, 'Workspace') }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.workspace', null, 'Workspace') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.workspaceRootPath || 'Unassigned' }), el('div', { className: 'browser-inbox-meta-value', textContent: capture.workspaceRootPath || tr('ui.unassigned', null, 'Unassigned') }),
el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.status', null, 'Status') }), el('div', { className: 'browser-inbox-meta-label', textContent: tr('ui.status', null, 'Status') }),
el('div', { className: 'browser-inbox-meta-value', textContent: capture.processed ? 'Processed' : 'Unprocessed' }) el('div', { className: 'browser-inbox-meta-value', textContent: capture.processed
? tr('ui.processed', null, 'Processed')
: tr('ui.unprocessed', null, 'Unprocessed') })
])); ]));
var assignmentSelect = el('select', { var assignmentSelect = el('select', {
className: 'browser-inbox-select', className: 'browser-inbox-select',
'data-browser-inbox-assignment': capture.captureId, 'data-browser-inbox-assignment': capture.captureId,
'aria-label': 'Assign capture workspace', 'aria-label': tr('ui.assignment', null, 'Assign to Deal'),
onChange: function (event) { onChange: function (event) {
assignWorkspace(capture.captureId, event && event.target && event.target.value); assignWorkspace(capture.captureId, event && event.target && event.target.value);
} }
}); });
assignmentSelect.appendChild(option('', 'Unassigned')); assignmentSelect.appendChild(option('', tr('ui.unassigned', null, 'Unassigned')));
workspaceRoots().forEach(function (workspaceRoot) { workspaceRoots().forEach(function (workspaceRoot) {
assignmentSelect.appendChild(option(workspaceRoot, workspaceRoot)); assignmentSelect.appendChild(option(workspaceRoot, workspaceRoot));
}); });
assignmentSelect.value = capture.workspaceRootPath || ''; assignmentSelect.value = capture.workspaceRootPath || '';
var assignmentControls = [ var assignmentControls = [
el('span', { className: 'browser-inbox-meta-label', textContent: 'Assign workspace' }), el('span', { className: 'browser-inbox-meta-label', textContent: tr('ui.assignment', null, 'Assign to Deal') }),
assignmentSelect assignmentSelect
]; ];
if (capture.workspaceRootPath) { if (capture.workspaceRootPath) {
assignmentControls.push(el('button', { assignmentControls.push(el('button', {
className: 'browser-inbox-btn', className: 'browser-inbox-btn',
'data-browser-inbox-action': 'clear-assignment', 'data-browser-inbox-action': 'clear-assignment',
textContent: 'Clear assignment', textContent: tr('ui.clearAssignment', null, 'Clear assignment'),
onClick: function () { onClick: function () {
assignWorkspace(capture.captureId, ''); assignWorkspace(capture.captureId, '');
} }
@ -881,7 +901,7 @@
} }
detailEl.appendChild(el('div', { className: 'browser-inbox-assignment' }, assignmentControls)); detailEl.appendChild(el('div', { className: 'browser-inbox-assignment' }, assignmentControls));
if (!capture.workspaceRootPath) { if (!capture.workspaceRootPath) {
detailEl.appendChild(el('div', { className: 'browser-inbox-detail-note', textContent: 'Assign a workspace before creating a note, link, or file.' })); detailEl.appendChild(el('div', { className: 'browser-inbox-detail-note', textContent: tr('ui.assignBeforeCreation', null, 'Assign a Deal before creating a note, link, or file.') }));
} }
if (capture.text) { if (capture.text) {
detailEl.appendChild(el('div', { className: 'browser-inbox-text', textContent: capture.text })); detailEl.appendChild(el('div', { className: 'browser-inbox-text', textContent: capture.text }));
@ -893,7 +913,9 @@
actionButtons.push(el('button', { actionButtons.push(el('button', {
className: 'browser-inbox-btn', className: 'browser-inbox-btn',
'data-browser-inbox-action': 'toggle-processed', 'data-browser-inbox-action': 'toggle-processed',
textContent: capture.processed ? 'Mark Unprocessed' : 'Mark Processed', textContent: capture.processed
? tr('ui.markUnprocessed', null, 'Mark unprocessed')
: tr('ui.markProcessed', null, 'Mark processed'),
onClick: function () { onClick: function () {
setProcessed(capture.captureId, !capture.processed); setProcessed(capture.captureId, !capture.processed);
} }
@ -902,7 +924,7 @@
actionButtons.push(el('button', { actionButtons.push(el('button', {
className: 'browser-inbox-btn', className: 'browser-inbox-btn',
'data-browser-inbox-action': 'open-link', 'data-browser-inbox-action': 'open-link',
textContent: 'Open link', textContent: tr('ui.openLink', null, 'Open link'),
onClick: function () { onClick: function () {
openCaptureURL(capture); openCaptureURL(capture);
} }
@ -942,7 +964,7 @@
actionButtons.push(el('button', { actionButtons.push(el('button', {
className: 'browser-inbox-btn', className: 'browser-inbox-btn',
'data-browser-inbox-action': 'restore', 'data-browser-inbox-action': 'restore',
textContent: 'Restore to Inbox', textContent: tr('ui.restore', null, 'Restore to Inbox'),
onClick: function () { onClick: function () {
restoreCapture(capture.captureId); restoreCapture(capture.captureId);
} }
@ -951,7 +973,7 @@
actionButtons.push(el('button', { actionButtons.push(el('button', {
className: 'browser-inbox-btn', className: 'browser-inbox-btn',
'data-browser-inbox-action': 'archive', 'data-browser-inbox-action': 'archive',
textContent: 'Archive', textContent: tr('ui.archive', null, 'Archive'),
onClick: function () { onClick: function () {
archiveCapture(capture.captureId); archiveCapture(capture.captureId);
} }
@ -960,7 +982,7 @@
actionButtons.push(el('button', { actionButtons.push(el('button', {
className: 'browser-inbox-btn danger', className: 'browser-inbox-btn danger',
'data-browser-inbox-action': 'delete-permanently', 'data-browser-inbox-action': 'delete-permanently',
textContent: 'Delete permanently', textContent: tr('ui.deletePermanently', null, 'Delete permanently'),
onClick: function () { onClick: function () {
permanentlyDeleteCapture(capture.captureId); permanentlyDeleteCapture(capture.captureId);
} }
@ -972,8 +994,11 @@
var visibleCount = visibleCaptures().length; var visibleCount = visibleCaptures().length;
var total = captures.length; var total = captures.length;
countEl.textContent = visibleCount === total countEl.textContent = visibleCount === total
? total + ' item' + (total === 1 ? '' : 's') ? localizedItemCount(total)
: visibleCount + ' of ' + total + ' items'; : tr('ui.items.filtered', {
visible: localizedItemCount(visibleCount),
total: localizedItemCount(total)
}, localizedItemCount(visibleCount) + ' of ' + localizedItemCount(total));
var scopeCount = scope.mode === 'global' var scopeCount = scope.mode === 'global'
? total ? total
: captures.filter(function (capture) { return capture.workspaceRootPath === scope.workspaceRoot; }).length; : captures.filter(function (capture) { return capture.workspaceRootPath === scope.workspaceRoot; }).length;
@ -1026,7 +1051,7 @@
}); });
}); });
}).catch(function (err) { }).catch(function (err) {
reportError('ui.loadError', 'Could not load the browser inbox. Please try again.', err); reportError('ui.loadError', 'Could not load browser materials. Please try again.', err);
}); });
} }
@ -1066,13 +1091,13 @@
}).then(function (saved) { }).then(function (saved) {
if (!saved) return; if (!saved) return;
selectedId = received.captureId; selectedId = received.captureId;
statusText = 'Capture received'; statusText = tr('ui.captureReceived', null, 'Material received');
statusClass = ''; statusClass = '';
render(); render();
}); });
} }
selectedId = received.captureId; selectedId = received.captureId;
statusText = 'Capture received'; statusText = tr('ui.captureReceived', null, 'Material received');
statusClass = ''; statusClass = '';
render(); render();
return undefined; return undefined;
@ -1081,7 +1106,9 @@
if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe); if (typeof unsubscribe === 'function') unsubscribers.push(unsubscribe);
}); });
})).then(function () { })).then(function () {
statusText = scope.mode === 'global' ? 'Receiver ready for all workspaces' : 'Receiver ready for workspace'; statusText = scope.mode === 'global'
? tr('ui.receiverReadyAll', null, 'Receiver ready for all Deals')
: tr('ui.receiverReadyWorkspace', null, 'Receiver ready for this Deal');
statusClass = ''; statusClass = '';
}).catch(function (err) { }).catch(function (err) {
reportError('ui.receiverError', 'The browser receiver is unavailable. Please try again.', err); reportError('ui.receiverError', 'The browser receiver is unavailable. Please try again.', err);
@ -1098,8 +1125,11 @@
}); });
if (api && api.i18n && typeof api.i18n.onDidChangeLocale === 'function') { if (api && api.i18n && typeof api.i18n.onDidChangeLocale === 'function') {
api.i18n.onDidChangeLocale(function () { api.i18n.onDidChangeLocale(function () {
titleEl.textContent = scope.mode === 'global' ? tr('ui.title', null, 'Browser Inbox') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Browser Inbox · ' + scope.label); titleEl.textContent = scope.mode === 'global' ? tr('ui.title', null, 'Browser') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Browser · ' + scope.label);
searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search captures')); searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search captures'));
statusFilterEl.setAttribute('aria-label', tr('ui.statusFilter', null, 'Material status filter'));
workspaceFilterEl.setAttribute('aria-label', tr('ui.workspaceFilter', null, 'Deal filter'));
searchInput.setAttribute('aria-label', tr('ui.search', null, 'Search captures'));
clearBtn.textContent = tr('ui.clear', null, 'Clear'); clearBtn.textContent = tr('ui.clear', null, 'Clear');
render(); render();
}); });

View File

@ -1,25 +1,26 @@
{ {
"manifest.name": "Browser Inbox", "manifest.name": "Browser",
"manifest.description": "Global browser capture queue with explicit workspace assignment delivered through the local receiver event protocol.", "manifest.description": "Global browser materials with explicit Deal assignment delivered through the local receiver event protocol.",
"contributions.views.verstak.browser-inbox.view.title": "Browser Inbox", "contributions.views.verstak.browser-inbox.view.title": "Browser",
"contributions.sidebarItems.verstak.browser-inbox.sidebar.title": "Browser Inbox", "contributions.sidebarItems.verstak.browser-inbox.sidebar.title": "Browser",
"contributions.workspaceItems.verstak.browser-inbox.workspace.title": "Browser Inbox", "contributions.workspaceItems.verstak.browser-inbox.workspace.title": "Browser",
"contributions.settingsPanels.verstak.browser-inbox.settings.title": "Browser Inbox", "contributions.settingsPanels.verstak.browser-inbox.settings.title": "Browser",
"ui.connecting": "Connecting to receiver events...", "ui.connecting": "Connecting to receiver events...",
"ui.title": "Browser Inbox", "ui.title": "Browser",
"ui.workspaceTitle": "Browser Inbox · {workspace}", "ui.workspaceTitle": "Browser · {workspace}",
"ui.allCaptures": "All captures", "ui.allCaptures": "All captures",
"ui.allDeals": "All Deals",
"ui.unassigned": "Unassigned", "ui.unassigned": "Unassigned",
"ui.unprocessed": "Unprocessed", "ui.unprocessed": "Unprocessed",
"ui.processed": "Processed", "ui.processed": "Processed",
"ui.search": "Search captures", "ui.search": "Search captures",
"ui.clear": "Clear", "ui.clear": "Clear",
"ui.assignedHere": "Assigned to this workspace", "ui.assignedHere": "Assigned to this Deal",
"ui.selectCapture": "Select a capture to inspect it.", "ui.selectCapture": "Select a capture to inspect it.",
"ui.kind": "Kind", "ui.kind": "Kind",
"ui.domain": "Domain", "ui.domain": "Domain",
"ui.captured": "Captured", "ui.captured": "Captured",
"ui.workspace": "Workspace", "ui.workspace": "Deal",
"ui.status": "Status", "ui.status": "Status",
"ui.createNote": "Create Note", "ui.createNote": "Create Note",
"ui.createLink": "Create Link", "ui.createLink": "Create Link",
@ -36,14 +37,54 @@
"ui.rotateConfirm": "Rotate pairing token?", "ui.rotateConfirm": "Rotate pairing token?",
"ui.rotating": "Rotating...", "ui.rotating": "Rotating...",
"ui.tokenRotated": "Token rotated", "ui.tokenRotated": "Token rotated",
"ui.saveError": "Could not update the browser inbox. Please try again.", "ui.saveError": "Could not update browser materials. Please try again.",
"ui.createNoteError": "Could not create the note. Please try again.", "ui.createNoteError": "Could not create the note. Please try again.",
"ui.createLinkError": "Could not create the link. Please try again.", "ui.createLinkError": "Could not create the link. Please try again.",
"ui.openLinkError": "Could not open the link. Please try again.", "ui.openLinkError": "Could not open the link. Please try again.",
"ui.createFileError": "Could not create the file. Please try again.", "ui.createFileError": "Could not create the file. Please try again.",
"ui.loadError": "Could not load the browser inbox. Please try again.", "ui.loadError": "Could not load browser materials. Please try again.",
"ui.receiverError": "The browser receiver is unavailable. Please try again.", "ui.receiverError": "The browser receiver is unavailable. Please try again.",
"ui.pairingLoadError": "Could not load browser connection settings. Please try again.", "ui.pairingLoadError": "Could not load browser connection settings. Please try again.",
"ui.clipboardError": "Could not copy to the clipboard. Please try again.", "ui.clipboardError": "Could not copy to the clipboard. Please try again.",
"ui.tokenRotateError": "Could not rotate the pairing token. Please try again." "ui.tokenRotateError": "Could not rotate the pairing token. Please try again.",
"ui.statusFilter": "Material status filter",
"ui.workspaceFilter": "Deal filter",
"ui.assignment": "Assign to Deal",
"ui.clearAssignment": "Clear assignment",
"ui.empty": "No browser materials yet. Send a page, selection, or link from the extension.",
"ui.emptyFiltered": "No materials match the current filters.",
"ui.untitledCapture": "Untitled material",
"ui.url": "URL",
"ui.browser": "Browser",
"ui.assignBeforeCreation": "Assign a Deal before creating a note, link, or file.",
"ui.markProcessed": "Mark processed",
"ui.markUnprocessed": "Mark unprocessed",
"ui.openLink": "Open link",
"ui.restore": "Restore to Inbox",
"ui.archive": "Archive",
"ui.deletePermanently": "Delete permanently",
"ui.captureReceived": "Material received",
"ui.captureLoadError": "Could not load the received material.",
"ui.inboxArchived": "Inbox archived",
"ui.workspaceCapturesArchived": "Deal materials archived",
"ui.assignedToWorkspace": "Material assigned to {workspace}",
"ui.captureUnassigned": "Material is unassigned",
"ui.captureArchived": "Material archived",
"ui.captureRestored": "Material restored to Inbox",
"ui.captureDeleted": "Material permanently deleted",
"ui.captureProcessed": "Material marked processed",
"ui.captureUnprocessed": "Material marked unprocessed",
"ui.creatingNote": "Creating note...",
"ui.noteCreated": "Note created: {path}",
"ui.creatingLink": "Creating link...",
"ui.linkCreated": "Link created",
"ui.creatingFile": "Creating file...",
"ui.fileCreated": "File created: {path}",
"ui.receiverReadyAll": "Receiver ready for all Deals",
"ui.receiverReadyWorkspace": "Receiver ready for this Deal",
"ui.items.one": "{count} item",
"ui.items.few": "{count} items",
"ui.items.many": "{count} items",
"ui.items.other": "{count} items",
"ui.items.filtered": "{visible} of {total}"
} }

View File

@ -1,25 +1,26 @@
{ {
"manifest.name": "Входящие из браузера", "manifest.name": "Браузер",
"manifest.description": "Общая очередь материалов из браузера с явным назначением рабочего пространства через локальный протокол приёма.", "manifest.description": "Общие материалы из браузера с явным назначением Дела через локальный протокол приёма.",
"contributions.views.verstak.browser-inbox.view.title": "Входящие из браузера", "contributions.views.verstak.browser-inbox.view.title": "Браузер",
"contributions.sidebarItems.verstak.browser-inbox.sidebar.title": "Входящие из браузера", "contributions.sidebarItems.verstak.browser-inbox.sidebar.title": "Браузер",
"contributions.workspaceItems.verstak.browser-inbox.workspace.title": "Входящие из браузера", "contributions.workspaceItems.verstak.browser-inbox.workspace.title": "Браузер",
"contributions.settingsPanels.verstak.browser-inbox.settings.title": "Входящие из браузера", "contributions.settingsPanels.verstak.browser-inbox.settings.title": "Браузер",
"ui.connecting": "Подключение к событиям приёмника...", "ui.connecting": "Подключение к событиям приёмника...",
"ui.title": "Входящие из браузера", "ui.title": "Браузер",
"ui.workspaceTitle": "Входящие из браузера · {workspace}", "ui.workspaceTitle": "Браузер · {workspace}",
"ui.allCaptures": "Все материалы", "ui.allCaptures": "Все материалы",
"ui.allDeals": "Все Дела",
"ui.unassigned": "Не назначено", "ui.unassigned": "Не назначено",
"ui.unprocessed": "Не обработано", "ui.unprocessed": "Не обработано",
"ui.processed": "Обработано", "ui.processed": "Обработано",
"ui.search": "Поиск материалов", "ui.search": "Поиск материалов",
"ui.clear": "Очистить", "ui.clear": "Очистить",
"ui.assignedHere": "Назначено этому рабочему пространству", "ui.assignedHere": "Назначено этому Делу",
"ui.selectCapture": "Выберите материал для просмотра.", "ui.selectCapture": "Выберите материал для просмотра.",
"ui.kind": "Тип", "ui.kind": "Тип",
"ui.domain": "Домен", "ui.domain": "Домен",
"ui.captured": "Получено", "ui.captured": "Получено",
"ui.workspace": "Рабочее пространство", "ui.workspace": "Дело",
"ui.status": "Состояние", "ui.status": "Состояние",
"ui.createNote": "Создать заметку", "ui.createNote": "Создать заметку",
"ui.createLink": "Создать ссылку", "ui.createLink": "Создать ссылку",
@ -36,14 +37,54 @@
"ui.rotateConfirm": "Сменить токен сопряжения?", "ui.rotateConfirm": "Сменить токен сопряжения?",
"ui.rotating": "Смена токена...", "ui.rotating": "Смена токена...",
"ui.tokenRotated": "Токен изменён", "ui.tokenRotated": "Токен изменён",
"ui.saveError": "Не удалось обновить входящие из браузера. Повторите попытку.", "ui.saveError": "Не удалось обновить материалы браузера. Повторите попытку.",
"ui.createNoteError": "Не удалось создать заметку. Повторите попытку.", "ui.createNoteError": "Не удалось создать заметку. Повторите попытку.",
"ui.createLinkError": "Не удалось создать ссылку. Повторите попытку.", "ui.createLinkError": "Не удалось создать ссылку. Повторите попытку.",
"ui.openLinkError": "Не удалось открыть ссылку. Повторите попытку.", "ui.openLinkError": "Не удалось открыть ссылку. Повторите попытку.",
"ui.createFileError": "Не удалось создать файл. Повторите попытку.", "ui.createFileError": "Не удалось создать файл. Повторите попытку.",
"ui.loadError": "Не удалось загрузить входящие из браузера. Повторите попытку.", "ui.loadError": "Не удалось загрузить материалы браузера. Повторите попытку.",
"ui.receiverError": "Приёмник браузера недоступен. Повторите попытку.", "ui.receiverError": "Приёмник браузера недоступен. Повторите попытку.",
"ui.pairingLoadError": "Не удалось загрузить параметры подключения браузера. Повторите попытку.", "ui.pairingLoadError": "Не удалось загрузить параметры подключения браузера. Повторите попытку.",
"ui.clipboardError": "Не удалось скопировать в буфер обмена. Повторите попытку.", "ui.clipboardError": "Не удалось скопировать в буфер обмена. Повторите попытку.",
"ui.tokenRotateError": "Не удалось сменить токен сопряжения. Повторите попытку." "ui.tokenRotateError": "Не удалось сменить токен сопряжения. Повторите попытку.",
"ui.statusFilter": "Фильтр состояния материала",
"ui.workspaceFilter": "Фильтр Дела",
"ui.assignment": "Назначить Делу",
"ui.clearAssignment": "Снять назначение",
"ui.empty": "Пока нет материалов из браузера. Отправьте страницу, выделенный текст или ссылку с помощью расширения.",
"ui.emptyFiltered": "Нет материалов по текущим фильтрам.",
"ui.untitledCapture": "Материал без названия",
"ui.url": "URL",
"ui.browser": "Браузер",
"ui.assignBeforeCreation": "Назначьте Дело перед созданием заметки, ссылки или файла.",
"ui.markProcessed": "Отметить обработанным",
"ui.markUnprocessed": "Отметить необработанным",
"ui.openLink": "Открыть ссылку",
"ui.restore": "Вернуть во входящие",
"ui.archive": "Архивировать",
"ui.deletePermanently": "Удалить навсегда",
"ui.captureReceived": "Материал получен",
"ui.captureLoadError": "Не удалось загрузить полученный материал.",
"ui.inboxArchived": "Входящие архивированы",
"ui.workspaceCapturesArchived": "Материалы Дела архивированы",
"ui.assignedToWorkspace": "Материал назначен Делу «{workspace}»",
"ui.captureUnassigned": "Материал не назначен",
"ui.captureArchived": "Материал архивирован",
"ui.captureRestored": "Материал возвращён во входящие",
"ui.captureDeleted": "Материал удалён навсегда",
"ui.captureProcessed": "Материал отмечен обработанным",
"ui.captureUnprocessed": "Материал отмечен необработанным",
"ui.creatingNote": "Создание заметки...",
"ui.noteCreated": "Заметка создана: {path}",
"ui.creatingLink": "Создание ссылки...",
"ui.linkCreated": "Ссылка создана",
"ui.creatingFile": "Создание файла...",
"ui.fileCreated": "Файл создан: {path}",
"ui.receiverReadyAll": "Приёмник готов для всех Дел",
"ui.receiverReadyWorkspace": "Приёмник готов для этого Дела",
"ui.items.one": "{count} материал",
"ui.items.few": "{count} материала",
"ui.items.many": "{count} материалов",
"ui.items.other": "{count} материала",
"ui.items.filtered": "{visible} из {total}"
} }

View File

@ -1,10 +1,10 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"id": "verstak.browser-inbox", "id": "verstak.browser-inbox",
"name": "Browser Inbox", "name": "Browser",
"version": "0.1.0", "version": "0.1.0",
"apiVersion": "0.1.0", "apiVersion": "0.1.0",
"description": "Global browser capture queue with explicit workspace assignment delivered through the local receiver event protocol.", "description": "Global browser materials with explicit Deal assignment delivered through the local receiver event protocol.",
"localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } }, "localization": { "defaultLocale": "en", "locales": { "en": "locales/en.json", "ru": "locales/ru.json" } },
"source": "official", "source": "official",
"icon": "inbox", "icon": "inbox",
@ -29,7 +29,7 @@
"views": [ "views": [
{ {
"id": "verstak.browser-inbox.view", "id": "verstak.browser-inbox.view",
"title": "Browser Inbox", "title": "Browser",
"icon": "inbox", "icon": "inbox",
"component": "BrowserInboxView" "component": "BrowserInboxView"
} }
@ -37,7 +37,7 @@
"sidebarItems": [ "sidebarItems": [
{ {
"id": "verstak.browser-inbox.sidebar", "id": "verstak.browser-inbox.sidebar",
"title": "Browser Inbox", "title": "Browser",
"icon": "inbox", "icon": "inbox",
"view": "verstak.browser-inbox.view", "view": "verstak.browser-inbox.view",
"position": 30 "position": 30
@ -46,7 +46,7 @@
"workspaceItems": [ "workspaceItems": [
{ {
"id": "verstak.browser-inbox.workspace", "id": "verstak.browser-inbox.workspace",
"title": "Browser Inbox", "title": "Browser",
"icon": "inbox", "icon": "inbox",
"component": "BrowserInboxView" "component": "BrowserInboxView"
} }
@ -54,7 +54,7 @@
"settingsPanels": [ "settingsPanels": [
{ {
"id": "verstak.browser-inbox.settings", "id": "verstak.browser-inbox.settings",
"title": "Browser Inbox", "title": "Browser",
"component": "BrowserInboxSettings" "component": "BrowserInboxSettings"
} }
] ]

View File

@ -6,6 +6,10 @@ const vm = require('vm');
const root = path.resolve(__dirname, '..'); const root = path.resolve(__dirname, '..');
const sourcePath = path.join(root, 'plugins', 'browser-inbox', 'frontend', 'src', 'index.js'); const sourcePath = path.join(root, 'plugins', 'browser-inbox', 'frontend', 'src', 'index.js');
const source = fs.readFileSync(sourcePath, 'utf8'); const source = fs.readFileSync(sourcePath, 'utf8');
const catalogs = {
en: JSON.parse(fs.readFileSync(path.join(root, 'plugins', 'browser-inbox', 'locales', 'en.json'), 'utf8')),
ru: JSON.parse(fs.readFileSync(path.join(root, 'plugins', 'browser-inbox', 'locales', 'ru.json'), 'utf8')),
};
const technicalErrors = []; const technicalErrors = [];
class FakeNode { class FakeNode {
@ -145,7 +149,7 @@ function loadComponent(document) {
return component; return component;
} }
function makeApi(initialSettings = {}) { function makeApi(initialSettings = {}, locale = 'en') {
const settings = { ...initialSettings }; const settings = { ...initialSettings };
const handlers = {}; const handlers = {};
const unsubscribed = []; const unsubscribed = [];
@ -162,6 +166,13 @@ function makeApi(initialSettings = {}) {
receiverToken: 'initial-browser-token', receiverToken: 'initial-browser-token',
}; };
let nextWriteError = null; let nextWriteError = null;
function translate(key, params, fallback) {
let value = catalogs[locale]?.[key] || catalogs.en[key] || fallback || key;
Object.entries(params || {}).forEach(([name, replacement]) => {
value = value.replace(new RegExp(`\\{${name}\\}`, 'g'), String(replacement));
});
return value;
}
function backendCaptures() { function backendCaptures() {
const keys = ['captures:global', 'captures', ...Object.keys(settings).filter((key) => key.startsWith('captures:workspace:'))]; const keys = ['captures:global', 'captures', ...Object.keys(settings).filter((key) => key.startsWith('captures:workspace:'))];
const seen = new Set(); const seen = new Set();
@ -214,6 +225,11 @@ function makeApi(initialSettings = {}) {
fileByteWrites, fileByteWrites,
openedURLs, openedURLs,
publishedEvents, publishedEvents,
i18n: {
getLocale: () => locale,
t: translate,
onDidChangeLocale: () => () => {},
},
failNextWrite(message) { failNextWrite(message) {
nextWriteError = new Error(message || 'write failed'); nextWriteError = new Error(message || 'write failed');
}, },
@ -307,6 +323,18 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
} }
styledView.component.unmount && styledView.component.unmount(styledView.container); styledView.component.unmount && styledView.component.unmount(styledView.container);
const russianView = await mountWithApi(makeApi({}, 'ru'), {});
if (!russianView.container.textContent.includes('Браузер')) {
throw new Error('Browser Inbox does not use the localized Browser title');
}
if (!russianView.container.textContent.includes('Все Дела')) {
throw new Error('Browser Inbox does not use the localized Deal filter');
}
if (!russianView.container.textContent.includes('Пока нет материалов из браузера')) {
throw new Error('Browser Inbox empty state is not localized');
}
russianView.component.unmount && russianView.component.unmount(russianView.container);
const api = makeApi(); const api = makeApi();
const settingsView = await mountSettingsWithApi(makeApi()); const settingsView = await mountSettingsWithApi(makeApi());
const receiverURLInput = walk(settingsView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-pairing-url') === ''); const receiverURLInput = walk(settingsView.container, (node) => node.getAttribute && node.getAttribute('data-browser-inbox-pairing-url') === '');