217 lines
6.5 KiB
JavaScript
217 lines
6.5 KiB
JavaScript
/* global API_METHODS styleManager tryRegExp debounce */
|
|
'use strict';
|
|
|
|
(() => {
|
|
// toLocaleLowerCase cache, autocleared after 1 minute
|
|
const cache = new Map();
|
|
|
|
// 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
|
|
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
|
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
|
* @returns {number[]} - array of matched styles ids
|
|
*/
|
|
API_METHODS.searchDB = ({query, ids}) => {
|
|
const parts = query.trim().split(/(".*?")|\s+/).filter(Boolean);
|
|
|
|
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));
|
|
}
|
|
|
|
// 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 (noWords) {
|
|
// no query or only filters are matching -> show all styles
|
|
results.push(id);
|
|
} else {
|
|
const text = searchFilters.within.map(within => within.get(style)).join(' ');
|
|
if (searchText(text, searchFilters)) {
|
|
results.push(id);
|
|
}
|
|
}
|
|
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);
|
|
if (result) return result;
|
|
result = text.toLocaleLowerCase();
|
|
cache.set(text, result);
|
|
return result;
|
|
}
|
|
|
|
function clearCache() {
|
|
cache.clear();
|
|
}
|
|
})();
|