ensure long words break before breaking the layout

supersedes 40075a0d
This commit is contained in:
tophf 2017-12-07 23:21:27 +03:00
parent fbcd3cc965
commit 99cce55a8e
4 changed files with 95 additions and 68 deletions

View File

@ -48,6 +48,7 @@ globals:
tHTML: false tHTML: false
tNodeList: false tNodeList: false
tDocLoader: false tDocLoader: false
tWordBreak: false
# dom.js # dom.js
onDOMready: false onDOMready: false
scrollElementIntoView: false scrollElementIntoView: false

View File

@ -75,30 +75,6 @@ function preinit() {
'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css' 'vendor/codemirror/theme/' + prefs.get('editor.theme') + '.css'
})); }));
// forcefully break long labels in aligned options to prevent the entire block layout from breaking
onDOMready().then(() => new Promise(requestAnimationFrame)).then(() => {
const maxWidth2ndChild = $$('#options .aligned > :nth-child(2)')
.sort((a, b) => b.offsetWidth - a.offsetWidth)[0].offsetWidth;
const widthFor1stChild = $('#options').offsetWidth - maxWidth2ndChild;
if (widthFor1stChild > 50) {
for (const el of $$('#options .aligned > :nth-child(1)')) {
if (el.offsetWidth > widthFor1stChild) {
el.style.cssText = 'word-break: break-all; hyphens: auto;';
}
}
} else {
const width = $('#options').clientWidth;
document.head.appendChild($create('style', `
#options .aligned > nth-child(1) {
max-width: 70px;
}
#options .aligned > nth-child(2) {
max-width: ${width - 70}px;
}
`));
}
});
if (chrome.windows) { if (chrome.windows) {
queryTabs({currentWindow: true}).then(tabs => { queryTabs({currentWindow: true}).then(tabs => {
const windowId = tabs[0].windowId; const windowId = tabs[0].windowId;

View File

@ -48,13 +48,51 @@ function tHTML(html, tag) {
function tNodeList(nodes) { function tNodeList(nodes) {
const PREFIX = 'i18n-'; const PREFIX = 'i18n-';
for (let n = nodes.length; --n >= 0;) { for (let n = nodes.length; --n >= 0;) {
const node = nodes[n]; const node = nodes[n];
// skip non-ELEMENT_NODE if (node.nodeType !== Node.ELEMENT_NODE) {
if (node.nodeType !== 1) {
continue; continue;
} }
if (node.localName === 'template') { if (node.localName === 'template') {
createTemplate(node);
continue;
}
for (let a = node.attributes.length; --a >= 0;) {
const attr = node.attributes[a];
const name = attr.nodeName;
if (!name.startsWith(PREFIX)) {
continue;
}
const type = name.substr(PREFIX.length);
const value = t(attr.value);
let toInsert, before;
switch (type) {
case 'word-break':
// we already know that: hasWordBreak
break;
case 'text':
before = node.firstChild;
// fallthrough to text-append
case 'text-append':
toInsert = createText(value);
break;
case 'html': {
toInsert = createHtml(value);
break;
}
default:
node.setAttribute(type, value);
}
tDocLoader.pause();
if (toInsert) {
node.insertBefore(toInsert, before || null);
}
node.removeAttribute(name);
}
}
function createTemplate(node) {
const elements = node.content.querySelectorAll('*'); const elements = node.content.querySelectorAll('*');
tNodeList(elements); tNodeList(elements);
template[node.dataset.id] = elements[0]; template[node.dataset.id] = elements[0];
@ -67,37 +105,32 @@ function tNodeList(nodes) {
toRemove.push(textNode); toRemove.push(textNode);
} }
} }
tDocLoader.pause();
toRemove.forEach(el => el.remove()); toRemove.forEach(el => el.remove());
continue;
} }
for (let a = node.attributes.length; --a >= 0;) {
const attr = node.attributes[a]; function createText(str) {
const name = attr.nodeName; return document.createTextNode(tWordBreak(str));
if (!name.startsWith(PREFIX)) {
continue;
} }
const type = name.substr(PREFIX.length);
const value = t(attr.value); function createHtml(value) {
switch (type) { // <a href=foo>bar</a> are the only recognizable HTML elements
case 'text': const rx = /(?:<a\s([^>]*)>([^<]*)<\/a>)?([^<]*)/gi;
node.insertBefore(document.createTextNode(value), node.firstChild); const bin = document.createDocumentFragment();
break; for (let m; (m = rx.exec(value)) && m[0];) {
case 'text-append': const [, linkParams, linkText, nextText] = m;
node.appendChild(document.createTextNode(value)); if (linkText) {
break; const href = /\bhref\s*=\s*(\S+)/.exec(linkParams);
case 'html': const a = bin.appendChild(document.createElement('a'));
// localized strings only allow having text nodes and links a.href = href && href[1].replace(/^(["'])(.*)\1$/, '$2') || '';
node.textContent = ''; a.appendChild(createText(linkText));
[...tHTML(value, 'div').childNodes]
.filter(a => a.nodeType === a.TEXT_NODE || a.tagName === 'A')
.forEach(n => node.appendChild(n));
break;
default:
node.setAttribute(type, value);
} }
node.removeAttribute(name); if (nextText) {
bin.appendChild(createText(nextText));
} }
} }
return bin;
}
} }
@ -115,33 +148,50 @@ function tDocLoader() {
t.cache = {browserUIlanguage: UIlang}; t.cache = {browserUIlanguage: UIlang};
localStorage.L10N = JSON.stringify(t.cache); localStorage.L10N = JSON.stringify(t.cache);
} }
const cacheLength = Object.keys(t.cache).length; const cacheLength = Object.keys(t.cache).length;
// localize HEAD Object.assign(tDocLoader, {
tNodeList(document.getElementsByTagName('*')); observer: new MutationObserver(process),
start() {
if (!tDocLoader.observing) {
tDocLoader.observing = true;
tDocLoader.observer.observe(document, {subtree: true, childList: true});
}
},
stop() {
tDocLoader.pause();
document.removeEventListener('DOMContentLoaded', onLoad);
},
pause() {
if (tDocLoader.observing) {
tDocLoader.observing = false;
tDocLoader.observer.disconnect();
}
},
});
// localize BODY tNodeList(document.getElementsByTagName('*'));
const process = mutations => { tDocLoader.start();
document.addEventListener('DOMContentLoaded', onLoad);
function process(mutations) {
for (const mutation of mutations) { for (const mutation of mutations) {
tNodeList(mutation.addedNodes); tNodeList(mutation.addedNodes);
} }
}; tDocLoader.start();
const observer = new MutationObserver(process); }
const onLoad = () => {
function onLoad() {
tDocLoader.stop(); tDocLoader.stop();
process(observer.takeRecords()); process(tDocLoader.observer.takeRecords());
if (cacheLength !== Object.keys(t.cache).length) { if (cacheLength !== Object.keys(t.cache).length) {
localStorage.L10N = JSON.stringify(t.cache); localStorage.L10N = JSON.stringify(t.cache);
} }
}; }
tDocLoader.start = () => { }
observer.observe(document, {subtree: true, childList: true});
};
tDocLoader.stop = () => { function tWordBreak(text) {
observer.disconnect(); // adds soft hyphens every 10 characters to ensure the long words break before breaking the layout
document.removeEventListener('DOMContentLoaded', onLoad); return text.length <= 10 ? text : text.replace(/[\d\w\u0080-\uFFFF]{10}|((?!\s)\W){10}/g, '$&\u00AD');
};
tDocLoader.start();
document.addEventListener('DOMContentLoaded', onLoad);
} }

View File

@ -179,7 +179,7 @@ function createStyleElement({style, name}) {
} }
const parts = createStyleElement.parts; const parts = createStyleElement.parts;
parts.checker.checked = style.enabled; parts.checker.checked = style.enabled;
parts.nameLink.textContent = style.name; parts.nameLink.textContent = tWordBreak(style.name);
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id; parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
parts.homepage.href = parts.homepage.title = style.url || ''; parts.homepage.href = parts.homepage.title = style.url || '';