feat: improve Markdown support and add transcript search functionality

- Add `.cache` to `.gitignore` for temporary build files.
- Introduce Markdown snippets for creating Season 1 and Season 5 show notes in `.vscode/markdown.code-snippets`.
- Update `.vscode/settings.json`:
  - Exclude `node_modules` directory.
  - Adjust quick suggestions to disable them for comments, strings, and others.
- Remove unnecessary `console.log` statements from `episodes.11tydata.js`.
- Add an image file `content/episodes/image.jpg`.
- Implement a transcript search feature:
  - Add `search-transcripts.hbs` to enable searching transcript cues with time markers.
  - Add `transcript-index.11ty.js` to generate a searchable transcript index.
- Update `search-index.11ty.js` to skip processing `<hr>` and `<img>` tags.
- Enhance episode layout with `startAt` query parameter to allow audio playback from a specific time.
- Add a new dependency:
  - `@11ty/eleventy-fetch` for fetching transcripts.
  - `media-captions` for parsing and handling transcript files.
- Update package-lock.json and package.json to include new dependencies.
This commit is contained in:
2024-12-24 12:07:32 -06:00
parent 4350690071
commit 27eb1e634c
13 changed files with 345 additions and 13 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ dist
.env
.DS_Store
*/.vscode
.cache

39
.vscode/markdown.code-snippets vendored Normal file
View File

@@ -0,0 +1,39 @@
{
"Season 5 Show Notes": {
"prefix": ";s5",
"body":[
"---",
"title: ${title}",
"date: ${date}",
"---",
"{% prologue %}",
"${newspaperName}",
"===",
"${newspaperTagline}",
"{% timeTag date %}",
"\n## ${newspaperHeadline}",
"${newspaperSubheadline}\n",
"${article}"
"{% endprologue%}",
"\n{% alternateTitles%}",
"\n${alternateTitles}\n",
"{% endalternateTitles%}",
]
},
"Season 1 Show Notes": {
"prefix": ";s1",
"body":[
"---",
"title: ${title}",
"date: ${date}",
"---",
"# Prologue",
"<section class=\"prologue\"><div>",
"\n# Episode {{episode}}",
"\n## ${headline}",
"**${subHeadline}**",
"\n${content}\n",
"</div></section>"
]
}
}

12
.vscode/settings.json vendored
View File

@@ -1,18 +1,22 @@
{
"files.exclude": {
// "dist/": true
"**/.obsidian/": true
"dist/": false,
"**/.obsidian/": true,
"node_modules":true
},
"files.associations": {
"*.md": "markdown-eleventy"
},
"terminal.integrated.env.osx": {
"VSCODE_HISTFILE": "${workspaceFolder}/.vscode/.zsh_history",
},
"[markdown-eleventy]": {
"editor.wordWrap": "on",
"editor.wordWrapColumn": 80, // Optional: Set to your preferred wrap column
"editor.quickSuggestions": false // Optional: Disable suggestions for Markdown
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off"
} // Optional: Disable suggestions for Markdown
}
}

View File

@@ -39,7 +39,6 @@ async function podcastData (data) {
resolve(data.podcast.enclosureUrl)
} else {
const url = `${data.site.cdn}/${file_stem}.mp3`
console.log(`Inferring URL @ ${url} for ${data.page.url}`)
fetch(url, { method: "HEAD" })
.then((res)=>{
if (res.ok) {
@@ -56,7 +55,6 @@ async function podcastData (data) {
resolve(data.podcast.transcriptUrl)
} else {
const url = `${data.site.cdn}/${file_stem}.srt`
console.log(`Inferring URL @ ${url}`)
fetch(url, { method: "HEAD" })
.then((res)=>{
if (res.ok) {

BIN
content/episodes/image.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

View File

@@ -2,7 +2,6 @@
title: I'm A Little Mindfreak
date: 2024-12-16
---
{% prologue %}
Mindflayer Monday
===

View File

@@ -1,6 +1,4 @@
import markdownit from 'markdown-it';
import { convert } from 'html-to-text';
const md = markdownit({html: true})
const { convert } = require('html-to-text');
class SearchIndex {
data() {
@@ -12,7 +10,12 @@ class SearchIndex {
return {
url:`${this.url(episode.url)}`,
title: episode.data.title,
text: convert (episode.content),
text: convert (episode.content, {
selectors: [
{ selector: 'hr', format: 'skip' },
{ selector: 'img', format: 'skip'}
]
}),
season: episode.data.season,
episode: episode.data.episode
}})
@@ -20,4 +23,4 @@ class SearchIndex {
}
}
export default SearchIndex
module.exports = SearchIndex

View File

@@ -0,0 +1,60 @@
---
layout: base-with-heading
title: Search Transcripts
eleventyExcludeFromCollections: ["episode"]
override:tags: []
override:eleventyComputed: []
---
<script src="https://unpkg.com/lunr/lunr.js"></script>
<div>
<form id="search-form">
<input type="text" class="form-control mb-3" name="searchQuery" id="searchQuery">
<button class="btn btn-primary mb-3" type="submit">Search</button>
</form>
<div id="results">
<ol></ol>
</div>
</div>
<script type="module">
import {Duration} from 'https://cdn.jsdelivr.net/npm/luxon@3.5.0/+esm' ;
let idx, docs
let search_index_promise = fetch('../transcript-index')
.then((res)=>res.json())
.then((documents)=>{
docs = documents.map(({title, episode, season, url, cues})=>cues.map(({startTime, text})=>({title, episode, season, url, startTime, text}))).flat()
console.log(documents)
idx = lunr(function(){
this.ref('id')
this.field('text')
this.metadataWhitelist = ['position']
docs.forEach(function (doc, idx) {
doc.id = idx;
this.add(doc);
}, this)
})
})
function handleSubmit(evt) {
evt.preventDefault();
const formData = new FormData(evt.target)
const {searchQuery} = Object.fromEntries(formData)
const results = idx.search(searchQuery)
results.forEach(r => {
r.title = docs[r.ref].title,
r.url = docs[r.ref].url
})
console.log('Form submitted!', results)
const results_ol = document.getElementById("results").querySelector('ol')
results_ol.innerHTML = ""
results.forEach(r => {
const el = document.createElement('li')
const {url, title, text, season, episode, startTime} = docs[r.ref]
el.innerHTML = `<a href="${url}?startAt=${startTime}">${title} (Season ${season}, episode ${episode})</a><p>${Duration.fromObject({seconds:startTime}).toFormat("hh:mm:ss")}</p><p>${text}</p>`
results_ol.appendChild(el)
})
}
document.getElementById('search-form').addEventListener('submit', handleSubmit)
</script>

View File

@@ -0,0 +1,31 @@
import {parseText} from 'media-captions';
import Fetch from "@11ty/eleventy-fetch";
import path from 'path';
class SearchIndex {
data() {
return {eleventyExcludeFromCollections:["episode"], layout: null}
}
async render (data) {
const episodesWithTranscript = data.collections.episode.filter(e=>e.data.podcast?.transcriptUrl)
const promises = episodesWithTranscript.map((episode)=>{
const {transcriptUrl} = episode.data.podcast
return Fetch(transcriptUrl, {type:'text', duration: "1d"})
.then(srt_buffer=>parseText(srt_buffer.toString(), {type:'srt'}))
.then(({cues})=>cues)
.then((cues)=>({
name: path.basename(transcriptUrl,".srt"),
episode: episode.data.episode,
season: episode.data.season,
title: episode.data.title,
url: `${this.url(episode.url)}`,
cues: cues.map(({id, startTime, text})=>({id,startTime,text}))
}))
})
const result = await Promise.all(promises)
return JSON.stringify(result)
}
}
export default SearchIndex

View File

@@ -8,6 +8,9 @@ links:
- name: Discord
url: discord
iconClasses: bi bi-discord
- name: Search Episodes (Beta)
url: /episodes/search
iconClasses: bi bi-binoculars
- name: GM Tools
url: gm-tools
iconClasses: bi bi-journal-bookmark

View File

@@ -13,12 +13,20 @@ eleventyComputed:
{{#if podcast}}
<a href="{{{podcast.enclosureUrl}}}">Download</a>
{{#if podcast.transcriptUrl}}| <a href="{{{podcast.transcriptUrl}}}">Transcript</a>{{/if}}
<div>
<audio controls>
<source src="{{{podcast.enclosureUrl}}}" type="audio/mpeg">
<source src="{{{podcast.enclosureUrl}}}" id="audio-player" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
<script>
params = new URLSearchParams(document.location.search)
let startAt = params.get('startAt')
if (startAt) {
document.getElementById("audio-player").currentTime = startAt
}
</script>
{{/if}}
<div class="d-flex felx-wrap m-1 gap-1">
{{#each tags}}

184
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@11ty/eleventy-fetch": "^5.0.1",
"@11ty/eleventy-navigation": "^0.3.5",
"@11ty/eleventy-plugin-handlebars": "^1.0.0",
"@11ty/eleventy-plugin-rss": "^2.0.2",
@@ -22,12 +23,33 @@
"htmlparser2": "^9.1.0",
"luxon": "^3.5.0",
"markdown-it": "^14.1.0",
"media-captions": "^0.0.18",
"music-metadata": "^10.5.1",
"npx": "^10.2.2",
"podcast": "^2.0.1",
"sass": "^1.80.3"
}
},
"node_modules/@11ty/eleventy-fetch": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@11ty/eleventy-fetch/-/eleventy-fetch-5.0.1.tgz",
"integrity": "sha512-n79UhOAWG7XcJkRIoV8cZBpKgGln6mn+2UgsZkP6Xjgg8BqiMfxlYMJrNobSDdw3utyRAs8w/ZAPXoc18qrvMw==",
"license": "MIT",
"dependencies": {
"@rgrove/parse-xml": "^4.2.0",
"debug": "^4.3.7",
"flat-cache": "^6.1.1",
"graceful-fs": "^4.2.11",
"p-queue": "6.6.2"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/11ty"
}
},
"node_modules/@11ty/eleventy-navigation": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@11ty/eleventy-navigation/-/eleventy-navigation-0.3.5.tgz",
@@ -103,6 +125,15 @@
"node": ">= 6"
}
},
"node_modules/@keyv/serialize": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.2.tgz",
"integrity": "sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -405,6 +436,15 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rgrove/parse-xml": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@rgrove/parse-xml/-/parse-xml-4.2.0.tgz",
"integrity": "sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==",
"license": "ISC",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
@@ -899,6 +939,26 @@
"node": ">=0.10.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -950,6 +1010,30 @@
"node": ">=8"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/cache-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@@ -970,6 +1054,16 @@
"node": ">=0.10.0"
}
},
"node_modules/cacheable": {
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.8.6.tgz",
"integrity": "sha512-RNBnqNhWBtgYNe4mF4395e6260Q9loh6zT2CDFia9LSJor5+vOsvkxhd7GAtg3U4m8i38adn1Q3jiCU1N33/gg==",
"license": "MIT",
"dependencies": {
"hookified": "^1.5.1",
"keyv": "^5.2.1"
}
},
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -1471,6 +1565,12 @@
"node": ">= 8"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -1632,6 +1732,23 @@
"node": ">=8"
}
},
"node_modules/flat-cache": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.4.tgz",
"integrity": "sha512-Km+tVF9BLnxaYqX2R9OKLkwSPvGjDXXlciDC8oBr/nSM4xMCNO8X9s0w5i6lNoE8E/6BEzSJBUF5Bar+TXmKJQ==",
"license": "MIT",
"dependencies": {
"cacheable": "^1.8.6",
"flatted": "^3.3.2",
"hookified": "^1.5.1"
}
},
"node_modules/flatted": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"license": "ISC"
},
"node_modules/for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -1791,6 +1908,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/gulp-header": {
"version": "1.8.12",
"resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
@@ -2226,6 +2349,12 @@
"node": "*"
}
},
"node_modules/hookified": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/hookified/-/hookified-1.6.0.tgz",
"integrity": "sha512-se7cpwTA+iA/eY548Bu03JJqBiEZAqU2jnyKdj5B5qurtBg64CZGHTgqCv4Yh7NWu6FGI09W61MCq+NoPj9GXA==",
"license": "MIT"
},
"node_modules/html-minifier": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
@@ -2552,6 +2681,15 @@
"node": ">=0.10.0"
}
},
"node_modules/keyv": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-5.2.3.tgz",
"integrity": "sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==",
"license": "MIT",
"dependencies": {
"@keyv/serialize": "^1.0.2"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -2721,6 +2859,15 @@
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
},
"node_modules/media-captions": {
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/media-captions/-/media-captions-0.0.18.tgz",
"integrity": "sha512-JW18P6FuHdyLSGwC4TQ0kF3WdNj/+wMw2cKOb8BnmY6vSJGtnwJ+vkYj+IjHOV34j3XMc70HDeB/QYKR7E7fuQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
@@ -7588,6 +7735,43 @@
"node": ">=0.10.0"
}
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/p-queue": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
"license": "MIT",
"dependencies": {
"eventemitter3": "^4.0.4",
"p-timeout": "^3.2.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"license": "MIT",
"dependencies": {
"p-finally": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/param-case": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",

View File

@@ -12,6 +12,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@11ty/eleventy-fetch": "^5.0.1",
"@11ty/eleventy-navigation": "^0.3.5",
"@11ty/eleventy-plugin-handlebars": "^1.0.0",
"@11ty/eleventy-plugin-rss": "^2.0.2",
@@ -25,6 +26,7 @@
"htmlparser2": "^9.1.0",
"luxon": "^3.5.0",
"markdown-it": "^14.1.0",
"media-captions": "^0.0.18",
"music-metadata": "^10.5.1",
"npx": "^10.2.2",
"podcast": "^2.0.1",