@ca-plant-list/ca-plant-list 0.4.3 → 0.4.4

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 ADDED
@@ -0,0 +1 @@
1
+ {}
@@ -7,5 +7,6 @@
7
7
  "fileMatch": ["**/exceptions.json"],
8
8
  "url": "./schemas/exceptions.schema.json"
9
9
  }
10
- ]
10
+ ],
11
+ "editor.tabSize": 2
11
12
  }
@@ -0,0 +1,3 @@
1
+ ## Resources:
2
+
3
+ - [How to identify Rosa rubiginosa (Sweet-Brier)](https://www.inaturalist.org/posts/99967-how-to-identify-rosa-rubiginosa-sweet-brier/) - iNaturalist post
@@ -1,152 +1,152 @@
1
1
  /* Defaults */
2
2
 
3
3
  :root {
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;
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
- --bs-nav-link-hover-color: rgba(255, 255, 255, .5);
11
+ --bs-nav-link-hover-color: rgba(255, 255, 255, .5);
12
12
  }
13
13
 
14
14
  .navbar {
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);
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
- margin-bottom: .5rem;
24
+ margin-bottom: .5rem;
25
25
  }
26
26
 
27
27
  span.label {
28
- font-weight: 600;
29
- padding-right: .5rem;
28
+ font-weight: 600;
29
+ padding-right: .5rem;
30
30
  }
31
31
 
32
32
  td {
33
- border: solid 1px;
34
- padding: 0 .5rem;
35
- vertical-align: top;
33
+ border: solid 1px;
34
+ padding: 0 .5rem;
35
+ vertical-align: top;
36
36
  }
37
37
 
38
38
  th {
39
- vertical-align: bottom;
39
+ vertical-align: bottom;
40
40
  }
41
41
 
42
42
  ul.listmenu {
43
- display: flex;
44
- padding: 0;
43
+ display: flex;
44
+ padding: 0;
45
45
  }
46
46
 
47
47
  ul.listmenu li {
48
- display: inline-block;
49
- padding: 0;
50
- padding-right: .25em;
48
+ display: inline-block;
49
+ padding: 0;
50
+ padding-right: .25em;
51
51
  }
52
52
 
53
53
  ul.listmenu li::after {
54
- content: " |";
54
+ content: " |";
55
55
  }
56
56
 
57
57
  ul.listmenu li:last-child:after {
58
- content: "";
58
+ content: "";
59
59
  }
60
60
 
61
61
  /* Utilities */
62
62
  .right {
63
- text-align: right;
63
+ text-align: right;
64
64
  }
65
65
 
66
66
  /* Lists */
67
67
 
68
68
  span.native {
69
- font-weight: bold;
69
+ font-weight: bold;
70
70
  }
71
71
 
72
72
  span.rare::before {
73
- content: "\2606";
73
+ content: "\2606";
74
74
  }
75
75
 
76
76
  /* Glossary */
77
77
  div.glossary img {
78
- max-height: 300px;
78
+ max-height: 300px;
79
79
  }
80
80
 
81
81
  /* Taxon Page */
82
82
 
83
83
  .common-names {
84
- font-size: larger;
85
- font-weight: bold;
84
+ font-size: larger;
85
+ font-weight: bold;
86
86
  }
87
87
 
88
88
  .native-status {
89
- font-weight: 600;
89
+ font-weight: 600;
90
90
  }
91
91
 
92
92
  div.grid {
93
- display: grid;
94
- grid-auto-flow: row;
95
- grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
96
- column-gap: 2rem;
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
- border: solid 1px;
101
- border-radius: 3%;
102
- padding: .5rem;
100
+ border: solid 1px;
101
+ border-radius: 3%;
102
+ padding: .5rem;
103
103
  }
104
104
 
105
105
  div.wrapper {
106
- display: flex;
107
- flex-wrap: wrap;
108
- column-gap: 4rem;
106
+ display: flex;
107
+ flex-wrap: wrap;
108
+ column-gap: 4rem;
109
109
  }
110
110
 
111
111
  div.section {
112
- margin-bottom: .5rem;
112
+ margin-bottom: .5rem;
113
113
  }
114
114
 
115
115
  div.section h2 {
116
- font-size: 1.25rem;
116
+ font-size: 1.25rem;
117
117
  }
118
118
 
119
- div.section ul {
120
- margin: 0;
121
- padding: 0;
119
+ div.section.nobullet ul {
120
+ margin: 0;
121
+ padding: 0;
122
122
  }
123
123
 
124
- div.section li {
125
- display: block;
126
- margin-left: 1rem;
127
- text-indent: -1rem;
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
- padding-left: 1rem;
131
+ padding-left: 1rem;
132
132
  }
133
133
 
134
134
  img.flr-color {
135
- margin-bottom: .2rem;
136
- margin-right: .5rem;
137
- width: 1rem;
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
- font-weight: bold;
142
+ font-weight: bold;
143
143
  }
144
144
 
145
145
  span.lcs {
146
- margin-right: 1rem;
146
+ margin-right: 1rem;
147
147
  }
148
148
 
149
149
  /* Forms */
150
150
  input {
151
- margin-right: .5em;
152
- }
151
+ margin-right: .5em;
152
+ }
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|undefined} subcat
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
- return this.getConfigValue("labels", name, undefined, dflt);
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
  }
@@ -4,194 +4,193 @@ import { XHTML } from "./xhtml.js";
4
4
  import { Config } from "../config.js";
5
5
 
6
6
  const MEDIA_TYPES = {
7
- SVG: "image/svg+xml",
8
- JPG: "image/jpeg",
9
- XHTML: "application/xhtml+xml",
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
- jpeg: MEDIA_TYPES.JPG,
17
- jpg: MEDIA_TYPES.JPG,
18
- svg: MEDIA_TYPES.SVG,
16
+ jpeg: MEDIA_TYPES.JPG,
17
+ jpg: MEDIA_TYPES.JPG,
18
+ svg: MEDIA_TYPES.SVG,
19
19
  };
20
20
 
21
21
  class EBook {
22
- #outputDir;
23
- #filename;
24
- #pub_id;
25
- #title;
26
-
27
- /**
28
- *
29
- * @param {string} outputDir
30
- * @param {string} filename
31
- * @param {string} pub_id
32
- * @param {string} title
33
- */
34
- constructor(outputDir, filename, pub_id, title) {
35
- this.#outputDir = outputDir;
36
- this.#filename = filename;
37
- this.#pub_id = pub_id;
38
- this.#title = title;
39
-
40
- // Initialize output directory.
41
- fs.rmSync(this.#outputDir, { force: true, recursive: true });
42
- fs.mkdirSync(this.getContentDir(), { recursive: true });
43
- }
44
-
45
- async create() {
46
- const contentDir = this.getContentDir();
47
-
48
- this.#createContainerFile();
49
- await this.createPages();
50
- this.#createPackageFile();
51
-
52
- // Copy assets
53
- fs.cpSync(Config.getPackageDir() + "/ebook", contentDir, {
54
- recursive: true,
55
- });
56
-
57
- this.createZip();
58
- }
59
-
60
- #createContainerFile() {
61
- const metaDir = this.#getMetaDir();
62
-
63
- fs.mkdirSync(metaDir, { recursive: true });
64
-
65
- let xml =
66
- '<?xml version="1.0"?>' +
67
- '<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">';
68
-
69
- xml +=
70
- '<rootfiles><rootfile full-path="epub/package.opf" media-type="application/oebps-package+xml" /></rootfiles>';
71
- xml += "</container>";
72
-
73
- fs.writeFileSync(metaDir + "/container.xml", xml);
74
- }
75
-
76
- #createPackageFile() {
77
- const dir = this.getContentDir();
78
-
79
- let xml =
80
- '<?xml version="1.0"?>\n' +
81
- '<package version="3.0" xml:lang="en" xmlns="http://www.idpf.org/2007/opf" unique-identifier="pub-id">';
82
-
83
- xml += this.#renderMetadata();
84
- xml += this.#renderManifest();
85
- xml += this.#renderSpine();
86
-
87
- xml += "</package>";
88
-
89
- fs.writeFileSync(dir + "/package.opf", xml);
90
- }
91
-
92
- async createPages() {
93
- throw new Error("must be implemented by subclass");
94
- }
95
-
96
- createZip() {
97
- // Create zip.
98
- const filename = this.#outputDir + "/" + this.#filename + ".epub";
99
- const output = fs.createWriteStream(filename);
100
- const archive = archiver("zip", {
101
- zlib: { level: 9 }, // Sets the compression level.
102
- });
103
-
104
- archive.pipe(output);
105
-
106
- archive.append("application/epub+zip", {
107
- name: "mimetype",
108
- store: true,
109
- });
110
-
111
- archive.directory(this.#getMetaDir(), "META-INF");
112
- archive.directory(this.getContentDir(), "epub");
113
-
114
- archive.finalize();
115
- }
116
-
117
- getContentDir() {
118
- return this.#outputDir + "/epub";
119
- }
120
-
121
- /**
122
- * @param {string} id
123
- * @param {string} href
124
- * @param {string} mediaType
125
- */
126
- static getManifestEntry(id, href, mediaType = MEDIA_TYPES.XHTML) {
127
- return (
128
- '<item id="' +
129
- id +
130
- '" href="' +
131
- href +
132
- '" media-type="' +
133
- mediaType +
134
- '" />'
135
- );
136
- }
137
-
138
- /**
139
- * @param {string} ext
140
- */
141
- static getMediaTypeForExt(ext) {
142
- return EXT_MEDIA_MAP[ext];
143
- }
144
-
145
- /**
146
- * @param {string} id
147
- */
148
- static getSpineEntry(id) {
149
- return '<itemref idref="' + id + '"/>';
150
- }
151
-
152
- #getMetaDir() {
153
- return this.#outputDir + "/META-INF";
154
- }
155
-
156
- #renderManifest() {
157
- let xml = "<manifest>";
158
- xml += '<item id="c0" href="css/main.css" media-type="text/css" />';
159
- xml += this.renderManifestEntries();
160
- xml +=
161
- '<item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" />';
162
- return xml + "</manifest>";
163
- }
164
-
165
- renderManifestEntries() {
166
- throw new Error("must be implemented by subclass");
167
- }
168
-
169
- #renderMetadata() {
170
- let xml = '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">';
171
- xml += XHTML.textElement("dc:identifier", this.#pub_id, {
172
- id: "pub-id",
173
- });
174
- xml += "<dc:language>en-US</dc:language>";
175
- xml += XHTML.textElement("dc:title", this.#title);
176
- const d = new Date();
177
- d.setUTCMilliseconds(0);
178
- xml +=
179
- '<meta property="dcterms:modified">' +
180
- d.toISOString().replace(".000", "") +
181
- "</meta>";
182
- return xml + "</metadata>";
183
- }
184
-
185
- #renderSpine() {
186
- let xml = "<spine>";
187
- xml += '<itemref idref="toc"/>';
188
- xml += this.renderSpineElements();
189
- return xml + "</spine>";
190
- }
191
-
192
- renderSpineElements() {
193
- throw new Error("must be implemented by subclass");
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 };