Bladeren bron

Add index/listing

Craig Fletcher 2 weken geleden
bovenliggende
commit
66cf94a211
5 gewijzigde bestanden met toevoegingen van 190 en 49 verwijderingen
  1. 12 0
      package-lock.json
  2. 1 0
      package.json
  3. 59 32
      src/defaults.js
  4. 82 14
      src/lib.js
  5. 36 3
      src/processors.js

+ 12 - 0
package-lock.json

@@ -14,6 +14,7 @@
         "gray-matter": "^4.0.3",
         "handlebars": "^4.7.8",
         "html-minifier-terser": "^7.2.0",
+        "lodash-es": "^4.17.22",
         "marked": "^15.0.6",
         "marked-code-preview": "^1.3.7",
         "safe-stable-stringify": "^2.5.0",
@@ -4217,6 +4218,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/lodash-es": {
+      "version": "4.17.22",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz",
+      "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==",
+      "license": "MIT"
+    },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -9789,6 +9796,11 @@
         "p-locate": "^5.0.0"
       }
     },
+    "lodash-es": {
+      "version": "4.17.22",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz",
+      "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q=="
+    },
     "lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "gray-matter": "^4.0.3",
     "handlebars": "^4.7.8",
     "html-minifier-terser": "^7.2.0",
+    "lodash-es": "^4.17.22",
     "marked": "^15.0.6",
     "marked-code-preview": "^1.3.7",
     "safe-stable-stringify": "^2.5.0",

+ 59 - 32
src/defaults.js

@@ -6,22 +6,23 @@ import {
   optimiseSvg,
   renderTemplate,
   renderMarkdownToHtml,
+  generateTaxonomy,
 } from "./processors.js"
 
 export const tasks = [
-  {
-    name: "images",
-    inputFiles: [{ pattern: "images/content/*.jpg" }],
-    stripPaths: ["images/content/"],
-    outputDir: "images/",
-    outputFileExtension: ".webp",
-    imageSizes: [
-      "640w", "768w", "1024w", "1366w", "1600w", "1920w", "2560w",
-    ],
-    quality: 80,
-    processor: imageToWebP,
-  },
   [
+    {
+      name: "images",
+      inputFiles: [{ pattern: "images/content/*.jpg" }],
+      stripPaths: ["images/content/"],
+      outputDir: "images/",
+      outputFileExtension: ".webp",
+      imageSizes: [
+        "640w", "768w", "1024w", "1366w", "1600w", "1920w", "2560w",
+      ],
+      quality: 80,
+      processor: imageToWebP,
+    },
     {
       name: "styles",
       inputFiles: [{ pattern: "styles/**/*.scss", ignore: "**/_*.scss" }],
@@ -51,30 +52,56 @@ export const tasks = [
       outputDir: "static/meta/",
       processor: generateFavicons,
     },
-    {
-      name: "markdown",
-      inputFiles: [{ pattern: "markdown/*.md" }],
-      stripPaths: ["markdown/"],
-      outputFileExtension: ".html",
-      processor: renderMarkdownToHtml,
-    },
   ],
   {
-    name: "includes",
-    inputFiles: [{ pattern: "includes/*.hbs" }],
-    stripPaths: ["includes/"],
+    name: "markdown",
+    inputFiles: [{ pattern: "markdown/*.md" }],
+    stripPaths: ["markdown/"],
     outputFileExtension: ".html",
-    processor: renderTemplate,
-  },
-  {
-    name: "stateSelectorTest",
-    stateSelectors: ["resources.markdown"],
-    skipCache: true,
-    logLevel: "debug",
-    processor: renderTemplate,
-    writeOut: true,
-    templateDirs: ["templates/", "~/.rhedyn/templates/"],
+    processor: renderMarkdownToHtml,
   },
+  [
+    {
+      name: "taxonomy",
+      stateSelectors: ["resources.markdown"],
+      processor: generateTaxonomy,
+      expand: false,
+      indexOn: "tags",
+      orderBy: "date",
+      properties: [
+        "title", "href", "date", "author",
+      ],
+      sortAscending: false,
+      skipCache: true,
+    },
+    {
+      name: "includes",
+      inputFiles: [{ pattern: "includes/*.hbs" }],
+      stripPaths: ["includes/"],
+      outputFileExtension: ".html",
+      processor: renderTemplate,
+    },
+  ],
+  [
+    {
+      name: "render pages",
+      stateSelectors: ["resources.markdown"],
+      processor: renderTemplate,
+      writeOut: true,
+      templateDirs: ["templates/", "~/.rhedyn/templates/"],
+    },
+    {
+      name: "render indexes",
+      stateSelectors: ["resources.taxonomy.taxonomy.detail"],
+      processor: renderTemplate,
+      writeOut: true,
+      template: "index",
+      templateDirs: ["templates/", "~/.rhedyn/templates/"],
+      outputFileExtension: ".html",
+      outputDir: "by-tag/",
+      buildFilePath: true,
+    },
+  ],
 ]
 
 export const opts = {

+ 82 - 14
src/lib.js

@@ -120,12 +120,14 @@ async function runTask({ meta, config, jobId }) {
   return taskResult
 }
 
-async function expandFileTask(patternsToInclude, config, meta) {
-  const filesToProcess = await readFilesByGlob(patternsToInclude)
+function selectFiles(patternsToInclude, config) {
   const pathsToStrip = (config.stripPaths || []).map(path => expandTilde(path))
   const outputDir = config.outputDir || ""
-  return await Promise.all(
-    filesToProcess.map(async filePath => {
+
+  return async meta => {
+    const filesToProcess = await readFilesByGlob(patternsToInclude)
+
+    return filesToProcess.map(filePath => {
       const fileOutputPath = path.join(
         meta.opts.outDir,
         outputDir,
@@ -135,38 +137,80 @@ async function expandFileTask(patternsToInclude, config, meta) {
         ),
       )
 
-      const fileOutputDir = path.dirname(fileOutputPath)
-      const jobConfig = {
-        ...config,
+      return {
         filePath,
-        fileOutputDir,
         fileOutputPath,
+        fileOutputDir: path.dirname(fileOutputPath),
       }
+    })
+  }
+}
+
+async function expandFileTask(patternsToInclude, config, meta) {
+  const filesToProcess = await selectFiles(patternsToInclude, config)(meta)
+
+  return await Promise.all(
+    filesToProcess.map(async fileJob => {
+      const jobConfig = {
+        ...config,
+        ...fileJob,
+      }
+
       return runTask({
         meta,
         config: jobConfig,
-        jobId: `${config.name} @ ${filePath}`,
+        jobId: `${config.name} @ ${fileJob.filePath}`,
       })
     }),
   )
 }
 
-async function expandStateTask(stateToExpand, config, meta) {
-  const stateToProcess = stateToExpand
+function selectState(stateToSelect, meta) {
+  return stateToSelect
     .map(property => {
       const values = getValueAtPath(meta, property)
       const expandedValues = Array.isArray(values)
         ? values
         : Object.values(values)
-      return expandedValues.map((value, index) => ({ property, index, value }))
+      const keys = Object.keys(values)
+      return expandedValues.map((value, index) => ({
+        property,
+        index,
+        value,
+        key: keys[index],
+      }))
     })
     .flat()
+}
 
+async function expandStateTask(stateToExpand, config, meta) {
+  const stateToProcess = selectState(stateToExpand, meta)
+  const pathsToStrip = (config.stripPaths || []).map(path => expandTilde(path))
+  const outputDir = config.outputDir || ""
   return await Promise.all(
-    stateToProcess.map(async stateJob => {
+    stateToProcess.map(async (stateJob, index) => {
+      const decorations = {
+        ...(Array.isArray(stateJob.value)
+          ? { inputs: stateJob.value }
+          : stateJob.value.detail),
+
+        ...(config.buildFilePath
+          ? {
+            fileOutputPath: path.join(
+              meta.opts.outDir,
+              outputDir,
+              replaceFileExtension(
+                removeBasePaths(pathsToStrip, stateJob.key || String(index)),
+                config.outputFileExtension,
+              ),
+            ),
+          }
+          : {}),
+      }
       const jobConfig = {
         ...config,
-        ...stateJob.value.detail,
+        ...decorations,
+        stateKey: stateJob.key,
       }
       return runTask({
         meta,
@@ -182,10 +226,34 @@ export async function expandAndRunTask(meta, config) {
   const patternsToInclude = [...(config?.inputFiles || []), ...includes]
 
   if (patternsToInclude.length) {
+    if (config.expand === false) {
+      const inputs = selectFiles(patternsToInclude, config)
+      const jobId = config.jobId || config.name
+      const taskResult = await runTask({
+        meta,
+        config: { ...config, inputs },
+        jobId,
+      })
+      return [taskResult]
+    }
+
     return expandFileTask(patternsToInclude, config, meta)
   }
 
   if (config.stateSelectors) {
+    if (config.expand === false) {
+      const inputs = selectState(config.stateSelectors, meta).map(
+        stateItem => stateItem.value.detail,
+      )
+      const jobId = config.jobId || config.name
+      const taskResult = await runTask({
+        meta,
+        config: { ...config, inputs },
+        jobId,
+      })
+      return [taskResult]
+    }
+
     return expandStateTask(config.stateSelectors, config, meta)
   }
 

+ 36 - 3
src/processors.js

@@ -17,6 +17,7 @@ import sharp from "sharp"
 import path from "path"
 import { minify } from "html-minifier-terser"
 import favicons from "favicons"
+import _ from "lodash-es"
 
 const templateCache = new Map()
 
@@ -123,9 +124,10 @@ export async function renderMarkdownToHtml({ config, meta }) {
 
   const renderer = createMarkdownRenderer(meta)
   const html = renderer(markdown)
+  const detail = { ...data, href, content: html, fileOutputPath }
 
   return {
-    detail: { ...data, href, content: html, fileOutputPath },
+    detail,
     ref: slugifyString(filePath),
   }
 }
@@ -187,7 +189,7 @@ export async function compileSass({ config, meta }) {
     paths: [fileOutputPath],
     ref: slugifyString(fileOutputPath),
     detail: {
-      href: fileOutputPath.replace(meta.opts.outDir, ""),
+      href: fileOutputPath.replace(meta.opts.outDir, "/"),
     },
     deps: {
       paths: [...result.loadedUrls.map(item => item.pathname)],
@@ -332,9 +334,40 @@ export async function generateFavicons({ meta, config }) {
           getCleanPath(path.join(fileOutputDir, file.name), meta),
         ),
       ],
-      ref: "metatags",
+      ref: config.name,
     }
   } catch (error) {
     throw new Error(`Failed to generate favicons: ${error.message}`)
   }
 }
+
+export async function generateTaxonomy({ config }) {
+  const allValues = config.inputs.reduce((values, curr) => {
+    return values.union(new Set(curr[config.indexOn]))
+  }, new Set())
+  const orderBy = config.orderBy || "date"
+  const sortedInputs = config.sortAscending
+    ? _.sortBy(config.inputs, orderBy)
+    : _.sortBy(config.inputs, orderBy).reverse()
+  const taxonomy = allValues.values().reduce((groups, currentGroup) => {
+    const grouped = {
+      ...groups,
+      [currentGroup]: sortedInputs
+        .filter(item => item[config.indexOn].includes(currentGroup))
+        .map(item => {
+          const entry = config.properties
+            ? config.properties.reduce(
+              (ent, prop) => ({ ...ent, [prop]: item[prop] }),
+              {},
+            )
+            : item
+          return entry
+        }),
+    }
+    return grouped
+  }, {})
+  return {
+    detail: taxonomy,
+    ref: config.name,
+  }
+}