@ca-plant-list/ca-plant-list 0.3.1 → 0.3.3
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/.vscode/settings.json +5 -0
- package/data/exceptions.json +88 -83
- package/data/glossary/ovary.md +3 -0
- package/data/glossary/pistil.md +3 -0
- package/data/glossary/stigma.md +3 -0
- package/data/glossary/style.md +3 -0
- package/data/illustrations/inkscape/pistil.svg +156 -0
- package/data/synonyms.csv +8 -2
- package/data/taxa.csv +56 -54
- package/data/text/Ceanothus-cuneatus-var-cuneatus.md +1 -0
- package/data/text/Ceanothus-leucodermis.md +1 -0
- package/data/text/Delphinium-californicum-subsp-californicum.md +1 -0
- package/data/text/Delphinium-decorum-subsp-decorum.md +1 -0
- package/data/text/Delphinium-hesperium-subsp-hesperium.md +1 -0
- package/data/text/Delphinium-patens-subsp-patens.md +1 -0
- package/data/text/Galium-andrewsii-subsp-gatense.md +1 -0
- package/data/text/Helianthella-californica-var-californica.md +1 -0
- package/data/text/Helianthella-castanea.md +1 -0
- package/data/text/Iris-macrosiphon.md +1 -0
- package/data/text/Lasthenia-californica-subsp-californica.md +1 -0
- package/data/text/Lasthenia-gracilis.md +1 -0
- package/data/text/Lithophragma-affine.md +1 -1
- package/data/text/Lithophragma-parviflorum-var-parviflorum.md +1 -1
- package/data/text/Lomatium-californicum.md +0 -0
- package/data/text/Lomatium-dasycarpum-subsp-dasycarpum.md +1 -0
- package/data/text/Lomatium-utriculatum.md +1 -0
- package/data/text/Nemophila-heterophylla.md +1 -0
- package/data/text/Nemophila-parviflora-var-parviflora.md +1 -0
- package/data/text/Plectritis-ciliosa.md +1 -0
- package/data/text/Plectritis-macrocera.md +1 -0
- package/data/text/Primula-clevelandii-var-patula.md +1 -0
- package/data/text/Primula-hendersonii.md +1 -0
- package/data/text/Sidalcea-diploscypha.md +1 -0
- package/data/text/Thysanocarpus-curvipes.md +1 -0
- package/data/text/Thysanocarpus-laciniatus.md +1 -0
- package/data/text/Trillium-chloropetalum.md +1 -1
- package/data/text/Viola-douglasii.md +1 -0
- package/data/text/Viola-pedunculata.md +1 -0
- package/data/text/Viola-purpurea-subsp-quercetorum.md +1 -0
- package/ebook/css/main.css +5 -0
- package/lib/basepagerenderer.js +30 -28
- package/lib/csv.js +13 -0
- package/lib/dateutils.js +36 -16
- package/lib/ebook/pages/taxonpage.js +63 -36
- package/lib/errorlog.js +16 -11
- package/lib/exceptions.js +2 -0
- package/lib/families.js +109 -71
- package/lib/files.js +103 -45
- package/lib/generictaxaloader.js +15 -7
- package/lib/html.js +2 -2
- package/lib/index.js +38 -3
- package/lib/taxa.js +175 -86
- package/lib/taxaloader.js +28 -15
- package/lib/taxaprocessor.js +6 -8
- package/lib/taxon.js +109 -56
- package/lib/web/pagetaxon.js +1 -1
- package/package.json +6 -8
- package/scripts/build-ebook.js +30 -25
- package/lib/index.d.ts +0 -327
@@ -4,88 +4,115 @@ import { EBookPage } from "../ebookpage.js";
|
|
4
4
|
import { XHTML } from "../xhtml.js";
|
5
5
|
import { Markdown } from "../../markdown.js";
|
6
6
|
import { DateUtils } from "../../dateutils.js";
|
7
|
+
// eslint-disable-next-line no-unused-vars
|
8
|
+
import { Taxon } from "../../taxon.js";
|
7
9
|
|
8
10
|
class TaxonPage extends EBookPage {
|
9
|
-
|
10
11
|
#outputDir;
|
11
12
|
#taxon;
|
12
13
|
#photos;
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
/**
|
16
|
+
*
|
17
|
+
* @param {string} outputDir
|
18
|
+
* @param {Taxon} taxon
|
19
|
+
* @param {*} photos
|
20
|
+
*/
|
21
|
+
constructor(outputDir, taxon, photos) {
|
22
|
+
super(outputDir + "/" + taxon.getFileName(), taxon.getName());
|
16
23
|
this.#outputDir = outputDir;
|
17
24
|
this.#taxon = taxon;
|
18
25
|
this.#photos = photos;
|
19
26
|
}
|
20
27
|
|
21
28
|
renderPageBody() {
|
22
|
-
|
23
|
-
|
29
|
+
/**
|
30
|
+
* @param {Taxon} taxon
|
31
|
+
*/
|
32
|
+
function renderBloomInfo(taxon) {
|
24
33
|
const colors = taxon.getFlowerColors();
|
25
34
|
const monthStart = taxon.getBloomStart();
|
26
35
|
const monthEnd = taxon.getBloomEnd();
|
27
|
-
if (
|
36
|
+
if (!colors && !monthStart) {
|
28
37
|
return "";
|
29
38
|
}
|
30
39
|
let html = "";
|
31
|
-
if (
|
32
|
-
for (
|
33
|
-
html += XHTML.textElement(
|
40
|
+
if (colors) {
|
41
|
+
for (const color of colors) {
|
42
|
+
html += XHTML.textElement("img", "", {
|
43
|
+
src: "./i/f-" + color + ".svg",
|
44
|
+
class: "flr",
|
45
|
+
});
|
34
46
|
}
|
35
47
|
}
|
36
|
-
if (
|
37
|
-
html += XHTML.textElement(
|
38
|
-
|
48
|
+
if (monthStart && monthEnd) {
|
49
|
+
html += XHTML.textElement(
|
50
|
+
"div",
|
51
|
+
DateUtils.getMonthName(monthStart) +
|
52
|
+
"-" +
|
53
|
+
DateUtils.getMonthName(monthEnd)
|
54
|
+
);
|
39
55
|
}
|
40
|
-
return XHTML.wrap(
|
56
|
+
return XHTML.wrap("div", html, { class: "section flr" });
|
41
57
|
}
|
42
58
|
|
43
|
-
|
44
|
-
|
59
|
+
/**
|
60
|
+
* @param {string} name
|
61
|
+
*/
|
62
|
+
function renderCustomText(name) {
|
45
63
|
// See if there is custom text.
|
46
|
-
const fileName =
|
47
|
-
|
64
|
+
const fileName =
|
65
|
+
Config.getPackageDir() + "/data/text/" + name + ".md";
|
66
|
+
if (!Files.exists(fileName)) {
|
48
67
|
return "";
|
49
68
|
}
|
50
|
-
const text = Files.read(
|
51
|
-
return Markdown.strToHTML(
|
69
|
+
const text = Files.read(fileName);
|
70
|
+
return Markdown.strToHTML(text);
|
52
71
|
}
|
53
72
|
|
54
73
|
const name = this.#taxon.getName();
|
55
|
-
let html = XHTML.textElement(
|
74
|
+
let html = XHTML.textElement("h1", name);
|
56
75
|
|
57
76
|
const family = this.#taxon.getFamily();
|
58
|
-
html += XHTML.wrap(
|
77
|
+
html += XHTML.wrap(
|
78
|
+
"div",
|
79
|
+
XHTML.getLink(family.getFileName(), family.getName()),
|
80
|
+
{ class: "section" }
|
81
|
+
);
|
59
82
|
|
60
83
|
const cn = this.#taxon.getCommonNames();
|
61
|
-
if (
|
62
|
-
html += XHTML.textElement(
|
84
|
+
if (cn && cn.length > 0) {
|
85
|
+
html += XHTML.textElement("div", cn.join(", "), {
|
86
|
+
class: "section",
|
87
|
+
});
|
63
88
|
}
|
64
89
|
|
65
|
-
html += renderBloomInfo(
|
90
|
+
html += renderBloomInfo(this.#taxon);
|
66
91
|
|
67
|
-
html += renderCustomText(
|
92
|
+
html += renderCustomText(this.#taxon.getBaseFileName());
|
68
93
|
|
69
|
-
if (
|
94
|
+
if (this.#photos) {
|
70
95
|
let photoHTML = "";
|
71
|
-
for (
|
96
|
+
for (const photo of this.#photos) {
|
72
97
|
const src = photo.getSrc();
|
73
|
-
const dimensions = sizeOf(
|
74
|
-
let img = XHTML.textElement(
|
98
|
+
const dimensions = sizeOf(this.#outputDir + "/" + src);
|
99
|
+
let img = XHTML.textElement("img", "", {
|
100
|
+
src: src,
|
101
|
+
style: "max-width:" + dimensions.width + "px",
|
102
|
+
});
|
75
103
|
const caption = photo.getCaption();
|
76
|
-
if (
|
77
|
-
img += XHTML.textElement(
|
104
|
+
if (caption) {
|
105
|
+
img += XHTML.textElement("figcaption", caption);
|
78
106
|
}
|
79
|
-
photoHTML += XHTML.wrap(
|
107
|
+
photoHTML += XHTML.wrap("figure", img);
|
80
108
|
}
|
81
|
-
if (
|
82
|
-
html += XHTML.wrap(
|
109
|
+
if (photoHTML) {
|
110
|
+
html += XHTML.wrap("div", photoHTML);
|
83
111
|
}
|
84
112
|
}
|
85
113
|
|
86
114
|
return html;
|
87
115
|
}
|
88
|
-
|
89
116
|
}
|
90
117
|
|
91
|
-
export { TaxonPage };
|
118
|
+
export { TaxonPage };
|
package/lib/errorlog.js
CHANGED
@@ -2,30 +2,35 @@ import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
3
3
|
|
4
4
|
class ErrorLog {
|
5
|
-
|
6
5
|
#fileName;
|
7
6
|
#echo;
|
7
|
+
/** @type string[] */
|
8
8
|
#errors = [];
|
9
9
|
|
10
|
-
|
10
|
+
/**
|
11
|
+
* @param {string} fileName
|
12
|
+
* @param {boolean} echo
|
13
|
+
*/
|
14
|
+
constructor(fileName, echo = false) {
|
11
15
|
this.#fileName = fileName;
|
12
16
|
this.#echo = echo;
|
13
17
|
}
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
/**
|
20
|
+
* @param {...string} args
|
21
|
+
*/
|
22
|
+
log(...args) {
|
23
|
+
if (this.#echo) {
|
24
|
+
console.log(args.join());
|
18
25
|
}
|
19
|
-
this.#errors.push(
|
26
|
+
this.#errors.push(args.join("\t"));
|
20
27
|
}
|
21
28
|
|
22
29
|
write() {
|
23
30
|
// Make sure directory exists.
|
24
|
-
fs.mkdirSync(
|
25
|
-
|
26
|
-
fs.writeFileSync( this.#fileName, this.#errors.join( "\n" ) );
|
31
|
+
fs.mkdirSync(path.dirname(this.#fileName), { recursive: true });
|
32
|
+
fs.writeFileSync(this.#fileName, this.#errors.join("\n"));
|
27
33
|
}
|
28
|
-
|
29
34
|
}
|
30
35
|
|
31
|
-
export { ErrorLog };
|
36
|
+
export { ErrorLog };
|
package/lib/exceptions.js
CHANGED
@@ -18,6 +18,8 @@ class Exceptions {
|
|
18
18
|
const localExceptions = readConfig( dir + "/exceptions.json" );
|
19
19
|
for ( const [ k, v ] of Object.entries( localExceptions ) ) {
|
20
20
|
this.#exceptions[ k ] = v;
|
21
|
+
// Tag as a local exception so we can distinguish between global and local.
|
22
|
+
v.local = true;
|
21
23
|
}
|
22
24
|
|
23
25
|
}
|
package/lib/families.js
CHANGED
@@ -4,30 +4,38 @@ import { Jepson } from "./jepson.js";
|
|
4
4
|
import { Taxa } from "./taxa.js";
|
5
5
|
import { Files } from "./files.js";
|
6
6
|
import { Config } from "./config.js";
|
7
|
+
// eslint-disable-next-line no-unused-vars
|
8
|
+
import { Taxon } from "./index.js";
|
7
9
|
|
8
10
|
class Family {
|
9
|
-
|
10
11
|
#name;
|
11
12
|
#data;
|
12
13
|
|
13
|
-
|
14
|
+
/**
|
15
|
+
* @param {string} name
|
16
|
+
* @param {*} data
|
17
|
+
*/
|
18
|
+
constructor(name, data) {
|
14
19
|
this.#name = name;
|
15
20
|
this.#data = data;
|
16
21
|
}
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
/**
|
24
|
+
* @param {Taxon} taxon
|
25
|
+
*/
|
26
|
+
addTaxon(taxon) {
|
27
|
+
if (!this.#data.taxa) {
|
20
28
|
this.#data.taxa = [];
|
21
29
|
}
|
22
|
-
this.#data.taxa.push(
|
23
|
-
Sections.addTaxon(
|
30
|
+
this.#data.taxa.push(taxon);
|
31
|
+
Sections.addTaxon(this.getSectionName(), taxon);
|
24
32
|
}
|
25
33
|
|
26
34
|
getBaseFileName() {
|
27
35
|
return this.getName();
|
28
36
|
}
|
29
37
|
|
30
|
-
getFileName(
|
38
|
+
getFileName(ext = "html") {
|
31
39
|
return this.getBaseFileName() + "." + ext;
|
32
40
|
}
|
33
41
|
|
@@ -46,163 +54,193 @@ class Family {
|
|
46
54
|
getTaxa() {
|
47
55
|
return this.#data.taxa;
|
48
56
|
}
|
49
|
-
|
50
57
|
}
|
51
58
|
|
52
59
|
class Families {
|
53
|
-
|
54
60
|
static #families;
|
55
61
|
|
56
62
|
static {
|
57
|
-
|
58
63
|
const dataDir = Config.getPackageDir() + "/data";
|
59
64
|
|
60
|
-
this.#families = JSON.parse(
|
61
|
-
for (
|
62
|
-
this.#families[
|
65
|
+
this.#families = JSON.parse(Files.read(dataDir + "/families.json"));
|
66
|
+
for (const [k, v] of Object.entries(this.#families)) {
|
67
|
+
this.#families[k] = new Family(k, v);
|
63
68
|
}
|
64
|
-
|
65
69
|
}
|
66
70
|
|
67
71
|
static getFamilies() {
|
68
|
-
return Object.values(
|
72
|
+
return Object.values(this.#families).sort((a, b) =>
|
73
|
+
a.getName().localeCompare(b.getName())
|
74
|
+
);
|
69
75
|
}
|
70
76
|
|
71
|
-
|
72
|
-
|
77
|
+
/**
|
78
|
+
* @param {string} familyName
|
79
|
+
*/
|
80
|
+
static getFamily(familyName) {
|
81
|
+
return this.#families[familyName];
|
73
82
|
}
|
74
83
|
|
75
|
-
|
76
|
-
|
84
|
+
/**
|
85
|
+
* @param {string} outputDir
|
86
|
+
* @param {import("./index.js").TaxaCol[]} taxaColumns
|
87
|
+
*/
|
88
|
+
static renderPages(outputDir, taxaColumns) {
|
89
|
+
new PageFamilyList(outputDir, this.#families).render(taxaColumns);
|
77
90
|
|
78
|
-
const names = Object.keys(
|
79
|
-
for (
|
80
|
-
const family = this.#families[
|
81
|
-
if (
|
82
|
-
new PageFamily(
|
91
|
+
const names = Object.keys(this.#families);
|
92
|
+
for (const name of names.sort()) {
|
93
|
+
const family = this.#families[name];
|
94
|
+
if (family.getTaxa()) {
|
95
|
+
new PageFamily(outputDir, family).render(taxaColumns);
|
83
96
|
}
|
84
97
|
}
|
85
98
|
}
|
86
|
-
|
87
99
|
}
|
88
100
|
|
89
101
|
class PageFamilyList extends GenericPage {
|
90
|
-
|
91
102
|
#families;
|
92
103
|
|
93
|
-
|
94
|
-
|
104
|
+
/**
|
105
|
+
* @param {string} outputDir
|
106
|
+
* @param {Object<string,Family>} families
|
107
|
+
*/
|
108
|
+
constructor(outputDir, families) {
|
109
|
+
super(outputDir, "Families", "list_families");
|
95
110
|
this.#families = families;
|
96
111
|
}
|
97
112
|
|
98
|
-
|
99
|
-
|
113
|
+
/**
|
114
|
+
* @param {import("./index.js").TaxaCol[]} taxaColumns
|
115
|
+
*/
|
116
|
+
render(taxaColumns) {
|
100
117
|
let html = this.getDefaultIntro();
|
101
118
|
|
102
119
|
const sections = Sections.getSections();
|
103
120
|
const sectionLinks = [];
|
104
|
-
for (
|
105
|
-
|
106
|
-
const taxa = sections[ name ];
|
121
|
+
for (const name of Object.keys(sections).sort()) {
|
122
|
+
const taxa = sections[name];
|
107
123
|
|
108
124
|
// Render the section page.
|
109
|
-
new PageSection(
|
125
|
+
new PageSection(this.getOutputDir(), name, taxa).render(
|
126
|
+
taxaColumns
|
127
|
+
);
|
110
128
|
|
111
129
|
// Render the link.
|
112
130
|
const href = "./" + name + ".html";
|
113
|
-
sectionLinks.push(
|
131
|
+
sectionLinks.push(
|
132
|
+
HTML.getLink(href, name) + " (" + taxa.length + ")"
|
133
|
+
);
|
114
134
|
}
|
115
|
-
html += HTML.wrap(
|
135
|
+
html += HTML.wrap("ul", HTML.arrayToLI(sectionLinks), {
|
136
|
+
class: "listmenu",
|
137
|
+
});
|
116
138
|
|
117
139
|
html += "<table>";
|
118
140
|
html += "<thead>";
|
119
|
-
html += HTML.textElement(
|
120
|
-
html += HTML.textElement(
|
141
|
+
html += HTML.textElement("th", "Family");
|
142
|
+
html += HTML.textElement("th", "Number of Species", { class: "right" });
|
121
143
|
html += "</thead>";
|
122
144
|
|
123
145
|
html += "<tbody>";
|
124
|
-
const names = Object.keys(
|
125
|
-
for (
|
126
|
-
const family = this.#families[
|
146
|
+
const names = Object.keys(this.#families).sort();
|
147
|
+
for (const name of names) {
|
148
|
+
const family = this.#families[name];
|
127
149
|
const taxa = family.getTaxa();
|
128
|
-
if (
|
150
|
+
if (!taxa) {
|
129
151
|
continue;
|
130
152
|
}
|
131
|
-
let cols = HTML.wrap(
|
132
|
-
|
133
|
-
|
153
|
+
let cols = HTML.wrap(
|
154
|
+
"td",
|
155
|
+
HTML.getLink("./" + family.getFileName(), family.getName())
|
156
|
+
);
|
157
|
+
cols += HTML.wrap("td", taxa.length, { class: "right" });
|
158
|
+
html += HTML.wrap("tr", cols);
|
134
159
|
}
|
135
160
|
html += "</tbody>";
|
136
161
|
|
137
162
|
html += "</table>";
|
138
163
|
|
139
|
-
this.writeFile(
|
164
|
+
this.writeFile(html);
|
140
165
|
}
|
141
166
|
}
|
142
167
|
|
143
168
|
class PageFamily extends GenericPage {
|
144
|
-
|
145
169
|
#family;
|
146
170
|
|
147
|
-
|
148
|
-
|
171
|
+
/**
|
172
|
+
* @param {string} outputDir
|
173
|
+
* @param {Family} family
|
174
|
+
*/
|
175
|
+
constructor(outputDir, family) {
|
176
|
+
super(outputDir, family.getName(), family.getBaseFileName());
|
149
177
|
this.#family = family;
|
150
178
|
}
|
151
179
|
|
152
|
-
|
153
|
-
|
180
|
+
/**
|
181
|
+
*
|
182
|
+
* @param {import("./index.js").TaxaCol[]} columns
|
183
|
+
*/
|
184
|
+
render(columns) {
|
154
185
|
let html = this.getDefaultIntro();
|
155
186
|
|
156
187
|
html += HTML.wrap(
|
157
188
|
"div",
|
158
|
-
Jepson.getEFloraLink(
|
189
|
+
Jepson.getEFloraLink(this.#family.getJepsonID()),
|
159
190
|
{ class: "section" }
|
160
191
|
);
|
161
192
|
|
162
|
-
html += Taxa.getHTMLTable(
|
163
|
-
|
164
|
-
this.writeFile( html );
|
193
|
+
html += Taxa.getHTMLTable(this.#family.getTaxa(), columns);
|
165
194
|
|
195
|
+
this.writeFile(html);
|
166
196
|
}
|
167
197
|
}
|
168
198
|
|
169
199
|
class PageSection extends GenericPage {
|
170
|
-
|
171
200
|
#taxa;
|
172
201
|
|
173
|
-
|
174
|
-
|
202
|
+
/**
|
203
|
+
* @param {string} outputDir
|
204
|
+
* @param {string} name
|
205
|
+
* @param {Taxon[]} taxa
|
206
|
+
*/
|
207
|
+
constructor(outputDir, name, taxa) {
|
208
|
+
super(outputDir, name, name);
|
175
209
|
this.#taxa = taxa;
|
176
210
|
}
|
177
211
|
|
178
|
-
|
179
|
-
|
212
|
+
/**
|
213
|
+
* @param {import("./index.js").TaxaCol[]} columns
|
214
|
+
*/
|
215
|
+
render(columns) {
|
180
216
|
let html = this.getDefaultIntro();
|
181
217
|
|
182
|
-
html += Taxa.getHTMLTable(
|
183
|
-
|
184
|
-
this.writeFile( html );
|
218
|
+
html += Taxa.getHTMLTable(this.#taxa, columns);
|
185
219
|
|
220
|
+
this.writeFile(html);
|
186
221
|
}
|
187
222
|
}
|
188
223
|
|
189
224
|
class Sections {
|
190
|
-
|
225
|
+
/** @type {Object<string,Taxon[]} */
|
191
226
|
static #sections = {};
|
192
227
|
|
193
|
-
|
194
|
-
|
195
|
-
|
228
|
+
/**
|
229
|
+
* @param {string} name
|
230
|
+
* @param {Taxon} taxon
|
231
|
+
*/
|
232
|
+
static addTaxon(name, taxon) {
|
233
|
+
let section = this.#sections[name];
|
234
|
+
if (!section) {
|
196
235
|
section = [];
|
197
|
-
this.#sections[
|
236
|
+
this.#sections[name] = section;
|
198
237
|
}
|
199
|
-
section.push(
|
238
|
+
section.push(taxon);
|
200
239
|
}
|
201
240
|
|
202
241
|
static getSections() {
|
203
242
|
return this.#sections;
|
204
243
|
}
|
205
|
-
|
206
244
|
}
|
207
245
|
|
208
|
-
export { Families };
|
246
|
+
export { Families };
|