From 5047ceb1fac1b893b01b6e4b51d90abe7b05b21f Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Mon, 23 Mar 2020 07:25:50 -0500 Subject: [PATCH] Switch to filter text/buttons --- _locales/en/messages.json | 4 +- background/search-db.js | 228 ++++++++++++++++++++------- manage.html | 321 ++++++++++++-------------------------- manage/bulk-actions.js | 1 - manage/filters.js | 60 ++----- manage/import-export.js | 6 +- manage/manage-actions.js | 91 ++++++++--- manage/manage-ui.js | 65 ++++++++ manage/manage.css | 122 +++++++++++---- manage/updater-ui.js | 26 ++- 10 files changed, 534 insertions(+), 390 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 9bcf3a80..e7ed173f 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1253,8 +1253,8 @@ "description": "Text for button to apply the selected action" }, "bulkActionsTooltip": { - "message": "Click to open the filter, search and bulk actions panel", - "description": "Text for button to apply the selected action" + "message": "Bulk actions can be applied to selected styles in this column", + "description": "Select style for bulk action header tooltip" }, "bulkActionsError": { "message": "Choose at least one style", diff --git a/background/search-db.js b/background/search-db.js index 75318304..4f9c9a2d 100644 --- a/background/search-db.js +++ b/background/search-db.js @@ -4,13 +4,108 @@ (() => { // toLocaleLowerCase cache, autocleared after 1 minute const cache = new Map(); - // top-level style properties to be searched - const PARTS = { - name: searchText, - url: searchText, - sourceCode: searchText, - sections: searchSections, - }; + + // Creates an array of intermediate words (2 letter minimum) + // 'usercss' => ["us", "use", "user", "userc", "usercs", "usercss"] + // this makes it so the user can type partial queries and not have the search + // constantly switching between using & ignoring the filter + const createPartials = id => id.split('').reduce((acc, _, index) => { + if (index > 0) { + acc.push(id.substring(0, index + 1)); + } + return acc; + }, []); + + const searchWithin = [{ + id: 'code', + labels: createPartials('code'), + get: style => style.sections.map(section => section.code).join(' ') + }, { + id: 'usercss', + labels: [...createPartials('usercss'), ...createPartials('meta')], + get: style => JSON.stringify(style.usercssData || {}) + // remove JSON structure; restore urls + .replace(/[[\]{},":]/g, ' ').replace(/\s\/\//g, '://') + }, { + id: 'name', // default + labels: createPartials('name'), + get: style => style.name + }]; + + const styleProps = [{ + id: 'enabled', + labels: ['on', ...createPartials('enabled')], + check: style => style.enabled + }, { + id: 'disabled', + labels: ['off', ...createPartials('disabled')], + check: style => !style.enabled + }, { + id: 'local', + labels: createPartials('local'), + check: style => !style.updateUrl + }, { + id: 'external', + labels: createPartials('external'), + check: style => style.updateUrl + }, { + id: 'usercss', + labels: createPartials('usercss'), + check: style => style.usercssData + }, { + id: 'non usercss', + labels: ['original', ...createPartials('nonusercss')], + check: style => !style.usercssData + }]; + + const matchers = [{ + id: 'url', + test: query => /url:\w+/i.test(query), + matches: query => { + const matchUrl = query.match(/url:([/.-_\w]+)/); + const result = matchUrl && matchUrl[1] + ? styleManager.getStylesByUrl(matchUrl[1]) + .then(result => result.map(r => r.data.id)) + : []; + return {result}; + }, + }, { + id: 'regex', + test: query => { + const x = query.includes('/') && !query.includes('//') && + /^\/(.+?)\/([gimsuy]*)$/.test(query); + // console.log('regex match?', query, x); + return x; + }, + matches: () => ({regex: tryRegExp(RegExp.$1, RegExp.$2)}) + }, { + id: 'props', + test: query => /is:/.test(query), + matches: query => { + const label = /is:(\w+)/g.exec(query); + return label && label[1] + ? {prop: styleProps.find(p => p.labels.includes(label[1]))} + : {}; + } + }, { + id: 'within', + test: query => /in:/.test(query), + matches: query => { + const label = /in:(\w+)/g.exec(query); + return label && label[1] + ? {within: searchWithin.find(s => s.labels.includes(label[1]))} + : {}; + } + }, { + id: 'default', + test: () => true, + matches: query => { + const word = query.startsWith('"') && query.endsWith('"') + ? query.slice(1, -1) + : query; + return {word: word || query}; + } + }]; /** * @param params @@ -19,76 +114,93 @@ * @returns {number[]} - array of matched styles ids */ API_METHODS.searchDB = ({query, ids}) => { - let rx, words, icase, matchUrl; - query = query.trim(); + const parts = query.trim().split(/(".*?")|\s+/).filter(Boolean); - if (/^url:/i.test(query)) { - matchUrl = query.slice(query.indexOf(':') + 1).trim(); - if (matchUrl) { - return styleManager.getStylesByUrl(matchUrl) - .then(results => results.map(r => r.data.id)); + const searchFilters = { + words: [], + regex: null, // only last regex expression is used + results: [], + props: [], + within: [], + }; + + const searchText = (text, searchFilters) => { + if (searchFilters.regex) return searchFilters.regex.test(text); + for (let pass = 1; pass <= (searchFilters.icase ? 2 : 1); pass++) { + if (searchFilters.words.every(w => text.includes(w))) return true; + text = lower(text); } + }; + + const searchProps = (style, searchFilters) => { + const x = searchFilters.props.every(prop => { + const y = prop.check(style) + // if (y) console.log('found prop', prop.id, style.id) + return y; + }); + // if (x) console.log('found prop', style.id) + return x; + }; + + parts.forEach(part => { + matchers.some(matcher => { + if (matcher.test(part)) { + const {result, regex, word, prop, within} = matcher.matches(part || ''); + if (result) searchFilters.results.push(result); + if (regex) searchFilters.regex = regex; // limited to a single regexp + if (word) searchFilters.words.push(word); + if (prop) searchFilters.props.push(prop); + if (within) searchFilters.within.push(within); + return true; + } + }); + }); + if (!searchFilters.within.length) { + searchFilters.within.push(...searchWithin.slice(-1)); } - if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) { - rx = tryRegExp(RegExp.$1, RegExp.$2); - } - if (!rx) { - words = query - .split(/(".*?")|\s+/) - .filter(Boolean) - .map(w => w.startsWith('"') && w.endsWith('"') - ? w.slice(1, -1) - : w) - .filter(w => w.length > 1); - words = words.length ? words : [query]; - icase = words.some(w => w === lower(w)); + + // console.log('matchers', searchFilters); + // url matches + if (searchFilters.results.length) { + return searchFilters.results; } + searchFilters.icase = searchFilters.words.some(w => w === lower(w)); + query = parts.join(' ').trim(); return styleManager.getAllStyles().then(styles => { if (ids) { const idSet = new Set(ids); styles = styles.filter(s => idSet.has(s.id)); } + const results = []; + const propResults = []; + const hasProps = searchFilters.props.length > 0; + const noWords = searchFilters.words.length === 0; for (const style of styles) { const id = style.id; - if (!query || words && !words.length) { + if (noWords) { + // no query or only filters are matching -> show all styles results.push(id); - continue; - } - for (const part in PARTS) { - const text = style[part]; - if (text && PARTS[part](text, rx, words, icase)) { + } else { + const text = searchFilters.within.map(within => within.get(style)).join(' '); + if (searchText(text, searchFilters)) { results.push(id); - break; } } - } - if (cache.size) debounce(clearCache, 60e3); - return results; - }); - }; - - function searchText(text, rx, words, icase) { - if (rx) return rx.test(text); - for (let pass = 1; pass <= (icase ? 2 : 1); pass++) { - if (words.every(w => text.includes(w))) return true; - text = lower(text); - } - } - - function searchSections(sections, rx, words, icase) { - for (const section of sections) { - for (const prop in section) { - const value = section[prop]; - if (typeof value === 'string') { - if (searchText(value, rx, words, icase)) return true; - } else if (Array.isArray(value)) { - if (value.some(str => searchText(str, rx, words, icase))) return true; + if (hasProps && searchProps(style, searchFilters) && results.includes(id)) { + propResults.push(id); } } - } - } + // results AND propResults + const finalResults = hasProps + ? propResults.filter(id => results.includes(id)) + : results; + if (cache.size) debounce(clearCache, 60e3); + // console.log('final', finalResults) + return finalResults; + }); + }; function lower(text) { let result = cache.get(text); diff --git a/manage.html b/manage.html index 9dcb964d..a977d808 100644 --- a/manage.html +++ b/manage.html @@ -221,10 +221,7 @@ - - - + @@ -256,12 +253,6 @@ - - - - - -
@@ -329,98 +320,7 @@ - - - + + + diff --git a/manage/bulk-actions.js b/manage/bulk-actions.js index eb490992..765cb3ce 100644 --- a/manage/bulk-actions.js +++ b/manage/bulk-actions.js @@ -104,7 +104,6 @@ const bulk = { if (installed.dataset.total) { // ignore filter checkboxes if (target.type === 'checkbox' && target.closest('.toggle-all, .entry-filter')) { - handleEvent.toggleBulkActions({hidden: false}); const bulk = $('#toggle-all-filters'); const state = target.checked; const visibleEntries = $$('.entry-filter-toggle') diff --git a/manage/filters.js b/manage/filters.js index a4072d96..1d62b93e 100644 --- a/manage/filters.js +++ b/manage/filters.js @@ -1,4 +1,4 @@ -/* global installed messageBox sorter $ $$ $create t debounce prefs API router */ +/* global installed messageBox sorter $ $$ $create t debounce prefs API UI router resetUpdates */ /* exported filterAndAppend */ 'use strict'; @@ -37,8 +37,9 @@ HTMLSelectElement.prototype.adjustWidth = function () { }; function init() { - $('#search').oninput = e => { - router.updateSearch('search', e.target.value); + $('#search').oninput = event => { + router.updateSearch('search', event.target.value); + UI.updateFilterLabels(); }; $('#search-help').onclick = event => { @@ -57,48 +58,13 @@ function init() { } else { return s; } - })))), + })) + ) + ), buttons: [t('confirmOK')], }); }; - $$('select[id$=".invert"]').forEach(el => { - const slave = $('#' + el.id.replace('.invert', '')); - const slaveData = slave.dataset; - const valueMap = new Map([ - [false, slaveData.filter], - [true, slaveData.filterHide], - ]); - // enable slave control when user switches the value - el.oninput = () => { - if (!slave.checked) { - // oninput occurs before onchange - setTimeout(() => { - if (!slave.checked) { - slave.checked = true; - slave.dispatchEvent(new Event('change', {bubbles: true})); - } - }); - } - }; - // swap slave control's filtering rules - el.onchange = event => { - const value = el.value === 'true'; - const filter = valueMap.get(value); - if (slaveData.filter === filter) { - return; - } - slaveData.filter = filter; - slaveData.filterHide = valueMap.get(!value); - debounce(filterOnChange, 0, event); - // avoid triggering MutationObserver during page load - if (document.readyState === 'complete') { - el.adjustWidth(); - } - }; - el.onchange({target: el}); - }); - $$('[data-filter]').forEach(el => { el.onchange = filterOnChange; if (el.closest('.hidden')) { @@ -108,9 +74,9 @@ function init() { $('#reset-filters').onclick = event => { event.preventDefault(); - if (!filtersSelector.hide) { - return; - } + // if (!filtersSelector.hide) { + // return; + // } for (const el of $$('#tools-wrapper [data-filter]')) { let value; if (el.type === 'checkbox' && el.checked) { @@ -127,6 +93,8 @@ function init() { } filterOnChange({forceRefilter: true}); router.updateSearch('search', ''); + resetUpdates(); + UI.updateFilterLabels(); }; filterOnChange({forceRefilter: true}); @@ -134,7 +102,7 @@ function init() { function filterOnChange({target: el, forceRefilter}) { - const getValue = el => (el.type === 'checkbox' ? el.checked : el.value.trim()); + const getValue = elm => (elm.type === 'search') ? elm.value.trim() : elm.checked; if (!forceRefilter) { const value = getValue(el); if (value === el.lastValue) { @@ -155,6 +123,7 @@ function filterOnChange({target: el, forceRefilter}) { hide: buildFilter(true), unhide: buildFilter(false), }); + console.log('filter on change', filtersSelector, installed) if (installed) { reapplyFilter().then(sorter.updateStripes); } @@ -283,6 +252,7 @@ function searchStyles({immediately, container} = {}) { el.lastValue = query; const entries = container && container.children || container || installed.children; + console.log('search?', query) return API.searchDB({ query, ids: [...entries].map(el => el.styleId), diff --git a/manage/import-export.js b/manage/import-export.js index a3f8f922..47d25504 100644 --- a/manage/import-export.js +++ b/manage/import-export.js @@ -1,5 +1,6 @@ /* global messageBox styleSectionsEqual API onDOMready tryJSONparse scrollElementIntoView $ $$ API $create t animateElement + handleEvent styleJSONseemsValid */ 'use strict'; @@ -46,8 +47,9 @@ onDOMready().then(() => { this.ondragend(); if (event.dataTransfer.files.length) { event.preventDefault(); - if ($('#only-updates input').checked) { - $('#only-updates input').click(); + const updates = $('#only-updates'); + if (updates.checked) { + handleEvent.checkFilterSelectors(updates); } importFromFile({file: event.dataTransfer.files[0]}); } diff --git a/manage/manage-actions.js b/manage/manage-actions.js index 27c2af06..bed5abaf 100644 --- a/manage/manage-actions.js +++ b/manage/manage-actions.js @@ -1,7 +1,7 @@ /* global messageBox getStyleWithNoCode filterAndAppend showFiltersStats - checkUpdate handleUpdateInstalled + checkUpdate handleUpdateInstalled resetUpdates objectDiff configDialog sorter msg prefs API onDOMready $ $$ setupLivePrefs @@ -80,8 +80,19 @@ function initGlobalEvents() { $('#update-all').onclick = event => { event.preventDefault(); - handleEvent.toggleBulkActions({hidden: false}); bulk.updateAll(); + }; + + $('#filters-wrapper').onclick = event => { + event.preventDefault(); + handleEvent.toggleFilter(event.target); + }; + + $('#search').onsearch = event => { + if (event.target.value === '') { + console.log('search empty') + handleEvent.resetFilters(); + } } $$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external)); @@ -95,28 +106,12 @@ function initGlobalEvents() { $$('.applies-to-extra[open]').forEach(el => { el.removeAttribute('open'); }); - // Close bulk actions - handleEvent.toggleBulkActions({hidden: true}); } else if (event.which === 32 && event.target.classList.contains('checkmate')) { // pressing space toggles the containing checkbox $('input[type="checkbox"]', event.target).click(); } }); - $$('[data-toggle-on-click]').forEach(el => { - // dataset on SVG doesn't work in Chrome 49-??, works in 57+ - const target = $(el.getAttribute('data-toggle-on-click')); - el.onclick = event => { - event.preventDefault(); - target.classList.toggle('hidden'); - if (target.classList.contains('hidden')) { - el.removeAttribute('open'); - } else { - el.setAttribute('open', ''); - } - }; - }); - // triggered automatically by setupLivePrefs() below enforceInputRange($('#manage.newUI.targets')); @@ -146,7 +141,6 @@ Object.assign(handleEvent, { '.update': 'update', '.entry-delete': 'delete', '.entry-configure-usercss': 'config', - '.header-filter': 'toggleBulkActions', '.sortable': 'updateSort', '#applies-to-config': 'appliesConfig', '.applies-to-extra-expander': 'toggleExtraAppliesTo' @@ -215,12 +209,6 @@ Object.assign(handleEvent, { UI.addLabels(entry); }, - toggleBulkActions({hidden}) { - const tools = $('#tools-wrapper'); - tools.classList.toggle('hidden', hidden); - $('.header-filter').classList.toggle('active', !tools.classList.contains('hidden')); - }, - toggleExtraAppliesTo(event, entry) { event.preventDefault(); entry.classList.toggle('hide-extra'); @@ -230,6 +218,59 @@ Object.assign(handleEvent, { } }, + resetFilters() { + $('#reset-filters').click(); + // TODO: figure out why we need to press this twice + $('#reset-filters').click(); + resetUpdates(); + }, + + toggleFilter(el) { + if (el.classList.contains('reset-filters')) { + return handleEvent.resetFilters(); + } + + const target = (el.nodeName === 'LABEL') ? $('input', el) : el; + const type = Object.values(UI.searchFilters).find(filter => filter.id === target.id); + const filterQuery = type && type.query || ''; + const remove = type && type.invert ? UI.searchFilters[type.invert].query : ''; + const len = filterQuery.length + 1; + const search = $('#search'); + + let {selectionStart, selectionEnd, value} = search; + if (value.includes(filterQuery)) { + value = ` ${value} `.replace(` ${filterQuery} `, ' ').trim(); + if (selectionEnd > value.length) { + selectionStart -= len; + selectionEnd -= len; + } + } else { + if (selectionEnd === value.length) { + selectionStart += len; + selectionEnd += len; + } + value = (` ${value} ${filterQuery} `.replace(` ${remove} `, ' ')).trim(); + } + search.value = value; + search.selectionStart = selectionStart; + search.selectionEnd = selectionEnd; + search.focus(); + router.updateSearch('search', value); + UI.updateFilterLabels(); + // updates or issues (special case) + if (target.dataset.filterSelectors) { + handleEvent.checkFilterSelectors(target); + } + }, + + checkFilterSelectors(target) { + const selectors = target.dataset.filterSelectors; + const checked = target.classList.contains('checked'); + $$('.entry').forEach(entry => { + entry.classList.toggle('hidden', checked && !entry.matches(selectors)); + }); + }, + check(event, entry) { event.preventDefault(); checkUpdate(entry, {single: true}); diff --git a/manage/manage-ui.js b/manage/manage-ui.js index 708af194..dd601ece 100644 --- a/manage/manage-ui.js +++ b/manage/manage-ui.js @@ -40,6 +40,11 @@ const UI = { content: "${t('filteredStylesAllHidden')}"; } `)); + // remove update filter on init + const search = $('#search'); + search.value = search.value.replace(UI.searchFilters.updatable.query, ''); + // update filter labels to match location.search + UI.updateFilterLabels(); }, showStyles: (styles = [], matchUrlIds) => { @@ -178,6 +183,24 @@ const UI = { : ''; }, + updateFilterLabels: () => { + const filterLabels = $$('#filters-wrapper .search-filter input'); + filterLabels.forEach(cb => { + cb.checked = false; + cb.parentElement.classList.remove('checked'); + }); + const filters = Object.values(UI.searchFilters); + $('#search').value.split(' ').forEach(part => { + const filter = filters.find(entry => entry.query === part); + if (filter) { + const button = filterLabels.filter(btn => btn.id === filter.id); + if (button.length) { + button[0].checked = true; + button[0].parentElement.classList.add('checked'); + } + } + }); + }, createStyleTargetsElement: ({entry, style}) => { const parts = UI._parts; @@ -226,6 +249,48 @@ const UI = { entry.classList.toggle('global', !numTargets); }, + // This order matters + searchFilters: { + enabled: { + id: 'manage.onlyEnabled', + query: 'is:enabled', + invert: 'disabled' + }, + disabled: { + id: 'manage.onlyEnabled.invert', + query: 'is:disabled', + invert: 'enabled' + }, + usercss: { + id: 'manage.onlyUsercss', + query: 'is:usercss', + invert: 'original' + }, + original: { + id: 'manage.onlyUsercss.invert', + query: 'is:nonusercss', + invert: 'usercss' + }, + local: { + id: 'manage.onlyLocal', + query: 'is:local', + invert: 'external' + }, + external: { + id: 'manage.onlyLocal.invert', + query: 'is:external', + invert: 'local' + }, + // only checkbox; all others are radio buttons + updatable: { + id: 'only-updates', + query: '', // 'has:updates', + }, + reset: { + id: 'reset-filters', + query: '' + } + }, getFaviconImgSrc: (container = installed) => { if (!UI.favicons) return; diff --git a/manage/manage.css b/manage/manage.css index 02717fa9..6b72353c 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -4,7 +4,7 @@ --favicon-size: 16px; --narrow-column: 60px; --header-height: 40px; - --toolbar-height: 60px; + --toolbar-height: 40px; --entry-height: 30px; --onoffswitch-width: 60px; @@ -15,6 +15,7 @@ --header-icon-hover-color: #2afefe; --tools-bg-color: #ccc; + --tools-bg-hover: #eee; --entry-header-bg-color: #ddd; --entry-header-text-color: #111; @@ -71,13 +72,17 @@ a:hover { color: var(--entry-text-hover); } +body a[disabled], +body button[disabled] { + cursor: default; +} + .invisible { visibility: hidden; pointer-events: none; } .svg-icon { - cursor: pointer; width: var(--entry-icon-size); height: var(--entry-icon-size); vertical-align: middle; @@ -85,6 +90,10 @@ a:hover { fill: var(--entry-icon-color); } +.svg-icon:not(.no-pointer) { + cursor: pointer; +} + #main-header .svg-icon { width: var(--header-icon-size); height: var(--header-icon-size); @@ -122,6 +131,7 @@ a:hover { #bulk-actions { justify-content: flex-start; + align-items: center; } #bulk-info > span, @@ -147,9 +157,9 @@ a:hover { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } -.entry-header a:hover .svg-icon, -.entry a:hover .svg-icon, -.svg-icon:hover { +.entry-header a:hover .svg-icon:not(.no-pointer), +.entry a:hover .svg-icon:not(.no-pointer), +.svg-icon:not(.no-pointer):hover { fill: var(--entry-icon-hover-color); } @@ -340,14 +350,6 @@ body.all-styles-hidden-by-filters #installed:after { content: '▾'; } -.header-filter span:before { - content: '►'; - color: var(--entry-icon-color); - position: relative; - left: 7px; -} - -.header-filter:hover span:before, .header-filter:hover .svg-icon, .header-filter.active svg { transition: all .5s; @@ -355,11 +357,6 @@ body.all-styles-hidden-by-filters #installed:after { fill: var(--entry-text-hover); } -.header-filter.active span:before { - content: '▲'; - color: var(--entry-text-hover); -} - .targets { flex-wrap: wrap; } @@ -473,7 +470,8 @@ a svg, .svg-icon.sort { } /* Checkbox */ -.checkmate input:checked + svg.checkbox .filled-circle { +.checkmate input:checked + svg.checkbox .filled-circle, +svg.checkbox-enabled .filled-circle { fill: var(--checkbox-bg-color); display: block; } @@ -486,7 +484,8 @@ a svg, .svg-icon.sort { display: none; } -.checkmate input:checked + svg.checkbox .checkmark { +.checkmate input:checked + svg.checkbox .checkmark, +svg.checkbox-enabled .checkmark { fill: var(--checkbox-icon-color); } @@ -693,6 +692,9 @@ a svg, .svg-icon.sort { height: var(--toolbar-height); padding: 4px 0; z-index: 100; + display: flex; + align-items: center; + justify-content: space-between; } #tools-wrapper:not(.hidden) + #installed { @@ -702,20 +704,85 @@ a svg, .svg-icon.sort { top: calc(var(--header-height) + var(--toolbar-height)); } -#tools-wrapper .select-resizer { - background: #fff; -} - .manage-row { padding: 2px 18px; display: flex; align-items: center; - justify-content: space-between; + justify-content: flex-end; + width: 50%; } #filters-wrapper, #bulk-filter-count { + display: flex; + align-items: center; margin-right: 20px; + padding-left: 5px; +} + +#filters-wrapper input { + display: none; +} + +#filters-wrapper svg { + height: 16px; +} + +.manage-row label, +.manage-row button, +.manage-row select { + min-width: 2em; + height: 24px; + max-height: 24px; + line-height: 24px; + padding: 0 6px; + margin: 0 2px; + vertical-align: middle; + align-items: center; + display: inline-flex; + flex-shrink: 0; + cursor: pointer; +} + +#filters-wrapper label:not(.checked):hover { + background-color: var(--tools-bg-hover); +} + +.manage-row select { + padding-right: 20px; +} + +.search-filter .svg-icon, +.search-filter span { + pointer-events: none; + fill: currentColor; +} + +.button-group { + display: inline-flex; +} + +.button-group label:first-child { + margin-right: 0; + border-right-width: 1px; +} + +.button-group label:last-child { + margin-left: 0; + border-left-width: 0; +} + +.manage-row button, +.search-filter { + background: #e0e1e2; + border: 1px #9e9e9e solid; +} + +.search-filter.checked, +.search-filter.checked:hover { + background-color: var(--label-usercss-bg-color); + color: #fff; + fill: #fff; } #search-wrapper { @@ -723,7 +790,8 @@ a svg, .svg-icon.sort { } #search { - width: calc(100% - var(--entry-icon-size) * 1.4); + width: 100%; + height: 2.2em; } #search-help { @@ -751,7 +819,7 @@ a svg, .svg-icon.sort { display: none !important; } -.active #filters-stats, +#filters-stats, #bulk-filter-count:not(:empty) { background-color: var(--checked-count-bg-color); border-color: var(--checked-count-bg-color); diff --git a/manage/updater-ui.js b/manage/updater-ui.js index 24dd6264..af619f69 100644 --- a/manage/updater-ui.js +++ b/manage/updater-ui.js @@ -1,6 +1,6 @@ -/* global messageBox UI filtersSelector filterAndAppend +/* global messageBox UI handleEvent filtersSelector filterAndAppend sorter $ $$ $create API onDOMready scrollElementIntoView t chromeLocal */ -/* exported handleUpdateInstalled */ +/* exported handleUpdateInstalled resetUpdates */ 'use strict'; let updateTimer; @@ -27,6 +27,15 @@ function applyUpdateAll() { }); } +function resetUpdates() { + $('#check-all-updates-force').classList.add('hidden'); + $('#apply-all-updates').classList.add('hidden'); + $('#update-history').classList.add('hidden'); + const checkbox = $('#only-updates'); + checkbox.checked = false; + checkbox.parentElement.classList.add('hidden'); +} + function checkUpdateBulk() { clearTimeout(updateTimer); @@ -120,6 +129,7 @@ function checkUpdate(entry, {single} = {}) { function reportUpdateState({updated, style, error, STATES}) { const isCheckAll = document.body.classList.contains('update-in-progress'); const entry = $(UI.ENTRY_ID_PREFIX + style.id); + if (!entry) return; const newClasses = new Map([ /* When a style is updated/installed, handleUpdateInstalled() clears "updatable" @@ -138,7 +148,10 @@ function reportUpdateState({updated, style, error, STATES}) { if (updated) { newClasses.set('can-update', true); entry.updatedCode = style; - $('#only-updates').classList.remove('hidden'); + const onlyUpdates = $('#only-updates'); + onlyUpdates.parentElement.classList.remove('hidden'); + onlyUpdates.checked = true; + onlyUpdates.change(); } else if (!entry.classList.contains('can-update')) { const same = ( error === STATES.SAME_MD5 || @@ -202,16 +215,15 @@ function reportUpdateState({updated, style, error, STATES}) { } } - function renderUpdatesOnlyFilter({show, check} = {}) { const numUpdatable = $$('.can-update').length; const mightUpdate = numUpdatable > 0 || $('.update-problem'); - const checkbox = $('#only-updates input'); + const checkbox = $('#only-updates'); show = show !== undefined ? show : mightUpdate; check = check !== undefined ? show && check : checkbox.checked && mightUpdate; - $('#only-updates').classList.toggle('hidden', !show); - checkbox.checked = check && show; + checkbox.checked = check; + checkbox.parentElement.classList.toggle('hidden', !show); checkbox.dispatchEvent(new Event('change')); const btnApply = $('#apply-all-updates');