@ca-plant-list/ca-plant-list 0.1.12 → 0.1.14

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/build-site.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Config } from "./lib/config.js";
4
3
  import { DataLoader } from "./lib/dataloader.js";
5
4
  import { ErrorLog } from "./lib/errorlog.js";
6
5
  import { PageRenderer } from "./lib/pagerenderer.js";
@@ -8,11 +7,9 @@ import commandLineArgs from "command-line-args";
8
7
 
9
8
  const options = commandLineArgs( DataLoader.getOptionDefs() );
10
9
 
11
- const CONFIG_DATA_DIR = "./data";
12
10
  const OUTPUT_DIR = "./output";
13
11
 
14
- Config.init( CONFIG_DATA_DIR );
15
12
  DataLoader.load( options );
16
13
  PageRenderer.render( OUTPUT_DIR );
17
14
 
18
- ErrorLog.write( OUTPUT_DIR + "/errors.txt" );
15
+ ErrorLog.write( OUTPUT_DIR + "/errors.tsv" );
package/data/genera.json CHANGED
@@ -11,6 +11,10 @@
11
11
  "family": "Lycopodiaceae",
12
12
  "id": "9179"
13
13
  },
14
+ "Rhinotropis": {
15
+ "family": "Polygalaceae",
16
+ "id": 99965
17
+ },
14
18
  "Selaginella": {
15
19
  "family": "Selaginellaceae",
16
20
  "id": "8877"
@@ -47,6 +47,9 @@
47
47
 
48
48
  {%if page.js%}
49
49
  <script src="{{site.baseurl}}/assets/js/{{page.js}}" type="module"></script>{%endif%}
50
+
51
+ <script src="{{site.baseurl}}/assets/js/ui.js" type="module"></script>
52
+
50
53
  {%include analytics.html%}
51
54
  </body>
52
55
 
@@ -17,10 +17,6 @@
17
17
  background-color: #3D550C;
18
18
  }
19
19
 
20
- span[title] {
21
- text-decoration: underline dotted;
22
- }
23
-
24
20
  span.label {
25
21
  font-weight: 600;
26
22
  padding-right: .5rem;
@@ -0,0 +1,10 @@
1
+ class UI {
2
+
3
+ static init() {
4
+ const tooltips = document.querySelectorAll( "span[title]" );
5
+ [ ...tooltips ].map( tooltipTriggerEl => new bootstrap.Tooltip( tooltipTriggerEl ) );
6
+ }
7
+
8
+ }
9
+
10
+ UI.init();
@@ -1,19 +1,19 @@
1
- import * as fs from "node:fs";
2
1
  import { Config } from "./config.js";
3
2
  import { Families } from "./families.js";
3
+ import { Files } from "./files.js";
4
4
 
5
5
  class BasePageRenderer {
6
6
 
7
- static render( outputDir, Taxa ) {
7
+ static render( outputDir, Taxa, familyCols ) {
8
8
 
9
9
  // Copy static files
10
- fs.rmSync( outputDir, { force: true, recursive: true, maxRetries: 2, retryDelay: 1000 } );
10
+ Files.rmDir( outputDir );
11
11
  // First copy default Jekyll files from package.
12
- fs.cpSync( Config.getPackageDir() + "/jekyll", outputDir, { recursive: true } );
12
+ Files.copyDir( Config.getPackageDir() + "/jekyll", outputDir );
13
13
  // Then copy Jekyll files from current dir (which may override default files).
14
- fs.cpSync( "jekyll", outputDir, { recursive: true } );
14
+ Files.copyDir( "jekyll", outputDir );
15
15
 
16
- Families.renderPages( outputDir );
16
+ Families.renderPages( outputDir, familyCols );
17
17
 
18
18
  this.renderTools( outputDir, Taxa );
19
19
 
@@ -39,7 +39,7 @@ class BasePageRenderer {
39
39
  names.push( row );
40
40
  }
41
41
 
42
- fs.writeFileSync( outputDir + "/_includes/names.json", JSON.stringify( names ) );
42
+ Files.write( outputDir + "/_includes/names.json", JSON.stringify( names ) );
43
43
 
44
44
  }
45
45
 
package/lib/config.js CHANGED
@@ -1,12 +1,20 @@
1
1
  import * as path from "node:path";
2
2
  import * as url from "node:url";
3
- import * as fs from "node:fs";
3
+ import { Files } from "./files.js";
4
4
 
5
5
  class Config {
6
6
 
7
7
  static #config = {};
8
8
  static #packageDir = path.dirname( path.dirname( url.fileURLToPath( import.meta.url ) ) );
9
9
 
10
+ static {
11
+ try {
12
+ this.#config = JSON.parse( Files.read( "./data/config.json" ) );
13
+ } catch ( e ) {
14
+ console.log( e );
15
+ }
16
+ }
17
+
10
18
  static getConfigValue( prefix, name, subcat, dflt ) {
11
19
  const obj = this.#config[ prefix ];
12
20
  if ( obj ) {
@@ -26,6 +34,10 @@ class Config {
26
34
  return dflt;
27
35
  }
28
36
 
37
+ static getCountyCodes() {
38
+ return this.#config[ "counties" ];
39
+ }
40
+
29
41
  static getLabel( name, dflt ) {
30
42
  return this.getConfigValue( "labels", name, undefined, dflt );
31
43
  }
@@ -34,14 +46,6 @@ class Config {
34
46
  return this.#packageDir;
35
47
  }
36
48
 
37
- static init( dir ) {
38
- try {
39
- this.#config = JSON.parse( fs.readFileSync( dir + "/config.json" ) );
40
- } catch ( e ) {
41
- console.log( e );
42
- }
43
- }
44
-
45
49
  }
46
50
 
47
51
  export { Config };
package/lib/exceptions.js CHANGED
@@ -1,35 +1,39 @@
1
- import * as fs from "node:fs";
1
+ import { Files } from "./files.js";
2
2
 
3
3
  class Exceptions {
4
4
 
5
5
  static #exceptions = {};
6
6
 
7
- static hasException( name, cat, subcat ) {
7
+ static getExceptions() {
8
+ return Object.entries( this.#exceptions );
9
+ }
10
+
11
+ static getValue( name, cat, subcat, defaultValue ) {
8
12
  const taxonData = this.#exceptions[ name ];
9
13
  if ( taxonData ) {
10
14
  const catData = taxonData[ cat ];
11
15
  if ( catData ) {
12
- return catData[ subcat ] !== undefined;
16
+ const val = catData[ subcat ];
17
+ return ( val === undefined ) ? defaultValue : val;
13
18
  }
14
19
  }
15
- return false;
20
+ return defaultValue;
16
21
  }
17
22
 
18
- static getValue( name, cat, subcat, defaultValue ) {
23
+ static hasException( name, cat, subcat ) {
19
24
  const taxonData = this.#exceptions[ name ];
20
25
  if ( taxonData ) {
21
26
  const catData = taxonData[ cat ];
22
27
  if ( catData ) {
23
- const val = catData[ subcat ];
24
- return ( val === undefined ) ? defaultValue : val;
28
+ return catData[ subcat ] !== undefined;
25
29
  }
26
30
  }
27
- return defaultValue;
31
+ return false;
28
32
  }
29
33
 
30
34
  static init( dir ) {
31
35
  try {
32
- this.#exceptions = JSON.parse( fs.readFileSync( dir + "/exceptions.json" ) );
36
+ this.#exceptions = JSON.parse( Files.read( dir + "/exceptions.json" ) );
33
37
  } catch ( e ) {
34
38
  console.log( e );
35
39
  }
package/lib/families.js CHANGED
@@ -19,12 +19,12 @@ class Families {
19
19
  }
20
20
  }
21
21
 
22
- static renderPages( outputDir ) {
22
+ static renderPages( outputDir, columns ) {
23
23
  new PageFamilyList( Object.values( this.#families ) ).render( outputDir );
24
24
 
25
25
  for ( const family of Object.values( this.#families ) ) {
26
26
  if ( family.getTaxa() ) {
27
- new PageFamily( family ).render( outputDir );
27
+ new PageFamily( family ).render( outputDir, columns );
28
28
  }
29
29
  }
30
30
  }
@@ -48,15 +48,25 @@ class PageFamilyList extends HTMLPage {
48
48
 
49
49
  html += HTML.textElement( "h1", title );
50
50
 
51
- html += "<ul>";
51
+ html += "<table>";
52
+ html += "<thead>";
53
+ html += HTML.textElement( "th", "Family" );
54
+ html += HTML.textElement( "th", "Number of Species", { class: "right" } );
55
+ html += "</thead>";
56
+
57
+ html += "<tbody>";
52
58
  for ( const family of this.#families ) {
53
59
  const taxa = family.getTaxa();
54
- if ( taxa ) {
55
- const link = HTML.getLink( "./" + family.getFileName(), family.getName() );
56
- html += "<li>" + link + " (" + taxa.length + ")" + "</li>";
60
+ if ( !taxa ) {
61
+ continue;
57
62
  }
63
+ let cols = HTML.wrap( "td", HTML.getLink( "./" + family.getFileName(), family.getName() ) );
64
+ cols += HTML.wrap( "td", taxa.length, { class: "right" } );
65
+ html += HTML.wrap( "tr", cols );
58
66
  }
59
- html += "</ul>";
67
+ html += "</tbody>";
68
+
69
+ html += "</table>";
60
70
 
61
71
  this.writeFile( outputDir, "list_families.html", html );
62
72
  }
@@ -71,7 +81,7 @@ class PageFamily extends HTMLPage {
71
81
  this.#family = family;
72
82
  }
73
83
 
74
- render( outputDir ) {
84
+ render( outputDir, columns ) {
75
85
 
76
86
  let html = this.getFrontMatter( this.#family.getName() );
77
87
 
@@ -83,7 +93,7 @@ class PageFamily extends HTMLPage {
83
93
  { class: "section" }
84
94
  );
85
95
 
86
- html += Taxa.getHTMLTable( this.#family.getTaxa() );
96
+ html += Taxa.getHTMLTable( this.#family.getTaxa(), columns );
87
97
 
88
98
  this.writeFile( outputDir, this.#family.getFileName(), html );
89
99
 
package/lib/files.js CHANGED
@@ -3,6 +3,10 @@ import { default as unzipper } from "unzipper";
3
3
 
4
4
  class Files {
5
5
 
6
+ static copyDir( srcDir, targetDir ) {
7
+ fs.cpSync( srcDir, targetDir, { recursive: true } );
8
+ }
9
+
6
10
  static createFileFromStream( fileName, inStream ) {
7
11
 
8
12
  function implementation( fileName, inStream, resolve ) {
@@ -18,11 +22,30 @@ class Files {
18
22
  return fs.existsSync( path );
19
23
  }
20
24
 
21
- static async fetch( url, targetFileName ) {
22
- const response = await fetch( url );
25
+ /**
26
+ * Retrieve data from a URL and write it to a file.
27
+ * @param {string|URL} url
28
+ * @param {string|undefined} targetFileName If targetFileName is undefined, the data will be retrieved but not written to a file.
29
+ * @param {Object} [headers={}] Request Headers.
30
+ */
31
+ static async fetch( url, targetFileName, headers = {} ) {
32
+ const response = await fetch( url, headers );
23
33
  const data = await response.blob();
24
- const buffer = await data.arrayBuffer();
25
- fs.writeFileSync( targetFileName, Buffer.from( buffer ) );
34
+ const arrayBuffer = await data.arrayBuffer();
35
+ const buffer = Buffer.from( arrayBuffer );
36
+ if ( targetFileName ) {
37
+ fs.writeFileSync( targetFileName, buffer );
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Retrieve data from a URL and return the Response Header.
43
+ * @param {string|URL} url
44
+ * @returns {Headers}
45
+ */
46
+ static async fetchHeaders( url ) {
47
+ const response = await fetch( url );
48
+ return response.headers;
26
49
  }
27
50
 
28
51
  static mkdir( path ) {
@@ -33,8 +56,12 @@ class Files {
33
56
  return fs.readFileSync( path, "utf8" );
34
57
  }
35
58
 
36
- static write( path, data ) {
37
- if ( this.exists( path ) ) {
59
+ static rmDir( dir ) {
60
+ fs.rmSync( dir, { force: true, recursive: true, maxRetries: 2, retryDelay: 1000 } );
61
+ }
62
+
63
+ static write( path, data, overwrite = false ) {
64
+ if ( !overwrite && this.exists( path ) ) {
38
65
  throw new Error( path + " already exists" );
39
66
  }
40
67
  fs.writeFileSync( path, data );
@@ -0,0 +1,48 @@
1
+ import { Files, HTML, Jekyll } from "@ca-plant-list/ca-plant-list";
2
+
3
+ class GenericPage {
4
+
5
+ #outputDir;
6
+ #title;
7
+ #baseFileName;
8
+ #js;
9
+
10
+ constructor( outputDir, title, baseFileName, js ) {
11
+ this.#outputDir = outputDir;
12
+ this.#title = title;
13
+ this.#baseFileName = baseFileName;
14
+ this.#js = js;
15
+ }
16
+
17
+ getDefaultIntro() {
18
+ let html = this.#getFrontMatter();
19
+ html += HTML.textElement( "h1", this.#title );
20
+ const introPath = "lists/" + this.#baseFileName + "-intro.md";
21
+ if ( Jekyll.hasInclude( this.#outputDir, introPath ) ) {
22
+ html += HTML.wrap( "div", Jekyll.include( introPath ), { class: "section" } );
23
+ }
24
+ return html;
25
+ }
26
+
27
+ #getFrontMatter() {
28
+ return "---\n"
29
+ + "title: \"" + this.#title + "\"\n"
30
+ + ( this.#js ? ( "js: " + this.#js + "\n" ) : "" )
31
+ + "---\n";
32
+ }
33
+
34
+ getOutputDir() {
35
+ return this.#outputDir;
36
+ }
37
+
38
+ getTitle() {
39
+ return this.#title;
40
+ }
41
+
42
+ writeFile( html ) {
43
+ Files.write( this.#outputDir + "/" + this.#baseFileName + ".html", html );
44
+ }
45
+
46
+ }
47
+
48
+ export { GenericPage };
package/lib/html.js CHANGED
@@ -45,21 +45,40 @@ export class HTML {
45
45
  return html;
46
46
  }
47
47
 
48
- static getLink( href, linkText, attributes = {}, options = 0 ) {
48
+ /**
49
+ * Generate HTML for an &lt;a> element.
50
+ * @param {string|undefined} href
51
+ * @param {string} linkText
52
+ * @param {Object} attributes
53
+ * @param {boolean} openInNewWindow true if the link should open in a new window.
54
+ * @returns {string} an HTML &lt;a> element.
55
+ */
56
+ static getLink( href, linkText, attributes = {}, openInNewWindow ) {
49
57
  let html = "<a";
50
58
  if ( href !== undefined ) {
51
59
  html += this.renderAttribute( "href", href );
52
60
  }
53
61
  html += this.renderAttributes( attributes );
54
- if ( options & HTML_OPTIONS.OPEN_NEW ) {
62
+ if ( openInNewWindow ) {
55
63
  html += this.renderAttribute( "target", "_blank" );
56
64
  }
57
65
  return html + ">" + this.escapeText( linkText ) + "</a >";
58
66
  }
59
67
 
60
- static getToolTip( text, tooltip ) {
68
+ /**
69
+ * Get a Bootstrap formatted tooltip element.
70
+ * @param {string} text - The text or HTML that should trigger the tooltip on hover.
71
+ * @param {string} tooltip - The tooltip text or HTML.
72
+ * @param {Object} options
73
+ * @param {boolean} options.icon [true] display an icon after the text
74
+ * @returns {string} A &lt;span> element to be used as a Bootstrap tooltip.
75
+ */
76
+ static getToolTip( text, tooltip, options = {} ) {
61
77
  const func = text.charAt( 0 ) === "<" ? HTML.wrap : HTML.textElement;
62
- return func( "span", text + " ⓘ", { title: tooltip } );
78
+ if ( options.icon !== false ) {
79
+ text += " ⓘ";
80
+ }
81
+ return func( "span", text, { "data-bs-html": "true", "title": tooltip } );
63
82
  }
64
83
 
65
84
  static renderAttribute( n, v ) {
package/lib/htmlpage.js CHANGED
@@ -1,5 +1,8 @@
1
- import * as fs from "node:fs";
1
+ import { Files } from "./files.js";
2
2
 
3
+ /**
4
+ * @deprecated
5
+ */
3
6
  class HTMLPage {
4
7
 
5
8
  getFrontMatter( title, js ) {
@@ -10,7 +13,7 @@ class HTMLPage {
10
13
  }
11
14
 
12
15
  writeFile( outputDir, fileName, html ) {
13
- fs.writeFileSync( outputDir + "/" + fileName, html );
16
+ Files.write( outputDir + "/" + fileName, html );
14
17
  }
15
18
 
16
19
  }
package/lib/index.d.ts CHANGED
@@ -1,10 +1,24 @@
1
1
  export class Files {
2
+ static copyDir(srcDir: any, targetDir: any): void;
2
3
  static createFileFromStream(fileName: any, inStream: any): Promise<any>;
3
4
  static exists(path: any): boolean;
4
- static fetch(url: any, targetFileName: any): Promise<void>;
5
+ /**
6
+ * Retrieve data from a URL and write it to a file.
7
+ * @param {string|URL} url
8
+ * @param {string|undefined} targetFileName If targetFileName is undefined, the data will be retrieved but not written to a file.
9
+ * @param {Object} [headers={}] Request Headers.
10
+ */
11
+ static fetch(url: string | URL, targetFileName: string | undefined, headers?: any): Promise<void>;
12
+ /**
13
+ * Retrieve data from a URL and return the Response Header.
14
+ * @param {string|URL} url
15
+ * @returns {Headers}
16
+ */
17
+ static fetchHeaders(url: string | URL): Headers;
5
18
  static mkdir(path: any): void;
6
19
  static read(path: any): string;
7
- static write(path: any, data: any): void;
20
+ static rmDir(dir: any): void;
21
+ static write(path: any, data: any, overwrite?: boolean): void;
8
22
  static zipFileExtract(zipFilePath: any, fileNameToUnzip: any, targetFilePath: any): Promise<void>;
9
23
  }
10
24
  export namespace HTML_OPTIONS {
@@ -21,8 +35,26 @@ export class HTML {
21
35
  */
22
36
  static getElement(elName: any, text: any, attributes?: {}, options?: number): string;
23
37
  static "__#2@#getElement"(elName: any, text: any, attributes: any, escape: any): string;
24
- static getLink(href: any, linkText: any, attributes?: {}, options?: number): string;
25
- static getToolTip(text: any, tooltip: any): string;
38
+ /**
39
+ * Generate HTML for an &lt;a> element.
40
+ * @param {string|undefined} href
41
+ * @param {string} linkText
42
+ * @param {Object} attributes
43
+ * @param {boolean} openInNewWindow true if the link should open in a new window.
44
+ * @returns {string} an HTML &lt;a> element.
45
+ */
46
+ static getLink(href: string | undefined, linkText: string, attributes: any, openInNewWindow: boolean): string;
47
+ /**
48
+ * Get a Bootstrap formatted tooltip element.
49
+ * @param {string} text - The text or HTML that should trigger the tooltip on hover.
50
+ * @param {string} tooltip - The tooltip text or HTML.
51
+ * @param {Object} options
52
+ * @param {boolean} options.icon [true] display an icon after the text
53
+ * @returns {string} A &lt;span> element to be used as a Bootstrap tooltip.
54
+ */
55
+ static getToolTip(text: string, tooltip: string, options?: {
56
+ icon: boolean;
57
+ }): string;
26
58
  static renderAttribute(n: any, v: any): string;
27
59
  static renderAttributes(attributes: any): string;
28
60
  static textElement(elName: any, text: any, attributes?: {}): string;
@@ -1,14 +1,14 @@
1
- import * as fs from "node:fs";
2
1
  import { HTML } from "./html.js";
3
- import { Taxa, COLUMNS } from "./taxa.js";
2
+ import { Taxa, TAXA_LIST_COLS } from "./taxa.js";
4
3
  import { HTMLPage } from "./htmlpage.js";
5
4
  import { PageTaxon } from "./pagetaxon.js";
6
5
  import { Config } from "./config.js";
7
6
  import { RarePlants } from "./rareplants.js";
8
7
  import { BasePageRenderer } from "./basepagerenderer.js";
8
+ import { Files } from "./files.js";
9
9
 
10
- const RPI_CESA = [ COLUMNS.COL_SPECIES, COLUMNS.COL_COMMON_NAME, COLUMNS.COL_CESA ];
11
- const RPI_COLUMNS = [ COLUMNS.COL_SPECIES, COLUMNS.COL_COMMON_NAME, COLUMNS.COL_CNPS_RANK ];
10
+ const RPI_CESA = [ TAXA_LIST_COLS.SPECIES, TAXA_LIST_COLS.COMMON_NAME, TAXA_LIST_COLS.CESA ];
11
+ const RPI_COLUMNS = [ TAXA_LIST_COLS.SPECIES_BARE, TAXA_LIST_COLS.COMMON_NAME, TAXA_LIST_COLS.CNPS_RANK ];
12
12
 
13
13
  class PageRenderer extends BasePageRenderer {
14
14
 
@@ -20,7 +20,7 @@ class PageRenderer extends BasePageRenderer {
20
20
 
21
21
  const taxa = Taxa.getTaxa();
22
22
  for ( const taxon of taxa ) {
23
- new PageTaxon( taxon ).render( outputDir );
23
+ new PageTaxon( outputDir, taxon ).render();
24
24
  }
25
25
 
26
26
  }
@@ -46,8 +46,8 @@ class PageRenderer extends BasePageRenderer {
46
46
  continue;
47
47
  }
48
48
 
49
- fs.writeFileSync( outputDir + "/calflora_" + list.filename + ".txt", calfloraTaxa.join( "\n" ) );
50
- fs.writeFileSync( outputDir + "/inat_" + list.filename + ".txt", iNatTaxa.join( "\n" ) );
49
+ Files.write( outputDir + "/calflora_" + list.filename + ".txt", calfloraTaxa.join( "\n" ) );
50
+ Files.write( outputDir + "/inat_" + list.filename + ".txt", iNatTaxa.join( "\n" ) );
51
51
 
52
52
  const cols = columns ? columns : list.columns;
53
53
  new PageTaxonList().render( outputDir, taxa, list.filename, list.name, cols );
@@ -170,7 +170,7 @@ class PageRenderer extends BasePageRenderer {
170
170
  html += "</div>";
171
171
 
172
172
  // Write lists to includes directory so it can be inserted into pages.
173
- fs.writeFileSync( outputDir + "/_includes/plantlists.html", html );
173
+ Files.write( outputDir + "/_includes/plantlists.html", html );
174
174
 
175
175
  }
176
176
 
package/lib/pagetaxon.js CHANGED
@@ -1,16 +1,16 @@
1
- import * as fs from "node:fs";
2
- import { HTML, HTML_OPTIONS } from "./html.js";
1
+ import { HTML } from "./html.js";
3
2
  import { Jepson } from "./jepson.js";
4
- import { HTMLPage } from "./htmlpage.js";
5
3
  import { Config } from "./config.js";
6
4
  import { RarePlants } from "./rareplants.js";
5
+ import { GenericPage } from "./genericpage.js";
6
+ import { Files } from "./files.js";
7
7
 
8
- class PageTaxon extends HTMLPage {
8
+ class PageTaxon extends GenericPage {
9
9
 
10
10
  #taxon;
11
11
 
12
- constructor( taxon ) {
13
- super();
12
+ constructor( outputDir, taxon ) {
13
+ super( outputDir, taxon.getName(), taxon.getBaseFileName() );
14
14
  this.#taxon = taxon;
15
15
  }
16
16
 
@@ -40,11 +40,11 @@ class PageTaxon extends HTMLPage {
40
40
  links.push(
41
41
  HTML.getLink(
42
42
  "https://www.calflora.org/entry/observ.html?track=m#srch=t&grezc=5&cols=b&lpcli=t&cc="
43
- + Config.getConfigValue( "calflora", "counties" ).join( "!" ) + "&incobs=f&taxon="
43
+ + Config.getCountyCodes().join( "!" ) + "&incobs=f&taxon="
44
44
  + this.#taxon.getCalfloraName().replaceAll( " ", "+" ),
45
45
  "Calflora",
46
46
  {},
47
- HTML_OPTIONS.OPEN_NEW
47
+ true
48
48
  )
49
49
  );
50
50
  const iNatID = this.#taxon.getINatID();
@@ -55,7 +55,7 @@ class PageTaxon extends HTMLPage {
55
55
  + "&quality_grade=research&subview=map&taxon_id=" + iNatID,
56
56
  "iNaturalist",
57
57
  {},
58
- HTML_OPTIONS.OPEN_NEW
58
+ true
59
59
  )
60
60
  );
61
61
  }
@@ -67,7 +67,7 @@ class PageTaxon extends HTMLPage {
67
67
  let html = "";
68
68
  if ( list.length > 0 ) {
69
69
  html += "<div class=\"section " + className + "\">";
70
- html += HTML.getElement( "h2", header );
70
+ html += HTML.textElement( "h2", header );
71
71
  html += "<ul>";
72
72
  html += HTML.arrayToLI( list );
73
73
  html += "</ul>";
@@ -83,17 +83,16 @@ class PageTaxon extends HTMLPage {
83
83
  }
84
84
  const ranks = [];
85
85
 
86
- ranks.push( HTML.getElement( "span", "CNPS Rare Plant Rank:", { class: "label" } )
87
- + HTML.getElement( "span", cnpsRank, { title: RarePlants.getRPIRankAndThreatDescriptions( cnpsRank ).join( "\n" ) } ) );
86
+ ranks.push( HTML.textElement( "span", "CNPS Rare Plant Rank:", { class: "label" } )
87
+ + HTML.getToolTip( cnpsRank, this.#taxon.getRPIRankAndThreatTooltip() ) );
88
88
  if ( this.#taxon.getCESA() ) {
89
- ranks.push( HTML.getElement( "span", "CESA:", { class: "label" } ) + RarePlants.getCESADescription( this.#taxon.getCESA() ) );
89
+ ranks.push( HTML.textElement( "span", "CESA:", { class: "label" } ) + RarePlants.getCESADescription( this.#taxon.getCESA() ) );
90
90
  }
91
91
 
92
- return HTML.getElement(
92
+ return HTML.wrap(
93
93
  "div",
94
94
  "<ul>" + HTML.arrayToLI( ranks ) + "</ul>",
95
- { class: "section" },
96
- HTML_OPTIONS.NO_ESCAPE
95
+ { class: "section" }
97
96
  );
98
97
  }
99
98
 
@@ -115,27 +114,24 @@ class PageTaxon extends HTMLPage {
115
114
  return this.#taxon.getSynonyms();
116
115
  }
117
116
 
118
- render( outputDir ) {
117
+ render() {
119
118
 
120
- let html = this.getFrontMatter( this.#taxon.getName() );
121
-
122
- html += HTML.getElement( "h1", this.#taxon.getName() );
119
+ let html = this.getDefaultIntro();
123
120
 
124
121
  html += "<div class=\"wrapper\">";
125
122
 
126
123
  const cn = this.#taxon.getCommonNames();
127
124
  if ( cn.length > 0 ) {
128
- html += HTML.getElement( "div", cn.join( ", " ), { class: "section common-names" } );
125
+ html += HTML.textElement( "div", cn.join( ", " ), { class: "section common-names" } );
129
126
  }
130
127
 
131
- html += HTML.getElement( "div", this.#taxon.getStatusDescription(), { class: "section native-status" } );
128
+ html += HTML.textElement( "div", this.#taxon.getStatusDescription(), { class: "section native-status" } );
132
129
 
133
130
  const family = this.#taxon.getFamily();
134
- html += HTML.getElement(
131
+ html += HTML.wrap(
135
132
  "div",
136
- HTML.getElement( "span", "Family:", { class: "label" } ) + HTML.getLink( "./" + family.getFileName(), family.getName() ),
137
- { class: "section" },
138
- HTML_OPTIONS.NO_ESCAPE
133
+ HTML.textElement( "span", "Family:", { class: "label" } ) + HTML.getLink( "./" + family.getFileName(), family.getName() ),
134
+ { class: "section" }
139
135
  );
140
136
 
141
137
  html += this.#getRarityInfo();
@@ -143,8 +139,8 @@ class PageTaxon extends HTMLPage {
143
139
  html += "</div>";
144
140
 
145
141
  const introName = "intros/" + this.#taxon.getFileName( "md" );
146
- if ( fs.existsSync( "./jekyll/_includes/" + introName ) ) {
147
- html += HTML.getElement(
142
+ if ( Files.exists( "./jekyll/_includes/" + introName ) ) {
143
+ html += HTML.wrap(
148
144
  "div",
149
145
  "{% capture my_include %}{% include " + introName + "%}{% endcapture %}{{ my_include | markdownify }}",
150
146
  { class: "section" }
@@ -158,7 +154,7 @@ class PageTaxon extends HTMLPage {
158
154
  html += this.#getListSectionHTML( this.#getSynonyms(), "Synonyms", "synonyms" );
159
155
  html += "</div>";
160
156
 
161
- this.writeFile( outputDir, this.#taxon.getFileName(), html );
157
+ this.writeFile( html );
162
158
 
163
159
  }
164
160
 
package/lib/taxa.js CHANGED
@@ -4,14 +4,30 @@ import { HTML } from "./html.js";
4
4
  import { CSV } from "./csv.js";
5
5
  import { RarePlants } from "./rareplants.js";
6
6
 
7
- const COLUMNS = {
8
- COL_CESA: "CESA",
9
- COL_COMMON_NAME: "Common Name",
10
- COL_CNPS_RANK: "CNPS Rank",
11
- COL_SPECIES: "Species",
7
+ const TAXA_LIST_COLS = {
8
+ CESA: {
9
+ title: "CESA",
10
+ data: ( t ) => RarePlants.getCESADescription( t.getCESA() )
11
+ },
12
+ COMMON_NAME: {
13
+ title: "Common Name",
14
+ data: ( t ) => t.getCommonNames().join( ", " )
15
+ },
16
+ CNPS_RANK: {
17
+ title: "CNPS Rank",
18
+ data: ( t ) => HTML.getToolTip( HTML.textElement( "span", t.getRPIRankAndThreat() ), t.getRPIRankAndThreatTooltip() )
19
+ },
20
+ SPECIES: {
21
+ title: "Species",
22
+ data: ( t ) => t.getHTMLLink( true, true )
23
+ },
24
+ SPECIES_BARE: {
25
+ title: "Species",
26
+ data: ( t ) => t.getHTMLLink( true, false )
27
+ },
12
28
  };
13
29
 
14
- const DEFAULT_COLUMNS = [ COLUMNS.COL_SPECIES, COLUMNS.COL_COMMON_NAME ];
30
+ const DEFAULT_COLUMNS = [ TAXA_LIST_COLS.SPECIES, TAXA_LIST_COLS.COMMON_NAME ];
15
31
 
16
32
  class Taxa {
17
33
 
@@ -20,36 +36,22 @@ class Taxa {
20
36
 
21
37
  static getHTMLTable( taxa, columns = DEFAULT_COLUMNS ) {
22
38
 
23
- let includeRPI = true;
24
-
25
39
  let html = "<table><thead>";
26
- for ( const column of columns ) {
27
- html += HTML.textElement( "th", column );
28
- if ( column === COLUMNS.COL_CNPS_RANK ) {
29
- // Don't show rarity with species link if CNPS Rank column is included.
30
- includeRPI = false;
31
- }
40
+ for ( const col of columns ) {
41
+ const className = col.class;
42
+ const atts = className ? { class: className } : {};
43
+ html += HTML.textElement( "th", col.title, atts );
32
44
  }
33
45
  html += "</thead>";
34
46
  html += "<tbody>";
35
47
 
36
48
  for ( const taxon of taxa ) {
37
49
  html += "<tr>";
38
- for ( const column of columns ) {
39
- switch ( column ) {
40
- case COLUMNS.COL_CESA:
41
- html += HTML.textElement( "td", RarePlants.getCESADescription( taxon.getCESA() ) );
42
- break;
43
- case COLUMNS.COL_COMMON_NAME:
44
- html += HTML.textElement( "td", taxon.getCommonNames().join( ", " ) );
45
- break;
46
- case COLUMNS.COL_CNPS_RANK:
47
- html += HTML.wrap( "td", HTML.wrap( "span", taxon.getRPIRankAndThreat(), taxon.getRPIRankAndThreatTooltip( {} ) ) );
48
- break;
49
- case COLUMNS.COL_SPECIES:
50
- html += HTML.wrap( "td", taxon.getHTMLLink( true, includeRPI ) );
51
- break;
52
- }
50
+ for ( const col of columns ) {
51
+ const data = col.data( taxon );
52
+ const className = col.class;
53
+ const atts = className ? { class: className } : {};
54
+ html += HTML.wrap( "td", data, atts );
53
55
  }
54
56
  html += "</tr>";
55
57
  }
@@ -120,4 +122,4 @@ class Taxa {
120
122
 
121
123
  }
122
124
 
123
- export { Taxa, COLUMNS };
125
+ export { Taxa, TAXA_LIST_COLS };
package/lib/taxon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Config } from "./config.js";
2
2
  import { Genera } from "./genera.js";
3
- import { HTML, HTML_OPTIONS } from "./html.js";
3
+ import { HTML } from "./html.js";
4
4
  import { RarePlants } from "./rareplants.js";
5
5
 
6
6
  class Taxon {
@@ -72,7 +72,7 @@ class Taxon {
72
72
  "https://www.calflora.org/app/taxon?crn=" + calfloraID,
73
73
  "Calflora",
74
74
  {},
75
- HTML_OPTIONS.OPEN_NEW
75
+ true
76
76
  );
77
77
  }
78
78
 
@@ -107,10 +107,11 @@ class Taxon {
107
107
  className = "rare";
108
108
  }
109
109
  const attributes = { class: className };
110
+ const link = HTML.wrap( "span", HTML.getLink( href, this.getName() ), attributes );
110
111
  if ( className === "rare" ) {
111
- this.getRPIRankAndThreatTooltip( attributes );
112
+ return HTML.getToolTip( link, this.getRPIRankAndThreatTooltip(), { icon: false } );
112
113
  }
113
- return HTML.wrap( "span", HTML.getLink( href, this.getName() ), attributes );
114
+ return link;
114
115
  }
115
116
 
116
117
  getINatID() {
@@ -127,7 +128,7 @@ class Taxon {
127
128
  if ( !iNatID ) {
128
129
  return "";
129
130
  }
130
- const link = HTML.getLink( "https://www.inaturalist.org/taxa/" + iNatID, "iNaturalist", {}, HTML_OPTIONS.OPEN_NEW );
131
+ const link = HTML.getLink( "https://www.inaturalist.org/taxa/" + iNatID, "iNaturalist", {}, true );
131
132
  return this.#iNatSyn ? ( link + " (" + this.#iNatSyn + ")" ) : link;
132
133
  }
133
134
 
@@ -154,9 +155,8 @@ class Taxon {
154
155
  return this.#rankRPI;
155
156
  }
156
157
 
157
- getRPIRankAndThreatTooltip( attributes ) {
158
- attributes[ "title" ] = RarePlants.getRPIRankAndThreatDescriptions( this.getRPIRankAndThreat() ).join( "\n" );
159
- return attributes;
158
+ getRPIRankAndThreatTooltip() {
159
+ return RarePlants.getRPIRankAndThreatDescriptions( this.getRPIRankAndThreat() ).join( "<br>" );
160
160
  }
161
161
 
162
162
  getRPITaxonLink() {
@@ -164,7 +164,7 @@ class Taxon {
164
164
  if ( !rpiID ) {
165
165
  return "";
166
166
  }
167
- const link = HTML.getLink( "https://rareplants.cnps.org/Plants/Details/" + rpiID, "CNPS Rare Plant Inventory", {}, HTML_OPTIONS.OPEN_NEW );
167
+ const link = HTML.getLink( "https://rareplants.cnps.org/Plants/Details/" + rpiID, "CNPS Rare Plant Inventory", {}, true );
168
168
  return link;
169
169
  }
170
170
 
@@ -192,6 +192,10 @@ class Taxon {
192
192
  return this.#status === "N" || this.#status === "NC";
193
193
  }
194
194
 
195
+ /**
196
+ * Determine whether a species is a local native.
197
+ * @returns {boolean} true if taxon is a local native; false if not a CA native, or native elsewhere in CA.
198
+ */
195
199
  isNative() {
196
200
  return this.#status === "N";
197
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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": {
File without changes
@@ -1,7 +0,0 @@
1
- ---
2
- title: Plant Lists
3
- ---
4
- # Plant Lists
5
-
6
- Species:
7
- {%include plantlists.html%}