vowel 0.5.0 → 0.6.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vowel",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.1",
5
5
  "bin": "bin.js",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -58,6 +58,7 @@
58
58
  "remark-html": "^16.0.1",
59
59
  "remark-parse": "^11.0.0",
60
60
  "remark-rehype": "^11.1.2",
61
+ "sharp": "^0.35.3",
61
62
  "unified": "^11.0.5",
62
63
  "unist-util-find": "^3.0.0",
63
64
  "unist-util-is": "^6.0.0",
@@ -66,6 +67,7 @@
66
67
  "unist-util-stringify-position": "^4.0.0",
67
68
  "unist-util-visit": "^5.0.0",
68
69
  "unist-util-visit-parents": "^6.0.0",
70
+ "unpic": "^4.2.2",
69
71
  "url-metadata": "^5.4.1",
70
72
  "voot": "^0.1.0",
71
73
  "votive": "^0.4.0",
@@ -1,10 +1,38 @@
1
+ import { randomUUID } from "node:crypto"
1
2
  import fs from "fs/promises"
3
+ import sharp from 'sharp'
4
+ import path from "node:path"
5
+ import {createImagePath, imageSizes, imageExts } from "./../../utils.js"
2
6
 
3
7
  /** @import * as Votive from "votive" */
4
8
 
9
+
5
10
  /** @type {Votive.ProcessorWrite} */
6
11
  async function writeImage(destination, database, config) {
12
+ const { uuid, sourcePath } = destination.abstract
13
+
14
+ const defaultFormat = path.extname(sourcePath)
15
+
16
+ const image = sharp(destination.path)
17
+
18
+ const images = imageSizes.flatMap(size => {
19
+ [...imageExts, defaultFormat].map(ext => {
20
+ const filePath = createImagePath({
21
+ targetFilePath: sourcePath,
22
+ size,
23
+ ext,
24
+ targetDirectory: config.destinationFolder,
25
+ uuid
26
+ })
27
+
28
+ return image.clone().resize(size).toFile(filePath)
29
+ })
30
+ })
31
+
32
+ await Promise.all(images)
33
+
7
34
  const buffer = await fs.readFile(destination.path)
35
+
8
36
  return {
9
37
  data: buffer,
10
38
  }
@@ -12,11 +40,13 @@ async function writeImage(destination, database, config) {
12
40
 
13
41
  /** @type {Votive.ProcessorRead} */
14
42
  function readImagePath(string) {
43
+ const uuid =randomUUID()
15
44
 
16
45
  const data = {
17
46
  metadata: {},
18
47
  abstract: {
19
- path: string
48
+ sourcePath: string,
49
+ uuid
20
50
  }
21
51
  }
22
52
 
@@ -19,7 +19,7 @@ import { h } from 'hastscript'
19
19
  import { normalizeHeadings } from 'mdast-normalize-headings'
20
20
  import { readFileSync } from "fs"
21
21
  import { remove } from "unist-util-remove"
22
- import { testURL, testHashtags, createHashtagPage, toTitleCase, hashtagRegexSingle } from "./../../utils.js"
22
+ import { testURL, testHashtags, createHashtagPage, toTitleCase, hashtagRegexSingle, createImagePaths, imageSizes, imageExts } from "./../../utils.js"
23
23
  import { toHast } from 'mdast-util-to-hast'
24
24
  import { toString as hastToString } from 'hast-util-to-string'
25
25
  import { toString as mdastToString } from 'mdast-util-to-string'
@@ -27,6 +27,7 @@ import { unified } from "unified"
27
27
  import { visit } from "unist-util-visit"
28
28
  import toc from "@jsdevtools/rehype-toc"
29
29
  import slug from "rehype-slug"
30
+ import * as unpic from "unpic"
30
31
 
31
32
  const VOWEL_DIR = path.normalize(path.join(import.meta.dirname, "../../"))
32
33
 
@@ -50,7 +51,86 @@ User-agent: CCBot
50
51
  Disallow: /`
51
52
 
52
53
 
53
- function makeHead(metadata, url) {
54
+
55
+ /**
56
+ * @param {string} imagePath
57
+ * @param {Votive.Database} database
58
+ * @param {string} dependent
59
+ */
60
+ function createDynamicImage(imagePath, database, dependent, alt, itemprop) {
61
+ // TODO: Hardcode image height and width
62
+ const parsed = path.parse(imagePath);
63
+ const isURL = testURL(imagePath)
64
+ const isImg = !parsed.ext.search(/^.(png|jpeg|jpg)$/);
65
+
66
+ if (isURL) {
67
+ const parsedImageURL = unpic.parseUrl(imagePath)
68
+ if (parsedImageURL) {
69
+
70
+ const sources = [...imageExts, "jpeg"].map((format, index) => {
71
+
72
+ const isImg = index === imageExts.length
73
+ // TODO: Ignore images that have options already specified
74
+ const urls = imageSizes.map(size => {
75
+ return unpic.transformUrl({
76
+ format: format,
77
+ url: parsedImageURL.src,
78
+ provider: parsedImageURL.cdn,
79
+ width: size
80
+ })
81
+ })
82
+
83
+ const sizes = urls.map((url, index) => `${url} ${imageSizes[index]}w`).join(", ")
84
+
85
+ return h(isImg ? "img" : "source", {
86
+ type: "image/" + format,
87
+ srcset: sizes,
88
+ src: isImg && urls.at(-1),
89
+ loading: isImg && "lazy",
90
+ sizes: "100vw",
91
+ alt: isImg && alt
92
+ })
93
+ })
94
+
95
+ return h("picture", { itemprop: itemprop && "image" }, sources)
96
+ }
97
+ }
98
+
99
+ if (!isImg) return
100
+
101
+ const relativePath = imagePath.startsWith("/") ? path.relative("/", imagePath) : imagePath
102
+ const image = database.target.getWithTrackers(relativePath, dependent)
103
+ console.log({ image, relativePath })
104
+ if (!image) return
105
+ const formats = createImagePaths(image.abstract.sourcePath, "./", image.abstract.uuid)
106
+
107
+
108
+ const sources = formats.map((format, index) => {
109
+ const isImg = index === formats.length - 1
110
+
111
+ const sizes = format.map((size, index) => `/${size} ${imageSizes[index]}w`).join(", ")
112
+ const type = "image/" + path.extname(format[0]).slice(1)
113
+ return h(isImg ? "img" : "source", {
114
+ type,
115
+ srcset: sizes,
116
+ loading: isImg && "lazy",
117
+ src: isImg && "/" + format.at(-1),
118
+ sizes: "100vw",
119
+ alt: isImg && alt
120
+ })
121
+ })
122
+
123
+
124
+ return h("picture", { itemprop: itemprop && "image" }, sources)
125
+ }
126
+
127
+ /**
128
+ * @param {object} metadata
129
+ * @param {string} url
130
+ * @param {Votive.Database} database
131
+ * @param {Votive.VotiveConfig} config
132
+ */
133
+ function makeHead(metadata, url, database, config) {
54
134
 
55
135
  const treeMainHead = []
56
136
 
@@ -75,19 +155,16 @@ function makeHead(metadata, url) {
75
155
  )
76
156
  }
77
157
 
78
- if (metadata.image) treeMainHead.push(
79
- h("img", {
80
- src: metadata.image,
81
- itemprop: "image"
82
- })
83
- )
84
158
 
85
- if (!metadata.image && metadata.first_image && url) treeMainHead.push(
86
- h("img", {
87
- src: metadata.first_image,
88
- itemprop: "image"
89
- })
90
- )
159
+
160
+
161
+ if (metadata.image || (metadata.first_image && url)) {
162
+ const metaImage = metadata.image || metadata.first_image
163
+
164
+ treeMainHead.push(
165
+ createDynamicImage(metaImage, database, url, null, true)
166
+ )
167
+ }
91
168
 
92
169
  if (metadata.fm_description) treeMainHead.push(
93
170
  h("p",
@@ -991,7 +1068,12 @@ function writeFile(destination, database, config) {
991
1068
 
992
1069
  const treeContentSlugged = slugger.runSync(treeContent)
993
1070
 
994
- const treeMainHead = makeHead(metadata)
1071
+ const treeMainHead = makeHead(metadata, null, database, config)
1072
+
1073
+ visit(treeContent, { tagName: "img" }, (node, index, parent) => {
1074
+ const { src, alt } = node.properties
1075
+ parent.children.splice(index, 1, createDynamicImage(src, database, destination.path, alt))
1076
+ })
995
1077
 
996
1078
  visit(treeContent, testPaths, ({ children: [child] }, i, p) => {
997
1079
 
@@ -1011,7 +1093,7 @@ function writeFile(destination, database, config) {
1011
1093
  const target = database.target.getWithTrackers(targetFilePath, destination.path)
1012
1094
 
1013
1095
  if (target) {
1014
- const article = h('article', makeHead(target.metadata, target.metadata.prettyURL))
1096
+ const article = h('article', makeHead(target.metadata, target.metadata.prettyURL, database, config))
1015
1097
 
1016
1098
  p.children.splice(i, 1, article)
1017
1099
  }
@@ -1046,7 +1128,7 @@ function writeFile(destination, database, config) {
1046
1128
  const list = h(`ul.${dirClasses}`,
1047
1129
  targets.map(target => {
1048
1130
  return h('li',
1049
- h('article', makeHead(target.metadata, target.metadata.prettyURL))
1131
+ h('article', makeHead(target.metadata, target.metadata.prettyURL, database, config))
1050
1132
  )
1051
1133
  })
1052
1134
  )
package/utils.js CHANGED
@@ -1,7 +1,47 @@
1
1
  import {fromMarkdown} from 'mdast-util-from-markdown'
2
2
  import {toHast} from 'mdast-util-to-hast'
3
3
  import {toHtml} from 'hast-util-to-html'
4
+ import path from "node:path"
5
+ import { randomUUID } from "node:crypto"
4
6
 
7
+ export const imageSizes = [414, 768, 1440, 1920]
8
+ export const imageExts = ["avif", "webp"]
9
+
10
+ /**
11
+ * @param {object} params
12
+ * @property {string} targetFilePath
13
+ * @property {number} size
14
+ * @property {string} targetDirectory
15
+ * @property {string} uuid
16
+ */
17
+ export function createImagePath({ targetFilePath, size, ext, targetDirectory, uuid }) {
18
+ const { dir, name } = path.parse(targetFilePath)
19
+ return path.format({
20
+ dir: path.relative(".", path.normalize(path.join(targetDirectory, dir))),
21
+ name: `${name}-${uuid}-${size}`,
22
+ ext
23
+ })
24
+ }
25
+
26
+ /**
27
+ * @param {string} targetFilePath
28
+ * @param {string} targetDirectory
29
+ * @param {string} uuid
30
+ */
31
+ export function createImagePaths(targetFilePath, targetDirectory, uuid) {
32
+ const defaultExt = path.extname(targetFilePath)
33
+ return [...imageExts, defaultExt].map(ext => {
34
+ return imageSizes.map(size => {
35
+ return createImagePath({
36
+ targetFilePath,
37
+ size,
38
+ ext,
39
+ targetDirectory,
40
+ uuid
41
+ })
42
+ })
43
+ })
44
+ }
5
45
 
6
46
  /** @param {string} text */
7
47
  export function testURL(text) {