@ca-plant-list/ca-plant-list 0.4.24 → 0.4.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/taxa.csv +7 -6
- package/data/text/Erodium-botrys.footer.md +3 -0
- package/data/text/Erodium-botrys.md +1 -0
- package/data/text/Erodium-brachycarpum.footer.md +3 -0
- package/data/text/Erodium-brachycarpum.md +1 -0
- package/data/text/Erodium-cicutarium.md +1 -0
- package/data/text/Erodium-moschatum.footer.md +3 -0
- package/data/text/Erodium-moschatum.md +1 -0
- package/generators/eleventy/layouts/h1.njk +6 -0
- package/generators/eleventy/layouts/html.njk +55 -0
- package/lib/basepagerenderer.js +36 -18
- package/lib/config.js +5 -3
- package/lib/ebook/ebookpage.js +1 -4
- package/lib/ebook/ebooksitegenerator.js +4 -12
- package/lib/ebook/glossarypages.js +1 -3
- package/lib/ebook/plantbook.js +1 -1
- package/lib/files.js +1 -0
- package/lib/htmltaxon.js +8 -19
- package/lib/index.d.ts +32 -9
- package/lib/index.js +4 -2
- package/lib/jekyll.js +40 -59
- package/lib/markdown.js +3 -5
- package/lib/sitegenerator.js +68 -12
- package/lib/taxonomy/taxa.js +4 -4
- package/lib/types.js +4 -0
- package/lib/utils/eleventyGenerator.js +82 -0
- package/lib/utils/htmlFragments.js +19 -0
- package/lib/web/glossarypages.js +6 -10
- package/lib/web/pageFamily.js +14 -14
- package/lib/web/pageGeneric.js +78 -0
- package/lib/web/{pagetaxon.js → pageTaxon.js} +4 -4
- package/lib/web/pageTaxonList.js +53 -0
- package/lib/{pagerenderer.js → web/renderAllPages.js} +38 -80
- package/package.json +12 -10
- package/scripts/build-site.js +20 -52
- package/static/assets/js/nameSearchData.js +2 -0
- package/static/assets/js/name_search.js +203 -0
- package/static/assets/js/ui.js +11 -0
- package/static/assets/js/utils.js +56 -0
- package/static/name_search.html +15 -0
- package/jekyll/_includes/glossary.html +0 -0
- package/jekyll/assets/js/name_search.js +0 -165
- package/jekyll/assets/js/ui.js +0 -10
- package/jekyll/assets/js/utils.js +0 -26
- package/jekyll/name_search.html +0 -17
- package/lib/genericpage.js +0 -88
- /package/{jekyll → generators}/_includes/analytics.html +0 -0
- /package/{jekyll → generators}/_includes/menu_extra.html +0 -0
- /package/{jekyll → generators/jekyll}/_config.yml +0 -0
- /package/{jekyll → generators/jekyll}/_layouts/default.html +0 -0
- /package/{jekyll → generators/jekyll}/_layouts/html.html +0 -0
- /package/{jekyll → static}/assets/css/main.css +0 -0
- /package/{jekyll → static}/index.md +0 -0
@@ -1,10 +1,11 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import {
|
1
|
+
import { RarePlants } from "../rareplants.js";
|
2
|
+
import { BasePageRenderer } from "../basepagerenderer.js";
|
3
|
+
import { Files } from "../files.js";
|
4
|
+
import { HTML } from "../html.js";
|
5
|
+
import { TAXA_LIST_COLS } from "../htmltaxon.js";
|
6
|
+
import { PageTaxonList } from "./pageTaxonList.js";
|
7
|
+
import { HTMLFragments } from "../utils/htmlFragments.js";
|
8
|
+
import { PageTaxon } from "./pageTaxon.js";
|
8
9
|
|
9
10
|
const ENDANGERED_COLS = [
|
10
11
|
TAXA_LIST_COLS.SPECIES,
|
@@ -18,33 +19,33 @@ const RPI_COLUMNS = [
|
|
18
19
|
TAXA_LIST_COLS.CNPS_RANK,
|
19
20
|
];
|
20
21
|
|
21
|
-
class PageRenderer extends BasePageRenderer {
|
22
|
+
export class PageRenderer extends BasePageRenderer {
|
22
23
|
/**
|
23
|
-
* @param {
|
24
|
-
* @param {import('
|
25
|
-
* @param {import("
|
24
|
+
* @param {import("../types.js").SiteGenerator} siteGenerator
|
25
|
+
* @param {import('../config.js').Config} config
|
26
|
+
* @param {import("../types.js").Taxa} taxa
|
26
27
|
*/
|
27
|
-
static
|
28
|
-
super.renderBasePages(
|
28
|
+
static renderAll(siteGenerator, config, taxa) {
|
29
|
+
super.renderBasePages(siteGenerator, taxa);
|
29
30
|
|
30
|
-
this.renderLists(
|
31
|
+
this.renderLists(siteGenerator, config, taxa);
|
31
32
|
|
32
33
|
const taxonList = taxa.getTaxonList();
|
33
34
|
for (const taxon of taxonList) {
|
34
|
-
new PageTaxon(
|
35
|
+
new PageTaxon(siteGenerator, config, taxon).render();
|
35
36
|
}
|
36
37
|
}
|
37
38
|
|
38
39
|
/**
|
39
|
-
* @param {
|
40
|
-
* @param {import('
|
41
|
-
* @param {import("
|
40
|
+
* @param {import("../types.js").SiteGenerator} siteGenerator
|
41
|
+
* @param {import('../config.js').Config} config
|
42
|
+
* @param {import("../types.js").Taxa} taxa
|
42
43
|
*/
|
43
|
-
static renderLists(
|
44
|
+
static renderLists(siteGenerator, config, taxa) {
|
44
45
|
/**
|
45
46
|
* @param {ListInfo[]} listInfo
|
46
47
|
* @param {Object<string,string>} attributes
|
47
|
-
* @param {import("
|
48
|
+
* @param {import("../types.js").TaxaColDef[]} [columns]
|
48
49
|
* @returns {string}
|
49
50
|
*/
|
50
51
|
function getListArray(listInfo, attributes = {}, columns) {
|
@@ -75,10 +76,11 @@ class PageRenderer extends BasePageRenderer {
|
|
75
76
|
);
|
76
77
|
|
77
78
|
const cols = columns ? columns : list.columns;
|
78
|
-
new PageTaxonList(
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
new PageTaxonList(
|
80
|
+
siteGenerator,
|
81
|
+
list.name,
|
82
|
+
list.filename,
|
83
|
+
).render(listTaxa, cols);
|
82
84
|
|
83
85
|
// Check for sublists.
|
84
86
|
const subListHTML = list.listInfo
|
@@ -117,7 +119,9 @@ class PageRenderer extends BasePageRenderer {
|
|
117
119
|
return html;
|
118
120
|
}
|
119
121
|
|
120
|
-
|
122
|
+
const outputDir = siteGenerator.getBaseDir();
|
123
|
+
|
124
|
+
/** @typedef {{name:string,filename:string,include:function(import("../types.js").Taxon):boolean,columns?:import("../types.js").TaxaColDef[],listInfo?:ListInfo[]}} ListInfo */
|
121
125
|
/** @type {{title:string,listInfo:ListInfo[]}[]} */
|
122
126
|
const sections = [
|
123
127
|
{
|
@@ -191,7 +195,10 @@ class PageRenderer extends BasePageRenderer {
|
|
191
195
|
},
|
192
196
|
];
|
193
197
|
|
194
|
-
let html =
|
198
|
+
let html =
|
199
|
+
HTMLFragments.getMarkdownSection("./data/intros/index_lists.md") +
|
200
|
+
'<div class="wrapper">';
|
201
|
+
|
195
202
|
for (const section of sections) {
|
196
203
|
const listHTML = getListArray(section.listInfo);
|
197
204
|
|
@@ -208,59 +215,10 @@ class PageRenderer extends BasePageRenderer {
|
|
208
215
|
|
209
216
|
html += "</div>";
|
210
217
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
class PageTaxonList extends GenericPage {
|
217
|
-
/**
|
218
|
-
* @param {string} outputDir
|
219
|
-
* @param {string} title
|
220
|
-
* @param {string} baseName
|
221
|
-
*/
|
222
|
-
constructor(outputDir, title, baseName) {
|
223
|
-
super(outputDir, title, baseName);
|
224
|
-
}
|
225
|
-
|
226
|
-
/**
|
227
|
-
*
|
228
|
-
* @param {import("./types.js").Taxon[]} taxa
|
229
|
-
* @param {import("./types.js").TaxaColDef[]|undefined} columns
|
230
|
-
*/
|
231
|
-
render(taxa, columns) {
|
232
|
-
let html = this.getDefaultIntro();
|
233
|
-
|
234
|
-
html += '<div class="wrapper">';
|
235
|
-
|
236
|
-
html += '<div class="section">';
|
237
|
-
html += HTMLTaxon.getTaxaTable(taxa, columns);
|
238
|
-
html += "</div>";
|
239
|
-
|
240
|
-
html += '<div class="section nobullet">';
|
241
|
-
html += HTML.textElement("h2", "Download");
|
242
|
-
html += "<ul>";
|
243
|
-
html +=
|
244
|
-
"<li>" +
|
245
|
-
HTML.getLink(
|
246
|
-
"./calflora_" + this.getBaseFileName() + ".txt",
|
247
|
-
"Calflora List",
|
248
|
-
) +
|
249
|
-
"</li>";
|
250
|
-
html +=
|
251
|
-
"<li>" +
|
252
|
-
HTML.getLink(
|
253
|
-
"./inat_" + this.getBaseFileName() + ".txt",
|
254
|
-
"iNaturalist List",
|
255
|
-
) +
|
256
|
-
"</li>";
|
257
|
-
html += "</ul>";
|
258
|
-
html += "</div>";
|
259
|
-
|
260
|
-
html += "</div>";
|
261
|
-
|
262
|
-
this.writeFile(html);
|
218
|
+
siteGenerator.writeTemplate(
|
219
|
+
html,
|
220
|
+
{ title: "Plant lists" },
|
221
|
+
"index_lists.html",
|
222
|
+
);
|
263
223
|
}
|
264
224
|
}
|
265
|
-
|
266
|
-
export { PageRenderer };
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ca-plant-list/ca-plant-list",
|
3
|
-
"version": "0.4.
|
4
|
-
"description": "Tools to create
|
3
|
+
"version": "0.4.27",
|
4
|
+
"description": "Tools to create files for a website listing plants in an area of California.",
|
5
5
|
"license": "MIT",
|
6
6
|
"repository": {
|
7
7
|
"type": "git",
|
@@ -12,9 +12,10 @@
|
|
12
12
|
"files": [
|
13
13
|
"data",
|
14
14
|
"ebook",
|
15
|
-
"
|
15
|
+
"generators",
|
16
16
|
"lib",
|
17
|
-
"scripts"
|
17
|
+
"scripts",
|
18
|
+
"static"
|
18
19
|
],
|
19
20
|
"exports": {
|
20
21
|
".": "./lib/index.js"
|
@@ -22,10 +23,10 @@
|
|
22
23
|
"types": "./lib/index.d.ts",
|
23
24
|
"scripts": {
|
24
25
|
"check": "npm run eslint && npm run tsc && npm run jest",
|
25
|
-
"eslint": "
|
26
|
+
"eslint": "eslint",
|
26
27
|
"jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests",
|
27
|
-
"prettier": "
|
28
|
-
"tsc": "
|
28
|
+
"prettier": "prettier -l .",
|
29
|
+
"tsc": "tsc"
|
29
30
|
},
|
30
31
|
"bin": {
|
31
32
|
"ca-plant-list": "scripts/build-site.js",
|
@@ -35,10 +36,11 @@
|
|
35
36
|
"inatobsphotos": "scripts/inatobsphotos.js"
|
36
37
|
},
|
37
38
|
"dependencies": {
|
39
|
+
"@11ty/eleventy": "^3.0.0",
|
38
40
|
"@htmltools/scrape": "^0.1.1",
|
39
41
|
"archiver": "^5.3.1",
|
40
42
|
"cli-progress": "^3.12.0",
|
41
|
-
"commander": "^
|
43
|
+
"commander": "^13.1.0",
|
42
44
|
"csv-parse": "^5.6.0",
|
43
45
|
"csv-stringify": "^6.5.2",
|
44
46
|
"exceljs": "^4.4.0",
|
@@ -55,9 +57,9 @@
|
|
55
57
|
"@types/markdown-it": "^14.1.2",
|
56
58
|
"@types/node": "^22.10.7",
|
57
59
|
"@types/unzipper": "^0.10.9",
|
58
|
-
"eslint": "^9.
|
60
|
+
"eslint": "^9.20.1",
|
59
61
|
"jest": "^29.7.0",
|
60
|
-
"prettier": "^3.
|
62
|
+
"prettier": "^3.5.1",
|
61
63
|
"puppeteer": "^24.1.1",
|
62
64
|
"typescript": "^5.7.3"
|
63
65
|
}
|
package/scripts/build-site.js
CHANGED
@@ -1,76 +1,44 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
2
|
|
3
|
-
import * as child_process from "node:child_process";
|
4
|
-
import * as path from "node:path";
|
5
3
|
import { Config } from "../lib/config.js";
|
6
|
-
import { PageRenderer } from "../lib/
|
4
|
+
import { PageRenderer } from "../lib/web/renderAllPages.js";
|
7
5
|
import { Files } from "../lib/files.js";
|
8
6
|
import { Program } from "../lib/program.js";
|
9
7
|
import { Taxa } from "../lib/taxonomy/taxa.js";
|
10
8
|
import { ErrorLog } from "../lib/errorlog.js";
|
11
9
|
|
12
|
-
class JekyllRenderer {
|
13
|
-
#srcDir = "./output";
|
14
|
-
#destDir = "./public";
|
15
|
-
|
16
|
-
async renderPages() {
|
17
|
-
/**
|
18
|
-
* @param {string[]} configFiles
|
19
|
-
* @param {string} dir
|
20
|
-
* @param {string} name
|
21
|
-
*/
|
22
|
-
function addConfigFile(configFiles, dir, name) {
|
23
|
-
const fullPath = path.join(dir, name);
|
24
|
-
if (Files.exists(fullPath)) {
|
25
|
-
configFiles.push(fullPath);
|
26
|
-
}
|
27
|
-
}
|
28
|
-
|
29
|
-
// Remove existing files.
|
30
|
-
Files.rmDir(this.#destDir);
|
31
|
-
|
32
|
-
const options = [
|
33
|
-
"--source",
|
34
|
-
this.#srcDir,
|
35
|
-
"--destination",
|
36
|
-
this.#destDir,
|
37
|
-
];
|
38
|
-
|
39
|
-
// Find out what config files are available.
|
40
|
-
/** @type {string[]} */
|
41
|
-
const configFiles = [];
|
42
|
-
addConfigFile(configFiles, this.#srcDir, "_config.yml");
|
43
|
-
addConfigFile(configFiles, this.#srcDir, "_config-local.yml");
|
44
|
-
addConfigFile(configFiles, ".", "_config-dev.yml");
|
45
|
-
options.push("--config", `"${configFiles.join()}"`);
|
46
|
-
|
47
|
-
const result = child_process.execSync(
|
48
|
-
"bundle exec jekyll build " + options.join(" "),
|
49
|
-
);
|
50
|
-
console.log(result.toString());
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
10
|
/**
|
55
|
-
* @param {
|
11
|
+
* @param {{outputdir:string,datadir:string,webdir:string,showFlowerErrors:boolean,render:boolean}} options
|
56
12
|
*/
|
57
13
|
async function build(options) {
|
58
|
-
|
59
|
-
const
|
14
|
+
console.info("generating templates");
|
15
|
+
const outputDir = options.outputdir;
|
16
|
+
Files.rmDir(outputDir);
|
17
|
+
const errorLog = new ErrorLog(outputDir + "/errors.tsv");
|
60
18
|
const taxa = new Taxa(
|
61
19
|
Program.getIncludeList(options.datadir),
|
62
20
|
errorLog,
|
63
21
|
options.showFlowerErrors,
|
64
22
|
);
|
65
|
-
|
23
|
+
const config = new Config(options.datadir);
|
24
|
+
const generator = PageRenderer.newSiteGenerator(config, outputDir);
|
25
|
+
PageRenderer.renderAll(generator, config, taxa);
|
66
26
|
errorLog.write();
|
67
27
|
|
68
|
-
|
69
|
-
|
70
|
-
|
28
|
+
if (options.render) {
|
29
|
+
console.info("generating site");
|
30
|
+
Files.rmDir(options.webdir);
|
31
|
+
await generator.generate(options.webdir);
|
32
|
+
}
|
71
33
|
}
|
72
34
|
|
73
35
|
const program = Program.getProgram();
|
36
|
+
program.option(
|
37
|
+
"--no-render",
|
38
|
+
"Do not render full HTML output (only build the templates)",
|
39
|
+
);
|
40
|
+
program.option("-w, --webdir", "Directory for fully processed files", "public");
|
41
|
+
|
74
42
|
program.action(build);
|
75
43
|
|
76
44
|
await program.parseAsync();
|
@@ -0,0 +1,203 @@
|
|
1
|
+
import { NAMES } from "./nameSearchData.js";
|
2
|
+
import { Utils } from "./utils.js";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @typedef {{raw:import("../../../lib/types.js").NameSearchData,searchSci:string,searchCommon?:string,synonyms?:string[]}} SearchData
|
6
|
+
*/
|
7
|
+
|
8
|
+
const MIN_LEN = 2;
|
9
|
+
const MAX_RESULTS = 50;
|
10
|
+
|
11
|
+
class Search {
|
12
|
+
/** @type {number|undefined} */
|
13
|
+
static #debounceTimer;
|
14
|
+
/** @type {SearchData[]} */
|
15
|
+
static #searchData;
|
16
|
+
|
17
|
+
/**
|
18
|
+
* @param {number} [timeout]
|
19
|
+
*/
|
20
|
+
static #debounce(timeout = 500) {
|
21
|
+
clearTimeout(this.#debounceTimer);
|
22
|
+
this.#debounceTimer = window.setTimeout(Search.#doSearch, timeout);
|
23
|
+
}
|
24
|
+
|
25
|
+
static #doSearch() {
|
26
|
+
/**
|
27
|
+
* @param {SearchData} taxon
|
28
|
+
* @param {string} value
|
29
|
+
*/
|
30
|
+
function matchTaxon(taxon, value) {
|
31
|
+
/**
|
32
|
+
* @param {string[]|undefined} syns
|
33
|
+
* @param {string} value
|
34
|
+
* @returns {number[]}
|
35
|
+
*/
|
36
|
+
function matchSynonyms(syns, value) {
|
37
|
+
const matchedIndexes = [];
|
38
|
+
if (syns) {
|
39
|
+
for (let index = 0; index < syns.length; index++) {
|
40
|
+
if (syns[index].includes(value)) {
|
41
|
+
matchedIndexes.push(index);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
return matchedIndexes;
|
46
|
+
}
|
47
|
+
|
48
|
+
const name = taxon.searchSci;
|
49
|
+
const cn = taxon.searchCommon;
|
50
|
+
const syns = matchSynonyms(taxon.synonyms, value);
|
51
|
+
if (syns.length > 0) {
|
52
|
+
// Include any matching synonyms.
|
53
|
+
for (const index of syns) {
|
54
|
+
matches.push([
|
55
|
+
taxon.raw.t,
|
56
|
+
taxon.raw.c,
|
57
|
+
taxon.raw.s ? taxon.raw.s[index] : undefined,
|
58
|
+
]);
|
59
|
+
}
|
60
|
+
} else {
|
61
|
+
// No synonyms match; see if the scientific or common names match.
|
62
|
+
const namesMatch =
|
63
|
+
name.includes(value) || (cn && cn.includes(value));
|
64
|
+
if (namesMatch) {
|
65
|
+
matches.push([taxon.raw.t, taxon.raw.c]);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
Search.#debounceTimer = undefined;
|
71
|
+
|
72
|
+
const input = Utils.getElement("name");
|
73
|
+
if (!(input instanceof HTMLInputElement)) {
|
74
|
+
throw new Error();
|
75
|
+
}
|
76
|
+
const value = Search.#normalizeName(input.value);
|
77
|
+
|
78
|
+
/**
|
79
|
+
* @type {([string,string|undefined]|[string,string|undefined,string|undefined])[]}
|
80
|
+
*/
|
81
|
+
const matches = [];
|
82
|
+
const shouldSearch = value.length >= MIN_LEN;
|
83
|
+
|
84
|
+
if (shouldSearch) {
|
85
|
+
// If the search data is not done generating, try again later.
|
86
|
+
if (!Search.#searchData) {
|
87
|
+
this.#debounce();
|
88
|
+
}
|
89
|
+
|
90
|
+
for (const taxon of Search.#searchData) {
|
91
|
+
matchTaxon(taxon, value);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
const eBody = document.createElement("tbody");
|
96
|
+
if (matches.length <= MAX_RESULTS) {
|
97
|
+
for (const match of matches) {
|
98
|
+
const tr = document.createElement("tr");
|
99
|
+
|
100
|
+
// Scientific name.
|
101
|
+
const name = match[0];
|
102
|
+
const syn = match[2];
|
103
|
+
const td1 = document.createElement("td");
|
104
|
+
const link = Utils.domTaxonLink(name);
|
105
|
+
td1.appendChild(link);
|
106
|
+
if (syn) {
|
107
|
+
td1.appendChild(document.createTextNode(" (" + syn + ")"));
|
108
|
+
}
|
109
|
+
tr.appendChild(td1);
|
110
|
+
|
111
|
+
const cn = match[1];
|
112
|
+
const td2 = document.createElement("td");
|
113
|
+
if (cn) {
|
114
|
+
td2.textContent = cn;
|
115
|
+
}
|
116
|
+
tr.appendChild(td2);
|
117
|
+
|
118
|
+
eBody.appendChild(tr);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
// Delete current message
|
123
|
+
const eMessage = Utils.getElement("message");
|
124
|
+
if (eMessage.firstChild) {
|
125
|
+
eMessage.removeChild(eMessage.firstChild);
|
126
|
+
}
|
127
|
+
if (shouldSearch) {
|
128
|
+
if (matches.length === 0) {
|
129
|
+
eMessage.textContent = "Nothing found.";
|
130
|
+
}
|
131
|
+
if (matches.length > MAX_RESULTS) {
|
132
|
+
eMessage.textContent = "Too many results.";
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
// Delete current results
|
137
|
+
const eTable = Utils.getElement("results");
|
138
|
+
if (eTable.firstChild) {
|
139
|
+
eTable.removeChild(eTable.firstChild);
|
140
|
+
}
|
141
|
+
|
142
|
+
eTable.appendChild(eBody);
|
143
|
+
}
|
144
|
+
|
145
|
+
static async generateSearchData() {
|
146
|
+
/** @type {SearchData[]} */
|
147
|
+
const searchData = [];
|
148
|
+
|
149
|
+
/** @type {import("../../../lib/types.js").NameSearchData[]} */
|
150
|
+
const names = NAMES;
|
151
|
+
|
152
|
+
for (const taxon of names) {
|
153
|
+
/** @type {SearchData} */
|
154
|
+
const taxonData = {
|
155
|
+
raw: taxon,
|
156
|
+
searchSci: this.#normalizeName(taxon.t),
|
157
|
+
};
|
158
|
+
if (taxon.c) {
|
159
|
+
taxonData.searchCommon = taxon.c.toLowerCase();
|
160
|
+
}
|
161
|
+
if (taxon.s) {
|
162
|
+
const syns = [];
|
163
|
+
for (const syn of taxon.s) {
|
164
|
+
syns.push(this.#normalizeName(syn));
|
165
|
+
}
|
166
|
+
taxonData.synonyms = syns;
|
167
|
+
}
|
168
|
+
searchData.push(taxonData);
|
169
|
+
}
|
170
|
+
this.#searchData = searchData;
|
171
|
+
}
|
172
|
+
|
173
|
+
static #handleChange() {
|
174
|
+
this.#debounce();
|
175
|
+
}
|
176
|
+
|
177
|
+
static #handleSubmit() {
|
178
|
+
this.#debounce(0);
|
179
|
+
}
|
180
|
+
|
181
|
+
static init() {
|
182
|
+
this.generateSearchData();
|
183
|
+
const eName = Utils.getElement("name");
|
184
|
+
eName.focus();
|
185
|
+
eName.oninput = () => {
|
186
|
+
return this.#handleChange();
|
187
|
+
};
|
188
|
+
Utils.getElement("search_form").onsubmit = () => {
|
189
|
+
this.#handleSubmit();
|
190
|
+
return false;
|
191
|
+
};
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* @param {string} name
|
196
|
+
* @returns {string}
|
197
|
+
*/
|
198
|
+
static #normalizeName(name) {
|
199
|
+
return name.toLowerCase().replace(/ (subsp|var)\./, "");
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
Search.init();
|
@@ -0,0 +1,56 @@
|
|
1
|
+
export class Utils {
|
2
|
+
/**
|
3
|
+
* @param {string} name
|
4
|
+
* @param {Object<string,string>} attributes
|
5
|
+
* @param {string} [content]
|
6
|
+
* @returns {Element}
|
7
|
+
*/
|
8
|
+
static domElement(name, attributes = {}, content) {
|
9
|
+
const e = document.createElement(name);
|
10
|
+
for (const [k, v] of Object.entries(attributes)) {
|
11
|
+
e.setAttribute(k, v);
|
12
|
+
}
|
13
|
+
if (content) {
|
14
|
+
e.textContent = content;
|
15
|
+
}
|
16
|
+
return e;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @param {string} href
|
21
|
+
* @param {string} text
|
22
|
+
* @param {Object<string,string>} attributes
|
23
|
+
* @returns {Element}
|
24
|
+
*/
|
25
|
+
static domLink(href, text, attributes = {}) {
|
26
|
+
const e = this.domElement(
|
27
|
+
"a",
|
28
|
+
Object.assign({ href: href }, attributes),
|
29
|
+
);
|
30
|
+
e.textContent = text;
|
31
|
+
return e;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* @param {string} name
|
36
|
+
* @returns {Element}
|
37
|
+
*/
|
38
|
+
static domTaxonLink(name) {
|
39
|
+
return this.domLink(
|
40
|
+
"./" + name.replaceAll(".", "").replaceAll(" ", "-") + ".html",
|
41
|
+
name,
|
42
|
+
);
|
43
|
+
}
|
44
|
+
|
45
|
+
/**
|
46
|
+
* @param {string} id
|
47
|
+
* @returns {HTMLElement}
|
48
|
+
*/
|
49
|
+
static getElement(id) {
|
50
|
+
const e = document.getElementById(id);
|
51
|
+
if (e === null) {
|
52
|
+
throw new Error();
|
53
|
+
}
|
54
|
+
return e;
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
title: Name Search
|
3
|
+
js: name_search.js
|
4
|
+
---
|
5
|
+
|
6
|
+
<form id="search_form">
|
7
|
+
<input type="text" id="name" />
|
8
|
+
<label for="name"
|
9
|
+
>Search for scientific name, common name, or synonym.</label
|
10
|
+
>
|
11
|
+
</form>
|
12
|
+
|
13
|
+
<div class="section" id="message"></div>
|
14
|
+
|
15
|
+
<table id="results"></table>
|
File without changes
|