eleventy-plugin-podcaster 1.4.1 → 2.0.0-alpha.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/README.md +9 -6
- package/ava.config.js +3 -0
- package/eleventy.config.js +22 -123
- package/package.json +3 -3
- package/src/calculateFilenameSizeAndDuration.js +47 -35
- package/src/episodeData.js +59 -0
- package/src/excerpts.js +1 -3
- package/src/pageTitle.js +16 -0
- package/src/podcastData.js +39 -0
- package/src/podcastFeed.js +28 -0
- package/src/readableFilters.js +27 -0
package/README.md
CHANGED
|
@@ -38,13 +38,16 @@ export default function (eleventyConfig) {
|
|
|
38
38
|
|
|
39
39
|
## ➡ [Documentation and usage][Podcaster]
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
[Podcaster]: https://eleventy-plugin-podcaster.com/docs
|
|
42
42
|
|
|
43
|
-
[
|
|
43
|
+
Detailed and specific information about how to install and use **Podcaster** can be found in [the Documentation section](docs/index.md) of the site, but here's a quick summary.
|
|
44
44
|
|
|
45
|
-
**Podcaster** is an Eleventy plugin.
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
1. **Podcaster** is an Eleventy plugin. Create an Eleventy site and install the `eleventy-plugin-podcaster` plugin in the usual way.
|
|
46
|
+
2. In the data directory, create a `podcast.json` file. This will contain information about your podcast and its site — at the very least, its title, the URL of the site, a description, its language, and its category.
|
|
47
|
+
3. In the input directory, create a `episodeFiles` directory and put your podcast MP3s in there.
|
|
48
|
+
4. In the input directory, create a `episodePosts` directory. You will have a post for each episode, and that post will include information about the episode in its filename and front matter and will have as its content the episode description or show notes.
|
|
49
|
+
5. Create pages on your site for each of your episodes using the posts in the `episodePosts` directory. You can also create index pages, topic pages, guest pages or anything you like, using the information you have supplied to **Podcaster**.
|
|
50
|
+
6. Find a host for your site, and a CDN for your podcast episodes.
|
|
48
51
|
|
|
49
52
|
> [!WARNING]
|
|
50
|
-
> **Podcaster**
|
|
53
|
+
> **Podcaster** requires Node 20 or later.
|
package/ava.config.js
ADDED
package/eleventy.config.js
CHANGED
|
@@ -1,138 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { readFileSync } from 'node:fs'
|
|
5
|
-
import path from 'node:path'
|
|
1
|
+
import podcastFeed from './src/podcastFeed.js'
|
|
2
|
+
import podcastData from './src/podcastData.js'
|
|
3
|
+
import episodeData from './src/episodeData.js'
|
|
6
4
|
import calculateFilenameSizeAndDuration from './src/calculateFilenameSizeAndDuration.js'
|
|
5
|
+
import readableFilters from './src/readableFilters.js'
|
|
7
6
|
import excerpts from './src/excerpts.js'
|
|
8
7
|
import drafts from './src/drafts.js'
|
|
8
|
+
import pageTitle from './src/pageTitle.js'
|
|
9
9
|
|
|
10
10
|
export default function (eleventyConfig, options = {}) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
eleventyConfig.addPlugin(podcastFeed, options)
|
|
12
|
+
eleventyConfig.addPlugin(podcastData, options)
|
|
13
|
+
eleventyConfig.addPlugin(episodeData, options)
|
|
14
|
+
eleventyConfig.addPlugin(calculateFilenameSizeAndDuration, options)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
posthtmlRenderOptions: {
|
|
17
|
-
closingSingleTag: 'default' // opt-out of <img/>-style XHTML single tags
|
|
18
|
-
}
|
|
19
|
-
})
|
|
16
|
+
// Filters
|
|
20
17
|
|
|
21
|
-
eleventyConfig.
|
|
22
|
-
return data => data.podcast.feedPath || '/feed/podcast.xml'
|
|
23
|
-
})
|
|
18
|
+
eleventyConfig.addPlugin(readableFilters, options)
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
return data => data.podcast.imagePath || '/img/podcast-logo.jpg'
|
|
27
|
-
})
|
|
20
|
+
// Optional features
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!data.podcast.startingYear || data.podcast.startingYear === thisYear) {
|
|
34
|
-
yearRange = thisYear
|
|
35
|
-
} else {
|
|
36
|
-
yearRange = `${data.podcast.startingYear}–${thisYear}`
|
|
37
|
-
}
|
|
38
|
-
return `© ${yearRange} ${data.podcast.copyright || data.podcast.author}`
|
|
39
|
-
}
|
|
22
|
+
if (options.optionalFeatures) {
|
|
23
|
+
options.handleDrafts = true
|
|
24
|
+
options.handleExcerpts = true
|
|
25
|
+
options.handlePageTitle ??= true // preserve setting for custom separators
|
|
40
26
|
}
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
calculateCopyrightNotice
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
eleventyConfig.addGlobalData(
|
|
48
|
-
'eleventyComputed.copyrightNotice',
|
|
49
|
-
calculateCopyrightNotice
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
eleventyConfig.addGlobalData(
|
|
53
|
-
'podcast.feedLastBuildDate',
|
|
54
|
-
DateTime.now().toRFC2822()
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
eleventyConfig.addGlobalData('eleventyComputed.episode.url', () => {
|
|
58
|
-
return data => {
|
|
59
|
-
if (!data.tags?.includes('podcastEpisode')) return
|
|
60
|
-
|
|
61
|
-
const episodeUrlBase = data.podcast.episodeUrlBase
|
|
62
|
-
const filename = data.episode.filename
|
|
63
|
-
return new URL(filename, episodeUrlBase).toString()
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
eleventyConfig.addShortcode('year', () => DateTime.now().year)
|
|
68
|
-
|
|
69
|
-
if (options.readableDateLocale) {
|
|
70
|
-
eleventyConfig.addFilter('readableDate', function (date) {
|
|
71
|
-
if (date instanceof Date) {
|
|
72
|
-
date = date.toISOString()
|
|
73
|
-
}
|
|
74
|
-
const result = DateTime.fromISO(date, {
|
|
75
|
-
zone: 'UTC'
|
|
76
|
-
})
|
|
77
|
-
return result.setLocale(options.readableDateLocale).toLocaleString(DateTime.DATE_HUGE)
|
|
78
|
-
})
|
|
28
|
+
if (options.handleExcerpts) {
|
|
29
|
+
eleventyConfig.addPlugin(excerpts, options)
|
|
79
30
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const separator = options.calculatePageTitle === true ? '·' : options.calculatePageTitle
|
|
83
|
-
|
|
84
|
-
eleventyConfig.addGlobalData('eleventyComputed.pageTitle', () => {
|
|
85
|
-
return data => {
|
|
86
|
-
const siteTitle = data.site?.title || data.podcast.title
|
|
87
|
-
if (data.title && data.title.length > 0 && data.title !== siteTitle) {
|
|
88
|
-
return `${data.title} ${separator} ${siteTitle}`
|
|
89
|
-
} else {
|
|
90
|
-
return siteTitle
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
})
|
|
31
|
+
if (options.handleDrafts) {
|
|
32
|
+
eleventyConfig.addPlugin(drafts, options)
|
|
94
33
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!seconds) return '0:00:00'
|
|
98
|
-
if (omitLeadingZero && seconds < 3600) {
|
|
99
|
-
return Duration.fromMillis(seconds * 1000).toFormat('mm:ss')
|
|
100
|
-
}
|
|
101
|
-
return Duration.fromMillis(seconds * 1000).toFormat('h:mm:ss')
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
eleventyConfig.addFilter('readableSize', (bytes, fixedPrecision) =>
|
|
105
|
-
(fixedPrecision)
|
|
106
|
-
? hr.fromBytes(bytes, { fixedPrecision })
|
|
107
|
-
: hr.fromBytes(bytes)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
const podcastFeedPath = path.join(import.meta.dirname, './src/podcastFeed.njk')
|
|
111
|
-
|
|
112
|
-
eleventyConfig.addTemplate('feed.njk', readFileSync(podcastFeedPath), {
|
|
113
|
-
eleventyExcludeFromCollections: true,
|
|
114
|
-
eleventyImport: {
|
|
115
|
-
collections: ['podcastEpisode']
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
if (options.handleEpisodePermalinks) {
|
|
120
|
-
eleventyConfig.addGlobalData('eleventyComputed.permalink', () => {
|
|
121
|
-
return data => {
|
|
122
|
-
if (data.permalink) return data.permalink
|
|
123
|
-
|
|
124
|
-
if (data.tags?.includes('podcastEpisode')) {
|
|
125
|
-
if (data.episode?.seasonNumber) {
|
|
126
|
-
return `/s${data.episode.seasonNumber}/e${data.episode.episodeNumber}/`
|
|
127
|
-
} else {
|
|
128
|
-
return `/${data.episode.episodeNumber}/`
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
})
|
|
34
|
+
if (options.handlePageTitle) {
|
|
35
|
+
eleventyConfig.addPlugin(pageTitle, options)
|
|
133
36
|
}
|
|
134
|
-
|
|
135
|
-
eleventyConfig.addPlugin(calculateFilenameSizeAndDuration, options)
|
|
136
|
-
eleventyConfig.addPlugin(excerpts, options)
|
|
137
|
-
eleventyConfig.addPlugin(drafts, options)
|
|
138
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eleventy-plugin-podcaster",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.1",
|
|
4
4
|
"description": "An Eleventy plugin that allows you to create a podcast and its accompanying website",
|
|
5
5
|
"main": "eleventy.config.js",
|
|
6
6
|
"exports": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/nathan-bottomley/eleventy-plugin-podcaster.git"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://
|
|
13
|
+
"homepage": "https://eleventy-plugin-podcaster.com",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"test": "ava"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"htmlparser2": "^9.1.0",
|
|
37
37
|
"luxon": "^3.4.4",
|
|
38
38
|
"markdown-it": "^14.1.0",
|
|
39
|
-
"
|
|
39
|
+
"music-metadata": "^11.7.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"ava": "^6.1.3",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Duration } from 'luxon'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { existsSync } from 'node:fs'
|
|
4
|
-
import { readdir, stat,
|
|
5
|
-
import
|
|
4
|
+
import { readdir, stat, writeFile } from 'node:fs/promises'
|
|
5
|
+
import { parseFile } from 'music-metadata'
|
|
6
6
|
import hr from '@tsmx/human-readable'
|
|
7
7
|
import chalk from 'chalk'
|
|
8
8
|
|
|
@@ -11,73 +11,85 @@ const convertSecondsToReadableDuration = seconds =>
|
|
|
11
11
|
.shiftTo('days', 'hours', 'minutes', 'seconds')
|
|
12
12
|
.toHuman()
|
|
13
13
|
|
|
14
|
-
export default function (eleventyConfig
|
|
14
|
+
export default function (eleventyConfig) {
|
|
15
15
|
let firstRun = true
|
|
16
|
-
eleventyConfig.on('eleventy.before', async ({
|
|
16
|
+
eleventyConfig.on('eleventy.before', async ({ directories }) => {
|
|
17
17
|
// don't keep recalculating episode data in serve mode
|
|
18
18
|
if (!firstRun || process.env.SKIP_EPISODE_CALCULATIONS === 'true') return
|
|
19
19
|
firstRun = false
|
|
20
|
-
|
|
21
|
-
const episodesDir = path.join(process.cwd(), options.episodesDir || 'episodes')
|
|
20
|
+
const episodesDir = path.join(directories.input, 'episodeFiles')
|
|
22
21
|
if (!existsSync(episodesDir)) return
|
|
23
22
|
|
|
24
23
|
const episodes = await readdir(episodesDir)
|
|
25
|
-
const
|
|
26
|
-
let
|
|
24
|
+
const episodeData = {}
|
|
25
|
+
let numberOfEpisodes = 0
|
|
27
26
|
let totalSize = 0
|
|
28
27
|
let totalDuration = 0
|
|
29
28
|
|
|
30
29
|
for (const episode of episodes) {
|
|
31
30
|
if (!episode.endsWith('.mp3')) continue
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
numberOfEpisodes++
|
|
34
33
|
const episodePath = path.join(episodesDir, episode)
|
|
35
34
|
const episodeSize = (await stat(episodePath)).size
|
|
36
35
|
totalSize += episodeSize
|
|
37
|
-
const
|
|
38
|
-
const episodeDuration =
|
|
36
|
+
const episodeMetadata = await parseFile(episodePath, { duration: true })
|
|
37
|
+
const episodeDuration = episodeMetadata.format.duration
|
|
39
38
|
totalDuration += episodeDuration
|
|
40
|
-
|
|
39
|
+
episodeData[episode] = {
|
|
41
40
|
size: episodeSize,
|
|
42
|
-
duration: episodeDuration
|
|
41
|
+
duration: Math.round(episodeDuration * 1000) / 1000
|
|
43
42
|
}
|
|
43
|
+
totalDuration = Math.round(totalDuration * 1000) / 1000
|
|
44
44
|
}
|
|
45
|
+
const podcastData = { numberOfEpisodes, totalSize, totalDuration }
|
|
45
46
|
|
|
46
47
|
const dataDir = path.join(process.cwd(), directories.data)
|
|
47
|
-
await writeFile(path.join(dataDir, '
|
|
48
|
+
await writeFile(path.join(dataDir, 'episodeData.json'), JSON.stringify(episodeData, null, 2))
|
|
49
|
+
await writeFile(path.join(dataDir, 'podcastData.json'), JSON.stringify(podcastData, null, 2))
|
|
48
50
|
|
|
49
|
-
console.log(chalk.yellow(`${
|
|
51
|
+
console.log(chalk.yellow(`${numberOfEpisodes} episodes; ${hr.fromBytes(totalSize)}; ${convertSecondsToReadableDuration(totalDuration)}.`))
|
|
50
52
|
})
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const filenameSeasonAndEpisodePattern =
|
|
55
|
+
/^.*?\b[sS](?<seasonNumber>\d+)[eE](?<episodeNumber>\d+)\b.*\.mp3$/
|
|
56
|
+
const filenameEpisodePattern = /^.*?\b(?<episodeNumber>\d+)\b.*\.mp3$/
|
|
57
|
+
|
|
58
|
+
eleventyConfig.addGlobalData('eleventyComputed.episode.filename', () => {
|
|
59
|
+
return data => {
|
|
60
|
+
if (data.episode.filename) return data.episode.filename
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
if (!data.page.inputPath.includes('/episodePosts/')) return
|
|
63
|
+
|
|
64
|
+
for (const file of Object.keys(data.episodeData)) {
|
|
65
|
+
if (data.episode.seasonNumber && data.episode.episodeNumber) {
|
|
66
|
+
const seasonAndEpisodeMatch = file.match(filenameSeasonAndEpisodePattern)
|
|
67
|
+
if (seasonAndEpisodeMatch) {
|
|
68
|
+
const matchedSeasonNumber = parseInt(seasonAndEpisodeMatch.groups.seasonNumber)
|
|
69
|
+
const matchedEpisodeNumber = parseInt(seasonAndEpisodeMatch.groups.episodeNumber)
|
|
70
|
+
if (matchedSeasonNumber === data.episode.seasonNumber &&
|
|
71
|
+
matchedEpisodeNumber === data.episode.episodeNumber) {
|
|
64
72
|
return file
|
|
65
|
-
}
|
|
66
|
-
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else if (data.episode.episodeNumber) {
|
|
76
|
+
const episodeMatch = file.match(filenameEpisodePattern)
|
|
77
|
+
if (episodeMatch) {
|
|
78
|
+
const matchedEpisodeNumber = parseInt(episodeMatch.groups.episodeNumber)
|
|
79
|
+
if (matchedEpisodeNumber === data.episode.episodeNumber) {
|
|
67
80
|
return file
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
74
87
|
|
|
75
88
|
eleventyConfig.addGlobalData('eleventyComputed.episode.size', () => {
|
|
76
89
|
return data => {
|
|
77
90
|
if (data.episode.size) return data.episode.size
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return data.episodesData[data.episode.filename]?.size
|
|
91
|
+
if (data.page.inputPath.includes('/episodePosts/') && data.episodeData) {
|
|
92
|
+
return data.episodeData[data.episode.filename]?.size
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
})
|
|
@@ -86,8 +98,8 @@ export default function (eleventyConfig, options = {}) {
|
|
|
86
98
|
return data => {
|
|
87
99
|
if (data.episode.duration) return data.episode.duration
|
|
88
100
|
|
|
89
|
-
if (data.
|
|
90
|
-
return data.
|
|
101
|
+
if (data.page.inputPath.includes('/episodePosts/') && data.episodeData) {
|
|
102
|
+
return data.episodeData[data.episode.filename]?.duration
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export default function (eleventyConfig, options = {}) {
|
|
2
|
+
const postFilenameSeasonAndEpisodePattern =
|
|
3
|
+
/^[sS](?<seasonNumber>\d+)[eE](?<episodeNumber>\d+)/i
|
|
4
|
+
const postFilenameEpisodePattern = /^(?:e|ep|episode-)(?<episodeNumber>\d+)/i
|
|
5
|
+
|
|
6
|
+
eleventyConfig.addGlobalData('eleventyComputed.episode.seasonNumber', () => {
|
|
7
|
+
return data => {
|
|
8
|
+
if (data.episode?.seasonNumber) return data.episode.seasonNumber
|
|
9
|
+
|
|
10
|
+
if (!data.page.inputPath.includes('/episodePosts/')) return
|
|
11
|
+
|
|
12
|
+
const seasonAndEpisodeMatch = data.page.fileSlug.match(postFilenameSeasonAndEpisodePattern)
|
|
13
|
+
if (seasonAndEpisodeMatch) {
|
|
14
|
+
return parseInt(seasonAndEpisodeMatch.groups.seasonNumber, 10)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
eleventyConfig.addGlobalData('eleventyComputed.episode.episodeNumber', () => {
|
|
20
|
+
return data => {
|
|
21
|
+
if (data.episode?.episodeNumber) return data.episode.episodeNumber
|
|
22
|
+
|
|
23
|
+
if (!data.page.inputPath.includes('/episodePosts/')) return
|
|
24
|
+
|
|
25
|
+
const seasonAndEpisodeMatch = data.page.fileSlug.match(postFilenameSeasonAndEpisodePattern)
|
|
26
|
+
if (seasonAndEpisodeMatch) {
|
|
27
|
+
return parseInt(seasonAndEpisodeMatch.groups.episodeNumber, 10)
|
|
28
|
+
}
|
|
29
|
+
const episodeMatch = data.page.fileSlug.match(postFilenameEpisodePattern)
|
|
30
|
+
if (episodeMatch) {
|
|
31
|
+
return parseInt(episodeMatch.groups.episodeNumber, 10)
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`[eleventy-plugin-podcaster] Cannot determine episode number for ${data.page.inputPath}. Please ensure the file slug contains a number or set the episodeNumber explicitly in the front matter.`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
eleventyConfig.addGlobalData('eleventyComputed.permalink', () => {
|
|
39
|
+
return data => {
|
|
40
|
+
if (data.permalink) return data.permalink
|
|
41
|
+
|
|
42
|
+
if (data.episode?.seasonNumber && data.episode?.episodeNumber) {
|
|
43
|
+
return `/s${data.episode.seasonNumber}/e${data.episode.episodeNumber}/`
|
|
44
|
+
} else if (data.episode?.episodeNumber) {
|
|
45
|
+
return `/${data.episode.episodeNumber}/`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
eleventyConfig.addGlobalData('eleventyComputed.episode.url', () => {
|
|
51
|
+
return data => {
|
|
52
|
+
if (!data.page.inputPath.includes('/episodePosts/')) return
|
|
53
|
+
|
|
54
|
+
const episodeUrlBase = data.podcast.episodeUrlBase
|
|
55
|
+
const filename = data.episode.filename
|
|
56
|
+
return URL.parse(filename, episodeUrlBase)
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
package/src/excerpts.js
CHANGED
|
@@ -4,10 +4,8 @@ import markdownIt from 'markdown-it'
|
|
|
4
4
|
|
|
5
5
|
export default function (eleventyConfig, options = {}) {
|
|
6
6
|
eleventyConfig.addGlobalData('eleventyComputed.excerpt', () => {
|
|
7
|
-
if (!options.handleExcerpts) return
|
|
8
|
-
|
|
9
7
|
return (data) => {
|
|
10
|
-
if (!data.
|
|
8
|
+
if (!data.page.inputPath.includes('/episodePosts/')) return
|
|
11
9
|
|
|
12
10
|
const md = markdownIt({
|
|
13
11
|
html: true,
|
package/src/pageTitle.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default function (eleventyConfig, options = {}) {
|
|
2
|
+
const separator = (options.handlePageTitle === true)
|
|
3
|
+
? '·'
|
|
4
|
+
: options.handlePageTitle
|
|
5
|
+
|
|
6
|
+
eleventyConfig.addGlobalData('eleventyComputed.pageTitle', () => {
|
|
7
|
+
return data => {
|
|
8
|
+
const siteTitle = data.site?.title || data.podcast.title
|
|
9
|
+
if (data.title && data.title.length > 0 && data.title !== siteTitle) {
|
|
10
|
+
return `${data.title} ${separator} ${siteTitle}`
|
|
11
|
+
} else {
|
|
12
|
+
return siteTitle
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DateTime } from 'luxon'
|
|
2
|
+
|
|
3
|
+
export default function (eleventyConfig) {
|
|
4
|
+
eleventyConfig.addGlobalData('eleventyComputed.podcast.feedPath', () => {
|
|
5
|
+
return data => data.podcast.feedPath || '/feed/podcast.xml'
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
eleventyConfig.addGlobalData('eleventyComputed.podcast.imagePath', () => {
|
|
9
|
+
return data => data.podcast.imagePath || '/img/podcast-logo.jpg'
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
eleventyConfig.addGlobalData('eleventyComputed.podcast.episodeUrlBase', () => {
|
|
13
|
+
return data => {
|
|
14
|
+
if (data.podcast.episodeUrlBase) return data.podcast.episodeUrlBase
|
|
15
|
+
let siteUrl
|
|
16
|
+
try {
|
|
17
|
+
siteUrl = data.podcast.siteUrl || data.site.url
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.error('[eleventy-plugin-podcaster] No site URL found. Please set `siteUrl` in your podcast data.')
|
|
20
|
+
}
|
|
21
|
+
return URL.parse('episodes/', siteUrl)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
eleventyConfig.addGlobalData('eleventyComputed.podcast.copyrightNotice', () => {
|
|
26
|
+
return data => {
|
|
27
|
+
const thisYear = DateTime.now().year
|
|
28
|
+
let yearRange
|
|
29
|
+
if (!data.podcast.startingYear || data.podcast.startingYear === thisYear) {
|
|
30
|
+
yearRange = thisYear
|
|
31
|
+
} else {
|
|
32
|
+
yearRange = `${data.podcast.startingYear}–${thisYear}`
|
|
33
|
+
}
|
|
34
|
+
return `© ${yearRange} ${data.podcast.copyright || data.podcast.author}`
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
eleventyConfig.addGlobalData('podcast.feedLastBuildDate', DateTime.now().toRFC2822())
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import rssPlugin from '@11ty/eleventy-plugin-rss'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export default function (eleventyConfig, options = {}) {
|
|
6
|
+
if (!('addTemplate' in eleventyConfig)) {
|
|
7
|
+
console.error('[eleventy-plugin-podcasting] Eleventy plugin compatibility: Virtual Templates are required for this plugin — please use Eleventy v3.0 or newer.')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const podcastFeedPath = path.join(import.meta.dirname, './podcastFeed.njk')
|
|
11
|
+
|
|
12
|
+
eleventyConfig.addTemplate('feed.njk', readFileSync(podcastFeedPath), {
|
|
13
|
+
eleventyExcludeFromCollections: true,
|
|
14
|
+
eleventyImport: {
|
|
15
|
+
collections: ['podcastEpisode']
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
eleventyConfig.addPlugin(rssPlugin, {
|
|
20
|
+
posthtmlRenderOptions: {
|
|
21
|
+
closingSingleTag: 'default' // opt-out of <img/>-style XHTML single tags
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
eleventyConfig.addCollection('podcastEpisode', (collectionApi) => {
|
|
26
|
+
return collectionApi.getFilteredByGlob('**/episodePosts/*')
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DateTime, Duration } from 'luxon'
|
|
2
|
+
import hr from '@tsmx/human-readable'
|
|
3
|
+
|
|
4
|
+
export default function (eleventyConfig, options = {}) {
|
|
5
|
+
eleventyConfig.addFilter('readableDate', function (date) {
|
|
6
|
+
const readableDateLocale = options.readableDateLocale ?? 'en-AU'
|
|
7
|
+
if (date instanceof Date) {
|
|
8
|
+
date = date.toISOString()
|
|
9
|
+
}
|
|
10
|
+
const result = DateTime.fromISO(date, {
|
|
11
|
+
zone: 'UTC'
|
|
12
|
+
})
|
|
13
|
+
return result.setLocale(readableDateLocale).toLocaleString(DateTime.DATE_HUGE)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
eleventyConfig.addFilter('readableDuration', (seconds) => {
|
|
17
|
+
if (!seconds) return '0:00:00'
|
|
18
|
+
if (seconds < 3600) {
|
|
19
|
+
return Duration.fromMillis(seconds * 1000).toFormat('mm:ss')
|
|
20
|
+
}
|
|
21
|
+
return Duration.fromMillis(seconds * 1000).toFormat('h:mm:ss')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
eleventyConfig.addFilter('readableSize', (bytes, fixedPrecision = 1) =>
|
|
25
|
+
hr.fromBytes(bytes, { fixedPrecision })
|
|
26
|
+
)
|
|
27
|
+
}
|