Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
85420872ab
|
+2
-4
@@ -9,11 +9,9 @@ isHelp() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
echoReadmeInfo() {
|
echoReadmeInfo() {
|
||||||
local -r script="bs/${0##*bs/}"
|
local -r script="bs/${0##*/}"
|
||||||
local info
|
local info
|
||||||
if ! info="$(grep -A1 "## $script" "$readme" | tail -n1)"; then
|
info="$(grep -A1 "## $script" "$readme" | tail -n1)"
|
||||||
info="No info found in $readme for $script"
|
|
||||||
fi
|
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
$info
|
$info
|
||||||
Usage: $script [options]
|
Usage: $script [options]
|
||||||
|
|||||||
@@ -10,16 +10,21 @@ The plan is derived from:
|
|||||||
- Build scripts in `bs/` – only linting is required at this stage.
|
- Build scripts in `bs/` – only linting is required at this stage.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
1. **API client** (DONE)
|
1. **Environment handling** (TODO)
|
||||||
|
- Create a small helper (`@core/env.ts`) that reads `.env.ts` (or falls back to process.env).
|
||||||
|
- Expose constants: `COMMAFEED_URL`, `USERNAME`, `PASSWORD`.
|
||||||
|
1. **API client** (TODO)
|
||||||
- Adds `src/api` folder to the project structure (update also @src/README.md).
|
- Adds `src/api` folder to the project structure (update also @src/README.md).
|
||||||
- Implement a lightweight wrapper around `fetch` in `src/api/fetchAPI.ts`.
|
- Implement a lightweight wrapper around `fetch` in `src/api/index.ts`.
|
||||||
- Build `authHeader()` that returns the Basic‑Auth header using for now hardcoded vars.
|
- Build `authHeader()` that returns the Basic‑Auth header using env vars.
|
||||||
1. **Episode service** (DONE)
|
1. **Episode service** (TODO)
|
||||||
- Add `src/api/episodes.ts` with `getEpisodes(feedId)` and `getEpisode(id)`.
|
- Add `src/api/episodes.ts` with `getEpisodes(feedId)` and `getEpisode(id)`.
|
||||||
- Add data types
|
- Add data types
|
||||||
- Use `/rest/feed/entries?feed_id=${feedId}` endpoint.
|
- Use `/rest/feed/entries?feed_id=${feedId}` endpoint.
|
||||||
|
1. **Route integration** (TODO)
|
||||||
|
- In `app-episodes/routes.ts`, call the service during `enter()` to pre‑fetch episodes.
|
||||||
|
- Pass fetched data via route context or set a global state (e.g., using Lit Labs signals).
|
||||||
1. **Page update** (TODO)
|
1. **Page update** (TODO)
|
||||||
- Add fetching all episodes
|
|
||||||
- Update `<app-episodes>` to reflect loading state(s).
|
- Update `<app-episodes>` to reflect loading state(s).
|
||||||
- Add episode list item component.
|
- Add episode list item component.
|
||||||
- Update `<app-episodes>` to render the episode list received from the route context.
|
- Update `<app-episodes>` to render the episode list received from the route context.
|
||||||
|
|||||||
@@ -3,5 +3,3 @@
|
|||||||
- [x] initial setup (`bs`, app structure, …)
|
- [x] initial setup (`bs`, app structure, …)
|
||||||
- [x] adds routing support
|
- [x] adds routing support
|
||||||
- [ ] [episodes basic fetch](./plans/plan-basic-episodes.md)
|
- [ ] [episodes basic fetch](./plans/plan-basic-episodes.md)
|
||||||
- [ ] :bug: cors
|
|
||||||
- [ ] :bug: `.env.js`/`ENV`
|
|
||||||
|
|||||||
Generated
-127
@@ -24,7 +24,6 @@
|
|||||||
"@web/test-runner": "~0.20",
|
"@web/test-runner": "~0.20",
|
||||||
"babel-plugin-template-html-minifier": "~4.1",
|
"babel-plugin-template-html-minifier": "~4.1",
|
||||||
"deepmerge": "~4.3",
|
"deepmerge": "~4.3",
|
||||||
"koa-proxies": "^0.12.4",
|
|
||||||
"rollup": "~4.61",
|
"rollup": "~4.61",
|
||||||
"rollup-plugin-esbuild": "~6.2",
|
"rollup-plugin-esbuild": "~6.2",
|
||||||
"rollup-plugin-workbox": "~8.1",
|
"rollup-plugin-workbox": "~8.1",
|
||||||
@@ -6035,13 +6034,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eventemitter3": {
|
|
||||||
"version": "4.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
|
||||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/events-universal": {
|
"node_modules/events-universal": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
|
||||||
@@ -6254,27 +6246,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
|
||||||
"version": "1.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
|
||||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"debug": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
@@ -6815,21 +6786,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-proxy": {
|
|
||||||
"version": "1.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
|
||||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"eventemitter3": "^4.0.0",
|
|
||||||
"follow-redirects": "^1.0.0",
|
|
||||||
"requires-port": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
@@ -7734,21 +7690,6 @@
|
|||||||
"etag": "^1.8.1"
|
"etag": "^1.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/koa-proxies": {
|
|
||||||
"version": "0.12.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/koa-proxies/-/koa-proxies-0.12.4.tgz",
|
|
||||||
"integrity": "sha512-xxrEtN0e7s7/gNRoOMUltCbuIaCWqTQUTZNWQqet/8MoxSW0hG422lx2Al9FfYO3nCeA+b5c5/YmILRzavivDA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"http-proxy": "^1.18.1",
|
|
||||||
"path-match": "^1.2.4",
|
|
||||||
"uuid": "^8.3.2"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"koa": ">=2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/koa-send": {
|
"node_modules/koa-send": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz",
|
||||||
@@ -8838,39 +8779,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-match": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-match/-/path-match-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==",
|
|
||||||
"deprecated": "This package is archived and no longer maintained. For support, visit https://github.com/expressjs/express/discussions",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"http-errors": "~1.4.0",
|
|
||||||
"path-to-regexp": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-match/node_modules/http-errors": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "2.0.1",
|
|
||||||
"statuses": ">= 1.2.1 < 2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-match/node_modules/inherits": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
@@ -8902,23 +8810,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
|
||||||
"version": "1.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
|
|
||||||
"integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"isarray": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-to-regexp/node_modules/isarray": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
@@ -9384,13 +9275,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/requires-port": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
@@ -10846,17 +10730,6 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "8.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
|
||||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
|
||||||
"deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/v8-to-istanbul": {
|
"node_modules/v8-to-istanbul": {
|
||||||
"version": "9.3.0",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
"@web/test-runner": "~0.20",
|
"@web/test-runner": "~0.20",
|
||||||
"babel-plugin-template-html-minifier": "~4.1",
|
"babel-plugin-template-html-minifier": "~4.1",
|
||||||
"deepmerge": "~4.3",
|
"deepmerge": "~4.3",
|
||||||
"koa-proxies": "^0.12.4",
|
|
||||||
"rollup": "~4.61",
|
"rollup": "~4.61",
|
||||||
"rollup-plugin-esbuild": "~6.2",
|
"rollup-plugin-esbuild": "~6.2",
|
||||||
"rollup-plugin-workbox": "~8.1",
|
"rollup-plugin-workbox": "~8.1",
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { fetchAPI } from "./fetchAPI.js";
|
|
||||||
|
|
||||||
export interface Episode {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
content: string; // HTML
|
|
||||||
date: number;
|
|
||||||
feedId: string;
|
|
||||||
feedName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FeedEntriesResponse {
|
|
||||||
entries: Episode[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all entries for a given feed.
|
|
||||||
* @param feedId - The ID of the feed.
|
|
||||||
*/
|
|
||||||
export async function getEpisodes(feedId: string= "all"): Promise<Episode[]> {
|
|
||||||
const response = await fetchAPI(`category/entries?id=${feedId}`);
|
|
||||||
if (!response.ok)
|
|
||||||
throw new Error(`Failed to fetch episodes: ${response.statusText}`);
|
|
||||||
const data = await response.json() as FeedEntriesResponse;
|
|
||||||
return data.entries || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a single episode by its ID.
|
|
||||||
* @param id - The ID of the episode.
|
|
||||||
*/
|
|
||||||
export async function getEpisode(id: string): Promise<Episode> {
|
|
||||||
const response = await fetchAPI(`feed/entry/${id}/`);
|
|
||||||
if (!response.ok)
|
|
||||||
throw new Error(`Failed to fetch episode: ${response.statusText}`);
|
|
||||||
return await response.json() as Episode;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
const url_server = "";
|
|
||||||
const url_base = `${url_server}/rest`;
|
|
||||||
const [ username, password ] = ":".split(':');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the Basic Authentication header.
|
|
||||||
*/
|
|
||||||
export function authHeader(): string {
|
|
||||||
const credentials = btoa(`${username}:${password}`);
|
|
||||||
return `Basic ${credentials}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A lightweight wrapper around fetch for the CommaFeed API.
|
|
||||||
* @param endpoint - The API endpoint (e.g., '/rest/feed/')
|
|
||||||
* @param options - Fetch options
|
|
||||||
*/
|
|
||||||
export async function fetchAPI(endpoint: string, options: RequestInit = {}): Promise<Response> {
|
|
||||||
|
|
||||||
const url = `${url_base}/${endpoint}`;
|
|
||||||
const auth = authHeader();
|
|
||||||
|
|
||||||
options = { ...options };
|
|
||||||
const { headers = {} } = options;
|
|
||||||
Reflect.deleteProperty(options, "headers");
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
Authorization: auth,
|
|
||||||
Accept: "application/json",
|
|
||||||
...headers,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,6 @@ export const styles = css`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
--width: 33ch;
|
justify-content: flex-start;
|
||||||
/* internal */
|
|
||||||
margin-inline: max(7.5ch, calc(50% - var(--width)));
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,50 +1,14 @@
|
|||||||
import { LitElement, html } from "lit";
|
import { LitElement, html } from "lit";
|
||||||
import { customElement, state, property } from "lit/decorators.js";
|
import { customElement } from "lit/decorators.js";
|
||||||
import { styles } from "./index.css.js";
|
import { styles } from "./index.css.js";
|
||||||
import { getEpisodes, type Episode } from "../api/episodes.js";
|
|
||||||
import "../components/c-episode-list-card/index.js";
|
|
||||||
|
|
||||||
@customElement("app-episodes")
|
@customElement("app-episodes")
|
||||||
export class AppEpisodes extends LitElement {
|
export class AppEpisodes extends LitElement {
|
||||||
static override styles = styles;
|
static override styles = styles;
|
||||||
|
|
||||||
@property({ type: String }) feedId: string = "all";
|
|
||||||
@state() private episodes: Episode[] = [];
|
|
||||||
@state() private loading = true;
|
|
||||||
@state() private error: string | null = null;
|
|
||||||
|
|
||||||
override async firstUpdated() {
|
|
||||||
await this.fetchEpisodes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async fetchEpisodes() {
|
|
||||||
this.loading = true;
|
|
||||||
this.error = null;
|
|
||||||
try {
|
|
||||||
this.episodes = await getEpisodes(this.feedId);
|
|
||||||
} catch (e) {
|
|
||||||
this.error = e instanceof Error ? e.message : "An unknown error occurred";
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
if (this.loading) {
|
|
||||||
return html`<p>Loading episodes...</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.error) {
|
|
||||||
return html`<p class="error">Error: ${this.error}</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.episodes.length === 0) {
|
|
||||||
return html`<p>No episodes found.</p>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<h1>Episodes</h1>
|
<div class="logo"></div>
|
||||||
${this.episodes.map((episode) => html`<c-episode-list-card .episode=${episode}></c-episode-list-card>`)}
|
<a href="/episodes/1">Episode 1</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class AppHome extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<h1>My app</h1>
|
<h1>My app</h1>
|
||||||
<p>Hello world</p>
|
<p>Hello world</p>
|
||||||
<a href="/episodes">Episodes</a>
|
<a href="/episodes">Episode</a>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { css } from "lit";
|
|
||||||
|
|
||||||
export const styles = css`
|
|
||||||
:host {
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas:
|
|
||||||
"title title"
|
|
||||||
"date feed"
|
|
||||||
"content content";
|
|
||||||
grid-template-rows:
|
|
||||||
fit-content
|
|
||||||
fit-content
|
|
||||||
1fr;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
grid-area: title;
|
|
||||||
text-wrap: balance;
|
|
||||||
text-wrap: pretty;
|
|
||||||
}
|
|
||||||
time, .feed {
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
time { grid-area: date; }
|
|
||||||
.feed { grid-area: feed; }
|
|
||||||
.content {
|
|
||||||
grid-area: content;
|
|
||||||
max-height: 3.5lh;
|
|
||||||
overflow: auto;
|
|
||||||
font-size: .9em;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { html } from "lit";
|
|
||||||
import { fixture, expect } from "@open-wc/testing";
|
|
||||||
import { type Episode } from "@/api/episodes.js";
|
|
||||||
import "./index.js";
|
|
||||||
|
|
||||||
describe("EpisodeListItem", () => {
|
|
||||||
const mockEpisode: Episode = {
|
|
||||||
id: "1",
|
|
||||||
title: "Test Episode",
|
|
||||||
date: 1781258812000,
|
|
||||||
content: "<b>Test content</b>",
|
|
||||||
feedId: "2",
|
|
||||||
feedName: "Test Feed",
|
|
||||||
};
|
|
||||||
|
|
||||||
it("renders correctly with episode data", async () => {
|
|
||||||
const el = await fixture(html`<episode-list-card .episode=${mockEpisode}></episode-list-card>`);
|
|
||||||
|
|
||||||
const title = el.shadowRoot!.querySelector("h2");
|
|
||||||
expect(title?.textContent).to.equal("Test Episode");
|
|
||||||
const time = el.shadowRoot!.querySelector("time");
|
|
||||||
expect(time?.textContent).to.contain("2023");
|
|
||||||
const feed = el.shadowRoot!.querySelector(".feed");
|
|
||||||
expect(feed?.textContent).to.equal("Test Feed");
|
|
||||||
const content = el.shadowRoot!.querySelector(".content");
|
|
||||||
expect(content?.innerHTML.trim()).to.equal("<b>Test content</b>");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders empty when no episode is provided", async () => {
|
|
||||||
const el = await fixture(html`<episode-list-card></episode-list-card>`);
|
|
||||||
expect(el.shadowRoot!.innerHTML.trim()).to.equal("");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { LitElement, html } from "lit";
|
|
||||||
import { property, customElement } from "lit/decorators.js";
|
|
||||||
import { type Episode } from "@/api/episodes.js";
|
|
||||||
import { styles } from "./index.css.js";
|
|
||||||
import { templateContent } from "lit/directives/template-content.js";
|
|
||||||
|
|
||||||
@customElement("c-episode-list-card")
|
|
||||||
export class EpisodeListCard extends LitElement {
|
|
||||||
static override styles = styles;
|
|
||||||
|
|
||||||
@property({ type: Object }) episode!: Episode;
|
|
||||||
|
|
||||||
override render() {
|
|
||||||
if (!this.episode) return html``;
|
|
||||||
const { id, title, content, date, feedId, feedName }= this.episode;
|
|
||||||
const dateString = new Date(date).toLocaleDateString();
|
|
||||||
const template = document.createElement("template");
|
|
||||||
try {
|
|
||||||
// @ts-expect-error 2551
|
|
||||||
template.setHTML(content);
|
|
||||||
} catch (e) {
|
|
||||||
template.innerText = content;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<h2>
|
|
||||||
<a href="/episodes/${id}">${title}</a>
|
|
||||||
</h2>
|
|
||||||
<time datetime="${date}">${dateString}</time>
|
|
||||||
<a class="feed" href="/feeds/${feedId}">${feedName}</a>
|
|
||||||
<div class="content">
|
|
||||||
${templateContent(template)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-2
@@ -34,8 +34,7 @@
|
|||||||
"types": ["mocha"],
|
"types": ["mocha"],
|
||||||
"lib": ["es2021", "dom", "DOM.Iterable"],
|
"lib": ["es2021", "dom", "DOM.Iterable"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"]
|
||||||
"ENV": ["./.env.js"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts"]
|
"include": ["**/*.ts"]
|
||||||
|
|||||||
@@ -17,21 +17,7 @@ const target = mode !== "dist" // OR "src"
|
|||||||
appIndex: pathDist("index.html"),
|
appIndex: pathDist("index.html"),
|
||||||
};
|
};
|
||||||
|
|
||||||
import proxy from "koa-proxies";
|
|
||||||
import { users } from "./.env.js";
|
|
||||||
const middleware = [
|
|
||||||
proxy("/rest", {
|
|
||||||
target: "https://rss.jaandrle.cz",
|
|
||||||
headers: {
|
|
||||||
Authorization: "Basic " + Buffer.from(users[0]).toString("base64"),
|
|
||||||
},
|
|
||||||
logs: true,
|
|
||||||
changeOrigin: true,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
export default /** @type {import("@web/dev-server").DevServerConfig} */ ({
|
export default /** @type {import("@web/dev-server").DevServerConfig} */ ({
|
||||||
middleware,
|
|
||||||
open: "/", // SPA routing
|
open: "/", // SPA routing
|
||||||
nodeResolve: { // Resolve bare module imports
|
nodeResolve: { // Resolve bare module imports
|
||||||
exportConditions: ["browser", "development"],
|
exportConditions: ["browser", "development"],
|
||||||
|
|||||||
Reference in New Issue
Block a user