@ca-plant-list/ca-plant-list 0.4.21 → 0.4.23

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 (47) hide show
  1. package/data/exceptions.json +22 -1
  2. package/data/synonyms.csv +2 -0
  3. package/data/taxa.csv +1754 -1753
  4. package/lib/basepagerenderer.js +10 -4
  5. package/lib/ebook/images.js +3 -3
  6. package/lib/ebook/pages/{page_list_families.js → pageListFamilies.js} +1 -1
  7. package/lib/ebook/pages/{page_list_flowers.js → pageListFlowers.js} +2 -2
  8. package/lib/ebook/pages/page_list_species.js +1 -1
  9. package/lib/ebook/pages/taxonpage.js +1 -1
  10. package/lib/ebook/pages/tocpage.js +2 -2
  11. package/lib/ebook/plantbook.js +3 -3
  12. package/lib/errorlog.js +1 -1
  13. package/lib/externalsites.js +113 -35
  14. package/lib/files.js +3 -5
  15. package/lib/flowercolor.js +2 -2
  16. package/lib/genera.js +4 -4
  17. package/lib/html.js +7 -8
  18. package/lib/htmltaxon.js +122 -33
  19. package/lib/index.d.ts +72 -27
  20. package/lib/index.js +3 -3
  21. package/lib/pagerenderer.js +6 -6
  22. package/lib/taxonomy/families.js +104 -0
  23. package/lib/{taxa.js → taxonomy/taxa.js} +18 -18
  24. package/lib/{taxon.js → taxonomy/taxon.js} +41 -111
  25. package/lib/taxonomy/taxonomy.js +17 -0
  26. package/lib/tools/calflora.js +2 -2
  27. package/lib/tools/calscape.js +3 -3
  28. package/lib/tools/cch2.js +128 -10
  29. package/lib/tools/fna.js +163 -0
  30. package/lib/tools/inat.js +3 -3
  31. package/lib/tools/jepsoneflora.js +21 -2
  32. package/lib/tools/rpi.js +5 -5
  33. package/lib/tools/supplementaltext.js +1 -1
  34. package/lib/tools/taxacsv.js +23 -4
  35. package/lib/types.js +10 -0
  36. package/lib/utils/inat-tools.js +2 -2
  37. package/lib/web/pageFamily.js +146 -0
  38. package/lib/web/pagetaxon.js +21 -63
  39. package/package.json +2 -1
  40. package/scripts/build-ebook.js +4 -4
  41. package/scripts/build-site.js +3 -3
  42. package/scripts/cpl-photos.js +1 -1
  43. package/scripts/cpl-tools.js +18 -2
  44. package/scripts/inatobsphotos.js +2 -2
  45. package/scripts/inattaxonphotos.js +2 -2
  46. package/lib/families.js +0 -243
  47. package/lib/jepson.js +0 -17
@@ -0,0 +1,163 @@
1
+ import path from "node:path";
2
+ import { Files } from "../files.js";
3
+ import { scrape } from "@htmltools/scrape";
4
+ import { TaxaCSV } from "./taxacsv.js";
5
+
6
+ /**
7
+ * @typedef {{name:string}} FNATaxon
8
+ * @typedef {Map<string,FNATaxon>} FNATaxa
9
+ */
10
+
11
+ export class FNA {
12
+ /**
13
+ * @param {string} toolsDataDir
14
+ * @param {string} dataDir
15
+ * @param {import("../types.js").Taxa} taxa
16
+ * @param {import("../errorlog.js").ErrorLog} errorLog
17
+ * @param {boolean} update
18
+ */
19
+ static async analyze(toolsDataDir, dataDir, taxa, errorLog, update) {
20
+ const toolsDataPath = path.join(toolsDataDir, "fna");
21
+ const fnaTaxa = await getFNATaxa(toolsDataPath);
22
+
23
+ const namesToUpdate = new Map();
24
+
25
+ for (const taxon of taxa.getTaxonList()) {
26
+ const fnaName = getFNAName(taxon, fnaTaxa) ?? "";
27
+ const taxonFNAName = taxon.getFNAName();
28
+ if (fnaName !== taxonFNAName) {
29
+ const name = taxon.getName();
30
+ errorLog.log(
31
+ name,
32
+ "FNA name does not match name from FNA data",
33
+ taxonFNAName,
34
+ fnaName,
35
+ );
36
+ namesToUpdate.set(name, fnaName === name ? "true" : fnaName);
37
+ }
38
+ }
39
+
40
+ if (update) {
41
+ updateNames(dataDir, namesToUpdate);
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @param {import("../types.js").Taxon} taxon
48
+ * @param {FNATaxa} fnaTaxa
49
+ * @returns {string|undefined}
50
+ */
51
+ function getFNAName(taxon, fnaTaxa) {
52
+ /**
53
+ * @param {string} input
54
+ * @returns {string|undefined}
55
+ */
56
+ function getName(input) {
57
+ if (fnaTaxa.has(input)) {
58
+ return input;
59
+ }
60
+
61
+ // See if we can swap var./subsp. to find it.
62
+ const parts = input.split(" ");
63
+ if (parts.length === 4) {
64
+ parts[2] = parts[2] === "var." ? "subsp." : "var.";
65
+ input = parts.join(" ");
66
+ if (fnaTaxa.has(input)) {
67
+ return input;
68
+ }
69
+ }
70
+
71
+ // If it's the nominate subsp/var, see if we can find the species.
72
+ if (parts.length === 4 && parts[1] === parts[3]) {
73
+ input = parts[0] + " " + parts[1];
74
+ if (fnaTaxa.has(input)) {
75
+ return input;
76
+ }
77
+ }
78
+ }
79
+ const name = getName(taxon.getName());
80
+ if (name !== undefined) {
81
+ return name;
82
+ }
83
+
84
+ // See if any synonyms match.
85
+ for (const synonym of taxon.getSynonyms()) {
86
+ const name = getName(synonym);
87
+ if (name !== undefined) {
88
+ return name;
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * @param {string} toolsDataPath
95
+ * @returns {Promise<FNATaxa>}
96
+ */
97
+ async function getFNATaxa(toolsDataPath) {
98
+ /** @type {FNATaxa} */
99
+ const fnaTaxa = new Map();
100
+
101
+ Files.mkdir(toolsDataPath);
102
+
103
+ // Get list of volumes.
104
+ const volumePage = path.join(toolsDataPath, "volumes.html");
105
+ if (!Files.exists(volumePage)) {
106
+ await Files.fetch(
107
+ "http://floranorthamerica.org/Special:SearchByProperty/:Volume/",
108
+ volumePage,
109
+ );
110
+ }
111
+
112
+ const volDoc = scrape.parseFile(volumePage);
113
+ const links = scrape.getSubtrees(volDoc, (e) => {
114
+ const href = scrape.getAttr(e, "href");
115
+ return href !== undefined && href.startsWith("/Volume_");
116
+ });
117
+ const vols = links.map((e) => scrape.getTextContent(e));
118
+
119
+ // For each volume, retrieve the JSON.
120
+ const baseURL =
121
+ "http://floranorthamerica.org/Special:Ask/limit=5000/unescape=true/format=json";
122
+ for (const vol of vols) {
123
+ const fileName = path.join(toolsDataPath, `${vol}.json`);
124
+ if (!Files.exists(fileName)) {
125
+ const url = baseURL + `/-5B-5BVolume::${vol}-5D-5D`;
126
+ await Files.fetch(url, fileName);
127
+ }
128
+
129
+ const text = Files.read(fileName);
130
+ /** @type {{results:Object<string,{}>}} */
131
+ const json = JSON.parse(text);
132
+ const results = json.results;
133
+
134
+ // If there are more than 5000 results, this will need to be updated to retrieve chunks.
135
+ if (Object.entries(results).length >= 5000) {
136
+ throw new Error(`${vol} has more than 5000 results`);
137
+ }
138
+
139
+ for (const [k] of Object.entries(results)) {
140
+ fnaTaxa.set(k, { name: k });
141
+ }
142
+ }
143
+
144
+ return fnaTaxa;
145
+ }
146
+
147
+ /**
148
+ * @param {string} dataDir
149
+ * @param {Map<string,string>} namesToUpdate
150
+ */
151
+ function updateNames(dataDir, namesToUpdate) {
152
+ const taxa = new TaxaCSV(dataDir);
153
+
154
+ for (const taxonData of taxa.getTaxa()) {
155
+ const newName = namesToUpdate.get(taxonData.taxon_name);
156
+ if (!newName) {
157
+ continue;
158
+ }
159
+ taxonData.fna = newName;
160
+ }
161
+
162
+ taxa.write();
163
+ }
package/lib/tools/inat.js CHANGED
@@ -21,7 +21,7 @@ export class INat {
21
21
  /**
22
22
  * @param {string} toolsDataDir
23
23
  * @param {string} dataDir
24
- * @param {import("../taxa.js").Taxa} taxa
24
+ * @param {import("../types.js").Taxa} taxa
25
25
  * @param {import("../exceptions.js").Exceptions} exceptions
26
26
  * @param {import("../errorlog.js").ErrorLog} errorLog
27
27
  * @param {string} csvFileName
@@ -115,7 +115,7 @@ export class INat {
115
115
 
116
116
  /**
117
117
  *
118
- * @param {import("../taxa.js").Taxa} taxa
118
+ * @param {import("../types.js").Taxa} taxa
119
119
  * @param {import("../exceptions.js").Exceptions} exceptions
120
120
  * @param {import("../errorlog.js").ErrorLog} errorLog
121
121
  */
@@ -168,7 +168,7 @@ export class INat {
168
168
 
169
169
  /**
170
170
  *
171
- * @param {import("../taxa.js").Taxa} taxa
171
+ * @param {import("../types.js").Taxa} taxa
172
172
  * @param {import("../exceptions.js").Exceptions} exceptions
173
173
  * @param {import("../errorlog.js").ErrorLog} errorLog
174
174
  * @param {string} name
@@ -54,7 +54,7 @@ export class JepsonEFlora {
54
54
 
55
55
  /**
56
56
  * @param {string} toolsDataDir
57
- * @param {import("../taxa.js").Taxa} taxa
57
+ * @param {import("../taxonomy/taxa.js").Taxa} taxa
58
58
  * @param {import("../errorlog.js").ErrorLog} errorLog
59
59
  */
60
60
  constructor(toolsDataDir, taxa, errorLog) {
@@ -102,8 +102,25 @@ export class JepsonEFlora {
102
102
  idsToUpdate.set(name, jepsInfo.id);
103
103
  }
104
104
 
105
- const efStatus = this.#getStatusCode(jepsInfo);
105
+ let efStatus = this.#getStatusCode(jepsInfo);
106
106
  const taxonStatus = taxon.getStatus();
107
+ // Override if exception is specified.
108
+ const nativeException = exceptions.getValue(
109
+ name,
110
+ "jepson",
111
+ "native",
112
+ );
113
+ if (typeof nativeException === "boolean") {
114
+ const overrideStatus = nativeException ? "N" : "X";
115
+ if (overrideStatus === efStatus) {
116
+ this.#errorLog.log(
117
+ name,
118
+ "has unnecessary Jepson native override",
119
+ );
120
+ }
121
+ efStatus = overrideStatus;
122
+ }
123
+
107
124
  if (
108
125
  efStatus !== taxonStatus &&
109
126
  !(taxonStatus === "NC" && efStatus === "N")
@@ -154,6 +171,8 @@ export class JepsonEFlora {
154
171
  for (const [k] of Object.entries(exceptions)) {
155
172
  const jepsonData = this.#nameInfo.get(name);
156
173
  switch (k) {
174
+ case "native":
175
+ break;
157
176
  case "notineflora":
158
177
  // Make sure it is really not in eFlora.
159
178
  if (jepsonData) {
package/lib/tools/rpi.js CHANGED
@@ -12,7 +12,7 @@ class RPI {
12
12
 
13
13
  /**
14
14
  * @param {string} toolsDataDir
15
- * @param {import("../taxa.js").Taxa} taxa
15
+ * @param {import("../types.js").Taxa} taxa
16
16
  * @param {import("../config.js").Config} config
17
17
  * @param {import("../exceptions.js").Exceptions} exceptions
18
18
  * @param {import("../errorlog.js").ErrorLog} errorLog
@@ -80,8 +80,8 @@ class RPI {
80
80
  const rank = row["CRPR"];
81
81
  const rawCESA = row["CESA"];
82
82
  const rawFESA = row["FESA"];
83
- const cesa = rawCESA === "None" ? undefined : rawCESA;
84
- const fesa = rawFESA === "None" ? undefined : rawFESA;
83
+ const cesa = rawCESA === "None" ? "" : rawCESA;
84
+ const fesa = rawFESA === "None" ? "" : rawFESA;
85
85
  const cnddb = row["SRank"];
86
86
  const globalRank = row["GRank"];
87
87
  const taxon = taxa.getTaxon(name);
@@ -202,7 +202,7 @@ class RPI {
202
202
  }
203
203
 
204
204
  /**
205
- * @param {import("../taxa.js").Taxa} taxa
205
+ * @param {import("../types.js").Taxa} taxa
206
206
  * @param {import("../config.js").Config} config
207
207
  * @param {import("../exceptions.js").Exceptions} exceptions
208
208
  * @param {import("../errorlog.js").ErrorLog} errorLog
@@ -366,7 +366,7 @@ class RPI {
366
366
 
367
367
  /**
368
368
  * @param {string} toolsDataDir
369
- * @param {import("../taxa.js").Taxa} taxa
369
+ * @param {import("../types.js").Taxa} taxa
370
370
  * @param {import("../exceptions.js").Exceptions} exceptions
371
371
  * @param {import("../errorlog.js").ErrorLog} errorLog
372
372
  */
@@ -5,7 +5,7 @@ const VALID_EXTENSIONS = new Set(["md", "footer.md"]);
5
5
  export class SupplementalText {
6
6
  /**
7
7
  *
8
- * @param {import("../taxa.js").Taxa} taxa
8
+ * @param {import("../types.js").Taxa} taxa
9
9
  * @param {import("../errorlog.js").ErrorLog} errorLog
10
10
  */
11
11
  static analyze(taxa, errorLog) {
@@ -1,9 +1,30 @@
1
1
  import path from "path";
2
2
  import { CSV } from "../csv.js";
3
3
 
4
+ const HEADERS = [
5
+ "taxon_name",
6
+ "common name",
7
+ "status",
8
+ "jepson id",
9
+ "calrecnum",
10
+ "inat id",
11
+ "cch2_id",
12
+ "fna",
13
+ "calscape_cn",
14
+ "life_cycle",
15
+ "flower_color",
16
+ "bloom_start",
17
+ "bloom_end",
18
+ "RPI ID",
19
+ "CRPR",
20
+ "CESA",
21
+ "FESA",
22
+ "SRank",
23
+ "GRank",
24
+ ];
25
+
4
26
  export class TaxaCSV {
5
27
  #filePath;
6
- #headers;
7
28
  /** @type {import("../index.js").TaxonData[]} */
8
29
  #taxa;
9
30
 
@@ -13,9 +34,7 @@ export class TaxaCSV {
13
34
  constructor(dataDir) {
14
35
  this.#filePath = path.join(dataDir, "taxa.csv");
15
36
  const csv = CSV.readFileAndHeaders(this.#filePath);
16
- // @ts-ignore
17
37
  this.#taxa = csv.data;
18
- this.#headers = csv.headers;
19
38
  }
20
39
 
21
40
  /**
@@ -26,6 +45,6 @@ export class TaxaCSV {
26
45
  }
27
46
 
28
47
  write() {
29
- CSV.writeFileObject(this.#filePath, this.#taxa, this.#headers);
48
+ CSV.writeFileObject(this.#filePath, this.#taxa, HEADERS);
30
49
  }
31
50
  }
package/lib/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Classes
3
+ * @typedef {import("./config.js").Config} Config
4
+ * @typedef {import("./taxonomy/families.js").Families} Families
5
+ * @typedef {import("./taxonomy/families.js").Family} Family
6
+ * @typedef {import("./taxonomy/taxa.js").Taxa} Taxa
7
+ * @typedef {import("./index.js").TaxaColDef<import("./types.js").Taxon>} TaxaColDef
8
+ * @typedef {import("./taxonomy/taxon.js").Taxon} Taxon
9
+ * @typedef {import("./taxonomy/taxonomy.js").Taxonomy} Taxonomy
10
+ */
@@ -41,7 +41,7 @@ import { chunk, sleep } from "../util.js";
41
41
  const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
42
42
 
43
43
  /**
44
- * @param {import("../taxon.js").Taxon[]} taxa
44
+ * @param {import("../types.js").Taxon[]} taxa
45
45
  * @return {Promise<InatApiTaxon[]>}
46
46
  */
47
47
  async function fetchInatTaxa(taxa) {
@@ -57,7 +57,7 @@ async function fetchInatTaxa(taxa) {
57
57
  }
58
58
 
59
59
  /**
60
- * @param {import("../taxon.js").Taxon[]} taxaToUpdate
60
+ * @param {import("../types.js").Taxon[]} taxaToUpdate
61
61
  * @returns {Promise<Map<string,InatPhotoInfo[]>>}
62
62
  */
63
63
  export async function getTaxonPhotos(taxaToUpdate) {
@@ -0,0 +1,146 @@
1
+ import { ExternalSites } from "../externalsites.js";
2
+ import { GenericPage } from "../genericpage.js";
3
+ import { HTML } from "../html.js";
4
+ import { HTMLTaxon } from "../htmltaxon.js";
5
+ import { Sections } from "../taxonomy/families.js";
6
+
7
+ export class PageFamilyList extends GenericPage {
8
+ #families;
9
+
10
+ /**
11
+ * @param {string} outputDir
12
+ * @param {import("../types.js").Family[]} families
13
+ */
14
+ constructor(outputDir, families) {
15
+ super(outputDir, "Families", "list_families");
16
+ this.#families = families;
17
+ }
18
+
19
+ /**
20
+ * @param {import("../types.js").TaxaColDef[]} [taxaColumns]
21
+ */
22
+ render(taxaColumns) {
23
+ let html = this.getDefaultIntro();
24
+
25
+ const sections = Sections.getSections();
26
+ const sectionLinks = [];
27
+ for (const name of Object.keys(sections).sort()) {
28
+ const taxa = sections[name];
29
+
30
+ // Render the section page.
31
+ new PageSection(this.getOutputDir(), name, taxa).render(
32
+ taxaColumns,
33
+ );
34
+
35
+ // Render the link.
36
+ const href = "./" + name + ".html";
37
+ sectionLinks.push(
38
+ HTML.getLink(href, name) + " (" + taxa.length + ")",
39
+ );
40
+ }
41
+ html += HTML.wrap("ul", HTML.arrayToLI(sectionLinks), {
42
+ class: "listmenu",
43
+ });
44
+
45
+ html += "<table>";
46
+ html += "<thead>";
47
+ html += HTML.textElement("th", "Family");
48
+ html += HTML.textElement("th", "Number of Species", { class: "right" });
49
+ html += "</thead>";
50
+
51
+ html += "<tbody>";
52
+ for (const family of this.#families) {
53
+ const taxa = family.getTaxa();
54
+ if (!taxa) {
55
+ continue;
56
+ }
57
+ let cols = HTML.wrap(
58
+ "td",
59
+ HTML.getLink("./" + family.getFileName(), family.getName()),
60
+ );
61
+ cols += HTML.wrap("td", taxa.length, { class: "right" });
62
+ html += HTML.wrap("tr", cols);
63
+ }
64
+ html += "</tbody>";
65
+
66
+ html += "</table>";
67
+
68
+ this.writeFile(html);
69
+ }
70
+
71
+ /**
72
+ * @param {string} outputDir
73
+ * @param {import("../types.js").TaxaColDef[]} [taxaColumns]
74
+ */
75
+ renderPages(outputDir, taxaColumns) {
76
+ for (const family of this.#families) {
77
+ if (family.getTaxa()) {
78
+ new PageFamily(outputDir, family).render(taxaColumns);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ class PageFamily extends GenericPage {
85
+ #family;
86
+
87
+ /**
88
+ * @param {string} outputDir
89
+ * @param {import("../types.js").Family} family
90
+ */
91
+ constructor(outputDir, family) {
92
+ super(outputDir, family.getName(), family.getBaseFileName());
93
+ this.#family = family;
94
+ }
95
+
96
+ /**
97
+ * @param {import("../types.js").TaxaColDef[]} [columns]
98
+ */
99
+ render(columns) {
100
+ let html = this.getDefaultIntro();
101
+
102
+ const jepsonLink = ExternalSites.getJepsonRefLink(this.#family);
103
+ if (jepsonLink) {
104
+ html += HTML.wrap(
105
+ "div",
106
+ HTML.getLink(jepsonLink, "Jepson eFlora", {}, true),
107
+ {
108
+ class: "section",
109
+ },
110
+ );
111
+ }
112
+
113
+ const taxa = this.#family.getTaxa();
114
+ if (!taxa) {
115
+ throw new Error();
116
+ }
117
+ html += HTMLTaxon.getTaxaTable(taxa, columns);
118
+
119
+ this.writeFile(html);
120
+ }
121
+ }
122
+
123
+ class PageSection extends GenericPage {
124
+ #taxa;
125
+
126
+ /**
127
+ * @param {string} outputDir
128
+ * @param {string} name
129
+ * @param {import("../types.js").Taxon[]} taxa
130
+ */
131
+ constructor(outputDir, name, taxa) {
132
+ super(outputDir, name, name);
133
+ this.#taxa = taxa;
134
+ }
135
+
136
+ /**
137
+ * @param {import("../types.js").TaxaColDef[]} [columns]
138
+ */
139
+ render(columns) {
140
+ let html = this.getDefaultIntro();
141
+
142
+ html += HTMLTaxon.getTaxaTable(this.#taxa, columns);
143
+
144
+ this.writeFile(html);
145
+ }
146
+ }
@@ -1,7 +1,5 @@
1
- import { Jepson } from "../jepson.js";
2
1
  import { RarePlants } from "../rareplants.js";
3
2
  import { GenericPage } from "../genericpage.js";
4
- import { ExternalSites } from "../externalsites.js";
5
3
  import { HTML } from "../html.js";
6
4
  import { HTMLTaxon } from "../htmltaxon.js";
7
5
 
@@ -12,7 +10,7 @@ export class PageTaxon extends GenericPage {
12
10
  /**
13
11
  * @param {string} outputDir
14
12
  * @param {import("../config.js").Config} config
15
- * @param {import("../taxon.js").Taxon} taxon
13
+ * @param {import("../types.js").Taxon} taxon
16
14
  */
17
15
  constructor(outputDir, config, taxon) {
18
16
  super(outputDir, taxon.getName(), taxon.getBaseFileName());
@@ -21,72 +19,31 @@ export class PageTaxon extends GenericPage {
21
19
  }
22
20
 
23
21
  #getInfoLinks() {
22
+ /** @type {string[]} */
24
23
  const links = [];
25
- const jepsonID = this.#taxon.getJepsonID();
26
- if (jepsonID) {
27
- links.push(Jepson.getEFloraLink(jepsonID));
28
- }
29
- const cfLink = this.#taxon.getCalfloraTaxonLink();
30
- if (cfLink) {
31
- links.push(cfLink);
32
- }
33
- const iNatLink = this.#taxon.getINatTaxonLink();
34
- if (iNatLink) {
35
- links.push(iNatLink);
36
- }
37
- const calscapeLink = HTMLTaxon.getCalscapeLink(this.#taxon);
38
- if (calscapeLink) {
39
- links.push(calscapeLink);
40
- }
41
- const rpiLink = this.#taxon.getRPITaxonLink();
42
- if (rpiLink) {
43
- links.push(rpiLink);
44
- }
45
- HTMLTaxon.addLink(
24
+ HTMLTaxon.addRefLink(links, this.#taxon, "jepson");
25
+ HTMLTaxon.addRefLink(links, this.#taxon, "calflora");
26
+ HTMLTaxon.addRefLink(
46
27
  links,
47
- ExternalSites.getCCH2RefLink(this.#taxon),
48
- "CCH2",
28
+ this.#taxon,
29
+ "inat",
30
+ this.#taxon.getINatSyn()
31
+ ? " (" + this.#taxon.getINatSyn() + ")"
32
+ : "",
49
33
  );
34
+ HTMLTaxon.addRefLink(links, this.#taxon, "rpi");
35
+ HTMLTaxon.addRefLink(links, this.#taxon, "fna");
36
+ HTMLTaxon.addRefLink(links, this.#taxon, "cch");
37
+ HTMLTaxon.addRefLink(links, this.#taxon, "calscape");
50
38
  return links;
51
39
  }
52
40
 
53
41
  #getObsLinks() {
42
+ /** @type {string[]} */
54
43
  const links = [];
55
- links.push(
56
- HTML.getLink(
57
- "https://www.calflora.org/entry/observ.html?track=m#srch=t&grezc=5&cols=b&lpcli=t&cc=" +
58
- this.#config.getCountyCodes().join("!") +
59
- "&incobs=f&taxon=" +
60
- this.#taxon.getCalfloraName().replaceAll(" ", "+"),
61
- "Calflora",
62
- {},
63
- true,
64
- ),
65
- );
66
- const iNatID = this.#taxon.getINatID();
67
- if (iNatID) {
68
- links.push(
69
- HTML.getLink(
70
- ExternalSites.getInatObsLink({
71
- project_id: this.#config.getConfigValue(
72
- "inat",
73
- "project_id",
74
- ),
75
- subview: "map",
76
- taxon_id: iNatID,
77
- }),
78
- "iNaturalist",
79
- {},
80
- true,
81
- ),
82
- );
83
- }
84
- HTMLTaxon.addLink(
85
- links,
86
- ExternalSites.getCCH2ObsLink(this.#taxon, this.#config),
87
- "CCH2",
88
- );
89
-
44
+ HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "inat");
45
+ HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "calflora");
46
+ HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "cch");
90
47
  return links;
91
48
  }
92
49
 
@@ -103,7 +60,7 @@ export class PageTaxon extends GenericPage {
103
60
  }) +
104
61
  HTML.getToolTip(
105
62
  cnpsRank,
106
- this.#taxon.getRPIRankAndThreatTooltip(),
63
+ HTMLTaxon.getRPIRankAndThreatTooltip(this.#taxon),
107
64
  ),
108
65
  );
109
66
  if (this.#taxon.getCESA()) {
@@ -126,7 +83,8 @@ export class PageTaxon extends GenericPage {
126
83
  if (taxa.length > 1) {
127
84
  for (const taxon of taxa) {
128
85
  links.push(
129
- taxon.getHTMLLink(
86
+ HTMLTaxon.getHTMLLink(
87
+ taxon,
130
88
  taxon.getName() !== this.#taxon.getName(),
131
89
  ),
132
90
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.4.21",
3
+ "version": "0.4.23",
4
4
  "description": "Tools to create Jekyll files for a website listing plants in an area of California.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -58,6 +58,7 @@
58
58
  "eslint": "^9.18.0",
59
59
  "jest": "^29.7.0",
60
60
  "prettier": "^3.4.2",
61
+ "puppeteer": "^24.1.1",
61
62
  "typescript": "^5.7.3"
62
63
  }
63
64
  }
@@ -5,7 +5,7 @@ import { ErrorLog } from "../lib/errorlog.js";
5
5
  import { Files } from "../lib/files.js";
6
6
  import { PlantBook } from "../lib/ebook/plantbook.js";
7
7
  import { Program } from "../lib/program.js";
8
- import { Taxa } from "../lib/taxa.js";
8
+ import { Taxa } from "../lib/taxonomy/taxa.js";
9
9
 
10
10
  const program = Program.getProgram();
11
11
  program
@@ -33,7 +33,7 @@ async function build(options) {
33
33
  await buildBook(
34
34
  outputBase + suffix,
35
35
  path,
36
- options.showFlowerErrors
36
+ options.showFlowerErrors,
37
37
  );
38
38
  }
39
39
  }
@@ -42,7 +42,7 @@ async function build(options) {
42
42
  await buildBook(
43
43
  options.outputdir,
44
44
  options.datadir,
45
- options.showFlowerErrors
45
+ options.showFlowerErrors,
46
46
  );
47
47
  }
48
48
  }
@@ -58,7 +58,7 @@ async function buildBook(outputDir, dataDir, showFlowerErrors) {
58
58
  const taxa = new Taxa(
59
59
  Program.getIncludeList(dataDir),
60
60
  errorLog,
61
- showFlowerErrors
61
+ showFlowerErrors,
62
62
  );
63
63
 
64
64
  const config = new Config(dataDir);