vowel 0.7.1-beta.0 → 0.7.3
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/LICENSE +21 -0
- package/README.md +41 -239
- package/bin.js +91 -31
- package/cli.js +32 -0
- package/config.js +12 -0
- package/extractDate.js +1 -1
- package/package.json +8 -34
- package/pathDate.js +69 -0
- package/plugins/assets/index.js +1 -1
- package/plugins/atproto/body.js +3 -1
- package/plugins/atproto/index.js +5 -45
- package/plugins/atproto/publish.js +42 -11
- package/plugins/atproto/record.js +47 -4
- package/plugins/deploy-cloudflare/index.js +4 -1
- package/plugins/export/index.js +131 -0
- package/plugins/html/bundle/index.js +101 -44
- package/plugins/html/editor/Editor.svelte +9 -5
- package/plugins/html/editor/create-session.js +3 -1
- package/plugins/html/editor/directives.js +12 -0
- package/plugins/html/editor/frontmatter.js +77 -12
- package/plugins/html/editor/ingest.js +29 -31
- package/plugins/html/editor/main.js +5 -1
- package/plugins/html/editor/serialize.js +8 -4
- package/plugins/html/editor/socket.js +34 -4
- package/plugins/html/image.js +88 -27
- package/plugins/html/index.js +237 -109
- package/plugins/html/partials.js +41 -17
- package/plugins/html/socialLinks.js +13 -11
- package/plugins/html/writeRules.js +234 -42
- package/plugins/images/favicon.png +0 -0
- package/plugins/images/index.js +69 -8
- package/plugins/markdown/colorScheme.js +70 -14
- package/plugins/markdown/frontmatter.js +18 -8
- package/plugins/markdown/index.js +94 -25
- package/plugins/markdown/metadata.js +132 -32
- package/plugins/markdown/typography.js +11 -11
- package/plugins/styles/index.js +4 -4
- package/plugins/xml/index.js +17 -10
- package/stylesheets/DefaultStyles.css +163 -108
- package/stylesheets/TypographyStyles.css +148 -81
- package/utils.js +74 -21
|
@@ -51,10 +51,12 @@ export default function openSocket() {
|
|
|
51
51
|
|
|
52
52
|
const target = JSON.parse(e.data)
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
// A file that isn't a page can only matter to this page as something
|
|
55
|
+
// it loads: an image the page named before its file existed.
|
|
56
|
+
if (target.extension !== ".html") {
|
|
57
|
+
retryImages("/" + target.path)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
58
60
|
|
|
59
61
|
const regex = new RegExp(window.location.pathname + "(index)?(\\.html)")
|
|
60
62
|
if (!("/" + target.path).match(regex)) return
|
|
@@ -185,6 +187,34 @@ function patchBody(body, nextBody) {
|
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/**
|
|
191
|
+
* A page can arrive before the image files it names: an image's
|
|
192
|
+
* derivatives are encoded after the page is written, and a browser never
|
|
193
|
+
* asks again for an image that failed. When a file lands, every image
|
|
194
|
+
* that failed and names it is replaced with a copy of itself, which
|
|
195
|
+
* asks again. A <picture> is copied whole, so its <source>s are chosen
|
|
196
|
+
* from again too.
|
|
197
|
+
* @param {string} url - the file's url, from the root
|
|
198
|
+
*/
|
|
199
|
+
function retryImages(url) {
|
|
200
|
+
const failed = [...document.images].filter(img => img.complete && img.naturalWidth === 0)
|
|
201
|
+
const elements = failed.map(img => img.parentElement?.tagName === "PICTURE" ? img.parentElement : img)
|
|
202
|
+
const naming = elements.filter(element => urlsOf(element).includes(url))
|
|
203
|
+
for (const element of naming) element.replaceWith(element.cloneNode(true))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Every url an <img> or a <picture> could load.
|
|
208
|
+
* @param {Element} element
|
|
209
|
+
*/
|
|
210
|
+
function urlsOf(element) {
|
|
211
|
+
const nodes = [element, ...element.querySelectorAll("source, img")]
|
|
212
|
+
return nodes.flatMap(node => {
|
|
213
|
+
const candidates = (node.getAttribute("srcset") ?? "").split(",").map(candidate => candidate.trim().split(/\s+/)[0])
|
|
214
|
+
return [node.getAttribute("src"), ...candidates].filter(Boolean)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
188
218
|
/**
|
|
189
219
|
* The client adds elements that aren't the build's: the editor's own
|
|
190
220
|
* <script type="module">, and whatever Svelte mounts into the body (the
|
package/plugins/html/image.js
CHANGED
|
@@ -1,10 +1,71 @@
|
|
|
1
|
-
import { testURL,
|
|
1
|
+
import { testURL, imageSizes, imageExts } from "./../../utils.js"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { h } from 'hastscript'
|
|
4
4
|
import * as unpic from "unpic"
|
|
5
5
|
|
|
6
6
|
/** @import * as Votive from "votive" */
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* The image target a path names, from the root. Lowercased: every vowel
|
|
10
|
+
* target path is (config.js's router), and the author may well have
|
|
11
|
+
* typed the file's real name.
|
|
12
|
+
* @param {string} imagePath
|
|
13
|
+
* @param {import("votive").PluginAPI} api
|
|
14
|
+
*/
|
|
15
|
+
function findImage(imagePath, api) {
|
|
16
|
+
const relativePath = (imagePath.startsWith("/") ? path.relative("/", imagePath) : imagePath).toLowerCase()
|
|
17
|
+
return api.target(relativePath)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The derivative to link where one file goes rather than a srcset: in
|
|
22
|
+
* the image's own format, at `size` (one of imageSizes). Undefined for
|
|
23
|
+
* an image without derivatives (plugins/images: a format that isn't
|
|
24
|
+
* optimised, the root favicon.png, or an image not read yet).
|
|
25
|
+
* @param {any} image - the image's target
|
|
26
|
+
* @param {number} size
|
|
27
|
+
*/
|
|
28
|
+
function derivativeURL(image, size) {
|
|
29
|
+
const format = path.extname(image.path).slice(1)
|
|
30
|
+
const derivative = image.metadata?.derivatives?.find(entry => entry.format === format && entry.size === size)
|
|
31
|
+
if (!derivative) return
|
|
32
|
+
return "/" + derivative.path
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The url to put where an image path goes as one file - og:image, the
|
|
37
|
+
* logo, an icon. A png or jpeg is not published at full size
|
|
38
|
+
* (plugins/images), so the path names a derivative; anything else, an
|
|
39
|
+
* external url included, is returned as it is. A tracked read, so the
|
|
40
|
+
* page is rewritten when the image is read or changes.
|
|
41
|
+
* @param {string | undefined} imagePath - from the root
|
|
42
|
+
* @param {import("votive").PluginAPI} api
|
|
43
|
+
* @param {number} size - one of imageSizes
|
|
44
|
+
*/
|
|
45
|
+
function imageURL(imagePath, api, size) {
|
|
46
|
+
if (!imagePath || testURL(imagePath) || !/\.(png|jpe?g)$/i.test(imagePath)) return imagePath
|
|
47
|
+
const image = findImage(imagePath, api)
|
|
48
|
+
if (!image) return imagePath
|
|
49
|
+
return derivativeURL(image, size) ?? imagePath
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The page image's dominant colour as a style for the element that
|
|
54
|
+
* holds it - a page's <main>, a card's <article> - so CSS outside the
|
|
55
|
+
* image can use it (text that contrasts with it, say). The <picture>
|
|
56
|
+
* carries it too, for its own placeholder. Nothing here decides that
|
|
57
|
+
* anything should use it. Undefined for no image, an external one, or
|
|
58
|
+
* one not read yet.
|
|
59
|
+
* @param {string | undefined} imagePath - from the root
|
|
60
|
+
* @param {import("votive").PluginAPI} api
|
|
61
|
+
*/
|
|
62
|
+
function dominantStyle(imagePath, api) {
|
|
63
|
+
if (!imagePath || testURL(imagePath)) return
|
|
64
|
+
const dominant = findImage(imagePath, api)?.metadata.dominant
|
|
65
|
+
if (!dominant) return
|
|
66
|
+
return `--color-image-dominant: ${dominant}`
|
|
67
|
+
}
|
|
68
|
+
|
|
8
69
|
/**
|
|
9
70
|
* @param {string} imagePath
|
|
10
71
|
* @param {import("votive").PluginAPI} api
|
|
@@ -45,42 +106,39 @@ function createDynamicImage(imagePath, api, alt, itemprop) {
|
|
|
45
106
|
})
|
|
46
107
|
})
|
|
47
108
|
|
|
48
|
-
|
|
49
|
-
// path cannot be recovered from them. Carried here rather than as a
|
|
50
|
-
// <source>, which is a loading candidate a browser could select.
|
|
51
|
-
return h("picture", { itemprop: itemprop && "image", "data-original": imagePath }, sources)
|
|
109
|
+
return h("picture", { itemprop: itemprop && "image" }, sources)
|
|
52
110
|
}
|
|
53
111
|
}
|
|
54
112
|
|
|
55
113
|
if (!isImg) return
|
|
56
114
|
|
|
57
|
-
|
|
58
|
-
// the author may well have typed the file's real name.
|
|
59
|
-
const relativePath = (imagePath.startsWith("/") ? path.relative("/", imagePath) : imagePath).toLowerCase()
|
|
60
|
-
const image = api.target(relativePath)
|
|
115
|
+
const image = findImage(imagePath, api)
|
|
61
116
|
if (!image) return
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
if (!
|
|
67
|
-
|
|
117
|
+
// An image without derivatives is published as it is (the root
|
|
118
|
+
// favicon.png), and a plain <img> links it. The tracked read of
|
|
119
|
+
// `derivatives` rewrites this page if they arrive.
|
|
120
|
+
const { derivatives } = image.metadata
|
|
121
|
+
if (!derivatives) return h("img", { src: "/" + image.path, alt, loading: "lazy" })
|
|
122
|
+
// One group per format, in the order the read lists them, which is
|
|
123
|
+
// <picture> order: avif, webp, then the image's own format as the <img>.
|
|
124
|
+
const formats = [...new Set(derivatives.map(entry => entry.format))]
|
|
125
|
+
.map(format => derivatives.filter(entry => entry.format === format))
|
|
68
126
|
|
|
69
127
|
// Intrinsic size, measured at read (plugins/images): the browser
|
|
70
128
|
// reserves the right box before the bytes arrive, and the reset's
|
|
71
129
|
// `img { height: auto }` keeps the ratio when the width is capped.
|
|
72
|
-
const { width, height, dominant } = image.metadata
|
|
130
|
+
const { width, height, dominant, placeholder } = image.metadata
|
|
73
131
|
|
|
74
132
|
const sources = formats.map((format, index) => {
|
|
75
133
|
const isImg = index === formats.length - 1
|
|
76
134
|
|
|
77
|
-
const sizes = format.map(
|
|
78
|
-
const type = "image/" +
|
|
135
|
+
const sizes = format.map(entry => `/${entry.path} ${entry.size}w`).join(", ")
|
|
136
|
+
const type = "image/" + format[0].format
|
|
79
137
|
return h(isImg ? "img" : "source", {
|
|
80
138
|
type,
|
|
81
139
|
srcset: sizes,
|
|
82
140
|
loading: isImg && "lazy",
|
|
83
|
-
src: isImg && "/" + format.at(-1),
|
|
141
|
+
src: isImg && "/" + format.at(-1).path,
|
|
84
142
|
sizes: "100vw",
|
|
85
143
|
width: isImg && width,
|
|
86
144
|
height: isImg && height,
|
|
@@ -88,17 +146,20 @@ function createDynamicImage(imagePath, api, alt, itemprop) {
|
|
|
88
146
|
})
|
|
89
147
|
})
|
|
90
148
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
// Two values for a stylesheet to show behind the image while it loads:
|
|
150
|
+
// its dominant colour, and a tiny blurry copy (plugins/images; none
|
|
151
|
+
// for an image with transparency). Nothing here decides that it should.
|
|
152
|
+
const style = [
|
|
153
|
+
dominant && `--color-image-dominant: ${dominant}`,
|
|
154
|
+
// Unquoted: base64 has no character url() needs quoted, and the
|
|
155
|
+
// minifier would spell each quote as `"`.
|
|
156
|
+
placeholder && `--url-image-placeholder: url(${placeholder})`
|
|
157
|
+
].filter(Boolean).join("; ")
|
|
97
158
|
return h("picture", {
|
|
98
159
|
itemprop: itemprop && "image",
|
|
99
|
-
|
|
100
|
-
style: dominant && `--dominant: ${dominant}`
|
|
160
|
+
style: style || undefined
|
|
101
161
|
}, sources)
|
|
102
162
|
}
|
|
103
163
|
|
|
164
|
+
export { dominantStyle, imageURL, derivativeURL }
|
|
104
165
|
export default createDynamicImage
|