feature/FA-misc_AstroMigration #36
35
fictionarchive-web-astro/eslint.config.js
Normal file
35
fictionarchive-web-astro/eslint.config.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import astro from 'eslint-plugin-astro';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
...astro.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: tseslint.parser
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Disabled because we sanitize HTML with DOMPurify before rendering
|
||||||
|
'svelte/no-at-html-tags': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ['node_modules/', 'dist/', '.astro/', 'src/lib/graphql/__generated__/']
|
||||||
|
}
|
||||||
|
);
|
||||||
1535
fictionarchive-web-astro/package-lock.json
generated
1535
fictionarchive-web-astro/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@
|
|||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"codegen": "graphql-codegen --config codegen.ts -r dotenv/config --use-system-ca"
|
"codegen": "graphql-codegen --config codegen.ts -r dotenv/config --use-system-ca",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.5.1",
|
"@astrojs/node": "^9.5.1",
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"dompurify": "^3.3.0",
|
||||||
"graphql": "^16.12.0",
|
"graphql": "^16.12.0",
|
||||||
"oidc-client-ts": "^3.4.1",
|
"oidc-client-ts": "^3.4.1",
|
||||||
"svelte": "^5.45.2",
|
"svelte": "^5.45.2",
|
||||||
@@ -27,15 +30,22 @@
|
|||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
"@graphql-codegen/cli": "^6.1.0",
|
"@graphql-codegen/cli": "^6.1.0",
|
||||||
"@graphql-codegen/typed-document-node": "^6.1.3",
|
"@graphql-codegen/typed-document-node": "^6.1.3",
|
||||||
"@graphql-codegen/typescript": "^5.0.5",
|
"@graphql-codegen/typescript": "^5.0.5",
|
||||||
"@graphql-codegen/typescript-operations": "^5.0.5",
|
"@graphql-codegen/typescript-operations": "^5.0.5",
|
||||||
"@internationalized/date": "^3.10.0",
|
"@internationalized/date": "^3.10.0",
|
||||||
"@lucide/svelte": "^0.544.0",
|
"@lucide/svelte": "^0.544.0",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
"bits-ui": "^2.14.4",
|
"bits-ui": "^2.14.4",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-astro": "^1.5.0",
|
||||||
|
"eslint-plugin-svelte": "^3.13.0",
|
||||||
|
"globals": "^16.5.0",
|
||||||
"tailwind-variants": "^3.2.2",
|
"tailwind-variants": "^3.2.2",
|
||||||
"tw-animate-css": "^1.4.0"
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"typescript-eslint": "^8.48.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
TooltipProvider
|
TooltipProvider
|
||||||
} from '$lib/components/ui/tooltip';
|
} from '$lib/components/ui/tooltip';
|
||||||
import { formatRelativeTime, formatAbsoluteTime } from '$lib/utils/time';
|
import { formatRelativeTime, formatAbsoluteTime } from '$lib/utils/time';
|
||||||
|
import { sanitizeHtml } from '$lib/utils/sanitize';
|
||||||
|
|
||||||
let { novel }: NovelCardProps = $props();
|
let { novel }: NovelCardProps = $props();
|
||||||
|
|
||||||
@@ -44,7 +45,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const title = $derived(pickText(novel.name));
|
const title = $derived(pickText(novel.name));
|
||||||
const description = $derived(pickText(novel.description));
|
const descriptionRaw = $derived(pickText(novel.description));
|
||||||
|
const descriptionHtml = $derived(sanitizeHtml(descriptionRaw));
|
||||||
const coverSrc = $derived(novel.coverImage?.newPath ?? novel.coverImage?.originalPath);
|
const coverSrc = $derived(novel.coverImage?.newPath ?? novel.coverImage?.originalPath);
|
||||||
|
|
||||||
const latestChapter = $derived(
|
const latestChapter = $derived(
|
||||||
@@ -87,9 +89,9 @@
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="pt-0 pb-4 space-y-3">
|
<CardContent class="pt-0 pb-4 space-y-3">
|
||||||
<p class="line-clamp-3 text-sm text-muted-foreground" title={description}>
|
<div class="line-clamp-3 text-sm text-muted-foreground" title={descriptionRaw}>
|
||||||
{description}
|
{@html descriptionHtml}
|
||||||
</p>
|
</div>
|
||||||
{#if chapterDisplay || relativeTime}
|
{#if chapterDisplay || relativeTime}
|
||||||
<div class="flex items-center gap-1 text-xs text-muted-foreground/80">
|
<div class="flex items-center gap-1 text-xs text-muted-foreground/80">
|
||||||
{#if chapterDisplay}
|
{#if chapterDisplay}
|
||||||
|
|||||||
12
fictionarchive-web-astro/src/lib/utils/sanitize.ts
Normal file
12
fictionarchive-web-astro/src/lib/utils/sanitize.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes HTML content, allowing only safe inline formatting elements.
|
||||||
|
* Removes scripts, event handlers, iframes, and other risky elements.
|
||||||
|
*/
|
||||||
|
export function sanitizeHtml(html: string): string {
|
||||||
|
return DOMPurify.sanitize(html, {
|
||||||
|
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'br', 'p', 'span'],
|
||||||
|
ALLOWED_ATTR: []
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user