Add Yazzy integration (#12)
* Remove eslint completely * Make the 'last updated' time relative * Restore accidentally commited feeds * Add yazzy link to each article
This commit is contained in:
parent
6b1e3157ec
commit
740191e050
8 changed files with 128 additions and 122 deletions
12
biome.json
Normal file
12
biome.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -122,6 +122,11 @@
|
||||||
<div class="article-timestamp">
|
<div class="article-timestamp">
|
||||||
<relative-time data-time="{{item.timestamp}}">{{ item.timestamp | relative}}</relative-time>
|
<relative-time data-time="{{item.timestamp}}">{{ item.timestamp | relative}}</relative-time>
|
||||||
</div>
|
</div>
|
||||||
|
{% if yazzyUrl %}
|
||||||
|
<div class="article-links">
|
||||||
|
<a href="{{ yazzyUrl }}/{{ item.link }}" target="_blank" rel="noreferrer noopener">yazzy</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -141,7 +146,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<footer>
|
<footer>
|
||||||
<hr>
|
<hr>
|
||||||
<p>Last updated {{ now }}.</p>
|
<p>Last updated <relative-time data-time="{{ now }}">{{ now | relative}}</relative-time>.</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://github.com/carterworks/rss-reader">View on GitHub</a>
|
<a href="https://github.com/carterworks/rss-reader">View on GitHub</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
import pluginJs from "@eslint/js";
|
|
||||||
import globals from "globals";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
|
|
||||||
pluginJs.configs.recommended,
|
|
||||||
...tseslint.configs.recommended,
|
|
||||||
];
|
|
||||||
86
package.json
86
package.json
|
|
@ -1,46 +1,44 @@
|
||||||
{
|
{
|
||||||
"name": "bubo-reader",
|
"name": "bubo-reader",
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"description": "A simple but effective feed reader (RSS, JSON)",
|
"description": "A simple but effective feed reader (RSS, JSON)",
|
||||||
"homepage": "https://github.com/georgemandis/bubo-rss",
|
"homepage": "https://github.com/georgemandis/bubo-rss",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"build": "bun src/index.ts",
|
"build": "bun src/index.ts",
|
||||||
"check": "biome check --write ./{src,config,public} ./eslint.config.js"
|
"check": "biome check --write ./{src,config,public} ./*.json --no-errors-on-unmatched"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "George Mandis",
|
"name": "George Mandis",
|
||||||
"email": "george@mand.is",
|
"email": "george@mand.is",
|
||||||
"url": "https://george.mand.is"
|
"url": "https://george.mand.is"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/georgemandis"
|
"url": "https://github.com/sponsors/georgemandis"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/georgemandis/bubo-rss/issues",
|
"url": "https://github.com/georgemandis/bubo-rss/issues",
|
||||||
"email": "george+bubo@mand.is"
|
"email": "george+bubo@mand.is"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@feelinglovelynow/get-relative-time": "^1.1.2",
|
"@feelinglovelynow/get-relative-time": "^1.1.2",
|
||||||
"chalk": "^5.2.0",
|
"chalk": "^5.2.0",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
"rss-parser": "^3.13.0"
|
"rss-parser": "^3.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.8.1",
|
"@biomejs/biome": "^1.8.1",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/nunjucks": "^3.2.2",
|
"@types/nunjucks": "^3.2.2",
|
||||||
"@types/xml2js": "^0.4.11",
|
"@types/xml2js": "^0.4.11",
|
||||||
"tslib": "^2.5.3",
|
"tslib": "^2.5.3",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.1.3",
|
||||||
"typescript-eslint": "^7.13.1"
|
"typescript-eslint": "^7.13.1"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": ["@biomejs/biome"]
|
||||||
"@biomejs/biome"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@ details:has(li.has-recent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-timestamp,
|
.article-timestamp,
|
||||||
.feed-url {
|
.feed-url,
|
||||||
|
.article-links {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
75
src/index.ts
75
src/index.ts
|
|
@ -88,6 +88,7 @@ const finishBuild: () => void = async () => {
|
||||||
data: sortedFeeds,
|
data: sortedFeeds,
|
||||||
errors: errors,
|
errors: errors,
|
||||||
info: buboInfo,
|
info: buboInfo,
|
||||||
|
yazzyUrl: process.env.YAZZY_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
// write the output to public/index.html
|
// write the output to public/index.html
|
||||||
|
|
@ -116,48 +117,48 @@ const processFeed =
|
||||||
feed: string;
|
feed: string;
|
||||||
startTime: number;
|
startTime: number;
|
||||||
}) =>
|
}) =>
|
||||||
async (response: Response): Promise<void> => {
|
async (response: Response): Promise<void> => {
|
||||||
const body = await parseFeed(response);
|
const body = await parseFeed(response);
|
||||||
//skip to the next one if this didn't work out
|
//skip to the next one if this didn't work out
|
||||||
if (!body) return;
|
if (!body) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const contents: FeedItem = (
|
const contents: FeedItem = (
|
||||||
typeof body === "string" ? await parser.parseString(body) : body
|
typeof body === "string" ? await parser.parseString(body) : body
|
||||||
) as FeedItem;
|
) as FeedItem;
|
||||||
|
|
||||||
contents.feed = feed;
|
contents.feed = feed;
|
||||||
contents.title = getTitle(contents);
|
contents.title = getTitle(contents);
|
||||||
contents.link = getLink(contents);
|
contents.link = getLink(contents);
|
||||||
|
|
||||||
// try to normalize date attribute naming
|
// try to normalize date attribute naming
|
||||||
for (const item of contents.items) {
|
for (const item of contents.items) {
|
||||||
item.timestamp = getTimestamp(item);
|
item.timestamp = getTimestamp(item);
|
||||||
item.title = getTitle(item);
|
item.title = getTitle(item);
|
||||||
item.link = getLink(item);
|
item.link = getLink(item);
|
||||||
const timestamp = new Date(Number.parseInt(item.timestamp));
|
const timestamp = new Date(Number.parseInt(item.timestamp));
|
||||||
const eightHoursAgo = new Date();
|
const eightHoursAgo = new Date();
|
||||||
eightHoursAgo.setHours(eightHoursAgo.getHours() - 8);
|
eightHoursAgo.setHours(eightHoursAgo.getHours() - 8);
|
||||||
item.isRecent = timestamp > eightHoursAgo;
|
item.isRecent = timestamp > eightHoursAgo;
|
||||||
}
|
|
||||||
|
|
||||||
contents.hasRecent = contents.items.some((item) => item.isRecent);
|
|
||||||
|
|
||||||
contentFromAllFeeds[group].push(contents as object);
|
|
||||||
process.stdout.write(
|
|
||||||
`${success("Successfully fetched:")} ${feed} - ${benchmark(startTime)}\n`,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
process.stdout.write(
|
|
||||||
`${error("Error processing:")} ${feed} - ${benchmark(
|
|
||||||
startTime,
|
|
||||||
)}\n${err}\n`,
|
|
||||||
);
|
|
||||||
errors.push(`Error processing: ${feed}\n\t${err}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finishBuild();
|
contents.hasRecent = contents.items.some((item) => item.isRecent);
|
||||||
};
|
|
||||||
|
contentFromAllFeeds[group].push(contents as object);
|
||||||
|
process.stdout.write(
|
||||||
|
`${success("Successfully fetched:")} ${feed} - ${benchmark(startTime)}\n`,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
process.stdout.write(
|
||||||
|
`${error("Error processing:")} ${feed} - ${benchmark(
|
||||||
|
startTime,
|
||||||
|
)}\n${err}\n`,
|
||||||
|
);
|
||||||
|
errors.push(`Error processing: ${feed}\n\t${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
finishBuild();
|
||||||
|
};
|
||||||
|
|
||||||
// go through each group of feeds and process
|
// go through each group of feeds and process
|
||||||
const processFeeds = () => {
|
const processFeeds = () => {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import { readFile } from "node:fs/promises";
|
||||||
import { getRelativeTime } from "@feelinglovelynow/get-relative-time";
|
import { getRelativeTime } from "@feelinglovelynow/get-relative-time";
|
||||||
import type { Feeds, JSONValue } from "./@types/bubo";
|
import type { Feeds, JSONValue } from "./@types/bubo";
|
||||||
|
|
||||||
|
const yazzyUrl = process.env.YAZZY_URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global filters for my Nunjucks templates
|
* Global filters for my Nunjucks templates
|
||||||
*/
|
*/
|
||||||
|
|
@ -23,7 +25,7 @@ env.addFilter("formatTime", (dateString): string => {
|
||||||
return !Number.isNaN(date.getTime()) ? date.toLocaleTimeString() : dateString;
|
return !Number.isNaN(date.getTime()) ? date.toLocaleTimeString() : dateString;
|
||||||
});
|
});
|
||||||
|
|
||||||
env.addGlobal("now", new Date().toUTCString());
|
env.addGlobal("now", new Date().getTime());
|
||||||
|
|
||||||
// load the template
|
// load the template
|
||||||
const template: string = (
|
const template: string = (
|
||||||
|
|
@ -35,15 +37,18 @@ const render = ({
|
||||||
data,
|
data,
|
||||||
errors,
|
errors,
|
||||||
info,
|
info,
|
||||||
|
yazzyUrl,
|
||||||
}: {
|
}: {
|
||||||
data: Feeds;
|
data: Feeds;
|
||||||
errors: unknown[];
|
errors: unknown[];
|
||||||
info?: JSONValue;
|
info?: JSONValue;
|
||||||
|
yazzyUrl?: string;
|
||||||
}) => {
|
}) => {
|
||||||
return env.renderString(template, {
|
return env.renderString(template, {
|
||||||
data,
|
data,
|
||||||
errors,
|
errors,
|
||||||
info,
|
info,
|
||||||
|
yazzyUrl,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,23 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES2021",
|
"target": "ES2021",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"typeRoots": [
|
"typeRoots": ["src/@types"],
|
||||||
"src/@types"
|
"paths": {
|
||||||
],
|
"*": ["node_modules/*", "src/@types"]
|
||||||
"paths": {
|
}
|
||||||
"*": [
|
},
|
||||||
"node_modules/*",
|
"include": ["src/**/*"]
|
||||||
"src/@types"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue