@ca-plant-list/ca-plant-list 0.3.3 → 0.3.5
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/glossary/calyx.md +1 -0
- package/data/glossary/pedicel.md +1 -0
- package/data/glossary/sepal.md +1 -0
- package/data/synonyms.csv +6 -4
- package/data/taxa.csv +293 -291
- package/data/text/Agoseris-grandiflora-var-grandiflora.md +1 -0
- package/data/text/Agoseris-heterophylla-var-cryptopleura.md +1 -0
- package/data/text/Calochortus-argillosus.md +1 -0
- package/data/text/Calochortus-luteus.md +1 -0
- package/data/text/Calochortus-venustus.md +1 -0
- package/data/text/Claytonia-parviflora-subsp-parviflora.md +1 -0
- package/data/text/Claytonia-perfoliata-subsp-perfoliata.md +1 -0
- package/data/text/Collinsia-heterophylla-var-heterophylla.md +1 -0
- package/data/text/Galium-aparine.md +1 -0
- package/data/text/Galium-triflorum.md +1 -0
- package/data/text/Hypochaeris-glabra.md +1 -1
- package/data/text/Hypochaeris-radicata.md +1 -1
- package/data/text/Leptosiphon-ambiguus.md +1 -0
- package/data/text/Leptosiphon-androsaceus.md +1 -0
- package/data/text/Leptosiphon-bicolor.md +1 -1
- package/data/text/Leptosiphon-parviflorus.md +1 -0
- package/data/text/Lithophragma-affine.md +1 -1
- package/data/text/Lupinus-formosus-var-formosus.md +1 -1
- package/data/text/Lupinus-latifolius-var-latifolius.md +1 -1
- package/data/text/Lupinus-microcarpus-var-densiflorus.md +1 -1
- package/data/text/Lupinus-microcarpus-var-microcarpus.md +1 -1
- package/data/text/Lupinus-succulentus.md +1 -1
- package/data/text/Pinus-ponderosa-var-pacifica.md +1 -0
- package/data/text/Pinus-sabiniana.md +1 -0
- package/data/text/Quercus-douglasii.md +1 -0
- package/data/text/Quercus-kelloggii.md +1 -0
- package/data/text/Quercus-lobata.md +1 -0
- package/data/text/Sidalcea-diploscypha.md +1 -1
- package/ebook/css/main.css +6 -1
- package/jekyll/assets/css/main.css +6 -1
- package/lib/basepagerenderer.js +1 -2
- package/lib/ebook/image.js +6 -4
- package/lib/ebook/pages/page_list_families.js +15 -9
- package/lib/ebook/pages/taxonpage.js +9 -36
- package/lib/ebook/plantbook.js +82 -48
- package/lib/families.js +5 -8
- package/lib/genera.js +40 -26
- package/lib/html.js +70 -41
- package/lib/htmltaxon.js +51 -0
- package/lib/index.js +2 -1
- package/lib/taxa.js +14 -5
- package/lib/taxon.js +16 -5
- package/lib/textutils.js +10 -0
- package/lib/web/pagetaxon.js +107 -82
- package/package.json +3 -3
- package/scripts/build-ebook.js +3 -0
package/lib/genera.js
CHANGED
@@ -1,62 +1,76 @@
|
|
1
1
|
import { Config } from "./config.js";
|
2
2
|
import { Families } from "./families.js";
|
3
3
|
import { Files } from "./files.js";
|
4
|
+
// eslint-disable-next-line no-unused-vars
|
5
|
+
import { Taxon } from "./taxon.js";
|
4
6
|
|
5
7
|
class Genera {
|
8
|
+
#families;
|
9
|
+
#genera;
|
6
10
|
|
7
|
-
|
8
|
-
|
9
|
-
|
11
|
+
/**
|
12
|
+
* @param {Families} families
|
13
|
+
*/
|
14
|
+
constructor(families) {
|
10
15
|
const dataDir = Config.getPackageDir() + "/data";
|
11
|
-
this.#genera = JSON.parse(
|
16
|
+
this.#genera = JSON.parse(Files.read(dataDir + "/genera.json"));
|
17
|
+
this.#families = families;
|
12
18
|
}
|
13
19
|
|
14
|
-
|
15
|
-
|
20
|
+
/**
|
21
|
+
* @param {Taxon} taxon
|
22
|
+
*/
|
23
|
+
addTaxon(taxon) {
|
16
24
|
const genusName = taxon.getGenusName();
|
17
|
-
const genusData = this.#genera[
|
18
|
-
if (
|
19
|
-
console.log(
|
25
|
+
const genusData = this.#genera[genusName];
|
26
|
+
if (!genusData) {
|
27
|
+
console.log(taxon.getName() + " genus not found");
|
20
28
|
return;
|
21
29
|
}
|
22
30
|
|
23
|
-
if (
|
31
|
+
if (genusData.taxa === undefined) {
|
24
32
|
genusData.taxa = [];
|
25
33
|
}
|
26
|
-
genusData.taxa.push(
|
34
|
+
genusData.taxa.push(taxon);
|
27
35
|
|
28
|
-
const family = this.getFamily(
|
29
|
-
if (
|
30
|
-
console.log(
|
36
|
+
const family = this.getFamily(genusName);
|
37
|
+
if (!family) {
|
38
|
+
console.log(taxon.getName() + " family not found");
|
31
39
|
return;
|
32
40
|
}
|
33
|
-
family.addTaxon(
|
41
|
+
family.addTaxon(taxon);
|
34
42
|
}
|
35
43
|
|
36
|
-
|
37
|
-
|
44
|
+
/**
|
45
|
+
* @param {string} genusName
|
46
|
+
*/
|
47
|
+
getGenus(genusName) {
|
48
|
+
return new Genus(this.#genera[genusName]);
|
38
49
|
}
|
39
50
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
51
|
+
/**
|
52
|
+
* @param {string} genusName
|
53
|
+
*/
|
54
|
+
getFamily(genusName) {
|
55
|
+
const genus = this.#genera[genusName];
|
56
|
+
if (genus) {
|
57
|
+
return this.#families.getFamily(genus.family);
|
44
58
|
}
|
45
59
|
}
|
46
|
-
|
47
60
|
}
|
48
61
|
|
49
62
|
class Genus {
|
50
|
-
|
51
63
|
#data;
|
52
64
|
|
53
|
-
constructor(
|
65
|
+
constructor(data) {
|
54
66
|
this.#data = data;
|
55
67
|
}
|
56
68
|
|
57
69
|
getTaxa() {
|
58
|
-
return this.#data.taxa.sort(
|
70
|
+
return this.#data.taxa.sort((a, b) =>
|
71
|
+
a.getName().localeCompare(b.getName())
|
72
|
+
);
|
59
73
|
}
|
60
74
|
}
|
61
75
|
|
62
|
-
export { Genera };
|
76
|
+
export { Genera };
|
package/lib/html.js
CHANGED
@@ -8,41 +8,60 @@ export const HTML_OPTIONS = {
|
|
8
8
|
|
9
9
|
/** HTML utility functions. */
|
10
10
|
export class HTML {
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
/**
|
12
|
+
* @param {string[]} items
|
13
|
+
*/
|
14
|
+
static arrayToLI(items) {
|
15
|
+
return items.reduce(
|
16
|
+
(itemHTML, currVal) => itemHTML + "<li>" + currVal + "</li>",
|
17
|
+
""
|
18
|
+
);
|
14
19
|
}
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
/**
|
22
|
+
* @param {string} value
|
23
|
+
*/
|
24
|
+
static escapeAttribute(value) {
|
25
|
+
return value.replaceAll('"', """);
|
18
26
|
}
|
19
27
|
|
20
|
-
|
21
|
-
|
28
|
+
/**
|
29
|
+
* @param {string} text
|
30
|
+
*/
|
31
|
+
static escapeText(text) {
|
32
|
+
return text
|
33
|
+
.replaceAll("&", "&")
|
34
|
+
.replaceAll("<", "<")
|
35
|
+
.replaceAll(">", ">");
|
22
36
|
}
|
23
37
|
|
24
38
|
/**
|
25
39
|
* @deprecated
|
26
40
|
*/
|
27
|
-
static getElement(
|
41
|
+
static getElement(elName, text, attributes = {}, options = 0) {
|
28
42
|
let html = "<" + elName;
|
29
|
-
html += this.renderAttributes(
|
30
|
-
if (
|
31
|
-
text = this.escapeText(
|
43
|
+
html += this.renderAttributes(attributes);
|
44
|
+
if (!(options & HTML_OPTIONS.NO_ESCAPE)) {
|
45
|
+
text = this.escapeText(text);
|
32
46
|
}
|
33
47
|
html += ">" + text + "</" + elName + ">";
|
34
48
|
return html;
|
35
|
-
|
36
49
|
}
|
37
50
|
|
38
|
-
|
51
|
+
/**
|
52
|
+
* @param {string} elName
|
53
|
+
* @param {string} text
|
54
|
+
* @param {string|Object<string,string>|undefined} attributes
|
55
|
+
* @param {boolean} escape
|
56
|
+
*/
|
57
|
+
static #getElement(elName, text, attributes, escape) {
|
39
58
|
let html = "<" + elName;
|
40
|
-
html += this.renderAttributes(
|
41
|
-
if (
|
42
|
-
text = this.escapeText(
|
59
|
+
html += this.renderAttributes(attributes);
|
60
|
+
if (escape && typeof text === "string") {
|
61
|
+
text = this.escapeText(text);
|
43
62
|
}
|
44
63
|
// If tag is empty, make it self-closing so it is XHTML (epub) compatible.
|
45
|
-
if (
|
64
|
+
if (text === "") {
|
46
65
|
return html + "/>";
|
47
66
|
}
|
48
67
|
return html + ">" + text + "</" + elName + ">";
|
@@ -50,22 +69,22 @@ export class HTML {
|
|
50
69
|
|
51
70
|
/**
|
52
71
|
* Generate HTML for an <a> element.
|
53
|
-
* @param {string|undefined} href
|
54
|
-
* @param {string} linkText
|
55
|
-
* @param {Object} [attributes]
|
72
|
+
* @param {string|undefined} href
|
73
|
+
* @param {string} linkText
|
74
|
+
* @param {Object} [attributes]
|
56
75
|
* @param {boolean} [openInNewWindow] true if the link should open in a new window.
|
57
76
|
* @returns {string} an HTML <a> element.
|
58
77
|
*/
|
59
|
-
static getLink(
|
78
|
+
static getLink(href, linkText, attributes = {}, openInNewWindow) {
|
60
79
|
let html = "<a";
|
61
|
-
if (
|
62
|
-
html += this.renderAttribute(
|
80
|
+
if (href !== undefined) {
|
81
|
+
html += this.renderAttribute("href", href);
|
63
82
|
}
|
64
|
-
html += this.renderAttributes(
|
65
|
-
if (
|
66
|
-
html += this.renderAttribute(
|
83
|
+
html += this.renderAttributes(attributes);
|
84
|
+
if (openInNewWindow) {
|
85
|
+
html += this.renderAttribute("target", "_blank");
|
67
86
|
}
|
68
|
-
return html + ">" + this.escapeText(
|
87
|
+
return html + ">" + this.escapeText(linkText) + "</a>";
|
69
88
|
}
|
70
89
|
|
71
90
|
/**
|
@@ -76,32 +95,42 @@ export class HTML {
|
|
76
95
|
* @param {boolean} options.icon [true] display an icon after the text
|
77
96
|
* @returns {string} A <span> element to be used as a Bootstrap tooltip.
|
78
97
|
*/
|
79
|
-
static getToolTip(
|
80
|
-
const func = text.charAt(
|
81
|
-
if (
|
98
|
+
static getToolTip(text, tooltip, options = {}) {
|
99
|
+
const func = text.charAt(0) === "<" ? HTML.wrap : HTML.textElement;
|
100
|
+
if (options.icon !== false) {
|
82
101
|
text += " ⓘ";
|
83
102
|
}
|
84
|
-
return func(
|
103
|
+
return func("span", text, { "data-bs-html": "true", title: tooltip });
|
85
104
|
}
|
86
105
|
|
87
|
-
static renderAttribute(
|
88
|
-
return " " + n + "
|
106
|
+
static renderAttribute(n, v) {
|
107
|
+
return " " + n + '="' + this.escapeAttribute(v) + '"';
|
89
108
|
}
|
90
109
|
|
91
|
-
|
110
|
+
/**
|
111
|
+
* @param {string|Object<string,string>|undefined} attributes
|
112
|
+
*/
|
113
|
+
static renderAttributes(attributes = {}) {
|
114
|
+
if (typeof attributes === "string") {
|
115
|
+
return this.renderAttribute("class", attributes);
|
116
|
+
}
|
92
117
|
let html = "";
|
93
|
-
for (
|
94
|
-
html += this.renderAttribute(
|
118
|
+
for (const [k, v] of Object.entries(attributes)) {
|
119
|
+
html += this.renderAttribute(k, v);
|
95
120
|
}
|
96
121
|
return html;
|
97
122
|
}
|
98
123
|
|
99
|
-
static textElement(
|
100
|
-
return HTML.#getElement(
|
124
|
+
static textElement(elName, text, attributes = {}) {
|
125
|
+
return HTML.#getElement(elName, text, attributes, true);
|
101
126
|
}
|
102
127
|
|
103
|
-
|
104
|
-
|
128
|
+
/**
|
129
|
+
* @param {string} elName
|
130
|
+
* @param {string} text
|
131
|
+
* @param {string|Object<string,string>|undefined} [attributes]
|
132
|
+
*/
|
133
|
+
static wrap(elName, text, attributes) {
|
134
|
+
return HTML.#getElement(elName, text, attributes, false);
|
105
135
|
}
|
106
|
-
|
107
136
|
}
|
package/lib/htmltaxon.js
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import { DateUtils } from "./dateutils.js";
|
2
|
+
import { HTML } from "./html.js";
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
4
|
+
import { Taxon } from "./taxon.js";
|
5
|
+
import { TextUtils } from "./textutils.js";
|
6
|
+
|
7
|
+
class HTMLTaxon {
|
8
|
+
/**
|
9
|
+
* @param {Taxon} taxon
|
10
|
+
*/
|
11
|
+
static getFlowerInfo(taxon) {
|
12
|
+
const lifeCycle = taxon.getLifeCycle();
|
13
|
+
const colors = taxon.getFlowerColors();
|
14
|
+
const monthStart = taxon.getBloomStart();
|
15
|
+
const monthEnd = taxon.getBloomEnd();
|
16
|
+
|
17
|
+
const parts = [];
|
18
|
+
if (lifeCycle) {
|
19
|
+
parts.push(
|
20
|
+
HTML.wrap("span", TextUtils.ucFirst(lifeCycle), "lc") + "."
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
if (colors || monthStart) {
|
25
|
+
let html = "Flowers: ";
|
26
|
+
if (colors) {
|
27
|
+
for (const color of colors) {
|
28
|
+
html += HTML.textElement("img", "", {
|
29
|
+
src: "./i/f-" + color + ".svg",
|
30
|
+
alt: color + " flowers",
|
31
|
+
title: color,
|
32
|
+
class: "flr-color",
|
33
|
+
});
|
34
|
+
}
|
35
|
+
}
|
36
|
+
if (monthStart && monthEnd) {
|
37
|
+
html += HTML.wrap(
|
38
|
+
"span",
|
39
|
+
DateUtils.getMonthName(monthStart) +
|
40
|
+
"-" +
|
41
|
+
DateUtils.getMonthName(monthEnd),
|
42
|
+
{ class: "flr-time" }
|
43
|
+
);
|
44
|
+
}
|
45
|
+
parts.push(HTML.wrap("span", html));
|
46
|
+
}
|
47
|
+
return HTML.wrap("div", parts.join(" "), { class: "section" });
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
export { HTMLTaxon };
|
package/lib/index.js
CHANGED
@@ -5,12 +5,13 @@
|
|
5
5
|
* calrecnum:string;
|
6
6
|
* CESA:string;
|
7
7
|
* CRPR:string;
|
8
|
-
*
|
8
|
+
* "common name":string;
|
9
9
|
* FESA:string;
|
10
10
|
* flower_color:string;
|
11
11
|
* GRank:string;
|
12
12
|
* "inat id":string;
|
13
13
|
* "jepson id":string;
|
14
|
+
* life_cycle:"annual"|"perennial"|undefined
|
14
15
|
* SRank:string;
|
15
16
|
* status:string;
|
16
17
|
* "RPI ID":string;
|
package/lib/taxa.js
CHANGED
@@ -2,7 +2,9 @@ import { Config } from "./config.js";
|
|
2
2
|
import { HTML } from "./html.js";
|
3
3
|
import { CSV } from "./csv.js";
|
4
4
|
import { RarePlants } from "./rareplants.js";
|
5
|
-
import { ErrorLog, Taxon } from "./index.js";
|
5
|
+
import { ErrorLog, Families, Taxon } from "./index.js";
|
6
|
+
import { Genera } from "./genera.js";
|
7
|
+
import { TextUtils } from "./textutils.js";
|
6
8
|
|
7
9
|
const FLOWER_COLORS = [
|
8
10
|
{ name: "white", color: "white" },
|
@@ -12,6 +14,7 @@ const FLOWER_COLORS = [
|
|
12
14
|
{ name: "yellow", color: "yellow" },
|
13
15
|
{ name: "blue", color: "blue" },
|
14
16
|
{ name: "purple", color: "purple" },
|
17
|
+
{ name: "green", color: "green" },
|
15
18
|
];
|
16
19
|
|
17
20
|
/**
|
@@ -70,9 +73,7 @@ class FlowerColor {
|
|
70
73
|
}
|
71
74
|
|
72
75
|
getColorName(uc = false) {
|
73
|
-
return uc
|
74
|
-
? this.#color[0].toUpperCase() + this.#color.substring(1)
|
75
|
-
: this.#color;
|
76
|
+
return uc ? TextUtils.ucFirst(this.#color) : this.#color;
|
76
77
|
}
|
77
78
|
|
78
79
|
getFileName() {
|
@@ -85,6 +86,7 @@ class FlowerColor {
|
|
85
86
|
}
|
86
87
|
|
87
88
|
class Taxa {
|
89
|
+
#families;
|
88
90
|
#errorLog;
|
89
91
|
/** @type {Object<string,Taxon>} */
|
90
92
|
#taxa = {};
|
@@ -123,6 +125,8 @@ class Taxa {
|
|
123
125
|
|
124
126
|
const dataDir = Config.getPackageDir() + "/data";
|
125
127
|
|
128
|
+
this.#families = new Families();
|
129
|
+
|
126
130
|
const taxaCSV = CSV.parseFile(dataDir, "taxa.csv");
|
127
131
|
this.#loadTaxa(
|
128
132
|
taxaCSV,
|
@@ -186,6 +190,10 @@ class Taxa {
|
|
186
190
|
return html;
|
187
191
|
}
|
188
192
|
|
193
|
+
getFamilies() {
|
194
|
+
return this.#families;
|
195
|
+
}
|
196
|
+
|
189
197
|
/**
|
190
198
|
* @param {string} name
|
191
199
|
*/
|
@@ -257,6 +265,7 @@ class Taxa {
|
|
257
265
|
* @param {boolean} showFlowerErrors
|
258
266
|
*/
|
259
267
|
#loadTaxa(taxaCSV, inclusionList, taxaMeta, taxonClass, showFlowerErrors) {
|
268
|
+
const genera = new Genera(this.#families);
|
260
269
|
for (const row of taxaCSV) {
|
261
270
|
const name = row["taxon_name"];
|
262
271
|
|
@@ -276,7 +285,7 @@ class Taxa {
|
|
276
285
|
if (status !== undefined) {
|
277
286
|
row["status"] = status;
|
278
287
|
}
|
279
|
-
const taxon = new taxonClass(row, taxaMeta[name]);
|
288
|
+
const taxon = new taxonClass(row, genera, taxaMeta[name]);
|
280
289
|
this.#taxa[name] = taxon;
|
281
290
|
const colors = taxon.getFlowerColors();
|
282
291
|
if (colors) {
|
package/lib/taxon.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { HTML } from "./index.js";
|
2
|
-
import { Genera } from "./genera.js";
|
3
2
|
import { RarePlants } from "./rareplants.js";
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
4
|
+
import { Genera } from "./genera.js";
|
4
5
|
|
5
6
|
const TAXA_COLNAMES = {
|
6
7
|
BLOOM_START: "bloom_start",
|
@@ -10,6 +11,8 @@ const TAXA_COLNAMES = {
|
|
10
11
|
};
|
11
12
|
|
12
13
|
class Taxon {
|
14
|
+
/** @type {Genera} */
|
15
|
+
#genera;
|
13
16
|
#name;
|
14
17
|
#genus;
|
15
18
|
#commonNames;
|
@@ -21,6 +24,7 @@ class Taxon {
|
|
21
24
|
#iNatID;
|
22
25
|
/**@type {string|undefined} */
|
23
26
|
#iNatSyn;
|
27
|
+
#lifeCycle;
|
24
28
|
#flowerColors;
|
25
29
|
#bloomStart;
|
26
30
|
#bloomEnd;
|
@@ -35,8 +39,10 @@ class Taxon {
|
|
35
39
|
|
36
40
|
/**
|
37
41
|
* @param {import("./index.js").TaxonData} data
|
42
|
+
* @param {Genera} genera
|
38
43
|
*/
|
39
|
-
constructor(data) {
|
44
|
+
constructor(data, genera) {
|
45
|
+
this.#genera = genera;
|
40
46
|
const name = data["taxon_name"];
|
41
47
|
const commonNames = data["common name"];
|
42
48
|
const cesa = data["CESA"];
|
@@ -52,6 +58,7 @@ class Taxon {
|
|
52
58
|
this.#jepsonID = data["jepson id"];
|
53
59
|
this.#calRecNum = data["calrecnum"];
|
54
60
|
this.#iNatID = data["inat id"];
|
61
|
+
this.#lifeCycle = data.life_cycle;
|
55
62
|
const colors = data["flower_color"];
|
56
63
|
this.#flowerColors = colors ? colors.split(",") : undefined;
|
57
64
|
if (data["bloom_start"]) {
|
@@ -66,7 +73,7 @@ class Taxon {
|
|
66
73
|
this.#fesa = fesa ? fesa : undefined;
|
67
74
|
this.#rankCNDDB = rankCNDDB ? rankCNDDB : undefined;
|
68
75
|
this.#rankGlobal = rankGlobal ? rankGlobal : undefined;
|
69
|
-
|
76
|
+
genera.addTaxon(this);
|
70
77
|
}
|
71
78
|
|
72
79
|
/**
|
@@ -144,7 +151,7 @@ class Taxon {
|
|
144
151
|
}
|
145
152
|
|
146
153
|
getFamily() {
|
147
|
-
return
|
154
|
+
return this.#genera.getFamily(this.#genus);
|
148
155
|
}
|
149
156
|
|
150
157
|
getFESA() {
|
@@ -160,7 +167,7 @@ class Taxon {
|
|
160
167
|
}
|
161
168
|
|
162
169
|
getGenus() {
|
163
|
-
return
|
170
|
+
return this.#genera.getGenus(this.#genus);
|
164
171
|
}
|
165
172
|
|
166
173
|
getGenusName() {
|
@@ -229,6 +236,10 @@ class Taxon {
|
|
229
236
|
return this.#jepsonID;
|
230
237
|
}
|
231
238
|
|
239
|
+
getLifeCycle() {
|
240
|
+
return this.#lifeCycle;
|
241
|
+
}
|
242
|
+
|
232
243
|
getName() {
|
233
244
|
return this.#name;
|
234
245
|
}
|