feat: add browser extension language selector
This commit is contained in:
parent
766458d0a0
commit
cae140a348
|
|
@ -20,6 +20,10 @@ class Element {
|
||||||
click() {
|
click() {
|
||||||
if (this.listeners.click) this.listeners.click();
|
if (this.listeners.click) this.listeners.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
change() {
|
||||||
|
if (this.listeners.change) this.listeners.change({ target: this });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = {};
|
const elements = {};
|
||||||
|
|
@ -32,10 +36,23 @@ const elements = {};
|
||||||
'file-input',
|
'file-input',
|
||||||
'pending-count',
|
'pending-count',
|
||||||
'status-dot',
|
'status-dot',
|
||||||
|
'subtitle',
|
||||||
|
'receiver-label',
|
||||||
|
'pending-label',
|
||||||
|
'url-label',
|
||||||
'capture-page',
|
'capture-page',
|
||||||
'capture-file',
|
'capture-file',
|
||||||
'retry',
|
'retry',
|
||||||
|
'file-label',
|
||||||
|
'receiver-url-label',
|
||||||
|
'receiver-token-label',
|
||||||
|
'language-label',
|
||||||
|
'language-select',
|
||||||
|
'language-system-option',
|
||||||
|
'language-en-option',
|
||||||
|
'language-ru-option',
|
||||||
'save-settings',
|
'save-settings',
|
||||||
|
'context-menu-hint',
|
||||||
].forEach((id) => {
|
].forEach((id) => {
|
||||||
elements[id] = new Element();
|
elements[id] = new Element();
|
||||||
});
|
});
|
||||||
|
|
@ -45,12 +62,14 @@ const initialState = {
|
||||||
settings: {
|
settings: {
|
||||||
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
|
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
|
||||||
receiverToken: 'persisted-token',
|
receiverToken: 'persisted-token',
|
||||||
|
language: 'system',
|
||||||
},
|
},
|
||||||
pendingCount: 0,
|
pendingCount: 0,
|
||||||
status: {},
|
status: {},
|
||||||
};
|
};
|
||||||
const browser = {
|
const browser = {
|
||||||
runtime: {
|
runtime: {
|
||||||
|
getURL(relativePath) { return `extension://${relativePath}`; },
|
||||||
sendMessage(message) {
|
sendMessage(message) {
|
||||||
if (message.action === 'getState') return Promise.resolve(initialState);
|
if (message.action === 'getState') return Promise.resolve(initialState);
|
||||||
if (message.action === 'saveSettings') {
|
if (message.action === 'saveSettings') {
|
||||||
|
|
@ -60,24 +79,56 @@ const browser = {
|
||||||
return Promise.resolve(initialState);
|
return Promise.resolve(initialState);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
i18n: { getUILanguage() { return 'ru-RU'; } },
|
||||||
};
|
};
|
||||||
const document = {
|
const document = {
|
||||||
activeElement: null,
|
activeElement: null,
|
||||||
|
documentElement: { lang: '' },
|
||||||
getElementById(id) {
|
getElementById(id) {
|
||||||
return elements[id];
|
return elements[id];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const catalogs = {
|
||||||
|
en: JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'shared', 'locales', 'en.json'), 'utf8')),
|
||||||
|
ru: JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'shared', 'locales', 'ru.json'), 'utf8')),
|
||||||
|
};
|
||||||
|
function fetchCatalog(url) {
|
||||||
|
const locale = /\/ru\.json$/.test(url) ? 'ru' : 'en';
|
||||||
|
return Promise.resolve({ ok: true, json: () => Promise.resolve(catalogs[locale]) });
|
||||||
|
}
|
||||||
|
const i18nPath = path.join(__dirname, '..', 'shared', 'i18n.js');
|
||||||
const popupPath = path.join(__dirname, '..', 'shared', 'popup', 'popup.js');
|
const popupPath = path.join(__dirname, '..', 'shared', 'popup', 'popup.js');
|
||||||
vm.runInNewContext(fs.readFileSync(popupPath, 'utf8'), { browser, console, document, Promise, btoa });
|
const context = vm.createContext({ browser, console, document, Promise, btoa, fetch: fetchCatalog, navigator: { language: 'en-US' } });
|
||||||
|
context.globalThis = context;
|
||||||
|
vm.runInContext(fs.readFileSync(i18nPath, 'utf8'), context, { filename: i18nPath });
|
||||||
|
vm.runInContext(fs.readFileSync(popupPath, 'utf8'), context, { filename: popupPath });
|
||||||
|
|
||||||
async function flush() {
|
async function flush() {
|
||||||
for (let i = 0; i < 4; i += 1) await Promise.resolve();
|
for (let i = 0; i < 16; i += 1) await Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await flush();
|
await flush();
|
||||||
assert.strictEqual(elements['receiver-input'].value, initialState.settings.receiverUrl);
|
assert.strictEqual(elements['receiver-input'].value, initialState.settings.receiverUrl);
|
||||||
assert.strictEqual(elements['receiver-token-input'].value, initialState.settings.receiverToken);
|
assert.strictEqual(elements['receiver-token-input'].value, initialState.settings.receiverToken);
|
||||||
|
assert.strictEqual(elements['language-select'].value, 'system');
|
||||||
|
assert.strictEqual(elements['capture-page'].textContent, 'Отправить страницу');
|
||||||
|
assert.strictEqual(elements['receiver-state'].textContent, 'Неизвестно');
|
||||||
|
assert.strictEqual(document.documentElement.lang, 'ru');
|
||||||
|
elements['capture-file'].click();
|
||||||
|
assert.strictEqual(elements.status.textContent, 'Сначала выберите файл');
|
||||||
|
|
||||||
|
elements['language-select'].value = 'en';
|
||||||
|
elements['language-select'].change();
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
assert.strictEqual(elements['capture-page'].textContent, 'Send Page');
|
||||||
|
assert.strictEqual(elements['receiver-state'].textContent, 'Unknown');
|
||||||
|
assert.strictEqual(document.documentElement.lang, 'en');
|
||||||
|
assert.ok(savedSettings);
|
||||||
|
assert.strictEqual(savedSettings.language, 'en');
|
||||||
|
assert.strictEqual(savedSettings.receiverUrl, initialState.settings.receiverUrl);
|
||||||
|
assert.strictEqual(savedSettings.receiverToken, initialState.settings.receiverToken);
|
||||||
|
|
||||||
elements['receiver-input'].value = 'http://127.0.0.1:47731/api/browser-inbox/v1/captures';
|
elements['receiver-input'].value = 'http://127.0.0.1:47731/api/browser-inbox/v1/captures';
|
||||||
elements['receiver-token-input'].value = 'new-token';
|
elements['receiver-token-input'].value = 'new-token';
|
||||||
|
|
@ -87,7 +138,8 @@ async function flush() {
|
||||||
assert.ok(savedSettings);
|
assert.ok(savedSettings);
|
||||||
assert.strictEqual(savedSettings.receiverUrl, 'http://127.0.0.1:47731/api/browser-inbox/v1/captures');
|
assert.strictEqual(savedSettings.receiverUrl, 'http://127.0.0.1:47731/api/browser-inbox/v1/captures');
|
||||||
assert.strictEqual(savedSettings.receiverToken, 'new-token');
|
assert.strictEqual(savedSettings.receiverToken, 'new-token');
|
||||||
console.log('browser extension popup settings tests passed');
|
assert.strictEqual(savedSettings.language, 'en');
|
||||||
|
console.log('browser extension popup localization/settings tests passed');
|
||||||
})().catch((error) => {
|
})().catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,17 @@ input {
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
border: 1px solid #374151;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="ru">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
@ -17,15 +17,15 @@
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span>Receiver</span>
|
<span id="receiver-label">Receiver</span>
|
||||||
<strong id="receiver-state">Unknown</strong>
|
<strong id="receiver-state">Unknown</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span>Pending</span>
|
<span id="pending-label">Pending</span>
|
||||||
<strong id="pending-count">0</strong>
|
<strong id="pending-count">0</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="row url-row">
|
<div class="row url-row">
|
||||||
<span>URL</span>
|
<span id="url-label">URL</span>
|
||||||
<code id="receiver-url"></code>
|
<code id="receiver-url"></code>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -37,21 +37,28 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="file-picker">
|
<section class="file-picker">
|
||||||
<label for="file-input">File</label>
|
<label id="file-label" for="file-input">File</label>
|
||||||
<input id="file-input" type="file">
|
<input id="file-input" type="file">
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="settings">
|
<section class="settings">
|
||||||
<label for="receiver-input">Receiver URL</label>
|
<label id="receiver-url-label" for="receiver-input">Receiver URL</label>
|
||||||
<input id="receiver-input" type="url" spellcheck="false">
|
<input id="receiver-input" type="url" spellcheck="false">
|
||||||
<label for="receiver-token-input">Pairing token</label>
|
<label id="receiver-token-label" for="receiver-token-input">Pairing token</label>
|
||||||
<input id="receiver-token-input" type="password" spellcheck="false" autocomplete="off">
|
<input id="receiver-token-input" type="password" spellcheck="false" autocomplete="off">
|
||||||
|
<label id="language-label" for="language-select">Language</label>
|
||||||
|
<select id="language-select">
|
||||||
|
<option id="language-system-option" value="system">System</option>
|
||||||
|
<option id="language-en-option" value="en">English</option>
|
||||||
|
<option id="language-ru-option" value="ru">Русский</option>
|
||||||
|
</select>
|
||||||
<button id="save-settings" class="secondary">Save</button>
|
<button id="save-settings" class="secondary">Save</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p class="hint">Selection and link captures are available from the page context menu.</p>
|
<p id="context-menu-hint" class="hint">Selection and link captures are available from the page context menu.</p>
|
||||||
<p id="status"></p>
|
<p id="status"></p>
|
||||||
</main>
|
</main>
|
||||||
|
<script src="../i18n.js"></script>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,90 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var ext = typeof browser !== 'undefined' ? browser : chrome;
|
var ext = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
|
var i18n = globalThis.VerstakBrowserI18n;
|
||||||
var statusEl = document.getElementById('status');
|
var statusEl = document.getElementById('status');
|
||||||
var receiverStateEl = document.getElementById('receiver-state');
|
var receiverStateEl = document.getElementById('receiver-state');
|
||||||
var receiverUrlEl = document.getElementById('receiver-url');
|
var receiverUrlEl = document.getElementById('receiver-url');
|
||||||
var receiverInputEl = document.getElementById('receiver-input');
|
var receiverInputEl = document.getElementById('receiver-input');
|
||||||
var receiverTokenInputEl = document.getElementById('receiver-token-input');
|
var receiverTokenInputEl = document.getElementById('receiver-token-input');
|
||||||
|
var languageSelectEl = document.getElementById('language-select');
|
||||||
var fileInputEl = document.getElementById('file-input');
|
var fileInputEl = document.getElementById('file-input');
|
||||||
var pendingCountEl = document.getElementById('pending-count');
|
var pendingCountEl = document.getElementById('pending-count');
|
||||||
var statusDotEl = document.getElementById('status-dot');
|
var statusDotEl = document.getElementById('status-dot');
|
||||||
var MAX_FILE_TEXT_LENGTH = 2 * 1024 * 1024;
|
var MAX_FILE_TEXT_LENGTH = 2 * 1024 * 1024;
|
||||||
var MAX_FILE_BYTES = 8 * 1024 * 1024;
|
var MAX_FILE_BYTES = 8 * 1024 * 1024;
|
||||||
|
var catalogs = { en: {}, ru: {} };
|
||||||
|
var currentPreference = 'system';
|
||||||
|
var currentState = {};
|
||||||
|
var t = i18n.createTranslator(catalogs, 'en');
|
||||||
|
|
||||||
|
var staticText = {
|
||||||
|
subtitle: 'popup.subtitle',
|
||||||
|
'receiver-label': 'label.receiver',
|
||||||
|
'pending-label': 'label.pending',
|
||||||
|
'url-label': 'label.url',
|
||||||
|
'file-label': 'label.file',
|
||||||
|
'receiver-url-label': 'label.receiverUrl',
|
||||||
|
'receiver-token-label': 'label.pairingToken',
|
||||||
|
'language-label': 'label.language',
|
||||||
|
'capture-page': 'action.sendPage',
|
||||||
|
'capture-file': 'action.sendFile',
|
||||||
|
retry: 'action.retryPending',
|
||||||
|
'save-settings': 'action.save',
|
||||||
|
'context-menu-hint': 'hint.contextMenu',
|
||||||
|
'language-system-option': 'language.system',
|
||||||
|
'language-en-option': 'language.en',
|
||||||
|
'language-ru-option': 'language.ru'
|
||||||
|
};
|
||||||
|
|
||||||
|
function browserLocale() {
|
||||||
|
try {
|
||||||
|
if (ext.i18n && ext.i18n.getUILanguage) return ext.i18n.getUILanguage();
|
||||||
|
} catch (_) {}
|
||||||
|
return typeof navigator !== 'undefined' ? navigator.language : 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCatalogs() {
|
||||||
|
return i18n.loadCatalogs(function (locale) {
|
||||||
|
return fetch(ext.runtime.getURL('locales/' + locale + '.json')).then(function (response) {
|
||||||
|
if (!response.ok) throw new Error('catalog load failed: ' + locale);
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.warn('[verstak] localization catalogs unavailable:', error);
|
||||||
|
return { en: {}, ru: {} };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyReceiverState(state) {
|
||||||
|
var reachable = state && state.status && state.status.receiverReachable;
|
||||||
|
if (reachable === true) {
|
||||||
|
receiverStateEl.textContent = t('receiver.online', null, 'Online');
|
||||||
|
receiverStateEl.className = 'online';
|
||||||
|
statusDotEl.className = 'dot online';
|
||||||
|
} else if (reachable === false) {
|
||||||
|
receiverStateEl.textContent = t('receiver.offline', null, 'Offline');
|
||||||
|
receiverStateEl.className = 'offline';
|
||||||
|
statusDotEl.className = 'dot offline';
|
||||||
|
} else {
|
||||||
|
receiverStateEl.textContent = t('receiver.unknown', null, 'Unknown');
|
||||||
|
receiverStateEl.className = '';
|
||||||
|
statusDotEl.className = 'dot unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyLocale(preference) {
|
||||||
|
currentPreference = i18n.normalizePreference(preference);
|
||||||
|
var locale = i18n.resolveLocale(currentPreference, browserLocale());
|
||||||
|
t = i18n.createTranslator(catalogs, locale);
|
||||||
|
document.documentElement.lang = locale;
|
||||||
|
languageSelectEl.value = currentPreference;
|
||||||
|
Object.keys(staticText).forEach(function (id) {
|
||||||
|
var element = document.getElementById(id);
|
||||||
|
if (element) element.textContent = t(staticText[id]);
|
||||||
|
});
|
||||||
|
applyReceiverState(currentState);
|
||||||
|
}
|
||||||
|
|
||||||
function arrayBufferToBase64(buffer) {
|
function arrayBufferToBase64(buffer) {
|
||||||
var bytes = new Uint8Array(buffer);
|
var bytes = new Uint8Array(buffer);
|
||||||
|
|
@ -42,48 +116,58 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(state) {
|
function render(state) {
|
||||||
state = state || {};
|
currentState = state || {};
|
||||||
var settings = state.settings || {};
|
var settings = currentState.settings || {};
|
||||||
var status = state.status || {};
|
pendingCountEl.textContent = String(currentState.pendingCount || 0);
|
||||||
var reachable = status.receiverReachable;
|
|
||||||
pendingCountEl.textContent = String(state.pendingCount || 0);
|
|
||||||
receiverUrlEl.textContent = settings.receiverUrl || '';
|
receiverUrlEl.textContent = settings.receiverUrl || '';
|
||||||
if (document.activeElement !== receiverInputEl) {
|
if (document.activeElement !== receiverInputEl) receiverInputEl.value = settings.receiverUrl || '';
|
||||||
receiverInputEl.value = settings.receiverUrl || '';
|
if (document.activeElement !== receiverTokenInputEl) receiverTokenInputEl.value = settings.receiverToken || '';
|
||||||
}
|
applyReceiverState(currentState);
|
||||||
if (document.activeElement !== receiverTokenInputEl) {
|
|
||||||
receiverTokenInputEl.value = settings.receiverToken || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reachable === true) {
|
|
||||||
receiverStateEl.textContent = 'Online';
|
|
||||||
receiverStateEl.className = 'online';
|
|
||||||
statusDotEl.className = 'dot online';
|
|
||||||
} else if (reachable === false) {
|
|
||||||
receiverStateEl.textContent = 'Offline';
|
|
||||||
receiverStateEl.className = 'offline';
|
|
||||||
statusDotEl.className = 'dot offline';
|
|
||||||
} else {
|
|
||||||
receiverStateEl.textContent = 'Unknown';
|
|
||||||
receiverStateEl.className = '';
|
|
||||||
statusDotEl.className = 'dot unknown';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
return request({ type: 'verstak.capture', action: 'getState' }).then(render).catch(function (err) {
|
return request({ type: 'verstak.capture', action: 'getState' }).then(function (state) {
|
||||||
setStatus(err && err.message ? err.message : String(err));
|
render(state);
|
||||||
|
applyLocale(state.settings && state.settings.language || 'system');
|
||||||
|
return state;
|
||||||
|
}).catch(function (error) {
|
||||||
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentSettings() {
|
||||||
|
return {
|
||||||
|
receiverUrl: receiverInputEl.value.trim(),
|
||||||
|
receiverToken: receiverTokenInputEl.value.trim(),
|
||||||
|
language: currentPreference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCurrentSettings(successMessage) {
|
||||||
|
return request({
|
||||||
|
type: 'verstak.capture',
|
||||||
|
action: 'saveSettings',
|
||||||
|
settings: currentSettings()
|
||||||
|
}).then(function (state) {
|
||||||
|
render(state);
|
||||||
|
if (successMessage) setStatus(t('status.saved', null, 'Saved'));
|
||||||
|
return state;
|
||||||
|
}).catch(function (error) {
|
||||||
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(message) {
|
function send(message) {
|
||||||
setStatus('Sending...');
|
setStatus(t('status.sending', null, 'Sending...'));
|
||||||
request(message).then(function (state) {
|
request(message).then(function (state) {
|
||||||
render(state);
|
render(state);
|
||||||
if (state.status && state.status.lastResult === 'queued') setStatus('Queued until Verstak is available');
|
if (state.status && state.status.lastResult === 'queued') {
|
||||||
else setStatus('Done');
|
setStatus(t('status.queued', null, 'Queued until Verstak is available'));
|
||||||
}).catch(function (err) {
|
} else {
|
||||||
setStatus(err && err.message ? err.message : String(err));
|
setStatus(t('status.done', null, 'Done'));
|
||||||
|
}
|
||||||
|
}).catch(function (error) {
|
||||||
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,27 +178,26 @@
|
||||||
document.getElementById('capture-file').addEventListener('click', function () {
|
document.getElementById('capture-file').addEventListener('click', function () {
|
||||||
var file = fileInputEl.files && fileInputEl.files[0];
|
var file = fileInputEl.files && fileInputEl.files[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
setStatus('Choose a text file first');
|
setStatus(t('error.chooseFile', null, 'Choose a file first'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.size > MAX_FILE_BYTES) {
|
if (file.size > MAX_FILE_BYTES) {
|
||||||
setStatus('File is too large for browser capture');
|
setStatus(t('error.fileTooLarge', null, 'File is too large for browser capture'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setStatus('Reading file...');
|
setStatus(t('status.readingFile', null, 'Reading file...'));
|
||||||
Promise.all([file.arrayBuffer(), readOptionalText(file)]).then(function (results) {
|
Promise.all([file.arrayBuffer(), readOptionalText(file)]).then(function (results) {
|
||||||
var content = results[1] || '';
|
|
||||||
send({
|
send({
|
||||||
type: 'verstak.capture',
|
type: 'verstak.capture',
|
||||||
kind: 'file',
|
kind: 'file',
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
fileMime: file.type || '',
|
fileMime: file.type || '',
|
||||||
fileSize: file.size,
|
fileSize: file.size,
|
||||||
fileText: content,
|
fileText: results[1] || '',
|
||||||
fileDataBase64: arrayBufferToBase64(results[0])
|
fileDataBase64: arrayBufferToBase64(results[0])
|
||||||
});
|
});
|
||||||
}).catch(function (err) {
|
}).catch(function (error) {
|
||||||
setStatus(err && err.message ? err.message : String(err));
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,24 +205,22 @@
|
||||||
send({ type: 'verstak.capture', action: 'retryPending' });
|
send({ type: 'verstak.capture', action: 'retryPending' });
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('save-settings').addEventListener('click', function () {
|
languageSelectEl.addEventListener('change', function () {
|
||||||
var receiverUrl = receiverInputEl.value.trim();
|
applyLocale(languageSelectEl.value);
|
||||||
var receiverToken = receiverTokenInputEl.value.trim();
|
saveCurrentSettings(true);
|
||||||
if (!/^https?:\/\//.test(receiverUrl)) {
|
|
||||||
setStatus('Receiver URL must start with http:// or https://');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
request({
|
|
||||||
type: 'verstak.capture',
|
|
||||||
action: 'saveSettings',
|
|
||||||
settings: { receiverUrl: receiverUrl, receiverToken: receiverToken }
|
|
||||||
}).then(function (state) {
|
|
||||||
render(state);
|
|
||||||
setStatus('Saved');
|
|
||||||
}).catch(function (err) {
|
|
||||||
setStatus(err && err.message ? err.message : String(err));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
refresh();
|
document.getElementById('save-settings').addEventListener('click', function () {
|
||||||
|
if (!/^https?:\/\//.test(receiverInputEl.value.trim())) {
|
||||||
|
setStatus(t('error.invalidReceiverUrl', null, 'Receiver URL must start with http:// or https://'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveCurrentSettings(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
loadCatalogs().then(function (loadedCatalogs) {
|
||||||
|
catalogs = loadedCatalogs;
|
||||||
|
applyLocale('system');
|
||||||
|
return refresh();
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue