diff --git a/lib.js b/lib.js
index 2e27f5e..af5ef76 100644
--- a/lib.js
+++ b/lib.js
@@ -1,6 +1,7 @@
import Path from "path"
import FS from "fs/promises"
import { JSDOM } from "jsdom"
+import Mustache from "mustache"
@@ -286,11 +287,8 @@ export const createCache = async (cache = {}) => {
return cache
}
-export const getCacheFilename = (source) =>
- source.name + '.xml'
-
export const getCachePath = (source, cache) =>
- Path.join(cache.path, getCacheFilename(source))
+ Path.join(cache.path, source.cacheFilename)
export const cacheSource = (source, cache) =>
write(getCachePath(source, cache), renderCache(source, cache))
@@ -346,7 +344,7 @@ export const renderCache = (source, cache) => `\
${source.displayName}
${source.description}
${buildCacheLink(source)}
-
+
${new Date(source.latestPostDate).toUTCString()}
rssssing
${source.items.map(item => item.outerHTML.replaceAll(/\n\s*/g, '')).join('\n')}
@@ -467,6 +465,16 @@ export const createView = async (view = {}) => {
if(view.imageStoreDirectory)
await openImageStore(view)
+ if(isUnset(view.templatesPath)) {
+ view.templatesPath = Path.join(import.meta.dirname, 'templates')
+ }
+
+ if(isUnset(view.stylesheetPath)) {
+ view.stylesheetPath = Path.join(import.meta.dirname, 'assets/style.css')
+ }
+
+ view.batch.add(openTemplates(view))
+
return view
}
@@ -481,32 +489,76 @@ export const openImageStore = async view => {
let dirents = await FS.readdir(imageStorePath, { withFileTypes: true })
for(let dirent of dirents) {
- if(dirent.isFile()) {
- let basename = dirent.name.slice(0, dirent.name.lastIndexOf('.'))
- view.imageStore.set(basename, Path.join(view.imageStoreDirectory, dirent.name))
- }
+ if(!dirent.isFile())
+ continue
+
+ let basename = dirent.name.slice(0, dirent.name.lastIndexOf('.'))
+ view.imageStore.set(basename, Path.join(view.imageStoreDirectory, dirent.name))
+ }
+
+ return view
+}
+
+export const openTemplates = async (view) => {
+ view.templates = {}
+ let exists = await doesExist(view.templatesPath)
+ let dirents
+
+ if(exists)
+ dirents = await FS.readdir(view.templatesPath, { withFileTypes: true })
+
+ if(!exists || dirents.length === 0)
+ throw new Error('Assets directory must contain a "nav" and "main" file.')
+
+ for(let dirent of dirents) {
+ if(!dirent.isFile())
+ continue
+
+ let extensionStart = dirent.name.lastIndexOf('.')
+ let basename = dirent.name.slice(0, extensionStart)
+ let extension = dirent.name.slice(extensionStart + 1)
+
+ if(basename == 'main' && isUnset(view.formatExtension))
+ view.formatExtension = extension
+
+ view.batch.add(
+ FS.readFile(Path.join(view.templatesPath, dirent.name), { encoding: 'utf-8' })
+ // Must remove trailing newlines so partials will work cleanly
+ .then(template => view.templates[basename] = template )
+ )
}
return view
}
export const writeView = (sources, feeds, view) => {
- view.header = renderNav(feeds, sources)
+ for(let source of sources)
+ labelList(source, view)
+
+ for(let feed of feeds)
+ labelList(feed, view)
+
+ view.header = renderNav(sources, feeds, view)
let pages = []
+
+ for(let source of sources) {
+ pages = pages.concat(createPages(source, view))
+ }
for(let feed of feeds) {
pages = pages.concat(createPages(feed, view))
}
- for(let source of sources) {
- pages = pages.concat(createPages(source, view))
- }
-
for(let page of pages) {
writePage(page, view)
}
- writeStylesheet(Path.join(import.meta.dirname, 'assets/style.css'), view)
+ writeStylesheet(view.stylesheetPath, view)
+}
+
+export const labelList = (list, view) => {
+ list.indexFilename = nameFinalPage(list, view)
+ list.empty = list.posts.length == 0
}
export const createPages = (list, view) => {
@@ -520,11 +572,12 @@ export const createPages = (list, view) => {
posts.push(list.posts[i])
if(i % view.pageSize == 0) {
- let title = getPageTitle(list, pages.length)
- let filename = i < view.pageSize ? getFinalPageFilename(list) : getPageFilename(list, pages.length)
+ let filename = i < view.pageSize ? list.indexFilename : namePage(list, pages.length, view)
+
let page = {
filename,
- title,
+ title: list.displayName,
+ index: pages.length,
posts: posts.reverse(),
lastPageLink
}
@@ -546,152 +599,74 @@ export const createPages = (list, view) => {
return pages
}
+export const namePage = (list, number, view) =>
+ list.name + '-' + number + '.' + view.formatExtension
+
+export const nameFinalPage = (list, view) =>
+ (list.main ? 'index' : list.name) + '.' + view.formatExtension
+
export const writePage = (page, view) => {
- let html = renderPage(page.title, page.posts, view.header, renderNextPageLink(page.lastPageLink))
- let promise = write(Path.join(view.path, page.filename), html)
+ let content = renderPage(page, view)
+ let promise = write(Path.join(view.path, page.filename), content)
- view.batch.add(promise.then(annotate(`Created "${page.title}" (${page.filename})`)))
+ view.batch.add(promise.then(annotate(`Created ${page.filename}`)))
}
-export const getFinalPageFilename = list =>
- (list.main ? 'index' : list.name) + '.html'
+export const renderPage = (page, view) =>
+ Mustache.render(view.templates.main, {
+ ...page,
+ sections: page.posts.map(createSection),
+ header: view.header,
+ footer: view.footer
+ }, view.templates)
-export const getPageFilename = (list, i) =>
- list.name + '-' + i + '.html'
+export const createMustacheDelimitedArray = (array) => {
+ if(isUnset(array) || array.length === 0)
+ return false
-export const getPageTitle = (list, i) =>
- list.displayName + ' - ' + (i + 1)
-
-export const renderPage = (title, posts, header, footer) => `\
-
-
-
-${title}
-
-
-
-
-
-
-
-
-
-${posts.map(renderPost).join('\n')}
-
-
-
-
-
-`
-
-export const renderPost = (post, index) => {
- let details = []
-
- if(post.title)
- details.push([ 'title', `"${post.title}"` ])
-
- if(post.categories && post.categories.length > 0)
- details.push([ 'categories', post.categories.map(name => `${name}`).join(', ') ])
-
- details.push([ 'source', `${post.source.hostname}` ])
- details.push([ 'lists', post.occurances.map(occ => `${occ.list.displayName}`).join(', ') ])
-
- return `\
-
-${post.images.map(renderImage).join('\n')}
-
-
-${post.source.displayName} (${post.source.type}) ${renderDate(new Date(post.date))} open
-
-${details.map(args => renderPostDetail(...args)).join('\n')}
-
-
-`
+ return {
+ final: array[array.length -1],
+ entries: array.slice(0, -1)
+ }
}
-export const renderPostDetail = (name, value) =>
- `${name} ${value}`
+export const createSection = (post, index) => {
+ let date = new Date(post.date)
-export const renderImage = href => {
- return `\
-
`
+ return {
+ post,
+ index,
+ date: {
+ month: date.getMonth() + 1,
+ day: date.getDate(),
+ year: date.getFullYear()
+ },
+ categories: createMustacheDelimitedArray(post.categories),
+ occurances: createMustacheDelimitedArray(post.occurances)
+ }
}
-export const renderDate = date =>
- (date.getMonth() + 1) + '.' + date.getDate() + '.' + date.getFullYear()
-
-export const renderNextPageLink = link => `\
-next`
-
-export const renderNav = (feeds, sources) => {
- let sections = {}
+export const renderNav = (sources, feeds, view) => {
+ let sourceTypes = []
for(let source of sources) {
- let section = sections[source.type]
+ let section = sourceTypes[source.type]
if(section) {
section.push(source)
} else {
- sections[source.type] = [
+ sourceTypes[source.type] = [
source
]
}
}
- let out = ''
-
- for(let name in sections) {
- out += `
-
-
-${name}
-${sections[name].map(renderNavEntry).join('\n')}
-
-`
- }
-
- return `\
-
-
-Feeds
-
-
-
-${feeds.map(renderNavEntry).join('\n')}
-
-
-
-
-
-
-
-
`
+ return Mustache.render(view.templates.nav, {
+ sourceTypes: Object.values(sourceTypes).map(createMustacheDelimitedArray),
+ feeds: createMustacheDelimitedArray(feeds),
+ }, view.templates)
}
-export const renderNavEntry = (list) => {
- let extra = ''
-
- if(list.errored) {
- extra += ' (errored)'
- } else if (list.posts.length == 0) {
- extra += ' (empty)'
- }
-
- return `${list.displayName}${extra}`
-}
-
-
-
//
// ,-. ,-. . . ;-. ,-. ,-.
// `-. | | | | | | |-'
@@ -723,6 +698,8 @@ export const populateSource = async (channel, source, postReducerCallback, cache
}
export const createSource = async (source, getChannel, postReducerCallback, cache) => {
+ source.cacheFilename = source.name + '.xml'
+
if(cache.enabled)
source = await openCache(source, cache)
source = await populateSource(await getChannel(source), source, postReducerCallback, cache)
diff --git a/package.json b/package.json
index 49f0e01..53155ff 100755
--- a/package.json
+++ b/package.json
@@ -7,10 +7,11 @@
"start": "node .",
"setup": "mkdir out && cp -r default/* ."
},
- "author": "",
+ "author": "Dakedres",
"license": "ISC",
"dependencies": {
- "jsdom": "^22.1.0"
+ "jsdom": "^22.1.0",
+ "mustache": "^4.2.0"
},
"type": "module"
}
diff --git a/templates/main.html b/templates/main.html
new file mode 100644
index 0000000..2abf398
--- /dev/null
+++ b/templates/main.html
@@ -0,0 +1,55 @@
+
+
+
+{{title}} - {{index}}
+
+
+
+
+
+
+
+
+
+{{#sections}}
+
+
+{{#post.images}}
+
+{{/post.images}}
+
+
+{{post.source.displayName}} ({{post.source.type}}) {{date.month}}.{{date.day}}.{{date.year}} open
+
+
+{{#post.title}}
+- title {{.}}
+{{/post.title}}
+{{#categories}}
+- categories {{#entries}}{{.}}, {{/entries}}{{final}}
+{{/categories}}
+- source {{post.source.hostname}}
+{{#occurances}}
+- lists {{#entries}}{{> occurance-link}} {{/entries}}{{#final}}{{> occurance-link}}{{/final}}
+{{/occurances}}
+
+
+
+
+
+
+
+{{/sections}}
+
+
+
+
+
+
diff --git a/templates/nav-link.html b/templates/nav-link.html
new file mode 100644
index 0000000..4a86728
--- /dev/null
+++ b/templates/nav-link.html
@@ -0,0 +1 @@
+{{displayName}}{{#errored}} (errored){{/errored}}{{^errored}}{{#empty}} (empty){{/empty}}{{/errored}}
diff --git a/templates/nav.html b/templates/nav.html
new file mode 100644
index 0000000..13a96c5
--- /dev/null
+++ b/templates/nav.html
@@ -0,0 +1,30 @@
+
+
+Feeds
+
+
+
+
+{{#feeds}}
+{{#entries}}{{> nav-link}} {{/entries}}{{#final}}{{> nav-link}}{{/final}}
+{{/feeds}}
+
+
+
+
+
+{{#sourceTypes}}
+-
+
+{{final.type}}
+{{#entries}}{{> nav-link}} {{/entries}}{{#final}}{{> nav-link}}{{/final}}
+
+
+{{/sourceTypes}}
+
+
+
+
+
+
+
diff --git a/templates/occurance-link.html b/templates/occurance-link.html
new file mode 100644
index 0000000..bbabdd6
--- /dev/null
+++ b/templates/occurance-link.html
@@ -0,0 +1 @@
+{{list.displayName}}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
old mode 100644
new mode 100755
index 8caed0b..afb41b6
--- a/yarn.lock
+++ b/yarn.lock
@@ -162,18 +162,16 @@ mime-types@^2.1.12:
dependencies:
mime-db "1.52.0"
-mime-types@^2.1.35:
- version "2.1.35"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
- integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
- dependencies:
- mime-db "1.52.0"
-
ms@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+mustache@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
+ integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
+
nwsapi@^2.2.4:
version "2.2.7"
resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz"