Compare commits

...

11 Commits

Author SHA1 Message Date
matthieu42morin e7b8dc721a script to create blogpost
Playwright Tests / test (push) Waiting to run Details
Vercel Preview Deployment / Deploy-Preview (push) Has been cancelled Details
Publish to Netlify / publish (push) Has been cancelled Details
2024-04-29 15:45:46 +02:00
matthieu42morin 62726a6a40 disable anon comments 2024-04-29 15:45:33 +02:00
matthieu42morin 02d4ca7898 TODO add support for added icons, list in app.html 2024-04-29 15:45:18 +02:00
matthieu42morin 910b62cd1b urara fix from Sevichecc 2024-04-29 15:44:44 +02:00
matthieu42morin b687c154b0 gitea workflows 2024-04-29 15:43:19 +02:00
matthieu42morin f90270bc24 fix site scrolling 2024-04-29 15:42:50 +02:00
matthieu42morin b0b463bea6 update err list, styles 2024-04-29 15:42:29 +02:00
matthieu42morin 4e64c1b2f1 fonts, schema 2024-04-29 15:42:11 +02:00
matthieu42morin 6e29c4f9e5 projs 2024-04-29 15:41:45 +02:00
matthieu42morin 25f2766ec2 upgrade configs 2024-04-29 15:40:37 +02:00
matthieu42morin 608045ef6c update deps 2024-04-29 15:39:55 +02:00
16 changed files with 439 additions and 257 deletions

View File

@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm exec playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View File

@ -0,0 +1,33 @@
name: 'Publish to Netlify'
on:
push:
branches: [ prod, deploy ]
pull_request:
branches: [ prod, deploy ]
jobs:
publish:
timeout-minutes: 10
runs-on: debian-latest
steps:
- uses: actions/checkout@v3
name: Checkout
- name: Build
run: |
npm --version && node --version
npm ci --no-update-notifier
npm run build
- uses: https://github.com/nwtgck/actions-netlify@v2.0
name: Deploy
with:
publish-dir: './dist'
production-deploy: true
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deployed from Gitea Action"
enable-commit-comment: false
enable-pull-request-comment: false
overwrites-pull-request-comment: true
enable-github-deployment: false
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
timeout-minutes: 1

View File

@ -90,6 +90,7 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2",
"@sentry/sveltekit": "^7.112.2",
"@sveltejs/adapter-cloudflare": "^4.4.0",
"@yushijinhun/three-minifier-rollup": "^0.4.0",
"vite-plugin-tailwind-purgecss": "^0.3.3",
"vitest": "^1.5.2"

View File

@ -5,14 +5,26 @@
<meta name="generator" content="gh:importantimport/urara" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/assets/apple-touch-icon.png" />
<link rel="icon" href="%sveltekit.assets%/assets/favicon@192.png" />
<link rel="icon" href="%sveltekit.assets%/assets/maskable@192.png" />
<link rel="manifest" crossorigin="use-credentials" href="/manifest.webmanifest" />
<link rel="alternate" type="application/feed+json" href="/feed.json" />
<link rel="alternate" type="application/atom+xml" href="/atom.xml" />
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
%sveltekit.head%
</head>
<body itemscope itemtype="https://schema.org/WebPage">
<body itemscope itemtype="https://schema.org/WebPage" data-sveltekit-prefetch>
<script>
const themeLocalStorageKey = 'theme'
if (
!!localStorage.getItem(themeLocalStorageKey)
? localStorage.getItem(themeLocalStorageKey) === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches
) {
document.body.classList.add('dark')
} else {
document.body.classList.add('light')
}
</script>
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -45,8 +45,8 @@ h6 {
@font-face {
font-family: 'Orbitron-Variable';
src:
url('assets/fonts/Orbitron-VariableFont_wght.woff') format('woff'),
url('assets/fonts/Orbitron-VariableFont_wght.ttf') format('truetype');
url('/assets/fonts/Orbitron-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/Orbitron-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900;
font-display: swap;
font-style: normal;
@ -55,8 +55,8 @@ h6 {
@font-face {
font-family: 'RobotoMono-Italic-Variable';
src:
url('assets/fonts/RobotoMono-Italic-VariableFont_wght.woff') format('woff'),
url('assets/fonts/RobotoMono-Italic-VariableFont_wght.ttf') format('truetype');
url('/assets/fonts/RobotoMono-Italic-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/RobotoMono-Italic-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900;
font-display: swap;
font-style: normal;
@ -65,8 +65,8 @@ h6 {
@font-face {
font-family: 'RobotoMono-VariableFont';
src:
url('assets/fonts/RobotoMono-VariableFont_wght.woff') format('woff'),
url('assets/fonts/RobotoMono-VariableFont_wght.ttf') format('truetype');
url('/assets/fonts/RobotoMono-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/RobotoMono-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900;
font-display: swap;
font-style: italic;

View File

@ -42,3 +42,58 @@ export const maskable: { [key: number]: Icon } = {
type: 'image/png'
}
}
// <link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
// <link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
// <link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
// <link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
// <link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
// <link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
// <link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
// <link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
// <link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
// <link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
// <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
// <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
// <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
// <link rel="manifest" href="/manifest.json">
// <meta name="msapplication-TileColor" content="#ffffff">
// <meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
// <meta name="theme-color" content="#ffffff">
// {
// "src": "\/android-icon-36x36.png",
// "sizes": "36x36",
// "type": "image\/png",
// "density": "0.75"
// },
// {
// "src": "\/android-icon-48x48.png",
// "sizes": "48x48",
// "type": "image\/png",
// "density": "1.0"
// },
// {
// "src": "\/android-icon-72x72.png",
// "sizes": "72x72",
// "type": "image\/png",
// "density": "1.5"
// },
// {
// "src": "\/android-icon-96x96.png",
// "sizes": "96x96",
// "type": "image\/png",
// "density": "2.0"
// },
// {
// "src": "\/android-icon-144x144.png",
// "sizes": "144x144",
// "type": "image\/png",
// "density": "3.0"
// },
// {
// "src": "\/android-icon-192x192.png",
// "sizes": "192x192",
// "type": "image\/png",
// "density": "4.0"
// }

View File

@ -9,7 +9,7 @@ export const post: PostConfig = {
sortBy: 'created', // sort by: created / updated
sortDir: 'down', // sort order: up / down
form: true, // enable comments: true / false
commentParade: true // enable anonymous comments: true / false
commentParade: false // enable anonymous comments: true / false
}
}
}

View File

@ -3,7 +3,7 @@
</script>
{#if $page.status === 404}
<div class="flex flex-col items-center m-4 py-8">
<div class="flex flex-col items-center m-4 py-8 space-y-8">
<div
class="relative mb-[3.33vh] flex h-24 w-24 md:h-64 md:w-64 items-center justify-center bg-surface-800"
>
@ -15,13 +15,18 @@
<h1 class="h1 absolute leading-[5rem] text-white">404</h1>
</div>
<h2 class="h4 mb-2">
<h4 class="flex text-center justify-center w-1/2">
? My God, what are you doing here, this page doesn't exist and so on and so on.
</h2>
</h4>
<p>
This site either never existed or just suddenly committed disappearance from this
universe.
universe after I changed something.
</p>
<h5>Maybe try one of these links?</h5>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/projects">Projects</a></li>
</ul>
</div>
{:else}
<h2>{$page.status}</h2>
@ -32,8 +37,7 @@
<p><strong>Sorry!</strong> A grave error has occured. Maybe try one of these links?</p>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/projects">Projects</a></li>
<li><a class="link link-accent" href="/">Home</a></li>
<li><a class="link link-accent" href="/projects">Projects</a></li>
</ul>
{/if}

View File

@ -1,5 +1,5 @@
import type { LayoutLoad } from './$types'
export const prerender = false
export const prerender = true
export const trailingSlash = 'always'
export const load: LayoutLoad = async ({ url, fetch }) => ({
path: url.pathname,

View File

@ -21,6 +21,23 @@
storedTitle.set('')
function handleTagClick(tag) {
if (tags.includes(tag)) {
tags = tags.filter(tagName => tagName !== tag);
} else {
tags = [...tags, tag];
}
scrollToRecentFeed();
}
function scrollToRecentFeed() {
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
}
$: storedPosts.subscribe(storedPosts => (allPosts = storedPosts.filter(post => !post.flags?.includes('unlisted'))))
$: storedTags.subscribe(storedTags => (allTags = storedTags as string[]))
@ -29,13 +46,9 @@
$: if (tags) {
posts = !tags ? allPosts : allPosts.filter(post => tags.every(tag => post.tags?.includes(tag)))
if (browser) {
const newUrl = tags.length > 0 ? `/?tags=${tags.toString()}#recent-feed` : '/#recent-feed';
if (loaded && browser) {
const newUrl = `/?tags=${tags.toString()}`;
history.pushState(null, '', newUrl);
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
}
}
@ -79,17 +92,7 @@
{#each allTags as tag}
<button
id={tag}
on:click|preventDefault={() => {
if (tags.includes(tag)) {
tags = tags.filter(tagName => tagName != tag);
} else {
tags = [...tags, tag];
}
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
}}
on:click={() => handleTagClick(tag)}
class:!btn-secondary={tags.includes(tag)}
class:shadow-lg={tags.includes(tag)}
class="btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 mt-4 mb-8 xl:m-0 whitespace-nowrap">

View File

@ -0,0 +1 @@
export const prerender = true

View File

@ -1,6 +1,9 @@
// svelte adapter
import adapterAuto from '@sveltejs/adapter-auto'
import adapterNode from '@sveltejs/adapter-node'
import adapterVercel from '@sveltejs/adapter-vercel'
import adapterNetlify from '@sveltejs/adapter-netlify'
import adapterCloudflare from '@sveltejs/adapter-cloudflare'
import adapterStatic from '@sveltejs/adapter-static'
// svelte preprocessor
import { mdsvex } from 'mdsvex'
@ -8,14 +11,22 @@ import mdsvexConfig from './mdsvex.config.js'
// import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import preprocess from 'svelte-preprocess'
const adapter = {
auto: adapterAuto(),
node: adapterNode(),
static: adapterStatic({
function getAdapter() {
if (Object.keys(process.env).some(key => key.includes('VERCEL'))) {
return adapterVercel()
} else if (Object.keys(process.env).some(key => key.includes('NETLIFY'))) {
return adapterNetlify()
} else if (Object.keys(process.env).some(key => key.includes('CF_PAGES'))) {
return adapterCloudflare()
} else {
return process.env.ADAPTER === 'node'
? adapterNode({ out: 'build' })
: adapterStatic({
pages: 'build',
assets: 'build',
fallback: undefined
})
}
}
/** @type {import("@svletejs/kit".Config)} */
@ -26,11 +37,7 @@ export default {
inspector: true
},
kit: {
adapter: process.env.ADAPTER
? adapter[process.env.ADAPTER.toLowerCase()]
: Object.keys(process.env).some(key => ['VERCEL', 'NETLIFY'].includes(key))
? adapter['auto']
: adapter['static'],
adapter: getAdapter(),
alias: {
$lib: './src/lib',
$root: './',
@ -44,14 +51,18 @@ export default {
prerender: {
crawl: true,
handleMissingId: 'warn',
handleHttpError: details => {
handleHttpError: ({ status, path, referrer, referenceType, message }) => {
// Handle blog trying to prerender relative links that it can't
if (details.status == 404 && details.path.startsWith('/blog' && '/projects') && details.referenceType == 'linked') {
console.warn(`PRERENDER ignored route ${details.path}`)
if (
(status == 404 && path.startsWith('/blog')) ||
path.startsWith('/projects') ||
(path.startsWith('/') && referenceType == 'linked')
) {
console.warn(`PRERENDER ignored route ${path}`)
return
}
throw new Error(`${details.status} ${details.path} from ${details.referrer}`)
throw new Error(`${status} ${path} from ${referrer}, ~~~~~~~~~ message: ${message}~~~~~~~~~`)
}
}
}

View File

@ -1,14 +1,28 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": false,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"types": ["vite/client", "vite-plugin-pwa/client"]
}
"types": ["vite/client"]
},
"include": [
"./types/**/$types.d.ts",
"./svelte.config.js",
"./vite.config.js",
"./vite.config.ts",
"./src/**/*.js",
"./src/**/*.ts",
"./src/**/*.svelte",
"./tests/**/*.js",
"./tests/**/*.ts",
"./tests/**/*.svelte"
],
"exclude": ["node_modules", "build/**/*", "src/lib/template/**/*", "src/generated/**/*"]
}

View File

@ -9,15 +9,11 @@ import chokidar from 'chokidar'
import chalk from 'chalk'
const config = {
extensions: {
posts: ['md'],
images: ['jpg', 'png', 'webp', 'avif']
},
images: [''],
extensions: ['md'],
catch: ['ENOENT', 'EEXIST']
}
const check = (ext: string) => (config.extensions.posts.includes(ext) ? 'src/routes' : 'static')
const check = (ext: string) => (config.extensions.includes(ext) ? 'src/routes' : 'static')
const log = (color: string, msg: string, dest?: string | Error) =>
console.log(
@ -41,25 +37,13 @@ const error = (err: { code: string; message: unknown }) => {
}
const cpFile = (src: string, { stat = 'copy', dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) =>
config.extensions.images.includes(path.parse(src).ext.slice(1))
? fs
.copyFile(src, path.join('src/static', src.slice(6)))
.then(() => fs.copyFile(src, path.join('static', src.slice(6))))
.then(() => log('green', `${stat} file`, dest))
.catch(error)
: fs
fs
.copyFile(src, dest)
.then(() => log('green', `${stat} file`, dest))
.catch(error)
const rmFile = (src: string, { dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) =>
config.extensions.images.includes(path.parse(src).ext.slice(1))
? fs
.rm(path.join('src/static', src.slice(6)))
.then(() => fs.rm(path.join('static', src.slice(6))))
.then(() => log('yellow', 'remove file', dest))
.catch(error)
: fs
fs
.rm(dest)
.then(() => log('yellow', 'remove file', dest))
.catch(error)
@ -79,12 +63,7 @@ const cpDir = (src: string) =>
})
)
const mkDir = (
src: string,
{
dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6)), path.join('src/static', src.slice(6))]
} = {}
) => {
const mkDir = (src: string, { dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6))] } = {}) => {
dest.forEach(path =>
fs
.mkdir(path)
@ -93,12 +72,7 @@ const mkDir = (
)
}
const rmDir = (
src: string,
{
dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6)), path.join('src/static', src.slice(6))]
} = {}
) => {
const rmDir = (src: string, { dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6))] } = {}) => {
dest.forEach(path =>
fs
.rm(path, { force: true, recursive: true })
@ -117,14 +91,12 @@ const cleanDir = (src: string) =>
const build = () => {
mkDir('static', { dest: ['static'] })
mkDir('src/static', { dest: ['src/static'] })
cpDir('urara')
}
const clean = () => {
cleanDir('urara')
rmDir('static', { dest: ['static'] })
rmDir('src/static', { dest: ['src/static'] })
}
switch (process.argv[2]) {

49
utils/create-blog.sh Normal file
View File

@ -0,0 +1,49 @@
#!/bin/bash
BASE_PATH="./user/blogs"
# Check if a directory name is provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <directory-name>"
exit 1
fi
# Get the current date in ISO 8601 format with timezone
CURRENT_DATE=$(date "+%Y-%m-%dT%H:%M:%S.000")
TIMEZONE_FORMATTED=$(date "+%z" | sed 's/\([0-9][0-9]\)\([0-9][0-9]\)/\1:\2/')
# Every time you enter pnpm run createblog YourDirName,
# a folder will be created for you based on the following configuration,
# along with a default content for index.md.
# You are free to edit the information as needed.
DIRECTORY="$BASE_PATH/$1"
MD_TEMPLATE="---
title:
description:
summary:
published: '$CURRENT_DATE$TIMEZONE_FORMATTED'
updated: '$CURRENT_DATE$TIMEZONE_FORMATTED'
cover: ./cover.jpg
coverCaption: Photo by
coverStyle: 'IN'
series_tag:
series_title:
tag:
- [svelte-QWER]
---"
# Ensure base path exists
if [ ! -d "$BASE_PATH" ]; then
mkdir -p "$BASE_PATH"
fi
# Check if directory exists
if [ -d "$DIRECTORY" ]; then
echo "Directory $DIRECTORY already exists!"
else
mkdir "$DIRECTORY"
echo "$MD_TEMPLATE" > "$DIRECTORY/index.md"
echo "Directory $DIRECTORY and index.md created successfully!"
fi

View File

@ -91,8 +91,8 @@ export default defineConfig({
manifest: false,
scope: '/',
workbox: {
globPatterns: ['posts.json', '**/*.{js,css,html,svg,ico,png,webp,avif}'],
globIgnores: ['**/sw*', '**/workbox-*']
globPatterns: ['robots.txt', 'posts.json', '**/*.{js,css,html,svg,ico,png,webp,avif}', 'prerendered/**/*.html'],
globIgnores: ['**/sw*', '**/workbox-*', '*.xml', 'feed.json', 'tags.json']
}
})
]