style-search-db.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. /* global API */// msg.js
  2. /* global RX_META debounce stringAsRegExp tryRegExp */// toolbox.js
  3. /* global addAPI */// common.js
  4. 'use strict';
  5. (() => {
  6. // toLocaleLowerCase cache, autocleared after 1 minute
  7. const cache = new Map();
  8. const METAKEYS = ['customName', 'name', 'url', 'installationUrl', 'updateUrl'];
  9. const extractMeta = style =>
  10. style.usercssData
  11. ? (style.sourceCode.match(RX_META) || [''])[0]
  12. : null;
  13. const stripMeta = style =>
  14. style.usercssData
  15. ? style.sourceCode.replace(RX_META, '')
  16. : null;
  17. const MODES = Object.assign(Object.create(null), {
  18. code: (style, test) =>
  19. style.usercssData
  20. ? test(stripMeta(style))
  21. : searchSections(style, test, 'code'),
  22. meta: (style, test, part) =>
  23. METAKEYS.some(key => test(style[key])) ||
  24. test(part === 'all' ? style.sourceCode : extractMeta(style)) ||
  25. searchSections(style, test, 'funcs'),
  26. name: (style, test) =>
  27. test(style.customName) ||
  28. test(style.name),
  29. all: (style, test) =>
  30. MODES.meta(style, test, 'all') ||
  31. !style.usercssData && MODES.code(style, test),
  32. });
  33. addAPI(/** @namespace API */ {
  34. styles: {
  35. /**
  36. * @param params
  37. * @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
  38. * @param {'name'|'meta'|'code'|'all'|'url'} [params.mode=all]
  39. * @param {number[]} [params.ids] - if not specified, all styles are searched
  40. * @returns {number[]} - array of matched styles ids
  41. */
  42. async searchDB({query, mode = 'all', ids}) {
  43. let res = [];
  44. if (mode === 'url' && query) {
  45. res = (await API.styles.getByUrl(query)).map(r => r.style.id);
  46. } else if (mode in MODES) {
  47. const modeHandler = MODES[mode];
  48. const m = /^\/(.+?)\/([gimsuy]*)$/.exec(query);
  49. const rx = m && tryRegExp(m[1], m[2]);
  50. const test = rx ? rx.test.bind(rx) : createTester(query);
  51. res = (await API.styles.getAll())
  52. .filter(style =>
  53. (!ids || ids.includes(style.id)) &&
  54. (!query || modeHandler(style, test)))
  55. .map(style => style.id);
  56. if (cache.size) debounce(clearCache, 60e3);
  57. }
  58. return res;
  59. },
  60. },
  61. });
  62. function createTester(query) {
  63. const flags = `u${lower(query) === query ? 'i' : ''}`;
  64. const words = query
  65. .split(/(".*?")|\s+/)
  66. .filter(Boolean)
  67. .map(w => w.startsWith('"') && w.endsWith('"')
  68. ? w.slice(1, -1)
  69. : w)
  70. .filter(w => w.length > 1);
  71. const rxs = (words.length ? words : [query])
  72. .map(w => stringAsRegExp(w, flags));
  73. return text => rxs.every(rx => rx.test(text));
  74. }
  75. function searchSections({sections}, test, part) {
  76. const inCode = part === 'code' || part === 'all';
  77. const inFuncs = part === 'funcs' || part === 'all';
  78. for (const section of sections) {
  79. for (const prop in section) {
  80. const value = section[prop];
  81. if (inCode && prop === 'code' && test(value) ||
  82. inFuncs && Array.isArray(value) && value.some(str => test(str))) {
  83. return true;
  84. }
  85. }
  86. }
  87. }
  88. function lower(text) {
  89. let result = cache.get(text);
  90. if (!result) cache.set(text, result = text.toLocaleLowerCase());
  91. return result;
  92. }
  93. function clearCache() {
  94. cache.clear();
  95. }
  96. })();