tdf#119415 change from fuse.js to fuzzysort.js
Tied the index listing to the current module.
Fuzzysort.js is MIT licensed.
Change-Id: I5d95cc1870a1856089217846ed0af572abc882d7
Reviewed-on: https://gerrit.libreoffice.org/63203
Tested-by: Jenkins
Reviewed-by: Olivier Hallot <olivier.hallot@libreoffice.org>
diff --git a/CustomTarget_html.mk b/CustomTarget_html.mk
index 00c6c7e..50fd6ae 100644
--- a/CustomTarget_html.mk
+++ b/CustomTarget_html.mk
@@ -157,9 +157,9 @@
$(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),CAT,2)
$(call gb_Helper_abbreviate_dirs,\
( \
echo 'document.getElementsByClassName( "index" )[0].innerHTML='"'"'\' \
echo 'var bookmarks = [' \
&& cat $(filter %.part,$^) \
&& echo "'" \
&& echo '];' \
) > $@ \
)
diff --git a/Package_html_static.mk b/Package_html_static.mk
index bab29c9..59941b0 100644
--- a/Package_html_static.mk
+++ b/Package_html_static.mk
@@ -18,7 +18,7 @@
$(eval $(call gb_Package_add_files,helpcontent2_html_static,$(LIBO_SHARE_HELP_FOLDER)$(if $(HELP_ONLINE),/$(PRODUCTVERSION)),\
default.css \
fuse.js \
fuzzysort.js \
help.js \
help2.js \
normalize.css \
diff --git a/help3xsl/default.css b/help3xsl/default.css
index a67a036..6fee232 100644
--- a/help3xsl/default.css
+++ b/help3xsl/default.css
@@ -287,7 +287,7 @@
.debug {
border: 1px solid black;
padding: 3px;
display: show;
display: none;
background-color: #222;
color: red;
text-align: left;
@@ -563,13 +563,10 @@
.pagination a {
text-decoration: none;
}
.fuseshown {
.index a {
display: block;
}
.fusehidden {
display: none;
}
.fuseshown.hidden {
.index .hidden {
display: none;
}
li.active {
diff --git a/help3xsl/fuse.js b/help3xsl/fuse.js
deleted file mode 100644
index b451ec1..0000000
--- a/help3xsl/fuse.js
+++ /dev/null
@@ -1,997 +0,0 @@
/*!
* Fuse.js v3.2.0 - Lightweight fuzzy-search (http://fusejs.io)
*
* Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me)
* All Rights Reserved. Apache Software License 2.0
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("Fuse", [], factory);
else if(typeof exports === 'object')
exports["Fuse"] = factory();
else
root["Fuse"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 8);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var bitapRegexSearch = __webpack_require__(5);
var bitapSearch = __webpack_require__(7);
var patternAlphabet = __webpack_require__(4);
var Bitap = function () {
function Bitap(pattern, _ref) {
var _ref$location = _ref.location,
location = _ref$location === undefined ? 0 : _ref$location,
_ref$distance = _ref.distance,
distance = _ref$distance === undefined ? 100 : _ref$distance,
_ref$threshold = _ref.threshold,
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
_ref$maxPatternLength = _ref.maxPatternLength,
maxPatternLength = _ref$maxPatternLength === undefined ? 32 : _ref$maxPatternLength,
_ref$isCaseSensitive = _ref.isCaseSensitive,
isCaseSensitive = _ref$isCaseSensitive === undefined ? false : _ref$isCaseSensitive,
_ref$tokenSeparator = _ref.tokenSeparator,
tokenSeparator = _ref$tokenSeparator === undefined ? / +/g : _ref$tokenSeparator,
_ref$findAllMatches = _ref.findAllMatches,
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
_ref$minMatchCharLeng = _ref.minMatchCharLength,
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng;
_classCallCheck(this, Bitap);
this.options = {
location: location,
distance: distance,
threshold: threshold,
maxPatternLength: maxPatternLength,
isCaseSensitive: isCaseSensitive,
tokenSeparator: tokenSeparator,
findAllMatches: findAllMatches,
minMatchCharLength: minMatchCharLength
};
this.pattern = this.options.isCaseSensitive ? pattern : pattern.toLowerCase();
if (this.pattern.length <= maxPatternLength) {
this.patternAlphabet = patternAlphabet(this.pattern);
}
}
_createClass(Bitap, [{
key: 'search',
value: function search(text) {
if (!this.options.isCaseSensitive) {
text = text.toLowerCase();
}
// Exact match
if (this.pattern === text) {
return {
isMatch: true,
score: 0,
matchedIndices: [[0, text.length - 1]]
};
}
// When pattern length is greater than the machine word length, just do a a regex comparison
var _options = this.options,
maxPatternLength = _options.maxPatternLength,
tokenSeparator = _options.tokenSeparator;
if (this.pattern.length > maxPatternLength) {
return bitapRegexSearch(text, this.pattern, tokenSeparator);
}
// Otherwise, use Bitap algorithm
var _options2 = this.options,
location = _options2.location,
distance = _options2.distance,
threshold = _options2.threshold,
findAllMatches = _options2.findAllMatches,
minMatchCharLength = _options2.minMatchCharLength;
return bitapSearch(text, this.pattern, this.patternAlphabet, {
location: location,
distance: distance,
threshold: threshold,
findAllMatches: findAllMatches,
minMatchCharLength: minMatchCharLength
});
}
}]);
return Bitap;
}();
// let x = new Bitap("od mn war", {})
// let result = x.search("Old Man's War")
// console.log(result)
module.exports = Bitap;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var isArray = __webpack_require__(0);
var deepValue = function deepValue(obj, path, list) {
if (!path) {
// If there's no path left, we've gotten to the object we care about.
list.push(obj);
} else {
var dotIndex = path.indexOf('.');
var firstSegment = path;
var remaining = null;
if (dotIndex !== -1) {
firstSegment = path.slice(0, dotIndex);
remaining = path.slice(dotIndex + 1);
}
var value = obj[firstSegment];
if (value !== null && value !== undefined) {
if (!remaining && (typeof value === 'string' || typeof value === 'number')) {
list.push(value.toString());
} else if (isArray(value)) {
// Search each item in the array.
for (var i = 0, len = value.length; i < len; i += 1) {
deepValue(value[i], remaining, list);
}
} else if (remaining) {
// An object. Recurse further.
deepValue(value, remaining, list);
}
}
}
return list;
};
module.exports = function (obj, path) {
return deepValue(obj, path, []);
};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function () {
var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
var matchedIndices = [];
var start = -1;
var end = -1;
var i = 0;
for (var len = matchmask.length; i < len; i += 1) {
var match = matchmask[i];
if (match && start === -1) {
start = i;
} else if (!match && start !== -1) {
end = i - 1;
if (end - start + 1 >= minMatchCharLength) {
matchedIndices.push([start, end]);
}
start = -1;
}
}
// (i-1 - start) + 1 => i - start
if (matchmask[i - 1] && i - start >= minMatchCharLength) {
matchedIndices.push([start, i - 1]);
}
return matchedIndices;
};
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (pattern) {
var mask = {};
var len = pattern.length;
for (var i = 0; i < len; i += 1) {
mask[pattern.charAt(i)] = 0;
}
for (var _i = 0; _i < len; _i += 1) {
mask[pattern.charAt(_i)] |= 1 << len - _i - 1;
}
return mask;
};
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var SPECIAL_CHARS_REGEX = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;
module.exports = function (text, pattern) {
var tokenSeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : / +/g;
var regex = new RegExp(pattern.replace(SPECIAL_CHARS_REGEX, '\\$&').replace(tokenSeparator, '|'));
var matches = text.match(regex);
var isMatch = !!matches;
var matchedIndices = [];
if (isMatch) {
for (var i = 0, matchesLen = matches.length; i < matchesLen; i += 1) {
var match = matches[i];
matchedIndices.push([text.indexOf(match), match.length - 1]);
}
}
return {
// TODO: revisit this score
score: isMatch ? 0.5 : 1,
isMatch: isMatch,
matchedIndices: matchedIndices
};
};
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (pattern, _ref) {
var _ref$errors = _ref.errors,
errors = _ref$errors === undefined ? 0 : _ref$errors,
_ref$currentLocation = _ref.currentLocation,
currentLocation = _ref$currentLocation === undefined ? 0 : _ref$currentLocation,
_ref$expectedLocation = _ref.expectedLocation,
expectedLocation = _ref$expectedLocation === undefined ? 0 : _ref$expectedLocation,
_ref$distance = _ref.distance,
distance = _ref$distance === undefined ? 100 : _ref$distance;
var accuracy = errors / pattern.length;
var proximity = Math.abs(expectedLocation - currentLocation);
if (!distance) {
// Dodge divide by zero error.
return proximity ? 1.0 : accuracy;
}
return accuracy + proximity / distance;
};
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var bitapScore = __webpack_require__(6);
var matchedIndices = __webpack_require__(3);
module.exports = function (text, pattern, patternAlphabet, _ref) {
var _ref$location = _ref.location,
location = _ref$location === undefined ? 0 : _ref$location,
_ref$distance = _ref.distance,
distance = _ref$distance === undefined ? 100 : _ref$distance,
_ref$threshold = _ref.threshold,
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
_ref$findAllMatches = _ref.findAllMatches,
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
_ref$minMatchCharLeng = _ref.minMatchCharLength,
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng;
var expectedLocation = location;
// Set starting location at beginning text and initialize the alphabet.
var textLen = text.length;
// Highest score beyond which we give up.
var currentThreshold = threshold;
// Is there a nearby exact match? (speedup)
var bestLocation = text.indexOf(pattern, expectedLocation);
var patternLen = pattern.length;
// a mask of the matches
var matchMask = [];
for (var i = 0; i < textLen; i += 1) {
matchMask[i] = 0;
}
if (bestLocation !== -1) {
var score = bitapScore(pattern, {
errors: 0,
currentLocation: bestLocation,
expectedLocation: expectedLocation,
distance: distance
});
currentThreshold = Math.min(score, currentThreshold);
// What about in the other direction? (speed up)
bestLocation = text.lastIndexOf(pattern, expectedLocation + patternLen);
if (bestLocation !== -1) {
var _score = bitapScore(pattern, {
errors: 0,
currentLocation: bestLocation,
expectedLocation: expectedLocation,
distance: distance
});
currentThreshold = Math.min(_score, currentThreshold);
}
}
// Reset the best location
bestLocation = -1;
var lastBitArr = [];
var finalScore = 1;
var binMax = patternLen + textLen;
var mask = 1 << patternLen - 1;
for (var _i = 0; _i < patternLen; _i += 1) {
// Scan for the best match; each iteration allows for one more error.
// Run a binary search to determine how far from the match location we can stray
// at this error level.
var binMin = 0;
var binMid = binMax;
while (binMin < binMid) {
var _score3 = bitapScore(pattern, {
errors: _i,
currentLocation: expectedLocation + binMid,
expectedLocation: expectedLocation,
distance: distance
});
if (_score3 <= currentThreshold) {
binMin = binMid;
} else {
binMax = binMid;
}
binMid = Math.floor((binMax - binMin) / 2 + binMin);
}
// Use the result from this iteration as the maximum for the next.
binMax = binMid;
var start = Math.max(1, expectedLocation - binMid + 1);
var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
// Initialize the bit array
var bitArr = Array(finish + 2);
bitArr[finish + 1] = (1 << _i) - 1;
for (var j = finish; j >= start; j -= 1) {
var currentLocation = j - 1;
var charMatch = patternAlphabet[text.charAt(currentLocation)];
if (charMatch) {
matchMask[currentLocation] = 1;
}
// First pass: exact match
bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
// Subsequent passes: fuzzy match
if (_i !== 0) {
bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
}
if (bitArr[j] & mask) {
finalScore = bitapScore(pattern, {
errors: _i,
currentLocation: currentLocation,
expectedLocation: expectedLocation,
distance: distance
});
// This match will almost certainly be better than any existing match.
// But check anyway.
if (finalScore <= currentThreshold) {
// Indeed it is
currentThreshold = finalScore;
bestLocation = currentLocation;
// Already passed `loc`, downhill from here on in.
if (bestLocation <= expectedLocation) {
break;
}
// When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
start = Math.max(1, 2 * expectedLocation - bestLocation);
}
}
}
// No hope for a (better) match at greater error levels.
var _score2 = bitapScore(pattern, {
errors: _i + 1,
currentLocation: expectedLocation,
expectedLocation: expectedLocation,
distance: distance
});
if (_score2 > currentThreshold) {
break;
}
lastBitArr = bitArr;
}
// Count exact matches (those with a score of 0) to be "almost" exact
return {
isMatch: bestLocation >= 0,
score: finalScore === 0 ? 0.001 : finalScore,
matchedIndices: matchedIndices(matchMask, minMatchCharLength)
};
};
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var Bitap = __webpack_require__(1);
var deepValue = __webpack_require__(2);
var isArray = __webpack_require__(0);
var Fuse = function () {
function Fuse(list, _ref) {
var _ref$location = _ref.location,
location = _ref$location === undefined ? 0 : _ref$location,
_ref$distance = _ref.distance,
distance = _ref$distance === undefined ? 100 : _ref$distance,
_ref$threshold = _ref.threshold,
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
_ref$maxPatternLength = _ref.maxPatternLength,
maxPatternLength = _ref$maxPatternLength === undefined ? 32 : _ref$maxPatternLength,
_ref$caseSensitive = _ref.caseSensitive,
caseSensitive = _ref$caseSensitive === undefined ? false : _ref$caseSensitive,
_ref$tokenSeparator = _ref.tokenSeparator,
tokenSeparator = _ref$tokenSeparator === undefined ? / +/g : _ref$tokenSeparator,
_ref$findAllMatches = _ref.findAllMatches,
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
_ref$minMatchCharLeng = _ref.minMatchCharLength,
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng,
_ref$id = _ref.id,
id = _ref$id === undefined ? null : _ref$id,
_ref$keys = _ref.keys,
keys = _ref$keys === undefined ? [] : _ref$keys,
_ref$shouldSort = _ref.shouldSort,
shouldSort = _ref$shouldSort === undefined ? true : _ref$shouldSort,
_ref$getFn = _ref.getFn,
getFn = _ref$getFn === undefined ? deepValue : _ref$getFn,
_ref$sortFn = _ref.sortFn,
sortFn = _ref$sortFn === undefined ? function (a, b) {
return a.score - b.score;
} : _ref$sortFn,
_ref$tokenize = _ref.tokenize,
tokenize = _ref$tokenize === undefined ? false : _ref$tokenize,
_ref$matchAllTokens = _ref.matchAllTokens,
matchAllTokens = _ref$matchAllTokens === undefined ? false : _ref$matchAllTokens,
_ref$includeMatches = _ref.includeMatches,
includeMatches = _ref$includeMatches === undefined ? false : _ref$includeMatches,
_ref$includeScore = _ref.includeScore,
includeScore = _ref$includeScore === undefined ? false : _ref$includeScore,
_ref$verbose = _ref.verbose,
verbose = _ref$verbose === undefined ? false : _ref$verbose;
_classCallCheck(this, Fuse);
this.options = {
location: location,
distance: distance,
threshold: threshold,
maxPatternLength: maxPatternLength,
isCaseSensitive: caseSensitive,
tokenSeparator: tokenSeparator,
findAllMatches: findAllMatches,
minMatchCharLength: minMatchCharLength,
id: id,
keys: keys,
includeMatches: includeMatches,
includeScore: includeScore,
shouldSort: shouldSort,
getFn: getFn,
sortFn: sortFn,
verbose: verbose,
tokenize: tokenize,
matchAllTokens: matchAllTokens
};
this.setCollection(list);
}
_createClass(Fuse, [{
key: 'setCollection',
value: function setCollection(list) {
this.list = list;
return list;
}
}, {
key: 'search',
value: function search(pattern) {
this._log('---------\nSearch pattern: "' + pattern + '"');
var _prepareSearchers2 = this._prepareSearchers(pattern),
tokenSearchers = _prepareSearchers2.tokenSearchers,
fullSearcher = _prepareSearchers2.fullSearcher;
var _search2 = this._search(tokenSearchers, fullSearcher),
weights = _search2.weights,
results = _search2.results;
this._computeScore(weights, results);
if (this.options.shouldSort) {
this._sort(results);
}
return this._format(results);
}
}, {
key: '_prepareSearchers',
value: function _prepareSearchers() {
var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var tokenSearchers = [];
if (this.options.tokenize) {
// Tokenize on the separator
var tokens = pattern.split(this.options.tokenSeparator);
for (var i = 0, len = tokens.length; i < len; i += 1) {
tokenSearchers.push(new Bitap(tokens[i], this.options));
}
}
var fullSearcher = new Bitap(pattern, this.options);
return { tokenSearchers: tokenSearchers, fullSearcher: fullSearcher };
}
}, {
key: '_search',
value: function _search() {
var tokenSearchers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var fullSearcher = arguments[1];
var list = this.list;
var resultMap = {};
var results = [];
// Check the first item in the list, if it's a string, then we assume
// that every item in the list is also a string, and thus it's a flattened array.
if (typeof list[0] === 'string') {
// Iterate over every item
for (var i = 0, len = list.length; i < len; i += 1) {
this._analyze({
key: '',
value: list[i],
record: i,
index: i
}, {
resultMap: resultMap,
results: results,
tokenSearchers: tokenSearchers,
fullSearcher: fullSearcher
});
}
return { weights: null, results: results };
}
// Otherwise, the first item is an Object (hopefully), and thus the searching
// is done on the values of the keys of each item.
var weights = {};
for (var _i = 0, _len = list.length; _i < _len; _i += 1) {
var item = list[_i];
// Iterate over every key
for (var j = 0, keysLen = this.options.keys.length; j < keysLen; j += 1) {
var key = this.options.keys[j];
if (typeof key !== 'string') {
weights[key.name] = {
weight: 1 - key.weight || 1
};
if (key.weight <= 0 || key.weight > 1) {
throw new Error('Key weight has to be > 0 and <= 1');
}
key = key.name;
} else {
weights[key] = {
weight: 1
};
}
this._analyze({
key: key,
value: this.options.getFn(item, key),
record: item,
index: _i
}, {
resultMap: resultMap,
results: results,
tokenSearchers: tokenSearchers,
fullSearcher: fullSearcher
});
}
}
return { weights: weights, results: results };
}
}, {
key: '_analyze',
value: function _analyze(_ref2, _ref3) {
var key = _ref2.key,
_ref2$arrayIndex = _ref2.arrayIndex,
arrayIndex = _ref2$arrayIndex === undefined ? -1 : _ref2$arrayIndex,
value = _ref2.value,
record = _ref2.record,
index = _ref2.index;
var _ref3$tokenSearchers = _ref3.tokenSearchers,
tokenSearchers = _ref3$tokenSearchers === undefined ? [] : _ref3$tokenSearchers,
_ref3$fullSearcher = _ref3.fullSearcher,
fullSearcher = _ref3$fullSearcher === undefined ? [] : _ref3$fullSearcher,
_ref3$resultMap = _ref3.resultMap,
resultMap = _ref3$resultMap === undefined ? {} : _ref3$resultMap,
_ref3$results = _ref3.results,
results = _ref3$results === undefined ? [] : _ref3$results;
// Check if the text value can be searched
if (value === undefined || value === null) {
return;
}
var exists = false;
var averageScore = -1;
var numTextMatches = 0;
if (typeof value === 'string') {
this._log('\nKey: ' + (key === '' ? '-' : key));
var mainSearchResult = fullSearcher.search(value);
this._log('Full text: "' + value + '", score: ' + mainSearchResult.score);
if (this.options.tokenize) {
var words = value.split(this.options.tokenSeparator);
var scores = [];
for (var i = 0; i < tokenSearchers.length; i += 1) {
var tokenSearcher = tokenSearchers[i];
this._log('\nPattern: "' + tokenSearcher.pattern + '"');
// let tokenScores = []
var hasMatchInText = false;
for (var j = 0; j < words.length; j += 1) {
var word = words[j];
var tokenSearchResult = tokenSearcher.search(word);
var obj = {};
if (tokenSearchResult.isMatch) {
obj[word] = tokenSearchResult.score;
exists = true;
hasMatchInText = true;
scores.push(tokenSearchResult.score);
} else {
obj[word] = 1;
if (!this.options.matchAllTokens) {
scores.push(1);
}
}
this._log('Token: "' + word + '", score: ' + obj[word]);
// tokenScores.push(obj)
}
if (hasMatchInText) {
numTextMatches += 1;
}
}
averageScore = scores[0];
var scoresLen = scores.length;
for (var _i2 = 1; _i2 < scoresLen; _i2 += 1) {
averageScore += scores[_i2];
}
averageScore = averageScore / scoresLen;
this._log('Token score average:', averageScore);
}
var finalScore = mainSearchResult.score;
if (averageScore > -1) {
finalScore = (finalScore + averageScore) / 2;
}
this._log('Score average:', finalScore);
var checkTextMatches = this.options.tokenize && this.options.matchAllTokens ? numTextMatches >= tokenSearchers.length : true;
this._log('\nCheck Matches: ' + checkTextMatches);
// If a match is found, add the item to <rawResults>, including its score
if ((exists || mainSearchResult.isMatch) && checkTextMatches) {
// Check if the item already exists in our results
var existingResult = resultMap[index];
if (existingResult) {
// Use the lowest score
// existingResult.score, bitapResult.score
existingResult.output.push({
key: key,
arrayIndex: arrayIndex,
value: value,
score: finalScore,
matchedIndices: mainSearchResult.matchedIndices
});
} else {
// Add it to the raw result list
resultMap[index] = {
item: record,
output: [{
key: key,
arrayIndex: arrayIndex,
value: value,
score: finalScore,
matchedIndices: mainSearchResult.matchedIndices
}]
};
results.push(resultMap[index]);
}
}
} else if (isArray(value)) {
for (var _i3 = 0, len = value.length; _i3 < len; _i3 += 1) {
this._analyze({
key: key,
arrayIndex: _i3,
value: value[_i3],
record: record,
index: index
}, {
resultMap: resultMap,
results: results,
tokenSearchers: tokenSearchers,
fullSearcher: fullSearcher
});
}
}
}
}, {
key: '_computeScore',
value: function _computeScore(weights, results) {
this._log('\n\nComputing score:\n');
for (var i = 0, len = results.length; i < len; i += 1) {
var output = results[i].output;
var scoreLen = output.length;
var totalScore = 0;
var bestScore = 1;
for (var j = 0; j < scoreLen; j += 1) {
var weight = weights ? weights[output[j].key].weight : 1;
var score = weight === 1 ? output[j].score : output[j].score || 0.001;
var nScore = score * weight;
if (weight !== 1) {
bestScore = Math.min(bestScore, nScore);
} else {
output[j].nScore = nScore;
totalScore += nScore;
}
}
results[i].score = bestScore === 1 ? totalScore / scoreLen : bestScore;
this._log(results[i]);
}
}
}, {
key: '_sort',
value: function _sort(results) {
this._log('\n\nSorting....');
results.sort(this.options.sortFn);
}
}, {
key: '_format',
value: function _format(results) {
var finalOutput = [];
if (this.options.verbose) {
this._log('\n\nOutput:\n\n', JSON.stringify(results));
}
var transformers = [];
if (this.options.includeMatches) {
transformers.push(function (result, data) {
var output = result.output;
data.matches = [];
for (var i = 0, len = output.length; i < len; i += 1) {
var item = output[i];
if (item.matchedIndices.length === 0) {
continue;
}
var obj = {
indices: item.matchedIndices,
value: item.value
};
if (item.key) {
obj.key = item.key;
}
if (item.hasOwnProperty('arrayIndex') && item.arrayIndex > -1) {
obj.arrayIndex = item.arrayIndex;
}
data.matches.push(obj);
}
});
}
if (this.options.includeScore) {
transformers.push(function (result, data) {
data.score = result.score;
});
}
for (var i = 0, len = results.length; i < len; i += 1) {
var result = results[i];
if (this.options.id) {
result.item = this.options.getFn(result.item, this.options.id)[0];
}
if (!transformers.length) {
finalOutput.push(result.item);
continue;
}
var data = {
item: result.item
};
for (var j = 0, _len2 = transformers.length; j < _len2; j += 1) {
transformers[j](result, data);
}
finalOutput.push(data);
}
return finalOutput;
}
}, {
key: '_log',
value: function _log() {
if (this.options.verbose) {
var _console;
(_console = console).log.apply(_console, arguments);
}
}
}]);
return Fuse;
}();
module.exports = Fuse;
/***/ })
/******/ ]);
});
diff --git a/help3xsl/fuzzysort.js b/help3xsl/fuzzysort.js
new file mode 100644
index 0000000..78c8fcc
--- /dev/null
+++ b/help3xsl/fuzzysort.js
@@ -0,0 +1,608 @@
/*
MIT License
Copyright (c) 2018 Stephen Kamenar
WHAT: SublimeText-like Fuzzy Search
USAGE:
fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
fuzzysort.single('test', 'test') // {score: 0}
fuzzysort.single('doesnt exist', 'target') // null
fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
// [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '<b>', '</b>')
// <b>F</b>uzzy <b>S</b>earch
*/
// UMD (Universal Module Definition) for fuzzysort
;(function(root, UMD) {
if(typeof define === 'function' && define.amd) define([], UMD)
else if(typeof module === 'object' && module.exports) module.exports = UMD()
else root.fuzzysort = UMD()
})(this, function UMD() { function fuzzysortNew(instanceOptions) {
var fuzzysort = {
single: function(search, target, options) {
if(!search) return null
if(!isObj(search)) search = fuzzysort.getPreparedSearch(search)
if(!target) return null
if(!isObj(target)) target = fuzzysort.getPrepared(target)
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
: true
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
return algorithm(search, target, search[0])
// var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
// var result = algorithm(search, target, search[0])
// if(result === null) return null
// if(result.score < threshold) return null
// return result
},
go: function(search, targets, options) {
if(!search) return noResults
search = fuzzysort.prepareSearch(search)
var searchLowerCode = search[0]
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
: true
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
var resultsLen = 0; var limitedCount = 0
var targetsLen = targets.length
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
// options.keys
if(options && options.keys) {
var scoreFn = options.scoreFn || defaultScoreFn
var keys = options.keys
var keysLen = keys.length
for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
var objResults = new Array(keysLen)
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
var key = keys[keyI]
var target = getValue(obj, key)
if(!target) { objResults[keyI] = null; continue }
if(!isObj(target)) target = fuzzysort.getPrepared(target)
objResults[keyI] = algorithm(search, target, searchLowerCode)
}
objResults.obj = obj // before scoreFn so scoreFn can use it
var score = scoreFn(objResults)
if(score === null) continue
if(score < threshold) continue
objResults.score = score
if(resultsLen < limit) { q.add(objResults); ++resultsLen }
else {
++limitedCount
if(score > q.peek().score) q.replaceTop(objResults)
}
}
// options.key
} else if(options && options.key) {
var key = options.key
for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
var target = getValue(obj, key)
if(!target) continue
if(!isObj(target)) target = fuzzysort.getPrepared(target)
var result = algorithm(search, target, searchLowerCode)
if(result === null) continue
if(result.score < threshold) continue
// have to clone result so duplicate targets from different obj can each reference the correct obj
result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
if(resultsLen < limit) { q.add(result); ++resultsLen }
else {
++limitedCount
if(result.score > q.peek().score) q.replaceTop(result)
}
}
// no keys
} else {
for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i]
if(!target) continue
if(!isObj(target)) target = fuzzysort.getPrepared(target)
var result = algorithm(search, target, searchLowerCode)
if(result === null) continue
if(result.score < threshold) continue
if(resultsLen < limit) { q.add(result); ++resultsLen }
else {
++limitedCount
if(result.score > q.peek().score) q.replaceTop(result)
}
}
}
if(resultsLen === 0) return noResults
var results = new Array(resultsLen)
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
results.total = resultsLen + limitedCount
return results
},
goAsync: function(search, targets, options) {
var canceled = false
var p = new Promise(function(resolve, reject) {
if(!search) return resolve(noResults)
search = fuzzysort.prepareSearch(search)
var searchLowerCode = search[0]
var q = fastpriorityqueue()
var iCurrent = targets.length - 1
var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
: instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
: true
var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
var resultsLen = 0; var limitedCount = 0
function step() {
if(canceled) return reject('canceled')
var startMs = Date.now()
// This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
// options.keys
if(options && options.keys) {
var scoreFn = options.scoreFn || defaultScoreFn
var keys = options.keys
var keysLen = keys.length
for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent]
var objResults = new Array(keysLen)
for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
var key = keys[keyI]
var target = getValue(obj, key)
if(!target) { objResults[keyI] = null; continue }
if(!isObj(target)) target = fuzzysort.getPrepared(target)
objResults[keyI] = algorithm(search, target, searchLowerCode)
}
objResults.obj = obj // before scoreFn so scoreFn can use it
var score = scoreFn(objResults)
if(score === null) continue
if(score < threshold) continue
objResults.score = score
if(resultsLen < limit) { q.add(objResults); ++resultsLen }
else {
++limitedCount
if(score > q.peek().score) q.replaceTop(objResults)
}
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
if(Date.now() - startMs >= 10/*asyncInterval*/) {
isNode?setImmediate(step):setTimeout(step)
return
}
}
}
// options.key
} else if(options && options.key) {
var key = options.key
for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent]
var target = getValue(obj, key)
if(!target) continue
if(!isObj(target)) target = fuzzysort.getPrepared(target)
var result = algorithm(search, target, searchLowerCode)
if(result === null) continue
if(result.score < threshold) continue
// have to clone result so duplicate targets from different obj can each reference the correct obj
result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
if(resultsLen < limit) { q.add(result); ++resultsLen }
else {
++limitedCount
if(result.score > q.peek().score) q.replaceTop(result)
}
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
if(Date.now() - startMs >= 10/*asyncInterval*/) {
isNode?setImmediate(step):setTimeout(step)
return
}
}
}
// no keys
} else {
for(; iCurrent >= 0; --iCurrent) { var target = targets[iCurrent]
if(!target) continue
if(!isObj(target)) target = fuzzysort.getPrepared(target)
var result = algorithm(search, target, searchLowerCode)
if(result === null) continue
if(result.score < threshold) continue
if(resultsLen < limit) { q.add(result); ++resultsLen }
else {
++limitedCount
if(result.score > q.peek().score) q.replaceTop(result)
}
if(iCurrent%1000/*itemsPerCheck*/ === 0) {
if(Date.now() - startMs >= 10/*asyncInterval*/) {
isNode?setImmediate(step):setTimeout(step)
return
}
}
}
}
if(resultsLen === 0) return resolve(noResults)
var results = new Array(resultsLen)
for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
results.total = resultsLen + limitedCount
resolve(results)
}
isNode?setImmediate(step):step()
})
p.cancel = function() { canceled = true }
return p
},
highlight: function(result, hOpen, hClose) {
if(result === null) return null
if(hOpen === undefined) hOpen = '<b>'
if(hClose === undefined) hClose = '</b>'
var highlighted = ''
var matchesIndex = 0
var opened = false
var target = result.target
var targetLen = target.length
var matchesBest = result.indexes
for(var i = 0; i < targetLen; ++i) { var char = target[i]
if(matchesBest[matchesIndex] === i) {
++matchesIndex
if(!opened) { opened = true
highlighted += hOpen
}
if(matchesIndex === matchesBest.length) {
highlighted += char + hClose + target.substr(i+1)
break
}
} else {
if(opened) { opened = false
highlighted += hClose
}
}
highlighted += char
}
return highlighted
},
prepare: function(target) {
if(!target) return
return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null} // hidden
},
prepareSlow: function(target) {
if(!target) return
return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null} // hidden
},
prepareSearch: function(search) {
if(!search) return
return fuzzysort.prepareLowerCodes(search)
},
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
// Below this point is only internal code
getPrepared: function(target) {
if(target.length > 999) return fuzzysort.prepare(target) // don't cache huge targets
var targetPrepared = preparedCache.get(target)
if(targetPrepared !== undefined) return targetPrepared
targetPrepared = fuzzysort.prepare(target)
preparedCache.set(target, targetPrepared)
return targetPrepared
},
getPreparedSearch: function(search) {
if(search.length > 999) return fuzzysort.prepareSearch(search) // don't cache huge searches
var searchPrepared = preparedSearchCache.get(search)
if(searchPrepared !== undefined) return searchPrepared
searchPrepared = fuzzysort.prepareSearch(search)
preparedSearchCache.set(search, searchPrepared)
return searchPrepared
},
algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
var targetLowerCodes = prepared._targetLowerCodes
var searchLen = searchLowerCodes.length
var targetLen = targetLowerCodes.length
var searchI = 0 // where we at
var targetI = 0 // where you at
var typoSimpleI = 0
var matchesSimpleLen = 0
// very basic fuzzy match; to remove non-matching targets ASAP!
// walk through target. find sequential matches.
// if all chars aren't found then exit
for(;;) {
var isMatch = searchLowerCode === targetLowerCodes[targetI]
if(isMatch) {
matchesSimple[matchesSimpleLen++] = targetI
++searchI; if(searchI === searchLen) break
searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))]
}
++targetI; if(targetI >= targetLen) { // Failed to find searchI
// Check for typo or exit
// we go as far as possible before trying to transpose
// then we transpose backwards until we reach the beginning
for(;;) {
if(searchI <= 1) return null // not allowed to transpose first char
if(typoSimpleI === 0) { // we haven't tried to transpose yet
--searchI
var searchLowerCodeNew = searchLowerCodes[searchI]
if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
typoSimpleI = searchI
} else {
if(typoSimpleI === 1) return null // reached the end of the line for transposing
--typoSimpleI
searchI = typoSimpleI
searchLowerCode = searchLowerCodes[searchI + 1]
var searchLowerCodeNew = searchLowerCodes[searchI]
if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
}
matchesSimpleLen = searchI
targetI = matchesSimple[matchesSimpleLen - 1] + 1
break
}
}
}
var searchI = 0
var typoStrictI = 0
var successStrict = false
var matchesStrictLen = 0
var nextBeginningIndexes = prepared._nextBeginningIndexes
if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
// Our target string successfully matched all characters in sequence!
// Let's try a more advanced and strict test to improve the score
// only count it as a match if it's consecutive or a beginning character!
if(targetI !== targetLen) for(;;) {
if(targetI >= targetLen) {
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
if(searchI <= 0) { // We failed to push chars forward for a better match
// transpose, starting from the beginning
++typoStrictI; if(typoStrictI > searchLen-2) break
if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) continue // doesn't make sense to transpose a repeat char
targetI = firstPossibleI
continue
}
--searchI
var lastMatch = matchesStrict[--matchesStrictLen]
targetI = nextBeginningIndexes[lastMatch]
} else {
var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI]
if(isMatch) {
matchesStrict[matchesStrictLen++] = targetI
++searchI; if(searchI === searchLen) { successStrict = true; break }
++targetI
} else {
targetI = nextBeginningIndexes[targetI]
}
}
}
{ // tally up the score & keep track of matches for highlighting later
if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
var score = 0
var lastTargetI = -1
for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
// score only goes down if they're not consecutive
if(lastTargetI !== targetI - 1) score -= targetI
lastTargetI = targetI
}
if(!successStrict) {
score *= 1000
if(typoSimpleI !== 0) score += -20/*typoPenalty*/
} else {
if(typoStrictI !== 0) score += -20/*typoPenalty*/
}
score -= targetLen - searchLen
prepared.score = score
prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
return prepared
}
},
algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
var targetLowerCodes = prepared._targetLowerCodes
var searchLen = searchLowerCodes.length
var targetLen = targetLowerCodes.length
var searchI = 0 // where we at
var targetI = 0 // where you at
var matchesSimpleLen = 0
// very basic fuzzy match; to remove non-matching targets ASAP!
// walk through target. find sequential matches.
// if all chars aren't found then exit
for(;;) {
var isMatch = searchLowerCode === targetLowerCodes[targetI]
if(isMatch) {
matchesSimple[matchesSimpleLen++] = targetI
++searchI; if(searchI === searchLen) break
searchLowerCode = searchLowerCodes[searchI]
}
++targetI; if(targetI >= targetLen) return null // Failed to find searchI
}
var searchI = 0
var successStrict = false
var matchesStrictLen = 0
var nextBeginningIndexes = prepared._nextBeginningIndexes
if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
// Our target string successfully matched all characters in sequence!
// Let's try a more advanced and strict test to improve the score
// only count it as a match if it's consecutive or a beginning character!
if(targetI !== targetLen) for(;;) {
if(targetI >= targetLen) {
// We failed to find a good spot for this search char, go back to the previous search char and force it forward
if(searchI <= 0) break // We failed to push chars forward for a better match
--searchI
var lastMatch = matchesStrict[--matchesStrictLen]
targetI = nextBeginningIndexes[lastMatch]
} else {
var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
if(isMatch) {
matchesStrict[matchesStrictLen++] = targetI
++searchI; if(searchI === searchLen) { successStrict = true; break }
++targetI
} else {
targetI = nextBeginningIndexes[targetI]
}
}
}
{ // tally up the score & keep track of matches for highlighting later
if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
var score = 0
var lastTargetI = -1
for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
// score only goes down if they're not consecutive
if(lastTargetI !== targetI - 1) score -= targetI
lastTargetI = targetI
}
if(!successStrict) score *= 1000
score -= targetLen - searchLen
prepared.score = score
prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
return prepared
}
},
prepareLowerCodes: function(str) {
var strLen = str.length
var lowerCodes = [] // new Array(strLen) sparse array is too slow
var lower = str.toLowerCase()
for(var i = 0; i < strLen; ++i) lowerCodes[i] = lower.charCodeAt(i)
return lowerCodes
},
prepareBeginningIndexes: function(target) {
var targetLen = target.length
var beginningIndexes = []; var beginningIndexesLen = 0
var wasUpper = false
var wasAlphanum = false
for(var i = 0; i < targetLen; ++i) {
var targetCode = target.charCodeAt(i)
var isUpper = targetCode>=65&&targetCode<=90
var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
wasUpper = isUpper
wasAlphanum = isAlphanum
if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
}
return beginningIndexes
},
prepareNextBeginningIndexes: function(target) {
var targetLen = target.length
var beginningIndexes = fuzzysort.prepareBeginningIndexes(target)
var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
var lastIsBeginning = beginningIndexes[0]
var lastIsBeginningI = 0
for(var i = 0; i < targetLen; ++i) {
if(lastIsBeginning > i) {
nextBeginningIndexes[i] = lastIsBeginning
} else {
lastIsBeginning = beginningIndexes[++lastIsBeginningI]
nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
}
}
return nextBeginningIndexes
},
cleanup: cleanup,
new: fuzzysortNew,
}
return fuzzysort
} // fuzzysortNew
// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
var isNode = typeof require !== 'undefined' && typeof window === 'undefined'
// var MAX_INT = Number.MAX_SAFE_INTEGER
// var MIN_INT = Number.MIN_VALUE
var preparedCache = new Map()
var preparedSearchCache = new Map()
var noResults = []; noResults.total = 0
var matchesSimple = []; var matchesStrict = []
function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = [] }
function defaultScoreFn(a) {
var max = -9007199254740991
for (var i = a.length - 1; i >= 0; --i) {
var result = a[i]; if(result === null) continue
var score = result.score
if(score > max) max = score
}
if(max === -9007199254740991) return null
return max
}
// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
// prop = 'key1.key2' 10ms
// prop = ['key1', 'key2'] 27ms
function getValue(obj, prop) {
var tmp = obj[prop]; if(tmp !== undefined) return tmp
var segs = prop
if(!Array.isArray(prop)) segs = prop.split('.')
var len = segs.length
var i = -1
while (obj && (++i < len)) obj = obj[segs[i]]
return obj
}
function isObj(x) { return typeof x === 'object' } // faster as a function
// Hacked version of https://github.com/lemire/FastPriorityQueue.js
var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c<o;){var f=c+1;e=c,f<o&&r[f].score<r[c].score&&(e=f),r[e-1>>1]=r[e],c=1+(e<<1)}for(var a=e-1>>1;e>0&&n.score<r[a].score;a=(e=a)-1>>1)r[e]=r[a];r[e]=n}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score<r[c].score;c=(n=c)-1>>1)r[n]=r[c];r[n]=e},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e}},e.peek=function(e){if(0!==o)return r[0]},e.replaceTop=function(o){r[0]=o,n()},e};
var q = fastpriorityqueue() // reuse this, except for async, it needs to make its own
return fuzzysortNew()
}) // UMD
// TODO: (performance) wasm version!?
// TODO: (performance) layout memory in an optimal way to go fast by avoiding cache misses
// TODO: (performance) preparedCache is a memory leak
// TODO: (like sublime) backslash === forwardslash
// TODO: (performance) i have no idea how well optizmied the allowing typos algorithm is
diff --git a/help3xsl/get_bookmark.xsl b/help3xsl/get_bookmark.xsl
index b3e0ab4..692ded0 100644
--- a/help3xsl/get_bookmark.xsl
+++ b/help3xsl/get_bookmark.xsl
@@ -63,13 +63,13 @@
<xsl:variable name="hrefhtml" select="substring-before($filename,'xhp')"/>
<xsl:variable name="href" select="concat($target,$Language,'/',$hrefhtml,'html?DbPAR=',$app,'#',@id)"/>
<xsl:for-each select="bookmark_value">
<xsl:text disable-output-escaping="yes"><![CDATA[<a target="_top" href="]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[{url:"]]></xsl:text>
<xsl:value-of select="$href"/>
<xsl:text disable-output-escaping="yes"><![CDATA[" class="fuseshown ]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[", app:"]]></xsl:text>
<xsl:value-of select="$app"/>
<xsl:text disable-output-escaping="yes"><![CDATA[">]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[", text:"]]></xsl:text>
<xsl:call-template name="replace"><xsl:with-param name="text" select="."/></xsl:call-template>
<xsl:text disable-output-escaping="yes"><![CDATA[</a>\]]>
</xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA["},]]>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
@@ -80,16 +80,20 @@
<xsl:with-param name="string">
<xsl:call-template name="apostrophe">
<xsl:with-param name="string">
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:value-of select="substring-before($text,';')"/>
<xsl:text disable-output-escaping="yes"><![CDATA[ -- ]]></xsl:text>
<xsl:value-of select="substring-after($text,';')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="doublequote">
<xsl:with-param name="string">
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:value-of select="substring-before($text,';')"/>
<xsl:text disable-output-escaping="yes"><![CDATA[ -- ]]></xsl:text>
<xsl:value-of select="substring-after($text,';')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
@@ -117,6 +121,26 @@
</xsl:choose>
</xsl:template>
<xsl:template name="doublequote">
<xsl:param name="string"/>
<xsl:variable name="dq">"</xsl:variable><!-- double quote -->
<xsl:choose>
<xsl:when test="contains($string,$dq)">
<xsl:variable name="newstr">
<xsl:value-of select="substring-before($string,$dq)"/>
<xsl:text disable-output-escaping="yes"><![CDATA[&]]>#34;</xsl:text>
<xsl:value-of select="substring-after($string,$dq)"/>
</xsl:variable>
<xsl:call-template name="doublequote">
<xsl:with-param name="string" select="$newstr"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="brand" >
<xsl:param name="string"/>
diff --git a/help3xsl/help.js b/help3xsl/help.js
index 76d47dd..b67df24 100644
--- a/help3xsl/help.js
+++ b/help3xsl/help.js
@@ -8,65 +8,62 @@
*/
// Pagination and fuzzy search
var fuseshown = document.getElementsByClassName("fuseshown");
var indexkids = document.getElementsByClassName("index")[0].children;
var liElements = Array.prototype.slice.call(fuseshown).map(function(elm) {
var item = elm;
var linktext = item.textContent;
var fuseObject = {
item: item,
linktext: linktext
};
return fuseObject;
});
var fuse = new Fuse(liElements, {
keys: ["linktext"],
distance: 60,
location: 0,
threshold: 0.2,
tokenize: true,
matchAllTokens: true,
maxPatternLength: 24,
minMatchCharLength: 2
});
var url = document.URL;
var moduleRegex = new RegExp('text\\/(\\w+)\\/');
var regexArray = moduleRegex.exec(url);
// get the module name from the URL and remove the first character
var currentModule = regexArray[1].toUpperCase().substring(1);
var results = null;
var fullLinkified = '';
var modules = ['CALC', 'WRITER', 'IMPRESS', 'DRAW', 'BASE', 'MATH', 'CHART', 'BASIC', 'SHARED'];
var indexkids = function() { document.getElementsByClassName("index")[0].children; };
// if user is not on a shared category page, limit the index to the current module + shared
if(currentModule !== 'HARED') {
bookmarks = bookmarks.filter(function(obj) {
return obj['app'] === currentModule || obj['app'] === 'SHARED';
});
}
bookmarks.forEach(function(obj) {
fullLinkified += '<a href="' + obj['url'] + '" class="' + obj['app'] + '">' + obj['text'] + '</a>';
});
function fullList() {
document.getElementsByClassName("index")[0].innerHTML = fullLinkified;
Paginator(document.getElementsByClassName("index")[0]);
addIds();
}
// add id to the first items of each category in the index. CSS ::before rule adds the heading text
function addIds() {
for (var i = 0, len = indexkids.length; i < len; i++) {
indexkids[i].removeAttribute("id");
}
modules.forEach(function(module) {
var fuseshownModule = document.getElementsByClassName("fuseshown " + module)[0];
if (typeof fuseshownModule !== 'undefined') {
fuseshownModule.setAttribute("id", module);
var hoduleHeader = document.getElementsByClassName(module)[0];
if (typeof moduleHeader !== 'undefined') {
hoduleHeader.setAttribute("id", module);
}
});
}
// render the unfiltered index list on page load
fullList();
Paginator(document.getElementsByClassName("index")[0]);
addIds();
// filter the index list based on search field input
var search = document.getElementById('search-bar');
var filter = function() {
var target = search.value.trim();
if (target.length < 1) {
liElements.forEach(function(obj) {
obj.item.classList.add('fuseshown');
obj.item.classList.remove('fusehidden');
});
Paginator(document.getElementsByClassName("index")[0]);
addIds();
fullList();
return;
}
var results = fuse.search(target);
liElements.forEach(function(obj) {
obj.item.classList.add('fusehidden');
obj.item.classList.remove('fuseshown');
results = fuzzysort.go(target, bookmarks, {threshold: -15000, key:'text'});
var filtered = '';
results.forEach(function(result) {
filtered += '<a href="' + result.obj['url'] + '" class="' + result.obj['app'] + '">' + result.obj['text'] + '</a>';
});
results.forEach(function(obj) {
obj.item.classList.add('fuseshown');
obj.item.classList.remove('fusehidden');
});
document.getElementsByClassName("index")[0].innerHTML = filtered;
Paginator(document.getElementsByClassName("index")[0]);
addIds();
};
function debounce(fn, wait) {
var timeout;
return function() {
@@ -76,9 +73,7 @@
}, (wait || 150));
};
}
Paginator(document.getElementsByClassName("index")[0]);
search.addEventListener('keyup', debounce(filter, 300));
addIds();
search.addEventListener('keyup', debounce(filter, 100));
// copy useful content to clipboard on mouse click
var copyable = document.getElementsByClassName("input");
for (var i = 0, len = copyable.length; i < len; i++) {
diff --git a/help3xsl/online_transform.xsl b/help3xsl/online_transform.xsl
index a7c3fd1..18cadf2 100644
--- a/help3xsl/online_transform.xsl
+++ b/help3xsl/online_transform.xsl
@@ -153,7 +153,7 @@
<script type="text/javascript" src="{$target}help2.js"></script>
<script type="text/javascript" src="{$target}languages.js"></script>
<script type="text/javascript" src="{$target}{$lang}/langnames.js"></script>
<script type="text/javascript" src="{$target}fuse.js"></script>
<script type="text/javascript" src="{$target}fuzzysort.js"></script>
<script type="text/javascript" src="{$target}paginathing.js"></script>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
</head>
diff --git a/help3xsl/paginathing.js b/help3xsl/paginathing.js
index 3bb88f3..2f36360 100644
--- a/help3xsl/paginathing.js
+++ b/help3xsl/paginathing.js
@@ -75,7 +75,7 @@
var startPage = 1;
var currentPage = 1;
var pageDivision = 0;
var totalItems = el.getElementsByClassName('fuseshown').length;
var totalItems = el.children.length;
var limitPagination = options.limitPagination;
pageDivision = Math.ceil(totalItems / options.perPage);
// let's not display pagination leading nowhere
@@ -187,7 +187,7 @@
var startAt = page === 1 ? 0 : (page - 1) * options.perPage;
var endAt = page * options.perPage;
var domLi = el.getElementsByClassName("fuseshown");
var domLi = el.children;
for (var i = 0, len = domLi.length; i < len; i++) {
var item = domLi[i];
diff --git a/source/text/shared/help/browserhelp.xhp b/source/text/shared/help/browserhelp.xhp
index 0f29667..d594820 100644
--- a/source/text/shared/help/browserhelp.xhp
+++ b/source/text/shared/help/browserhelp.xhp
@@ -19,7 +19,7 @@
<paragraph role="paragraph" id="par_id491525733955136" xml-lang="en-US"><variable id="module">Module</variable></paragraph>
<paragraph role="paragraph" id="par_id531525734031068" xml-lang="en-US"><variable id="language">Language</variable></paragraph>
<paragraph role="paragraph" id="par_id991525734084608" xml-lang="en-US"><variable id="contents">Contents</variable></paragraph>
<paragraph role="paragraph" id="par_id601525734140935" xml-lang="en-US"><variable id="index">Index</variable></paragraph>
<paragraph role="paragraph" id="par_id601525734140935" xml-lang="en-US"><variable id="index">Index of chosen module</variable></paragraph>
<paragraph role="paragraph" id="par_id191525734190260" xml-lang="en-US"><variable id="donate">If this page has been helpful, you can support us!</variable></paragraph>
<paragraph role="paragraph" id="par_id881525734289794" xml-lang="en-US"><variable id="LibreOfficeHelp">%PRODUCTNAME %PRODUCTVERSION Help</variable></paragraph>
<paragraph role="paragraph" id="par_id421525736799965" xml-lang="en-US"><variable id="copyclip">Click on text to copy to clipboard</variable></paragraph>