diff --git a/0-transpile.sh b/0-transpile.sh
index deb6bd8..4af87b6 100755
--- a/0-transpile.sh
+++ b/0-transpile.sh
@@ -1,4 +1,4 @@
#!/bin/sh
cd www
-npx tsc --allowJs --outFile main.comp.js polyfill.js i18n-ext.js i18n.js image.js main.js
+npx tsc --allowJs --outFile main.comp.js polyfill.js i18n-ext.js i18n.js image.js accessibility.js main.js
cd ..
diff --git a/README.md b/README.md
index 810a45b..51fd9ff 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ Currently:
- Friendly!
- Language support! You can participate in translation!
- Good user interface, adaptive to PC/mobile and light/dark theme!
+ - Accessibility features, everyone is considered!
- Feature-rich!
- Web UI, for most people!
diff --git a/i18n.i18n/i18n.md b/i18n.i18n/i18n.md
index b074fa3..1392a9c 100644
--- a/i18n.i18n/i18n.md
+++ b/i18n.i18n/i18n.md
@@ -15,12 +15,37 @@ In simple cases, you just make a copy of already-there language file, and modify
You should know what's your locale "code", for example "English (US)" is `en-US`. You can look at your browser locale configuration, or gather from the Web.
After that, add an entry in `list.json`.
+## Naming
+
+Plain:
+```json
+"there-is-an-apple": "There is an apple"
+```
+
+With parameter(s):
+```json
+"there-are-0-apples-in-1-baskets": "There are {0} apples in {1} baskets"
+```
+
+With Conditions: *(language dependent)*
+```json
+"0-apples": {
+ "single": "{0} apple",
+ "multiple": "{0} apples"
+}
+```
+
+Special Key:
+```json
+"KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm"
+```
+
## It seems can't satisfy another grammar
That's what is going to be fun:
This i18n implementation didn't do built-in basics.
-Instead the grammar details is all done by code.
+Instead the grammar details are all done by code.
That doesn't mean difficulty:
diff --git a/readme.i18n/README.zh_CN.md b/readme.i18n/README.zh_CN.md
index 4bf6abf..9256748 100644
--- a/readme.i18n/README.zh_CN.md
+++ b/readme.i18n/README.zh_CN.md
@@ -24,6 +24,7 @@
- 友好!
- 语言支持!您可参与翻译!
- 良好的用户界面,可适应桌面/移动端/明暗主题!
+ - 无障碍功能,考虑到每一个人!
- 功能丰富!
- 网页界面,所有人都可以用!
diff --git a/www/accessibility.js b/www/accessibility.js
new file mode 100644
index 0000000..01d1263
--- /dev/null
+++ b/www/accessibility.js
@@ -0,0 +1,99 @@
+
+function isHidden(element) {
+ let parents = [element];
+ while (parents[0].parentElement)
+ parents.unshift(parents[0].parentElement);
+ return parents.some(e => {
+ let rect = e.getBoundingClientRect();
+ return (
+ e.classList.contains('hidden') ||
+ e.classList.contains('hard-hidden') ||
+ e.style.display == 'none' ||
+ rect.width == 0 || rect.height == 0 ||
+ rect.x < 0 || rect.y < 0 ||
+ e.style.visibility == 'none' ||
+ e.style.opacity == '0'
+ );
+ });
+}
+
+function toLocaleKey(key) {
+ const qwerty = '1234567890qwertyuiopasdfghjklzxcvbnm';
+ let keys, index;
+ if (
+ typeof i18n === 'undefined' ||
+ key.length !== 1 ||
+ (keys = i18n('KeyboardLayout')) === 'KeyboardLayout' ||
+ (index = qwerty.indexOf(key)) === -1
+ ) return key;
+ return keys[index];
+}
+
+function initKeyboardShortcuts() {
+ const layer = document.getElementById('keyboard-shortcuts-layer');
+ const dialog = document.getElementById('dialog');
+ let key, keys = 'qwertyuiopasdfghjklzxcvbnm';
+ let focus, focusing = false, started = false;
+ let inputs, shortcuts = {};
+ const mark_keys = () => {
+ if (dialog.classList.contains('hidden'))
+ inputs = Array.from(document.querySelectorAll('*[data-key]'));
+ else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
+ /** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
+ let key_index = 0;
+ shortcuts = {};
+ if (focusing) shortcuts = { ESC: focus };
+ else
+ for (let input of inputs) {
+ if (isHidden(input)) continue;
+ let key = toLocaleKey(input.getAttribute('data-key') || keys[key_index++]);
+ shortcuts[key] = input;
+ }
+ // Array.from(layer.children).forEach(e => e.remove());
+ for (let i = layer.children.length; i <= inputs.length; i++) {
+ let span = document.createElement('span');
+ layer.appendChild(span);
+ }
+ let index = 0;
+ for (let key in shortcuts) {
+ let span = layer.children[index++];
+ let input = shortcuts[key];
+ let position = input.getBoundingClientRect();
+ let text = key.toUpperCase().replace(' ', 'SPACE');
+ if (span.innerText !== text) span.innerText = text;
+ span.style.top = position.y + 'px';
+ span.style.left = position.x + 'px';
+ span.style.display = '';
+ }
+ for (let i = index; i < layer.children.length; i++) {
+ layer.children[i].style.display = 'none';
+ }
+ }
+ const start = () => setInterval(mark_keys, 1000);
+ const types_to_click = ['submit', 'file', 'checkbox', 'radio', 'A'];
+ document.body.addEventListener('keyup', (event) => {
+ key = event.key;
+ if (!started) {
+ if (key !== 'Tab') return;
+ mark_keys();
+ start();
+ started = true;
+ }
+ document.body.addEventListener('keyup', () =>
+ requestAnimationFrame(mark_keys)
+ , { once: true });
+ let input = shortcuts[key];
+ if (input) {
+ if (types_to_click.includes(input.type || input.tagName))
+ input.click();
+ else {
+ input.focus();
+ focusing = true;
+ }
+ focus = input;
+ } else if (key === 'Escape' && focus) {
+ focus.blur();
+ focusing = !focusing;
+ }
+ });
+}
diff --git a/www/index.html b/www/index.html
index 94e95de..ab89c90 100644
--- a/www/index.html
+++ b/www/index.html
@@ -91,9 +91,9 @@
@@ -121,7 +121,7 @@
🌎
Language
-
@@ -143,7 +143,7 @@
-
+
-
+
diff --git a/www/jslicense.html b/www/jslicense.html
index be5541c..bece627 100644
--- a/www/jslicense.html
+++ b/www/jslicense.html
@@ -49,6 +49,12 @@
i18n-ext.js |
I18n "extensions" |
+
+ accessibility.js |
+ CC0-1.0-only |
+ accessibility.js |
+ Accessibility features |
+
main.js |
GNU-GPL-3.0-or-later |
diff --git a/www/lang/de-DE.json b/www/lang/de-DE.json
index 38d8817..a0dba02 100644
--- a/www/lang/de-DE.json
+++ b/www/lang/de-DE.json
@@ -1,5 +1,6 @@
{
"$language": "Deutsch",
+ "KeyboardLayout": "1234567890qwertzuiopasdfghjklyxcvbnm",
"cat-printer": "Cat Printer",
"printer": "Drucker",
"device-": "Gerät:",
diff --git a/www/loader.js b/www/loader.js
index a37204e..eb6e641 100644
--- a/www/loader.js
+++ b/www/loader.js
@@ -6,7 +6,7 @@
var fallbacks = [
// main scripts, which we will directly modify
- 'i18n-ext.js', 'i18n.js', 'image.js', 'main.js',
+ 'i18n-ext.js', 'i18n.js', 'image.js', 'accessibility.js', 'main.js',
// "compatibility" script, produced with eg. typescript tsc
'main.comp.js'
];
diff --git a/www/main.css b/www/main.css
index ab7c830..8ae510c 100644
--- a/www/main.css
+++ b/www/main.css
@@ -434,13 +434,7 @@ hr {
white-space: pre;
line-height: 1em;
font-family: 'DejaVu Sans Mono', 'Consolas', monospace;
- transform: translateY(calc(var(--font-size) * -1));
-}
-body.force-rtl #keyboard-shortcuts-layer span {
- transform: translate(
- calc(var(--font-size) * 2),
- calc(var(--font-size) * -1)
- );
+ transform: translate(-1em, calc(var(--font-size) * -1));
}
a {
@@ -580,7 +574,7 @@ body.high-contrast {
transition-duration: 0s;
}
body.high-contrast .shade { transition-duration: 0s; opacity: 1; }
-body.high-contrast * { background-color: var(--back-color); }
+/* body.high-contrast * { background-color: var(--back-color); } */
body.high-contrast .logo, canvas { filter: unset !important; }
body.high-contrast #notice * { border: var(--border) dashed var(--fore-color); }
body.high-contrast a:any-link { color: #00f; }
diff --git a/www/main.js b/www/main.js
index 20cc467..dd89edf 100644
--- a/www/main.js
+++ b/www/main.js
@@ -83,11 +83,12 @@ const Dialog = (function() {
last_choices = [];
dialog_input.value = '';
dialog_input.style.display = have_input ? 'unset' : 'none';
+ const keys = 'bn,.';
let index = 1;
for (let choice of choices) {
let button = document.createElement('button');
button.setAttribute('data-i18n', choice);
- button.setAttribute('data-key', index++);
+ button.setAttribute('data-key', keys[index++]);
button.innerText = i18n(choice);
if (!have_input)
button.addEventListener('click', () => dialog_input.value = choice);
@@ -463,97 +464,6 @@ async function testI18n(lang) {
, true);
}
-function isHidden(element) {
- let parents = [element];
- while (parents[0].parentElement)
- parents.unshift(parents[0].parentElement);
- return parents.some(e => {
- let rect = e.getBoundingClientRect();
- return (
- e.classList.contains('hidden') ||
- e.classList.contains('hard-hidden') ||
- e.style.display == 'none' ||
- rect.width == 0 || rect.height == 0 ||
- rect.x < 0 || rect.y < 0 ||
- e.style.visibility == 'none' ||
- e.style.opacity == '0'
- );
- });
-}
-
-function initKeyboardShortcuts() {
- const layer = document.getElementById('keyboard-shortcuts-layer');
- const dialog = document.getElementById('dialog');
- let key, keys = 'qwertyuiopasdfghjklzxcvbnm';
- let focused, onbreak = false, started = false;
- let inputs, shortcuts = {};
- const mark_keys = () => {
- if (dialog.classList.contains('hidden'))
- inputs = Array.from(document.querySelectorAll('*[data-key]'));
- else inputs = Array.from(document.querySelectorAll('#dialog *[data-key]'));
- /** @type {{ [key: string]: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement }} */
- let key_index = 0;
- shortcuts = {};
- for (let input of inputs) {
- if (isHidden(input)) continue;
- let key = input.getAttribute('data-key') || keys[key_index++];
- shortcuts[key] = input;
- }
- Array.from(layer.children).forEach(e => e.remove());
- for (let key in shortcuts) {
- if (onbreak) {
- let rect = focused.getBoundingClientRect();
- let span = document.createElement('span');
- span.innerText = i18n('ESCAPE');
- span.style.top = rect.y + 'px';
- span.style.left = rect.x + 'px';
- layer.appendChild(span);
- break;
- }
- let input = shortcuts[key];
- let position = input.getBoundingClientRect();
- let span = document.createElement('span');
- span.innerText = i18n(key.toUpperCase().replace(' ', 'SPACE'));
- span.style.top = position.y + 'px';
- span.style.right = (window.innerWidth - position.x) + 'px';
- layer.appendChild(span);
- }
- }
- const start = () => setInterval(mark_keys, 1000);
- document.body.addEventListener('keyup', (event) => {
- document.body.addEventListener('keyup', () => requestAnimationFrame(mark_keys), { once: true });
- key = event.key;
- if (key === 'Tab') {
- if (!started) { start(); started = true; }
- mark_keys();
- return;
- }
- let input = shortcuts[key];
- if (input) {
- switch (input.type || input.tagName) {
- case 'range':
- case 'text':
- case 'number':
- case 'tel':
- case 'date':
- case 'select-one':
- case 'select-multiple':
- case 'DIV':
- case 'TEXTAREA':
- input.focus();
- onbreak = true;
- break;
- default:
- input.click();
- }
- focused = input;
- } else if (key === 'Escape' && focused) {
- focused.blur();
- onbreak = !onbreak;
- }
- });
-}
-
class Main {
promise;
/** @type {CanvasController} */
@@ -580,6 +490,10 @@ class Main {
let iframe = document.getElementById('frame');
iframe.addEventListener('load', () => {
iframe.contentDocument.body.classList.value = document.body.classList.value;
+ iframe.contentDocument.body.addEventListener('keyup', (event) => {
+ if (event.key === 'Escape')
+ document.body.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
+ });
applyI18nToDom(iframe.contentDocument);
});
function apply_class(class_name, value) {
@@ -626,7 +540,7 @@ class Main {
await this.loadConfig();
if (this.settings['is_android'])
document.getElementById('select-language').multiple = false;
- initKeyboardShortcuts();
+ if (typeof initKeyboardShortcuts === 'function') initKeyboardShortcuts();
this.searchDevices();
document.querySelector('main').classList.remove('hard-hidden');
document.getElementById('loading-screen').classList.add('hidden');