From f5a996a64ee38230f786c30f562483f2e2368691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 16:07:14 +0100 Subject: [PATCH 01/72] Highlight current anchor with css instead of js. The `:target` selector is supported by all browsers. --- .../jsdoc/api/template/static/scripts/main.js | 18 ------------------ .../api/template/static/styles/jaguar.css | 3 ++- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 97dfd0facf..6e609f9d01 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -137,22 +137,4 @@ $(function () { '' + textParts[1] + ''; }); - - // Highlighting current anchor - - var anchors = $('.anchor'); - var _onHashChange = function () { - var activeHash = window.document.location.hash - .replace(/\./g, '\\.') // Escape dot in element id - .replace(/\~/g, '\\~'); // Escape tilde in element id - - anchors.removeClass('highlighted'); - - if (activeHash.length > 0) { - anchors.filter(activeHash).addClass('highlighted'); - } - }; - - $(window).on('hashchange', _onHashChange); - _onHashChange(); }); diff --git a/config/jsdoc/api/template/static/styles/jaguar.css b/config/jsdoc/api/template/static/styles/jaguar.css index 1a677bfa93..88433f363f 100644 --- a/config/jsdoc/api/template/static/styles/jaguar.css +++ b/config/jsdoc/api/template/static/styles/jaguar.css @@ -51,7 +51,8 @@ body { width: 0px; height: 0px; } -.nameContainer .anchor.highlighted + h4 { +/* Highlighting current anchor */ +.nameContainer .anchor:target + h4 { background-color: #faebcc; } a { From 6187118166b485adb64d43dfbbff5d694b09f60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 16 Feb 2020 14:47:01 +0100 Subject: [PATCH 02/72] Hide member-lists, show current item when clearing input --- .../jsdoc/api/template/static/scripts/main.js | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 6e609f9d01..a3a623c934 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -26,6 +26,22 @@ $(function () { return bW - aW; }; + // Show an item related a current documentation automatically + const longname = $('.page-title').data('filename') + .replace(/\.[a-z]+$/, '') + .replace('module-', 'module:') + .replace(/_/g, '/') + .replace(/-/g, '~'); + var $currentItem = $('.navigation .item[data-name*="' + longname + '"]:eq(0)'); + + if ($currentItem.length) { + $currentItem + .prependTo('.navigation .list') + .show() + .find('.member-list') + .show(); + } + // Search Items $('#search').on('keyup', function (e) { var value = $(this).val(); @@ -60,7 +76,9 @@ $(function () { .appendTo(".navigation ul.list"); // append again to the list } else { - $el.find('.item, .member-list').show(); + $currentItem.prependTo('.navigation .list'); + $currentItem.find('.member-list, li').show(); + $el.find('.item').show(); } $el.find('.list').scrollTop(0); @@ -71,23 +89,6 @@ $(function () { $(this).parent().parent().find('.member-list').toggle(); }); - // Show an item related a current documentation automatically - var filename = $('.page-title').data('filename') - .replace(/\.[a-z]+$/, '') - .replace('module-', 'module:') - .replace(/_/g, '/') - .replace(/-/g, '~'); - var $currentItem = $('.navigation .item[data-name*="' + filename + '"]:eq(0)'); - - if ($currentItem.length) { - $currentItem - .remove() - .prependTo('.navigation .list') - .show() - .find('.member-list') - .show(); - } - // Auto resizing on navigation var _onResize = function () { var height = $(window).height(); From b919074aa3b9c85096672444c1d1897d9c4a1fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Thu, 13 Feb 2020 23:06:17 +0100 Subject: [PATCH 03/72] Skip searches when typing fast / slow cpu. --- .../jsdoc/api/template/static/scripts/main.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index a3a623c934..4a212b3d98 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -42,8 +42,7 @@ $(function () { .show(); } - // Search Items - $('#search').on('keyup', function (e) { + function doSearch() { var value = $(this).val(); var $el = $('.navigation'); @@ -82,7 +81,22 @@ $(function () { } $el.find('.list').scrollTop(0); - }); + } + + const searchInput = $('#search').get(0); + // Skip searches when typing fast. + let key; + function queueSearch() { + if (!key) { + key = setTimeout(function () { + doSearch.call(searchInput); + key = undefined; + }, 0); + } + } + + // Search Items + searchInput.addEventListener('input', queueSearch); // Toggle when click an item element $('.navigation').on('click', '.toggle', function (e) { From 99ecce2a87d56a13efe683b6b1ec2c90644c63e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 16 Feb 2020 21:14:55 +0100 Subject: [PATCH 04/72] Provide the searchTerm as argument to doSearch function --- config/jsdoc/api/template/static/scripts/main.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 4a212b3d98..d91411db42 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -42,12 +42,11 @@ $(function () { .show(); } - function doSearch() { - var value = $(this).val(); + function doSearch(searchTerm) { var $el = $('.navigation'); - if (value && value.length > 1) { - var regexp = new RegExp(value, 'i'); + if (searchTerm.length > 1) { + var regexp = new RegExp(searchTerm, 'i'); $el.find('li, .member-list').hide(); $el.find('li').each(function (i, v) { @@ -61,7 +60,7 @@ $(function () { // Do the weight thing $classEntry.removeData('weight'); $classEntry.show(); - const weight = getSearchWeight(value, $classEntry); + const weight = getSearchWeight(searchTerm, $classEntry); $classEntry.data('weight', weight); $members.show(); @@ -89,7 +88,7 @@ $(function () { function queueSearch() { if (!key) { key = setTimeout(function () { - doSearch.call(searchInput); + doSearch(searchInput.value); key = undefined; }, 0); } From d56513b72237abaeea6e024ff939fa6b2aac8e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 16 Feb 2020 21:19:01 +0100 Subject: [PATCH 05/72] Add code to measure search function speed --- config/jsdoc/api/template/static/scripts/main.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index d91411db42..1df350cafa 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -85,11 +85,21 @@ $(function () { const searchInput = $('#search').get(0); // Skip searches when typing fast. let key; + let start; + const brandNode = document.querySelector('.brand'); function queueSearch() { if (!key) { + start = Date.now(); key = setTimeout(function () { - doSearch(searchInput.value); key = undefined; + + const searchTerm = searchInput.value; + doSearch(searchTerm); + + const time = Date.now() - start + brandNode.innerHTML = time + ' ms'; + console.log(searchTerm + ':', time, 'ms'); + start = undefined; }, 0); } } From 2b8582fcad9cfa874b6d5f2b6e67bf769939943d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 17:53:22 +0100 Subject: [PATCH 06/72] Catch errors when search term is an invalid regular expression On error search escape special characters, with only '.' matching any characters. --- .../jsdoc/api/template/static/scripts/main.js | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 1df350cafa..22943132ab 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -1,4 +1,8 @@ $(function () { + + // Allow user configuration? + const allowRegex = true; + // Search Items $('#include_modules').change(function (e) { console.log('change'); @@ -9,6 +13,29 @@ $(function () { } }); + const reNotCache = {}; + function escapeRegexp(s, not) { + if (not) { + let re = reNotCache[not]; + if (!re) { + const characters = '-\/\\^$*+?.()|[\]{}'.replace(new RegExp('[' + escapeRegexp(not) + ']', 'g'), ''); + re = reNotCache[not] = new RegExp('[' + escapeRegexp(characters) + ']', 'g'); + } + return s.replace(re, '\\$&'); + } + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + + function constructRegex(searchTerm, makeRe, allowRegex) { + try { + if (allowRegex) { + return makeRe(searchTerm); + } + } catch (e) { + } + return makeRe(escapeRegexp(searchTerm, '.')); + } + var getSearchWeight = function (searchTerm, $matchedItem) { let weight = 0; // We could get smarter on the weight here @@ -46,7 +73,9 @@ $(function () { var $el = $('.navigation'); if (searchTerm.length > 1) { - var regexp = new RegExp(searchTerm, 'i'); + const regexp = constructRegex(searchTerm, function (searchTerm) { + return new RegExp(searchTerm, 'i'); + }, allowRegex); $el.find('li, .member-list').hide(); $el.find('li').each(function (i, v) { From b9455bfad9c3db43ef50624cc1c3f93e7d0bc2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 17:56:40 +0100 Subject: [PATCH 07/72] Remove unnecessary / duplicate code. --- .../jsdoc/api/template/static/scripts/main.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 22943132ab..d6c755b948 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -3,16 +3,6 @@ $(function () { // Allow user configuration? const allowRegex = true; - // Search Items - $('#include_modules').change(function (e) { - console.log('change'); - if ($(this).is(':checked')) { - - } else { - - } - }); - const reNotCache = {}; function escapeRegexp(s, not) { if (not) { @@ -39,8 +29,7 @@ $(function () { var getSearchWeight = function (searchTerm, $matchedItem) { let weight = 0; // We could get smarter on the weight here - if ($matchedItem.data('shortname') - && $matchedItem.data('shortname').toLowerCase() === searchTerm.toLowerCase()) { + if ($matchedItem.data('shortname').toLowerCase() === searchTerm.toLowerCase()) { weight++; } return weight; @@ -82,19 +71,17 @@ $(function () { const $item = $(v); const name = $item.data('name'); - if (name && regexp.test(name)) { + if (regexp.test(name)) { const $classEntry = $item.closest('.item'); const $members = $item.closest('.member-list'); // Do the weight thing - $classEntry.removeData('weight'); - $classEntry.show(); const weight = getSearchWeight(searchTerm, $classEntry); $classEntry.data('weight', weight); + $item.show(); $members.show(); $classEntry.show(); - $item.show(); } }); From f7871d61036c7f50185008820c67c147ce0ebc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Thu, 13 Feb 2020 23:53:14 +0100 Subject: [PATCH 08/72] Store search data in lowercase and compare in lowercase This way we can do the search case-sensitive after only converting the search term to lowercase. --- config/jsdoc/api/template/static/scripts/main.js | 7 ++++--- config/jsdoc/api/template/tmpl/navigation.tmpl | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index d6c755b948..ca2ddb64b4 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -29,7 +29,7 @@ $(function () { var getSearchWeight = function (searchTerm, $matchedItem) { let weight = 0; // We could get smarter on the weight here - if ($matchedItem.data('shortname').toLowerCase() === searchTerm.toLowerCase()) { + if ($matchedItem.data('shortname') === searchTerm) { weight++; } return weight; @@ -48,7 +48,7 @@ $(function () { .replace('module-', 'module:') .replace(/_/g, '/') .replace(/-/g, '~'); - var $currentItem = $('.navigation .item[data-name*="' + longname + '"]:eq(0)'); + var $currentItem = $('.navigation .item[data-name*="' + longname.toLowerCase() + '"]:eq(0)'); if ($currentItem.length) { $currentItem @@ -62,8 +62,9 @@ $(function () { var $el = $('.navigation'); if (searchTerm.length > 1) { + searchTerm = searchTerm.toLowerCase(); const regexp = constructRegex(searchTerm, function (searchTerm) { - return new RegExp(searchTerm, 'i'); + return new RegExp(searchTerm); }, allowRegex); $el.find('li, .member-list').hide(); diff --git a/config/jsdoc/api/template/tmpl/navigation.tmpl b/config/jsdoc/api/template/tmpl/navigation.tmpl index 074e99672f..90e7e854e2 100644 --- a/config/jsdoc/api/template/tmpl/navigation.tmpl +++ b/config/jsdoc/api/template/tmpl/navigation.tmpl @@ -15,11 +15,11 @@ function getItemCssClass(type) { } const printList = v => { ?> -
  • "> { const cls = v.stability && v.stability !== 'stable' ? ' class="unstable"' : ''; ?> -
  • >">
      -
    • +
    • From d3b9b254292bc4f024e32b91c740255f740af482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 16 Feb 2020 23:15:25 +0100 Subject: [PATCH 09/72] Search if the search input is not empty after page loads Firefox remembers input on the same page if a normal reload is performed. In this case the results for the search term should be shown automatically. --- config/jsdoc/api/template/static/scripts/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index ca2ddb64b4..e9d7e604b5 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -123,6 +123,9 @@ $(function () { // Search Items searchInput.addEventListener('input', queueSearch); + if (searchInput.value) { + doSearch(searchInput.value); + } // Toggle when click an item element $('.navigation').on('click', '.toggle', function (e) { From 7640bcd163f436f37f1f1c0df63d3fb663bf11de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 17:13:26 +0100 Subject: [PATCH 10/72] Only match the displayed name of items when searching Currently the search term is matched against the longname of members. Because of this most members are matched when searching for a class / module and also searching for 'mod' matches everything as all longnames start with 'module:' --- config/jsdoc/api/template/static/scripts/main.js | 4 ++-- config/jsdoc/api/template/tmpl/navigation.tmpl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index e9d7e604b5..d923dedccb 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -29,7 +29,7 @@ $(function () { var getSearchWeight = function (searchTerm, $matchedItem) { let weight = 0; // We could get smarter on the weight here - if ($matchedItem.data('shortname') === searchTerm) { + if ($matchedItem.data('name') === searchTerm) { weight++; } return weight; @@ -48,7 +48,7 @@ $(function () { .replace('module-', 'module:') .replace(/_/g, '/') .replace(/-/g, '~'); - var $currentItem = $('.navigation .item[data-name*="' + longname.toLowerCase() + '"]:eq(0)'); + var $currentItem = $('.navigation .item[data-longname="' + longname + '"]:eq(0)'); if ($currentItem.length) { $currentItem diff --git a/config/jsdoc/api/template/tmpl/navigation.tmpl b/config/jsdoc/api/template/tmpl/navigation.tmpl index 90e7e854e2..104c8e98ef 100644 --- a/config/jsdoc/api/template/tmpl/navigation.tmpl +++ b/config/jsdoc/api/template/tmpl/navigation.tmpl @@ -15,11 +15,11 @@ function getItemCssClass(type) { } const printList = v => { ?> -
    • "> { const cls = v.stability && v.stability !== 'stable' ? ' class="unstable"' : ''; ?> -
    • >">
        -
      • +
      • From 8b857eed26279ec4cc0f0edd4281568c9ba29412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 17:19:11 +0100 Subject: [PATCH 11/72] Change prettyname of classes Now also includes the class name if it is different than its module name. `ol/source/Vector` -> `ol/source/Vector~VectorSource`. --- config/jsdoc/api/template/publish.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/config/jsdoc/api/template/publish.js b/config/jsdoc/api/template/publish.js index ecda2d2fb1..d8222784dc 100644 --- a/config/jsdoc/api/template/publish.js +++ b/config/jsdoc/api/template/publish.js @@ -189,9 +189,15 @@ function attachModuleSymbols(doclets, modules) { } function getPrettyName(longname) { - return longname - .split('~')[0] - .replace('module:', ''); + const fullname = longname.replace('module:', ''); + const parts = fullname.split(/[~\.]/); + if (parts.length > 1) { + const pathParts = parts[0].split('/'); + if (parts[parts.length - 1] === pathParts[pathParts.length - 1]) { + return parts[0]; + } + } + return fullname; } /** From cc561dab8eb6846d213a3af05735f206761363c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 15 Feb 2020 22:14:04 +0100 Subject: [PATCH 12/72] Allow word wrap before '.' and '~' in class names --- config/jsdoc/api/template/tmpl/navigation.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jsdoc/api/template/tmpl/navigation.tmpl b/config/jsdoc/api/template/tmpl/navigation.tmpl index 104c8e98ef..7a688e7452 100644 --- a/config/jsdoc/api/template/tmpl/navigation.tmpl +++ b/config/jsdoc/api/template/tmpl/navigation.tmpl @@ -45,7 +45,7 @@ function listContent(item, title, listItemPrinter) {
      • - + Date: Sun, 16 Feb 2020 10:27:24 +0100 Subject: [PATCH 13/72] Indent line-wrapped class names --- config/jsdoc/api/template/static/styles/jaguar.css | 9 +++++++-- config/jsdoc/api/template/tmpl/navigation.tmpl | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config/jsdoc/api/template/static/styles/jaguar.css b/config/jsdoc/api/template/static/styles/jaguar.css index 88433f363f..0088caa282 100644 --- a/config/jsdoc/api/template/static/styles/jaguar.css +++ b/config/jsdoc/api/template/static/styles/jaguar.css @@ -136,6 +136,11 @@ li { border-bottom: 1px solid #333; } +.navigation .glyphicon { + margin-right: 3px; + flex-shrink: 0; +} + .navigation li.perfect-match { border: 5px solid orange; } @@ -148,8 +153,8 @@ li { } .navigation li.item .title { cursor: pointer; - position: relative; - display: block; + display: -ms-flexbox; + display: flex; font-size: 0.85em; } .navigation li.item .title a { diff --git a/config/jsdoc/api/template/tmpl/navigation.tmpl b/config/jsdoc/api/template/tmpl/navigation.tmpl index 7a688e7452..6445c908a5 100644 --- a/config/jsdoc/api/template/tmpl/navigation.tmpl +++ b/config/jsdoc/api/template/tmpl/navigation.tmpl @@ -45,7 +45,7 @@ function listContent(item, title, listItemPrinter) {
      • - + Date: Thu, 13 Feb 2020 22:29:04 +0100 Subject: [PATCH 14/72] Improved weight function. ++++ Full match from start to end +++ Full match between word boundaries ++ Begin matches at word boundary, multiple matches are better + Matches somewhere Classes with (multiple) members matching at the beginning are weighted higher. --- .../jsdoc/api/template/static/scripts/main.js | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index d923dedccb..894b822242 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -2,19 +2,7 @@ $(function () { // Allow user configuration? const allowRegex = true; - - const reNotCache = {}; - function escapeRegexp(s, not) { - if (not) { - let re = reNotCache[not]; - if (!re) { - const characters = '-\/\\^$*+?.()|[\]{}'.replace(new RegExp('[' + escapeRegexp(not) + ']', 'g'), ''); - re = reNotCache[not] = new RegExp('[' + escapeRegexp(characters) + ']', 'g'); - } - return s.replace(re, '\\$&'); - } - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - } + const minInputForFullText = 1; function constructRegex(searchTerm, makeRe, allowRegex) { try { @@ -23,17 +11,47 @@ $(function () { } } catch (e) { } - return makeRe(escapeRegexp(searchTerm, '.')); + // In case of invalid regexp fall back to non-regexp, but still allow . to match / + return makeRe(searchTerm.replace(/\./g, '/').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); } - var getSearchWeight = function (searchTerm, $matchedItem) { - let weight = 0; - // We could get smarter on the weight here - if ($matchedItem.data('name') === searchTerm) { - weight++; + function getWeightFunction(searchTerm, allowRegex) { + function makeRe(searchTerm) { + return { + begin: new RegExp('(?:[.~]|\\b)' + searchTerm, 'g'), // Also match . and ~ to demote double matches, in weight function + name: new RegExp('\\b' + searchTerm + '(?:[~.]|$)'), // Match until module end or name end + fullName: new RegExp('^' + searchTerm + '$') + } } - return weight; - }; + const re = constructRegex(searchTerm, makeRe, allowRegex); + return function (matchedItem, beginOnly) { + // We could get smarter on the weight here + let weight = 0; + const name = matchedItem.data('name'); + const match = name.match(re.begin); + if (match) { + // `ol/geom/Geometry` matches twice when searching 'geom', is weighted higher. + // but don't boost something like `ol/source/Vector~VectorSource` when searching for 'Vect'. + let matches = match.length; + if (matches > 1 && /[.~]/.test(match[matches - 1])) { + matches -= 1; + } + weight += matches * 10000; + if (!beginOnly) { + // Full match of the last part of the path is weighted even higher + // Complete match is weighted highest. + if (re.fullName.test(name)) { + weight += 10000000; + } else if (re.name.test(name)) { + weight += 1000000; + } + } + // If everything else is equal, prefer shorter names. + weight -= name.length; + } + return weight; + } + } // sort function callback var weightSorter = function (a, b) { @@ -63,6 +81,8 @@ $(function () { if (searchTerm.length > 1) { searchTerm = searchTerm.toLowerCase(); + const getSearchWeight = getWeightFunction(searchTerm, allowRegex); + const beginOnly = searchTerm.length < minInputForFullText; const regexp = constructRegex(searchTerm, function (searchTerm) { return new RegExp(searchTerm); }, allowRegex); @@ -77,7 +97,7 @@ $(function () { const $members = $item.closest('.member-list'); // Do the weight thing - const weight = getSearchWeight(searchTerm, $classEntry); + const weight = getSearchWeight($classEntry, beginOnly); $classEntry.data('weight', weight); $item.show(); From b613e8e77fa7100c43944a61d2bd79ea10a8e8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 15 Feb 2020 10:59:26 +0100 Subject: [PATCH 15/72] Fix regex dot escape --- config/jsdoc/api/template/static/scripts/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 894b822242..f47d626ff1 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -12,7 +12,7 @@ $(function () { } catch (e) { } // In case of invalid regexp fall back to non-regexp, but still allow . to match / - return makeRe(searchTerm.replace(/\./g, '/').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + return makeRe(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\./g, '[./]')); } function getWeightFunction(searchTerm, allowRegex) { From 15beb1f2c6f0d2432e6c5b49ae51703fa2f4f175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 15 Feb 2020 15:52:04 +0100 Subject: [PATCH 16/72] Put more weight on the base name --- .../jsdoc/api/template/static/scripts/main.js | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index f47d626ff1..e065efab5a 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -18,37 +18,36 @@ $(function () { function getWeightFunction(searchTerm, allowRegex) { function makeRe(searchTerm) { return { - begin: new RegExp('(?:[.~]|\\b)' + searchTerm, 'g'), // Also match . and ~ to demote double matches, in weight function - name: new RegExp('\\b' + searchTerm + '(?:[~.]|$)'), // Match until module end or name end - fullName: new RegExp('^' + searchTerm + '$') + begin: new RegExp('\\b' + searchTerm), // Begin matches word boundary + baseName: new RegExp('\\b' + searchTerm + '[^/]*$'), // Begin matches word boundary of class / module name + fullName: new RegExp('\\b' + searchTerm + '(?:[~.]|$)'), // Complete word(s) of class / module matches + completeName: new RegExp('^' + searchTerm + '$') // Match from start to finish } } const re = constructRegex(searchTerm, makeRe, allowRegex); return function (matchedItem, beginOnly) { // We could get smarter on the weight here - let weight = 0; const name = matchedItem.data('name'); - const match = name.match(re.begin); - if (match) { - // `ol/geom/Geometry` matches twice when searching 'geom', is weighted higher. - // but don't boost something like `ol/source/Vector~VectorSource` when searching for 'Vect'. - let matches = match.length; - if (matches > 1 && /[.~]/.test(match[matches - 1])) { - matches -= 1; - } - weight += matches * 10000; - if (!beginOnly) { - // Full match of the last part of the path is weighted even higher - // Complete match is weighted highest. + if (beginOnly) { + return re.baseName.test(name) ? 10000 : 0; + } + let weight = 0; + if (name.match(re.begin)) { + weight += 10000; + if (re.baseName.test(name)) { + weight += 1000000; if (re.fullName.test(name)) { + // Full match of the last part of the path is weighted even higher weight += 10000000; - } else if (re.name.test(name)) { - weight += 1000000; + if (re.completeName.test(name)) { + // Complete match is weighted highest. + weight += 100000000; + } } } - // If everything else is equal, prefer shorter names. - weight -= name.length; } + // If everything else is equal, prefer shorter names. + weight -= name.length; return weight; } } From 99d9a9ff86cb0f5795c4778ea29ccc522405dc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 16 Feb 2020 14:10:31 +0100 Subject: [PATCH 17/72] Use strict mode --- config/jsdoc/api/template/static/scripts/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index e065efab5a..3bd79e9e1b 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -1,4 +1,5 @@ $(function () { + 'use strict'; // Allow user configuration? const allowRegex = true; From 646ecfd40546a50b4af41af033c9e2a5b82c12c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 15 Feb 2020 22:12:06 +0100 Subject: [PATCH 18/72] Weight function prefer classes over modules --- config/jsdoc/api/template/static/scripts/main.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 3bd79e9e1b..2d32f6e9de 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -32,23 +32,20 @@ $(function () { if (beginOnly) { return re.baseName.test(name) ? 10000 : 0; } - let weight = 0; + // If everything else is equal, prefer shorter names, and prefer classes over modules + let weight = matchedItem.data('longname').length - name.length * 100; if (name.match(re.begin)) { - weight += 10000; + weight += 100000; if (re.baseName.test(name)) { - weight += 1000000; + weight += 10000000; if (re.fullName.test(name)) { - // Full match of the last part of the path is weighted even higher - weight += 10000000; + weight += 100000000; if (re.completeName.test(name)) { - // Complete match is weighted highest. - weight += 100000000; + weight += 1000000000; } } } } - // If everything else is equal, prefer shorter names. - weight -= name.length; return weight; } } From 2dec296aaec918ac410e856a50a2c1adf8d375f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sun, 9 Feb 2020 19:14:34 +0100 Subject: [PATCH 19/72] Use css to show / hide items; track matched items ... to improve search speed. Instead of marking all items as hidden, and then marking the matched ones again, this keeps track of all matched items and only removes those that no longer match and adds those that previously did not match. --- .../jsdoc/api/template/static/scripts/main.js | 230 +++++++++++++----- .../api/template/static/styles/jaguar.css | 44 +++- .../jsdoc/api/template/tmpl/navigation.tmpl | 6 +- 3 files changed, 213 insertions(+), 67 deletions(-) diff --git a/config/jsdoc/api/template/static/scripts/main.js b/config/jsdoc/api/template/static/scripts/main.js index 2d32f6e9de..6d41345717 100644 --- a/config/jsdoc/api/template/static/scripts/main.js +++ b/config/jsdoc/api/template/static/scripts/main.js @@ -3,7 +3,9 @@ $(function () { // Allow user configuration? const allowRegex = true; - const minInputForFullText = 1; + const minInputForSearch = 1; + const minInputForFullText = 2; + const expandAllOnInputWithoutSearch = true; function constructRegex(searchTerm, makeRe, allowRegex) { try { @@ -28,12 +30,12 @@ $(function () { const re = constructRegex(searchTerm, makeRe, allowRegex); return function (matchedItem, beginOnly) { // We could get smarter on the weight here - const name = matchedItem.data('name'); + const name = matchedItem.dataset.name; if (beginOnly) { return re.baseName.test(name) ? 10000 : 0; } // If everything else is equal, prefer shorter names, and prefer classes over modules - let weight = matchedItem.data('longname').length - name.length * 100; + let weight = matchedItem.dataset.longname.length - name.length * 100; if (name.match(re.begin)) { weight += 100000; if (re.baseName.test(name)) { @@ -50,70 +52,162 @@ $(function () { } } - // sort function callback - var weightSorter = function (a, b) { - var aW = $(a).data('weight') || 0; - var bW = $(b).data('weight') || 0; - return bW - aW; - }; + const search = (function () { + const $nav = $('.navigation'); + const $navList = $nav.find('.list'); + let $classItems; + let $members; + let stateClass = (function () { + $nav.removeClass('search-started searching'); + $nav.addClass('search-empty'); + return 'search-empty'; + })(); + let manualToggles = {}; - // Show an item related a current documentation automatically - const longname = $('.page-title').data('filename') - .replace(/\.[a-z]+$/, '') - .replace('module-', 'module:') - .replace(/_/g, '/') - .replace(/-/g, '~'); - var $currentItem = $('.navigation .item[data-longname="' + longname + '"]:eq(0)'); + // Show an item related a current documentation automatically + const longname = $('.page-title').data('filename') + .replace(/\.[a-z]+$/, '') + .replace('module-', 'module:') + .replace(/_/g, '/') + .replace(/-/g, '~'); + const $currentItem = $navList.find('.item[data-longname="' + longname + '"]:eq(0)'); + $currentItem.prependTo($navList); + $currentItem.addClass('item-current'); + return { + $nav: $nav, + $navList: $navList, + $currentItem: $currentItem, + lastSearchTerm: undefined, + lastState: {}, + getClassList: function () { + return $classItems || ($classItems = $navList.find('li.item')); + }, + getMembers: function () { + return $members || ($members = $navList.find('.item li')); + }, + changeStateClass: function (newClass) { + if (newClass !== stateClass) { + const navNode = $nav.get(0); + navNode.classList.remove(stateClass); + navNode.classList.add(newClass); + stateClass = newClass; + } + }, + manualToggle: function ($node, show) { + $node.toggleClass('toggle-manual-hide', !show); + $node.toggleClass('toggle-manual-show', show); + manualToggles[$node.data('longname')] = $node; + }, + clearManualToggles: function() { + for (let clsName in manualToggles) { + manualToggles[clsName].removeClass('toggle-manual-show toggle-manual-hide'); + } + manualToggles = {}; + }, + }; + })(); - if ($currentItem.length) { - $currentItem - .prependTo('.navigation .list') - .show() - .find('.member-list') - .show(); + const dummy = {subItems: {}}; + function clearOldMatches(lastState, searchState) { + for (let itemName in lastState) { + const lastItem = lastState[itemName]; + const item = searchState[itemName]; + if (!item) { + lastItem.item.classList.remove('match'); + } + if (lastItem.subItems) { + clearOldMatches(lastItem.subItems, (item || dummy).subItems); + } + } } function doSearch(searchTerm) { - var $el = $('.navigation'); - - if (searchTerm.length > 1) { - searchTerm = searchTerm.toLowerCase(); - const getSearchWeight = getWeightFunction(searchTerm, allowRegex); - const beginOnly = searchTerm.length < minInputForFullText; - const regexp = constructRegex(searchTerm, function (searchTerm) { - return new RegExp(searchTerm); - }, allowRegex); - $el.find('li, .member-list').hide(); - - $el.find('li').each(function (i, v) { - const $item = $(v); - const name = $item.data('name'); - - if (regexp.test(name)) { - const $classEntry = $item.closest('.item'); - const $members = $item.closest('.member-list'); - - // Do the weight thing - const weight = getSearchWeight($classEntry, beginOnly); - $classEntry.data('weight', weight); - - $item.show(); - $members.show(); - $classEntry.show(); - } - }); - - $(".navigation ul.list li.item:visible") - .sort(weightSorter) // sort elements - .appendTo(".navigation ul.list"); // append again to the list - - } else { - $currentItem.prependTo('.navigation .list'); - $currentItem.find('.member-list, li').show(); - $el.find('.item').show(); + searchTerm = searchTerm.toLowerCase(); + const lastSearchTerm = search.lastSearchTerm; + if (searchTerm === lastSearchTerm) { + return; } - $el.find('.list').scrollTop(0); + // Avoid layout reflow by scrolling to top first. + search.$navList.scrollTop(0); + search.lastSearchTerm = searchTerm; + search.clearManualToggles(); + + if (searchTerm.length < minInputForSearch) { + const state = searchTerm.length && expandAllOnInputWithoutSearch ? 'search-started' : 'search-empty'; + search.changeStateClass(state); + if (lastSearchTerm !== undefined && lastSearchTerm.length >= minInputForSearch) { + // Restore the original, sorted order + search.$navList.append(search.getClassList()); + } + if (state === 'search-empty') { + search.manualToggle(search.$currentItem, true); + } + } else { + search.changeStateClass('searching'); + searchTerm = searchTerm.toLowerCase(); + const beginOnly = searchTerm.length < minInputForFullText; + const getSearchWeight = getWeightFunction(searchTerm, allowRegex); + const re = constructRegex(searchTerm, function (searchTerm) { + return new RegExp((beginOnly ? '\\b' : '') + searchTerm); + }, allowRegex); + const navList = search.$navList.get(0); + const classes = []; + const searchState = {}; + search.getMembers().each(function (i, li) { + const name = li.dataset.name; + if (re.test(name)) { + const itemMember = li.parentElement.parentElement; + const classEntry = itemMember.parentElement; + const className = classEntry.dataset.longname; + let cls = searchState[className]; + if (!cls) { + cls = searchState[className] = { + item: classEntry, + // Do the weight thing + weight: getSearchWeight(classEntry, beginOnly) * 10000, + subItems: {} + }; + classes.push(cls); + classEntry.classList.add('match'); + } + cls.weight += getSearchWeight(li, true) + 1; + const memberType = itemMember.dataset.type; + let members = cls.subItems[memberType]; + if (!members) { + members = cls.subItems[memberType] = { + item: itemMember, + subItems: {} + }; + itemMember.classList.add('match'); + } + members.subItems[name] = { item: li }; + li.classList.add('match'); + } + }); + search.getClassList().each(function (i, classEntry) { + const className = classEntry.dataset.longname; + if (!(className in searchState) && re.test(classEntry.dataset.name)) { + const cls = searchState[className] = { + item: classEntry, + // Do the weight thing + weight: getSearchWeight(classEntry, beginOnly) * 10000, + subItems: {} + }; + classes.push(cls); + classEntry.classList.add('match'); + } + }); + clearOldMatches(search.lastState, searchState); + search.lastState = searchState; + + classes.sort(function (a, b) { + return a.weight - b.weight; + }); + for (let i = classes.length - 1; i >= 0; --i) { + navList.appendChild(classes[i].item); + } + } } const searchInput = $('#search').get(0); @@ -132,7 +226,11 @@ $(function () { const time = Date.now() - start brandNode.innerHTML = time + ' ms'; - console.log(searchTerm + ':', time, 'ms'); + const msg = [searchTerm + ':', time, 'ms']; + if (searchTerm.length >= minInputForSearch) { + msg.push(search.lastState); + } + console.log.apply(console, msg); start = undefined; }, 0); } @@ -145,8 +243,14 @@ $(function () { } // Toggle when click an item element - $('.navigation').on('click', '.toggle', function (e) { - $(this).parent().parent().find('.member-list').toggle(); + search.$nav.on('click', '.toggle', function (e) { + const clsItem = $(this).closest('.item'); + let shown; + clsItem.find('.member-list').each(function (i, v) { + shown = $(v).is(':visible'); + return !shown; + }); + search.manualToggle(clsItem, !shown); }); // Auto resizing on navigation diff --git a/config/jsdoc/api/template/static/styles/jaguar.css b/config/jsdoc/api/template/static/styles/jaguar.css index 0088caa282..bf605518d7 100644 --- a/config/jsdoc/api/template/static/styles/jaguar.css +++ b/config/jsdoc/api/template/static/styles/jaguar.css @@ -190,9 +190,51 @@ li { margin-top: 2px; } .navigation li.item .member-list { - display: none; padding-left: 8px; } + +/* search state */ +/* show all classes when search is empty */ +.navigation.search-empty li.item { + display: block; +} +/* hide all members by default when search is empty */ +.navigation.search-empty li.item .member-list { + display: none; +} +/* but show the members of the current pages module / class */ +.navigation.search-empty li.item.item-current .member-list { + display: block; +} +/* expand all members when one character is entered in the search field */ +.navigation.search-started li.item, +.navigation.search-started .member-list, +.navigation.search-started .member-list li { + display: block; +} +/* when more than one character is entered hide everything that is not a match */ +.navigation.searching li.item, +.navigation.searching .member-list, +.navigation.searching .member-list li { + display: none; +} +.navigation.searching li.item.match, +.navigation.searching .member-list.match, +.navigation.searching .member-list li.match { + display: block; +} +/* allow user to hide / show members */ +.navigation .item.toggle-manual-show .member-list, +.navigation .item.toggle-manual-show li { + display: block!important; +} +.navigation .item.toggle-manual-hide .member-list, +.navigation .item.toggle-manual-hide li, +.navigation.searching .item.toggle-manual-show .member-list:not(.match), +.navigation.searching .item.toggle-manual-show li:not(.match) { + display: none!important; +} + .main { padding: 20px 20px; margin-left: 250px; diff --git a/config/jsdoc/api/template/tmpl/navigation.tmpl b/config/jsdoc/api/template/tmpl/navigation.tmpl index 6445c908a5..7cea44991b 100644 --- a/config/jsdoc/api/template/tmpl/navigation.tmpl +++ b/config/jsdoc/api/template/tmpl/navigation.tmpl @@ -25,7 +25,7 @@ const printListWithStability = v => { function listContent(item, title, listItemPrinter) { const type = title.toLowerCase(); if (item[type] && item[type].length) { ?> -
        +
          -