@ca-plant-list/ca-plant-list 0.4.3 → 0.4.6
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/.prettierrc +1 -0
- package/README.md +27 -1
- package/data/config.json +5 -0
- package/data/inattaxonphotos.csv +6059 -0
- package/data/taxa.csv +1 -0
- package/data/text/Rosa-rubiginosa.footer.md +3 -0
- package/eslint.config.mjs +13 -0
- package/jekyll/assets/css/main.css +59 -59
- package/jekyll/index.md +3 -0
- package/lib/config.js +12 -4
- package/lib/ebook/ebook.js +178 -179
- package/lib/ebook/plantbook.js +147 -139
- package/lib/genericpage.js +3 -4
- package/lib/htmltaxon.js +118 -123
- package/lib/inat_photo.js +43 -0
- package/lib/index.d.ts +12 -9
- package/lib/markdown.js +4 -0
- package/lib/pagerenderer.js +220 -221
- package/lib/photo.js +44 -0
- package/lib/taxa.js +37 -3
- package/lib/taxon.js +13 -0
- package/lib/util.js +20 -0
- package/lib/web/pagetaxon.js +212 -181
- package/package.json +9 -6
- package/scripts/build-site.js +1 -1
- package/scripts/inattaxonphotos.js +106 -0
- package/types/classes.d.ts +55 -9
- package/.vscode/settings.json +0 -11
package/data/taxa.csv
CHANGED
@@ -114,6 +114,7 @@ Antirrhinum kelloggii,,N,13568,401,165675
|
|
114
114
|
Antirrhinum majus,snapdragon,X,13557,404,48969
|
115
115
|
Antirrhinum thompsonii,Sierra snapdragon,N,108978,14286,168306,,pink,4,8
|
116
116
|
Antirrhinum vexillocalyculatum subsp. vexillocalyculatum,,N,88873,10499,840919,annual,purple,6,8
|
117
|
+
Aphanes occidentalis,western lady's mantle,N,13608,,1452907,annual,,3,5
|
117
118
|
Aphyllon californicum subsp. jepsonii,,N,103325,13438,802459
|
118
119
|
Aphyllon epigalium subsp. epigalium,,N,103327,13527,809377
|
119
120
|
Aphyllon fasciculatum,clustered broom-rape,N,100018,13441,802543
|
@@ -1,152 +1,152 @@
|
|
1
1
|
/* Defaults */
|
2
2
|
|
3
3
|
:root {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
/* See https://visme.co/blog/website-color-schemes/ theme 4 */
|
5
|
+
--bs-body-bg: #5cdb95;
|
6
|
+
--bs-link-color: #05386b;
|
7
|
+
--pl-navbar-bg: #379683;
|
8
8
|
}
|
9
9
|
|
10
10
|
.navbar-nav {
|
11
|
-
|
11
|
+
--bs-nav-link-hover-color: rgba(255, 255, 255, .5);
|
12
12
|
}
|
13
13
|
|
14
14
|
.navbar {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
--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");
|
16
|
+
--bs-navbar-color: white;
|
17
|
+
--bs-navbar-brand-hover-color: rgba(255, 255, 255, .5);
|
18
|
+
--bs-navbar-brand-color: white;
|
19
|
+
--bs-navbar-nav-link-padding-x: 1rem;
|
20
|
+
background-color: var(--pl-navbar-bg);
|
21
21
|
}
|
22
22
|
|
23
23
|
p {
|
24
|
-
|
24
|
+
margin-bottom: .5rem;
|
25
25
|
}
|
26
26
|
|
27
27
|
span.label {
|
28
|
-
|
29
|
-
|
28
|
+
font-weight: 600;
|
29
|
+
padding-right: .5rem;
|
30
30
|
}
|
31
31
|
|
32
32
|
td {
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
border: solid 1px;
|
34
|
+
padding: 0 .5rem;
|
35
|
+
vertical-align: top;
|
36
36
|
}
|
37
37
|
|
38
38
|
th {
|
39
|
-
|
39
|
+
vertical-align: bottom;
|
40
40
|
}
|
41
41
|
|
42
42
|
ul.listmenu {
|
43
|
-
|
44
|
-
|
43
|
+
display: flex;
|
44
|
+
padding: 0;
|
45
45
|
}
|
46
46
|
|
47
47
|
ul.listmenu li {
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
display: inline-block;
|
49
|
+
padding: 0;
|
50
|
+
padding-right: .25em;
|
51
51
|
}
|
52
52
|
|
53
53
|
ul.listmenu li::after {
|
54
|
-
|
54
|
+
content: " |";
|
55
55
|
}
|
56
56
|
|
57
57
|
ul.listmenu li:last-child:after {
|
58
|
-
|
58
|
+
content: "";
|
59
59
|
}
|
60
60
|
|
61
61
|
/* Utilities */
|
62
62
|
.right {
|
63
|
-
|
63
|
+
text-align: right;
|
64
64
|
}
|
65
65
|
|
66
66
|
/* Lists */
|
67
67
|
|
68
68
|
span.native {
|
69
|
-
|
69
|
+
font-weight: bold;
|
70
70
|
}
|
71
71
|
|
72
72
|
span.rare::before {
|
73
|
-
|
73
|
+
content: "\2606";
|
74
74
|
}
|
75
75
|
|
76
76
|
/* Glossary */
|
77
77
|
div.glossary img {
|
78
|
-
|
78
|
+
max-height: 300px;
|
79
79
|
}
|
80
80
|
|
81
81
|
/* Taxon Page */
|
82
82
|
|
83
83
|
.common-names {
|
84
|
-
|
85
|
-
|
84
|
+
font-size: larger;
|
85
|
+
font-weight: bold;
|
86
86
|
}
|
87
87
|
|
88
88
|
.native-status {
|
89
|
-
|
89
|
+
font-weight: 600;
|
90
90
|
}
|
91
91
|
|
92
92
|
div.grid {
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
display: grid;
|
94
|
+
grid-auto-flow: row;
|
95
|
+
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
96
|
+
column-gap: 2rem;
|
97
97
|
}
|
98
98
|
|
99
99
|
div.grid.borders>div.section {
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
border: solid 1px;
|
101
|
+
border-radius: 3%;
|
102
|
+
padding: .5rem;
|
103
103
|
}
|
104
104
|
|
105
105
|
div.wrapper {
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
display: flex;
|
107
|
+
flex-wrap: wrap;
|
108
|
+
column-gap: 4rem;
|
109
109
|
}
|
110
110
|
|
111
111
|
div.section {
|
112
|
-
|
112
|
+
margin-bottom: .5rem;
|
113
113
|
}
|
114
114
|
|
115
115
|
div.section h2 {
|
116
|
-
|
116
|
+
font-size: 1.25rem;
|
117
117
|
}
|
118
118
|
|
119
|
-
div.section ul {
|
120
|
-
|
121
|
-
|
119
|
+
div.section.nobullet ul {
|
120
|
+
margin: 0;
|
121
|
+
padding: 0;
|
122
122
|
}
|
123
123
|
|
124
|
-
div.section li {
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
div.section.nobullet li {
|
125
|
+
display: block;
|
126
|
+
margin-left: 1rem;
|
127
|
+
text-indent: -1rem;
|
128
128
|
}
|
129
129
|
|
130
130
|
div.section ul.indent {
|
131
|
-
|
131
|
+
padding-left: 1rem;
|
132
132
|
}
|
133
133
|
|
134
134
|
img.flr-color {
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
margin-bottom: .2rem;
|
136
|
+
margin-right: .5rem;
|
137
|
+
width: 1rem;
|
138
138
|
}
|
139
139
|
|
140
140
|
span.flr-time,
|
141
141
|
span.lc {
|
142
|
-
|
142
|
+
font-weight: bold;
|
143
143
|
}
|
144
144
|
|
145
145
|
span.lcs {
|
146
|
-
|
146
|
+
margin-right: 1rem;
|
147
147
|
}
|
148
148
|
|
149
149
|
/* Forms */
|
150
150
|
input {
|
151
|
-
|
152
|
-
}
|
151
|
+
margin-right: .5em;
|
152
|
+
}
|
package/jekyll/index.md
CHANGED
package/lib/config.js
CHANGED
@@ -20,9 +20,9 @@ class Config {
|
|
20
20
|
/**
|
21
21
|
* @param {string} prefix
|
22
22
|
* @param {string} name
|
23
|
-
* @param {string
|
24
|
-
* @param {string} dflt
|
25
|
-
* @returns {string}
|
23
|
+
* @param {string} [subcat]
|
24
|
+
* @param {string} [dflt]
|
25
|
+
* @returns {string|undefined}
|
26
26
|
*/
|
27
27
|
getConfigValue(prefix, name, subcat, dflt) {
|
28
28
|
const obj = this.#config[prefix];
|
@@ -58,11 +58,19 @@ class Config {
|
|
58
58
|
/**
|
59
59
|
* @param {string} name
|
60
60
|
* @param {string} dflt
|
61
|
+
* @returns {string}
|
61
62
|
*/
|
62
63
|
getLabel(name, dflt) {
|
63
|
-
|
64
|
+
const label = this.getConfigValue("labels", name, undefined, dflt);
|
65
|
+
if (label === undefined) {
|
66
|
+
throw new Error();
|
67
|
+
}
|
68
|
+
return label;
|
64
69
|
}
|
65
70
|
|
71
|
+
/**
|
72
|
+
* @returns {string}
|
73
|
+
*/
|
66
74
|
static getPackageDir() {
|
67
75
|
return this.#packageDir;
|
68
76
|
}
|
package/lib/ebook/ebook.js
CHANGED
@@ -4,194 +4,193 @@ import { XHTML } from "./xhtml.js";
|
|
4
4
|
import { Config } from "../config.js";
|
5
5
|
|
6
6
|
const MEDIA_TYPES = {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
SVG: "image/svg+xml",
|
8
|
+
JPG: "image/jpeg",
|
9
|
+
XHTML: "application/xhtml+xml",
|
10
10
|
};
|
11
11
|
|
12
12
|
/**
|
13
13
|
* @type {Object<string,string>}
|
14
14
|
*/
|
15
15
|
const EXT_MEDIA_MAP = {
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
jpeg: MEDIA_TYPES.JPG,
|
17
|
+
jpg: MEDIA_TYPES.JPG,
|
18
|
+
svg: MEDIA_TYPES.SVG,
|
19
19
|
};
|
20
20
|
|
21
21
|
class EBook {
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
}
|
22
|
+
#outputDir;
|
23
|
+
#filename;
|
24
|
+
#pub_id;
|
25
|
+
#title;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* @param {string} outputDir
|
29
|
+
* @param {string} filename
|
30
|
+
* @param {string} pub_id
|
31
|
+
* @param {string} title
|
32
|
+
*/
|
33
|
+
constructor(outputDir, filename, pub_id, title) {
|
34
|
+
this.#outputDir = outputDir;
|
35
|
+
this.#filename = filename;
|
36
|
+
this.#pub_id = pub_id;
|
37
|
+
this.#title = title;
|
38
|
+
|
39
|
+
// Initialize output directory.
|
40
|
+
fs.rmSync(this.#outputDir, { force: true, recursive: true });
|
41
|
+
fs.mkdirSync(this.getContentDir(), { recursive: true });
|
42
|
+
}
|
43
|
+
|
44
|
+
async create() {
|
45
|
+
const contentDir = this.getContentDir();
|
46
|
+
|
47
|
+
this.#createContainerFile();
|
48
|
+
await this.createPages();
|
49
|
+
this.#createPackageFile();
|
50
|
+
|
51
|
+
// Copy assets
|
52
|
+
fs.cpSync(Config.getPackageDir() + "/ebook", contentDir, {
|
53
|
+
recursive: true,
|
54
|
+
});
|
55
|
+
|
56
|
+
this.createZip();
|
57
|
+
}
|
58
|
+
|
59
|
+
#createContainerFile() {
|
60
|
+
const metaDir = this.#getMetaDir();
|
61
|
+
|
62
|
+
fs.mkdirSync(metaDir, { recursive: true });
|
63
|
+
|
64
|
+
let xml =
|
65
|
+
'<?xml version="1.0"?>' +
|
66
|
+
'<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">';
|
67
|
+
|
68
|
+
xml +=
|
69
|
+
'<rootfiles><rootfile full-path="epub/package.opf" media-type="application/oebps-package+xml" /></rootfiles>';
|
70
|
+
xml += "</container>";
|
71
|
+
|
72
|
+
fs.writeFileSync(metaDir + "/container.xml", xml);
|
73
|
+
}
|
74
|
+
|
75
|
+
#createPackageFile() {
|
76
|
+
const dir = this.getContentDir();
|
77
|
+
|
78
|
+
let xml =
|
79
|
+
'<?xml version="1.0"?>\n' +
|
80
|
+
'<package version="3.0" xml:lang="en" xmlns="http://www.idpf.org/2007/opf" unique-identifier="pub-id">';
|
81
|
+
|
82
|
+
xml += this.#renderMetadata();
|
83
|
+
xml += this.#renderManifest();
|
84
|
+
xml += this.#renderSpine();
|
85
|
+
|
86
|
+
xml += "</package>";
|
87
|
+
|
88
|
+
fs.writeFileSync(dir + "/package.opf", xml);
|
89
|
+
}
|
90
|
+
|
91
|
+
async createPages() {
|
92
|
+
throw new Error("must be implemented by subclass");
|
93
|
+
}
|
94
|
+
|
95
|
+
createZip() {
|
96
|
+
// Create zip.
|
97
|
+
const filename = this.#outputDir + "/" + this.#filename + ".epub";
|
98
|
+
const output = fs.createWriteStream(filename);
|
99
|
+
const archive = archiver("zip", {
|
100
|
+
zlib: { level: 9 }, // Sets the compression level.
|
101
|
+
});
|
102
|
+
|
103
|
+
archive.pipe(output);
|
104
|
+
|
105
|
+
archive.append("application/epub+zip", {
|
106
|
+
name: "mimetype",
|
107
|
+
store: true,
|
108
|
+
});
|
109
|
+
|
110
|
+
archive.directory(this.#getMetaDir(), "META-INF");
|
111
|
+
archive.directory(this.getContentDir(), "epub");
|
112
|
+
|
113
|
+
archive.finalize();
|
114
|
+
}
|
115
|
+
|
116
|
+
getContentDir() {
|
117
|
+
return this.#outputDir + "/epub";
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* @param {string} id
|
122
|
+
* @param {string} href
|
123
|
+
* @param {string} mediaType
|
124
|
+
*/
|
125
|
+
static getManifestEntry(id, href, mediaType = MEDIA_TYPES.XHTML) {
|
126
|
+
return (
|
127
|
+
'<item id="' +
|
128
|
+
id +
|
129
|
+
'" href="' +
|
130
|
+
href +
|
131
|
+
'" media-type="' +
|
132
|
+
mediaType +
|
133
|
+
'" />'
|
134
|
+
);
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* @param {string} ext
|
139
|
+
*/
|
140
|
+
static getMediaTypeForExt(ext) {
|
141
|
+
return EXT_MEDIA_MAP[ext];
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* @param {string} id
|
146
|
+
*/
|
147
|
+
static getSpineEntry(id) {
|
148
|
+
return '<itemref idref="' + id + '"/>';
|
149
|
+
}
|
150
|
+
|
151
|
+
#getMetaDir() {
|
152
|
+
return this.#outputDir + "/META-INF";
|
153
|
+
}
|
154
|
+
|
155
|
+
#renderManifest() {
|
156
|
+
let xml = "<manifest>";
|
157
|
+
xml += '<item id="c0" href="css/main.css" media-type="text/css" />';
|
158
|
+
xml += this.renderManifestEntries();
|
159
|
+
xml +=
|
160
|
+
'<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />';
|
161
|
+
return xml + "</manifest>";
|
162
|
+
}
|
163
|
+
|
164
|
+
renderManifestEntries() {
|
165
|
+
throw new Error("must be implemented by subclass");
|
166
|
+
}
|
167
|
+
|
168
|
+
#renderMetadata() {
|
169
|
+
let xml = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">';
|
170
|
+
xml += XHTML.textElement("dc:identifier", this.#pub_id, {
|
171
|
+
id: "pub-id",
|
172
|
+
});
|
173
|
+
xml += "<dc:language>en-US</dc:language>";
|
174
|
+
xml += XHTML.textElement("dc:title", this.#title);
|
175
|
+
const d = new Date();
|
176
|
+
d.setUTCMilliseconds(0);
|
177
|
+
xml +=
|
178
|
+
'<meta property="dcterms:modified">' +
|
179
|
+
d.toISOString().replace(".000", "") +
|
180
|
+
"</meta>";
|
181
|
+
return xml + "</metadata>";
|
182
|
+
}
|
183
|
+
|
184
|
+
#renderSpine() {
|
185
|
+
let xml = "<spine>";
|
186
|
+
xml += '<itemref idref="toc"/>';
|
187
|
+
xml += this.renderSpineElements();
|
188
|
+
return xml + "</spine>";
|
189
|
+
}
|
190
|
+
|
191
|
+
renderSpineElements() {
|
192
|
+
throw new Error("must be implemented by subclass");
|
193
|
+
}
|
195
194
|
}
|
196
195
|
|
197
196
|
export { EBook };
|