fix: localize plugin operation errors
This commit is contained in:
parent
1f03226378
commit
a1b3c31d0d
|
|
@ -563,6 +563,14 @@
|
||||||
var disposed = false;
|
var disposed = false;
|
||||||
var unsubscribers = [];
|
var unsubscribers = [];
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.activity] ' + key, err);
|
||||||
|
}
|
||||||
|
statusText = tr(key, null, fallback);
|
||||||
|
statusClass = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
var toolbar = el('div', { className: 'activity-toolbar' });
|
var toolbar = el('div', { className: 'activity-toolbar' });
|
||||||
var titleEl = el('span', { className: 'activity-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Activity') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Activity · ' + scope.label) });
|
var titleEl = el('span', { className: 'activity-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Activity') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Activity · ' + scope.label) });
|
||||||
var countEl = el('span', { className: 'activity-count' });
|
var countEl = el('span', { className: 'activity-count' });
|
||||||
|
|
@ -634,8 +642,7 @@
|
||||||
? events.filter(function (item) { return !item._storageKey || item._storageKey === GLOBAL_KEY; })
|
? events.filter(function (item) { return !item._storageKey || item._storageKey === GLOBAL_KEY; })
|
||||||
: events;
|
: events;
|
||||||
return api.settings.write(scope.key, storageEvents(toStore)).then(persistSessionRegistry).then(persistCandidateCaches).catch(function (err) {
|
return api.settings.write(scope.key, storageEvents(toStore)).then(persistSessionRegistry).then(persistCandidateCaches).catch(function (err) {
|
||||||
statusText = tr('ui.saveError', { error: err && err.message ? err.message : String(err) }, 'Could not save activity: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.saveError', 'Could not save activity. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -668,8 +675,7 @@
|
||||||
statusText = tr('ui.cleared', null, 'Activity cleared');
|
statusText = tr('ui.cleared', null, 'Activity cleared');
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = tr('ui.clearError', { error: err && err.message ? err.message : String(err) }, 'Could not clear activity: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.clearError', 'Could not clear activity. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -797,8 +803,7 @@
|
||||||
statusText = tr('ui.dismissed', null, 'Candidate dismissed');
|
statusText = tr('ui.dismissed', null, 'Candidate dismissed');
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not dismiss candidate: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.dismissError', 'Could not dismiss the suggestion. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
}).then(render);
|
}).then(render);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -870,8 +875,7 @@
|
||||||
updateCandidates();
|
updateCandidates();
|
||||||
return persistSessionRegistry().then(persistCandidateCaches);
|
return persistSessionRegistry().then(persistCandidateCaches);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not load activity: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.loadError', 'Could not load activity. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -898,8 +902,7 @@
|
||||||
return api.commands.register(WORKLOG_COMMAND_ID, listWorkSessionCandidates).then(function (unregister) {
|
return api.commands.register(WORKLOG_COMMAND_ID, listWorkSessionCandidates).then(function (unregister) {
|
||||||
if (typeof unregister === 'function') unsubscribers.push(unregister);
|
if (typeof unregister === 'function') unsubscribers.push(unregister);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Activity commands unavailable: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.commandsUnavailable', 'Activity actions are unavailable. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -917,8 +920,7 @@
|
||||||
statusText = scope.mode === 'global' ? 'Listening for all activity' : 'Listening for workspace activity';
|
statusText = scope.mode === 'global' ? 'Listening for all activity' : 'Listening for workspace activity';
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Activity subscriptions unavailable: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.subscriptionsUnavailable', 'Activity updates are unavailable. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,13 @@
|
||||||
"ui.clearWorkspaceWarning": "This permanently deletes recorded activity for {workspace}. Activity in other cases remains.",
|
"ui.clearWorkspaceWarning": "This permanently deletes recorded activity for {workspace}. Activity in other cases remains.",
|
||||||
"ui.confirmClear": "Clear activity",
|
"ui.confirmClear": "Clear activity",
|
||||||
"ui.cancel": "Cancel",
|
"ui.cancel": "Cancel",
|
||||||
"ui.saveError": "Could not save activity: {error}",
|
"ui.saveError": "Could not save activity. Please try again.",
|
||||||
"ui.cleared": "Activity cleared",
|
"ui.cleared": "Activity cleared",
|
||||||
"ui.clearError": "Could not clear activity: {error}",
|
"ui.clearError": "Could not clear activity. Please try again.",
|
||||||
|
"ui.dismissError": "Could not dismiss the suggestion. Please try again.",
|
||||||
|
"ui.loadError": "Could not load activity. Please try again.",
|
||||||
|
"ui.commandsUnavailable": "Activity actions are unavailable. Please try again.",
|
||||||
|
"ui.subscriptionsUnavailable": "Activity updates are unavailable. Please try again.",
|
||||||
"ui.empty": "No activity events yet",
|
"ui.empty": "No activity events yet",
|
||||||
"ui.emptyHint": "File changes, browser captures, and conversions will appear here.",
|
"ui.emptyHint": "File changes, browser captures, and conversions will appear here.",
|
||||||
"ui.details": "Details",
|
"ui.details": "Details",
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,13 @@
|
||||||
"ui.clearWorkspaceWarning": "Будет безвозвратно удалена записанная активность Дела «{workspace}». Активность других Дел останется.",
|
"ui.clearWorkspaceWarning": "Будет безвозвратно удалена записанная активность Дела «{workspace}». Активность других Дел останется.",
|
||||||
"ui.confirmClear": "Очистить активность",
|
"ui.confirmClear": "Очистить активность",
|
||||||
"ui.cancel": "Отмена",
|
"ui.cancel": "Отмена",
|
||||||
"ui.saveError": "Не удалось сохранить активность: {error}",
|
"ui.saveError": "Не удалось сохранить активность. Повторите попытку.",
|
||||||
"ui.cleared": "Активность очищена",
|
"ui.cleared": "Активность очищена",
|
||||||
"ui.clearError": "Не удалось очистить активность: {error}",
|
"ui.clearError": "Не удалось очистить активность. Повторите попытку.",
|
||||||
|
"ui.dismissError": "Не удалось отклонить предложение. Повторите попытку.",
|
||||||
|
"ui.loadError": "Не удалось загрузить активность. Повторите попытку.",
|
||||||
|
"ui.commandsUnavailable": "Действия с активностью недоступны. Повторите попытку.",
|
||||||
|
"ui.subscriptionsUnavailable": "Обновления активности недоступны. Повторите попытку.",
|
||||||
"ui.empty": "Событий активности пока нет",
|
"ui.empty": "Событий активности пока нет",
|
||||||
"ui.emptyHint": "Здесь появятся изменения файлов, материалы из браузера и преобразования.",
|
"ui.emptyHint": "Здесь появятся изменения файлов, материалы из браузера и преобразования.",
|
||||||
"ui.details": "Подробнее",
|
"ui.details": "Подробнее",
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,16 @@
|
||||||
if (api && api.i18n && typeof api.i18n.t === 'function') return api.i18n.t(key, params, fallback);
|
if (api && api.i18n && typeof api.i18n.t === 'function') return api.i18n.t(key, params, fallback);
|
||||||
return fallback || key;
|
return fallback || key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.browser-inbox] ' + key, err);
|
||||||
|
}
|
||||||
|
statusText = tr(key, null, fallback);
|
||||||
|
statusClass = 'error';
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
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' });
|
||||||
|
|
@ -496,9 +506,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') {
|
||||||
statusText = 'Could not save inbox: events API unavailable';
|
reportError('ui.saveError', 'Could not update the browser inbox. Please try again.');
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
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 () {
|
||||||
|
|
@ -516,9 +524,7 @@
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not save inbox: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.saveError', 'Could not update the browser inbox. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -646,9 +652,7 @@
|
||||||
function createNoteFromCapture(capture) {
|
function createNoteFromCapture(capture) {
|
||||||
if (!capture || !capture.workspaceRootPath) return Promise.resolve();
|
if (!capture || !capture.workspaceRootPath) return Promise.resolve();
|
||||||
if (!api || !api.files || typeof api.files.writeText !== 'function') {
|
if (!api || !api.files || typeof api.files.writeText !== 'function') {
|
||||||
statusText = 'Could not create note: files API unavailable';
|
reportError('ui.createNoteError', 'Could not create the note. Please try again.');
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
var title = noteTitle(capture);
|
var title = noteTitle(capture);
|
||||||
|
|
@ -677,18 +681,14 @@
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
return archiveCapture(capture.captureId);
|
return archiveCapture(capture.captureId);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not create note: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.createNoteError', 'Could not create the note. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLinkFromCapture(capture) {
|
function createLinkFromCapture(capture) {
|
||||||
if (!capture || !capture.workspaceRootPath || !capture.url) return Promise.resolve();
|
if (!capture || !capture.workspaceRootPath || !capture.url) return Promise.resolve();
|
||||||
if (!api || !api.files || typeof api.files.writeText !== 'function') {
|
if (!api || !api.files || typeof api.files.writeText !== 'function') {
|
||||||
statusText = 'Could not create link: files API unavailable';
|
reportError('ui.createLinkError', 'Could not create the link. Please try again.');
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
var title = noteTitle(capture);
|
var title = noteTitle(capture);
|
||||||
|
|
@ -729,27 +729,21 @@
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
return archiveCapture(capture.captureId);
|
return archiveCapture(capture.captureId);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not create link: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.createLinkError', 'Could not create the link. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCaptureURL(capture) {
|
function openCaptureURL(capture) {
|
||||||
if (!capture || !capture.url || !api || !api.files || typeof api.files.openURL !== 'function') return Promise.resolve();
|
if (!capture || !capture.url || !api || !api.files || typeof api.files.openURL !== 'function') return Promise.resolve();
|
||||||
return api.files.openURL(capture.url).catch(function (err) {
|
return api.files.openURL(capture.url).catch(function (err) {
|
||||||
statusText = 'Could not open link: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.openLinkError', 'Could not open the link. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFileFromCapture(capture) {
|
function createFileFromCapture(capture) {
|
||||||
if (!capture || !capture.workspaceRootPath || capture.kind !== 'file' || !capture.fileName || (!capture.fileText && !capture.fileDataBase64)) return Promise.resolve();
|
if (!capture || !capture.workspaceRootPath || capture.kind !== 'file' || !capture.fileName || (!capture.fileText && !capture.fileDataBase64)) return Promise.resolve();
|
||||||
if (!api || !api.files || (capture.fileDataBase64 ? typeof api.files.writeBytes !== 'function' : typeof api.files.writeText !== 'function')) {
|
if (!api || !api.files || (capture.fileDataBase64 ? typeof api.files.writeBytes !== 'function' : typeof api.files.writeText !== 'function')) {
|
||||||
statusText = 'Could not create file: files API unavailable';
|
reportError('ui.createFileError', 'Could not create the file. Please try again.');
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
var fileName = safeFileFilename(capture.fileName);
|
var fileName = safeFileFilename(capture.fileName);
|
||||||
|
|
@ -785,9 +779,7 @@
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
return archiveCapture(capture.captureId);
|
return archiveCapture(capture.captureId);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not create file: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.createFileError', 'Could not create the file. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
render();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1034,8 +1026,7 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Could not load inbox: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.loadError', 'Could not load the browser inbox. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1093,8 +1084,7 @@
|
||||||
statusText = scope.mode === 'global' ? 'Receiver ready for all workspaces' : 'Receiver ready for workspace';
|
statusText = scope.mode === 'global' ? 'Receiver ready for all workspaces' : 'Receiver ready for workspace';
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = 'Receiver unavailable: ' + (err && err.message ? err.message : String(err));
|
reportError('ui.receiverError', 'The browser receiver is unavailable. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1201,6 +1191,13 @@
|
||||||
statusEl.className = 'browser-inbox-settings-status' + (isError ? ' error' : '');
|
statusEl.className = 'browser-inbox-settings-status' + (isError ? ' error' : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.browser-inbox.settings] ' + key, err);
|
||||||
|
}
|
||||||
|
setStatus(tr(key, null, fallback), true);
|
||||||
|
}
|
||||||
|
|
||||||
function setBusy(busy) {
|
function setBusy(busy) {
|
||||||
copyURLButton.disabled = busy;
|
copyURLButton.disabled = busy;
|
||||||
copyTokenButton.disabled = busy;
|
copyTokenButton.disabled = busy;
|
||||||
|
|
@ -1226,7 +1223,7 @@
|
||||||
applyPairing(pairing);
|
applyPairing(pairing);
|
||||||
setStatus('', false);
|
setStatus('', false);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus(text(err && err.message ? err.message : err), true);
|
reportError('ui.pairingLoadError', 'Could not load browser connection settings. Please try again.', err);
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
});
|
});
|
||||||
|
|
@ -1241,7 +1238,7 @@
|
||||||
navigator.clipboard.writeText(value).then(function () {
|
navigator.clipboard.writeText(value).then(function () {
|
||||||
setStatus(tr('ui.copied', { label: label }, '{label} copied'), false);
|
setStatus(tr('ui.copied', { label: label }, '{label} copied'), false);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus(text(err && err.message ? err.message : err), true);
|
reportError('ui.clipboardError', 'Could not copy to the clipboard. Please try again.', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1261,7 +1258,7 @@
|
||||||
applyPairing(pairing);
|
applyPairing(pairing);
|
||||||
setStatus(tr('ui.tokenRotated', null, 'Token rotated'), false);
|
setStatus(tr('ui.tokenRotated', null, 'Token rotated'), false);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus(text(err && err.message ? err.message : err), true);
|
reportError('ui.tokenRotateError', 'Could not rotate the pairing token. Please try again.', err);
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,15 @@
|
||||||
"ui.copied": "{label} copied",
|
"ui.copied": "{label} copied",
|
||||||
"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.createNoteError": "Could not create the note. Please try again.",
|
||||||
|
"ui.createLinkError": "Could not create 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.loadError": "Could not load the browser inbox. 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.clipboardError": "Could not copy to the clipboard. Please try again.",
|
||||||
|
"ui.tokenRotateError": "Could not rotate the pairing token. Please try again."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,15 @@
|
||||||
"ui.copied": "{label} скопирован",
|
"ui.copied": "{label} скопирован",
|
||||||
"ui.rotateConfirm": "Сменить токен сопряжения?",
|
"ui.rotateConfirm": "Сменить токен сопряжения?",
|
||||||
"ui.rotating": "Смена токена...",
|
"ui.rotating": "Смена токена...",
|
||||||
"ui.tokenRotated": "Токен изменён"
|
"ui.tokenRotated": "Токен изменён",
|
||||||
|
"ui.saveError": "Не удалось обновить входящие из браузера. Повторите попытку.",
|
||||||
|
"ui.createNoteError": "Не удалось создать заметку. Повторите попытку.",
|
||||||
|
"ui.createLinkError": "Не удалось создать ссылку. Повторите попытку.",
|
||||||
|
"ui.openLinkError": "Не удалось открыть ссылку. Повторите попытку.",
|
||||||
|
"ui.createFileError": "Не удалось создать файл. Повторите попытку.",
|
||||||
|
"ui.loadError": "Не удалось загрузить входящие из браузера. Повторите попытку.",
|
||||||
|
"ui.receiverError": "Приёмник браузера недоступен. Повторите попытку.",
|
||||||
|
"ui.pairingLoadError": "Не удалось загрузить параметры подключения браузера. Повторите попытку.",
|
||||||
|
"ui.clipboardError": "Не удалось скопировать в буфер обмена. Повторите попытку.",
|
||||||
|
"ui.tokenRotateError": "Не удалось сменить токен сопряжения. Повторите попытку."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -495,10 +495,10 @@
|
||||||
rebuildEditorArea();
|
rebuildEditorArea();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
console.warn('[default-editor] load error:', err);
|
||||||
editorWrap.innerHTML = '';
|
editorWrap.innerHTML = '';
|
||||||
editorWrap.appendChild(el('div', { className: 'de-error' }, [
|
editorWrap.appendChild(el('div', { className: 'de-error' }, [
|
||||||
el('div', {}, [tr('ui.loadFailed', null, 'Failed to load file')]),
|
el('div', {}, [tr('ui.loadFailed', null, 'Could not load the file. Please try again.')])
|
||||||
el('div', { className: 'de-error-msg' }, [(err && err.message) ? err.message : String(err)])
|
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@
|
||||||
"ui.saved": "Saved",
|
"ui.saved": "Saved",
|
||||||
"ui.discardConfirm": "Discard unsaved changes and reload from disk?",
|
"ui.discardConfirm": "Discard unsaved changes and reload from disk?",
|
||||||
"ui.loading": "Loading...",
|
"ui.loading": "Loading...",
|
||||||
"ui.loadFailed": "Failed to load file"
|
"ui.loadFailed": "Could not load the file. Please try again."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@
|
||||||
"ui.saved": "Сохранено",
|
"ui.saved": "Сохранено",
|
||||||
"ui.discardConfirm": "Отменить несохранённые изменения и перечитать файл с диска?",
|
"ui.discardConfirm": "Отменить несохранённые изменения и перечитать файл с диска?",
|
||||||
"ui.loading": "Загрузка...",
|
"ui.loading": "Загрузка...",
|
||||||
"ui.loadFailed": "Не удалось загрузить файл"
|
"ui.loadFailed": "Не удалось загрузить файл. Повторите попытку."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,9 @@
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
console.warn('[file-preview] preview error:', err);
|
||||||
body.className = 'fp-error';
|
body.className = 'fp-error';
|
||||||
body.textContent = tr('ui.error', { error: err && err.message ? err.message : String(err) }, 'Preview error: ' + (err && err.message ? err.message : String(err)));
|
body.textContent = tr('ui.error', null, 'Could not preview this file. Please try again.');
|
||||||
});
|
});
|
||||||
if (api.i18n && typeof api.i18n.onDidChangeLocale === 'function') {
|
if (api.i18n && typeof api.i18n.onDidChangeLocale === 'function') {
|
||||||
api.i18n.onDidChangeLocale(function () {
|
api.i18n.onDidChangeLocale(function () {
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@
|
||||||
"ui.openExternal": "Open External",
|
"ui.openExternal": "Open External",
|
||||||
"ui.preview": "Preview",
|
"ui.preview": "Preview",
|
||||||
"ui.loading": "Loading...",
|
"ui.loading": "Loading...",
|
||||||
"ui.error": "Preview error: {error}"
|
"ui.error": "Could not preview this file. Please try again."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,5 @@
|
||||||
"ui.openExternal": "Открыть во внешнем приложении",
|
"ui.openExternal": "Открыть во внешнем приложении",
|
||||||
"ui.preview": "Просмотр",
|
"ui.preview": "Просмотр",
|
||||||
"ui.loading": "Загрузка...",
|
"ui.loading": "Загрузка...",
|
||||||
"ui.error": "Ошибка просмотра: {error}"
|
"ui.error": "Не удалось открыть предварительный просмотр файла. Повторите попытку."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -320,14 +320,14 @@
|
||||||
return Promise.reject(new Error('clipboard unavailable'));
|
return Promise.reject(new Error('clipboard unavailable'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showExternalFallback(entry, mode, reason) {
|
function showExternalFallback(entry, mode) {
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
var pathToShow = entry.relativePath;
|
var pathToShow = entry.relativePath;
|
||||||
if (mode === 'explorer' && entry.type !== 'folder') {
|
if (mode === 'explorer' && entry.type !== 'folder') {
|
||||||
pathToShow = parentPath(entry.relativePath) || entry.relativePath;
|
pathToShow = parentPath(entry.relativePath) || entry.relativePath;
|
||||||
}
|
}
|
||||||
var title = mode === 'explorer' ? 'Show in Explorer' : 'Open External';
|
var title = mode === 'explorer' ? 'Show in Explorer' : 'Open External';
|
||||||
var message = title + ' failed.\n' + (reason ? String(reason) + '\n' : '') + 'Vault-relative path:\n' + pathToShow;
|
var message = title + ' failed.\nVault-relative path:\n' + pathToShow;
|
||||||
confirmModal(message, { confirmText: 'Copy Path', cancelText: 'Close' }).then(function (copy) {
|
confirmModal(message, { confirmText: 'Copy Path', cancelText: 'Close' }).then(function (copy) {
|
||||||
if (!copy) return;
|
if (!copy) return;
|
||||||
copyTextToClipboard(pathToShow).catch(function (err) {
|
copyTextToClipboard(pathToShow).catch(function (err) {
|
||||||
|
|
@ -374,6 +374,13 @@
|
||||||
return fallback || key;
|
return fallback || key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.files] ' + key, err);
|
||||||
|
}
|
||||||
|
return tr(key, null, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
function scopedPath(local) {
|
function scopedPath(local) {
|
||||||
local = cleanPath(local);
|
local = cleanPath(local);
|
||||||
return workspaceRoot ? (local ? workspaceRoot + '/' + local : workspaceRoot) : local;
|
return workspaceRoot ? (local ? workspaceRoot + '/' + local : workspaceRoot) : local;
|
||||||
|
|
@ -714,10 +721,10 @@
|
||||||
renderList();
|
renderList();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
console.warn('[verstak.files] load error:', err);
|
||||||
listContainer.innerHTML = '';
|
listContainer.innerHTML = '';
|
||||||
listContainer.appendChild(el('div', { className: 'files-error' }, [
|
listContainer.appendChild(el('div', { className: 'files-error' }, [
|
||||||
el('div', {}, [tr('ui.loadFailed', null, 'Failed to load files')]),
|
el('div', {}, [tr('ui.loadFailed', null, 'Could not load files. Please try again.')])
|
||||||
el('div', { className: 'files-error-msg' }, [(err && err.message) ? err.message : String(err)])
|
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -863,7 +870,7 @@
|
||||||
api.workbench.openResource({ kind: 'vault-file', path: full, mode: 'edit', extension: ext ? '.' + ext : '', context: { sourcePluginId: 'verstak.files', sourceView: 'files' } }).catch(function () {});
|
api.workbench.openResource({ kind: 'vault-file', path: full, mode: 'edit', extension: ext ? '.' + ext : '', context: { sourcePluginId: 'verstak.files', sourceView: 'files' } }).catch(function () {});
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setCreateError('Error: ' + ((err && err.message) ? err.message : String(err)));
|
setCreateError(reportError('ui.createError', 'Could not create this item. Please try again.', err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -923,7 +930,7 @@
|
||||||
setRenameError('A file with that name already exists');
|
setRenameError('A file with that name already exists');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setRenameError('Error: ' + ((err && err.message) ? err.message : String(err)));
|
setRenameError(reportError('ui.renameError', 'Could not rename this item. Please try again.', err));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -935,7 +942,9 @@
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
api.files.trash(entry.relativePath).then(function () {
|
api.files.trash(entry.relativePath).then(function () {
|
||||||
loadEntries();
|
loadEntries();
|
||||||
}).catch(function (err) { window.alert((err && err.message) ? err.message : String(err)); });
|
}).catch(function (err) {
|
||||||
|
window.alert(reportError('ui.trashError', 'Could not move this item to trash. Please try again.', err));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else if (count > 1) {
|
} else if (count > 1) {
|
||||||
confirmModal('Move ' + count + ' items to trash?', { danger: true }).then(function (ok) {
|
confirmModal('Move ' + count + ' items to trash?', { danger: true }).then(function (ok) {
|
||||||
|
|
@ -994,11 +1003,12 @@
|
||||||
var filesApi = api && api.files;
|
var filesApi = api && api.files;
|
||||||
var action = mode === 'explorer' ? filesApi && filesApi.showInFolder : filesApi && filesApi.openExternal;
|
var action = mode === 'explorer' ? filesApi && filesApi.showInFolder : filesApi && filesApi.openExternal;
|
||||||
if (typeof action !== 'function') {
|
if (typeof action !== 'function') {
|
||||||
showExternalFallback(entry, mode, 'files external-open API is unavailable.');
|
showExternalFallback(entry, mode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action(entry.relativePath).catch(function (err) {
|
action(entry.relativePath).catch(function (err) {
|
||||||
showExternalFallback(entry, mode, err && err.message ? err.message : err);
|
console.warn('[verstak.files] external open error:', err);
|
||||||
|
showExternalFallback(entry, mode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,8 @@
|
||||||
"ui.noMatches": "No matches",
|
"ui.noMatches": "No matches",
|
||||||
"ui.clearFilter": "Clear filter",
|
"ui.clearFilter": "Clear filter",
|
||||||
"ui.loading": "Loading...",
|
"ui.loading": "Loading...",
|
||||||
"ui.loadFailed": "Failed to load files"
|
"ui.loadFailed": "Could not load files. Please try again.",
|
||||||
|
"ui.createError": "Could not create this item. Please try again.",
|
||||||
|
"ui.renameError": "Could not rename this item. Please try again.",
|
||||||
|
"ui.trashError": "Could not move this item to trash. Please try again."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,8 @@
|
||||||
"ui.noMatches": "Совпадений нет",
|
"ui.noMatches": "Совпадений нет",
|
||||||
"ui.clearFilter": "Сбросить фильтр",
|
"ui.clearFilter": "Сбросить фильтр",
|
||||||
"ui.loading": "Загрузка...",
|
"ui.loading": "Загрузка...",
|
||||||
"ui.loadFailed": "Не удалось загрузить файлы"
|
"ui.loadFailed": "Не удалось загрузить файлы. Повторите попытку.",
|
||||||
|
"ui.createError": "Не удалось создать этот элемент. Повторите попытку.",
|
||||||
|
"ui.renameError": "Не удалось переименовать этот элемент. Повторите попытку.",
|
||||||
|
"ui.trashError": "Не удалось переместить элемент в корзину. Повторите попытку."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,14 @@
|
||||||
var statusClass = '';
|
var statusClass = '';
|
||||||
var modalHost = el('div', { className: 'journal-modal-host', hidden: 'hidden' });
|
var modalHost = el('div', { className: 'journal-modal-host', hidden: 'hidden' });
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.journal] ' + key, err);
|
||||||
|
}
|
||||||
|
statusText = tr(key, null, fallback);
|
||||||
|
statusClass = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
var toolbar = el('div', { className: 'journal-toolbar' });
|
var toolbar = el('div', { className: 'journal-toolbar' });
|
||||||
var titleEl = el('span', { className: 'journal-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Journal') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Journal · ' + scope.label) });
|
var titleEl = el('span', { className: 'journal-title', textContent: scope.mode === 'global' ? tr('ui.title', null, 'Journal') : tr('ui.workspaceTitle', { workspace: scope.label }, 'Journal · ' + scope.label) });
|
||||||
var countEl = el('span', { className: 'journal-count' });
|
var countEl = el('span', { className: 'journal-count' });
|
||||||
|
|
@ -311,8 +319,7 @@
|
||||||
var target = cleanWorkspace(workspaceRoot || scope.workspaceRoot);
|
var target = cleanWorkspace(workspaceRoot || scope.workspaceRoot);
|
||||||
if (!target) return Promise.resolve();
|
if (!target) return Promise.resolve();
|
||||||
return api.settings.write(WORKLOG_PREFIX + encodeKey(target), storageEntries(values || entries)).catch(function (err) {
|
return api.settings.write(WORKLOG_PREFIX + encodeKey(target), storageEntries(values || entries)).catch(function (err) {
|
||||||
statusText = tr('ui.saveError', { error: err && err.message ? err.message : String(err) }, 'Could not save journal: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.saveError', 'Could not save journal. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,8 +346,7 @@
|
||||||
statusText = tr('ui.aggregating', null, 'Aggregating worklogs');
|
statusText = tr('ui.aggregating', null, 'Aggregating worklogs');
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = tr('ui.loadError', { error: err && err.message ? err.message : String(err) }, 'Could not load journal: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.loadError', 'Could not load journal. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return api.settings.read(scope.key).then(function (stored) {
|
return api.settings.read(scope.key).then(function (stored) {
|
||||||
|
|
@ -348,8 +354,7 @@
|
||||||
statusText = tr('ui.ready', null, 'Ready');
|
statusText = tr('ui.ready', null, 'Ready');
|
||||||
statusClass = '';
|
statusClass = '';
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = tr('ui.loadError', { error: err && err.message ? err.message : String(err) }, 'Could not load journal: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.loadError', 'Could not load journal. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
"ui.title": "Journal",
|
"ui.title": "Journal",
|
||||||
"ui.workspaceTitle": "Journal · {workspace}",
|
"ui.workspaceTitle": "Journal · {workspace}",
|
||||||
"ui.add": "Add",
|
"ui.add": "Add",
|
||||||
"ui.saveError": "Could not save journal: {error}",
|
"ui.saveError": "Could not save journal. Please try again.",
|
||||||
"ui.aggregating": "Aggregating worklogs",
|
"ui.aggregating": "Aggregating worklogs",
|
||||||
"ui.loadError": "Could not load journal: {error}",
|
"ui.loadError": "Could not load journal. Please try again.",
|
||||||
"ui.ready": "Ready",
|
"ui.ready": "Ready",
|
||||||
"ui.workItem": "Work item",
|
"ui.workItem": "Work item",
|
||||||
"ui.body": "Body",
|
"ui.body": "Body",
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
"ui.title": "Журнал",
|
"ui.title": "Журнал",
|
||||||
"ui.workspaceTitle": "Журнал · {workspace}",
|
"ui.workspaceTitle": "Журнал · {workspace}",
|
||||||
"ui.add": "Добавить",
|
"ui.add": "Добавить",
|
||||||
"ui.saveError": "Не удалось сохранить журнал: {error}",
|
"ui.saveError": "Не удалось сохранить журнал. Повторите попытку.",
|
||||||
"ui.aggregating": "Сбор записей о работе",
|
"ui.aggregating": "Сбор записей о работе",
|
||||||
"ui.loadError": "Не удалось загрузить журнал: {error}",
|
"ui.loadError": "Не удалось загрузить журнал. Повторите попытку.",
|
||||||
"ui.ready": "Готово",
|
"ui.ready": "Готово",
|
||||||
"ui.workItem": "Выполненная работа",
|
"ui.workItem": "Выполненная работа",
|
||||||
"ui.body": "Описание",
|
"ui.body": "Описание",
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function userFacingError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.notes] ' + key, err);
|
||||||
|
}
|
||||||
|
return tr(key, null, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
function loadNotes() {
|
function loadNotes() {
|
||||||
listContainer.innerHTML = '';
|
listContainer.innerHTML = '';
|
||||||
listContainer.appendChild(el('div', { className: 'notes-empty' }, [tr('ui.loading', null, 'Loading...')]));
|
listContainer.appendChild(el('div', { className: 'notes-empty' }, [tr('ui.loading', null, 'Loading...')]));
|
||||||
|
|
@ -335,7 +342,7 @@
|
||||||
renderList();
|
renderList();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
renderEmpty('Error: ' + (err.message || err));
|
renderEmpty(userFacingError('ui.loadError', 'Could not load notes. Please try again.', err));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,8 +386,7 @@
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
if (!disposed) setStatus(action.label + ' complete', 'success');
|
if (!disposed) setStatus(action.label + ' complete', 'success');
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error('[notes] contribution action failed:', err);
|
if (!disposed) setStatus(userFacingError('ui.actionError', 'Could not complete this note action. Please try again.', err), 'error');
|
||||||
if (!disposed) setStatus('Error: ' + (err && err.message ? err.message : err), 'error');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,7 +539,7 @@
|
||||||
}).catch(function () {});
|
}).catch(function () {});
|
||||||
}
|
}
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus('Error: ' + (err.message || err), 'error');
|
setStatus(userFacingError('ui.createError', 'Could not create the note. Please try again.', err), 'error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,7 +574,7 @@
|
||||||
setStatus(tr('ui.renamed', null, 'Note renamed'), 'success');
|
setStatus(tr('ui.renamed', null, 'Note renamed'), 'success');
|
||||||
loadNotes();
|
loadNotes();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus('Error: ' + (err.message || err), 'error');
|
setStatus(userFacingError('ui.renameError', 'Could not rename the note. Please try again.', err), 'error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -585,7 +591,7 @@
|
||||||
setStatus(tr('ui.trashed', null, 'Note moved to trash'), 'success');
|
setStatus(tr('ui.trashed', null, 'Note moved to trash'), 'success');
|
||||||
loadNotes();
|
loadNotes();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
setStatus('Error: ' + (err.message || err), 'error');
|
setStatus(userFacingError('ui.trashError', 'Could not move the note to trash. Please try again.', err), 'error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,10 @@
|
||||||
"ui.existingFile": " Existing file: {path}.",
|
"ui.existingFile": " Existing file: {path}.",
|
||||||
"ui.chooseDifferent": " Please choose a different title.",
|
"ui.chooseDifferent": " Please choose a different title.",
|
||||||
"ui.trashTitle": "Move Note to Trash",
|
"ui.trashTitle": "Move Note to Trash",
|
||||||
"ui.trashConfirm": "Move \"{title}\" to trash?"
|
"ui.trashConfirm": "Move \"{title}\" to trash?",
|
||||||
|
"ui.loadError": "Could not load notes. Please try again.",
|
||||||
|
"ui.actionError": "Could not complete this note action. Please try again.",
|
||||||
|
"ui.createError": "Could not create the note. Please try again.",
|
||||||
|
"ui.renameError": "Could not rename the note. Please try again.",
|
||||||
|
"ui.trashError": "Could not move the note to trash. Please try again."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,10 @@
|
||||||
"ui.existingFile": " Существующий файл: {path}.",
|
"ui.existingFile": " Существующий файл: {path}.",
|
||||||
"ui.chooseDifferent": " Выберите другое название.",
|
"ui.chooseDifferent": " Выберите другое название.",
|
||||||
"ui.trashTitle": "Переместить заметку в корзину",
|
"ui.trashTitle": "Переместить заметку в корзину",
|
||||||
"ui.trashConfirm": "Переместить «{title}» в корзину?"
|
"ui.trashConfirm": "Переместить «{title}» в корзину?",
|
||||||
|
"ui.loadError": "Не удалось загрузить заметки. Повторите попытку.",
|
||||||
|
"ui.actionError": "Не удалось выполнить действие с заметкой. Повторите попытку.",
|
||||||
|
"ui.createError": "Не удалось создать заметку. Повторите попытку.",
|
||||||
|
"ui.renameError": "Не удалось переименовать заметку. Повторите попытку.",
|
||||||
|
"ui.trashError": "Не удалось переместить заметку в корзину. Повторите попытку."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,8 @@
|
||||||
try {
|
try {
|
||||||
providers = await api.contributions.list('searchProviders');
|
providers = await api.contributions.list('searchProviders');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.errors.push(err && err.message ? err.message : String(err));
|
console.warn('[verstak.search] list search providers:', err);
|
||||||
|
output.errors.push(true);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
providers = Array.isArray(providers) ? providers : [];
|
providers = Array.isArray(providers) ? providers : [];
|
||||||
|
|
@ -294,7 +295,8 @@
|
||||||
var normalized = normalizeProviderResults(provider, response && response.result);
|
var normalized = normalizeProviderResults(provider, response && response.result);
|
||||||
output.results = output.results.concat(normalized.slice(0, remaining - output.results.length));
|
output.results = output.results.concat(normalized.slice(0, remaining - output.results.length));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output.errors.push(err && err.message ? err.message : String(err));
|
console.warn('[verstak.search] provider search:', err);
|
||||||
|
output.errors.push(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
|
|
@ -371,7 +373,9 @@
|
||||||
if (typeof alertEl.removeAttribute === 'function') alertEl.removeAttribute('hidden');
|
if (typeof alertEl.removeAttribute === 'function') alertEl.removeAttribute('hidden');
|
||||||
alertEl.appendChild(el('details', {}, [
|
alertEl.appendChild(el('details', {}, [
|
||||||
el('summary', {}, [tr('ui.providersFailed', null, 'Some plugin search providers did not respond')]),
|
el('summary', {}, [tr('ui.providersFailed', null, 'Some plugin search providers did not respond')]),
|
||||||
el('div', {}, [state.providerErrors.join('; ')])
|
el('div', {}, [state.providerErrors.map(function () {
|
||||||
|
return tr('ui.providerUnavailable', null, 'A search provider is unavailable.');
|
||||||
|
}).join(' ')])
|
||||||
]));
|
]));
|
||||||
} else if (typeof alertEl.setAttribute === 'function') {
|
} else if (typeof alertEl.setAttribute === 'function') {
|
||||||
alertEl.setAttribute('hidden', 'hidden');
|
alertEl.setAttribute('hidden', 'hidden');
|
||||||
|
|
@ -460,8 +464,9 @@
|
||||||
state.providerErrors = external.errors;
|
state.providerErrors = external.errors;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (seq !== searchSeq) return;
|
if (seq !== searchSeq) return;
|
||||||
|
console.warn('[verstak.search] search:', err);
|
||||||
state.results = [];
|
state.results = [];
|
||||||
state.error = err && err.message ? err.message : String(err);
|
state.error = tr('ui.searchError', null, 'Could not search the vault. Please try again.');
|
||||||
state.providerErrors = [];
|
state.providerErrors = [];
|
||||||
} finally {
|
} finally {
|
||||||
if (seq !== searchSeq) return;
|
if (seq !== searchSeq) return;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
"ui.searching": "Searching...",
|
"ui.searching": "Searching...",
|
||||||
"ui.search": "Search",
|
"ui.search": "Search",
|
||||||
"ui.providersFailed": "Some plugin search providers did not respond",
|
"ui.providersFailed": "Some plugin search providers did not respond",
|
||||||
|
"ui.providerUnavailable": "A search provider is unavailable.",
|
||||||
|
"ui.searchError": "Could not search the vault. Please try again.",
|
||||||
"ui.noResults": "No results",
|
"ui.noResults": "No results",
|
||||||
"ui.open": "Open",
|
"ui.open": "Open",
|
||||||
"ui.count": "{count} result(s)"
|
"ui.count": "{count} result(s)"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
"ui.searching": "Поиск...",
|
"ui.searching": "Поиск...",
|
||||||
"ui.search": "Искать",
|
"ui.search": "Искать",
|
||||||
"ui.providersFailed": "Некоторые поставщики поиска не ответили",
|
"ui.providersFailed": "Некоторые поставщики поиска не ответили",
|
||||||
|
"ui.providerUnavailable": "Один из источников поиска недоступен.",
|
||||||
|
"ui.searchError": "Не удалось выполнить поиск в хранилище. Повторите попытку.",
|
||||||
"ui.noResults": "Ничего не найдено",
|
"ui.noResults": "Ничего не найдено",
|
||||||
"ui.open": "Открыть",
|
"ui.open": "Открыть",
|
||||||
"ui.count": "Результатов: {count}"
|
"ui.count": "Результатов: {count}"
|
||||||
|
|
|
||||||
|
|
@ -169,8 +169,8 @@
|
||||||
|
|
||||||
function userFacingError(action, err) {
|
function userFacingError(action, err) {
|
||||||
var raw = (err && err.message) ? err.message : String(err || '');
|
var raw = (err && err.message) ? err.message : String(err || '');
|
||||||
if (typeof console !== 'undefined' && typeof console.error === 'function') {
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
console.error('[verstak.secrets] ' + action + ' failed', err);
|
console.warn('[verstak.secrets] ' + action + ' failed', err);
|
||||||
}
|
}
|
||||||
if (action === 'unlock') {
|
if (action === 'unlock') {
|
||||||
var minimumLength = raw.match(/master password must be at least (\d+) characters/i);
|
var minimumLength = raw.match(/master password must be at least (\d+) characters/i);
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,9 @@
|
||||||
const INPUT_STYLE = 'width:100%;background:#0f3460;border:1px solid #1a3a5c;color:#e0e0f0;padding:8px 10px;border-radius:4px;font-size:0.85rem;box-sizing:border-box;height:36px;'
|
const INPUT_STYLE = 'width:100%;background:#0f3460;border:1px solid #1a3a5c;color:#e0e0f0;padding:8px 10px;border-radius:4px;font-size:0.85rem;box-sizing:border-box;height:36px;'
|
||||||
const INPUT_FOCUS_STYLE = INPUT_STYLE + 'outline:none;border-color:#4ecca3;'
|
const INPUT_FOCUS_STYLE = INPUT_STYLE + 'outline:none;border-color:#4ecca3;'
|
||||||
|
|
||||||
function sanitizeError(msg) {
|
function reportError(key, fallback, error) {
|
||||||
if (!msg) return tr('ui.unknownError', null, 'Unknown error')
|
console.warn('[verstak.sync] operation failed:', error)
|
||||||
let s = String(msg).replace(/<[^>]+>/g, '')
|
return tr(key, null, fallback)
|
||||||
if (s.length > 200) s = s.substring(0, 200) + '...'
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncAPI() {
|
function syncAPI() {
|
||||||
|
|
@ -51,15 +49,21 @@
|
||||||
syncInterval = saved.syncInterval || 5
|
syncInterval = saved.syncInterval || 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (error) {
|
||||||
|
console.warn('[verstak.sync] settings load failed:', error)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
settings = await syncAPI().status()
|
settings = await syncAPI().status()
|
||||||
if (settings) {
|
if (settings) {
|
||||||
if (settings.serverUrl) serverUrl = settings.serverUrl
|
if (settings.serverUrl) serverUrl = settings.serverUrl
|
||||||
if (settings.syncInterval != null) syncInterval = settings.syncInterval
|
if (settings.syncInterval != null) syncInterval = settings.syncInterval
|
||||||
if (settings.syncInterval > 0) autoSync = true
|
if (settings.syncInterval > 0) autoSync = true
|
||||||
|
if (settings.lastError) console.warn('[verstak.sync] last sync failed:', settings.lastError)
|
||||||
}
|
}
|
||||||
} catch (_) { settings = null }
|
} catch (error) {
|
||||||
|
console.warn('[verstak.sync] status load failed:', error)
|
||||||
|
settings = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load()
|
load()
|
||||||
|
|
@ -80,7 +84,7 @@
|
||||||
resultMsg = tr('ui.settingsSaved', null, 'Settings saved.')
|
resultMsg = tr('ui.settingsSaved', null, 'Settings saved.')
|
||||||
resultKind = ''
|
resultKind = ''
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg = sanitizeError(e.message || e)
|
errorMsg = reportError('ui.saveFailed', 'Could not save sync settings. Please try again.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +101,7 @@
|
||||||
connectionResult = tr('ui.connectionSuccessful', null, 'Connection successful.')
|
connectionResult = tr('ui.connectionSuccessful', null, 'Connection successful.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
connectionOk = false
|
connectionOk = false
|
||||||
connectionResult = tr('ui.connectionFailed', { error: sanitizeError(e.message || e) }, 'Connection failed: {error}')
|
connectionResult = reportError('ui.connectionFailed', 'Could not connect. Check the server address and credentials.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +119,7 @@
|
||||||
password = ''
|
password = ''
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg = sanitizeError(e.message || e)
|
errorMsg = reportError('ui.connectFailed', 'Could not connect this device. Please try again.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -129,23 +133,8 @@
|
||||||
return parts.join(' · ')
|
return parts.join(' · ')
|
||||||
}
|
}
|
||||||
|
|
||||||
function conflictField(conflict, keys) {
|
function formatSyncConflict() {
|
||||||
for (const key of keys) {
|
return tr('ui.syncConflictItem', null, 'A synchronization conflict needs attention.')
|
||||||
const value = conflict && conflict[key]
|
|
||||||
if (value != null && String(value).trim()) return String(value)
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSyncConflict(conflict) {
|
|
||||||
const entityType = conflictField(conflict, ['entity_type', 'entityType']) || 'item'
|
|
||||||
const entityId = conflictField(conflict, ['entity_id', 'entityId', 'path']) || 'unknown'
|
|
||||||
const opId = conflictField(conflict, ['op_id', 'opId'])
|
|
||||||
const reason = conflictField(conflict, ['reason', 'message'])
|
|
||||||
const parts = [entityType + ': ' + entityId]
|
|
||||||
if (opId) parts.push('op ' + opId)
|
|
||||||
if (reason) parts.push(reason)
|
|
||||||
return parts.join(' · ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runSyncNow() {
|
async function runSyncNow() {
|
||||||
|
|
@ -163,7 +152,7 @@
|
||||||
resultKind = warning ? 'warning' : ''
|
resultKind = warning ? 'warning' : ''
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg = sanitizeError(e.message || e)
|
errorMsg = reportError('ui.syncFailed', 'Could not synchronize. Please try again.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +183,7 @@
|
||||||
settings = null
|
settings = null
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg = sanitizeError(e.message || e)
|
errorMsg = reportError('ui.disconnectFailed', 'Could not disconnect from the server. Please try again.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +198,7 @@
|
||||||
resultKind = ''
|
resultKind = ''
|
||||||
await load()
|
await load()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg = sanitizeError(e.message || e)
|
errorMsg = reportError('ui.resetKeyFailed', 'Could not reset the sync key. Please try again.', e)
|
||||||
}
|
}
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
@ -243,7 +232,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if settings && settings.lastError && !errorMsg}
|
{#if settings && settings.lastError && !errorMsg}
|
||||||
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:6px;color:#ff6b6b;font-size:0.85rem;">
|
<div style="padding:0.5rem 0.75rem;margin-bottom:0.75rem;background:rgba(255,107,107,0.1);border:1px solid rgba(255,107,107,0.3);border-radius:6px;color:#ff6b6b;font-size:0.85rem;">
|
||||||
{tr('ui.lastSyncError', { error: sanitizeError(settings.lastError) }, 'Last sync error: {error}')}
|
{tr('ui.lastSyncError', null, 'The last synchronization did not finish. Try again.')}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,17 @@
|
||||||
"ui.title": "Sync",
|
"ui.title": "Sync",
|
||||||
"ui.description": "Synchronize your vault across devices.",
|
"ui.description": "Synchronize your vault across devices.",
|
||||||
"ui.unknownError": "Unknown error",
|
"ui.unknownError": "Unknown error",
|
||||||
"ui.apiUnavailable": "Plugin API sync namespace not available",
|
"ui.apiUnavailable": "Synchronization is unavailable right now.",
|
||||||
"ui.intervalError": "Sync interval must be between 1 and 1440 minutes.",
|
"ui.intervalError": "Sync interval must be between 1 and 1440 minutes.",
|
||||||
"ui.settingsSaved": "Settings saved.",
|
"ui.settingsSaved": "Settings saved.",
|
||||||
"ui.serverRequired": "Server URL is required.",
|
"ui.serverRequired": "Server URL is required.",
|
||||||
"ui.connectionSuccessful": "Connection successful.",
|
"ui.connectionSuccessful": "Connection successful.",
|
||||||
"ui.connectionFailed": "Connection failed: {error}",
|
"ui.connectionFailed": "Could not connect. Check the server address and credentials.",
|
||||||
|
"ui.saveFailed": "Could not save sync settings. Please try again.",
|
||||||
|
"ui.connectFailed": "Could not connect this device. Please try again.",
|
||||||
|
"ui.syncFailed": "Could not synchronize. Please try again.",
|
||||||
|
"ui.disconnectFailed": "Could not disconnect from the server. Please try again.",
|
||||||
|
"ui.resetKeyFailed": "Could not reset the sync key. Please try again.",
|
||||||
"ui.connectedSuccessfully": "Connected successfully.",
|
"ui.connectedSuccessfully": "Connected successfully.",
|
||||||
"ui.conflictsCount": "{count} conflict(s)",
|
"ui.conflictsCount": "{count} conflict(s)",
|
||||||
"ui.errorsCount": "{count} error(s)",
|
"ui.errorsCount": "{count} error(s)",
|
||||||
|
|
@ -18,7 +23,8 @@
|
||||||
"ui.disconnected": "Disconnected from server.",
|
"ui.disconnected": "Disconnected from server.",
|
||||||
"ui.keyReset": "Sync key reset. Connect again to pair this device.",
|
"ui.keyReset": "Sync key reset. Connect again to pair this device.",
|
||||||
"ui.syncConflicts": "Sync conflicts",
|
"ui.syncConflicts": "Sync conflicts",
|
||||||
"ui.lastSyncError": "Last sync error: {error}",
|
"ui.lastSyncError": "The last synchronization did not finish. Try again.",
|
||||||
|
"ui.syncConflictItem": "A synchronization conflict needs attention.",
|
||||||
"ui.server": "Server",
|
"ui.server": "Server",
|
||||||
"ui.serverUrl": "Server URL",
|
"ui.serverUrl": "Server URL",
|
||||||
"ui.username": "Username",
|
"ui.username": "Username",
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,17 @@
|
||||||
"ui.title": "Синхронизация",
|
"ui.title": "Синхронизация",
|
||||||
"ui.description": "Синхронизируйте хранилище между устройствами.",
|
"ui.description": "Синхронизируйте хранилище между устройствами.",
|
||||||
"ui.unknownError": "Неизвестная ошибка",
|
"ui.unknownError": "Неизвестная ошибка",
|
||||||
"ui.apiUnavailable": "API синхронизации плагина недоступен",
|
"ui.apiUnavailable": "Синхронизация сейчас недоступна.",
|
||||||
"ui.intervalError": "Интервал синхронизации должен быть от 1 до 1440 минут.",
|
"ui.intervalError": "Интервал синхронизации должен быть от 1 до 1440 минут.",
|
||||||
"ui.settingsSaved": "Настройки сохранены.",
|
"ui.settingsSaved": "Настройки сохранены.",
|
||||||
"ui.serverRequired": "Укажите URL сервера.",
|
"ui.serverRequired": "Укажите URL сервера.",
|
||||||
"ui.connectionSuccessful": "Подключение успешно.",
|
"ui.connectionSuccessful": "Подключение успешно.",
|
||||||
"ui.connectionFailed": "Не удалось подключиться: {error}",
|
"ui.connectionFailed": "Не удалось подключиться. Проверьте адрес сервера и учётные данные.",
|
||||||
|
"ui.saveFailed": "Не удалось сохранить настройки синхронизации. Повторите попытку.",
|
||||||
|
"ui.connectFailed": "Не удалось подключить это устройство. Повторите попытку.",
|
||||||
|
"ui.syncFailed": "Не удалось синхронизировать данные. Повторите попытку.",
|
||||||
|
"ui.disconnectFailed": "Не удалось отключиться от сервера. Повторите попытку.",
|
||||||
|
"ui.resetKeyFailed": "Не удалось сбросить ключ синхронизации. Повторите попытку.",
|
||||||
"ui.connectedSuccessfully": "Подключение установлено.",
|
"ui.connectedSuccessfully": "Подключение установлено.",
|
||||||
"ui.conflictsCount": "Конфликтов: {count}",
|
"ui.conflictsCount": "Конфликтов: {count}",
|
||||||
"ui.errorsCount": "Ошибок: {count}",
|
"ui.errorsCount": "Ошибок: {count}",
|
||||||
|
|
@ -18,7 +23,8 @@
|
||||||
"ui.disconnected": "Сервер отключён.",
|
"ui.disconnected": "Сервер отключён.",
|
||||||
"ui.keyReset": "Ключ синхронизации сброшен. Подключитесь снова, чтобы связать устройство.",
|
"ui.keyReset": "Ключ синхронизации сброшен. Подключитесь снова, чтобы связать устройство.",
|
||||||
"ui.syncConflicts": "Конфликты синхронизации",
|
"ui.syncConflicts": "Конфликты синхронизации",
|
||||||
"ui.lastSyncError": "Ошибка последней синхронизации: {error}",
|
"ui.lastSyncError": "Последняя синхронизация не завершилась. Повторите попытку.",
|
||||||
|
"ui.syncConflictItem": "Конфликт синхронизации требует внимания.",
|
||||||
"ui.server": "Сервер",
|
"ui.server": "Сервер",
|
||||||
"ui.serverUrl": "URL сервера",
|
"ui.serverUrl": "URL сервера",
|
||||||
"ui.username": "Имя пользователя",
|
"ui.username": "Имя пользователя",
|
||||||
|
|
|
||||||
|
|
@ -402,11 +402,18 @@
|
||||||
}).filter(function (item) { return item !== null; });
|
}).filter(function (item) { return item !== null; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportError(key, fallback, err) {
|
||||||
|
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
||||||
|
console.warn('[verstak.todo] ' + key, err);
|
||||||
|
}
|
||||||
|
statusText = tr(key, null, fallback);
|
||||||
|
statusClass = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
function syncNotifications() {
|
function syncNotifications() {
|
||||||
if (!api || !api.notifications || typeof api.notifications.replace !== 'function') return Promise.resolve();
|
if (!api || !api.notifications || typeof api.notifications.replace !== 'function') return Promise.resolve();
|
||||||
return api.notifications.replace(notificationRequests()).catch(function (err) {
|
return api.notifications.replace(notificationRequests()).catch(function (err) {
|
||||||
statusText = tr('ui.notificationError', { error: err && err.message ? err.message : String(err) }, 'Could not schedule reminders: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.notificationError', 'Could not schedule reminders. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,8 +422,7 @@
|
||||||
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).then(function () {
|
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).then(function () {
|
||||||
return syncNotifications();
|
return syncNotifications();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = tr('ui.saveError', { error: err && err.message ? err.message : String(err) }, 'Could not save todos: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.saveError', 'Could not save tasks. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,8 +432,7 @@
|
||||||
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
|
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
|
||||||
return syncNotifications();
|
return syncNotifications();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
statusText = tr('ui.loadError', { error: err && err.message ? err.message : String(err) }, 'Could not load todos: ' + (err && err.message ? err.message : String(err)));
|
reportError('ui.loadError', 'Could not load tasks. Please try again.', err);
|
||||||
statusClass = 'error';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
"ui.add": "Add Todo",
|
"ui.add": "Add Todo",
|
||||||
"ui.allWorkspaces": "All workspaces",
|
"ui.allWorkspaces": "All workspaces",
|
||||||
"ui.unassigned": "Unassigned",
|
"ui.unassigned": "Unassigned",
|
||||||
"ui.saveError": "Could not save todos: {error}",
|
"ui.saveError": "Could not save tasks. Please try again.",
|
||||||
"ui.loadError": "Could not load todos: {error}",
|
"ui.loadError": "Could not load tasks. Please try again.",
|
||||||
"ui.notificationError": "Could not schedule reminders: {error}",
|
"ui.notificationError": "Could not schedule reminders. Please try again.",
|
||||||
"ui.notificationTitle": "Todo reminder",
|
"ui.notificationTitle": "Todo reminder",
|
||||||
"ui.notificationBody": "{title}",
|
"ui.notificationBody": "{title}",
|
||||||
"ui.titlePlaceholder": "Todo title",
|
"ui.titlePlaceholder": "Todo title",
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@
|
||||||
"ui.add": "Добавить задачу",
|
"ui.add": "Добавить задачу",
|
||||||
"ui.allWorkspaces": "Все рабочие пространства",
|
"ui.allWorkspaces": "Все рабочие пространства",
|
||||||
"ui.unassigned": "Не назначено",
|
"ui.unassigned": "Не назначено",
|
||||||
"ui.saveError": "Не удалось сохранить задачи: {error}",
|
"ui.saveError": "Не удалось сохранить задачи. Повторите попытку.",
|
||||||
"ui.loadError": "Не удалось загрузить задачи: {error}",
|
"ui.loadError": "Не удалось загрузить задачи. Повторите попытку.",
|
||||||
"ui.notificationError": "Не удалось запланировать напоминания: {error}",
|
"ui.notificationError": "Не удалось запланировать напоминания. Повторите попытку.",
|
||||||
"ui.notificationTitle": "Напоминание о задаче",
|
"ui.notificationTitle": "Напоминание о задаче",
|
||||||
"ui.notificationBody": "{title}",
|
"ui.notificationBody": "{title}",
|
||||||
"ui.titlePlaceholder": "Название задачи",
|
"ui.titlePlaceholder": "Название задачи",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
function frontendSources(dir) {
|
||||||
|
if (!fs.existsSync(dir)) return [];
|
||||||
|
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||||
|
const target = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) return frontendSources(target);
|
||||||
|
return /\.(?:js|svelte)$/.test(entry.name) ? [target] : [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(path.join(root, 'plugins'))
|
||||||
|
.flatMap((plugin) => frontendSources(path.join(root, 'plugins', plugin, 'frontend', 'src')));
|
||||||
|
|
||||||
|
const renderPatterns = [
|
||||||
|
/statusText\s*=/,
|
||||||
|
/state\.error\s*=/,
|
||||||
|
/errorMsg\s*=/,
|
||||||
|
/setStatus\(/,
|
||||||
|
/setCreateError\(/,
|
||||||
|
/setRenameError\(/,
|
||||||
|
/renderEmpty\(/,
|
||||||
|
/output\.errors\.push\(/,
|
||||||
|
/window\.alert\(/,
|
||||||
|
/showExternalFallback\(/,
|
||||||
|
/de-error-msg/,
|
||||||
|
/files-error-msg/,
|
||||||
|
/body\.textContent\s*=/
|
||||||
|
];
|
||||||
|
|
||||||
|
const violations = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
||||||
|
lines.forEach((line, index) => {
|
||||||
|
if (!/(?:err(?:or)?\.message|String\(err(?:or)?\b)/.test(line)) return;
|
||||||
|
if (!renderPatterns.some((pattern) => pattern.test(line))) return;
|
||||||
|
violations.push(`${path.relative(root, file)}:${index + 1}: technical error text reaches a user-facing UI sink`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (violations.length) {
|
||||||
|
console.error('User-facing UI must not render raw backend/plugin errors:');
|
||||||
|
violations.forEach((violation) => console.error(` ${violation}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('user-facing error messages do not expose raw backend details');
|
||||||
|
|
@ -101,6 +101,18 @@ else
|
||||||
echo " ⚠️ node not available — skipping select style validation"
|
echo " ⚠️ node not available — skipping select style validation"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[user-facing errors]"
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
set +e
|
||||||
|
node "$ROOT/scripts/check-user-facing-errors.js"
|
||||||
|
STATUS=$?
|
||||||
|
set -e
|
||||||
|
report "user-facing errors" "$STATUS"
|
||||||
|
else
|
||||||
|
echo " ⚠️ node not available — skipping user-facing error validation"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
# Guard official plugins against bypassing the v2 plugin API for note features.
|
# Guard official plugins against bypassing the v2 plugin API for note features.
|
||||||
echo "[frontend API boundary]"
|
echo "[frontend API boundary]"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ 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 technicalErrors = [];
|
||||||
|
|
||||||
class FakeNode {
|
class FakeNode {
|
||||||
constructor(tagName) {
|
constructor(tagName) {
|
||||||
|
|
@ -113,7 +114,15 @@ function makeDocument() {
|
||||||
function loadComponents(document) {
|
function loadComponents(document) {
|
||||||
const registry = {};
|
const registry = {};
|
||||||
const sandbox = {
|
const sandbox = {
|
||||||
console,
|
console: {
|
||||||
|
...console,
|
||||||
|
warn(...args) {
|
||||||
|
technicalErrors.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
|
error(...args) {
|
||||||
|
technicalErrors.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
|
},
|
||||||
Date,
|
Date,
|
||||||
document,
|
document,
|
||||||
window: {
|
window: {
|
||||||
|
|
@ -738,9 +747,12 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (!failedConversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-conflict')) {
|
if (!failedConversionApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-conflict')) {
|
||||||
throw new Error('failed conversion removed capture from queue');
|
throw new Error('failed conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedConversionView.container.textContent.includes('Could not create note')) {
|
if (!failedConversionView.container.textContent.includes('Could not create the note. Please try again.')) {
|
||||||
throw new Error('failed conversion did not render an error status');
|
throw new Error('failed conversion did not render an error status');
|
||||||
}
|
}
|
||||||
|
if (failedConversionView.container.textContent.includes('file already exists')) {
|
||||||
|
throw new Error('failed conversion exposed a raw backend error');
|
||||||
|
}
|
||||||
if (failedConversionApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
if (failedConversionApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
||||||
throw new Error('failed conversion published converted event');
|
throw new Error('failed conversion published converted event');
|
||||||
}
|
}
|
||||||
|
|
@ -827,7 +839,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (!failedLinkApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-link-conflict')) {
|
if (!failedLinkApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-link-conflict')) {
|
||||||
throw new Error('failed link conversion removed capture from queue');
|
throw new Error('failed link conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedLinkView.container.textContent.includes('Could not create link')) {
|
if (!failedLinkView.container.textContent.includes('Could not create the link. Please try again.')) {
|
||||||
throw new Error('failed link conversion did not render an error status');
|
throw new Error('failed link conversion did not render an error status');
|
||||||
}
|
}
|
||||||
if (failedLinkApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
if (failedLinkApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
||||||
|
|
@ -934,7 +946,7 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
if (!failedFileApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-file-conflict')) {
|
if (!failedFileApi.getStoredCaptures(globalKey).some((capture) => capture.captureId === 'convert-file-conflict')) {
|
||||||
throw new Error('failed file conversion removed capture from queue');
|
throw new Error('failed file conversion removed capture from queue');
|
||||||
}
|
}
|
||||||
if (!failedFileView.container.textContent.includes('Could not create file')) {
|
if (!failedFileView.container.textContent.includes('Could not create the file. Please try again.')) {
|
||||||
throw new Error('failed file conversion did not render an error status');
|
throw new Error('failed file conversion did not render an error status');
|
||||||
}
|
}
|
||||||
if (failedFileApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
if (failedFileApi.publishedEvents.some((event) => event.name === 'browser.capture.converted')) {
|
||||||
|
|
@ -942,6 +954,10 @@ async function mountSettingsWithApi(api, document = makeDocument()) {
|
||||||
}
|
}
|
||||||
component.unmount && component.unmount(failedFileView.container);
|
component.unmount && component.unmount(failedFileView.container);
|
||||||
|
|
||||||
|
if (!technicalErrors.some((entry) => entry.includes('file already exists'))) {
|
||||||
|
throw new Error('failed conversion did not retain its technical details in the console log');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('browser inbox plugin smoke passed');
|
console.log('browser inbox plugin smoke passed');
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const sourcePath = path.join(root, 'plugins', 'search', 'frontend', 'src', 'inde
|
||||||
const manifestPath = path.join(root, 'plugins', 'search', 'plugin.json');
|
const manifestPath = path.join(root, 'plugins', 'search', 'plugin.json');
|
||||||
const source = fs.readFileSync(sourcePath, 'utf8');
|
const source = fs.readFileSync(sourcePath, 'utf8');
|
||||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||||
|
const technicalErrors = [];
|
||||||
|
|
||||||
class FakeNode {
|
class FakeNode {
|
||||||
constructor(tagName) {
|
constructor(tagName) {
|
||||||
|
|
@ -100,7 +101,15 @@ function makeDocument() {
|
||||||
function loadComponent(document) {
|
function loadComponent(document) {
|
||||||
const registry = {};
|
const registry = {};
|
||||||
vm.runInNewContext(source, {
|
vm.runInNewContext(source, {
|
||||||
console,
|
console: {
|
||||||
|
...console,
|
||||||
|
warn(...args) {
|
||||||
|
technicalErrors.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
|
error(...args) {
|
||||||
|
technicalErrors.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
|
},
|
||||||
document,
|
document,
|
||||||
window: {
|
window: {
|
||||||
VerstakPluginRegister(pluginId, bundle) {
|
VerstakPluginRegister(pluginId, bundle) {
|
||||||
|
|
@ -253,11 +262,13 @@ async function wait(ms) {
|
||||||
if (!container.textContent.includes('Project/Target Assets')) throw new Error('typing should search folder paths');
|
if (!container.textContent.includes('Project/Target Assets')) throw new Error('typing should search folder paths');
|
||||||
if (!container.textContent.includes('Project/External/target.note')) throw new Error('external provider result should be rendered');
|
if (!container.textContent.includes('Project/External/target.note')) throw new Error('external provider result should be rendered');
|
||||||
if (!container.textContent.includes('External Notes')) throw new Error('external provider label should be rendered');
|
if (!container.textContent.includes('External Notes')) throw new Error('external provider label should be rendered');
|
||||||
if (!container.textContent.includes('provider unavailable')) throw new Error('provider failure should be reported without failing search');
|
if (!container.textContent.includes('A search provider is unavailable.')) throw new Error('provider failure should be reported without failing search');
|
||||||
|
if (container.textContent.includes('provider unavailable')) throw new Error('provider failure leaked a raw backend error');
|
||||||
if (!container.textContent.includes('Content match')) throw new Error('content result type was not rendered');
|
if (!container.textContent.includes('Content match')) throw new Error('content result type was not rendered');
|
||||||
if (!container.textContent.includes('Folder name')) throw new Error('folder result type was not rendered');
|
if (!container.textContent.includes('Folder name')) throw new Error('folder result type was not rendered');
|
||||||
if (!pluginData['search-index'] || !Array.isArray(pluginData['search-index'].files)) throw new Error('search index was not written to plugin data storage');
|
if (!pluginData['search-index'] || !Array.isArray(pluginData['search-index'].files)) throw new Error('search index was not written to plugin data storage');
|
||||||
if (providerCalls.some((call) => call.pluginId === 'verstak.search')) throw new Error('search must not call itself as an external provider');
|
if (providerCalls.some((call) => call.pluginId === 'verstak.search')) throw new Error('search must not call itself as an external provider');
|
||||||
|
if (!technicalErrors.some((entry) => entry.includes('provider unavailable'))) throw new Error('provider failure was not retained in the console log');
|
||||||
|
|
||||||
input = queryInput();
|
input = queryInput();
|
||||||
input.value = 'image';
|
input.value = 'image';
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ function loadComponent(document, errorLog) {
|
||||||
vm.runInNewContext(source, {
|
vm.runInNewContext(source, {
|
||||||
console: {
|
console: {
|
||||||
...console,
|
...console,
|
||||||
|
warn(...args) {
|
||||||
|
errorLog.push(args.map((value) => String(value)).join(' '));
|
||||||
|
},
|
||||||
error(...args) {
|
error(...args) {
|
||||||
errorLog.push(args.map((value) => String(value)).join(' '));
|
errorLog.push(args.map((value) => String(value)).join(' '));
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,17 @@ const source = fs.readFileSync(sourcePath, 'utf8');
|
||||||
if (!source.includes('settings.lastError')) {
|
if (!source.includes('settings.lastError')) {
|
||||||
throw new Error('SyncSettings must render persisted settings.lastError');
|
throw new Error('SyncSettings must render persisted settings.lastError');
|
||||||
}
|
}
|
||||||
if (!source.includes('sanitizeError(settings.lastError)')) {
|
if (!source.includes('function reportError')) {
|
||||||
throw new Error('SyncSettings must sanitize persisted sync errors before rendering');
|
throw new Error('SyncSettings must map technical failures to localized action-specific errors');
|
||||||
}
|
}
|
||||||
if (!source.includes('Last sync error')) {
|
if (/sanitizeError\(/.test(source) || /\{ error: sanitizeError/.test(source)) {
|
||||||
|
throw new Error('SyncSettings must not interpolate raw technical errors into user-facing messages');
|
||||||
|
}
|
||||||
|
if (!source.includes("tr('ui.lastSyncError'")) {
|
||||||
throw new Error('SyncSettings must label the persisted sync error');
|
throw new Error('SyncSettings must label the persisted sync error');
|
||||||
}
|
}
|
||||||
if (!source.includes('function formatSyncConflict')) {
|
if (!source.includes("tr('ui.syncConflictItem'")) {
|
||||||
throw new Error('SyncSettings must format individual sync conflicts');
|
throw new Error('SyncSettings must hide technical conflict identifiers behind a user-facing summary');
|
||||||
}
|
}
|
||||||
if (!source.includes('conflictDetails')) {
|
if (!source.includes('conflictDetails')) {
|
||||||
throw new Error('SyncSettings must store sync conflict details after Sync Now');
|
throw new Error('SyncSettings must store sync conflict details after Sync Now');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue