From d25ef9720568a91fea337c487e62941d6de0feed Mon Sep 17 00:00:00 2001 From: tophf Date: Sun, 13 Feb 2022 07:56:55 +0300 Subject: [PATCH] set classes dynamically * `sticky` on #header when scrolled * `sectioned` on html for sectioned editor --- edit/base.js | 18 ++++++++---------- edit/edit.css | 35 +++++++++++++++++++++++------------ edit/edit.js | 34 +++++++++++++++++++++++++++++----- js/dom.js | 9 +++++---- 4 files changed, 65 insertions(+), 31 deletions(-) diff --git a/edit/base.js b/edit/base.js index 0e9afaf5..3aab872e 100644 --- a/edit/base.js +++ b/edit/base.js @@ -1,4 +1,4 @@ -/* global $ $$ $create setupLivePrefs waitForSelector */// dom.js +/* global $ $$ $create $root setupLivePrefs waitForSelector */// dom.js /* global API */// msg.js /* global CODEMIRROR_THEMES */ /* global CodeMirror */ @@ -32,7 +32,7 @@ const editor = { cancel: () => location.assign('/manage.html'), updateClass() { - document.documentElement.classList.toggle('is-new-style', !editor.style.id); + $root.classList.toggle('is-new-style', !editor.style.id); }, updateTitle(isDirty = editor.dirty.isDirty()) { @@ -49,16 +49,14 @@ const editor = { const baseInit = (() => { const domReady = waitForSelector('#sections'); - const mq = matchMedia('(max-width: 850px)'); - (mq.onchange = e => { - document.documentElement.classList.toggle('compact-layout', e.matches); - for (const el of $$('details[data-pref]')) { - el.open = e.matches ? false : prefs.get(el.dataset.pref); - } - })(mq); + const mqCompact = matchMedia('(max-width: 850px)'); + const toggleCompact = mq => $root.classList.toggle('compact-layout', mq.matches); + mqCompact.on('change', toggleCompact); + toggleCompact(mqCompact); return { domReady, + mqCompact, ready: Promise.all([ domReady, loadStyle(), @@ -94,7 +92,7 @@ const baseInit = (() => { editor.style = style; editor.updateClass(); editor.updateTitle(false); - document.documentElement.classList.toggle('usercss', editor.isUsercss); + $root.classList.add(editor.isUsercss ? 'usercss' : 'sectioned'); sessionStore.justEditedStyleId = style.id || ''; // no such style so let's clear the invalid URL parameters if (!style.id) history.replaceState({}, '', location.pathname); diff --git a/edit/edit.css b/edit/edit.css index f7dd4169..3e6e936c 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -379,11 +379,11 @@ input:invalid { box-shadow: none !important; pointer-events: auto !important; /* FF bug */ } -.section-editor .section { +.sectioned .section { margin: 0 0.7rem; padding: 1rem; } -.section-editor .section:not(:first-child) { +.sectioned .section:not(:first-child) { border-top: 2px solid hsl(0, 0%, 80%); } .add-section:after { @@ -419,10 +419,10 @@ input:invalid { content: counter(codebox) ": " attr(data-text); margin-left: 0.25rem; } -.single-editor .applies-to { +.usercss .applies-to { border-width: 1px 0; } -.single-editor .applies-to > label::before { +.usercss .applies-to > label::before { content: attr(data-index) ":"; margin-right: 0.25rem; font-size: 12px; @@ -586,7 +586,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high padding: 0; line-height: var(--input-height); } -.section-editor .applies-to label { +.sectioned .applies-to label { margin-left: -24px; position: absolute; } @@ -948,7 +948,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high box-shadow: none; } -html:not(.usercss) .usercss-only, +.sectioned .usercss-only, .usercss .sectioned-only { display: none !important; /* hide during page init */ } @@ -1020,28 +1020,40 @@ html:not(.usercss) .usercss-only, /************ reponsive layouts ************/ @media(max-width: 850px) { - html:not(.usercss) body { + .sectioned body { height: 100%; } .usercss body { display: flex; flex-direction: column; } - .usercss #header { - position: inherit; + .usercss #header, + #header:not(.sticky) { + position: static; } #header { display: block; flex: 0 0 auto; height: unset; - width: unset; - position: sticky; + width: 100%; background: #fff; border-right: none; border-bottom: 1px dashed #AAA; padding: var(--pad05) var(--pad05) 0; } + #header.sticky { + box-shadow: 0 0 3rem -.75rem black; + } + #header.sticky #basic-info, + #header.sticky #mozilla-format-buttons, + #header.sticky .buttons > button, #heading, + #header-sticker { + top: 0; + height: 1px; + position: absolute; + visibility: hidden; + } h2 { display: none; } @@ -1092,7 +1104,6 @@ html:not(.usercss) .usercss-only, #sections { padding-left: 0; } - /* TODO: switch from .single-editor to the global .usercss */ #sections > :not(.single-editor) { margin: 0 .5rem; padding: .5rem 0; diff --git a/edit/edit.js b/edit/edit.js index 89a945b5..700a1071 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -25,11 +25,7 @@ baseInit.ready.then(async () => { await editor.ready; editor.ready = true; editor.dirty.onChange(editor.updateDirty); - - prefs.subscribe('editor.linter', (key, value) => { - document.body.classList.toggle('linter-disabled', value === ''); - linterMan.run(); - }); + prefs.subscribe('editor.linter', () => linterMan.run()); // enabling after init to prevent flash of validation failure on an empty name $('#name').required = !editor.isUsercss; @@ -74,6 +70,34 @@ baseInit.ready.then(async () => { '/edit/drafts', '/edit/global-search', ]); +}).then(() => { + // Set up mini-header on scroll + const {isUsercss} = editor; + const el = $create('#header-sticker'); + const scroller = isUsercss ? $('.CodeMirror-scroll') : document.body; + const xo = new IntersectionObserver(onScrolled, {root: isUsercss ? scroller : document}); + scroller.appendChild(el); + onCompactToggled(baseInit.mqCompact); + baseInit.mqCompact.on('change', onCompactToggled); + + /** @param {MediaQueryList} mq */ + function onCompactToggled(mq) { + for (const el of $$('details[data-pref]')) { + el.open = mq.matches ? false : prefs.get(el.dataset.pref); + } + if (mq.matches) { + xo.observe(el); + } else { + xo.disconnect(); + } + } + /** @param {IntersectionObserverEntry[]} entries */ + function onScrolled([e]) { + const h = $('#header'); + const sticky = !e.isIntersecting; + if (!isUsercss) scroller.style.paddingTop = sticky ? h.offsetHeight + 'px' : ''; + h.classList.toggle('sticky', sticky); + } }); //#endregion diff --git a/js/dom.js b/js/dom.js index 241c8b12..d8c83fb6 100644 --- a/js/dom.js +++ b/js/dom.js @@ -27,6 +27,8 @@ Object.assign(EventTarget.prototype, { //#region Exports +const $root = document.documentElement; + // Makes the focus outline appear on keyboard tabbing, but not on mouse clicks. const focusAccessibility = { // last event's focusedViaClick @@ -474,10 +476,9 @@ const dom = {}; const lazyScripts = [ '/js/dom-on-load', ]; - const elHtml = document.documentElement; - if (!UA.windows) elHtml.classList.add('non-windows'); + if (!UA.windows) $root.classList.add('non-windows'); // set language for a) CSS :lang pseudo and b) hyphenation - elHtml.setAttribute('lang', chrome.i18n.getUILanguage()); + $root.setAttribute('lang', chrome.i18n.getUILanguage()); // set up header width resizer const HW = 'headerWidth.'; const HWprefId = HW + location.pathname.match(/^.(\w*)/)[1]; @@ -489,7 +490,7 @@ const dom = {}; // If this is a small window on a big monitor the user can maximize it later const max = (innerWidth < 850 ? screen.availWidth : innerWidth) / 3; width = Math.round(Math.max(200, Math.min(max, Number(width) || 0))); - elHtml.style.setProperty('--header-width', width + 'px'); + $root.style.setProperty('--header-width', width + 'px'); return width; }, });