eleventy-plugin-podcaster 2.0.0-alpha.4 → 2.0.0-alpha.6

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 CHANGED
@@ -1,6 +1,9 @@
1
1
  # eleventy-plugin-podcaster 🕚⚡️🎈🐀🎤📲
2
2
 
3
- `eleventy-plugin-podcaster` — or **Podcaster**, as we will call it from now on — is an Eleventy plugin which lets you create a podcast and its accompanying website. **Podcaster** creates the podcast feed that you submit to Apple Podcasts, Spotify or any other podcast directory. And it provides information about your podcast to your Eleventy templates. This means that you can include information about the podcast and its episodes on your podcast's website, creating pages for individual episodes, guests, topics, seasons or anything else at all.
3
+ `eleventy-plugin-podcaster` — or **Podcaster**, as we will call it from now on — is an Eleventy plugin which lets you create a podcast and its accompanying website.
4
+
5
+ - **Podcaster** creates the podcast feed that you submit to Apple Podcasts, Spotify or any other podcast directory.
6
+ - **Podcaster** also provides information about your podcast to your Eleventy templates. This means that you can describe the podcast and its episodes on your podcast website, creating pages for individual episodes, guests, topics, seasons or anything else at all.
4
7
 
5
8
  Plenty of services exist that will host your podcast online — [Spotify][], [Acast][], [Podbean][], [Buzzsprout][], [Blubrry][]. But none of these will allow you to own your podcast's presence on the web, and none of them will give you the freedom to create a site that presents your podcast in a way that reflects its premise, tone and style.
6
9
 
@@ -44,9 +47,9 @@ Detailed and specific information about how to install and use **Podcaster** can
44
47
 
45
48
  1. **Podcaster** is an Eleventy plugin. Create an Eleventy site and install the `eleventy-plugin-podcaster` plugin in the usual way.
46
49
  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
+ 3. In the input directory, create an `episode-files` directory and put your podcast MP3s in there.
51
+ 4. In the input directory, create an `episode-posts` directory. Here you will create a post for each episode, which will include information about the episode in its filename and front matter and which whill contain the episode description or show notes.
52
+ 5. Create pages on your site for each of your episodes using the posts in the `episode-posts` directory. And you can also create index pages, topic pages, guest pages or anything you like — all using the information you have supplied to **Podcaster** in your `podcast.json` file and your episode posts.
50
53
  6. Find a host for your site, and a CDN for your podcast episodes.
51
54
 
52
55
  > [!WARNING]
@@ -7,6 +7,7 @@ import calculateEpisodeSizeAndDuration from './src/calculateEpisodeSizeAndDurati
7
7
  import calculateEpisodeFilename from './src/calculateEpisodeFilename.js'
8
8
 
9
9
  import readableFilters from './src/readableFilters.js'
10
+ import chapters from './src/chapters.js'
10
11
  import excerpts from './src/excerpts.js'
11
12
  import drafts from './src/drafts.js'
12
13
  import pageTitle from './src/pageTitle.js'
@@ -26,6 +27,7 @@ export default function (eleventyConfig, options = {}) {
26
27
  // Filters
27
28
 
28
29
  eleventyConfig.addPlugin(readableFilters, options)
30
+ eleventyConfig.addPlugin(chapters, options)
29
31
 
30
32
  // Optional features
31
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eleventy-plugin-podcaster",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.6",
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": {
@@ -1,4 +1,5 @@
1
- import { Duration, DateTime } from 'luxon'
1
+ import { DateTime } from 'luxon'
2
+ import readableDuration from './readableDuration.js'
2
3
  import path from 'node:path'
3
4
  import { existsSync } from 'node:fs'
4
5
  import { readdir, stat, writeFile } from 'node:fs/promises'
@@ -11,29 +12,6 @@ import isEpisodePost from './isEpisodePost.js'
11
12
  const isAudioFile = episodeFilename => episodeFilename.endsWith('.mp3') ||
12
13
  episodeFilename.endsWith('.m4a')
13
14
 
14
- const convertSecondsToReadableDuration = seconds =>
15
- Duration.fromMillis(seconds * 1000)
16
- .shiftTo('days', 'hours', 'minutes', 'seconds')
17
- .toHuman()
18
-
19
- const convertReadableDurationToSeconds = duration => {
20
- const durationPattern = /^(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):(?<seconds>\d{2}(?:\.\d+)?)$/
21
-
22
- let match
23
- if (duration?.match) {
24
- match = duration.match(durationPattern)
25
- }
26
-
27
- if (match) {
28
- const hours = isNaN(parseInt(match.groups.hours))
29
- ? 0
30
- : parseInt(match.groups.hours)
31
- const minutes = parseInt(match.groups.minutes)
32
- const seconds = parseFloat(match.groups.seconds)
33
- return hours * 3600 + minutes * 60 + seconds
34
- }
35
- }
36
-
37
15
  async function readEpisodeDataLocally (episodeFilesDirectory) {
38
16
  const episodes = await readdir(episodeFilesDirectory)
39
17
  const episodeData = {}
@@ -62,7 +40,7 @@ function calculatePodcastData (episodeData) {
62
40
 
63
41
  function reportPodcastData (podcastData) {
64
42
  const { numberOfEpisodes, totalSize, totalDuration } = podcastData
65
- console.log(`\u001b[33m${numberOfEpisodes} episodes; ${hr.fromBytes(totalSize)}; ${convertSecondsToReadableDuration(totalDuration)}.\u001b[0m`)
43
+ console.log(`\u001b[33m${numberOfEpisodes} episodes; ${hr.fromBytes(totalSize)}; ${readableDuration.convertFromSeconds(totalDuration)}.\u001b[0m`)
66
44
  }
67
45
 
68
46
  async function writePodcastDataLocally (episodeData, podcastData, directories) {
@@ -164,6 +142,7 @@ export default function (eleventyConfig, options = {}) {
164
142
  let firstRun = true
165
143
  eleventyConfig.on('eleventy.before', async ({ directories }) => {
166
144
  if (!firstRun || process.env.SKIP_EPISODE_CALCULATIONS === 'true') return
145
+
167
146
  firstRun = false
168
147
 
169
148
  const episodeFilesDirectory = options.episodeFilesDirectory
@@ -195,7 +174,7 @@ export default function (eleventyConfig, options = {}) {
195
174
  eleventyConfig.addGlobalData('eleventyComputed.episode.duration', () => {
196
175
  return data => {
197
176
  if (data.episode.duration) {
198
- const convertedReadableDuration = convertReadableDurationToSeconds(data.episode.duration)
177
+ const convertedReadableDuration = readableDuration.convertToSeconds(data.episode.duration)
199
178
  return convertedReadableDuration ?? data.episode.duration
200
179
  }
201
180
 
@@ -0,0 +1,13 @@
1
+ import readableDuration from './readableDuration.js'
2
+
3
+ export default (eleventyConfig, options = {}) => {
4
+ eleventyConfig.addFilter('normalizeChaptersData', (data) => {
5
+ const result = data.map(x => {
6
+ if (x.startTime) {
7
+ x.startTime = readableDuration.convertToSeconds(x.startTime) ?? x.startTime
8
+ }
9
+ return x
10
+ })
11
+ return JSON.stringify(result, null, 2)
12
+ })
13
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ pagination:
3
+ data: collections.episodePostWithChapters
4
+ size: 1
5
+ alias: episodePost
6
+ permalink: "{{ episodePost.page.url }}/chapters.json"
7
+ ---
8
+
9
+ {{ episodePost.data.episode.chapters | normalizeChaptersData | safe }}
@@ -9,7 +9,6 @@ export default function (eleventyConfig, options = {}) {
9
9
  }
10
10
 
11
11
  const podcastFeedPath = path.join(import.meta.dirname, './podcastFeed.njk')
12
-
13
12
  eleventyConfig.addTemplate('feed.njk', readFileSync(podcastFeedPath), {
14
13
  eleventyExcludeFromCollections: true,
15
14
  eleventyImport: {
@@ -17,6 +16,14 @@ export default function (eleventyConfig, options = {}) {
17
16
  }
18
17
  })
19
18
 
19
+ const chaptersPath = path.join(import.meta.dirname, './chapters.njk')
20
+ eleventyConfig.addTemplate('chapters.njk', readFileSync(chaptersPath), {
21
+ eleventyExcludeFromCollections: true,
22
+ eleventyImport: {
23
+ collections: ['episodePostWithChapters']
24
+ }
25
+ })
26
+
20
27
  eleventyConfig.addPlugin(rssPlugin, {
21
28
  posthtmlRenderOptions: {
22
29
  closingSingleTag: 'default' // opt-out of <img/>-style XHTML single tags
@@ -24,6 +31,22 @@ export default function (eleventyConfig, options = {}) {
24
31
  })
25
32
 
26
33
  eleventyConfig.addCollection('episodePost', (collectionApi) => {
27
- return collectionApi.getAll().filter(item => isEpisodePost(item.data, options))
34
+ return collectionApi
35
+ .getAll()
36
+ .filter(item => isEpisodePost(item.data, options))
37
+ // sort order explicit: presence of template data files disrupts it otherwise
38
+ .sort((a, b) => a.date - b.date)
39
+ })
40
+
41
+ // included for backward compatibility, will be deprecated in 3.0
42
+ eleventyConfig.addCollection('podcastEpisode', (collectionApi) => {
43
+ return collectionApi
44
+ .getAll()
45
+ .filter(item => isEpisodePost(item.data, options))
46
+ .sort((a, b) => a.date - b.date)
47
+ })
48
+
49
+ eleventyConfig.addCollection('episodePostWithChapters', (collectionApi) => {
50
+ return collectionApi.getAll().filter(item => isEpisodePost(item.data, options) && item.data.episode?.chapters)
28
51
  })
29
52
  }
@@ -60,6 +60,10 @@ eleventyAllowMissingExtension: true
60
60
  <guid isPermaLink="true">{{ post.url | htmlBaseUrl(siteUrl) }}</guid>
61
61
  {% endif -%}
62
62
  <pubDate>{{ post.date | dateToRfc3339 }}</pubDate>
63
+ {%- if post.data.episode.chapters %}
64
+ <podcast:chapters url="{{ [post.url, 'chapters.json'] | join('/') | htmlBaseUrl(siteUrl) }}"
65
+ type="application/json+chapters" />
66
+ {% endif -%}
63
67
  {% if podcast.episodeDescriptionTemplate %}
64
68
  {%- set episodeDescription -%}
65
69
  {% include podcast.episodeDescriptionTemplate %}
@@ -0,0 +1,26 @@
1
+ import { Duration } from 'luxon'
2
+
3
+ export default {
4
+ convertFromSeconds (seconds) {
5
+ return Duration.fromMillis(seconds * 1000)
6
+ .shiftTo('days', 'hours', 'minutes', 'seconds')
7
+ .toHuman()
8
+ },
9
+ convertToSeconds (duration) {
10
+ const durationPattern = /^(?:(?<hours>\d+):)?(?<minutes>\d{1,2}):(?<seconds>\d{2}(?:\.\d+)?)$/
11
+
12
+ let match
13
+ if (duration?.match) {
14
+ match = duration.match(durationPattern)
15
+ }
16
+
17
+ if (match) {
18
+ const hours = isNaN(parseInt(match.groups.hours))
19
+ ? 0
20
+ : parseInt(match.groups.hours)
21
+ const minutes = parseInt(match.groups.minutes)
22
+ const seconds = parseFloat(match.groups.seconds)
23
+ return hours * 3600 + minutes * 60 + seconds
24
+ }
25
+ }
26
+ }
package/.env DELETED
@@ -1,5 +0,0 @@
1
- BUCKET_SECRET_KEY="GiBsTUbU7VwKdSZGZKvYb47i87lWjkUXRH1Edd+I2KQ"
2
- BUCKET_ACCESS_KEY="DO801WKX4LJP3Y9DWE2H"
3
- BUCKET_ENDPOINT="https://sfo3.digitaloceanspaces.com"
4
- BUCKET_REGION="us-east-1"
5
- BUCKET_NAME="startlingbarbarabain"
package/ava.config.js DELETED
@@ -1,3 +0,0 @@
1
- export default {
2
- files: ['test/**/*', '!test/v1']
3
- }