toggle narrow width mode tooltips via html+css; code cosmetics

This commit is contained in:
tophf 2017-12-19 02:48:47 +03:00
parent 1348eeb4e3
commit e50ff316ba
3 changed files with 41 additions and 73 deletions

View File

@ -179,17 +179,26 @@
class="CodeMirror-search-field" rows="1" required class="CodeMirror-search-field" rows="1" required
spellcheck="false"></textarea> spellcheck="false"></textarea>
</div> </div>
<button data-action="replace" i18n-text="replace" disabled> <button data-action="replace" i18n-text="replace" disabled></button>
<button data-action="replaceAll" i18n-text="replaceAll" disabled></button>
<button data-action="undo" i18n-text="undo" disabled></button>
<!--
Using a separate set of buttons because
1. FF can display tooltips only when specified on the <button>, ignores the nested <title> in <svg>
2. the icon doesn't fill the entire button area so tooltips aren't shown when the edges are hovered
-->
<button class="hidden" data-action="replace" i18n-title="replace" disabled>
<svg class="svg-icon" viewBox="0 0 20 20"> <svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/> <polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/>
</svg> </svg>
</button> </button>
<button data-action="replaceAll" i18n-text="replaceAll" disabled> <button class="hidden" data-action="replaceAll" i18n-title="replaceAll" disabled>
<svg class="svg-icon" viewBox="0 0 20 20"> <svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/> <polygon points="15.8,1.8 8.8,8.8 5.2,5.3 3.5,6.9 8.8,12.2 17.5,3.4 "/>
<polygon points="15.8,7.8 8.8,14.8 5.2,11.3 3.5,12.9 8.8,18.2 17.5,9.4 "/>
</svg> </svg>
</button> </button>
<button data-action="undo" i18n-text="undo" disabled> <button class="hidden" data-action="undo" i18n-title="undo" disabled>
<svg class="svg-icon" viewBox="0 0 20 20"> <svg class="svg-icon" viewBox="0 0 20 20">
<path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/> <path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/>
</svg> </svg>

View File

@ -209,10 +209,15 @@
right: 0; right: 0;
max-width: none; max-width: none;
} }
#search-replace-dialog[data-type="replace"] button { #search-replace-dialog[data-type="replace"] button:not(.hidden) {
display: none !important;
}
#search-replace-dialog[data-type="replace"] button.hidden {
display: block !important;
font-size: 0; font-size: 0;
width: 24px; width: 24px;
text-align: center; text-align: center;
cursor: pointer;
} }
#search-replace-dialog[data-type="replace"] button svg { #search-replace-dialog[data-type="replace"] button svg {
display: inline; display: inline;
@ -222,11 +227,4 @@
max-width: calc(50vw - 120px); max-width: calc(50vw - 120px);
width: auto; width: auto;
} }
#search-replace-dialog[data-type="replace"] button[data-action="replaceAll"] svg {
top: -3px;
position: relative;
/* unprefixed since Chrome 53 */
-webkit-filter: drop-shadow(0 5px 0 currentColor);
filter: drop-shadow(0 5px 0 currentColor);
}
} }

View File

@ -22,16 +22,6 @@ onDOMready().then(() => {
const RX_MAYBE_REGEXP = /^\s*\/(.+?)\/([simguy]*)\s*$/; const RX_MAYBE_REGEXP = /^\s*\/(.+?)\/([simguy]*)\s*$/;
const NARROW_WIDTH = [...document.styleSheets]
.filter(({href}) => href && href.endsWith('global-search.css'))
.map(sheet =>
[...sheet.cssRules]
.filter(r => r.media && r.conditionText.includes('max-width'))
.map(r => r.conditionText.match(/\d+/) | 0)
.sort((a, b) => a - b)
.pop())
.pop() || 800;
const state = { const state = {
// used for case-sensitive matching directly // used for case-sensitive matching directly
find: '', find: '',
@ -188,10 +178,12 @@ onDOMready().then(() => {
state.replaceValue = state.replace.replace(/(\\r)?\\n/g, '\n').replace(/\\t/g, '\t'); state.replaceValue = state.replace.replace(/(\\r)?\\n/g, '\n').replace(/\\t/g, '\t');
state.replaceHasRefs = /\$[$&`'\d]/.test(state.replaceValue); state.replaceHasRefs = /\$[$&`'\d]/.test(state.replaceValue);
} }
const cmFocused = document.activeElement && document.activeElement.closest('.CodeMirror');
state.activeAppliesTo = $(`.${APPLIES_VALUE_CLASS}:focus, .${APPLIES_VALUE_CLASS}.${TARGET_CLASS}`); state.activeAppliesTo = $(`.${APPLIES_VALUE_CLASS}:focus, .${APPLIES_VALUE_CLASS}.${TARGET_CLASS}`);
state.cmStart = CodeMirror.closestVisible( state.cmStart = CodeMirror.closestVisible(
document.activeElement && document.activeElement.closest('.CodeMirror') && document.activeElement || cmFocused && document.activeElement ||
state.activeAppliesTo || state.cm); state.activeAppliesTo ||
state.cm);
} }
@ -208,21 +200,19 @@ onDOMready().then(() => {
return; return;
} }
initState(); initState();
if (!state.find) {
clearMarker();
makeTargetVisible(null);
setupOverlay(editors.slice());
showTally(0, 0);
return;
}
const {cmStart} = state; const {cmStart} = state;
const {index, found, foundInCode} = doSearchInEditors({cmStart, canAdvance, inApplies}) || {}; const {index, found, foundInCode} = state.find && doSearchInEditors({cmStart, canAdvance, inApplies}) || {};
if (!foundInCode) clearMarker(); if (!foundInCode) clearMarker();
if (!found) makeTargetVisible(null); if (!found) makeTargetVisible(null);
const radiateFrom = foundInCode ? index : editors.indexOf(cmStart); const radiateFrom = foundInCode ? index : editors.indexOf(cmStart);
setupOverlay(radiateArray(editors, radiateFrom)); setupOverlay(radiateArray(editors, radiateFrom));
enableReplaceButtons(foundInCode); enableReplaceButtons(foundInCode);
debounce(showTally, 0, foundInCode && !state.numFound ? 1 : undefined); if (state.find) {
const firstSuccessfulSearch = foundInCode && !state.numFound;
debounce(showTally, 0, firstSuccessfulSearch ? 1 : undefined);
} else {
showTally(0, 0);
}
} }
@ -326,7 +316,7 @@ onDOMready().then(() => {
if (cursor) { if (cursor) {
state.undoHistory.push([cm]); state.undoHistory.push([cm]);
state.undo.disabled = false; enableUndoButton(true);
} }
} }
@ -338,7 +328,7 @@ onDOMready().then(() => {
if (found.length) { if (found.length) {
state.lastFind = null; state.lastFind = null;
state.undoHistory.push(found); state.undoHistory.push(found);
state.undo.disabled = false; enableUndoButton(true);
doSearch({canAdvance: false}); doSearch({canAdvance: false});
} }
} }
@ -388,8 +378,8 @@ onDOMready().then(() => {
undoneSome = true; undoneSome = true;
} }
} }
state.undo.disabled = !state.undoHistory.length; enableUndoButton(state.undoHistory.length);
(state.undo.disabled ? state.input : state.undo).focus(); (state.undoHistory.length ? state.undo : state.input).focus();
if (undoneSome) { if (undoneSome) {
state.lastFind = null; state.lastFind = null;
restoreWindowScrollPos(); restoreWindowScrollPos();
@ -463,7 +453,6 @@ onDOMready().then(() => {
cmState.annotateTimer = 0; cmState.annotateTimer = 0;
} }
if (hasMatches) { if (hasMatches) {
if (cmState.annotateTimer) clearTimeout(cmState.annotateTimer);
cmState.annotateTimer = setTimeout(annotateScrollbar, ANNOTATE_SCROLLBAR_DELAY, cmState.annotateTimer = setTimeout(annotateScrollbar, ANNOTATE_SCROLLBAR_DELAY,
cm, query, state.icase); cm, query, state.icase);
} }
@ -481,11 +470,10 @@ onDOMready().then(() => {
const match = this.query.exec(stream.string); const match = this.query.exec(stream.string);
if (match && match.index === stream.pos) { if (match && match.index === stream.pos) {
this.numFound++; this.numFound++;
//state.numFound++;
const t = performance.now(); const t = performance.now();
if (t - this.tallyShownTime > 10) { if (t - this.tallyShownTime > 10) {
debounce(showTally);
this.tallyShownTime = t; this.tallyShownTime = t;
debounce(showTally);
} }
stream.pos += match[0].length || 1; stream.pos += match[0].length || 1;
return MATCH_TOKEN_NAME; return MATCH_TOKEN_NAME;
@ -573,6 +561,7 @@ onDOMready().then(() => {
#search-replace-dialog [data-action="case"] { #search-replace-dialog [data-action="case"] {
color: ${colors.icon.fill}; color: ${colors.icon.fill};
} }
#search-replace-dialog[data-type="replace"] button:hover svg,
#search-replace-dialog svg:hover { #search-replace-dialog svg:hover {
fill: inherit; fill: inherit;
} }
@ -587,20 +576,11 @@ onDOMready().then(() => {
document.body.appendChild(dialog); document.body.appendChild(dialog);
dispatchEvent(new Event('showHotkeyInTooltip')); dispatchEvent(new Event('showHotkeyInTooltip'));
measureInput(state.input);
adjustTextareaSize(state.input); adjustTextareaSize(state.input);
if (type === 'replace') { if (type === 'replace') {
measureInput(state.input2);
adjustTextareaSize(state.input2); adjustTextareaSize(state.input2);
enableReplaceButtons(state.find !== ''); enableReplaceButtons(state.find !== '');
enableUndoButton(state.undoHistory.length);
addEventListener('resize', toggleReplaceButtonTooltips, {passive: true});
toggleReplaceButtonTooltips(true);
state.undo = $('[data-action="undo"]');
state.undo.disabled = !state.undoHistory.length;
} else {
removeEventListener('resize', toggleReplaceButtonTooltips, {passive: true});
} }
return dialog; return dialog;
@ -620,20 +600,11 @@ onDOMready().then(() => {
} }
function measureInput(input) {
const style = getComputedStyle(input);
input._padding = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
input._maxWidth = parseFloat(style.maxWidth);
input._rowHeight = input.clientHeight - input._padding;
}
function destroyDialog({restoreFocus = false} = {}) { function destroyDialog({restoreFocus = false} = {}) {
state.input = null; state.input = null;
$.remove(DIALOG_SELECTOR); $.remove(DIALOG_SELECTOR);
debounce.unregister(doSearch); debounce.unregister(doSearch);
makeTargetVisible(null); makeTargetVisible(null);
removeEventListener('resize', toggleReplaceButtonTooltips, {passive: true});
if (restoreFocus) setTimeout(focusNoScroll, 0, state.originalFocus); if (restoreFocus) setTimeout(focusNoScroll, 0, state.originalFocus);
} }
@ -675,20 +646,10 @@ onDOMready().then(() => {
} }
} }
function enableUndoButton(enabled) {
function toggleReplaceButtonTooltips(debounced) { if (state.dialog && state.dialog.dataset.type === 'replace') {
if (debounced !== true) { for (const el of $$('[data-action="undo"]', state.dialog)) {
debounce(toggleReplaceButtonTooltips, 0, true); el.disabled = !enabled;
} else {
const addTitle = window.innerWidth <= NARROW_WIDTH;
for (const el of state.dialog.getElementsByTagName('button')) {
if (addTitle && !el.title) {
el.title = el.textContent;
} else if (!addTitle && el.title) {
el.title = '';
} else {
break;
}
} }
} }
} }