bookshelf-doc/static/declaration-data.js

139 lines
3.6 KiB
JavaScript
Raw Normal View History

2022-02-22 04:40:14 +00:00
/**
* This module is a wrapper that facilitates manipulating the declaration data.
2022-02-22 07:01:14 +00:00
*
2022-02-22 04:40:14 +00:00
* Please see {@link DeclarationDataCenter} for more information.
*/
import { SITE_ROOT } from "./site-root.js";
/**
* The DeclarationDataCenter is used for declaration searching.
2022-02-22 07:01:14 +00:00
*
2022-02-22 04:40:14 +00:00
* For usage, see the {@link init} and {@link search} methods.
*/
export class DeclarationDataCenter {
/**
2022-02-22 07:01:14 +00:00
* The declaration data. Users should not interact directly with this field.
*
2022-02-22 04:40:14 +00:00
* *NOTE:* This is not made private to support legacy browsers.
*/
2022-02-22 07:01:14 +00:00
declarationData = null;
2022-02-22 04:40:14 +00:00
/**
* Used to implement the singleton, in case we need to fetch data mutiple times in the same page.
*/
static singleton = null;
/**
* Construct a DeclarationDataCenter with given data.
2022-02-22 07:01:14 +00:00
*
2022-02-22 04:40:14 +00:00
* Please use {@link DeclarationDataCenter.init} instead, which automates the data fetching process.
* @param {*} declarationData
*/
constructor(declarationData) {
this.declarationData = declarationData;
}
/**
* The actual constructor of DeclarationDataCenter
* @returns {Promise<DeclarationDataCenter>}
*/
static async init() {
if (!DeclarationDataCenter.singleton) {
const dataUrl = new URL(
`${SITE_ROOT}declaration-data.bmp`,
window.location
);
const response = await fetch(dataUrl);
const json = await response.json();
2022-02-22 07:01:14 +00:00
// the data is a map of name (original case) to declaration data.
const data = new Map(
json.map(({ name, doc, link, source: sourceLink }) => [
name,
{
name,
lowerName: name.toLowerCase(),
lowerDoc: doc.toLowerCase(),
link,
sourceLink,
},
])
);
2022-02-22 04:40:14 +00:00
DeclarationDataCenter.singleton = new DeclarationDataCenter(data);
}
return DeclarationDataCenter.singleton;
}
/**
* Search for a declaration.
* @returns {Array<any>}
*/
search(pattern, strict = false) {
if (!pattern) {
return [];
}
2022-02-22 07:01:14 +00:00
if (strict) {
let decl = this.declarationData.get(pattern);
return decl ? [decl] : [];
} else {
return getMatches(this.declarationData, pattern);
}
2022-02-22 04:40:14 +00:00
}
}
function isSeparater(char) {
return char === "." || char === "_";
}
2022-02-22 07:01:14 +00:00
// HACK: the fuzzy matching is quite hacky
function matchCaseSensitive(declName, lowerDeclName, pattern) {
2022-02-22 04:40:14 +00:00
let i = 0,
j = 0,
err = 0,
lastMatch = 0;
while (i < declName.length && j < pattern.length) {
2022-02-22 07:01:14 +00:00
if (pattern[j] === declName[i] || pattern[j] === lowerDeclName[i]) {
2022-02-22 04:40:14 +00:00
err += (isSeparater(pattern[j]) ? 0.125 : 1) * (i - lastMatch);
if (pattern[j] !== declName[i]) err += 0.5;
lastMatch = i + 1;
j++;
} else if (isSeparater(declName[i])) {
err += 0.125 * (i + 1 - lastMatch);
lastMatch = i + 1;
}
i++;
}
err += 0.125 * (declName.length - lastMatch);
if (j === pattern.length) {
return err;
}
}
function getMatches(declarations, pattern, maxResults = 30) {
const lowerPats = pattern.toLowerCase().split(/\s/g);
const patNoSpaces = pattern.replace(/\s/g, "");
const results = [];
2022-02-22 07:01:14 +00:00
for (const {
name,
lowerName,
lowerDoc,
link,
sourceLink,
} of declarations.values()) {
let err = matchCaseSensitive(name, lowerName, patNoSpaces);
2022-02-22 04:40:14 +00:00
// match all words as substrings of docstring
if (
2022-02-22 07:01:14 +00:00
err >= 3 &&
2022-02-22 04:40:14 +00:00
pattern.length > 3 &&
lowerPats.every((l) => lowerDoc.indexOf(l) != -1)
) {
err = 3;
}
if (err !== undefined) {
2022-02-22 07:01:14 +00:00
results.push({ name, err, lowerName, lowerDoc, link, sourceLink });
2022-02-22 04:40:14 +00:00
}
}
return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults);
}