@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.
Files changed (51) hide show
  1. package/data/glossary/calyx.md +1 -0
  2. package/data/glossary/pedicel.md +1 -0
  3. package/data/glossary/sepal.md +1 -0
  4. package/data/synonyms.csv +6 -4
  5. package/data/taxa.csv +293 -291
  6. package/data/text/Agoseris-grandiflora-var-grandiflora.md +1 -0
  7. package/data/text/Agoseris-heterophylla-var-cryptopleura.md +1 -0
  8. package/data/text/Calochortus-argillosus.md +1 -0
  9. package/data/text/Calochortus-luteus.md +1 -0
  10. package/data/text/Calochortus-venustus.md +1 -0
  11. package/data/text/Claytonia-parviflora-subsp-parviflora.md +1 -0
  12. package/data/text/Claytonia-perfoliata-subsp-perfoliata.md +1 -0
  13. package/data/text/Collinsia-heterophylla-var-heterophylla.md +1 -0
  14. package/data/text/Galium-aparine.md +1 -0
  15. package/data/text/Galium-triflorum.md +1 -0
  16. package/data/text/Hypochaeris-glabra.md +1 -1
  17. package/data/text/Hypochaeris-radicata.md +1 -1
  18. package/data/text/Leptosiphon-ambiguus.md +1 -0
  19. package/data/text/Leptosiphon-androsaceus.md +1 -0
  20. package/data/text/Leptosiphon-bicolor.md +1 -1
  21. package/data/text/Leptosiphon-parviflorus.md +1 -0
  22. package/data/text/Lithophragma-affine.md +1 -1
  23. package/data/text/Lupinus-formosus-var-formosus.md +1 -1
  24. package/data/text/Lupinus-latifolius-var-latifolius.md +1 -1
  25. package/data/text/Lupinus-microcarpus-var-densiflorus.md +1 -1
  26. package/data/text/Lupinus-microcarpus-var-microcarpus.md +1 -1
  27. package/data/text/Lupinus-succulentus.md +1 -1
  28. package/data/text/Pinus-ponderosa-var-pacifica.md +1 -0
  29. package/data/text/Pinus-sabiniana.md +1 -0
  30. package/data/text/Quercus-douglasii.md +1 -0
  31. package/data/text/Quercus-kelloggii.md +1 -0
  32. package/data/text/Quercus-lobata.md +1 -0
  33. package/data/text/Sidalcea-diploscypha.md +1 -1
  34. package/ebook/css/main.css +6 -1
  35. package/jekyll/assets/css/main.css +6 -1
  36. package/lib/basepagerenderer.js +1 -2
  37. package/lib/ebook/image.js +6 -4
  38. package/lib/ebook/pages/page_list_families.js +15 -9
  39. package/lib/ebook/pages/taxonpage.js +9 -36
  40. package/lib/ebook/plantbook.js +82 -48
  41. package/lib/families.js +5 -8
  42. package/lib/genera.js +40 -26
  43. package/lib/html.js +70 -41
  44. package/lib/htmltaxon.js +51 -0
  45. package/lib/index.js +2 -1
  46. package/lib/taxa.js +14 -5
  47. package/lib/taxon.js +16 -5
  48. package/lib/textutils.js +10 -0
  49. package/lib/web/pagetaxon.js +107 -82
  50. package/package.json +3 -3
  51. 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
- static #genera;
8
-
9
- static {
11
+ /**
12
+ * @param {Families} families
13
+ */
14
+ constructor(families) {
10
15
  const dataDir = Config.getPackageDir() + "/data";
11
- this.#genera = JSON.parse( Files.read( dataDir + "/genera.json" ) );
16
+ this.#genera = JSON.parse(Files.read(dataDir + "/genera.json"));
17
+ this.#families = families;
12
18
  }
13
19
 
14
- static addTaxon( taxon ) {
15
-
20
+ /**
21
+ * @param {Taxon} taxon
22
+ */
23
+ addTaxon(taxon) {
16
24
  const genusName = taxon.getGenusName();
17
- const genusData = this.#genera[ genusName ];
18
- if ( !genusData ) {
19
- console.log( taxon.getName() + " genus not found" );
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 ( genusData.taxa === undefined ) {
31
+ if (genusData.taxa === undefined) {
24
32
  genusData.taxa = [];
25
33
  }
26
- genusData.taxa.push( taxon );
34
+ genusData.taxa.push(taxon);
27
35
 
28
- const family = this.getFamily( genusName );
29
- if ( !family ) {
30
- console.log( taxon.getName() + " family not found" );
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( taxon );
41
+ family.addTaxon(taxon);
34
42
  }
35
43
 
36
- static getGenus( genusName ) {
37
- return new Genus( this.#genera[ genusName ] );
44
+ /**
45
+ * @param {string} genusName
46
+ */
47
+ getGenus(genusName) {
48
+ return new Genus(this.#genera[genusName]);
38
49
  }
39
50
 
40
- static getFamily( genusName ) {
41
- const genus = this.#genera[ genusName ];
42
- if ( genus ) {
43
- return Families.getFamily( genus.family );
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( data ) {
65
+ constructor(data) {
54
66
  this.#data = data;
55
67
  }
56
68
 
57
69
  getTaxa() {
58
- return this.#data.taxa.sort( ( a, b ) => a.getName().localeCompare( b.getName() ) );
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
- static arrayToLI( items ) {
13
- return items.reduce( ( itemHTML, currVal ) => itemHTML + "<li>" + currVal + "</li>", "" );
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
- static escapeAttribute( value ) {
17
- return value.replaceAll( "\"", "&quot;" );
21
+ /**
22
+ * @param {string} value
23
+ */
24
+ static escapeAttribute(value) {
25
+ return value.replaceAll('"', "&quot;");
18
26
  }
19
27
 
20
- static escapeText( text ) {
21
- return text.replaceAll( "&", "&amp;" ).replaceAll( "<", "&lt;" ).replaceAll( ">", "&gt;" );
28
+ /**
29
+ * @param {string} text
30
+ */
31
+ static escapeText(text) {
32
+ return text
33
+ .replaceAll("&", "&amp;")
34
+ .replaceAll("<", "&lt;")
35
+ .replaceAll(">", "&gt;");
22
36
  }
23
37
 
24
38
  /**
25
39
  * @deprecated
26
40
  */
27
- static getElement( elName, text, attributes = {}, options = 0 ) {
41
+ static getElement(elName, text, attributes = {}, options = 0) {
28
42
  let html = "<" + elName;
29
- html += this.renderAttributes( attributes );
30
- if ( !( options & HTML_OPTIONS.NO_ESCAPE ) ) {
31
- text = this.escapeText( text );
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
- static #getElement( elName, text, attributes, escape ) {
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( attributes );
41
- if ( escape && ( typeof text === "string" ) ) {
42
- text = this.escapeText( text );
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 ( text === "" ) {
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 &lt;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 &lt;a> element.
58
77
  */
59
- static getLink( href, linkText, attributes = {}, openInNewWindow ) {
78
+ static getLink(href, linkText, attributes = {}, openInNewWindow) {
60
79
  let html = "<a";
61
- if ( href !== undefined ) {
62
- html += this.renderAttribute( "href", href );
80
+ if (href !== undefined) {
81
+ html += this.renderAttribute("href", href);
63
82
  }
64
- html += this.renderAttributes( attributes );
65
- if ( openInNewWindow ) {
66
- html += this.renderAttribute( "target", "_blank" );
83
+ html += this.renderAttributes(attributes);
84
+ if (openInNewWindow) {
85
+ html += this.renderAttribute("target", "_blank");
67
86
  }
68
- return html + ">" + this.escapeText( linkText ) + "</a>";
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 &lt;span> element to be used as a Bootstrap tooltip.
78
97
  */
79
- static getToolTip( text, tooltip, options = {} ) {
80
- const func = text.charAt( 0 ) === "<" ? HTML.wrap : HTML.textElement;
81
- if ( options.icon !== false ) {
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( "span", text, { "data-bs-html": "true", "title": tooltip } );
103
+ return func("span", text, { "data-bs-html": "true", title: tooltip });
85
104
  }
86
105
 
87
- static renderAttribute( n, v ) {
88
- return " " + n + "=\"" + this.escapeAttribute( v ) + "\"";
106
+ static renderAttribute(n, v) {
107
+ return " " + n + '="' + this.escapeAttribute(v) + '"';
89
108
  }
90
109
 
91
- static renderAttributes( attributes ) {
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 ( const [ k, v ] of Object.entries( attributes ) ) {
94
- html += this.renderAttribute( k, v );
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( elName, text, attributes = {} ) {
100
- return HTML.#getElement( elName, text, attributes, true );
124
+ static textElement(elName, text, attributes = {}) {
125
+ return HTML.#getElement(elName, text, attributes, true);
101
126
  }
102
127
 
103
- static wrap( elName, text, attributes = {} ) {
104
- return HTML.#getElement( elName, text, attributes, false );
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
  }
@@ -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
- * common_name:string;
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
- Genera.addTaxon(this);
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 Genera.getFamily(this.#genus);
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 Genera.getGenus(this.#genus);
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
  }
@@ -0,0 +1,10 @@
1
+ class TextUtils {
2
+ /**
3
+ * @param {string} t
4
+ */
5
+ static ucFirst(t) {
6
+ return t[0].toUpperCase() + t.substring(1);
7
+ }
8
+ }
9
+
10
+ export { TextUtils };