diff --git a/edit.html b/edit.html index 7efc20ce..54b474e3 100644 --- a/edit.html +++ b/edit.html @@ -74,7 +74,7 @@ - + diff --git a/edit/applies-to-line-widget.js b/edit/applies-to-line-widget.js index 91d26f63..aea28e47 100644 --- a/edit/applies-to-line-widget.js +++ b/edit/applies-to-line-widget.js @@ -5,7 +5,7 @@ function createAppliesToLineWidget(cm) { const THROTTLE_DELAY = 400; let TPL, EVENTS, CLICK_ROUTE; let widgets = []; - let fromLine, toLine, styleVariables; + let fromLine, toLine, actualStyle; let initialized = false; return {toggle}; @@ -29,23 +29,14 @@ function createAppliesToLineWidget(cm) { $create('label', t('appliesLabel')), $create('ul.applies-to-list'), ]), - listItem: - $create('li.applies-to-item', [ - $create('select.applies-type', [ - $create('option', {value: 'url'}, t('appliesUrlOption')), - $create('option', {value: 'url-prefix'}, t('appliesUrlPrefixOption')), - $create('option', {value: 'domain'}, t('appliesDomainOption')), - $create('option', {value: 'regexp'}, t('appliesRegexpOption')), - ]), - $create('input.applies-value', {spellcheck: false}), - $create('button.test-regexp', t('styleRegexpTestButton')), - $create('button.remove-applies-to', t('appliesRemove')), - $create('button.add-applies-to', t('appliesAdd')), - ]), + listItem: template.appliesTo, appliesToEverything: $create('li.applies-to-everything', t('appliesToEverything')), }; + $('button', TPL.listItem).insertAdjacentElement('afterend', + $create('button.test-regexp', t('styleRegexpTestButton'))); + CLICK_ROUTE = { '.test-regexp': (item, apply) => { regExpTester.toggle(); @@ -136,15 +127,13 @@ function createAppliesToLineWidget(cm) { } }; - styleVariables = $create('style'); + actualStyle = $create('style'); fromLine = 0; toLine = cm.doc.size; cm.on('change', onChange); cm.on('optionChange', onOptionChange); - // is it possible to avoid flickering? - window.addEventListener('load', updateWidgetStyle); chrome.runtime.onMessage.addListener(onRuntimeMessage); updateWidgetStyle(); @@ -158,9 +147,8 @@ function createAppliesToLineWidget(cm) { widgets.length = 0; cm.off('change', onChange); cm.off('optionChange', onOptionChange); - window.removeEventListener('load', updateWidgetStyle); chrome.runtime.onMessage.removeListener(onRuntimeMessage); - styleVariables.remove(); + actualStyle.remove(); } function onChange(cm, event) { @@ -229,24 +217,112 @@ function createAppliesToLineWidget(cm) { } function updateWidgetStyle() { - const gutterStyle = getComputedStyle(cm.getGutterElement()); - const borderStyle = gutterStyle.borderRightWidth !== '0px' ? - `${gutterStyle.borderRightWidth} ${gutterStyle.borderRightStyle} ${gutterStyle.borderRightColor}` : - `1px solid ${gutterStyle.color}`; - const id = Date.now(); - styleVariables.textContent = ` - .single-editor { - --at-background-color-${id}: ${gutterStyle.backgroundColor}; - --at-border-top-${id}: ${borderStyle}; - --at-border-bottom-${id}: ${borderStyle}; - } + const MIN_LUMA = .05; + const MIN_LUMA_DIFF = .4; + const color = { + wrapper: getRealColors(cm.display.wrapper), + gutter: getRealColors(cm.display.gutters, { + bg: 'backgroundColor', + border: 'borderRightColor', + }), + line: getRealColors('.CodeMirror-linenumber'), + comment: getRealColors('span.cm-comment'), + }; + const hasBorder = + color.gutter.style.borderRightWidth !== '0px' && + !/transparent|\b0\)/g.test(color.gutter.style.borderRightColor); + const diff = { + wrapper: Math.abs(color.gutter.bgLuma - color.wrapper.foreLuma), + border: hasBorder ? Math.abs(color.gutter.bgLuma - color.gutter.borderLuma) : 0, + line: Math.abs(color.gutter.bgLuma - color.line.foreLuma), + }; + const preferLine = diff.line > diff.wrapper || diff.line > MIN_LUMA_DIFF; + const fore = preferLine ? color.line.fore : color.wrapper.fore; + + const border = fore.replace(/[\d.]+(?=\))/, MIN_LUMA_DIFF / 2); + const borderStyleForced = `1px ${hasBorder ? color.gutter.style.borderRightStyle : 'solid'} ${border}`; + + actualStyle.textContent = ` .applies-to { - background-color: var(--at-background-color-${id}); - border-top: var(--at-border-top-${id}); - border-bottom: var(--at-border-bottom-${id}); + background-color: ${color.gutter.bg}; + border-top: ${borderStyleForced}; + border-bottom: ${borderStyleForced}; + } + .applies-to label { + color: ${fore}; + } + .applies-to input, + .applies-to select { + background-color: rgba(255, 255, 255, ${ + Math.max(MIN_LUMA, Math.pow(Math.max(0, color.gutter.bgLuma - MIN_LUMA * 2), 2)).toFixed(2) + }); + border: ${borderStyleForced}; + transition: none; + color: ${fore}; + } + .applies-to .svg-icon.select-arrow { + fill: ${fore}; + transition: none; } `; - document.documentElement.appendChild(styleVariables); + document.documentElement.appendChild(actualStyle); + + function getRealColors(el, targets = {}) { + targets.fore = 'color'; + const colors = {}; + const done = {}; + let numDone = 0; + let numTotal = 0; + for (const k in targets) { + colors[k] = {r: 255, g: 255, b: 255, a: 1}; + numTotal++; + } + const isDummy = typeof el === 'string'; + el = isDummy ? cm.display.lineDiv.appendChild($create(el, {style: 'display: none'})) : el; + for (let current = el; current; current = current && current.parentElement) { + const style = getComputedStyle(current); + for (const k in targets) { + if (!done[k]) { + done[k] = blend(colors[k], style[targets[k]]); + numDone += done[k] ? 1 : 0; + if (numDone === numTotal) { + current = null; + break; + } + } + } + colors.style = colors.style || style; + } + if (isDummy) { + el.remove(); + } + for (const k in targets) { + const {r, g, b, a} = colors[k]; + colors[k] = `rgba(${r}, ${g}, ${b}, ${a})`; + // https://www.w3.org/TR/AERT#color-contrast + colors[k + 'Luma'] = (r * .299 + g * .587 + b * .114) / 256; + } + return colors; + } + + function blend(base, color) { + const [r, g, b, a = 255] = (color.match(/\d+/g) || []).map(Number); + if (a === 255) { + base.r = r; + base.g = g; + base.b = b; + base.a = 1; + } else if (a) { + const mixedA = 1 - (1 - a / 255) * (1 - base.a); + const q1 = a / 255 / mixedA; + const q2 = base.a * (1 - mixedA) / mixedA; + base.r = Math.round(r * q1 + base.r * q2); + base.g = Math.round(g * q1 + base.g * q2); + base.b = Math.round(b * q1 + base.b * q2); + base.a = mixedA; + } + return Math.abs(base.a - 1) < 1e-3; + } } function doUpdate() { diff --git a/edit/codemirror-default.css b/edit/codemirror-default.css index 72370f4c..66ef1966 100644 --- a/edit/codemirror-default.css +++ b/edit/codemirror-default.css @@ -56,7 +56,3 @@ position: absolute; pointer-events: none; } - -.CodeMirror-activeline .applies-to ul { - z-index: 2; -}