@ca-plant-list/ca-plant-list 0.0.0
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/README.md +1 -0
- package/build-site.js +19 -0
- package/data/families.json +199 -0
- package/data/genera.json +5562 -0
- package/jekyll/_layouts/html.html +49 -0
- package/jekyll/assets/css/main.css +64 -0
- package/jekyll/assets/js/common_name_search.js +87 -0
- package/jekyll/common_name_search.html +18 -0
- package/jekyll/index.md +3 -0
- package/jekyll/index_lists.md +7 -0
- package/lib/config.js +31 -0
- package/lib/csv.js +41 -0
- package/lib/dataloader.js +19 -0
- package/lib/errorlog.js +17 -0
- package/lib/families.js +129 -0
- package/lib/genera.js +55 -0
- package/lib/html.js +54 -0
- package/lib/htmlpage.js +18 -0
- package/lib/jepson.js +18 -0
- package/lib/pagerenderer.js +120 -0
- package/lib/pagetaxon.js +154 -0
- package/lib/taxa.js +86 -0
- package/lib/taxon.js +116 -0
- package/package.json +16 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
|
4
|
+
<head>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
8
|
+
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
|
9
|
+
<title>{{page.title}}</title>
|
10
|
+
<link href="{{site.baseurl}}/assets/css/main.css" rel="stylesheet">
|
11
|
+
</head>
|
12
|
+
|
13
|
+
<body>
|
14
|
+
|
15
|
+
<nav class="navbar navbar-expand-md">
|
16
|
+
<div class="container-xxl px-5">
|
17
|
+
<button id="hamburger" class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
18
|
+
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
|
19
|
+
aria-label="Toggle navigation">
|
20
|
+
<span class="navbar-toggler-icon"></span>
|
21
|
+
</button>
|
22
|
+
<a class="navbar-brand" href="{{'/' | relative_url}}">{{site.navbar-name}}</a>
|
23
|
+
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
24
|
+
<ul class="navbar-nav me-auto mb-lg-0">
|
25
|
+
<li class="nav-item">
|
26
|
+
<a class="nav-link" href="{{site.baseurl}}/index_lists.html">Plant Lists</a>
|
27
|
+
</li>
|
28
|
+
<li class="nav-item">
|
29
|
+
<a class="nav-link" href="{{site.baseurl}}/common_name_search.html">Common Name Search</a>
|
30
|
+
</li>
|
31
|
+
</ul>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</nav>
|
35
|
+
|
36
|
+
<div class="container-xxl px-5">
|
37
|
+
{{content}}
|
38
|
+
</div>
|
39
|
+
|
40
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"
|
41
|
+
integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa"
|
42
|
+
crossorigin="anonymous"></script>
|
43
|
+
|
44
|
+
{%if page.js%}
|
45
|
+
<script src="{{site.baseurl}}/assets/js/{{page.js}}" type="module"></script>{%endif%}
|
46
|
+
|
47
|
+
</body>
|
48
|
+
|
49
|
+
</html>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
/* Defaults */
|
2
|
+
|
3
|
+
:root {
|
4
|
+
--bs-body-bg: #ECF87F;
|
5
|
+
}
|
6
|
+
|
7
|
+
.navbar-nav {
|
8
|
+
--bs-nav-link-hover-color: rgba(255, 255, 255, .5);
|
9
|
+
}
|
10
|
+
|
11
|
+
.navbar {
|
12
|
+
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='white' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
13
|
+
--bs-navbar-color: white;
|
14
|
+
--bs-navbar-brand-hover-color: rgba(255, 255, 255, .5);
|
15
|
+
--bs-navbar-brand-color: white;
|
16
|
+
background-color: #3D550C;
|
17
|
+
}
|
18
|
+
|
19
|
+
td {
|
20
|
+
border: solid 1px;
|
21
|
+
vertical-align: top;
|
22
|
+
}
|
23
|
+
|
24
|
+
th {
|
25
|
+
vertical-align: bottom;
|
26
|
+
}
|
27
|
+
|
28
|
+
/* Lists */
|
29
|
+
|
30
|
+
a.native {
|
31
|
+
font-weight: bold;
|
32
|
+
font-style: italic;
|
33
|
+
}
|
34
|
+
|
35
|
+
/* Taxon Page */
|
36
|
+
|
37
|
+
.common-names {
|
38
|
+
font-size: larger;
|
39
|
+
font-weight: bold;
|
40
|
+
}
|
41
|
+
|
42
|
+
div.wrapper {
|
43
|
+
display: flex;
|
44
|
+
flex-wrap: wrap;
|
45
|
+
column-gap: 4rem;
|
46
|
+
}
|
47
|
+
|
48
|
+
div.section {
|
49
|
+
margin-bottom: .5rem;
|
50
|
+
;
|
51
|
+
}
|
52
|
+
|
53
|
+
div.section h2 {
|
54
|
+
font-size: 1.25rem;
|
55
|
+
}
|
56
|
+
|
57
|
+
div.section ul {
|
58
|
+
margin: 0;
|
59
|
+
padding: 0;
|
60
|
+
}
|
61
|
+
|
62
|
+
div.section li {
|
63
|
+
display: block;
|
64
|
+
}
|
@@ -0,0 +1,87 @@
|
|
1
|
+
const MIN_LEN = 2;
|
2
|
+
const MAX_RESULTS = 50;
|
3
|
+
|
4
|
+
class Search {
|
5
|
+
|
6
|
+
static #debounceTimer;
|
7
|
+
|
8
|
+
static #debounce( func ) {
|
9
|
+
clearTimeout( this.#debounceTimer );
|
10
|
+
this.#debounceTimer = setTimeout( func, 500 );
|
11
|
+
}
|
12
|
+
|
13
|
+
static #doSearch() {
|
14
|
+
|
15
|
+
Search.#debounceTimer = undefined;
|
16
|
+
|
17
|
+
const value = document.getElementById( "common-name" ).value.toLowerCase();
|
18
|
+
|
19
|
+
const matches = [];
|
20
|
+
const shouldSearch = ( value.length >= MIN_LEN );
|
21
|
+
|
22
|
+
if ( shouldSearch ) {
|
23
|
+
// eslint-disable-next-line no-undef
|
24
|
+
for ( const key of Object.keys( COMMON_NAMES ) ) {
|
25
|
+
if ( key.includes( value ) ) {
|
26
|
+
matches.push( key );
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
const eBody = document.createElement( "tbody" );
|
32
|
+
if ( matches.length <= MAX_RESULTS ) {
|
33
|
+
for ( const match of matches ) {
|
34
|
+
// eslint-disable-next-line no-undef
|
35
|
+
const data = COMMON_NAMES[ match ];
|
36
|
+
for ( const name of data.names ) {
|
37
|
+
const tr = document.createElement( "tr" );
|
38
|
+
const td1 = document.createElement( "td" );
|
39
|
+
const td2 = document.createElement( "td" );
|
40
|
+
const link = document.createElement( "a" );
|
41
|
+
td1.textContent = data.cn;
|
42
|
+
link.setAttribute( "href", "./" + name.replaceAll( ".", "" ).replaceAll( " ", "-" ) + ".html" );
|
43
|
+
link.textContent = name;
|
44
|
+
td2.appendChild( link );
|
45
|
+
tr.appendChild( td1 );
|
46
|
+
tr.appendChild( td2 );
|
47
|
+
eBody.appendChild( tr );
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
// Delete current message
|
53
|
+
const eMessage = document.getElementById( "message" );
|
54
|
+
if ( eMessage.firstChild ) {
|
55
|
+
eMessage.removeChild( eMessage.firstChild );
|
56
|
+
}
|
57
|
+
if ( shouldSearch ) {
|
58
|
+
if ( matches.length === 0 ) {
|
59
|
+
eMessage.textContent = "Nothing found.";
|
60
|
+
}
|
61
|
+
if ( matches.length > MAX_RESULTS ) {
|
62
|
+
eMessage.textContent = "Too many results.";
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
// Delete current results
|
67
|
+
const eTable = document.getElementById( "results" );
|
68
|
+
if ( eTable.firstChild ) {
|
69
|
+
eTable.removeChild( eTable.firstChild );
|
70
|
+
}
|
71
|
+
|
72
|
+
eTable.appendChild( eBody );
|
73
|
+
}
|
74
|
+
|
75
|
+
static #handleChange() {
|
76
|
+
this.#debounce( Search.#doSearch );
|
77
|
+
}
|
78
|
+
|
79
|
+
static init() {
|
80
|
+
const eName = document.getElementById( "common-name" );
|
81
|
+
eName.focus();
|
82
|
+
eName.oninput = ( ev ) => { return this.#handleChange( ev ); };
|
83
|
+
}
|
84
|
+
|
85
|
+
}
|
86
|
+
|
87
|
+
Search.init();
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
title: Common Name Search
|
3
|
+
js: common_name_search.js
|
4
|
+
---
|
5
|
+
<h1>Search for Common Name</h1>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
const COMMON_NAMES = {% include common_names.json%};
|
9
|
+
</script>
|
10
|
+
|
11
|
+
<form>
|
12
|
+
<input type="text" id="common-name">
|
13
|
+
</form>
|
14
|
+
|
15
|
+
<div class="section" id="message"></div>
|
16
|
+
|
17
|
+
<table id="results">
|
18
|
+
</table>
|
package/jekyll/index.md
ADDED
package/lib/config.js
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
|
3
|
+
class Config {
|
4
|
+
|
5
|
+
static #config = {};
|
6
|
+
|
7
|
+
static getConfigValue( prefix, name, dflt ) {
|
8
|
+
const obj = this.#config[ prefix ];
|
9
|
+
if ( obj ) {
|
10
|
+
if ( Object.hasOwn( obj, name ) ) {
|
11
|
+
return obj[ name ];
|
12
|
+
}
|
13
|
+
}
|
14
|
+
return dflt;
|
15
|
+
}
|
16
|
+
|
17
|
+
static getLabel( name, dflt ) {
|
18
|
+
return this.getConfigValue( "labels", name, dflt );
|
19
|
+
}
|
20
|
+
|
21
|
+
static init( dir ) {
|
22
|
+
try {
|
23
|
+
this.#config = JSON.parse( fs.readFileSync( dir + "/config.json" ) );
|
24
|
+
} catch ( e ) {
|
25
|
+
console.log( e );
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
}
|
30
|
+
|
31
|
+
export { Config };
|
package/lib/csv.js
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
import { parse } from "csv-parse/sync";
|
3
|
+
import path from "node:path";
|
4
|
+
|
5
|
+
class CSV {
|
6
|
+
|
7
|
+
static getMap( dir, fileName ) {
|
8
|
+
|
9
|
+
let headers;
|
10
|
+
|
11
|
+
function setHeaders( h ) {
|
12
|
+
headers = h;
|
13
|
+
return h;
|
14
|
+
}
|
15
|
+
|
16
|
+
const csv = this.parseFile( dir, fileName, setHeaders );
|
17
|
+
const map = {};
|
18
|
+
for ( const row of csv ) {
|
19
|
+
map[ row[ headers[ 0 ] ] ] = row[ headers[ 1 ] ];
|
20
|
+
}
|
21
|
+
|
22
|
+
return map;
|
23
|
+
}
|
24
|
+
|
25
|
+
static parseFile( dir, fileName, columns = true, delimiter ) {
|
26
|
+
const content = fs.readFileSync( dir + "/" + fileName );
|
27
|
+
|
28
|
+
const options = { relax_column_count_less: true };
|
29
|
+
options.columns = columns;
|
30
|
+
if ( path.extname( fileName ) === ".tsv" ) {
|
31
|
+
options.delimiter = "\t";
|
32
|
+
options.quote = false;
|
33
|
+
} else {
|
34
|
+
options.delimiter = delimiter ? delimiter : ",";
|
35
|
+
}
|
36
|
+
return parse( content, options );
|
37
|
+
}
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
export { CSV };
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { Taxa } from "./taxa.js";
|
2
|
+
import { Genera } from "./genera.js";
|
3
|
+
import { Families } from "./families.js";
|
4
|
+
|
5
|
+
class DataLoader {
|
6
|
+
|
7
|
+
static load( taxaDir, dataDir ) {
|
8
|
+
|
9
|
+
console.log( "loading data" );
|
10
|
+
|
11
|
+
Families.init( dataDir );
|
12
|
+
Genera.init( dataDir );
|
13
|
+
Taxa.init( taxaDir );
|
14
|
+
|
15
|
+
}
|
16
|
+
|
17
|
+
}
|
18
|
+
|
19
|
+
export { DataLoader };
|
package/lib/errorlog.js
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
|
3
|
+
class ErrorLog {
|
4
|
+
|
5
|
+
static #errors = [];
|
6
|
+
|
7
|
+
static log( ...args ) {
|
8
|
+
this.#errors.push( args.join( "\t" ) );
|
9
|
+
}
|
10
|
+
|
11
|
+
static write( fileName ) {
|
12
|
+
fs.writeFileSync( fileName, this.#errors.join( "\n" ) );
|
13
|
+
}
|
14
|
+
|
15
|
+
}
|
16
|
+
|
17
|
+
export { ErrorLog };
|
package/lib/families.js
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
import { HTMLPage } from "./htmlpage.js";
|
3
|
+
import { HTML, HTML_OPTIONS } from "./html.js";
|
4
|
+
import { Jepson } from "./jepson.js";
|
5
|
+
import { Taxa } from "./taxa.js";
|
6
|
+
|
7
|
+
class Families {
|
8
|
+
|
9
|
+
static #families;
|
10
|
+
|
11
|
+
static getFamily( familyName ) {
|
12
|
+
return this.#families[ familyName ];
|
13
|
+
}
|
14
|
+
|
15
|
+
static init( dataDir ) {
|
16
|
+
this.#families = JSON.parse( fs.readFileSync( dataDir + "/families.json" ) );
|
17
|
+
for ( const [ k, v ] of Object.entries( this.#families ) ) {
|
18
|
+
this.#families[ k ] = new Family( k, { id: v } );
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
static renderPages( outputDir ) {
|
23
|
+
new PageFamilyList( Object.values( this.#families ) ).render( outputDir );
|
24
|
+
|
25
|
+
for ( const family of Object.values( this.#families ) ) {
|
26
|
+
if ( family.getTaxa() ) {
|
27
|
+
new PageFamily( family ).render( outputDir );
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
}
|
33
|
+
|
34
|
+
class PageFamilyList extends HTMLPage {
|
35
|
+
|
36
|
+
#families;
|
37
|
+
|
38
|
+
constructor( families ) {
|
39
|
+
super();
|
40
|
+
this.#families = families;
|
41
|
+
}
|
42
|
+
|
43
|
+
render( outputDir ) {
|
44
|
+
|
45
|
+
const title = "Families";
|
46
|
+
|
47
|
+
let html = this.getFrontMatter( title );
|
48
|
+
|
49
|
+
html += HTML.getElement( "h1", title );
|
50
|
+
|
51
|
+
html += "<ul>";
|
52
|
+
for ( const family of this.#families ) {
|
53
|
+
const taxa = family.getTaxa();
|
54
|
+
if ( taxa ) {
|
55
|
+
const link = HTML.getLink( "./" + family.getFileName(), family.getName() );
|
56
|
+
html += "<li>" + link + " (" + taxa.length + ")" + "</li>";
|
57
|
+
}
|
58
|
+
}
|
59
|
+
html += "</ul>";
|
60
|
+
|
61
|
+
this.writeFile( outputDir, "list_families.html", html );
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
class PageFamily extends HTMLPage {
|
66
|
+
|
67
|
+
#family;
|
68
|
+
|
69
|
+
constructor( family ) {
|
70
|
+
super();
|
71
|
+
this.#family = family;
|
72
|
+
}
|
73
|
+
|
74
|
+
render( outputDir ) {
|
75
|
+
|
76
|
+
let html = this.getFrontMatter( this.#family.getName() );
|
77
|
+
|
78
|
+
html += HTML.getElement( "h1", this.#family.getName() );
|
79
|
+
|
80
|
+
html += HTML.getElement(
|
81
|
+
"div",
|
82
|
+
Jepson.getEFloraLink( this.#family.getJepsonID() ),
|
83
|
+
{ class: "section" },
|
84
|
+
HTML_OPTIONS.NO_ESCAPE
|
85
|
+
);
|
86
|
+
|
87
|
+
html += Taxa.getHTMLTable( this.#family.getTaxa() );
|
88
|
+
|
89
|
+
this.writeFile( outputDir, this.#family.getFileName(), html );
|
90
|
+
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
class Family {
|
95
|
+
|
96
|
+
#name;
|
97
|
+
#data;
|
98
|
+
|
99
|
+
constructor( name, data ) {
|
100
|
+
this.#name = name;
|
101
|
+
this.#data = data;
|
102
|
+
}
|
103
|
+
|
104
|
+
addTaxon( taxon ) {
|
105
|
+
if ( !this.#data.taxa ) {
|
106
|
+
this.#data.taxa = [];
|
107
|
+
}
|
108
|
+
this.#data.taxa.push( taxon );
|
109
|
+
}
|
110
|
+
|
111
|
+
getFileName( ext = "html" ) {
|
112
|
+
return this.getName() + "." + ext;
|
113
|
+
}
|
114
|
+
|
115
|
+
getJepsonID() {
|
116
|
+
return this.#data.id;
|
117
|
+
}
|
118
|
+
|
119
|
+
getName() {
|
120
|
+
return this.#name;
|
121
|
+
}
|
122
|
+
|
123
|
+
getTaxa() {
|
124
|
+
return this.#data.taxa;
|
125
|
+
}
|
126
|
+
|
127
|
+
}
|
128
|
+
|
129
|
+
export { Families };
|
package/lib/genera.js
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
import { Families } from "./families.js";
|
3
|
+
|
4
|
+
class Genera {
|
5
|
+
|
6
|
+
static #genera;
|
7
|
+
|
8
|
+
static addTaxon( taxon ) {
|
9
|
+
|
10
|
+
const genusName = taxon.getGenusName();
|
11
|
+
const genusData = this.#genera[ genusName ];
|
12
|
+
if ( genusData.taxa === undefined ) {
|
13
|
+
genusData.taxa = [];
|
14
|
+
}
|
15
|
+
genusData.taxa.push( taxon );
|
16
|
+
|
17
|
+
const family = this.getFamily( genusName );
|
18
|
+
if ( !family ) {
|
19
|
+
console.log( taxon.getName() + " genus/family not found" );
|
20
|
+
return;
|
21
|
+
}
|
22
|
+
family.addTaxon( taxon );
|
23
|
+
}
|
24
|
+
|
25
|
+
static getGenus( genusName ) {
|
26
|
+
return new Genus( this.#genera[ genusName ] );
|
27
|
+
}
|
28
|
+
|
29
|
+
static getFamily( genusName ) {
|
30
|
+
const genus = this.#genera[ genusName ];
|
31
|
+
if ( genus ) {
|
32
|
+
return Families.getFamily( genus.family );
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
static init( dataDir ) {
|
37
|
+
this.#genera = JSON.parse( fs.readFileSync( dataDir + "/genera.json" ) );
|
38
|
+
}
|
39
|
+
|
40
|
+
}
|
41
|
+
|
42
|
+
class Genus {
|
43
|
+
|
44
|
+
#data;
|
45
|
+
|
46
|
+
constructor( data ) {
|
47
|
+
this.#data = data;
|
48
|
+
}
|
49
|
+
|
50
|
+
getTaxa() {
|
51
|
+
return this.#data.taxa;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
export { Genera };
|
package/lib/html.js
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
const HTML_OPTIONS = {
|
2
|
+
OPEN_NEW: 1,
|
3
|
+
NO_ESCAPE: 2,
|
4
|
+
};
|
5
|
+
|
6
|
+
class HTML {
|
7
|
+
|
8
|
+
static arrayToLI( items ) {
|
9
|
+
return items.reduce( ( itemHTML, currVal ) => itemHTML + "<li>" + currVal + "</li>", "" );
|
10
|
+
}
|
11
|
+
|
12
|
+
static escapeAttribute( value ) {
|
13
|
+
return value.replaceAll( "\"", """ );
|
14
|
+
}
|
15
|
+
|
16
|
+
static escapeText( text ) {
|
17
|
+
return text.replaceAll( "&", "&" ).replaceAll( "<", "<" ).replaceAll( ">", ">" );
|
18
|
+
}
|
19
|
+
|
20
|
+
static getElement( elName, text, attributes = {}, options = 0 ) {
|
21
|
+
let html = "<" + elName;
|
22
|
+
html += this.renderAttributes( attributes );
|
23
|
+
if ( !( options & HTML_OPTIONS.NO_ESCAPE ) ) {
|
24
|
+
text = this.escapeText( text );
|
25
|
+
}
|
26
|
+
html += ">" + text + "</" + elName + ">";
|
27
|
+
return html;
|
28
|
+
|
29
|
+
}
|
30
|
+
|
31
|
+
static getLink( href, linkText, attributes = {}, options = 0 ) {
|
32
|
+
let html = "<a href=\"" + this.escapeAttribute( href ) + "\"";
|
33
|
+
html += this.renderAttributes( attributes );
|
34
|
+
if ( options & HTML_OPTIONS.OPEN_NEW ) {
|
35
|
+
html += this.renderAttribute( "target", "_blank" );
|
36
|
+
}
|
37
|
+
return html + ">" + this.escapeText( linkText ) + "</a >";
|
38
|
+
}
|
39
|
+
|
40
|
+
static renderAttribute( n, v ) {
|
41
|
+
return " " + n + "=\"" + this.escapeAttribute( v ) + "\"";
|
42
|
+
}
|
43
|
+
|
44
|
+
static renderAttributes( attributes ) {
|
45
|
+
let html = "";
|
46
|
+
for ( const [ k, v ] of Object.entries( attributes ) ) {
|
47
|
+
html += this.renderAttribute( k, v );
|
48
|
+
}
|
49
|
+
return html;
|
50
|
+
}
|
51
|
+
|
52
|
+
}
|
53
|
+
|
54
|
+
export { HTML, HTML_OPTIONS };
|
package/lib/htmlpage.js
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
|
3
|
+
class HTMLPage {
|
4
|
+
|
5
|
+
getFrontMatter( title, js ) {
|
6
|
+
return "---\n"
|
7
|
+
+ "title: \"" + title + "\"\n"
|
8
|
+
+ ( js ? ( "js: " + js + "\n" ) : "" )
|
9
|
+
+ "---\n";
|
10
|
+
}
|
11
|
+
|
12
|
+
writeFile( outputDir, fileName, html ) {
|
13
|
+
fs.writeFileSync( outputDir + "/" + fileName, html );
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
17
|
+
|
18
|
+
export { HTMLPage };
|
package/lib/jepson.js
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
import { HTML, HTML_OPTIONS } from "./html.js";
|
2
|
+
|
3
|
+
class Jepson {
|
4
|
+
|
5
|
+
static getEFloraLink( id ) {
|
6
|
+
|
7
|
+
return HTML.getLink(
|
8
|
+
"https://ucjeps.berkeley.edu/eflora/eflora_display.php?tid=" + id,
|
9
|
+
"Jepson eFlora",
|
10
|
+
{},
|
11
|
+
HTML_OPTIONS.OPEN_NEW
|
12
|
+
);
|
13
|
+
|
14
|
+
}
|
15
|
+
|
16
|
+
}
|
17
|
+
|
18
|
+
export { Jepson };
|