Switched from list.js to fuse.js and paginathing.js

Fuse.js has Apache License 2.0 and paginathing.js has
Expat License (aka MIT).

Paginathing is a paginating utility and it was originally jQuery,
but due to performance concerns I converted it to pure JavaScript
and rather brutally adapted it to our needs.

Fuse.js gives us fuzzy search with multiple strings. The settings
can be refined, if needed. Settings can be easily tested with the
live demo: http://fusejs.io/

Change-Id: I1cf6a6f7d06adbcac95760db90187ee26be8e908
Reviewed-on: https://gerrit.libreoffice.org/48906
Reviewed-by: Olivier Hallot <olivier.hallot@libreoffice.org>
Tested-by: Olivier Hallot <olivier.hallot@libreoffice.org>
This commit is contained in:
Ilmari Lauhakangas
2018-01-30 13:27:55 +02:00
committed by Olivier Hallot
parent 64187c4328
commit e37c19a9dd
12 changed files with 1344 additions and 99 deletions

View File

@ -114,7 +114,7 @@ $(call gb_CustomTarget_get_workdir,helpcontent2/help3xsl)/%/bookmarks.js :
$(call gb_Output_announce,$(subst $(WORKDIR)/,,$@),$(true),CAT,2)
$(call gb_Helper_abbreviate_dirs,\
( \
echo 'document.getElementById("Bookmarks").getElementsByClassName( "list" )[0].innerHTML='"'"'\' \
echo 'document.getElementsByClassName( "list" )[0].innerHTML='"'"'\' \
&& cat $(filter %.part,$^) \
&& echo "'" \
) > $@ \

View File

@ -18,8 +18,8 @@ $(eval $(call gb_Package_add_file,helpcontent2_html_static,$(LIBO_SHARE_HELP_FOL
$(eval $(call gb_Package_add_files,helpcontent2_html_static,$(LIBO_SHARE_HELP_FOLDER)/$(PRODUCTVERSION),\
help.js \
jquery-3.1.1.min.js \
list.min.js \
fuse.js \
paginathing.js \
normalize.css \
default.css \
))

View File

@ -284,7 +284,7 @@ echo '</sitemapindex>'>>$sitemap
###########################################
#cp help.html index.html html/
cp normalize.css default.css help.js jquery-3.1.1.min.js $outDir
cp normalize.css default.css help.js fuse.js paginathing.js $outDir
cp -r $enSource/source/media $outDir
mkdir -p $outDir/media/icon-themes
#cp -a ../../icon-themes/galaxy/* $outDir/media/icon-themes/

View File

@ -353,10 +353,10 @@ aside input[type=checkbox]:checked ~ .contents-treeview {
#Index {
margin-top: 10px;
}
#Index ul {
.list {
padding-left: 15px;
}
#Index ul li {
.list li {
list-style: none;
font-size: 16px;
margin-bottom: 5px;
@ -369,72 +369,96 @@ aside input[type=checkbox]:checked ~ .contents-treeview {
font-weight: bold;
color: #18A303;
}
#WRITER::before {
#writer::before {
content: "WRITER";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#CALC::before {
#calc::before {
content: "CALC";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#IMPRESS::before {
#impress::before {
content: "IMPRESS";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#DRAW::before {
#draw::before {
content: "DRAW";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#BASE::before {
#base::before {
content: "BASE";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#MATH::before {
#math::before {
content: "MATH";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#CHART::before {
#chart::before {
content: "CHART";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#BASIC::before {
#basic::before {
content: "BASIC";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
#SHARED::before {
#shared::before {
content: "GLOBAL";
display: block;
font-size: 22px;
font-weight: bold;
color: #18A303;
}
.pagination {
padding: 0;
}
.pagination li {
display: inline-block;
padding: 5px;
padding: 4px;
}
.pagination a {
text-decoration: none;
}
.fuseshown {
display: block;
}
.fusehidden {
display: none;
}
.fuseshown.hidden {
display: none;
}
li.active {
background-color: #18A303;
}
li.active a {
color: #fff;
}
li.disabled a {
color: #bedcba;
}
#search-bar {
margin-top: 10px;
@ -511,10 +535,10 @@ aside input[type=checkbox]:checked ~ .contents-treeview {
-webkit-user-select: none;
user-select: none;
}
.contents-treeview a {
.contents-treeview a, .list a {
text-decoration: none;
}
.contents-treeview a:hover {
.contents-treeview a:hover, .list a:hover {
text-decoration: underline;
}
.contents-treeview input + label + ul {

998
help3xsl/fuse.js Normal file
View File

@ -0,0 +1,998 @@
/*!
* 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 texvaluet 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;
/***/ })
/******/ ]);
});
//# sourceMappingURL=fuse.js.map

View File

@ -55,7 +55,7 @@ xsltproc get_bookmark.xsl <file.xhp>
<xsl:variable name="hrefhtml" select="substring-before($filename,'xhp')"/>
<xsl:variable name="href" select="concat($productversion,'/',$Language,'/',$hrefhtml,'html?DbPAR=',$app,'#',@id)"/>
<xsl:for-each select="bookmark_value">
<xsl:text disable-output-escaping="yes"><![CDATA[<li><a target="_top" href="]]></xsl:text>
<xsl:text disable-output-escaping="yes"><![CDATA[<li class="fuseshown"><a target="_top" href="]]></xsl:text>
<xsl:value-of select="$href"/>
<xsl:text disable-output-escaping="yes"><![CDATA[" class="]]></xsl:text>
<xsl:value-of select="$app"/>

View File

@ -32,8 +32,8 @@ cp index.html $outDir
cp help.html $outDir
cp index2.html $outDir'/'$productVersion'/index.html'
cp help.js $outDir'/'$productVersion'/'
cp jquery-3.1.1.min.js $outDir'/'$productVersion'/'
cp list.min.js $outDir'/'$productVersion'/'
cp fuse.js $outDir'/'$productVersion'/'
cp paginathing.js $outDir'/'$productVersion'/'
cp normalize.css $outDir'/'$productVersion'/'
cp default.css $outDir'/'$productVersion'/'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -133,9 +133,9 @@
<link rel="shortcut icon" href="{$productversion}/media/navigation/favicon.ico" />
<link type="text/css" href="{$productversion}/normalize.css" rel="Stylesheet" />
<link type="text/css" href="{$productversion}/default.css" rel="Stylesheet" />
<script type="text/javascript" src="{$productversion}/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="{$productversion}/help.js"></script>
<script type="text/javascript" src="{$productversion}/list.min.js"></script>
<script type="text/javascript" src="{$productversion}/fuse.js"></script>
<script type="text/javascript" src="{$productversion}/paginathing.js"></script>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
</head>
<body itemscope="true" itemtype="http://schema.org/TechArticle">
@ -258,7 +258,6 @@
<div id="Bookmarks">
<input id="search-bar" type="text" class="search" />
<ul class="list"></ul>
<ul class="pagination"></ul>
</div>
</div>
</aside>
@ -296,77 +295,65 @@
</div>
<script type="text/javascript" src="{$productversion}/{$lang}/bookmarks.js"/>
<script type="text/javascript" src="{$productversion}/{$lang}/contents.js"/>
<!-- for list.js -->
<!-- for fuse.js and paginathing.js -->
<script type="text/javascript">
<![CDATA[
// Polyfill for https://tc39.github.io/ecma262/#sec-array.prototype.findIndex
if (!Array.prototype.findIndex) {
Object.defineProperty(Array.prototype, 'findIndex', {
value: function(predicate) {
// 1. Let O be ? ToObject(this value).
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
var thisArg = arguments[1];
// 5. Let k be 0.
var k = 0;
// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk).
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
// d. If testResult is true, return k.
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return k;
}
// e. Increase k by 1.
k++;
}
// 7. Return -1.
return -1;
}
});
}
var modules = [ 'CALC', 'WRITER', 'IMPRESS', 'DRAW', 'BASE', 'MATH', 'CHART', 'BASIC', 'SHARED' ];
// options for List.js http://listjs.com/
var options = {
valueNames: modules,
page: 10,
pagination: true,
indexAsync: true
var liElements = Array.prototype.slice.call(document.getElementsByClassName("list")[0].getElementsByTagName("li")).map(function(elm) {
var item = elm;
var linktext = item.childNodes[0].textContent;
return {
item, linktext
};
var bookmarkList = new List('Bookmarks', options);
// the module ids have CSS rules to create ::before elements containing their names
function addIds() {
var visibleArray = bookmarkList.visibleItems;
visibleArray.forEach(function(element) {
element.elm.removeAttribute("id");
});
modules.forEach(function(module) {
function matchClass(element) {
return element.elm.childNodes[0].className === module;
}
var index = visibleArray.findIndex(matchClass);
if(typeof visibleArray[index] !== 'undefined') { visibleArray[index].elm.setAttribute("id", module); };
});
var fuse = new Fuse(liElements, {
keys: ["linktext"],
distance: 80,
location: 0,
threshold: 0.4,
tokenize: true,
matchAllTokens: true,
maxPatternLength: 24,
minMatchCharLength: 2
});
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("list")[0]);
return;
}
bookmarkList.on('updated', addIds);
var results = fuse.search(target);
liElements.forEach(function(obj) {
obj.item.classList.add('fusehidden');
obj.item.classList.remove('fuseshown');
});
results.forEach(function(obj) {
obj.item.classList.add('fuseshown');
obj.item.classList.remove('fusehidden');
});
Paginator(document.getElementsByClassName("list")[0]);
};
function debounce(fn, wait) {
var timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
fn.apply(this, arguments)
}, (wait || 150));
}
};
Paginator(document.getElementsByClassName("list")[0]);
search.addEventListener('keyup', debounce(filter, 200));
]]>
</script>
<xsl:choose>

242
help3xsl/paginathing.js Normal file
View File

@ -0,0 +1,242 @@
/**
* Paginathing
* Paginate Everything
*
* Original @author Alfred Crosby <https://github.com/alfredcrosby>
* Inspired from http://esimakin.github.io/twbs-pagination/
* Modified to pure JavaScript and specialised to LibreOffice Help by
* Ilmari Lauhakangas
*
* MIT License (Expat)
*
* Copyright (c) 2018 Alfred Crosby
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var options = {
perPage: 10,
limitPagination: 6,
prevNext: true,
firstLast: true,
prevText: '<<',
nextText: '>>',
firstText: '<',
lastText: '>',
containerClass: 'pagination-container',
ulClass: 'pagination',
liClass: 'page',
activeClass: 'active',
disabledClass: 'disabled'
};
var Paginator = function(element) {
el = element;
startPage = 1;
currentPage = 1;
pageDivision = 0;
totalItems = el.getElementsByClassName('fuseshown').length;
var limitPagination = options.limitPagination;
pageDivision = Math.ceil(totalItems / options.perPage);
// let's not display pagination leading nowhere
function pagLimit() {
if (options.limitPagination >= pageDivision) {
limitPagination = pageDivision;
} else {
limitPagination = options.limitPagination;
}
return limitPagination;
}
totalPages = Math.max(pageDivision, pagLimit());
existingContainer = document.getElementsByClassName('pagination-container')[0];
if (existingContainer) {
parent = existingContainer.parentNode;
parent.removeChild(existingContainer);
}
container = document.createElement('nav');
container.setAttribute('class', options.containerClass);
ul = document.createElement('ul');
ul.setAttribute('class', options.ulClass);
function paginationFunc(type, page) {
var li = document.createElement('li');
var a = document.createElement('a');
a.setAttribute('href', '#');
var cssClass = type === 'number' ? options.liClass : type;
var text = document.createTextNode(type === 'number' ? page : paginationText(type));
li.classList.add(cssClass);
li.setAttribute('data-pagination-type', type);
li.setAttribute('data-page', page);
a.appendChild(text);
li.appendChild(a);
return li;
}
function paginationText(type) {
return options[type + 'Text'];
}
function buildPagination() {
var pagination = [];
var prev = currentPage - 1 < startPage ? startPage : currentPage - 1;
var next = currentPage + 1 > totalPages ? totalPages : currentPage + 1;
var start = 0;
var end = 0;
var limit = limitPagination;
if (limit) {
if (currentPage <= Math.ceil(limit / 2) + 1) {
start = 1;
end = limit;
} else if (currentPage + Math.floor(limit / 2) >= totalPages) {
start = totalPages + 1 - limit;
end = totalPages;
} else {
start = currentPage - Math.ceil(limit / 2);
end = currentPage + Math.floor(limit / 2);
}
} else {
start = startPage;
end = totalPages;
}
// "First" button
if (options.firstLast) {
pagination.push(paginationFunc('first', startPage));
}
// "Prev" button
if (options.prevNext) {
pagination.push(paginationFunc('prev', prev));
}
// Pagination
for (var i = start; i <= end; i++) {
pagination.push(paginationFunc('number', i));
}
// "Next" button
if (options.prevNext) {
pagination.push(paginationFunc('next', next));
}
// "Last" button
if (options.firstLast) {
pagination.push(paginationFunc('last', totalPages));
}
return pagination;
}
function render(page) {
// Remove children before re-render (prevent duplicate)
while (ul.hasChildNodes()) {
ul.removeChild(ul.lastChild);
}
var paginationBuild = buildPagination();
paginationBuild.forEach(function(item) {
ul.appendChild(item);
});
// Manage active DOM
var startAt = page === 1 ? 0 : (page - 1) * options.perPage;
var endAt = page * options.perPage;
var domLi = el.getElementsByClassName("fuseshown");
for (var i = 0, len = domLi.length; i < len; i++) {
var item = domLi[i];
if (i >= startAt && i <= endAt) {
item.classList.remove('hidden');
} else {
item.classList.add('hidden');
}
}
// Manage active state
var ulKids = ul.getElementsByTagName("li");
for (var i = 0, len = ulKids.length; i < len; i++) {
var _li = ulKids[i];
var type = _li.getAttribute('data-pagination-type');
switch (type) {
case 'number':
if (parseInt(_li.getAttribute('data-page')) === page) {
_li.classList.add(options.activeClass);
}
break;
case 'first':
page === startPage && _li.classList.toggle(options.disabledClass);
break;
case 'last':
page === totalPages && _li.classList.toggle(options.disabledClass);
break;
case 'prev':
(page - 1) < startPage && _li.classList.toggle(options.disabledClass);
break;
case 'next':
(page + 1) > totalPages && _li.classList.toggle(options.disabledClass);
break;
default:
break;
}
}
el.after(container);
container.appendChild(ul);
}
function handle() {
var pagLi = container.childNodes[0].childNodes;
for (var i = 0, len = pagLi.length; i < len; i++) {
(function() {
var item = pagLi[i];
item.addEventListener('click', function(e) {
e.preventDefault();
var page = parseInt(item.getAttribute('data-page'));
currentPage = page;
// let's prevent the pagination from flowing to two rows
if (currentPage >= 98) {
limitPagination = 4;
} else {
limitPagination = pagLimit();
}
show(page);
});
}());
}
}
function show(page) {
render(page);
handle();
}
show(startPage);
return;
};

View File

@ -35,7 +35,7 @@ rm -f $bookmarkFile
touch $bookmarkFile
stub2=\'
stub1='document.getElementById("Bookmarks").getElementsByClassName( "list" )[0].innerHTML='\'\\
stub1='document.getElementsByClassName( "list" )[0].innerHTML='\'\\
echo $stub1 >> $bookmarkFile
xslfile=get_bookmark.xsl
@ -171,8 +171,8 @@ cp index.html $here'/html/'
cp help.html $here'/html/'
cp index2.html $here'/html/'$productversion'/index.html'
cp help.js $here'/html/'$productversion'/'
cp jquery-3.1.1.min.js $here'/html/'$productversion'/'
cp list.min.js $here'/html/'$productversion'/'
cp fuse.js $here'/html/'$productversion'/'
cp paginathing.js $here'/html/'$productversion'/'
cp normalize.css $here'/html/'$productversion'/'
cp default.css $here'/html/'$productversion'/'