diff --git a/edit/base.js b/edit/base.js index 73ead6a0..0764d8d9 100644 --- a/edit/base.js +++ b/edit/base.js @@ -41,12 +41,6 @@ const editor = { document.documentElement.classList.toggle('is-new-style', !editor.style.id); }, - updateHeaderWidth(w) { - w = Math.round(Math.max(200, Math.min(innerWidth / 2, w))); - document.documentElement.style.setProperty('--header-width', w + 'px'); - return w; - }, - updateTitle(isDirty = editor.dirty.isDirty()) { const {customName, name} = editor.style; document.title = `${ @@ -71,7 +65,6 @@ const baseInit = (() => { Promise.all([ loadTheme(), loadKeymaps(), - editor.updateHeaderWidth(prefs.get('editor.headerWidth')), ])), ]), }; diff --git a/edit/edit.css b/edit/edit.css index 55eb86cb..3dd8ac15 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -1,6 +1,5 @@ :root { --fixed-padding: unset; - --header-width: 280px; } body { @@ -8,18 +7,6 @@ body { height: 100vh; font: 12px arial,sans-serif; } -body.resizing-h { - cursor: e-resize; -} -body.resizing-v { - cursor: n-resize; -} -body.resizing-h > *, -body.resizing-v > * { - pointer-events: none; - -moz-user-select: none; - user-select: none; -} a { color: #000; @@ -135,30 +122,6 @@ label { margin-top: 0.1rem; min-height: 1.4rem; } -#header-resizer { - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 6px; - cursor: e-resize; - border-width: 0 1px; - border-style: solid; - color: #8888; - border-color: currentColor; - pointer-events: auto; -} -#header-resizer:active { - border-color: #888; -} -#header-resizer::after { - content: ''; - position: absolute; - border-right: 2px dotted currentColor; - left: 2px; - width: 0; - height: 100%; -} /* basic info */ #basic-info { @@ -1090,7 +1053,6 @@ body.linter-disabled .hidden-unless-compact { padding: 0; background-color: #fff; } - #header-resizer, .fixed-header #header > *:not(#details-wrapper), .fixed-header #options { display: none !important; diff --git a/edit/edit.js b/edit/edit.js index 879b0d04..da4771f8 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -55,7 +55,6 @@ baseInit.ready.then(async () => { require([ '/edit/autocomplete', '/edit/global-search', - '/edit/header-resizer', ]); }); diff --git a/global.css b/global.css index 2defbb98..6fd13082 100644 --- a/global.css +++ b/global.css @@ -265,6 +265,48 @@ input[type="number"][data-focused-via-click]:focus { box-shadow: none; } +/* header resizer */ +:root { + --header-width: 280px; +} +#header-resizer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 6px; + cursor: e-resize; + border-width: 0 1px; + border-style: solid; + color: #8888; + border-color: currentColor; + pointer-events: auto; +} +#header-resizer:active { + border-color: #888; +} +#header-resizer::after { + content: ''; + position: absolute; + border-right: 2px dotted currentColor; + left: 2px; + width: 0; + height: 100%; +} +body.resizing-h { + cursor: e-resize; +} +body.resizing-v { + cursor: n-resize; +} +body.resizing-h > *, +body.resizing-v > * { + pointer-events: none; + -moz-user-select: none; + user-select: none; +} +/* header resizer - end */ + @supports (-moz-appearance: none) { .moz-appearance-bug .svg-icon.checked, .moz-appearance-bug .onoffswitch input, @@ -318,3 +360,9 @@ input[type="number"][data-focused-via-click]:focus { padding: 2px 4px; } } + +@media (max-width: 850px) { + #header-resizer { + display: none !important; + } +} diff --git a/install-usercss.html b/install-usercss.html index 23e4dc4c..88ea8573 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -80,6 +80,7 @@ +
diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css index 0b25f5a2..7b373866 100644 --- a/install-usercss/install-usercss.css +++ b/install-usercss/install-usercss.css @@ -29,10 +29,9 @@ input:disabled + span { #header, .warnings { - flex: 0 0 280px; + flex: 0 0 var(--header-width); box-sizing: border-box; padding: 1rem; - border-right: 1px dashed #aaa; box-shadow: 0 0 50px -18px black; overflow-wrap: break-word; overflow-y: auto; diff --git a/js/dom-on-load.js b/js/dom-on-load.js new file mode 100644 index 00000000..5cee09d4 --- /dev/null +++ b/js/dom-on-load.js @@ -0,0 +1,126 @@ +/* global $ $$ focusAccessibility getEventKeyName */// dom.js +/* global debounce */// toolbox.js +/* global t */// localization.js +'use strict'; + +/** DOM housekeeping after a page finished loading */ + +(() => { + splitLongTooltips(); + addTooltipsToEllipsized(); + window.on('mousedown', suppressFocusRingOnClick, {passive: true}); + window.on('keydown', keepFocusRingOnTabbing, {passive: true}); + window.on('keypress', clickDummyLinkOnEnter); + window.on('wheel', changeFocusedInputOnWheel, {capture: true, passive: false}); + window.on('click', showTooltipNote); + window.on('resize', () => debounce(addTooltipsToEllipsized, 100)); + // Removing transition-suppressor rule + const {sheet} = $('link[href^="global.css"]'); + for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) { + if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) { + sheet.deleteRule(i); + break; + } + } + + function changeFocusedInputOnWheel(event) { + const el = document.activeElement; + if (!el || el !== event.target && !el.contains(event.target)) { + return; + } + const isSelect = el.tagName === 'SELECT'; + if (isSelect || el.tagName === 'INPUT' && el.type === 'range') { + const key = isSelect ? 'selectedIndex' : 'valueAsNumber'; + const old = el[key]; + const rawVal = old + Math.sign(event.deltaY) * (el.step || 1); + el[key] = Math.max(el.min || 0, Math.min(el.max || el.length - 1, rawVal)); + if (el[key] !== old) { + el.dispatchEvent(new Event('change', {bubbles: true})); + } + event.preventDefault(); + } + event.stopImmediatePropagation(); + } + + /** Displays a full text tooltip on buttons with ellipsis overflow and no inherent title */ + function addTooltipsToEllipsized() { + for (const btn of document.getElementsByTagName('button')) { + if (btn.title && !btn.titleIsForEllipsis) { + continue; + } + const width = btn.offsetWidth; + if (!width || btn.preresizeClientWidth === width) { + continue; + } + btn.preresizeClientWidth = width; + if (btn.scrollWidth > width) { + const text = btn.textContent; + btn.title = text.includes('\u00AD') ? text.replace(/\u00AD/g, '') : text; + btn.titleIsForEllipsis = true; + } else if (btn.title) { + btn.title = ''; + } + } + } + + function clickDummyLinkOnEnter(e) { + if (getEventKeyName(e) === 'Enter') { + const a = e.target.closest('a'); + const isDummy = a && !a.href && a.tabIndex === 0; + if (isDummy) a.dispatchEvent(new MouseEvent('click', {bubbles: true})); + } + } + + function keepFocusRingOnTabbing(event) { + if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) { + focusAccessibility.lastFocusedViaClick = false; + setTimeout(() => { + let el = document.activeElement; + if (el) { + el = el.closest('[data-focused-via-click]'); + if (el) delete el.dataset.focusedViaClick; + } + }); + } + } + + function suppressFocusRingOnClick({target}) { + const el = focusAccessibility.closest(target); + if (el) { + focusAccessibility.lastFocusedViaClick = true; + if (el.dataset.focusedViaClick === undefined) { + el.dataset.focusedViaClick = ''; + } + } + } + + function showTooltipNote(event) { + const el = event.target.closest('[data-cmd=note]'); + if (el) { + event.preventDefault(); + window.messageBoxProxy.show({ + className: 'note center-dialog', + contents: el.dataset.title || el.title, + buttons: [t('confirmClose')], + }); + } + } + + function splitLongTooltips() { + for (const el of $$('[title]')) { + el.dataset.title = el.title; + el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags + if (el.title.length < 50) { + continue; + } + const newTitle = el.title + .split('\n') + .map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n')) + .map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n')) + .join('\n'); + if (newTitle !== el.title) el.title = newTitle; + } + } +})(); + +//#endregion diff --git a/js/dom.js b/js/dom.js index a2601fc1..c5ddc075 100644 --- a/js/dom.js +++ b/js/dom.js @@ -1,6 +1,5 @@ -/* global FIREFOX debounce */// toolbox.js +/* global FIREFOX */// toolbox.js /* global prefs */ -/* global t */// localization.js 'use strict'; /* exported @@ -9,6 +8,7 @@ $remove $$remove animateElement + focusAccessibility getEventKeyName messageBoxProxy moveFocus @@ -427,6 +427,8 @@ async function waitForSheet({ //#endregion //#region Internals +const dom = {}; + (() => { const Collapsible = { @@ -459,42 +461,31 @@ async function waitForSheet({ } }, }; - - window.on('mousedown', suppressFocusRingOnClick, {passive: true}); - window.on('keydown', keepFocusRingOnTabbing, {passive: true}); - + const lazyScripts = [ + '/js/dom-on-load', + ]; + const elHtml = document.documentElement; if (!/^Win\d+/.test(navigator.platform)) { - document.documentElement.classList.add('non-windows'); + elHtml.classList.add('non-windows'); } // set language for a) CSS :lang pseudo and b) hyphenation - document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage()); - document.on('keypress', clickDummyLinkOnEnter); - document.on('wheel', changeFocusedInputOnWheel, {capture: true, passive: false}); - document.on('click', showTooltipNote); - - Promise.resolve().then(async () => { - if (!chrome.app) addFaviconFF(); - await prefs.ready; - waitForSelector('details[data-pref]', {recur: Collapsible.bindEvents}); - }); - - onDOMready().then(() => { - splitLongTooltips(); - debounce(addTooltipsToEllipsized, 500); - window.on('resize', () => debounce(addTooltipsToEllipsized, 100)); - }); - - window.on('load', () => { - const {sheet} = $('link[href^="global.css"]'); - for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) { - if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) { - sheet.deleteRule(i); - break; - } - } - }, {once: true}); - - function addFaviconFF() { + elHtml.setAttribute('lang', chrome.i18n.getUILanguage()); + // set header width and export stuff via `dom.headerResizer` + if (/^.(edit|manage|install)/.test(location.pathname)) { + const prefId = `${RegExp.$1 === 'edit' ? 'editor' : RegExp.$1}.headerWidth`; + const HR = dom.headerResizer = { + prefId, + setDomProp(width) { + width = Math.round(Math.max(200, Math.min(innerWidth / 2, width))); + elHtml.style.setProperty('--header-width', width + 'px'); + return width; + }, + }; + prefs.ready.then(() => HR.setDomProp(prefs.get(prefId))); + lazyScripts.push('/js/header-resizer'); + } + // add favicon in FF + if (FIREFOX) { const iconset = ['', 'light/'][prefs.get('iconset')] || ''; for (const size of [38, 32, 19, 16]) { document.head.appendChild($create('link', { @@ -504,105 +495,12 @@ async function waitForSheet({ })); } } - - function changeFocusedInputOnWheel(event) { - const el = document.activeElement; - if (!el || el !== event.target && !el.contains(event.target)) { - return; - } - const isSelect = el.tagName === 'SELECT'; - if (isSelect || el.tagName === 'INPUT' && el.type === 'range') { - const key = isSelect ? 'selectedIndex' : 'valueAsNumber'; - const old = el[key]; - const rawVal = old + Math.sign(event.deltaY) * (el.step || 1); - el[key] = Math.max(el.min || 0, Math.min(el.max || el.length - 1, rawVal)); - if (el[key] !== old) { - el.dispatchEvent(new Event('change', {bubbles: true})); - } - event.preventDefault(); - } - event.stopImmediatePropagation(); - } - - /** Displays a full text tooltip on buttons with ellipsis overflow and no inherent title */ - function addTooltipsToEllipsized() { - for (const btn of document.getElementsByTagName('button')) { - if (btn.title && !btn.titleIsForEllipsis) { - continue; - } - const width = btn.offsetWidth; - if (!width || btn.preresizeClientWidth === width) { - continue; - } - btn.preresizeClientWidth = width; - if (btn.scrollWidth > width) { - const text = btn.textContent; - btn.title = text.includes('\u00AD') ? text.replace(/\u00AD/g, '') : text; - btn.titleIsForEllipsis = true; - } else if (btn.title) { - btn.title = ''; - } - } - } - - function clickDummyLinkOnEnter(e) { - if (getEventKeyName(e) === 'Enter') { - const a = e.target.closest('a'); - const isDummy = a && !a.href && a.tabIndex === 0; - if (isDummy) a.dispatchEvent(new MouseEvent('click', {bubbles: true})); - } - } - - function keepFocusRingOnTabbing(event) { - if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) { - focusAccessibility.lastFocusedViaClick = false; - setTimeout(() => { - let el = document.activeElement; - if (el) { - el = el.closest('[data-focused-via-click]'); - if (el) delete el.dataset.focusedViaClick; - } - }); - } - } - - function suppressFocusRingOnClick({target}) { - const el = focusAccessibility.closest(target); - if (el) { - focusAccessibility.lastFocusedViaClick = true; - if (el.dataset.focusedViaClick === undefined) { - el.dataset.focusedViaClick = ''; - } - } - } - - function showTooltipNote(event) { - const el = event.target.closest('[data-cmd=note]'); - if (el) { - event.preventDefault(); - window.messageBoxProxy.show({ - className: 'note center-dialog', - contents: el.dataset.title || el.title, - buttons: [t('confirmClose')], - }); - } - } - - function splitLongTooltips() { - for (const el of $$('[title]')) { - el.dataset.title = el.title; - el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags - if (el.title.length < 50) { - continue; - } - const newTitle = el.title - .split('\n') - .map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n')) - .map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n')) - .join('\n'); - if (newTitle !== el.title) el.title = newTitle; - } - } + prefs.ready.then(() => { + waitForSelector('details[data-pref]', {recur: Collapsible.bindEvents}); + }); + window.on('load', () => { + require(lazyScripts); + }, {once: true}); })(); //#endregion diff --git a/edit/header-resizer.js b/js/header-resizer.js similarity index 82% rename from edit/header-resizer.js rename to js/header-resizer.js index 3d378770..a08c59ed 100644 --- a/edit/header-resizer.js +++ b/js/header-resizer.js @@ -1,14 +1,13 @@ -/* global $ $$ */// dom.js -/* global editor */ +/* global $ $$ dom */// dom.js /* global debounce */// toolbox.js /* global prefs */ 'use strict'; -(function HeaderResizer() { - const PREF_ID = 'editor.headerWidth'; +(() => { + const HR = dom.headerResizer; const el = $('#header-resizer'); let curW, offset, active; - prefs.subscribe(PREF_ID, (key, val) => { + prefs.subscribe(HR.prefId, (key, val) => { if (!active && val !== curW) { getCurWidth(); setWidth(val); @@ -43,11 +42,11 @@ } function save() { - prefs.set(PREF_ID, curW); + prefs.set(HR.prefId, curW); } function setWidth(w) { - const delta = (w = editor.updateHeaderWidth(w)) - curW; + const delta = (w = HR.setDomProp(w)) - curW; if (delta) { curW = w; for (const el of $$('.CodeMirror-linewidget[style*="width:"]')) { diff --git a/js/prefs.js b/js/prefs.js index fdfe8415..034c07f1 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -99,7 +99,6 @@ 'editor.appliesToLineWidget': true, // show applies-to line widget on the editor 'editor.livePreview': true, - 'editor.headerWidth': 280, // show CSS colors as clickable colored rectangles 'editor.colorpicker': true, @@ -124,6 +123,10 @@ 'badgeDisabled': '#8B0000', // badge background color when disabled 'badgeNormal': '#006666', // badge background color + 'editor.headerWidth': 280, + 'install.headerWidth': 280, + 'manage.headerWidth': 280, + 'popupWidth': 246, // popup width in pixels 'updateInterval': 24, // user-style automatic update interval, hours (0 = disable) diff --git a/manage.html b/manage.html index dc9e4dd1..597b4098 100644 --- a/manage.html +++ b/manage.html @@ -354,6 +354,7 @@
+
diff --git a/manage/manage.css b/manage/manage.css index 75b490da..f88b6f86 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -1,5 +1,4 @@ :root { - --header-width: 280px; --name-padding-left: 20px; --name-padding-right: 40px; --actions-width: 75px; @@ -52,7 +51,6 @@ a:hover { position: fixed; top: 0; padding: 1rem; - border-right: 1px dashed #AAA; box-shadow: 0 0 50px -18px black; overflow: auto; box-sizing: border-box;