-
Notifications
You must be signed in to change notification settings - Fork 0
/
.eleventy.js
315 lines (261 loc) · 11.8 KB
/
.eleventy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
const articleOrder = {}
articleOrder.vi2022 = require("./src/_data/article-order-vi-2022.js")
articleOrder.en2022 = require("./src/_data/article-order-en-2022.js")
articleOrder.en2023 = require("./src/_data/article-order-en-2023.js")
articleOrder.vi2023 = require("./src/_data/article-order-vi-2023.js")
articleOrder.en2024 = require("./src/_data/article-order-en-2024.js")
articleOrder.vi2024 = require("./src/_data/article-order-vi-2024.js")
const { EleventyRenderPlugin } = require("@11ty/eleventy")
const fs = require('fs')
const sizeOf = require('image-size')
const path = require("path")
const slugify = require('slugify')
// const sharp = require("sharp")
const Image = require("@11ty/eleventy-img")
const srcPath = "src/media/originals"
const calligraphyPath = "calligraphy/article-titles/"
const calligraphyPath2023 = "calligraphy2023/article-titles/"
const calligraphyPath2024 = "calligraphy2024/article-titles/"
const parallelVerses2023path = "media/originals/passthroughCopies/ParallelVerses2023/"
const parallelVerses2024path = "media/originals/passthroughCopies/ParallelVerses2024/"
var articleTitleCalligraphies = fs.readdirSync(`src/media/originals/${calligraphyPath}`)
var articleTitleCalligraphies2023 = fs.readdirSync(`src/media/originals/${calligraphyPath2023}`)
var articleTitleCalligraphies2024 = fs.readdirSync(`src/media/originals/${calligraphyPath2024}`)
var parallelVerses2023 = {
dir: parallelVerses2023path,
list: fs.readdirSync(`src/${parallelVerses2023path}`)
}
var parallelVerses2024 = {
dir: parallelVerses2024path,
list: fs.readdirSync(`src/${parallelVerses2024path}`)
}
let firstRun = true
async function imageShortcode(src, optClasses = "", imgLabel = "") {
let result = await imageData(src)
// Previous. Changed in order to crop images with overflow hidden. Needed to add another container to allow figcaption to stay visible
// let html = `<figure id="${result.autoId}" class="${optClasses}"><img src="${result.srcAttribute}" decoding="async">${imgLabel != "" ? `<figcaption>${imgLabel}</figcaption>` : ""}</figure>`
let html = `<figure id="${result.autoId}" class="${optClasses}"><div><img src="${result.srcAttribute}" decoding="async"></div>${imgLabel != "" ? `<figcaption>${imgLabel}</figcaption>` : ""}</figure>`
// img loading="lazy" is buggy! stops chrome from running pagedjs
return html
}
async function imageSrcShortcode(src) {
let result = await imageData(src)
return result.srcAttribute
}
async function imageData(src) {
// use images that are already build without going through the slow Image()
let fastProcess = true
let justCopy = false
// ~2600px width/A4 = 300 PPI
// ~4340px width/A4 = 500 PPI
// let maxWidth = 500
// let quality = 60
let maxWidth = 1500
let quality = 60
// let maxWidth = 5000
// let quality = 96
// '24 EN/VN: does not work anymore ...
// let maxWidth = 5500
let imgFormat = "jpeg"
// when the input image is a webp, the output image should also be webp, because jpeg does not support transparency
if (src.match(/\.webp$/g)) {
imgFormat = "webp"
}
let dryRun = false
let srcFull = path.join(srcPath, src)
let destPath = justCopy ? "/media_copy/" : `/media_${maxWidth}_q${quality}/`
let parsed = path.parse(src)
let outputDir = parsed.dir ? path.join(`docs/${destPath}`, parsed.dir) : `docs/${destPath}`
// the processed output image names by eleventy-img are randomised. when we want to speed up processing, we need to keep the original name
let destFileSameName = path.join(outputDir, parsed.base)
let data = { filename: parsed.base }
let options = {
formats: [imgFormat, "svg"], /* jpeg, png, webp, gif, tiff, avif */
outputDir: outputDir,
// this could be multiple sizes
widths: [maxWidth],
dryRun: dryRun,
sharpOptions: {},
// https://sharp.pixelplumbing.com/api-output#webp
sharpWebpOptions: { quality: quality, },
sharpJpegOptions: { quality: quality, },
svgShortCircuit: true
}
try {
// Image.statsSync doesn’t generate any files, but will tell you where the asynchronously generated files will end up!
// let metadata = await Image(srcFull, options)
// Image.statsSync(srcFull, options)
// console.log("processing:", src)
if (!fastProcess || !fs.existsSync(destFileSameName)) {
// only process images with Image() if the maxWidth is exceeded
const dimensions = sizeOf(srcFull)
if (justCopy || (dimensions && dimensions.width < maxWidth)) {
fs.mkdirSync(outputDir, { recursive: true }, (err) => {
if (err) throw err
})
console.log("COPYING: ", srcFull, " -> ", destFileSameName)
fs.copyFileSync(srcFull, destFileSameName, fs.constants.COPYFILE_EXCL)
} else {
/* metadata:
{
svg: [],
jpeg: [
{
format: 'jpeg',
width: 400,
height: 400,
url: '/img/oUr82sN_M--400.jpeg',
sourceType: 'image/jpeg',
srcset: '/img/oUr82sN_M--400.jpeg 400w',
filename: 'oUr82sN_M--400.jpeg',
outputPath: 'docs/media_5000_q96/build/article/br-minh-hy/oUr82sN_M--400.jpeg',
size: 137473
},
{
...
}
]
} */
let metadata = await Image(srcFull, options)
// console.log(metadata)
if(metadata.svg.length) {
data = metadata.svg[metadata.svg.length - 1]
} else {
data = metadata[imgFormat][metadata[imgFormat].length - 1]
}
if (fastProcess && !fs.existsSync(destFileSameName)) {
console.log("RENAMING: ", data.outputPath, " -> ", destFileSameName)
fs.renameSync(data.outputPath, destFileSameName)
data.outputPath = destFileSameName
data.filename = parsed.base
}
}
}
let result = {
autoId: slugify(`${parsed.dir}/${parsed.name}`, { strict: true }),
srcAttribute: path.join(destPath, parsed.dir, data.filename)
}
return result
} catch (err) {
console.error(src, err)
return ""
}
}
function idMap(prefix, items, suffix = '') {
return items.map(e => `${prefix}${e[0]}${suffix}`)
}
// https://rbyte.github.io/spreadFn/
const spreadFn = (y) => y === 0 ? (x) => x : (y > 0
? (x) => Math.atan( (x-0.5)*y*2 )/Math.atan(y)/2+0.5
: (x) => Math.tan( (x-0.5)*Math.atan(-y)*2 )/-y/2+0.5)
function easingGradient(start = 0, end = 100, stops = 2, smoothness = 3) {
var fn = spreadFn(smoothness)
var range = end - start
var result = []
for (let i = 0; i < stops; i++) {
var posR = 1 / (stops-1) * i
posR = posR.toFixed(2)
var posFn = fn(posR).toFixed(2)
result.push(`rgba(0,0,0, ${posFn}) ${start + range * posR}%`)
}
return result.join(',\n')
}
module.exports = function(eleventyConfig) {
// https://www.11ty.dev/docs/plugins/render/#renderfile
eleventyConfig.addPlugin(EleventyRenderPlugin)
eleventyConfig.addPassthroughCopy("src/pagedjs")
eleventyConfig.addPassthroughCopy("src/media/originals/passthroughCopies/")
eleventyConfig.addPassthroughCopy("src/css")
eleventyConfig.addWatchTarget("src/css")
eleventyConfig.addPassthroughCopy("src/js")
eleventyConfig.addWatchTarget("src/js")
eleventyConfig.on('afterBuild', () => {
// after first starting the server, the result is buggy,propably due to some race condition during image processing. We always need to reload after the first serve is ready. Touch a file to trigger a reload. I do not know how to do this programmatically
if (firstRun) {
let file = "./src/triggerReload.njk"
function touchFileAfterTimeout() {
return new Promise((resolve) => {
setTimeout(() => {
if (fs.existsSync(file))
fs.unlinkSync(file)
fs.writeFile(file, "", function (err) {
if (err) throw err
console.log("created file to trigger a reload")
resolve()
})
}, 1000)
})
}
async function f1() {
await touchFileAfterTimeout()
}
f1()
}
firstRun = false
})
let createSortedCollection = function(year, lang) {
eleventyConfig.addCollection(`articles_${year}_${lang}`,
(collection) => collection
.getFilteredByGlob([`./src/${year}/${lang}/articles/*.md`, `./src/${year}/${lang}/articles/*.njk`])
.sort((a, b) => {
// console.assert()), `Missing order for ${a.fileSlug}`)
return articleOrder[lang+year].indexOf(a.fileSlug) - articleOrder[lang+year].indexOf(b.fileSlug)
})
.map(e => {
e.existsInArticleOrder = articleOrder[lang+year].includes(e.fileSlug)
// sometimes I set id to make it custom. if not, use title:
if (!e.data.id && e.data.title) {
// I emulate nunjucks' slugify here to get the same result
e.data.id = slugify(e.data.title, { strict: true, lower: true })
}
return e
})
)
}
// Articles: https://docs.google.com/spreadsheets/d/1pC-qmOUWU6diB3jMjgpbRYse9seF1wOx_XF3gJBeTC4/edit#gid=0
createSortedCollection("2022", "en")
createSortedCollection("2022", "vi")
createSortedCollection("2023", "en")
createSortedCollection("2023", "vi")
createSortedCollection("2024", "en")
createSortedCollection("2024", "vi")
eleventyConfig.addCollection('parallelVerses2023', function(c) {
return parallelVerses2023
})
eleventyConfig.addCollection('parallelVerses2024', function(c) {
return parallelVerses2024
})
// https://www.11ty.dev/docs/languages/nunjucks/#generic-global
eleventyConfig.addNunjucksGlobal("articleCalligraphies", function(fileSlug) {
let e = {}
e.hasCalligraphy = articleTitleCalligraphies.includes(`${fileSlug}.webp`)
if (e.hasCalligraphy) {
e.calligraphyFile = `${calligraphyPath}${fileSlug}.webp`
} else {
e.hasCalligraphy = articleTitleCalligraphies2023.includes(`${fileSlug}.webp`)
if (e.hasCalligraphy) {
e.calligraphyFile = `${calligraphyPath2023}${fileSlug}.webp`
} else {
e.hasCalligraphy = articleTitleCalligraphies2024.includes(`${fileSlug}.webp`)
if (e.hasCalligraphy) {
// console.log("found calligraphy for article: ", fileSlug)
e.calligraphyFile = `${calligraphyPath2024}${fileSlug}.webp`
}
}
}
return e
})
eleventyConfig.addNunjucksShortcode("idMap", idMap)
eleventyConfig.addNunjucksShortcode("easingGradient", easingGradient)
eleventyConfig.addNunjucksAsyncShortcode("image", imageShortcode)
eleventyConfig.addNunjucksAsyncShortcode("imageSrc", imageSrcShortcode)
eleventyConfig.addLiquidShortcode("image", imageShortcode)
eleventyConfig.addJavaScriptFunction("image", imageShortcode)
return {
dir: {
input: "src",
output: "docs"
},
markdownTemplateEngine: "njk"
}
}