@ca-plant-list/ca-plant-list 0.4.24 → 0.4.27

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.
Files changed (53) hide show
  1. package/data/taxa.csv +7 -6
  2. package/data/text/Erodium-botrys.footer.md +3 -0
  3. package/data/text/Erodium-botrys.md +1 -0
  4. package/data/text/Erodium-brachycarpum.footer.md +3 -0
  5. package/data/text/Erodium-brachycarpum.md +1 -0
  6. package/data/text/Erodium-cicutarium.md +1 -0
  7. package/data/text/Erodium-moschatum.footer.md +3 -0
  8. package/data/text/Erodium-moschatum.md +1 -0
  9. package/generators/eleventy/layouts/h1.njk +6 -0
  10. package/generators/eleventy/layouts/html.njk +55 -0
  11. package/lib/basepagerenderer.js +36 -18
  12. package/lib/config.js +5 -3
  13. package/lib/ebook/ebookpage.js +1 -4
  14. package/lib/ebook/ebooksitegenerator.js +4 -12
  15. package/lib/ebook/glossarypages.js +1 -3
  16. package/lib/ebook/plantbook.js +1 -1
  17. package/lib/files.js +1 -0
  18. package/lib/htmltaxon.js +8 -19
  19. package/lib/index.d.ts +32 -9
  20. package/lib/index.js +4 -2
  21. package/lib/jekyll.js +40 -59
  22. package/lib/markdown.js +3 -5
  23. package/lib/sitegenerator.js +68 -12
  24. package/lib/taxonomy/taxa.js +4 -4
  25. package/lib/types.js +4 -0
  26. package/lib/utils/eleventyGenerator.js +82 -0
  27. package/lib/utils/htmlFragments.js +19 -0
  28. package/lib/web/glossarypages.js +6 -10
  29. package/lib/web/pageFamily.js +14 -14
  30. package/lib/web/pageGeneric.js +78 -0
  31. package/lib/web/{pagetaxon.js → pageTaxon.js} +4 -4
  32. package/lib/web/pageTaxonList.js +53 -0
  33. package/lib/{pagerenderer.js → web/renderAllPages.js} +38 -80
  34. package/package.json +12 -10
  35. package/scripts/build-site.js +20 -52
  36. package/static/assets/js/nameSearchData.js +2 -0
  37. package/static/assets/js/name_search.js +203 -0
  38. package/static/assets/js/ui.js +11 -0
  39. package/static/assets/js/utils.js +56 -0
  40. package/static/name_search.html +15 -0
  41. package/jekyll/_includes/glossary.html +0 -0
  42. package/jekyll/assets/js/name_search.js +0 -165
  43. package/jekyll/assets/js/ui.js +0 -10
  44. package/jekyll/assets/js/utils.js +0 -26
  45. package/jekyll/name_search.html +0 -17
  46. package/lib/genericpage.js +0 -88
  47. /package/{jekyll → generators}/_includes/analytics.html +0 -0
  48. /package/{jekyll → generators}/_includes/menu_extra.html +0 -0
  49. /package/{jekyll → generators/jekyll}/_config.yml +0 -0
  50. /package/{jekyll → generators/jekyll}/_layouts/default.html +0 -0
  51. /package/{jekyll → generators/jekyll}/_layouts/html.html +0 -0
  52. /package/{jekyll → static}/assets/css/main.css +0 -0
  53. /package/{jekyll → static}/index.md +0 -0
@@ -1,16 +1,29 @@
1
+ import path from "node:path";
1
2
  import { optimize } from "svgo-ll";
2
3
 
3
4
  import { Config } from "./config.js";
4
5
  import { Files } from "./files.js";
5
6
 
6
- class SiteGenerator {
7
+ const FRONT_DELIM = "---";
8
+
9
+ export class SiteGenerator {
10
+ #config;
7
11
  #baseDir;
12
+ #options;
8
13
 
9
14
  /**
15
+ * @param {Config} config
10
16
  * @param {string} baseDir
17
+ * @param {import("./index.js").SiteGeneratorOptions} [options]
11
18
  */
12
- constructor(baseDir) {
19
+ constructor(config, baseDir, options = {}) {
20
+ this.#config = config;
13
21
  this.#baseDir = baseDir;
22
+ this.#options = options;
23
+ }
24
+
25
+ copyGeneratorFiles() {
26
+ throw new Error("must be implemented by subclass");
14
27
  }
15
28
 
16
29
  /**
@@ -23,11 +36,11 @@ class SiteGenerator {
23
36
  */
24
37
  function createFlowerColorIcons(outputDir, flowerColors) {
25
38
  // Read generic input.
26
- const inputFileName = Files.join(outputDir, "flower.svg");
39
+ const inputFileName = path.join(outputDir, "flower.svg");
27
40
  const srcSVG = Files.read(inputFileName);
28
41
  for (const color of flowerColors) {
29
42
  Files.write(
30
- Files.join(outputDir, "f-" + color.getColorName() + ".svg"),
43
+ path.join(outputDir, "f-" + color.getColorName() + ".svg"),
31
44
  srcSVG.replace("#ff0", color.getColorCode()),
32
45
  );
33
46
  }
@@ -59,26 +72,69 @@ class SiteGenerator {
59
72
  createFlowerColorIcons(outputDir, flowerColors);
60
73
  }
61
74
 
75
+ copyStaticFiles() {
76
+ // First copy default files from ca-plant-list.
77
+ Files.copyDir(
78
+ path.join(Config.getPackageDir(), "static"),
79
+ this.#baseDir,
80
+ );
81
+ // Then copy files from current directory (which may override default files).
82
+ Files.copyDir("./static", this.#baseDir);
83
+ }
84
+
85
+ /**
86
+ * @param {string} webDir
87
+ */
88
+ // eslint-disable-next-line no-unused-vars
89
+ async generate(webDir) {
90
+ throw new Error("must be implemented by subclass");
91
+ }
92
+
62
93
  getBaseDir() {
63
94
  return this.#baseDir;
64
95
  }
65
96
 
97
+ getConfig() {
98
+ return this.#config;
99
+ }
100
+
66
101
  /**
67
- * @param {string} path
102
+ * @param {Object<string,string|undefined>} atts
68
103
  */
69
- mkdir(path) {
70
- Files.mkdir(Files.join(this.#baseDir, path));
104
+ getFrontMatter(atts) {
105
+ const lines = [FRONT_DELIM];
106
+ for (const [k, v] of Object.entries(atts)) {
107
+ if (v) {
108
+ lines.push(k + ': "' + v + '"');
109
+ }
110
+ }
111
+ lines.push(FRONT_DELIM);
112
+ return lines.join("\n") + "\n\n";
113
+ }
114
+
115
+ /**
116
+ * @returns {string[]}
117
+ */
118
+ getPassThroughPatterns() {
119
+ return this.#options.passThroughPatterns ?? [];
120
+ }
121
+
122
+ /**
123
+ * @param {string} outputSubdir
124
+ */
125
+ mkdir(outputSubdir) {
126
+ Files.mkdir(path.join(this.#baseDir, outputSubdir));
71
127
  }
72
128
 
73
129
  /**
74
130
  * @param {string} content
75
- * @param {{title:string}} attributes
131
+ * @param {Object<string,string>} attributes
76
132
  * @param {string} filename
77
133
  */
78
- // eslint-disable-next-line no-unused-vars
79
134
  writeTemplate(content, attributes, filename) {
80
- throw new Error("must be implemented by subclass");
135
+ Files.write(
136
+ path.join(this.getBaseDir(), filename),
137
+ this.getFrontMatter(attributes) + content,
138
+ );
81
139
  }
82
140
  }
83
-
84
- export { SiteGenerator };
@@ -40,7 +40,7 @@ class Taxa {
40
40
  #isSubset;
41
41
 
42
42
  /**
43
- * @param {Object<string,import("../index.js").TaxonData>|true} inclusionList
43
+ * @param {Object<string,import("../index.js").TaxonOverrides>|true} inclusionList
44
44
  * @param {ErrorLog} errorLog
45
45
  * @param {boolean} showFlowerErrors
46
46
  * @param {function(import("../index.js").TaxonData,Genera):Taxon} taxonFactory
@@ -224,7 +224,7 @@ class Taxa {
224
224
 
225
225
  /**
226
226
  * @param {SynonymData[]} synCSV
227
- * @param {Object<string,import("../index.js").TaxonData>|boolean} inclusionList
227
+ * @param {Object<string,import("../index.js").TaxonOverrides>|boolean} inclusionList
228
228
  */
229
229
  #loadSyns(synCSV, inclusionList) {
230
230
  for (const syn of synCSV) {
@@ -246,7 +246,7 @@ class Taxa {
246
246
 
247
247
  /**
248
248
  * @param {import("../index.js").TaxonData[]} taxaCSV
249
- * @param {Object<string,import("../index.js").TaxonData>|true} inclusionList
249
+ * @param {Object<string,import("../index.js").TaxonOverrides>|true} inclusionList
250
250
  * @param {function(import("../index.js").TaxonData,Genera):Taxon} taxonFactory
251
251
  * @param {Genera} genera
252
252
  * @param {boolean} showFlowerErrors
@@ -255,7 +255,7 @@ class Taxa {
255
255
  for (const row of taxaCSV) {
256
256
  const name = row["taxon_name"];
257
257
 
258
- /** @type {import("../index.js").TaxonData|{status?:import("../index.js").NativeStatusCode}} */
258
+ /** @type {import("../index.js").TaxonOverrides} */
259
259
  let taxon_overrides = {};
260
260
  if (inclusionList !== true) {
261
261
  taxon_overrides = inclusionList[name];
package/lib/types.js CHANGED
@@ -1,8 +1,12 @@
1
1
  /**
2
+ * Types
3
+ * @typedef {{t:string,c?:string,s?:string[]}} NameSearchData
4
+ *
2
5
  * Classes
3
6
  * @typedef {import("./config.js").Config} Config
4
7
  * @typedef {import("./taxonomy/families.js").Families} Families
5
8
  * @typedef {import("./taxonomy/families.js").Family} Family
9
+ * @typedef {import("./sitegenerator.js").SiteGenerator} SiteGenerator
6
10
  * @typedef {import("./taxonomy/taxa.js").Taxa} Taxa
7
11
  * @typedef {import("./index.js").TaxaColDef<import("./types.js").Taxon>} TaxaColDef
8
12
  * @typedef {import("./taxonomy/taxon.js").Taxon} Taxon
@@ -0,0 +1,82 @@
1
+ // @ts-ignore
2
+ import { Eleventy } from "@11ty/eleventy";
3
+ import { SiteGenerator } from "../sitegenerator.js";
4
+ import path from "node:path";
5
+ import { Files } from "../files.js";
6
+ import { Config } from "../config.js";
7
+
8
+ export class EleventyGenerator extends SiteGenerator {
9
+ copyGeneratorFiles() {
10
+ // First copy default files from package.
11
+ const layoutSrc = "./generators/eleventy/layouts";
12
+ const commonSrc = "./generators/_includes";
13
+ const dest = path.join(this.getBaseDir(), "_includes");
14
+
15
+ Files.copyDir(path.join(Config.getPackageDir(), commonSrc), dest);
16
+ Files.copyDir(path.join(Config.getPackageDir(), layoutSrc), dest);
17
+
18
+ // Then copy files from current dir (which may override default files).
19
+ if (Files.isDir(commonSrc)) {
20
+ Files.copyDir(commonSrc, dest);
21
+ }
22
+ if (Files.isDir(layoutSrc)) {
23
+ Files.copyDir(layoutSrc, dest);
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {string} webDir
29
+ */
30
+ async generate(webDir) {
31
+ const srcDir = this.getBaseDir();
32
+ const config = this.getConfig();
33
+ const generator = this;
34
+ let elev = new Eleventy(srcDir, webDir, {
35
+ quietMode: true,
36
+ config:
37
+ // @ts-ignore
38
+ function (eleventyConfig) {
39
+ // Not running in project root, so using .gitignore will break things.
40
+ eleventyConfig.setUseGitIgnore(false);
41
+
42
+ // Don't change file system structure when writing output files.
43
+ eleventyConfig.addGlobalData("permalink", () => {
44
+ // @ts-ignore
45
+ return (data) => {
46
+ // Include directories in the generated content.
47
+ const inputPath = path.relative(
48
+ srcDir,
49
+ data.page.inputPath,
50
+ );
51
+ // Remove the file extension.
52
+ const parsed = path.parse(inputPath);
53
+ return path.join(parsed.dir, `${parsed.name}.html`);
54
+ };
55
+ });
56
+
57
+ // Use layout with <h1> by default.
58
+ eleventyConfig.addGlobalData("layout", "h1.njk");
59
+
60
+ // Set site name for use in nav bar.
61
+ eleventyConfig.addGlobalData(
62
+ "siteName",
63
+ config.getSiteName(),
64
+ );
65
+
66
+ const passThroughPatterns = [
67
+ "assets",
68
+ "i",
69
+ "errors.tsv",
70
+ ...generator.getPassThroughPatterns(),
71
+ ];
72
+ for (const pattern of passThroughPatterns) {
73
+ eleventyConfig.addPassthroughCopy(
74
+ // Eleventy apparently can't handle windows paths in globs, so change it.
75
+ path.join(srcDir, pattern).replaceAll("\\", "/"),
76
+ );
77
+ }
78
+ },
79
+ });
80
+ await elev.write();
81
+ }
82
+ }
@@ -0,0 +1,19 @@
1
+ import { HTML } from "../html.js";
2
+ import { Markdown } from "../markdown.js";
3
+
4
+ /**
5
+ * Utilities to create HTML fragments specific to ca-plant-list.
6
+ */
7
+ export class HTMLFragments {
8
+ /**
9
+ * @param {string} filePath
10
+ * @returns {string}
11
+ */
12
+ static getMarkdownSection(filePath) {
13
+ const footerMarkdown = Markdown.fileToHTML(filePath);
14
+ if (footerMarkdown) {
15
+ return HTML.wrap("div", footerMarkdown, "section");
16
+ }
17
+ return "";
18
+ }
19
+ }
@@ -1,11 +1,11 @@
1
1
  import { Glossary } from "../plants/glossary.js";
2
2
  import { Markdown } from "../markdown.js";
3
3
  import { HTML } from "../html.js";
4
- import { Files } from "../files.js";
4
+ import path from "node:path";
5
5
 
6
6
  const ENTRY_DIR = "g";
7
7
 
8
- class GlossaryPages {
8
+ export class GlossaryPages {
9
9
  #siteGenerator;
10
10
  #glossary;
11
11
 
@@ -22,14 +22,13 @@ class GlossaryPages {
22
22
  */
23
23
  #generateEntryPage(entry) {
24
24
  const title = entry.getTermName();
25
- let html = HTML.textElement("h1", title);
26
- html += HTML.wrap("div", Markdown.strToHTML(entry.getMarkdown()), {
25
+ let html = HTML.wrap("div", Markdown.strToHTML(entry.getMarkdown()), {
27
26
  class: "glossary",
28
27
  });
29
28
  this.#siteGenerator.writeTemplate(
30
29
  html,
31
30
  { title: title },
32
- Files.join(ENTRY_DIR, title + ".html"),
31
+ path.posix.join(ENTRY_DIR, title + ".html"),
33
32
  );
34
33
  }
35
34
 
@@ -49,13 +48,12 @@ class GlossaryPages {
49
48
  for (const entry of entries) {
50
49
  links.push(
51
50
  HTML.getLink(
52
- Files.join(ENTRY_DIR, entry.getHTMLFileName()),
51
+ path.posix.join(ENTRY_DIR, entry.getHTMLFileName()),
53
52
  entry.getTermName(),
54
53
  ),
55
54
  );
56
55
  }
57
- let html = HTML.wrap("h1", "Glossary");
58
- html += HTML.wrap("ol", HTML.arrayToLI(links));
56
+ const html = HTML.wrap("ol", HTML.arrayToLI(links));
59
57
  this.#siteGenerator.writeTemplate(
60
58
  html,
61
59
  { title: "Glossary" },
@@ -72,5 +70,3 @@ class GlossaryPages {
72
70
  this.#generateEntryPages();
73
71
  }
74
72
  }
75
-
76
- export { GlossaryPages };
@@ -1,5 +1,5 @@
1
1
  import { ExternalSites } from "../externalsites.js";
2
- import { GenericPage } from "../genericpage.js";
2
+ import { GenericPage } from "./pageGeneric.js";
3
3
  import { HTML } from "../html.js";
4
4
  import { HTMLTaxon } from "../htmltaxon.js";
5
5
  import { Sections } from "../taxonomy/families.js";
@@ -8,11 +8,11 @@ export class PageFamilyList extends GenericPage {
8
8
  #families;
9
9
 
10
10
  /**
11
- * @param {string} outputDir
11
+ * @param {import("../types.js").SiteGenerator} siteGenerator
12
12
  * @param {import("../types.js").Family[]} families
13
13
  */
14
- constructor(outputDir, families) {
15
- super(outputDir, "Families", "list_families");
14
+ constructor(siteGenerator, families) {
15
+ super(siteGenerator, "Families", "list_families");
16
16
  this.#families = families;
17
17
  }
18
18
 
@@ -28,7 +28,7 @@ export class PageFamilyList extends GenericPage {
28
28
  const taxa = sections[name];
29
29
 
30
30
  // Render the section page.
31
- new PageSection(this.getOutputDir(), name, taxa).render(
31
+ new PageSection(this.getSiteGenerator(), name, taxa).render(
32
32
  taxaColumns,
33
33
  );
34
34
 
@@ -69,13 +69,13 @@ export class PageFamilyList extends GenericPage {
69
69
  }
70
70
 
71
71
  /**
72
- * @param {string} outputDir
72
+ * @param {import("../types.js").SiteGenerator} siteGenerator
73
73
  * @param {import("../types.js").TaxaColDef[]} [taxaColumns]
74
74
  */
75
- renderPages(outputDir, taxaColumns) {
75
+ renderPages(siteGenerator, taxaColumns) {
76
76
  for (const family of this.#families) {
77
77
  if (family.getTaxa()) {
78
- new PageFamily(outputDir, family).render(taxaColumns);
78
+ new PageFamily(siteGenerator, family).render(taxaColumns);
79
79
  }
80
80
  }
81
81
  }
@@ -85,11 +85,11 @@ class PageFamily extends GenericPage {
85
85
  #family;
86
86
 
87
87
  /**
88
- * @param {string} outputDir
88
+ * @param {import("../types.js").SiteGenerator} siteGenerator
89
89
  * @param {import("../types.js").Family} family
90
90
  */
91
- constructor(outputDir, family) {
92
- super(outputDir, family.getName(), family.getBaseFileName());
91
+ constructor(siteGenerator, family) {
92
+ super(siteGenerator, family.getName(), family.getBaseFileName());
93
93
  this.#family = family;
94
94
  }
95
95
 
@@ -124,12 +124,12 @@ class PageSection extends GenericPage {
124
124
  #taxa;
125
125
 
126
126
  /**
127
- * @param {string} outputDir
127
+ * @param {import("../types.js").SiteGenerator} siteGenerator
128
128
  * @param {string} name
129
129
  * @param {import("../types.js").Taxon[]} taxa
130
130
  */
131
- constructor(outputDir, name, taxa) {
132
- super(outputDir, name, name);
131
+ constructor(siteGenerator, name, taxa) {
132
+ super(siteGenerator, name, name);
133
133
  this.#taxa = taxa;
134
134
  }
135
135
 
@@ -0,0 +1,78 @@
1
+ import path from "node:path";
2
+ import { Config } from "../config.js";
3
+ import { Files } from "../files.js";
4
+ import { HTMLFragments } from "../utils/htmlFragments.js";
5
+
6
+ export class GenericPage {
7
+ #siteGenerator;
8
+ #title;
9
+ #baseFileName;
10
+ #js;
11
+
12
+ /**
13
+ * @param {import("../types.js").SiteGenerator} siteGenerator
14
+ * @param {string} title
15
+ * @param {string} baseFileName
16
+ * @param {string} [js]
17
+ */
18
+ constructor(siteGenerator, title, baseFileName, js) {
19
+ this.#siteGenerator = siteGenerator;
20
+ this.#title = title;
21
+ this.#baseFileName = baseFileName;
22
+ this.#js = js;
23
+ }
24
+
25
+ getBaseFileName() {
26
+ return this.#baseFileName;
27
+ }
28
+
29
+ getDefaultIntro() {
30
+ let html = this.getFrontMatter();
31
+ return html + this.getMarkdown();
32
+ }
33
+
34
+ getFrontMatter() {
35
+ return this.#siteGenerator.getFrontMatter({
36
+ title: this.#title,
37
+ js: this.#js,
38
+ });
39
+ }
40
+
41
+ getMarkdown() {
42
+ const localTextPath = path.join(
43
+ "./data/intros",
44
+ `${this.#baseFileName}.md`,
45
+ );
46
+ const globalTextPath = path.join(
47
+ Config.getPackageDir(),
48
+ "./data/text/",
49
+ `${this.#baseFileName}.md`,
50
+ );
51
+ return (
52
+ HTMLFragments.getMarkdownSection(localTextPath) +
53
+ HTMLFragments.getMarkdownSection(globalTextPath)
54
+ );
55
+ }
56
+
57
+ getOutputDir() {
58
+ return this.#siteGenerator.getBaseDir();
59
+ }
60
+
61
+ getSiteGenerator() {
62
+ return this.#siteGenerator;
63
+ }
64
+
65
+ getTitle() {
66
+ return this.#title;
67
+ }
68
+
69
+ /**
70
+ * @param {string} html
71
+ */
72
+ writeFile(html) {
73
+ Files.write(
74
+ path.join(this.getOutputDir(), `${this.#baseFileName}.html`),
75
+ html,
76
+ );
77
+ }
78
+ }
@@ -1,5 +1,5 @@
1
1
  import { RarePlants } from "../rareplants.js";
2
- import { GenericPage } from "../genericpage.js";
2
+ import { GenericPage } from "./pageGeneric.js";
3
3
  import { HTML } from "../html.js";
4
4
  import { HTMLTaxon } from "../htmltaxon.js";
5
5
 
@@ -8,12 +8,12 @@ export class PageTaxon extends GenericPage {
8
8
  #taxon;
9
9
 
10
10
  /**
11
- * @param {string} outputDir
11
+ * @param {import("../types.js").SiteGenerator} siteGenerator
12
12
  * @param {import("../config.js").Config} config
13
13
  * @param {import("../types.js").Taxon} taxon
14
14
  */
15
- constructor(outputDir, config, taxon) {
16
- super(outputDir, taxon.getName(), taxon.getBaseFileName());
15
+ constructor(siteGenerator, config, taxon) {
16
+ super(siteGenerator, taxon.getName(), taxon.getBaseFileName());
17
17
  this.#config = config;
18
18
  this.#taxon = taxon;
19
19
  }
@@ -0,0 +1,53 @@
1
+ import { GenericPage } from "./pageGeneric.js";
2
+ import { HTML } from "../html.js";
3
+ import { HTMLTaxon } from "../htmltaxon.js";
4
+
5
+ export class PageTaxonList extends GenericPage {
6
+ /**
7
+ * @param {import("../types.js").SiteGenerator} siteGenerator
8
+ * @param {string} title
9
+ * @param {string} baseName
10
+ */
11
+ constructor(siteGenerator, title, baseName) {
12
+ super(siteGenerator, title, baseName);
13
+ }
14
+
15
+ /**
16
+ *
17
+ * @param {import("../types.js").Taxon[]} taxa
18
+ * @param {import("../types.js").TaxaColDef[]|undefined} columns
19
+ */
20
+ render(taxa, columns) {
21
+ let html = this.getDefaultIntro();
22
+
23
+ html += '<div class="wrapper">';
24
+
25
+ html += '<div class="section">';
26
+ html += HTMLTaxon.getTaxaTable(taxa, columns);
27
+ html += "</div>";
28
+
29
+ html += '<div class="section nobullet">';
30
+ html += HTML.textElement("h2", "Download");
31
+ html += "<ul>";
32
+ html +=
33
+ "<li>" +
34
+ HTML.getLink(
35
+ "./calflora_" + this.getBaseFileName() + ".txt",
36
+ "Calflora List",
37
+ ) +
38
+ "</li>";
39
+ html +=
40
+ "<li>" +
41
+ HTML.getLink(
42
+ "./inat_" + this.getBaseFileName() + ".txt",
43
+ "iNaturalist List",
44
+ ) +
45
+ "</li>";
46
+ html += "</ul>";
47
+ html += "</div>";
48
+
49
+ html += "</div>";
50
+
51
+ this.writeFile(html);
52
+ }
53
+ }