@ca-plant-list/ca-plant-list 0.1.2 → 0.1.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/jekyll/_includes/plantlists.html +0 -0
- package/jekyll/_layouts/html.html +1 -1
- package/jekyll/assets/css/main.css +6 -0
- package/jekyll/assets/js/name_search.js +156 -0
- package/jekyll/name_search.html +18 -0
- package/lib/basepagerenderer.js +46 -0
- package/lib/exceptions.js +4 -2
- package/lib/files.js +17 -0
- package/lib/index.js +5 -0
- package/lib/pagerenderer.js +3 -32
- package/package.json +5 -4
- package/jekyll/assets/js/common_name_search.js +0 -87
- package/jekyll/common_name_search.html +0 -18
File without changes
|
@@ -29,7 +29,7 @@
|
|
29
29
|
<a class="nav-link" href="{{site.baseurl}}/rare_plants.html">Rare Plants</a>
|
30
30
|
</li>
|
31
31
|
<li class="nav-item">
|
32
|
-
<a class="nav-link" href="{{site.baseurl}}/
|
32
|
+
<a class="nav-link" href="{{site.baseurl}}/name_search.html">Name Search</a>
|
33
33
|
</li>
|
34
34
|
{%include menu_extra.html %}
|
35
35
|
</ul>
|
@@ -28,6 +28,7 @@ span.label {
|
|
28
28
|
|
29
29
|
td {
|
30
30
|
border: solid 1px;
|
31
|
+
padding: 0 .5rem;
|
31
32
|
vertical-align: top;
|
32
33
|
}
|
33
34
|
|
@@ -85,4 +86,9 @@ div.section li {
|
|
85
86
|
|
86
87
|
div.section ul.indent {
|
87
88
|
padding-left: 1rem;
|
89
|
+
}
|
90
|
+
|
91
|
+
/* Forms */
|
92
|
+
input {
|
93
|
+
margin-right: .5em;
|
88
94
|
}
|
@@ -0,0 +1,156 @@
|
|
1
|
+
const MIN_LEN = 2;
|
2
|
+
const MAX_RESULTS = 50;
|
3
|
+
|
4
|
+
class Search {
|
5
|
+
|
6
|
+
static #debounceTimer;
|
7
|
+
static #searchData;
|
8
|
+
|
9
|
+
static #debounce( func ) {
|
10
|
+
clearTimeout( this.#debounceTimer );
|
11
|
+
this.#debounceTimer = setTimeout( func, 500 );
|
12
|
+
}
|
13
|
+
|
14
|
+
static #doSearch() {
|
15
|
+
|
16
|
+
function matchTaxon( taxon, value ) {
|
17
|
+
|
18
|
+
function matchSynonyms( syns, value ) {
|
19
|
+
const matchedIndexes = [];
|
20
|
+
if ( syns ) {
|
21
|
+
for ( let index = 0; index < syns.length; index++ ) {
|
22
|
+
if ( syns[ index ].includes( value ) ) {
|
23
|
+
matchedIndexes.push( index );
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
return matchedIndexes;
|
28
|
+
}
|
29
|
+
|
30
|
+
const rawData = taxon[ 0 ];
|
31
|
+
const name = taxon[ 1 ];
|
32
|
+
const cn = taxon[ 2 ];
|
33
|
+
const syns = matchSynonyms( taxon[ 3 ], value );
|
34
|
+
if ( syns.length > 0 ) {
|
35
|
+
// Include any matching synonyms.
|
36
|
+
for ( const index of syns ) {
|
37
|
+
matches.push( [ rawData[ 0 ], rawData[ 1 ], rawData[ 2 ][ index ] ] );
|
38
|
+
}
|
39
|
+
} else {
|
40
|
+
// No synonyms match; see if the scientific or common names match.
|
41
|
+
const namesMatch = name.includes( value ) || ( cn && cn.includes( value ) );
|
42
|
+
if ( namesMatch ) {
|
43
|
+
matches.push( [ rawData[ 0 ], rawData[ 1 ] ] );
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
}
|
48
|
+
|
49
|
+
Search.#debounceTimer = undefined;
|
50
|
+
|
51
|
+
const value = document.getElementById( "name" ).value.toLowerCase();
|
52
|
+
|
53
|
+
const matches = [];
|
54
|
+
const shouldSearch = ( value.length >= MIN_LEN );
|
55
|
+
|
56
|
+
if ( shouldSearch ) {
|
57
|
+
|
58
|
+
// If the search data is not done generating, try again later.
|
59
|
+
if ( !Search.#searchData ) {
|
60
|
+
this.#debounce( Search.#doSearch );
|
61
|
+
}
|
62
|
+
|
63
|
+
for ( const taxon of Search.#searchData ) {
|
64
|
+
matchTaxon( taxon, value );
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
const eBody = document.createElement( "tbody" );
|
69
|
+
if ( matches.length <= MAX_RESULTS ) {
|
70
|
+
for ( const match of matches ) {
|
71
|
+
|
72
|
+
const tr = document.createElement( "tr" );
|
73
|
+
|
74
|
+
// Scientific name.
|
75
|
+
const name = match[ 0 ];
|
76
|
+
const syn = match[ 2 ];
|
77
|
+
const td1 = document.createElement( "td" );
|
78
|
+
const link = document.createElement( "a" );
|
79
|
+
link.setAttribute( "href", "./" + name.replaceAll( ".", "" ).replaceAll( " ", "-" ) + ".html" );
|
80
|
+
link.textContent = name;
|
81
|
+
td1.appendChild( link );
|
82
|
+
if ( syn ) {
|
83
|
+
td1.appendChild( document.createTextNode( " (" + syn + ")" ) );
|
84
|
+
}
|
85
|
+
tr.appendChild( td1 );
|
86
|
+
|
87
|
+
const cn = match[ 1 ];
|
88
|
+
const td2 = document.createElement( "td" );
|
89
|
+
if ( cn ) {
|
90
|
+
td2.textContent = cn;
|
91
|
+
}
|
92
|
+
tr.appendChild( td2 );
|
93
|
+
|
94
|
+
eBody.appendChild( tr );
|
95
|
+
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
// Delete current message
|
100
|
+
const eMessage = document.getElementById( "message" );
|
101
|
+
if ( eMessage.firstChild ) {
|
102
|
+
eMessage.removeChild( eMessage.firstChild );
|
103
|
+
}
|
104
|
+
if ( shouldSearch ) {
|
105
|
+
if ( matches.length === 0 ) {
|
106
|
+
eMessage.textContent = "Nothing found.";
|
107
|
+
}
|
108
|
+
if ( matches.length > MAX_RESULTS ) {
|
109
|
+
eMessage.textContent = "Too many results.";
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
// Delete current results
|
114
|
+
const eTable = document.getElementById( "results" );
|
115
|
+
if ( eTable.firstChild ) {
|
116
|
+
eTable.removeChild( eTable.firstChild );
|
117
|
+
}
|
118
|
+
|
119
|
+
eTable.appendChild( eBody );
|
120
|
+
}
|
121
|
+
|
122
|
+
static async generateSearchData() {
|
123
|
+
const searchData = [];
|
124
|
+
// eslint-disable-next-line no-undef
|
125
|
+
for ( const taxon of NAMES ) {
|
126
|
+
const taxonData = [ taxon ];
|
127
|
+
taxonData.push( taxon[ 0 ].toLowerCase().replace( / (subsp|var)\./, "" ) );
|
128
|
+
if ( taxon[ 1 ] ) {
|
129
|
+
taxonData.push( taxon[ 1 ].toLowerCase() );
|
130
|
+
}
|
131
|
+
if ( taxon[ 2 ] ) {
|
132
|
+
const syns = [];
|
133
|
+
for ( const syn of taxon[ 2 ] ) {
|
134
|
+
syns.push( syn.toLowerCase().replace( / (subsp|var)\./, "" ) );
|
135
|
+
}
|
136
|
+
taxonData[ 3 ] = syns;
|
137
|
+
}
|
138
|
+
searchData.push( taxonData );
|
139
|
+
}
|
140
|
+
this.#searchData = searchData;
|
141
|
+
}
|
142
|
+
|
143
|
+
static #handleChange() {
|
144
|
+
this.#debounce( Search.#doSearch );
|
145
|
+
}
|
146
|
+
|
147
|
+
static init() {
|
148
|
+
this.generateSearchData();
|
149
|
+
const eName = document.getElementById( "name" );
|
150
|
+
eName.focus();
|
151
|
+
eName.oninput = ( ev ) => { return this.#handleChange( ev ); };
|
152
|
+
}
|
153
|
+
|
154
|
+
}
|
155
|
+
|
156
|
+
Search.init();
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
title: Name Search
|
3
|
+
js: name_search.js
|
4
|
+
---
|
5
|
+
<h1>Search</h1>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
const NAMES = {% include names.json%};
|
9
|
+
</script>
|
10
|
+
|
11
|
+
<form>
|
12
|
+
<input type="text" id="name"><label for="name">Search for scientific name, common name, or synonym</label>
|
13
|
+
</form>
|
14
|
+
|
15
|
+
<div class="section" id="message"></div>
|
16
|
+
|
17
|
+
<table id="results">
|
18
|
+
</table>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
import { Config } from "./config.js";
|
3
|
+
|
4
|
+
class BasePageRenderer {
|
5
|
+
|
6
|
+
static render( outputDir, Taxa ) {
|
7
|
+
|
8
|
+
// Copy static files
|
9
|
+
fs.rmSync( outputDir, { force: true, recursive: true, maxRetries: 2, retryDelay: 200 } );
|
10
|
+
// First copy default Jekyll files from package.
|
11
|
+
fs.cpSync( Config.getPackageDir() + "/jekyll", outputDir, { recursive: true } );
|
12
|
+
// Then copy Jekyll files from current dir (which may override default files).
|
13
|
+
fs.cpSync( "jekyll", outputDir, { recursive: true } );
|
14
|
+
|
15
|
+
this.renderTools( outputDir, Taxa );
|
16
|
+
|
17
|
+
}
|
18
|
+
|
19
|
+
static renderTools( outputDir, Taxa ) {
|
20
|
+
|
21
|
+
const names = [];
|
22
|
+
for ( const taxon of Taxa.getTaxa() ) {
|
23
|
+
const row = [];
|
24
|
+
row.push( taxon.getName() );
|
25
|
+
const cn = taxon.getCommonNames().join( "," );
|
26
|
+
if ( cn ) {
|
27
|
+
row.push( cn );
|
28
|
+
}
|
29
|
+
const synonyms = [];
|
30
|
+
for ( const syn of taxon.getSynonyms() ) {
|
31
|
+
synonyms.push( syn );
|
32
|
+
}
|
33
|
+
if ( synonyms.length > 0 ) {
|
34
|
+
row[ 2 ] = synonyms;
|
35
|
+
}
|
36
|
+
names.push( row );
|
37
|
+
}
|
38
|
+
|
39
|
+
fs.writeFileSync( outputDir + "/_includes/names.json", JSON.stringify( names ) );
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
}
|
45
|
+
|
46
|
+
export { BasePageRenderer };
|
package/lib/exceptions.js
CHANGED
@@ -15,14 +15,16 @@ class Exceptions {
|
|
15
15
|
return false;
|
16
16
|
}
|
17
17
|
|
18
|
-
static getValue( name, cat, subcat ) {
|
18
|
+
static getValue( name, cat, subcat, defaultValue ) {
|
19
19
|
const taxonData = this.#exceptions[ name ];
|
20
20
|
if ( taxonData ) {
|
21
21
|
const catData = taxonData[ cat ];
|
22
22
|
if ( catData ) {
|
23
|
-
|
23
|
+
const val = catData[ subcat ];
|
24
|
+
return ( val === undefined ) ? defaultValue : val;
|
24
25
|
}
|
25
26
|
}
|
27
|
+
return defaultValue;
|
26
28
|
}
|
27
29
|
|
28
30
|
static init( dir ) {
|
package/lib/files.js
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
import * as fs from "node:fs";
|
2
|
+
|
3
|
+
class Files {
|
4
|
+
|
5
|
+
static async fetch( url, targetFileName ) {
|
6
|
+
const response = await fetch( url );
|
7
|
+
const data = await response.text();
|
8
|
+
fs.writeFileSync( targetFileName, data );
|
9
|
+
}
|
10
|
+
|
11
|
+
static read( path ) {
|
12
|
+
return fs.readFileSync( path, "utf8" );
|
13
|
+
}
|
14
|
+
|
15
|
+
}
|
16
|
+
|
17
|
+
export { Files };
|
package/lib/index.js
ADDED
package/lib/pagerenderer.js
CHANGED
@@ -6,20 +6,15 @@ import { HTMLPage } from "./htmlpage.js";
|
|
6
6
|
import { PageTaxon } from "./pagetaxon.js";
|
7
7
|
import { Config } from "./config.js";
|
8
8
|
import { RarePlants } from "./rareplants.js";
|
9
|
+
import { BasePageRenderer } from "./basepagerenderer.js";
|
9
10
|
|
10
|
-
class PageRenderer {
|
11
|
+
class PageRenderer extends BasePageRenderer {
|
11
12
|
|
12
13
|
static render( outputDir ) {
|
13
14
|
|
14
|
-
|
15
|
-
fs.rmSync( outputDir, { force: true, recursive: true } );
|
16
|
-
// First copy default Jekyll files from package.
|
17
|
-
fs.cpSync( Config.getPackageDir() + "/jekyll", outputDir, { recursive: true } );
|
18
|
-
// Then copy Jekyll files from current dir (which may override default files).
|
19
|
-
fs.cpSync( "jekyll", outputDir, { recursive: true } );
|
15
|
+
super.render( outputDir, Taxa );
|
20
16
|
|
21
17
|
this.renderLists( outputDir );
|
22
|
-
this.renderTools( outputDir );
|
23
18
|
|
24
19
|
Families.renderPages( outputDir );
|
25
20
|
|
@@ -174,30 +169,6 @@ class PageRenderer {
|
|
174
169
|
|
175
170
|
}
|
176
171
|
|
177
|
-
static renderTools( outputDir ) {
|
178
|
-
|
179
|
-
const commonNames = [];
|
180
|
-
for ( const taxon of Taxa.getTaxa() ) {
|
181
|
-
for ( const commonName of taxon.getCommonNames() ) {
|
182
|
-
commonNames.push( [ commonName, taxon.getName() ] );
|
183
|
-
}
|
184
|
-
}
|
185
|
-
|
186
|
-
const cnObj = {};
|
187
|
-
for ( const commonName of commonNames.sort( ( a, b ) => a[ 0 ].localeCompare( b[ 0 ] ) ) ) {
|
188
|
-
const normalizedName = commonName[ 0 ].toLowerCase().replaceAll( "-", " " ).replaceAll( "'", "" );
|
189
|
-
let data = cnObj[ normalizedName ];
|
190
|
-
if ( !data ) {
|
191
|
-
data = { cn: commonName[ 0 ], names: [] };
|
192
|
-
cnObj[ normalizedName ] = data;
|
193
|
-
}
|
194
|
-
data.names.push( commonName[ 1 ] );
|
195
|
-
}
|
196
|
-
fs.writeFileSync( outputDir + "/_includes/common_names.json", JSON.stringify( cnObj ) );
|
197
|
-
|
198
|
-
|
199
|
-
}
|
200
|
-
|
201
172
|
}
|
202
173
|
|
203
174
|
class PageTaxonList extends HTMLPage {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ca-plant-list/ca-plant-list",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.3",
|
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": {
|
@@ -10,9 +10,7 @@
|
|
10
10
|
"homepage": "https://github.com/ca-plants/ca-plant-list",
|
11
11
|
"type": "module",
|
12
12
|
"exports": {
|
13
|
-
"
|
14
|
-
"import": "./lib/config.js"
|
15
|
-
},
|
13
|
+
".": "./lib/index.js",
|
16
14
|
"./CSV": {
|
17
15
|
"import": "./lib/csv.js"
|
18
16
|
},
|
@@ -22,6 +20,9 @@
|
|
22
20
|
"./Exceptions": {
|
23
21
|
"import": "./lib/exceptions.js"
|
24
22
|
},
|
23
|
+
"./Files": {
|
24
|
+
"import": "./lib/files.js"
|
25
|
+
},
|
25
26
|
"./Taxa": {
|
26
27
|
"import": "./lib/taxa.js"
|
27
28
|
}
|
@@ -1,87 +0,0 @@
|
|
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();
|
@@ -1,18 +0,0 @@
|
|
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>
|