diff --git a/.storybook/main.ts b/.storybook/main.ts index 97f259f..c0aa788 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,46 +1,39 @@ -import type { StorybookConfig } from '@storybook/sveltekit'; -import { mergeConfig } from 'vite'; -import path from 'path'; +import type { StorybookConfig } from '@storybook/sveltekit' +import { mergeConfig } from 'vite' +import path from 'path' const config: StorybookConfig = { - stories: [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|ts|svelte)" - ], - addons: [ - "@storybook/addon-svelte-csf", - "@storybook/addon-docs", - "@storybook/addon-a11y" - ], - framework: { - name: "@storybook/sveltekit", - options: {} - }, - viteFinal: async (config) => { - return mergeConfig(config, { - resolve: { - alias: { - '$lib': path.resolve('./src/lib'), - '$components': path.resolve('./src/lib/components'), - '$icons': path.resolve('./src/assets/icons'), - '$illos': path.resolve('./src/assets/illos'), - '$styles': path.resolve('./src/assets/styles') - } - }, - css: { - preprocessorOptions: { - scss: { - additionalData: ` + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'], + addons: ['@storybook/addon-svelte-csf', '@storybook/addon-docs', '@storybook/addon-a11y'], + framework: { + name: '@storybook/sveltekit', + options: {} + }, + viteFinal: async (config) => { + return mergeConfig(config, { + resolve: { + alias: { + $lib: path.resolve('./src/lib'), + $components: path.resolve('./src/lib/components'), + $icons: path.resolve('./src/assets/icons'), + $illos: path.resolve('./src/assets/illos'), + $styles: path.resolve('./src/assets/styles') + } + }, + css: { + preprocessorOptions: { + scss: { + additionalData: ` @import './src/assets/styles/variables.scss'; @import './src/assets/styles/fonts.scss'; @import './src/assets/styles/themes.scss'; `, - api: 'modern-compiler' - } - } - } - }); - } -}; + api: 'modern-compiler' + } + } + } + }) + } +} -export default config; \ No newline at end of file +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts index d5ce9cd..f442071 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,42 +1,42 @@ -import type { Preview } from '@storybook/sveltekit'; -import '../src/assets/styles/reset.css'; -import '../src/assets/styles/globals.scss'; +import type { Preview } from '@storybook/sveltekit' +import '../src/assets/styles/reset.css' +import '../src/assets/styles/globals.scss' const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - backgrounds: { - default: 'light', - values: [ - { name: 'light', value: '#ffffff' }, - { name: 'dark', value: '#333333' }, - { name: 'admin', value: '#f5f5f5' }, - { name: 'grey-95', value: '#f8f9fa' }, - ], - }, - viewport: { - viewports: { - mobile: { - name: 'Mobile', - styles: { width: '375px', height: '667px' } - }, - tablet: { - name: 'Tablet', - styles: { width: '768px', height: '1024px' } - }, - desktop: { - name: 'Desktop', - styles: { width: '1440px', height: '900px' } - }, - }, - }, - }, -}; + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/ + } + }, + backgrounds: { + default: 'light', + values: [ + { name: 'light', value: '#ffffff' }, + { name: 'dark', value: '#333333' }, + { name: 'admin', value: '#f5f5f5' }, + { name: 'grey-95', value: '#f8f9fa' } + ] + }, + viewport: { + viewports: { + mobile: { + name: 'Mobile', + styles: { width: '375px', height: '667px' } + }, + tablet: { + name: 'Tablet', + styles: { width: '768px', height: '1024px' } + }, + desktop: { + name: 'Desktop', + styles: { width: '1440px', height: '900px' } + } + } + } + } +} -export default preview; \ No newline at end of file +export default preview diff --git a/CLAUDE.md b/CLAUDE.md index 6d946cb..1fcaf98 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,6 @@ We are using Svelte 5 in Runes mode, so make sure to only write solutions that w Make sure to use the CSS variables that are defined across the various files in `src/assets/styles`. When making new colors or defining new variables, check that it doesn't exist first, then define it. - ### Key Architecture Components **API Integration Layer** (`src/routes/api/`) diff --git a/eslint.config.js b/eslint.config.js index b092b0d..8621138 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,5 @@ // For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format -import storybook from "eslint-plugin-storybook"; +import storybook from 'eslint-plugin-storybook' import js from '@eslint/js' import ts from 'typescript-eslint' @@ -9,12 +9,12 @@ import globals from 'globals' /** @type {import('eslint').Linter.FlatConfig[]} */ export default [ - js.configs.recommended, - ...ts.configs.recommended, - ...svelte.configs['flat/recommended'], - prettier, - ...svelte.configs['flat/prettier'], - { + js.configs.recommended, + ...ts.configs.recommended, + ...svelte.configs['flat/recommended'], + prettier, + ...svelte.configs['flat/prettier'], + { languageOptions: { globals: { ...globals.browser, @@ -22,7 +22,7 @@ export default [ } } }, - { + { files: ['**/*.svelte'], languageOptions: { parserOptions: { @@ -30,8 +30,8 @@ export default [ } } }, - { + { ignores: ['build/', '.svelte-kit/', 'dist/'] }, - ...storybook.configs["flat/recommended"] -]; + ...storybook.configs['flat/recommended'] +] diff --git a/package.json b/package.json index a8bb48a..42a0a38 100644 --- a/package.json +++ b/package.json @@ -1,117 +1,117 @@ { - "name": "jedmund-svelte", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "start": "node build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write .", - "db:migrate": "prisma migrate dev", - "db:seed": "prisma db seed", - "db:studio": "prisma studio", - "setup:local": "./scripts/setup-local.sh", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" - }, - "devDependencies": { - "@musicorum/lastfm": "github:jedmund/lastfm", - "@poppanator/sveltekit-svg": "^5.0.0-svelte5.4", - "@storybook/addon-a11y": "^9.0.1", - "@storybook/addon-docs": "^9.0.1", - "@storybook/addon-svelte-csf": "^5.0.3", - "@storybook/sveltekit": "^9.0.1", - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", - "@types/eslint": "^8.56.7", - "@types/node": "^22.0.2", - "autoprefixer": "^10.4.19", - "eslint": "^9.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-storybook": "^9.0.1", - "eslint-plugin-svelte": "^2.36.0", - "globals": "^15.0.0", - "postcss": "^8.4.39", - "prettier": "^3.1.1", - "prettier-plugin-svelte": "^3.1.2", - "sass": "^1.77.8", - "storybook": "^9.0.1", - "svelte": "^5.0.0-next.1", - "svelte-check": "^3.6.0", - "tslib": "^2.4.1", - "tsx": "^4.19.4", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.0-alpha.20", - "vite": "^5.0.3" - }, - "type": "module", - "dependencies": { - "@aarkue/tiptap-math-extension": "^1.3.6", - "@prisma/client": "^6.8.2", - "@sveltejs/adapter-node": "^5.2.0", - "@tiptap/core": "^2.12.0", - "@tiptap/extension-bubble-menu": "^2.12.0", - "@tiptap/extension-character-count": "^2.12.0", - "@tiptap/extension-code-block-lowlight": "^2.12.0", - "@tiptap/extension-color": "^2.12.0", - "@tiptap/extension-floating-menu": "^2.12.0", - "@tiptap/extension-highlight": "^2.12.0", - "@tiptap/extension-image": "^2.12.0", - "@tiptap/extension-link": "^2.12.0", - "@tiptap/extension-placeholder": "^2.12.0", - "@tiptap/extension-subscript": "^2.12.0", - "@tiptap/extension-superscript": "^2.12.0", - "@tiptap/extension-table": "^2.12.0", - "@tiptap/extension-table-header": "^2.12.0", - "@tiptap/extension-table-row": "^2.12.0", - "@tiptap/extension-task-item": "^2.12.0", - "@tiptap/extension-task-list": "^2.12.0", - "@tiptap/extension-text": "^2.12.0", - "@tiptap/extension-text-align": "^2.12.0", - "@tiptap/extension-text-style": "^2.12.0", - "@tiptap/extension-typography": "^2.12.0", - "@tiptap/extension-underline": "^2.12.0", - "@tiptap/pm": "^2.12.0", - "@tiptap/starter-kit": "^2.12.0", - "@tiptap/suggestion": "^2.12.0", - "@types/multer": "^1.4.12", - "@types/redis": "^4.0.10", - "@types/steamapi": "^2.2.5", - "cloudinary": "^2.6.1", - "dotenv": "^16.5.0", - "exifr": "^7.1.3", - "giantbombing-api": "^1.0.4", - "gray-matter": "^4.0.3", - "ioredis": "^5.4.1", - "katex": "^0.16.22", - "lowlight": "^3.3.0", - "lucide-svelte": "^0.511.0", - "marked": "^15.0.12", - "multer": "^2.0.0", - "node-itunes-search": "^1.2.3", - "prisma": "^6.8.2", - "psn-api": "github:jedmund/psn-api", - "redis": "^4.7.0", - "sharp": "^0.34.2", - "steamapi": "^3.0.11", - "svelte-tiptap": "^2.1.0", - "svgo": "^3.3.2", - "tinyduration": "^3.3.1", - "tippy.js": "^6.3.7", - "tiptap-extension-auto-joiner": "^0.1.3", - "tiptap-extension-global-drag-handle": "^0.1.18", - "tiptap-markdown": "^0.8.10", - "zod": "^3.25.30" - }, - "prisma": { - "seed": "tsx prisma/seed.ts" - }, - "overrides": { - "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6" - } + "name": "jedmund-svelte", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "start": "node build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write .", + "db:migrate": "prisma migrate dev", + "db:seed": "prisma db seed", + "db:studio": "prisma studio", + "setup:local": "./scripts/setup-local.sh", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@musicorum/lastfm": "github:jedmund/lastfm", + "@poppanator/sveltekit-svg": "^5.0.0-svelte5.4", + "@storybook/addon-a11y": "^9.0.1", + "@storybook/addon-docs": "^9.0.1", + "@storybook/addon-svelte-csf": "^5.0.3", + "@storybook/sveltekit": "^9.0.1", + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6", + "@types/eslint": "^8.56.7", + "@types/node": "^22.0.2", + "autoprefixer": "^10.4.19", + "eslint": "^9.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-storybook": "^9.0.1", + "eslint-plugin-svelte": "^2.36.0", + "globals": "^15.0.0", + "postcss": "^8.4.39", + "prettier": "^3.1.1", + "prettier-plugin-svelte": "^3.1.2", + "sass": "^1.77.8", + "storybook": "^9.0.1", + "svelte": "^5.0.0-next.1", + "svelte-check": "^3.6.0", + "tslib": "^2.4.1", + "tsx": "^4.19.4", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.0-alpha.20", + "vite": "^5.0.3" + }, + "type": "module", + "dependencies": { + "@aarkue/tiptap-math-extension": "^1.3.6", + "@prisma/client": "^6.8.2", + "@sveltejs/adapter-node": "^5.2.0", + "@tiptap/core": "^2.12.0", + "@tiptap/extension-bubble-menu": "^2.12.0", + "@tiptap/extension-character-count": "^2.12.0", + "@tiptap/extension-code-block-lowlight": "^2.12.0", + "@tiptap/extension-color": "^2.12.0", + "@tiptap/extension-floating-menu": "^2.12.0", + "@tiptap/extension-highlight": "^2.12.0", + "@tiptap/extension-image": "^2.12.0", + "@tiptap/extension-link": "^2.12.0", + "@tiptap/extension-placeholder": "^2.12.0", + "@tiptap/extension-subscript": "^2.12.0", + "@tiptap/extension-superscript": "^2.12.0", + "@tiptap/extension-table": "^2.12.0", + "@tiptap/extension-table-header": "^2.12.0", + "@tiptap/extension-table-row": "^2.12.0", + "@tiptap/extension-task-item": "^2.12.0", + "@tiptap/extension-task-list": "^2.12.0", + "@tiptap/extension-text": "^2.12.0", + "@tiptap/extension-text-align": "^2.12.0", + "@tiptap/extension-text-style": "^2.12.0", + "@tiptap/extension-typography": "^2.12.0", + "@tiptap/extension-underline": "^2.12.0", + "@tiptap/pm": "^2.12.0", + "@tiptap/starter-kit": "^2.12.0", + "@tiptap/suggestion": "^2.12.0", + "@types/multer": "^1.4.12", + "@types/redis": "^4.0.10", + "@types/steamapi": "^2.2.5", + "cloudinary": "^2.6.1", + "dotenv": "^16.5.0", + "exifr": "^7.1.3", + "giantbombing-api": "^1.0.4", + "gray-matter": "^4.0.3", + "ioredis": "^5.4.1", + "katex": "^0.16.22", + "lowlight": "^3.3.0", + "lucide-svelte": "^0.511.0", + "marked": "^15.0.12", + "multer": "^2.0.0", + "node-itunes-search": "^1.2.3", + "prisma": "^6.8.2", + "psn-api": "github:jedmund/psn-api", + "redis": "^4.7.0", + "sharp": "^0.34.2", + "steamapi": "^3.0.11", + "svelte-tiptap": "^2.1.0", + "svgo": "^3.3.2", + "tinyduration": "^3.3.1", + "tippy.js": "^6.3.7", + "tiptap-extension-auto-joiner": "^0.1.3", + "tiptap-extension-global-drag-handle": "^0.1.18", + "tiptap-markdown": "^0.8.10", + "zod": "^3.25.30" + }, + "prisma": { + "seed": "tsx prisma/seed.ts" + }, + "overrides": { + "@sveltejs/vite-plugin-svelte": "^4.0.0-next.6" + } } diff --git a/prisma/seed.ts b/prisma/seed.ts index 58cba1c3..02aaa61 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -101,7 +101,8 @@ async function main() { slug: 'granblue-team', title: 'granblue.team', subtitle: 'Comprehensive web app for Granblue Fantasy players', - description: 'A comprehensive web application for Granblue Fantasy players to track raids, manage crews, and optimize team compositions. Features real-time raid tracking, character databases, and community tools.', + description: + 'A comprehensive web application for Granblue Fantasy players to track raids, manage crews, and optimize team compositions. Features real-time raid tracking, character databases, and community tools.', year: 2022, client: 'Personal Project', role: 'Full-Stack Developer', @@ -119,7 +120,8 @@ async function main() { slug: 'subway-board', title: 'Subway Board', subtitle: 'Beautiful, minimalist NYC subway dashboard', - description: 'A beautiful, minimalist dashboard displaying real-time NYC subway arrival times. Clean interface inspired by the classic subway map design with live MTA data integration.', + description: + 'A beautiful, minimalist dashboard displaying real-time NYC subway arrival times. Clean interface inspired by the classic subway map design with live MTA data integration.', year: 2023, client: 'Personal Project', role: 'Developer & Designer', @@ -136,7 +138,8 @@ async function main() { slug: 'siero-discord', title: 'Siero for Discord', subtitle: 'Discord bot for Granblue Fantasy communities', - description: 'A Discord bot for Granblue Fantasy communities providing character lookups, raid notifications, and server management tools. Serves thousands of users across multiple servers.', + description: + 'A Discord bot for Granblue Fantasy communities providing character lookups, raid notifications, and server management tools. Serves thousands of users across multiple servers.', year: 2021, client: 'Personal Project', role: 'Bot Developer', @@ -153,7 +156,8 @@ async function main() { slug: 'homelab', title: 'Homelab', subtitle: 'Self-hosted infrastructure on Kubernetes', - description: 'Self-hosted infrastructure running on Kubernetes with monitoring, media servers, and development environments. Includes automated deployments and backup strategies.', + description: + 'Self-hosted infrastructure running on Kubernetes with monitoring, media servers, and development environments. Includes automated deployments and backup strategies.', year: 2023, client: 'Personal Project', role: 'DevOps Engineer', @@ -181,11 +185,13 @@ async function main() { { type: 'paragraph', content: 'This is my first essay on the new CMS!' }, { type: 'paragraph', - content: 'The system now uses a simplified post type system with just essays and posts.' + content: + 'The system now uses a simplified post type system with just essays and posts.' }, { type: 'paragraph', - content: 'Essays are perfect for longer-form content with titles and excerpts, while posts are great for quick thoughts and updates.' + content: + 'Essays are perfect for longer-form content with titles and excerpts, while posts are great for quick thoughts and updates.' } ] }, @@ -203,7 +209,8 @@ async function main() { blocks: [ { type: 'paragraph', - content: 'Just pushed a major update to the site. The new simplified post types are working great! 🎉' + content: + 'Just pushed a major update to the site. The new simplified post types are working great! 🎉' } ] }, @@ -221,7 +228,8 @@ async function main() { blocks: [ { type: 'paragraph', - content: 'Design systems have become essential for maintaining consistency across large products.' + content: + 'Design systems have become essential for maintaining consistency across large products.' }, { type: 'paragraph', @@ -229,7 +237,8 @@ async function main() { }, { type: 'paragraph', - content: 'Too rigid, and designers feel boxed in. Too flexible, and you lose consistency.' + content: + 'Too rigid, and designers feel boxed in. Too flexible, and you lose consistency.' } ] }, @@ -264,7 +273,8 @@ async function main() { blocks: [ { type: 'paragraph', - content: 'Built a small CLI tool over the weekend. Sometimes the best projects come from scratching your own itch.' + content: + 'Built a small CLI tool over the weekend. Sometimes the best projects come from scratching your own itch.' } ] }, diff --git a/src/lib/admin-auth.ts b/src/lib/admin-auth.ts index 91cd843..a89ff9b 100644 --- a/src/lib/admin-auth.ts +++ b/src/lib/admin-auth.ts @@ -15,9 +15,9 @@ export function getAuthHeaders(): HeadersInit { // In production, this should redirect to login adminCredentials = btoa('admin:localdev') } - + return { - 'Authorization': `Basic ${adminCredentials}` + Authorization: `Basic ${adminCredentials}` } } @@ -32,14 +32,17 @@ export function clearAuth() { } // Make authenticated API request -export async function authenticatedFetch(url: string, options: RequestInit = {}): Promise { +export async function authenticatedFetch( + url: string, + options: RequestInit = {} +): Promise { const headers = { ...getAuthHeaders(), ...options.headers } - + return fetch(url, { ...options, headers }) -} \ No newline at end of file +} diff --git a/src/lib/components/AvatarSimple.svelte b/src/lib/components/AvatarSimple.svelte index ed75bbb..0ec51d3 100644 --- a/src/lib/components/AvatarSimple.svelte +++ b/src/lib/components/AvatarSimple.svelte @@ -11,11 +11,11 @@ display: inline-block; width: 100%; height: 100%; - + :global(svg) { width: 100%; height: 100%; border-radius: $avatar-radius; } } - \ No newline at end of file + diff --git a/src/lib/components/DynamicPostContent.svelte b/src/lib/components/DynamicPostContent.svelte index 9c1d90f..dcefdfa 100644 --- a/src/lib/components/DynamicPostContent.svelte +++ b/src/lib/components/DynamicPostContent.svelte @@ -14,9 +14,12 @@ const getPostTypeLabel = (postType: string) => { switch (postType) { - case 'post': return 'Post' - case 'essay': return 'Essay' - default: return 'Post' + case 'post': + return 'Post' + case 'essay': + return 'Essay' + default: + return 'Post' } } @@ -42,18 +45,22 @@ case 'bulletList': case 'ul': - const listItems = (block.content || []).map((item: any) => { - const itemText = item.content || item.text || '' - return `
  • ${itemText}
  • ` - }).join('') + const listItems = (block.content || []) + .map((item: any) => { + const itemText = item.content || item.text || '' + return `
  • ${itemText}
  • ` + }) + .join('') return `` case 'orderedList': case 'ol': - const orderedItems = (block.content || []).map((item: any) => { - const itemText = item.content || item.text || '' - return `
  • ${itemText}
  • ` - }).join('') + const orderedItems = (block.content || []) + .map((item: any) => { + const itemText = item.content || item.text || '' + return `
  • ${itemText}
  • ` + }) + .join('') return `
      ${orderedItems}
    ` case 'blockquote': @@ -110,11 +117,13 @@ {#if post.linkUrl}
    - +
    {/if} @@ -316,7 +325,8 @@ background: $grey-95; padding: 2px 6px; border-radius: 4px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; + font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', + monospace; font-size: 0.9em; color: $grey-10; } @@ -392,4 +402,4 @@ text-underline-offset: 0.15em; } } - \ No newline at end of file + diff --git a/src/lib/components/LabCard.svelte b/src/lib/components/LabCard.svelte index d787959..46a165a 100644 --- a/src/lib/components/LabCard.svelte +++ b/src/lib/components/LabCard.svelte @@ -38,9 +38,18 @@ {#if project.status === 'password-protected'}
    - - - + + + Password Protected
    @@ -81,8 +90,20 @@ {#if project.status === 'list-only'}
    - - + + View Only
    @@ -232,4 +253,4 @@ font-weight: 500; } } - \ No newline at end of file + diff --git a/src/lib/components/PhotoGrid.svelte b/src/lib/components/PhotoGrid.svelte index ade94e8..156f50e 100644 --- a/src/lib/components/PhotoGrid.svelte +++ b/src/lib/components/PhotoGrid.svelte @@ -2,12 +2,12 @@ import PhotoItem from '$components/PhotoItem.svelte' import type { PhotoItem as PhotoItemType } from '$lib/types/photos' - const { + const { photoItems, - albumSlug - }: { + albumSlug + }: { photoItems: PhotoItemType[] - albumSlug?: string + albumSlug?: string } = $props() diff --git a/src/lib/components/ProjectContent.svelte b/src/lib/components/ProjectContent.svelte index 0cc45a0..20adb6c 100644 --- a/src/lib/components/ProjectContent.svelte +++ b/src/lib/components/ProjectContent.svelte @@ -278,4 +278,4 @@ color: $grey-20; } } - \ No newline at end of file + diff --git a/src/lib/components/ProjectPasswordProtection.svelte b/src/lib/components/ProjectPasswordProtection.svelte index f8d154d..f48cd68 100644 --- a/src/lib/components/ProjectPasswordProtection.svelte +++ b/src/lib/components/ProjectPasswordProtection.svelte @@ -32,7 +32,7 @@ error = '' // Simulate a small delay for better UX - await new Promise(resolve => setTimeout(resolve, 500)) + await new Promise((resolve) => setTimeout(resolve, 500)) if (password === correctPassword) { // Store in session storage @@ -63,7 +63,13 @@ {#snippet passwordHeader()}
    - + -
    -
    - Album -
    +
    Album
    @@ -27,8 +25,8 @@ {#if album.coverPhoto}
    - {album.coverPhoto.caption @@ -60,9 +58,7 @@
    @@ -217,4 +213,4 @@ height: 16px; fill: $grey-40; } - \ No newline at end of file + diff --git a/src/lib/components/UniverseFeed.svelte b/src/lib/components/UniverseFeed.svelte index 572b62d..4f6ac16 100644 --- a/src/lib/components/UniverseFeed.svelte +++ b/src/lib/components/UniverseFeed.svelte @@ -39,4 +39,4 @@ font-size: 1.125rem; } } - \ No newline at end of file + diff --git a/src/lib/components/UniversePostCard.svelte b/src/lib/components/UniversePostCard.svelte index 9b47ccf..2069c15 100644 --- a/src/lib/components/UniversePostCard.svelte +++ b/src/lib/components/UniversePostCard.svelte @@ -15,9 +15,12 @@ const getPostTypeLabel = (postType: string) => { switch (postType) { - case 'post': return 'Post' - case 'essay': return 'Essay' - default: return 'Post' + case 'post': + return 'Post' + case 'essay': + return 'Essay' + default: + return 'Post' } } @@ -85,9 +88,7 @@ {/if}
    @@ -241,4 +242,4 @@ height: 16px; fill: $grey-40; } - \ No newline at end of file + diff --git a/src/lib/components/admin/AdminByline.svelte b/src/lib/components/admin/AdminByline.svelte index 7e46c13..5cbed27 100644 --- a/src/lib/components/admin/AdminByline.svelte +++ b/src/lib/components/admin/AdminByline.svelte @@ -40,4 +40,4 @@ gap: $unit-half; } } - \ No newline at end of file + diff --git a/src/lib/components/admin/AdminHeader.svelte b/src/lib/components/admin/AdminHeader.svelte index dd45b72..64ddb0a 100644 --- a/src/lib/components/admin/AdminHeader.svelte +++ b/src/lib/components/admin/AdminHeader.svelte @@ -36,4 +36,4 @@ align-items: center; gap: $unit-2x; } - \ No newline at end of file + diff --git a/src/lib/components/admin/AlbumForm.svelte b/src/lib/components/admin/AlbumForm.svelte index a47ecc4..e038f84 100644 --- a/src/lib/components/admin/AlbumForm.svelte +++ b/src/lib/components/admin/AlbumForm.svelte @@ -40,7 +40,11 @@ $effect(() => { if (initialData && mode === 'edit') { // Parse album content structure - if (initialData.content && typeof initialData.content === 'object' && 'type' in initialData.content) { + if ( + initialData.content && + typeof initialData.content === 'object' && + 'type' in initialData.content + ) { const albumContent = initialData.content as any if (albumContent.type === 'album') { // Album content structure: { type: 'album', gallery: [mediaIds], description: JSONContent } @@ -56,7 +60,7 @@ // Fallback to regular content content = initialData.content || { type: 'doc', content: [] } } - + // Load gallery from initialData if provided directly if (initialData.gallery) { gallery = initialData.gallery @@ -80,7 +84,7 @@ }) const mediaResults = await Promise.all(mediaPromises) - gallery = mediaResults.filter(media => media !== null) + gallery = mediaResults.filter((media) => media !== null) } catch (error) { console.error('Failed to load gallery media:', error) } @@ -114,9 +118,9 @@ postType: 'album', status: newStatus, content, - gallery: gallery.map(media => media.id), + gallery: gallery.map((media) => media.id), featuredImage: gallery.length > 0 ? gallery[0].id : undefined, - tags: tags.trim() ? tags.split(',').map(tag => tag.trim()) : [] + tags: tags.trim() ? tags.split(',').map((tag) => tag.trim()) : [] } const url = mode === 'edit' ? `/api/posts/${postId}` : '/api/posts' @@ -169,7 +173,7 @@ if (mode === 'create') { return title.trim().length > 0 || gallery.length > 0 || tags.trim().length > 0 } - + // For edit mode, compare with initial data return ( title !== (initialData?.title || '') || @@ -197,19 +201,23 @@
    {#if mode === 'create'} - - + - {:else} - + @@ -364,4 +372,4 @@ gap: $unit; } } - \ No newline at end of file + diff --git a/src/lib/components/admin/AlbumListItem.svelte b/src/lib/components/admin/AlbumListItem.svelte index 312f06f..23afafd 100644 --- a/src/lib/components/admin/AlbumListItem.svelte +++ b/src/lib/components/admin/AlbumListItem.svelte @@ -50,19 +50,19 @@ const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000) if (diffInSeconds < 60) return 'just now' - + const minutes = Math.floor(diffInSeconds / 60) if (diffInSeconds < 3600) return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago` - + const hours = Math.floor(diffInSeconds / 3600) if (diffInSeconds < 86400) return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago` - + const days = Math.floor(diffInSeconds / 86400) if (diffInSeconds < 2592000) return `${days} ${days === 1 ? 'day' : 'days'} ago` - + const months = Math.floor(diffInSeconds / 2592000) if (diffInSeconds < 31536000) return `${months} ${months === 1 ? 'month' : 'months'} ago` - + const years = Math.floor(diffInSeconds / 31536000) return `${years} ${years === 1 ? 'year' : 'years'} ago` } @@ -90,17 +90,17 @@ // Get thumbnail - try cover photo first, then first photo function getThumbnailUrl(): string | null { if (album.coverPhotoId && album.photos.length > 0) { - const coverPhoto = album.photos.find(p => p.id === album.coverPhotoId) + const coverPhoto = album.photos.find((p) => p.id === album.coverPhotoId) if (coverPhoto) { return coverPhoto.thumbnailUrl || coverPhoto.url } } - + // Fallback to first photo if (album.photos.length > 0) { return album.photos[0].thumbnailUrl || album.photos[0].url } - + return null } @@ -133,15 +133,15 @@

    {album.title}

    -
    @@ -300,4 +300,4 @@ background-color: $grey-90; margin: $unit-half 0; } - \ No newline at end of file + diff --git a/src/lib/components/admin/AlbumMetadataPopover.svelte b/src/lib/components/admin/AlbumMetadataPopover.svelte index 5cb8921..b5c4ca3 100644 --- a/src/lib/components/admin/AlbumMetadataPopover.svelte +++ b/src/lib/components/admin/AlbumMetadataPopover.svelte @@ -95,10 +95,10 @@ }) - \ No newline at end of file +/> diff --git a/src/lib/components/admin/DeleteConfirmationModal.svelte b/src/lib/components/admin/DeleteConfirmationModal.svelte index aa3dc38..5e7d0d4 100644 --- a/src/lib/components/admin/DeleteConfirmationModal.svelte +++ b/src/lib/components/admin/DeleteConfirmationModal.svelte @@ -80,13 +80,13 @@ font-size: 1.25rem; font-weight: 700; color: $grey-10; - } + } p { margin: 0 0 $unit-4x; color: $grey-20; line-height: 1.5; - } + } } .modal-actions { diff --git a/src/lib/components/admin/DropdownItem.svelte b/src/lib/components/admin/DropdownItem.svelte index 315d8a6..a768e25 100644 --- a/src/lib/components/admin/DropdownItem.svelte +++ b/src/lib/components/admin/DropdownItem.svelte @@ -5,12 +5,7 @@ disabled?: boolean } - let { - onclick, - variant = 'default', - disabled = false, - children - }: Props = $props() + let { onclick, variant = 'default', disabled = false, children }: Props = $props() function handleClick(event: MouseEvent) { if (disabled) return @@ -56,4 +51,4 @@ cursor: not-allowed; } } - \ No newline at end of file + diff --git a/src/lib/components/admin/DropdownMenu.svelte b/src/lib/components/admin/DropdownMenu.svelte index 70308b8..55c6a51 100644 --- a/src/lib/components/admin/DropdownMenu.svelte +++ b/src/lib/components/admin/DropdownMenu.svelte @@ -17,12 +17,7 @@ divider?: boolean } - let { - isOpen = $bindable(), - triggerElement, - items, - onClose - }: Props = $props() + let { isOpen = $bindable(), triggerElement, items, onClose }: Props = $props() let dropdownElement: HTMLDivElement const dispatch = createEventDispatcher() @@ -35,7 +30,7 @@ const rect = triggerElement.getBoundingClientRect() const dropdownWidth = 180 - + return { top: rect.bottom + 4, left: rect.right - dropdownWidth @@ -51,7 +46,7 @@ function handleOutsideClick(event: MouseEvent) { if (!dropdownElement || !isOpen) return - + const target = event.target as HTMLElement if (!dropdownElement.contains(target) && !triggerElement?.contains(target)) { isOpen = false @@ -131,4 +126,4 @@ background-color: $grey-90; margin: $unit-half 0; } - \ No newline at end of file + diff --git a/src/lib/components/admin/DropdownMenuContainer.svelte b/src/lib/components/admin/DropdownMenuContainer.svelte index 55d2551..91559cc 100644 --- a/src/lib/components/admin/DropdownMenuContainer.svelte +++ b/src/lib/components/admin/DropdownMenuContainer.svelte @@ -25,4 +25,4 @@ min-width: 180px; z-index: 10; } - \ No newline at end of file + diff --git a/src/lib/components/admin/Editor.svelte b/src/lib/components/admin/Editor.svelte index 21223c3..c2e4f39 100644 --- a/src/lib/components/admin/Editor.svelte +++ b/src/lib/components/admin/Editor.svelte @@ -71,7 +71,7 @@ const timer = setTimeout(() => { editor.commands.focus() }, 100) - + return () => clearTimeout(timer) } }) diff --git a/src/lib/components/admin/EditorWithUpload.svelte b/src/lib/components/admin/EditorWithUpload.svelte index 68b161d..65d7964 100644 --- a/src/lib/components/admin/EditorWithUpload.svelte +++ b/src/lib/components/admin/EditorWithUpload.svelte @@ -212,7 +212,9 @@ if (!clipboardData) return false // Check for images first - const imageItem = Array.from(clipboardData.items).find(item => item.type.indexOf('image') === 0) + const imageItem = Array.from(clipboardData.items).find( + (item) => item.type.indexOf('image') === 0 + ) if (imageItem) { const file = imageItem.getAsFile() if (!file) return false @@ -232,11 +234,11 @@ // Handle text paste - strip HTML formatting const htmlData = clipboardData.getData('text/html') const plainText = clipboardData.getData('text/plain') - + if (htmlData && plainText) { // If we have both HTML and plain text, use plain text to strip formatting event.preventDefault() - + // Use editor commands to insert text so all callbacks are triggered const editorInstance = (view as any).editor if (editorInstance) { @@ -248,7 +250,7 @@ const transaction = state.tr.insertText(plainText, selection.from, selection.to) dispatch(transaction) } - + return true // Prevent default paste behavior } diff --git a/src/lib/components/admin/EssayForm.svelte b/src/lib/components/admin/EssayForm.svelte index ba17ea8..cbafcb4 100644 --- a/src/lib/components/admin/EssayForm.svelte +++ b/src/lib/components/admin/EssayForm.svelte @@ -264,18 +264,9 @@ }} >
    - + - + item.id)) - const newImages = media.filter(item => !existingIds.has(item.id)) - + const existingIds = new Set(value.map((item) => item.id)) + const newImages = media.filter((item) => !existingIds.has(item.id)) + if (maxItems) { const availableSlots = maxItems - value.length value = [...value, ...newImages.slice(0, availableSlots)] } else { value = [...value, ...newImages] } - + showModal = false } @@ -51,11 +51,11 @@ // Drag and Drop functionality function handleDragStart(event: DragEvent, index: number) { if (!event.dataTransfer) return - + draggedIndex = index event.dataTransfer.effectAllowed = 'move' event.dataTransfer.setData('text/html', '') - + // Add dragging class to the dragged element const target = event.target as HTMLElement target.style.opacity = '0.5' @@ -64,7 +64,7 @@ function handleDragEnd(event: DragEvent) { const target = event.target as HTMLElement target.style.opacity = '1' - + draggedIndex = null dragOverIndex = null } @@ -72,7 +72,7 @@ function handleDragOver(event: DragEvent, index: number) { event.preventDefault() if (!event.dataTransfer) return - + event.dataTransfer.dropEffect = 'move' dragOverIndex = index } @@ -83,7 +83,7 @@ function handleDrop(event: DragEvent, dropIndex: number) { event.preventDefault() - + if (draggedIndex === null || draggedIndex === dropIndex) { return } @@ -91,16 +91,16 @@ // Reorder the array const newValue = [...value] const draggedItem = newValue[draggedIndex] - + // Remove the dragged item newValue.splice(draggedIndex, 1) - + // Insert at the new position (adjust index if necessary) const insertIndex = draggedIndex < dropIndex ? dropIndex - 1 : dropIndex newValue.splice(insertIndex, 0, draggedItem) - + value = newValue - + // Reset drag state draggedIndex = null dragOverIndex = null @@ -117,10 +117,8 @@ // Computed properties const hasImages = $derived(value.length > 0) const canAddMore = $derived(!maxItems || value.length < maxItems) - const selectedIds = $derived(value.map(item => item.id)) - const itemsText = $derived( - value.length === 1 ? '1 image' : `${value.length} images` - ) + const selectedIds = $derived(value.map((item) => item.id)) + const itemsText = $derived(value.length === 1 ? '1 image' : `${value.length} images`)