diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 49e4608..0000000 --- a/.dockerignore +++ /dev/null @@ -1,19 +0,0 @@ -Dockerfile -.dockerignore -.git -.gitignore -.gitattributes -README.md -.npmrc -.prettierrc -.eslintrc.cjs -.graphqlrc -.editorconfig -.svelte-kit -.vscode -node_modules -build -package -**/.env -**/dist -*.local \ No newline at end of file diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4892518..0000000 --- a/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true -indent_style = tab -indent_size = 4 -charset = utf-8 -trim_trailing_whitespace = false -quote_type = single - -[*.{yml, md}] -indent_style = space -indent_size = 2 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 7241103..0000000 --- a/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example -*.local -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index ebc1958..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - root: true, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:svelte/recommended', - 'prettier' - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] - }, - env: { - browser: true, - es2017: true, - node: true - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser' - } - } - ] -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..2e18c9b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:svelte/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "svelte" + ], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2020, + "extraFileExtensions": [ + ".svelte" + ] + }, + "env": { + "browser": true, + "es2017": true, + "node": true + }, + "overrides": [ + { + "files": [ + "*.svelte" + ], + "parser": "svelte-eslint-parser", + "parserOptions": { + "parser": "@typescript-eslint/parser" + } + } + ] +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitea/workflows/playwright.yml b/.gitea/workflows/playwright.yml deleted file mode 100644 index 0404c10..0000000 --- a/.gitea/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -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 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d7aff9f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +Thanks for ur interest in contributing to Urara! Please take a moment to read this document before submitting a pull request. + +## Pull requests + +pls ask before u start working on any important new feature. + +for minor features and bug fixes: I will accept them as long as I think the code quality is good enough. + +### Commit message + +This is not mandatory at this time, but pls use [gitmoji](https://gitmoji.dev) and [Conventional Commits](https://www.conventionalcommits.org) whenever possible. + +### Check the code + +Run this command to check the code: + +```bash +pnpm check +``` + +In general, expect to see output like this: + +```text +svelte-check found 0 errors, 0 warnings, and 0 hints +``` + +### Format the code + +run this command to format the code: + +```bash +pnpm format +``` diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..04e2ce9 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,14 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: main + workflow_dispatch: + +jobs: + deploy: + uses: importantimport/.github/.github/workflows/pnpm-gh-pages.yml@main + permissions: + contents: write + with: + publish_dir: build diff --git a/.gitignore b/.gitignore index 00d95dc..009016c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# temp file +src/routes/**/+page.svelte.md +src/routes/**/+page.md +src/static +urara.js + + .turbo + build/** + dist/** diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2068f54 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +strict-peer-dependencies=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a8d3ff9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v21.6.1 diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index a77fdde..0000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], - "pluginSearchDirs": ["."], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..794552c --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,27 @@ +{ + "printWidth": 128, + "useTabs": false, + "tabWidth": 2, + "semi": false, + "singleQuote": true, + "endOfLine": "lf", + "arrowParens": "avoid", + "trailingComma": "none", + "bracketSpacing": true, + "bracketSameLine": true, + "htmlWhitespaceSensitivity": "ignore", + "plugins": [ + "prettier-plugin-svelte" + ], + "pluginSearchDirs": [ + "." + ], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + } + ] +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e2f4226..836d9d9 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["svelte.svelte-vscode", "orta.vscode-twoslash-queries"] + "recommendations": ["svelte.svelte-vscode", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index d57a0f7..31c9240 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,112 +1,10 @@ { - "editor.formatOnSave": true, - "svelte.plugin.typescript.format.enable": true, // enable Svelte formatter for TypeScript in Svelte files - "svelte.plugin.svelte.format.enable": true, // enable Svelte formatter for HTML and CSS in Svelte files - "[svelte]": { - "editor.defaultFormatter": "svelte.svelte-vscode" // enable Prettier formatter for JavaScript files - }, - - "editor.fontFamily": "Fira Code", - "editor.fontLigatures": true, - "markdownlint.config": { - "MD033": { - "allowed_elements": ["a"] - } - }, - "prettier.documentSelectors": ["**/*.svelte"], - "tailwindCSS.classAttributes": [ - "class", - "accent", - "active", - "aspectRatio", - "background", - "bgBackdrop", - "bgDark", - "bgDrawer", - "bgLight", - "blur", - "border", - "button", - "buttonClasses", - "buttonTextFirst", - "buttonTextLast", - "buttonTextNext", - "buttonTextPrevious", - "caretClosed", - "caretOpen", - "color", - "controlSeparator", - "controlVariant", - "cursor", - "display", - "element", - "fill", - "fillDark", - "fillLight", - "flex", - "gap", - "gridColumns", - "height", - "hover", - "inactive", - "indent", - "justify", - "meter", - "padding", - "regionAnchor", - "regionBackdrop", - "regionBody", - "regionCaption", - "regionCaret", - "regionCell", - "regionChildren", - "regionCone", - "regionContent", - "regionControl", - "regionDefault", - "regionDrawer", - "regionFoot", - "regionFootCell", - "regionHead", - "regionHeadCell", - "regionHeader", - "regionIcon", - "regionInterface", - "regionInterfaceText", - "regionLabel", - "regionLead", - "regionLegend", - "regionList", - "regionListItem", - "regionNavigation", - "regionPage", - "regionPanel", - "regionRowHeadline", - "regionRowMain", - "regionSummary", - "regionSymbol", - "regionTab", - "regionTrail", - "ring", - "rounded", - "select", - "shadow", - "slotDefault", - "slotFooter", - "slotHeader", - "slotLead", - "slotMessage", - "slotMeta", - "slotPageContent", - "slotPageFooter", - "slotPageHeader", - "slotSidebarLeft", - "slotSidebarRight", - "slotTrail", - "spacing", - "text", - "track", - "width", - "zIndex" - ] + "editor.formatOnSave": true, + "files.eol": "\n", + "typescript.tsdk": "node_modules\\typescript\\lib", + "css.lint.unknownAtRules": "ignore", + "svelte.plugin.css.diagnostics.enable": false, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 22783c6..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM node:18-alpine AS builder - -WORKDIR /app -COPY package.json pnpm-lock.yaml ./ - -RUN npm install -g pnpm && \ - echo "Installing pnpm..." -RUN pnpm install --frozen-lockfile && \ - echo "Installing deps..." - -COPY . . - -RUN pnpm run build && \ - echo "Building..." && \ - pnpm prune --production - - -FROM node:18-alpine - -WORKDIR /app - -COPY --from=builder /app/build build/ -COPY --from=builder /app/node_modules node_modules/ -COPY package.json . - -EXPOSE 3000 - -ENV NODE_ENV=production -CMD [ "node", "build" ] \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index dbf0931..0000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2023-2024 Matt Morin - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 89309e9..ebeef8b 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,123 @@ -# Hello world, this is my personal site +
+
+ +urara +
+
-Featuring a blog, projects, current social accounts, skills and so on and so on, look at [Technical Features](#technical-features) +

+fff +Language +License +FOSSA Status +

+

+🚀 Demo +/ +📝 Documentation +/ +💬 Discussions +

+

+English +| +正體中文 +

-## Stack info +## 🎉 Try it now! -I focused on researching the best possible modern solutions to frontend and worked on my previous knowledge of svelte. +### Local -Javascript, Typescript -Framework: Sveltekit -CSS: Tailwindcss, postcss -[MDsveX](https://mdsvex.pngwn.io/) for markdown text file processing with plugins -Dockerfile, node-adapter for custom deploy -AWS S3 for static assets -AWS lambda for automation +```bash +npx degit importantimport/urara my-blog && cd my-blog # create a new project in my-blog +pnpm i # if u don't have pnpm installed, run: npm i -g pnpm +``` -## Technical Features +### Remote -- A possibility of great .md file processing with ability to use svelte components in .md -- Rss feed -- Sitemap, robots, Manifest, Workers -- Some playwright testing +[![Open in StackBlitz](https://img.shields.io/badge/-Open%20in%20StackBlitz-1374ef?style=for-the-badge&logo=Amp)](https://stackblitz.com/github/importantimport/urara) [![Use this template](https://img.shields.io/badge/-Use%20this%20Template-181717?style=for-the-badge&logo=GitHub)](https://github.com/importantimport/urara/generate) [![Deploy with Vercel](https://img.shields.io/badge/-Deploy%20with%20Vercel-1374ef?style=for-the-badge&logo=Vercel)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fimportantimport%2Furara&env=URARA_SITE_URL&envDescription=Site%20URL.&envLink=https%3A%2F%2Fexample.com&project-name=urara-blog&repository-name=urara-blog) [![Deploy to Netlify](https://img.shields.io/badge/-Deploy%20to%20Netlify-15847d?style=for-the-badge&logo=Netlify&logoColor=white)](https://app.netlify.com/start/deploy?repository=https%3A%2F%2Fgithub.com%2Fimportantimport%2Furara#URARA_SITE_URL=https://example.com&CUSTOM_LOGO=https://github.com/importantimport/urara/raw/main/urara/assets/any@512.png) -[CSP from rodnylab](https://rodneylab.com/sveltekit-content-security-policy/) +## ⚡️ Usage -## Credits +### Developing -This project is using [Skeleton Labs UI / Component / utils Library](https://www.skeleton.dev/) for sveltekit. -This project used some logic of gitpod.io sveltekit blog with MIT License, however they have shortly pulled their site off of github, their source or license now unreachable. -I have learned some svelte tricks used here from [Matt Croat](https://matia.xyz) alias [https://joyofcode.xyz/] +Start a development server: + +```bash +pnpm dev +``` + +### Building + +Create a production version of ur blog: + +```bash +pnpm build +``` + +u can preview the built app with `pnpm preview`. + +### Documentation + +For full documentation, visit [urara-docs.netlify.app](https://urara-docs.netlify.app). + +### Give this project a star + +tyvm! ur ⭐ will give me more motivation to improve this project. + +## ✨ Features + +- Out of the box **Atom feed** (WebSub), **Sitemap**, **PWA** (Web app manifest & ServiceWorker) support. +- Present beautiful interface designs and animations with daisyUI, of course. +- Good [IndieWeb](https://indieweb.org/) Compatibility - Multi-kind posts with [microformats2](https://microformats.org/) markup content, Showcasing [Webmentions](https://indieweb.org/Webmention) via [webmentions.io](https://webmentions.io) API. +- Don't worry about the article and image directories - just put them under a folder and they'll be [copied automatically at build time](https://github.com/importantimport/urara/blob/main/urara.ts). +- [Comment Components](https://github.com/importantimport/urara/tree/main/src/lib/components/comments): Webmentions, Giscus, Utterances... u can use more than one. + +## 📦️ Pre-packed + +### TailwindCSS & PostCSS Plugins + +- [daisyUI](https://github.com/saadeghi/daisyui) - The most popular, free and open-source Tailwind CSS component library. +- [Tailwind CSS Typography](https://github.com/tailwindlabs/tailwindcss-typography) - Beautiful typographic defaults for HTML you don't control. +- [Autoprefixer](https://github.com/postcss/autoprefixer) - Parse CSS and add vendor prefixes to rules by Can I Use. +- [CSSNANO](https://github.com/cssnano/cssnano) - A modular minifier, built on top of the PostCSS ecosystem. + +### Markdown preprocessor & Syntax highlighter + +- [MDsveX](https://github.com/pngwn/MDsveX) - A markdown preprocessor for Svelte. +- [Shiki Twoslash](https://github.com/shikijs/twoslash) - A beautiful Syntax Highlighter. + +### Vite Plugins + +- [UnoCSS](https://github.com/unocss/unocss) - The instant on-demand atomic CSS engine. +- [VitePWA](https://github.com/antfu/vite-plugin-pwa) - Zero-config PWA for Vite. + +## 🚀 Sites + +- [./kwaa.dev](https://kwaa.dev) - [kwaa/blog](https://github.com/kwaa/blog) +- [Seviche.cc](https://seviche.cc) - [Sevichecc/Urara-Blog](https://github.com/Sevichecc/Urara-Blog) +- [./khatta.sh](https://blog.shameerkashif.me) - [hash3liZer/khatta](https://github.com/hash3liZer/khatta) + +and more... + +- [urara-blog - Discussions](https://github.com/importantimport/urara/discussions/2) +- [urara-blog - Topics](https://github.com/topics/urara-blog) + +are u using Urara? add the `urara-blog` topic on ur repo! + +## 👥 Contributing + +If u're interested in contributing to Urara, pls read [contributing docs](.github/CONTRIBUTING.md) before submitting a pull request. + +## 📝 License + +This work is free, it comes without any warranty. You can redistribute it and/or modify it under the +terms of the Do What The Fuck You Want To Public License, Version 2, +as published by Sam Hocevar. See the [COPYING](https://github.com/importantimport/urara/blob/main/COPYING) file for more details. + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fimportantimport%2Furara.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fimportantimport%2Furara?ref=badge_large) + +special thanks / inspired from: + +- [@michaeloliverx - Generate Posts List](https://github.com/pngwn/MDsveX/issues/294#issuecomment-907029639) +- icon made by [Kpouri](https://github.com/kpouri) diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..bd64e4b --- /dev/null +++ b/README.zh.md @@ -0,0 +1,125 @@ +
+
+ +urara +
+
+ +

+fff +Language +License +FOSSA Status +

+

+🚀 演示 +/ +📝 文檔 +/ +💬 討論 +

+

+English +| +正體中文 +

+ +## 🎉 現在就試試! + +### 本地 + +```bash +npx degit importantimport/urara my-blog && cd my-blog # 在當前目錄創建一個名為 my-blog 的新項目 +pnpm i # 如果你沒有安裝 pnpm,運行:npm i -g pnpm +``` + +### 遠端 + +[![Open in StackBlitz](https://img.shields.io/badge/-Open%20in%20StackBlitz-1374ef?style=for-the-badge&logo=Amp)](https://stackblitz.com/github/importantimport/urara) [![Use this template](https://img.shields.io/badge/-Use%20this%20Template-181717?style=for-the-badge&logo=GitHub)](https://github.com/importantimport/urara/generate) [![Deploy with Vercel](https://img.shields.io/badge/-Deploy%20with%20Vercel-1374ef?style=for-the-badge&logo=Vercel)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fimportantimport%2Furara&env=URARA_SITE_URL&envDescription=Site%20URL.&envLink=https%3A%2F%2Fexample.com&project-name=urara-blog&repository-name=urara-blog) [![Deploy to Netlify](https://img.shields.io/badge/-Deploy%20to%20Netlify-15847d?style=for-the-badge&logo=Netlify&logoColor=white)](https://app.netlify.com/start/deploy?repository=https%3A%2F%2Fgithub.com%2Fimportantimport%2Furara#URARA_SITE_URL=https://example.com&CUSTOM_LOGO=https://github.com/importantimport/urara/raw/main/urara/assets/any@512.png) + +## ⚡️ 用法 + +### 開發 + +啟動開發服務器: + +```bash +pnpm dev +``` + +### 構建 + +創建你的博客的產品版本: + +```bash +pnpm build +``` + +你還可以使用 `pnpm preview` 預覽構建的網站。 + +### 文檔 + +如需完整文檔,請訪問 [urara-docs.netlify.app](https://urara-docs.netlify.app)。 + +### 給這個項目一個 star + +非常感謝!你的 ⭐ 會給我更多的動力來改進這個項目。 + +## ✨ 特徵 + +- 開箱即用的 **Atom feed** (WebSub), **Sitemap**, **PWA** (Web app manifest & ServiceWorker) 支持。 +- 使用 daisyUI 呈現精美的界面設計和動畫效果,當然。 +- 良好的 [IndieWeb](https://indieweb.org/) 兼容性 - 帶有 [microformats2](https://microformats.org/) 標記內容的多種帖子,通過 [webmentions.io](https://webmentions.io) API 展示 [Webmentions](https://indieweb.org/Webmention)。 +- 不用擔心文章和圖像目錄 - 只需將它們放在一個文件夾下,它們就會[在構建時自動複製](https://github.com/importantimport/urara/blob/main/urara.ts)。 +- [評論組件](https://github.com/importantimport/urara/tree/main/src/lib/components/comments): Webmentions、 Giscus、 Utterances... 你可以使用不止一個。 + +## 📦️ 預捆綁 + +### TailwindCSS & PostCSS 插件 + +- [daisyUI](https://github.com/saadeghi/daisyui) - The most popular, free and open-source Tailwind CSS component library. +- [Tailwind CSS Typography](https://github.com/tailwindlabs/tailwindcss-typography) - Beautiful typographic defaults for HTML you don't control. +- [Autoprefixer](https://github.com/postcss/autoprefixer) - Parse CSS and add vendor prefixes to rules by Can I Use. +- [CSSNANO](https://github.com/cssnano/cssnano) - A modular minifier, built on top of the PostCSS ecosystem. + +### Markdown 預處理器和語法高亮 + +- [MDsveX](https://github.com/pngwn/MDsveX) - A markdown preprocessor for Svelte. +- [Shiki Twoslash](https://github.com/shikijs/twoslash) - A beautiful Syntax Highlighter. + +### Vite 插件 + +- [UnoCSS](https://github.com/unocss/unocss) - The instant on-demand atomic CSS engine. +- [VitePWA](https://github.com/antfu/vite-plugin-pwa) - Zero-config PWA for Vite. + +## 🚀 網站 + +- [./kwaa.dev](https://kwaa.dev) - [kwaa/blog](https://github.com/kwaa/blog) +- [Seviche.cc](https://seviche.cc) - [Sevichecc/Urara-Blog](https://github.com/Sevichecc/Urara-Blog) + +和更多... + +- [urara-blog - Discussions](https://github.com/importantimport/urara/discussions/2) +- [urara-blog - Topics](https://github.com/topics/urara-blog) + +你在用 Urara 嗎?在你的 repo 上添加 `urara-blog` 主題! + +## 👥 貢獻 + +如果您有興趣為 Urara 做出貢獻,請在提交拉取請求之前閱讀[貢獻文檔](.github/CONTRIBUTING.md)。 + +## 📝 License + +這項工作是免費的,它沒有任何保證。你可以在以下條款下重新發布和/或修改它: + +Do What The Fuck You Want To Public License, Version 2, +as published by Sam Hocevar. + +有關詳細信息,請參閱 [COPYING](COPYING) 文件。 + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fimportantimport%2Furara.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fimportantimport%2Furara?ref=badge_large) + +特別感謝 / 靈感來自於: + +- [@michaeloliverx - Generate Posts List](https://github.com/pngwn/MDsveX/issues/294#issuecomment-907029639) +- [Kpouri](https://github.com/kpouri) 製作的圖標 diff --git a/mdsvex.config.js b/mdsvex.config.js index 028ba3c..162303b 100644 --- a/mdsvex.config.js +++ b/mdsvex.config.js @@ -1,73 +1,119 @@ -import { defineMDSveXConfig as defineConfig } from 'mdsvex'; -import remarkExternalLinks from 'remark-external-links'; -import remarkSetImagePath from './src/lib/utils/remark-set-image-path.js'; -import remarkLinkWithImageAsOnlyChild from './src/lib/utils/remark-link-with-image-as-only-child.js'; -import rehypeImgSize from 'rehype-img-size'; +// rehype plugins +import rehypeSlug from 'rehype-slug' +import rehypeAutolinkHeadings from 'rehype-autolink-headings' +import rehypeExternalLinks from 'rehype-external-links' -import remarkUnwrapImages from 'remark-unwrap-images'; -import remarkToc from 'remark-toc'; -import rehypeSlug from 'rehype-slug'; +// urara remark plugins +import { parse, join } from 'node:path' +import { visit } from 'unist-util-visit' +import { toString } from 'mdast-util-to-string' +import Slugger from 'github-slugger' +import remarkFFF from 'remark-fff' +import remarkFootnotes from 'remark-footnotes' -// import { highlightCode } from './src/lib/utils/highlighter.js'; +// highlighter +import { escapeSvelte } from 'mdsvex' +import { lex, parse as parseFence } from 'fenceparser' +import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from 'shiki-twoslash' -/** @type {import('mdsvex').MdsvexOptions} */ -const config = defineConfig({ - extensions: ['.svelte.md', '.md', '.svx'], - smartypants: { - dashes: 'oldschool' - }, - /* Wait for skeleton to implement Prismjs, for now use in .md files */ - // layout: { - // blog: './src/lib/components/blog/_blog-layout.svelte', - // project: './src/lib/components/projects/_project-layout.svelte', - // _: './src/lib/components/fallback/_layout.svelte' - // }, - /* Plugins */ - rehypePlugins: [ - [rehypeSlug], - [rehypeImgSize] - // [ - // /** Custom rehype plugin to add loading="lazy" to all images */ - // () => { - // return (tree) => { - // visit(tree, 'element', (node) => { - // if (node.tagName === 'img') { - // node.properties.loading = 'lazy'; - // } - // }); - // }; - // } - // ] - ], - remarkPlugins: [ - [remarkToc, { maxDepth: 3, tight: true }], - [ - (remarkExternalLinks, - { - target: '_blank' - }) - ], - [remarkUnwrapImages], - remarkSetImagePath, - remarkLinkWithImageAsOnlyChild - // [ - // headings, - // { - // behavior: 'append', - // linkProperties: {}, - // content: function (node) { - // return [ - // h('span.icon.icon-link header-anchor', { - // ariaLabel: toString(node) + ' permalink' - // }) - // ]; - // } - // } - // ], +const remarkUraraFm = + () => + (tree, { data, filename }) => { + const filepath = filename ? filename.split('/src/routes')[1] : 'unknown' + const { dir, name } = parse(filepath) + if (!data.fm) data.fm = {} + // Generate slug & path + data.fm.slug = filepath + data.fm.path = join(dir, `/${name}`.replace('/+page', '').replace('.svelte', '')) + // Generate ToC + if (data.fm.toc !== false) { + const [slugs, toc] = [new Slugger(), []] + visit(tree, 'heading', node => { + toc.push({ + depth: node.depth, + title: toString(node), + slug: slugs.slug(toString(node), false) + }) + }) + if (toc.length > 0) data.fm.toc = toc + else data.fm.toc = false + } + } - // remarkHeadingsPermaLinks, - // getHeadings - ] -}); +// Better type definitions needed +const remarkUraraSpoiler = () => tree => + visit(tree, 'paragraph', node => { + const { children } = node + const text = children[0].value + const re = /\|\|(.{1,}?)\|\|/g + if (re.test(children[0].value)) { + children[0].type = 'html' + children[0].value = text.replace(re, (_match, p1) => `${p1}`) + } + return node + }) -export default config; +/** @type {import("mdsvex").MdsvexOptions} */ +export default { + extensions: ['.svelte.md', '.md'], + smartypants: { + dashes: 'oldschool' + }, + layout: { + _: './src/lib/components/post_layout.svelte' + }, + highlight: { + highlighter: async (code, lang, meta) => { + let fence, twoslash + try { + fence = parseFence(lex([lang, meta].filter(Boolean).join(' '))) + } catch (error) { + throw new Error(`Could not parse the codefence for this code sample \n${code}`) + } + if (fence?.twoslash === true) twoslash = runTwoSlash(code, lang) + return `{@html \`${escapeSvelte( + renderCodeToHTML( + code, + lang, + fence ?? {}, + { themeName: 'material-default' }, + await createShikiHighlighter({ theme: 'material-default' }), + twoslash + ) + )}\` }` + } + }, + remarkPlugins: [ + [ + remarkFFF, + { + presets: [], + target: 'mdsvex', + autofill: { + provider: 'fs', + path: path => path.replace('/src/routes/', '/urara/') + }, + strict: { + media: { + type: 'string', + array: false + } + } + } + ], + remarkUraraFm, + remarkUraraSpoiler, + [remarkFootnotes, { inlineNotes: true }] + ], + rehypePlugins: [ + rehypeSlug, + [rehypeAutolinkHeadings, { behavior: 'wrap' }], + [ + rehypeExternalLinks, + { + rel: ['nofollow', 'noopener', 'noreferrer', 'external'], + target: '_blank' + } + ] + ] +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..02c6c1b --- /dev/null +++ b/netlify.toml @@ -0,0 +1,7 @@ +[build] +command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm build" +publish = "build" +[build.environment] +NPM_FLAGS = "--version" +[functions] +node_bundler = "esbuild" diff --git a/package.json b/package.json index f82cee6..5443c14 100644 --- a/package.json +++ b/package.json @@ -1,83 +1,86 @@ { - "name": "its-personal~portfolio", - "description": "Uses pnpm, svelte, mdsvex", - "version": "1.0.0", - "main": "index.js", - "private": true, - "homepage": "mattmor.in", - "author": "Matthieu Morin", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://git.mattmor.in/Madmin/its-personal/" - }, - "packageManager": "pnpm@8.6.6", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test": "vitest", - "lint": "prettier --plugin-search-dir . --check . && eslint .", - "format": "prettier --plugin-search-dir . --write .", - "test-ct": "playwright test -c playwright-ct.config.ts" - }, - "devDependencies": { - "@playwright/test": "^1.41.2", - "@skeletonlabs/skeleton": "2.0.0", - "@skeletonlabs/tw-plugin": "0.1.0", - "@sveltejs/kit": "^2.5.0", - "@tailwindcss/forms": "0.5.6", - "@tailwindcss/typography": "0.5.9", - "@types/js-cookie": "^3.0.6", - "@types/node": "20.5.7", - "@types/prismjs": "^1.26.3", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "autoprefixer": "10.4.15", - "emoji-regex": "^10.3.0", - "eslint": "^8.56.0", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-svelte": "^2.35.1", - "js-cookie": "^3.0.5", - "postcss": "8.4.29", - "prettier": "^2.8.8", - "prettier-plugin-svelte": "^2.10.1", - "rehype-autolink-headings": "^7.1.0", - "rehype-img-size": "^1.0.1", - "rehype-slug": "^6.0.0", - "remark-external-links": "^9.0.1", - "remark-toc": "^9.0.0", - "remark-unwrap-images": "^4.0.0", - "sass": "^1.71.0", - "shiki": "^1.1.6", - "svelte": "^4.2.11", - "svelte-check": "^3.6.4", - "tailwindcss": "3.3.3", - "tslib": "^2.6.2", - "typescript": "^5.3.3", - "unist-util-visit": "^5.0.0", - "vite": "^5.1.3", - "vite-plugin-tailwind-purgecss": "0.2.0", - "vitest": "^0.34.6" - }, - "dependencies": { - "@floating-ui/dom": "1.5.1", - "@fortawesome/fontawesome-free": "^6.5.1", - "@sentry/sveltekit": "^7.102.0", - "@sveltejs/adapter-node": "^4.0.1", - "@sveltejs/vite-plugin-svelte": "^3.0.2", - "@threlte/core": "^6.1.1", - "@threlte/extras": "^8.7.5", - "@yushijinhun/three-minifier-rollup": "^0.4.0", - "highlight.js": "11.8.0", - "latest": "^0.2.0", - "linkedom": "^0.15.6", - "mdsvex": "^0.11.0", - "prismjs": "^1.29.0", - "rss": "^1.2.2", - "svelte-preprocess": "^5.1.3" - }, - "type": "module" + "name": "urara", + "type": "module", + "version": "0.0.1", + "license": "WTFPL", + "repository": "importantimport/urara", + "homepage": "https://github.com/importantimport/urara", + "bugs": "https://github.com/importantimport/urara/issues", + "author": "藍+85CD", + "packageManager": "pnpm@8.14.1", + "scripts": { + "clean": "node urara.js clean", + "tsc": "tsc -p tsconfig.node.json", + "tsc:watch": "tsc -w -p tsconfig.node.json", + "urara:build": "node urara.js build", + "urara:watch": "node urara.js watch", + "kit:dev": "cross-env NODE_OPTIONS=--max_old_space_size=7680 vite dev", + "kit:build": "cross-env NODE_OPTIONS=--max_old_space_size=7680 vite build", + "dev:parallel": "run-p -r tsc:watch urara:watch \"kit:dev {@} \" --", + "dev": "run-s tsc \"dev:parallel {@} \" --", + "build": "run-s tsc urara:build kit:build clean", + "preview": "vite preview", + "start": "cross-env ADAPTER=node pnpm build && node build", + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", + "format": "prettier --write --plugin-search-dir=. ." + }, + "devDependencies": { + "@iconify-json/heroicons-outline": "^1.1.10", + "@iconify-json/heroicons-solid": "^1.1.11", + "@sveltejs/adapter-auto": "^3.1.1", + "@sveltejs/adapter-netlify": "^4.1.0", + "@sveltejs/adapter-node": "^4.0.1", + "@sveltejs/adapter-static": "^3.0.1", + "@sveltejs/adapter-vercel": "^5.1.0", + "@sveltejs/kit": "^2.5.0", + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "@tailwindcss/typography": "^0.5.10", + "@types/node": "^20.11.17", + "@types/unist": "^3.0.2", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@unocss/extractor-svelte": "^0.55.7", + "@vite-pwa/sveltekit": "^0.1.3", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "cross-env": "^7.0.3", + "daisyui": "^4.6.2", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.35.1", + "fenceparser": "^2.2.0", + "fff-flavored-frontmatter": "1.0.0-alpha.1", + "github-slugger": "^2.0.0", + "mdast-util-to-string": "^3.2.0", + "mdsvex": "^0.11.0", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.35", + "postcss-lightningcss": "^0.7.0", + "prettier": "^3.2.5", + "prettier-plugin-svelte": "^3.1.2", + "rehype-autolink-headings": "^6.1.1", + "rehype-external-links": "^2.1.0", + "rehype-slug": "^5.1.0", + "remark": "^14.0.3", + "remark-fff": "1.0.0-alpha.1", + "remark-footnotes": "~2.0.0", + "shiki-twoslash": "^3.1.2", + "svelte": "^4.2.10", + "svelte-check": "^3.6.4", + "svelte-eslint-parser": "^0.33.1", + "svelte-preprocess": "^5.1.3", + "sveltekit-embed": "^0.0.14", + "tailwindcss": "^3.4.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3", + "unist-util-visit": "^4.1.2", + "unocss": "^0.58.5", + "vite": "^5.1.1", + "vite-imagetools": "^4.0.19", + "vite-plugin-pwa": "^0.17.5", + "workbox-build": "^7.0.0", + "workbox-window": "^7.0.0" + } } diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index d58e542..0000000 --- a/playwright.config.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry' - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] } - }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] } - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] } - }, - - /* Test against mobile viewports. */ - { - name: 'Mobile Chrome', - use: { ...devices['Pixel 5'] } - }, - { - name: 'Mobile Safari', - use: { ...devices['iPhone 12'] } - } - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ] - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, -}); diff --git a/postcss.config.cjs b/postcss.config.cjs deleted file mode 100644 index 16dce0b..0000000 --- a/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts index 8f4d638..ca1ea75 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,9 +1,94 @@ -// See https://kit.svelte.dev/docs/types#app -// for information about these interfaces -// and what to do when importing types -declare namespace App { - // interface Locals {} - // interface PageData {} - // interface Error {} - // interface Platform {} +/// + +import type { FFFBase, FFFMedia, FFFMention } from 'fff-flavored-frontmatter' + +interface ImportMetaEnv extends Readonly> { + readonly URARA_SITE_PROTOCOL?: 'http://' | 'https://' + readonly URARA_SITE_DOMAIN?: string +} + +interface ImportMeta { + glob(pattern: string): Record + readonly env: ImportMetaEnv +} + +declare global { + namespace Urara { + namespace Post { + type Frontmatter = Omit & + Pick & + Pick & { + /** + * post type. + * @remarks auto-generated + */ + type: 'article' | 'note' | 'photo' | 'reply' | 'audio' | 'video' | 'like' | 'repost' | 'bookmark' + /** + * post layout. + */ + layout?: 'article' | 'note' | 'photo' | 'reply' + /** + * post path. + * @remarks auto-generated + */ + path: string + /** + * post slug. + * @remarks auto-generated + */ + slug: string + /** + * table of contents. + * @remarks auto-generated, article-only, set to `false` to disable + */ + toc?: false | Toc[] + /** + * the created date of the post. + * @remarks auto-generated or set manually + */ + created: string + /** + * the updated date of the post. + * @remarks auto-generated or set manually + */ + updated: string + /** + * the published date of the post. + */ + published?: string + /** + * the featured image for article, or image for "photo" / "multi-photo" posts. + * @remarks currently only supports string + */ + image?: string + /** enable some advanced features. + * @property hidden - deprecated, transfer to `unlisted` + * @property unlisted - hide this post from the homepage and feed. + * @property bridgy-fed - add a link to Bridgy Fed in the post. https://fed.brid.gy/ + * @property bridgy-{target} - add a link to Bridgy in the post. https://brid.gy/publish/{target} + */ + flags?: string[] + } + type Toc = { + depth: number + title?: string + slug?: string + children?: Toc[] + } + interface Module { + default: { + render: () => { + html: string + head: string + css: { + code: string + } + } + } + metadata: Frontmatter + } + } + type Post = Post.Frontmatter & { html?: string } + type Page = { title?: string; path: string } + } } diff --git a/src/app.html b/src/app.html index d2bc029..a92aa80 100644 --- a/src/app.html +++ b/src/app.html @@ -1,22 +1,16 @@ - - - - - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- + + + + + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ diff --git a/src/app.pcss b/src/app.pcss new file mode 100644 index 0000000..f89cac9 --- /dev/null +++ b/src/app.pcss @@ -0,0 +1,176 @@ +/* tailwind */ + +@tailwind base; + +@tailwind components; + +@tailwind utilities; + +/* global */ + +html { + @apply !bg-base-200 scroll-smooth overflow-x-hidden overflow-y-scroll; +} + +::selection { + @apply bg-primary/20; +} + +/* .urara-prose */ + +.urara-prose { + @apply !max-w-none; +} + +/* .urara-prose heading */ + +.urara-prose > :is(h1, h2, h3, h4, h5) > a { + @apply no-underline font-bold; +} + +.urara-prose > :is(h1, h2, h3, h4, h5) > a::after { + @apply pl-2 text-base-200 transition-all content-['#']; +} + +.urara-prose > :is(h1, h2, h3, h4, h5):hover > a::after { + @apply text-primary; +} + +/* .urara-prose table */ + +.urara-prose div > table > thead { + @apply border-0; +} + +.urara-prose div > table > thead > tr > th { + @apply !relative; +} + +/* .urara-prose a */ + +.urara-prose :is(p, li) > a { + @apply bg-[length:100%_0.2em] hover:bg-[length:100%_100%] bg-[position:0_88%] bg-gradient-to-t from-secondary/50 to-secondary/40 bg-no-repeat transition-all ease-in-out !no-underline; +} + +/* .urara-prose misc */ + +.urara-prose > p img { + @apply w-full; +} + +.urara-prose :is(p, li) > code { + @apply bg-base-200 px-2; +} + +.urara-prose li > input { + @apply checkbox checkbox-xs me-2 -my-0.5; +} + +.urara-prose kbd { + @apply kbd; +} + +.urara-prose hr { + @apply border-none divider; +} + +/* footer a */ + +footer a { + @apply !no-underline hover:text-primary hover:!underline transition-all; +} + +.spoiler { + @apply blur-sm hover:blur-none active:blur-none transition-all select-all; +} + +/* .prose pre */ + +.prose pre { + @apply mockup-code !bg-neutral min-w-0; +} + +.prose pre:not(.shiki) { + @apply bg-neutral text-neutral-content; +} + +.prose pre:not(.shiki)::before { + @apply sticky -left-5 -ml-5; +} + +/* .urara-prose pre */ + +@media (max-width: 768px) { + .urara-prose > pre { + @apply -mx-8 rounded-none; + } +} + +.urara-prose > pre { + @apply pb-0; +} + +.urara-prose > pre > div.code-container { + @apply pb-5 overflow-x-auto; +} + +/* shiki */ + +pre.shiki { + @apply px-0; +} + +pre.shiki::before { + @apply sticky; +} + +pre.shiki > div.code-title { + @apply absolute -mt-10 ml-20 pt-1.5 pl-1.5 opacity-50; +} + +pre.shiki .language-id { + @apply hidden; +} + +pre.shiki > .code-container { + @apply overflow-auto; +} + +:is(pre.shiki[text='true'], pre.shiki[svelte='true']) > div.code-container { + @apply mx-5; +} + +pre.shiki:not([text='true'], [svelte='true']) > .code-container > code > div.line > span:first-child { + @apply pl-5; +} + +pre.shiki:not([text='true'], [svelte='true']) > .code-container > code > div.line > span:last-child { + @apply pr-5; +} + +pre.shiki div.dim { + @apply opacity-50 transition-opacity; +} + +pre.shiki:hover div.dim { + @apply opacity-100; +} + +pre.shiki div.highlight::before { + @apply bg-warning/20 absolute content-[''] w-full h-6; +} + +pre.twoslash data-lsp { + @apply border-b border-dashed border-transparent transition-all; +} + +pre.twoslash:hover data-lsp { + @apply border-neutral-content/30; +} + +pre.twoslash data-lsp:hover::before { + /* https://daisyui.com/blog/how-to-update-daisyui-4/#3-all--focus-colors-are-removed */ + @apply content-[attr(lsp)] absolute rounded translate-y-5 bg-[color-mix(in_oklab,oklch(var(--n)),black_7%)] text-neutral-content font-mono whitespace-pre-wrap transition-all px-2 py-1 z-50; +} + +/* your code here */ diff --git a/src/app.postcss b/src/app.postcss deleted file mode 100644 index 2b81492..0000000 --- a/src/app.postcss +++ /dev/null @@ -1,53 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; -@tailwind variants; - -html, -body { - @apply h-full overflow-hidden; -} - -:root [data-theme='wintry'] { -} - -.gradient-heading { - @apply bg-clip-text text-transparent box-decoration-clone; - /* Direction */ - @apply bg-gradient-to-br; - /* Color Stops */ - @apply from-primary-500 via-tertiary-500 to-secondary-500; -} -/* gold-nouveau theme */ -@font-face { - font-family: 'Quicksand'; - src: url('/fonts/Quicksand.ttf'); - font-display: swap; -} -/* -@font-face { - font-family: 'Magilio'; - src: url('/fonts/MagilioRegular.ttf') format('truetype'); - font-weight: 900; - font-display: swap; -} - -@font-face { - font-family: 'Satoshi-Variable'; - src: url('/fonts/satoshi/Satoshi-Variable.woff2') format('woff2'), - url('/fonts/satoshi/Satoshi-Variable.woff') format('woff'), - url('/fonts/satoshi/Satoshi-Variable.ttf') format('truetype'); - font-weight: 300 900; - font-display: swap; - font-style: normal; -} - -@font-face { - font-family: 'Satoshi-VariableItalic'; - src: url('/fonts/satoshi/Satoshi-VariableItalic.woff2') format('woff2'), - url('/fonts/satoshi/Satoshi-VariableItalic.woff') format('woff'), - url('/fonts/satoshi/Satoshi-VariableItalic.ttf') format('truetype'); - font-weight: 300 900; - font-display: swap; - font-style: italic; -} */ diff --git a/src/content/blog.ts b/src/content/blog.ts deleted file mode 100644 index 339df8e..0000000 --- a/src/content/blog.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Post } from '$lib/types/post'; -import type { MarkdownMetadata } from '$content/types'; -import type { MdsvexImport } from './types'; -import { parseReadContent } from '$content/utils'; -import { error } from '@sveltejs/kit'; - -export function listPosts() { - const posts = import.meta.glob('./blog/*.md', { - eager: true, - import: 'metadata' - }); - - return parseReadContent(posts); -} - -export async function getPostMetadata(slug: string) { - const { post } = await getPost(slug); - return post; -} - -export async function getPost(slug: string) { - try { - const data: MdsvexImport = await import(`./blog/${slug}.md`); - - return { - post: { ...data.metadata, slug }, - Component: data.default - }; - } catch { - throw error(404, `Unable to find blog post "${slug}"`); - } -} diff --git a/src/content/blog/Scene.svelte b/src/content/blog/Scene.svelte deleted file mode 100644 index f2b049e..0000000 --- a/src/content/blog/Scene.svelte +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - {#if Array.isArray(contributions) && contributions.length > 0} - {#each contributions as row, i} - {#each row as contribution, j} - {#if contribution !== null} - {@const z = normalize(contribution.level)} - - - - - - - {/if} - {/each} - {/each} - {/if} - diff --git a/src/content/blog/first-post.md b/src/content/blog/first-post.md deleted file mode 100644 index c8d07a2..0000000 --- a/src/content/blog/first-post.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: First post -excerpt: First post -date: 2021-01-01 -tags: - - first - - post -published: true -image: Feature.jpg ---- -## Svelte - -Media inside the **Svelte** folder is served from the `static` folder. - -```python - -input_text = ''' "yahooapis.com", - "hotmail.com", - "gfx.ms", - "afx.ms", - "live.com", -''' -# and so on... - -lines = input_text.split('\n') - -formatted_lines = ['* ' + line.strip()[1:-2] + ' * block' for line in lines if line] - -output_text = '\n'.join(formatted_lines) -print(output_text) - -``` diff --git a/src/content/blog/postwa.md b/src/content/blog/postwa.md deleted file mode 100644 index f9e4fa3..0000000 --- a/src/content/blog/postwa.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: w post -excerpt: w post -date: 2021-01-01 -tags: - - first - - post -published: true -image: Feature.jpg ---- - -## Svelte \ No newline at end of file diff --git a/src/content/blog/second-post.md b/src/content/blog/second-post.md deleted file mode 100644 index 930f194..0000000 --- a/src/content/blog/second-post.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Second post -excerpt: Second post -date: 2023-01-01 -tags: - - first - - post -published: true -image: Feature.jpg - ---- - - -## Switching to Self-Hosted Git - -You can check it out on git.mattmor.in, it's a simple encrypted gitea instance running on AWS - -### Media - -Media inside the **Svelte** folder is server from the `static` folder. - -### Bye Bye Github with your fancy features - -I am ditching the societal value of having a contributions table on my profile, you should view it on git.mattmor.in - - ---- If my contributions in 3d do not work, Github made breaking changes to their frontend and I can't scrape it anymore. \ No newline at end of file diff --git a/src/content/projects.ts b/src/content/projects.ts deleted file mode 100644 index bd46ce9..0000000 --- a/src/content/projects.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { MdsvexImport } from '$content/types'; -import type { MarkdownMetadata } from '$content/types'; -import type { Project } from '$lib/types/projects'; -import { parseReadContent } from './utils'; -import { error } from '@sveltejs/kit'; - -/** - * Gets all the projects metadata - */ -export function listProjects() { - const projects = import.meta.glob('./projects/*.md', { - eager: true, - import: 'metadata' - }); - - return parseReadContent(projects); -} -export async function getProjectMetadata(slug: string) { - const { post } = await getProject(slug); - return post; -} - -export async function getProject(slug: string) { - try { - const data: MdsvexImport = await import( - `./projects/${slug}.md` - ); - - return { - post: { ...data.metadata, slug }, - Component: data.default - }; - } catch { - throw error(404, `Unable to find project "${slug}"`); - } -} diff --git a/src/content/projects/erant.md b/src/content/projects/erant.md deleted file mode 100644 index 7a5e887..0000000 --- a/src/content/projects/erant.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Erant -excerpt: A SaaS helping SMEs in the tourism sector with virtualization, customer experience & analytics. It got into the republic finale of Soutěž & Podnikej. -date: 2021-01-01 -published: true -image: Feature.jpg ---- - -## Svelte - -Media inside the **Svelte** folder is served from the `static` folder. - -```python - -``` diff --git a/src/content/projects/seedling.md b/src/content/projects/seedling.md deleted file mode 100644 index a18b88c..0000000 --- a/src/content/projects/seedling.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Seedling -excerpt: An Iot Project, where we built a sensor system for plant care and accompanying app that alerted the user to what their plant needs. We made PCBs, industrial designs, 3D printed cases as clueless students. -date: 2021-01-01 -published: true -image: Feature.jpg ---- - -This is a recollection of my first "startup" journey. - -In 2020, after the onset of Covid, I remembered a presentation I heard from the founder of "Soutěž & Podnikej" Martin Vítek, on a meeting of the Prague Highschool Assembly. -It was an invitation to join their great program guiding highschoolers to launch an idea to financial fruition. The program, now sadly no longer operating, was inspirational to me as entrepreneurship and tech startups for me was a way to bring about a revolution, to solve a problem. -The journey that I embarked on, however, was not something I thought I signed up for. -I have not yet till that point in my life understood the deep rabbit hole of truly understanding a problem, the markets and people's needs, technical difficulties and the state of current technological developments, having the methodologies, networks of contacts and partners and capital at my disposal. - -However what the program gave me was at least a brief outline of where to begin and how to progress. I began with a problem. As I was engaged in discussions and student organizations centered on ecology, especially thanks to being a part of many MUN and EYP conferences, I remembered a paper of the UN FAO called [2050: A third more mouths to feed](https://www.fao.org/newsroom/detail/2050-A-third-more-mouths-to-feed/), basically amongst the array of possibilities - water scarcity, food demand, population will increase - - -[Ben Einsteins LinkedIn post](https://www.linkedin.com/pulse/heres-why-juiceros-press-so-expensive-ben-einstein/) sums up the problem of Hardware startups - -## Svelte - -Media inside the **Svelte** folder is served from the `static` folder. - -```python - -``` diff --git a/src/content/skills.ts b/src/content/skills.ts deleted file mode 100644 index 604204e..0000000 --- a/src/content/skills.ts +++ /dev/null @@ -1,139 +0,0 @@ -export type SkillLevel = 'A' | 'B' | 'C'; // A: Proficient, B: Experienced, C: limited Experience - -export interface Skill { - title: string; - level: SkillLevel; -} -export interface SkillSubCategory { - title: string; - level: number; - skills: Skill[]; -} -export interface SkillCategory { - title: string; - level: number; - subCategories: SkillSubCategory[]; -} -// prettier-ignore -const skillCategories: SkillCategory[] = [ - {title:'Software Development', level: 70, subcategories: [ - {title:'Programming Languages', level: 75, skills: [ - { title: 'JavaScript/TypeScript', level: 'A' }, - { title: 'Python', level: 'B' }, - { title: 'Rust', level: 'C' }, - { title: 'Bash', level: 'B' }, - { title: 'SQL', level: 'B' }, - { title: 'LaTeX', level: 'B' }, - ]}, - {title:'Web Frameworks', level: 70, skills: [ - { title: 'Svelte(Kit)', level: 'A' }, - { title: 'React', level: 'C'}, - ]}, - {title:'Configuration and Performance', level: 70, skills: [ - { title: 'SEO', level: 'B'}, - { title: 'Performance', level: 'B'}, - { title: 'Obfuscation', level: 'B'}, - ]}, - {title:'Databases', level: 70, skills: [ - { title: 'PostgreSQL', level: 'A' }, - { title: 'MariaDB', level: 'B' }, - { title: 'MongoDB', level: 'C' } - ]}, - {title:'Testing & Validation', level: 50, skills: [ - { title: 'ajv', level: 'A' }, - { title: 'Playwright', level: 'B'}, - { title: 'SEO, performance optimizations', level: 'B'}, - ]}, - ]}, - {title:'DevOps', level: 70, subcategories: [ - {title:'Infrastructure & Configuration Management', level: 80, skills: [ - { title: 'Terraform & tooling', level: 'A'}, - { title: 'Ansible', level: 'A'}, - { title: 'Docker, Docker-Compose', level: 'A' }, - { title: 'Kubernetes', level: 'C'}, - ]}, - { title: 'Version Control & CI/CD', level: 90, skills: [ - { title: 'Git', level: 'A' }, - { title: 'GitHub Ecosystem', level: 'A' }, - { title: 'Gitea', level: 'A' }, - { title: 'Gitlab Ecosystem', level: 'B' } - ]}, - { title: 'Monitoring & Observability ', level: 90, skills: [ - { title: 'Grafana', level: 'B' }, - { title: 'Prometheus', level: 'B' }, - ]}, - {title:'Vercel', level: 100, skills: []}, - ]}, - {title:'Cloud Computing', level: 70, subcategories: [ - {title:'AWS', level: 80, skills: [ - { title: 'EC2', level: 'A' }, - { title: 'RDS', level: 'A'}, - { title: 'S3', level: 'A'}, - { title: 'CloudFormation', level: 'C'}, - ]}, - { title: 'Azure (C)', skills: [ - { title: 'Azure OpenAI API', level: 'B' }, - { title: 'AKS', level: 'B' } - ]}, - { title: 'Hashicorp', skills: [ - { title: 'Vault', level: 'B' }, - { title: 'Consul', level: 'C' }, - ]}, - {title:'Vercel', level: 100, skills: []}, - {title:'DigitalOcean', level: 100, skills: []}, - ]}, - {title:'System Administration', level: 75, subcategories: [ - {title:'Operating Systems', level: 80, skills: [ - { title: 'Debian / Ubuntu', level: 'A' }, - { title: 'Nix(OS)', level: 'B' }, - { title: 'Arch Linux', level: 'B' }, - { title: 'Alpine', level: 'B' }, - { title: 'MicroSuck Winbloats', level: 'B' }, - ]}, - { title: 'Version Control & CI/CD', level: 90, skills: [ - { title: 'Git', level: 'A' }, - { title: 'GitHub & Gitea Ecosystem', level: 'A' }, - { title: 'Gitlab Ecosystem', level: 'B' } - ]}, - { title: 'Monitoring & Observability ', level: 90, skills: [ - { title: 'Grafana', level: 'B' }, - { title: 'Prometheus', level: 'B' }, - ]}, - { title: 'Secrets Man. & Cryptography', level: 70, skills: [ - { title: 'Hashicorp Vault', level: 'A' }, - { title: 'Sops', level: 'B' }, - { title: 'AWS Secrets Manager', level: 'B' }, - ]} - ]}, - {title:"Some fun geek skillz", level: 70, subcategories: [ - {title:'mini hardware', level: 80, skills: [ - { title: 'Raspberry Pi', level: 'A' }, - { title: 'ESP8266, ESP32', level: 'A' }, - { title: 'Arduino', level: 'A' }, - { title: 'MQTT', level: 'B' }, - { title: 'Wifi & BLE', level: 'B' } - ]}, - { title: '3D printing', level: 90, skills: [ - { title: 'knowledge, theory', level: 'A' }, - { title: 'materials - PLA, PETG', level: 'A' }, - { title: 'Autodesk Inventor', level: 'B' }, - { title: 'OctoPrint and Klipper', level: 'B' }, - { title: 'Ultimaker Cura', level: 'B' }, - ]}, - ]}, - {title:'Languages', level: 70, skills: [ - { title: 'English', level: 'A' }, - { title: 'Czech', level: 'A' }, - { title: 'French', level: 'B' }, - { title: 'German', level: 'C' } - ]}, - {title:'Design', level: 70, skills: [ - { title: 'Figma', level: 'A' }, - { title: 'UI/UX', level: 'B' }, - { title: 'LaTeX', level: 'C' }, - { title: 'Wireframing & Prototyping', level: 'B' }, - { title: 'Myriads of image manipulation and generation tools', level: 'B' }, - ]} -]; - -export default skillCategories; diff --git a/src/content/types.d.ts b/src/content/types.d.ts deleted file mode 100644 index 9655b9f..0000000 --- a/src/content/types.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface MarkdownHeading { - title: string; - slug: string; - level: number; - children: MarkdownHeading[]; -} - -export interface MarkdownMetadata { - headings: MarkdownHeading[]; -} - -export interface MdsvexImport { - // Technically not correct but needed to make language-tools happy - default: ConstructorOfATypedSvelteComponent; - metadata: T; -} diff --git a/src/content/utils.ts b/src/content/utils.ts deleted file mode 100644 index 5ed792b..0000000 --- a/src/content/utils.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { create_ssr_component } from 'svelte/internal'; - -/** - * Sorts an array of objects by their `date` property in descending order. - * @param a - The first object to compare. - * @param b - The second object to compare. - * @returns A number that represents the difference between the parsed dates of the two objects. - */ -export function dateSort(a: T, b: T): number { - return Date.parse(b.date) - Date.parse(a.date); -} - -/** - * Renders an mdsvex component and returns the resulting HTML string. - * @param component - The mdsvex component to render. - * @returns The HTML string that was generated by rendering the component. - * @throws An error if the `render` property of the component is not a function. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function renderMdsvexComponent(component: any): string { - if (typeof component['render'] != 'function') { - throw new Error("Unable to render something that isn't a mdsvex component"); - } - - return (component as ReturnType).render().html; -} - -/** - * Converts an md file path to a slug by removing the last segment of the path and the `.md` extension. - * @param path - The path of the md file. - * @returns The slug of the md file. - */ -export function mdPathToSlug(path: string) { - return path.split('/').at(-1).slice(0, -3); -} - -/** - * Parses an object of data that has string keys and values of type `T` that have an optional `date` property. - * @param data - The object of data to parse. - * @param dateSort - A function that sorts an array of objects by their `date` property in descending order. - * @param mdPathToSlug - A function that converts an md file path to a slug. - * @returns An array of objects that have a `slug` property and the properties of the original data objects. - */ -export function parseReadContent(data: Record): T[] { - return Object.entries(data) - .map(([file, data]) => ({ - slug: mdPathToSlug(file), - ...data - })) - .sort(dateSort); -} - -// Old utils -// /** -// * Formats a date string using the specified date style and locale. -// * @param date - The date string to format. -// * @param dateStyle - The style to use when formatting the date. Defaults to 'medium'. -// * @param locales - The locale to use when formatting the date. Defaults to 'en'. -// * @returns The formatted date string. -// */ -// export function formatDate(date: string, dateStyle: DateStyle = 'medium', locales = 'en') { -// const formatter = new Intl.DateTimeFormat(locales, { dateStyle }); -// return formatter.format(new Date(date)); -// } -// type DateStyle = Intl.DateTimeFormatOptions['dateStyle']; -/** - * Formats a date string into a human-readable format. - * @param date - The date string to format. - * @returns A string representing the formatted date, or an empty string if the input is invalid. - */ -export const formatDate = (date) => { - try { - const d = new Date(date); - return `${d.toLocaleString('default', { - month: 'long' - })} ${d.getDate()}, ${d.getFullYear()}`; - } catch (e) { - return ''; - } -}; - -export const scrollIntoView = (selector: string) => { - const scrollToElement = document.querySelector(selector); - - if (!scrollToElement) return; - - const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); - - scrollToElement.scrollIntoView({ - block: 'nearest', - inline: 'start', - behavior: mediaQuery.matches ? 'auto' : 'smooth' - }); -}; diff --git a/src/cspDirectives.ts b/src/cspDirectives.ts deleted file mode 100644 index f153ab0..0000000 --- a/src/cspDirectives.ts +++ /dev/null @@ -1,83 +0,0 @@ -// https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73, Rodney Lab - -import { - PUBLIC_DOMAIN, - PUBLIC_SENTRY_KEY, - PUBLIC_SENTRY_PROJECT_ID, - PUBLIC_SENTRY_ORG_ID, - PUBLIC_WORKER_URL -} from '$env/static/public'; - -export const rootDomain = PUBLIC_DOMAIN; // or your server IP for dev - -const directives = { - 'base-uri': ["'self'"], - 'child-src': ["'self'", 'blob:'], - // 'connect-src': ["'self'", 'ws://localhost:*'], - 'connect-src': [ - "'self'", - 'ws://localhost:*', - 'https://*.sentry.io', - 'https://hcaptcha.com', - 'https://*.hcaptcha.com', - 'https://*.cartocdn.com', - PUBLIC_DOMAIN, - PUBLIC_WORKER_URL - ], - 'img-src': ["'self'", 'data:', 'https://images.unsplash.com'], - 'font-src': ["'self'", 'data:'], - 'form-action': ["'self'"], - 'frame-ancestors': ["'self'"], - 'frame-src': [ - "'self'", - // "https://*.stripe.com", - // "https://*.facebook.com", - // "https://*.facebook.net", - 'https://hcaptcha.com', - 'https://*.hcaptcha.com', - 'https://www.openstreetmap.org', - 'https://*.cartocdn.com' - ], - 'manifest-src': ["'self'"], - 'media-src': ["'self'", 'data:'], - 'object-src': ["'none'"], - // 'style-src': ["'self'", "'unsafe-inline'"], - 'style-src': ["'self'", "'unsafe-inline'", 'https://hcaptcha.com', 'https://*.hcaptcha.com'], - 'default-src': [ - "'self'", - rootDomain, - `ws://${rootDomain}`, - // 'https://*.google.com', - // 'https://*.googleapis.com', - // 'https://*.firebase.com', - // 'https://*.gstatic.com', - // 'https://*.cloudfunctions.net', - // 'https://*.algolia.net', - // 'https://*.facebook.com', - // 'https://*.facebook.net', - // 'https://*.stripe.com', - 'https://*.sentry.io' - ], - 'script-src': [ - "'self'", - "'unsafe-inline'", - // 'https://*.stripe.com', - // 'https://*.facebook.com', - // 'https://*.facebook.net', - 'https://hcaptcha.com', - 'https://*.hcaptcha.com', - 'https://*.sentry.io', - // 'https://polyfill.io', - 'https://*.cartocdn.com' - ], - 'worker-src': ["'self'", 'blob:'], - //report-to can throw "Content-Security-Policy: Couldn’t process unknown directive ‘report-to’", leave it for older browsers. - 'report-to': ["'csp-endpoint'"], - 'report-uri': [ - `https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}` - ] -}; - -export const csp = Object.entries(directives) - .map(([key, arr]) => key + ' ' + arr.join(' ')) - .join('; '); diff --git a/src/hooks.client.ts b/src/hooks.client.ts deleted file mode 100644 index 4f55acc..0000000 --- a/src/hooks.client.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit'; -import * as Sentry from '@sentry/sveltekit'; -import { - PUBLIC_SENTRY_KEY, - PUBLIC_SENTRY_PROJECT_ID, - PUBLIC_SENTRY_ORG_ID -} from '$env/static/public'; - -Sentry.init({ - dsn: `https://${PUBLIC_SENTRY_KEY}@${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/${PUBLIC_SENTRY_PROJECT_ID}`, - tracesSampleRate: 1.0, - - // This sets the sample rate to be 10%. You may want this to be 100% while - // in development and sample at a lower rate in production - replaysSessionSampleRate: 0.1, - - // If the entire session is not sampled, use the below sample rate to sample - // sessions when an error occurs. - replaysOnErrorSampleRate: 1.0, - - // If you don't want to use Session Replay, just remove the line below: - integrations: [replayIntegration()] -}); - -// If you have a custom error handler, pass it to `handleErrorWithSentry` -export const handleError = handleErrorWithSentry(); diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 40663cf..00cbcc0 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,52 +1,7 @@ -import type { Handle } from '@sveltejs/kit'; -import { sequence } from '@sveltejs/kit/hooks'; +import type { Handle } from '@sveltejs/kit' +import { site } from '$lib/config/site' -import { handleErrorWithSentry, sentryHandle } from '@sentry/sveltekit'; -import * as Sentry from '@sentry/sveltekit'; -import { - PUBLIC_SENTRY_KEY, - PUBLIC_SENTRY_PROJECT_ID, - PUBLIC_SENTRY_ORG_ID -} from '$env/static/public'; - -import { csp, rootDomain } from './cspDirectives'; - -Sentry.init({ - dsn: `https://${PUBLIC_SENTRY_KEY}@${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/${PUBLIC_SENTRY_PROJECT_ID}`, - tracesSampleRate: 1.0 -}); - -export const cspHandle: Handle = async ({ event, resolve }) => { - if (!csp) { - throw new Error('csp is undefined'); - } - const response = await resolve(event); - - // Permission fullscreen necessary for maps fullscreen - const headers = { - 'X-Frame-Options': 'SAMEORIGIN', - 'Referrer-Policy': 'no-referrer', - 'Permissions-Policy': `accelerometer=(), autoplay=(), camera=(), document-domain=(self, 'js-profiling'), encrypted-media=(), fullscreen=(self ${rootDomain}), gyroscope=(), interest-cohort=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=(), geolocation=()`, - 'X-Content-Type-Options': 'nosniff', - // 'Content-Security-Policy-Report-Only': csp, - 'Content-Security-Policy': csp, - 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', - 'Expect-CT': `max-age=86400, report-uri="https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"`, - 'Report-To': `{group: "csp-endpoint", "max_age": 10886400, "endpoints": [{"url": "https://${PUBLIC_SENTRY_ORG_ID}.ingest.us.sentry.io/api/${PUBLIC_SENTRY_PROJECT_ID}/security/?sentry_key=${PUBLIC_SENTRY_KEY}"}]}` - }; - - Object.entries(headers).forEach(([key, value]) => { - response.headers.set(key, value); - }); - return response; -}; - -// If you have custom handlers, make sure to place them after `sentryHandle()` in the `sequence` function. -export const handle: Handle = sequence(sentryHandle(), cspHandle); - -// If you have a custom error handler, pass it to `handleErrorWithSentry` -export const handleError = handleErrorWithSentry(); -// https://gist.github.com/acoyfellow/d8e86979c66ebea25e1643594e38be73 -// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -// https://scotthelme.co.uk/content-security-policy-an-introduction/ -// scanner: https://securityheaders.com/ +export const handle: Handle = async ({ event, resolve }) => + await resolve(event, { + transformPageChunk: ({ html }) => html.replace('', ``) + }) diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index e07cbbd..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/src/lib/assets/home/home-open-graph-square.jpg b/src/lib/assets/home/home-open-graph-square.jpg deleted file mode 100644 index 84e305d..0000000 Binary files a/src/lib/assets/home/home-open-graph-square.jpg and /dev/null differ diff --git a/src/lib/assets/home/home-open-graph.jpg b/src/lib/assets/home/home-open-graph.jpg deleted file mode 100644 index a95ebe5..0000000 Binary files a/src/lib/assets/home/home-open-graph.jpg and /dev/null differ diff --git a/src/lib/assets/home/home-twitter.jpg b/src/lib/assets/home/home-twitter.jpg deleted file mode 100644 index ce5d264..0000000 Binary files a/src/lib/assets/home/home-twitter.jpg and /dev/null differ diff --git a/src/lib/assets/home/home.jpg b/src/lib/assets/home/home.jpg deleted file mode 100644 index dfc77bc..0000000 Binary files a/src/lib/assets/home/home.jpg and /dev/null differ diff --git a/src/lib/assets/prism-atom-dark.css b/src/lib/assets/prism-atom-dark.css deleted file mode 100644 index 749b17c..0000000 --- a/src/lib/assets/prism-atom-dark.css +++ /dev/null @@ -1,143 +0,0 @@ -/** - * atom-dark theme for `prism.js` - * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax - * @author Joe Gibson (@gibsjose) - */ - -code[class*="language-"], -pre[class*="language-"] { - color: #c5c8c6; - text-shadow: 0 1px rgba(0, 0, 0, 0.3); - font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; - border-radius: 0.3em; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #1d1f21; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #7C7C7C; -} - -.token.punctuation { - color: #c5c8c6; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.keyword, -.token.tag { - color: #96CBFE; -} - -.token.class-name { - color: #FFFFB6; - text-decoration: underline; -} - -.token.boolean, -.token.constant { - color: #99CC99; -} - -.token.symbol, -.token.deleted { - color: #f92672; -} - -.token.number { - color: #FF73FD; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #A8FF60; -} - -.token.variable { - color: #C6C5FE; -} - -.token.operator { - color: #EDEDED; -} - -.token.entity { - color: #FFFFB6; - cursor: help; -} - -.token.url { - color: #96CBFE; -} - -.language-css .token.string, -.style .token.string { - color: #87C38A; -} - -.token.atrule, -.token.attr-value { - color: #F9EE98; -} - -.token.function { - color: #DAD085; -} - -.token.regex { - color: #E9C062; -} - -.token.important { - color: #fd971f; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} diff --git a/src/lib/assets/prism-nord.css b/src/lib/assets/prism-nord.css deleted file mode 100644 index 23b8ac9..0000000 --- a/src/lib/assets/prism-nord.css +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Nord Theme Originally by Arctic Ice Studio - * https://nordtheme.com - * - * Ported for PrismJS by Zane Hitchcoxc (@zwhitchcox) and Gabriel Ramos (@gabrieluizramos) - */ - -code[class*="language-"], -pre[class*="language-"] { - color: #f8f8f2; - background: none; - font-family: "Fira Code", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; - border-radius: 0.3em; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #2E3440; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; - white-space: normal; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #636f88; -} - -.token.punctuation { - color: #81A1C1; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.constant, -.token.symbol, -.token.deleted { - color: #81A1C1; -} - -.token.number { - color: #B48EAD; -} - -.token.boolean { - color: #81A1C1; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #A3BE8C; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: #81A1C1; -} - -.token.atrule, -.token.attr-value, -.token.function, -.token.class-name { - color: #88C0D0; -} - -.token.keyword { - color: #81A1C1; -} - -.token.regex, -.token.important { - color: #EBCB8B; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} diff --git a/src/lib/components/SkillContainer.svelte b/src/lib/components/SkillContainer.svelte deleted file mode 100644 index e15f75a..0000000 --- a/src/lib/components/SkillContainer.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - -
-

My skillset

- {#each skills as skill} -
{skill.title}
-
- {#each skill.list as s} -
{s}
- {/each} -
- {/each} -
diff --git a/src/lib/components/SocialsCloud.svelte b/src/lib/components/SocialsCloud.svelte deleted file mode 100644 index 049264f..0000000 --- a/src/lib/components/SocialsCloud.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/lib/components/Video.svelte b/src/lib/components/Video.svelte deleted file mode 100644 index a920702..0000000 --- a/src/lib/components/Video.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - - diff --git a/src/lib/components/blog/BlogContentLayout.svelte b/src/lib/components/blog/BlogContentLayout.svelte deleted file mode 100644 index 02380c2..0000000 --- a/src/lib/components/blog/BlogContentLayout.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/components/blog/CategoryFilter.svelte b/src/lib/components/blog/CategoryFilter.svelte deleted file mode 100644 index 4646cca..0000000 --- a/src/lib/components/blog/CategoryFilter.svelte +++ /dev/null @@ -1,40 +0,0 @@ - - -
-

Sort by category

-
    - {#each options as option} -
  • - -
  • - {/each} -
-
diff --git a/src/lib/components/blog/PostLayout.svelte b/src/lib/components/blog/PostLayout.svelte deleted file mode 100644 index 4391a0c..0000000 --- a/src/lib/components/blog/PostLayout.svelte +++ /dev/null @@ -1,63 +0,0 @@ - - - - {title} - - - - - - - - - {#each tags as tag (tag)} - - {/each} - - -
-
-
- {`${title}`} -
-
- {#if tags && tags.length > 0} -
- tags: {#each tags as tag} - - {tag} - - {/each} -
- {/if} - On {formatDate(date)} -
-
-

{title}

-
- -
-
-
-
-
-
diff --git a/src/lib/components/blog/PostPreview.svelte b/src/lib/components/blog/PostPreview.svelte deleted file mode 100644 index 6d162c8..0000000 --- a/src/lib/components/blog/PostPreview.svelte +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - diff --git a/src/lib/components/comments/giscus.svelte b/src/lib/components/comments/giscus.svelte new file mode 100644 index 0000000..d984388 --- /dev/null +++ b/src/lib/components/comments/giscus.svelte @@ -0,0 +1,40 @@ + + +
+ +
+ {#key mentions} + {#each mentions as mention} + {@const [wmProperty, borderColor, textColor, tooltipColor] = { + 'in-reply-to': ['💬 replied', 'border-primary/50', 'text-primary', 'tooltip-primary'], + 'like-of': ['❤️ liked', 'border-secondary/50', 'text-secondary', 'tooltip-secondary'], + 'repost-of': ['🔄 reposted', 'border-accent/50', 'text-accent', 'tooltip-accent'], + 'bookmark-of': ['⭐️ bookmarked', 'border-neutral/50', 'text-neutral', 'tooltip-neutral'], + 'mention-of': ['💬 mentioned', 'border-base-300/50', 'text-base-content', 'tooltip-base-content'], + rsvp: [ + `📅 RSVPed ${ + mention.rsvp && + { + yes: '✅', + no: '❌', + interested: '💡', + maybe: '💭' + }[mention.rsvp] + }`, + 'border-warning/50', + 'text-warning', + 'tooltip-warning' + ] + }[mention['wm-property']]} + {#if mention.url !== null} +
+
+ {#if mention?.author?.photo} + {mention.author?.name + {/if} +
+

+ {#if mention?.author?.url} + + {mention.author?.name ?? new URL(mention.url).host} + + {:else} + {mention?.author?.name ?? new URL(mention.url).host} + {/if} + + {wmProperty} + + this post on + + {mention.published ? mention.published.slice(0, 10) : mention['wm-received'].slice(0, 10)} + +

+
+
+ {#if mention.content} +
+

{@html mention.content?.html ?? mention.content?.text}

+
+ {/if} +
+ {/if} + {/each} + {/key} + {#if loaded === true} + {#if end !== true} + + {:else if config?.form !== true} +
END
+ {/if} + {:else} + + + + {/if} + diff --git a/src/lib/components/extra/alert.svelte b/src/lib/components/extra/alert.svelte new file mode 100644 index 0000000..abd5e43 --- /dev/null +++ b/src/lib/components/extra/alert.svelte @@ -0,0 +1,35 @@ + + +
+
+ {#if status === 'success'} + + {:else if status === 'warning'} + + {:else if status === 'error'} + + {:else} + + {/if} +
+
{title}
+ {#if description} +
{description}
+ {/if} +
+
+ {#if $$slots.default} +
+ +
+ {/if} +
diff --git a/src/lib/components/footer.svelte b/src/lib/components/footer.svelte new file mode 100644 index 0000000..b0bee85 --- /dev/null +++ b/src/lib/components/footer.svelte @@ -0,0 +1,46 @@ + + +
+
+

+ {#if footerConfig.nav} + {#each footerConfig.nav as { text, link }, i} + {text} + {#if i + 1 < footerConfig.nav.length} + · + {/if} + {/each} +
+ {/if} + Copyright © {footerConfig.since && footerConfig.since !== new Date().toJSON().substring(0, 4) + ? `${footerConfig.since} - ${new Date().toJSON().substring(0, 4)}` + : new Date().toJSON().substring(0, 4)} + {site.author.name} +
+ Powered by + + Urara + + {#if footerConfig.html} +
+ {@html footerConfig.html} + {/if} +

+
+
diff --git a/src/lib/components/head.svelte b/src/lib/components/head.svelte new file mode 100644 index 0000000..5595bbc --- /dev/null +++ b/src/lib/components/head.svelte @@ -0,0 +1,39 @@ + + + + + {#if post} + + {#if post.type === 'article'} + {post.title} | {site.title} + {:else if post.type === 'note'} + {post.summary ?? post.path.slice(1)} | {site.title} + {/if} + {#if post.tags}{/if} + {#if post.summary}{/if} + {:else} + + + {#if page} + {page.title ?? page.path.slice(1)} | {site.title} + + {:else} + {site.subtitle ? `${site.title} - ${site.subtitle}` : site.title} + + {/if} + {/if} + {#if head.custom} + {#each head.custom({ dev, post, page }) as tag} + {@html tag} + {/each} + {/if} + + + diff --git a/src/lib/components/head_icon.svelte b/src/lib/components/head_icon.svelte new file mode 100644 index 0000000..37185fc --- /dev/null +++ b/src/lib/components/head_icon.svelte @@ -0,0 +1,15 @@ + + + + {#if favicon} + + {/if} + {#if any['180']} + + {/if} + {#if any['192']} + + {/if} + diff --git a/src/lib/components/head_opengraph.svelte b/src/lib/components/head_opengraph.svelte new file mode 100644 index 0000000..393848a --- /dev/null +++ b/src/lib/components/head_opengraph.svelte @@ -0,0 +1,45 @@ + + + + + + {#if post} + + + {#if post.summary} + + {/if} + {#if post.image} + + + {:else} + + + {/if} + {#if post.tags} + {#each post.tags as tag} + + {/each} + {/if} + + + + + {:else} + + + + {#if page} + + + {:else} + + + {/if} + {/if} + diff --git a/src/lib/components/head_static.svelte b/src/lib/components/head_static.svelte new file mode 100644 index 0000000..b5cd2be --- /dev/null +++ b/src/lib/components/head_static.svelte @@ -0,0 +1,19 @@ + + + + {#if head.me} + {#each head.me as href} + + {/each} + {/if} + {#if post.comment?.webmention?.username} + + + {/if} + + + diff --git a/src/lib/components/header.svelte b/src/lib/components/header.svelte new file mode 100644 index 0000000..5406be9 --- /dev/null +++ b/src/lib/components/header.svelte @@ -0,0 +1,138 @@ + + + + + + + + + + + diff --git a/src/lib/components/header_nav.svelte b/src/lib/components/header_nav.svelte new file mode 100644 index 0000000..820142d --- /dev/null +++ b/src/lib/components/header_nav.svelte @@ -0,0 +1,75 @@ + + + + + +
32 && title} class="swap order-last hidden lg:inline-grid"> + +
    64 && title} class="swap-off menu menu-horizontal p-0"> + {#each nav as { text, link, children }} + {#if link && !children} +
  • + {text} +
  • + {:else if children} +
  • + link === path)} class="!rounded-btn gap-1"> + {text} + + + + +
  • + {/if} + {/each} +
+
diff --git a/src/lib/components/header_search.svelte b/src/lib/components/header_search.svelte new file mode 100644 index 0000000..624d833 --- /dev/null +++ b/src/lib/components/header_search.svelte @@ -0,0 +1,21 @@ + + +
+ + + +
diff --git a/src/lib/components/home/HeroSection.svelte b/src/lib/components/home/HeroSection.svelte deleted file mode 100644 index 742cc24..0000000 --- a/src/lib/components/home/HeroSection.svelte +++ /dev/null @@ -1,105 +0,0 @@ - -
- -
-

Hello, I'm Matt.

- -

- A dev with an array of skills from from frontend and devops to design. I have a - strong passion for innovation and change in tech, automation and solving 👾 problems - . -
From wearing a lot of 🤠 hats in past projects and startups I became a - generalist, now I am actively deepening my knowledge in software: - DevOps, CyberSec and AI. -

- - -
- - - -
- - diff --git a/src/lib/components/home/QuickCards.svelte b/src/lib/components/home/QuickCards.svelte deleted file mode 100644 index b8ee942..0000000 --- a/src/lib/components/home/QuickCards.svelte +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -

Development

-

- I possess a wide range of skills that enable me to develop visually appealing and - interactive user interfaces for web applications. -

-
-
- -

Design

-

- Over the years, I have honed my ability to create visually appealing interfaces that - are both user-friendly and intuitive. -

-
-
- -

User Experience

-

- I understand the importance of creating a seamless UX for end-users. Which includes - a solid understanding user behavior. -

-
-
diff --git a/src/lib/components/home/QuickLinks.svelte b/src/lib/components/home/QuickLinks.svelte deleted file mode 100644 index c2759ed..0000000 --- a/src/lib/components/home/QuickLinks.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - diff --git a/src/lib/components/index_profile.svelte b/src/lib/components/index_profile.svelte new file mode 100644 index 0000000..4cb86b6 --- /dev/null +++ b/src/lib/components/index_profile.svelte @@ -0,0 +1,56 @@ + + +
+ +
+ {#if site.author.avatar} + {site.author.name} + {/if} + {#if site.author.status} +
+ {site.author.status} +
+ {/if} +
+
+

{site.author.name}

+

{@html site.author.bio}

+ {#if site.author.metadata} +
+ {#each site.author.metadata as { text, icon, link, rel }} + {#if link} + + {#if icon} + {icon} + {/if} + {#if text} + {text} + {/if} + + {:else} + + {/if} + {/each} +
+ {/if} +
+
diff --git a/src/lib/components/logos/GiteaLogo.svelte b/src/lib/components/logos/GiteaLogo.svelte deleted file mode 100644 index 7a21aae..0000000 --- a/src/lib/components/logos/GiteaLogo.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/lib/components/logos/MatrixLogo.svelte b/src/lib/components/logos/MatrixLogo.svelte deleted file mode 100644 index c74e5b7..0000000 --- a/src/lib/components/logos/MatrixLogo.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - diff --git a/src/lib/components/main/MainDrawer.svelte b/src/lib/components/main/MainDrawer.svelte deleted file mode 100644 index 1354640..0000000 --- a/src/lib/components/main/MainDrawer.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - - - {#if $drawerStore.id === 'demo'} - - {:else if $drawerStore.id === 'mobile-nav'} - - - {:else} - -
-
-

Invalid $drawerStore.id provided.

-
-
- {/if} -
diff --git a/src/lib/components/main/MainFooter.svelte b/src/lib/components/main/MainFooter.svelte deleted file mode 100644 index 5528aa8..0000000 --- a/src/lib/components/main/MainFooter.svelte +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/lib/components/main/MainHeader.svelte b/src/lib/components/main/MainHeader.svelte deleted file mode 100644 index 05e8669..0000000 --- a/src/lib/components/main/MainHeader.svelte +++ /dev/null @@ -1,63 +0,0 @@ - - -
- - - - - - -
- -
- -
diff --git a/src/lib/components/post_action.svelte b/src/lib/components/post_action.svelte new file mode 100644 index 0000000..4c98b17 --- /dev/null +++ b/src/lib/components/post_action.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/post_card.svelte b/src/lib/components/post_card.svelte new file mode 100644 index 0000000..472dd70 --- /dev/null +++ b/src/lib/components/post_card.svelte @@ -0,0 +1,130 @@ + + + + {#if !preview && postConfig.bridgy} + + {/if} + {#if post.in_reply_to} + + {/if} + {#if post.image && preview} +
+ {post.alt +
+ {/if} +
+
+ {#if post.image && !preview} +
+ {post.alt +
+ {/if} + + {#if post.title} + {#if preview} +

+ +

+ {:else} +

{post.title ?? post.path.slice(1)}

+ {/if} + {/if} + {#if post.summary} +

+ {post.summary} +

+ {/if} +
+
+ {#if !preview} + + {:else if post.html} + {@html post.html} + {/if} +
+ {#if !preview && post.tags} +
+
+ {#each post.tags as tag} + + #{tag} + + {/each} +
+ {/if} +
+ {#if !preview} + {#if (prev || next) && !post.flags?.includes('pagination-disabled') && !post.flags?.includes('unlisted')} + + {/if} + {#if browser && postConfig.comment && !post.flags?.includes('comment-disabled')} + + {/if} + {/if} + diff --git a/src/lib/components/post_comment.svelte b/src/lib/components/post_comment.svelte new file mode 100644 index 0000000..19666b7 --- /dev/null +++ b/src/lib/components/post_comment.svelte @@ -0,0 +1,46 @@ + + +{#if config?.use.length > 0} +
+ {#if config.use.length > 1} +
+ {#each config.use as name} + + + { + currentComment = toSnake(name) + localStorage.setItem('comment', toSnake(name)) + }} + class="flex-1 tab transition-all" + class:tab-active={currentComment === toSnake(name)}> + {name} + + {/each} +
+ {/if} + {#if currentComment} + {#key currentComment} + + {/key} + {/if} +
+{/if} diff --git a/src/lib/components/post_container.svelte b/src/lib/components/post_container.svelte new file mode 100644 index 0000000..96488d8 --- /dev/null +++ b/src/lib/components/post_container.svelte @@ -0,0 +1,39 @@ + + + + +
+
+ {#if browser} + + {/if} +
+
+ {#if browser && post.toc} + + {/if} +
+
+ + + +
+
+
diff --git a/src/lib/components/post_layout.svelte b/src/lib/components/post_layout.svelte new file mode 100644 index 0000000..52f324f --- /dev/null +++ b/src/lib/components/post_layout.svelte @@ -0,0 +1,32 @@ + + + + + + + diff --git a/src/lib/components/post_pagination.svelte b/src/lib/components/post_pagination.svelte new file mode 100644 index 0000000..a56ccb2 --- /dev/null +++ b/src/lib/components/post_pagination.svelte @@ -0,0 +1,59 @@ + + + diff --git a/src/lib/components/post_reply.svelte b/src/lib/components/post_reply.svelte new file mode 100644 index 0000000..4d3c0be --- /dev/null +++ b/src/lib/components/post_reply.svelte @@ -0,0 +1,17 @@ + + +
+ Reply to:  + + + {in_reply_to} + +
diff --git a/src/lib/components/post_status.svelte b/src/lib/components/post_status.svelte new file mode 100644 index 0000000..6665b95 --- /dev/null +++ b/src/lib/components/post_status.svelte @@ -0,0 +1,35 @@ + + + diff --git a/src/lib/components/post_toc.svelte b/src/lib/components/post_toc.svelte new file mode 100644 index 0000000..82a0c1b --- /dev/null +++ b/src/lib/components/post_toc.svelte @@ -0,0 +1,80 @@ + + + + + diff --git a/src/lib/components/projects/ProjectsContentLayout.svelte b/src/lib/components/projects/ProjectsContentLayout.svelte deleted file mode 100644 index f3a23cd..0000000 --- a/src/lib/components/projects/ProjectsContentLayout.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/src/lib/components/prose/img.svelte b/src/lib/components/prose/img.svelte new file mode 100644 index 0000000..e00e9b8 --- /dev/null +++ b/src/lib/components/prose/img.svelte @@ -0,0 +1,37 @@ + + +{#if source} + + `${src} ${w}w`).join(', ')} type="image/avif" /> + + +{:else} + +{/if} diff --git a/src/lib/components/prose/table.svelte b/src/lib/components/prose/table.svelte new file mode 100644 index 0000000..7039c4c --- /dev/null +++ b/src/lib/components/prose/table.svelte @@ -0,0 +1,5 @@ +
+ + +
+
diff --git a/src/lib/components/transition.svelte b/src/lib/components/transition.svelte new file mode 100644 index 0000000..e5f5795 --- /dev/null +++ b/src/lib/components/transition.svelte @@ -0,0 +1,13 @@ + + +{#key path} +
+ +
+{/key} diff --git a/src/lib/config.ts b/src/lib/config.ts deleted file mode 100644 index 5beaec4..0000000 --- a/src/lib/config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { dev } from '$app/environment'; - -export const title = "Matt's Portfolio"; -export const description = - 'I code, I think, I write. My thoughts go into the world of Free & Open Source Software, AI and philosophy of mind, Climate Change, Cybersecurity.'; -export const url = dev ? 'http://localhost:5174' : 'https://mattmor.in'; -export const author = 'Matt Morin'; -export const backgroundColor = '#111827'; -export const themeColor = '#3b82f6'; -export const logo = '/Logo.png'; -export const keywords = 'Dev, FOSS, Nix, Philosopher, DevOps, Climate'; -export const ogLanguage = 'en_US'; -export const siteLanguage = 'en-US'; - -// prettier-ignore -export const socialLinks = [ - { title: 'LinkedIn', href: 'https://linkedin.com/in/mattmor-in', icon: 'fa-brands fa-linkedin'}, - { title: 'Matrix', href: '', icon: './MatrixLogo' }, - { title: 'Gitea', href: 'https://git.mattmor.in', icon: './GiteaLogo' }, - { title: 'Mastodon', href: 'https://mastodon.social/@matt_mor', icon: 'fa-brands fa-mastodon'}, - { title: 'RSS feed', href: '/blog/feed', icon: 'fa-regular fa-square-rss' }, - { title: 'email', href: 'matt.b.morin@protonmail.com', icon: 'fa-regular mail'} -]; - -// Routes -export const NavRoutes = [ - { title: 'Home', href: '/' }, - { title: 'Blog', href: '/blog' }, - { title: 'Projects', href: '/projects' } -]; diff --git a/src/lib/config/general.ts b/src/lib/config/general.ts new file mode 100644 index 0000000..13b9e3f --- /dev/null +++ b/src/lib/config/general.ts @@ -0,0 +1,92 @@ +import type { ThemeConfig, HeadConfig, HeaderConfig, FooterConfig, DateConfig, FeedConfig } from '$lib/types/general' + +export const theme: ThemeConfig = [ + { + name: 'cmyk', + text: '🖨 Light' + }, + { + name: 'dracula', + text: '🧛 Dark' + }, + { + name: 'valentine', + text: '🌸 Valentine' + }, + { + name: 'aqua', + text: '💦 Aqua' + }, + { + name: 'synthwave', + text: '🌃 Synthwave' + }, + { + name: 'night', + text: '🌃 Night' + }, + { + name: 'lofi', + text: '🎶 Lo-Fi' + }, + { + name: 'lemonade', + text: '🍋 Lemonade' + }, + { + name: 'cupcake', + text: '🧁 Cupcake' + }, + { + name: 'garden', + text: '🏡 Garden' + }, + { + name: 'retro', + text: '🌇 Retro' + }, + { + name: 'black', + text: '🖤 Black' + } +] + +export const head: HeadConfig = {} + +export const header: HeaderConfig = { + nav: [ + { + text: 'Get Started', + link: '/hello-world' + }, + { + text: 'Elements', + link: '/hello-world/elements' + } + ] +} + +export const footer: FooterConfig = { + nav: [ + { + text: 'Feed', + link: '/atom.xml' + }, + { + text: 'Sitemap', + link: '/sitemap.xml' + } + ] +} + +export const date: DateConfig = { + locales: 'en-US', + options: { + year: '2-digit', + weekday: 'long', + month: 'short', + day: 'numeric' + } +} + +export const feed: FeedConfig = {} diff --git a/src/lib/config/icon.ts b/src/lib/config/icon.ts new file mode 100644 index 0000000..54909d6 --- /dev/null +++ b/src/lib/config/icon.ts @@ -0,0 +1,39 @@ +import type { Icon } from '$lib/types/icon' +import { site } from '$lib/config/site' + +export const favicon: Icon = { + src: site.protocol + site.domain + '/favicon.png', + sizes: '48x48', + type: 'image/png' +} + +export const any: { [key: number]: Icon } = { + 180: { + src: site.protocol + site.domain + '/assets/any@180.png', + sizes: '180x180', + type: 'image/png' + }, + 192: { + src: site.protocol + site.domain + '/assets/any@192.png', + sizes: '192x192', + type: 'image/png' + }, + 512: { + src: site.protocol + site.domain + '/assets/any@512.png', + sizes: '512x512', + type: 'image/png' + } +} + +export const maskable: { [key: number]: Icon } = { + 192: { + src: site.protocol + site.domain + '/assets/maskable@192.png', + sizes: '192x192', + type: 'image/png' + }, + 512: { + src: site.protocol + site.domain + '/assets/maskable@512.png', + sizes: '512x512', + type: 'image/png' + } +} diff --git a/src/lib/config/post.ts b/src/lib/config/post.ts new file mode 100644 index 0000000..acfa406 --- /dev/null +++ b/src/lib/config/post.ts @@ -0,0 +1,3 @@ +import type { PostConfig } from '$lib/types/post' + +export const post: PostConfig = {} diff --git a/src/lib/config/site.ts b/src/lib/config/site.ts new file mode 100644 index 0000000..db0492c --- /dev/null +++ b/src/lib/config/site.ts @@ -0,0 +1,17 @@ +import type { SiteConfig } from '$lib/types/site' + +export const site: SiteConfig = { + protocol: import.meta.env.URARA_SITE_PROTOCOL ?? import.meta.env.DEV ? 'http://' : 'https://', + domain: import.meta.env.URARA_SITE_DOMAIN ?? 'urara-demo.netlify.app', + title: 'Urara', + subtitle: 'Sweet & Powerful SvelteKit Blog Template', + lang: 'en-US', + description: 'Powered by SvelteKit/Urara', + author: { + avatar: '/assets/maskable@512.png', + name: 'John Doe', + status: '🌸', + bio: 'lorem ipsum dolor sit amet, consectetur adipiscing elit.' + }, + themeColor: '#3D4451' +} diff --git a/src/lib/stores/posts.ts b/src/lib/stores/posts.ts new file mode 100644 index 0000000..a4f659f --- /dev/null +++ b/src/lib/stores/posts.ts @@ -0,0 +1,4 @@ +import type { Writable } from 'svelte/store' +import { writable } from 'svelte/store' +export const posts: Writable = writable([]) +export const tags: Writable = writable([]) diff --git a/src/lib/stores/title.ts b/src/lib/stores/title.ts new file mode 100644 index 0000000..7f4df1b --- /dev/null +++ b/src/lib/stores/title.ts @@ -0,0 +1,2 @@ +import { writable } from 'svelte/store' +export const title = writable({}) diff --git a/src/lib/types/contributions.d.ts b/src/lib/types/contributions.d.ts deleted file mode 100644 index 0ae522b..0000000 --- a/src/lib/types/contributions.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -type Contribution = { - date: string; - level: number; -}; - -export type Contributions = Array; diff --git a/src/lib/types/general.ts b/src/lib/types/general.ts new file mode 100644 index 0000000..d57c03c --- /dev/null +++ b/src/lib/types/general.ts @@ -0,0 +1,42 @@ +export type ThemeConfig = { + text?: string + name: string +}[] + +export type HeadConfig = { + custom?: (params: { dev: boolean; post?: Urara.Post; page?: Urara.Page }) => string[] + me?: string[] +} + +export type HeaderConfig = { + nav?: { + text: string + link?: string + children?: { + text: string + link: string + }[] + }[] + search?: { + provider: 'google' | 'duckduckgo' + colors?: boolean + } +} + +export type FooterConfig = { + nav?: { + text: string + link: string + }[] + html?: string + since?: string +} + +export type DateConfig = { locales: string; options: Intl.DateTimeFormatOptions } + +export type FeedConfig = { + /** feed entry limit. */ + limit?: number + /** WebSub (formerly PubSubHubbub) hubs. one per line */ + hubs?: string[] +} diff --git a/src/lib/types/icon.ts b/src/lib/types/icon.ts new file mode 100644 index 0000000..ef39ca5 --- /dev/null +++ b/src/lib/types/icon.ts @@ -0,0 +1,5 @@ +export type Icon = { + src: string + sizes?: string + type?: string +} diff --git a/src/lib/types/post.d.ts b/src/lib/types/post.d.ts deleted file mode 100644 index 296f347..0000000 --- a/src/lib/types/post.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { MarkdownMetadata } from '$content/types'; - -export type Tag = 'DevOps' | 'Philosophy' | 'Updates' | ''; - -export interface Post extends MarkdownMetadata { - type?: 'Blog' | 'projects' | string; - date?: string; - excerpt: string; - image: string; - slug?: string; - href?: string; - tags?: Tag[]; - subtitle?: string; - teaserImage: string; - title: string; - isNotAnActualPost?: boolean; -} diff --git a/src/lib/types/post.ts b/src/lib/types/post.ts new file mode 100644 index 0000000..585ab6b --- /dev/null +++ b/src/lib/types/post.ts @@ -0,0 +1,107 @@ +export type PostConfig = { + bridgy?: { + [kind: string]: ('fed' | 'mastodon' | 'flickr' | 'github' | 'twitter')[] + } + comment?: CommentConfig +} + +export type CommentConfig = { + use: string[] + /** tab style for multiple comments, preview at https://daisyui.com/components/tab */ + style?: 'none' | 'bordered' | 'lifted' | 'boxed' + /** Webmention.io config, more at https://github.com/aaronpk/webmention.io#api */ + webmention?: WebmentionConfig + /** Giscus config, more at https://giscus.app */ + giscus?: GiscusConfig + /** Utterances config, more at https://utteranc.es */ + utterances?: UtterancesConfig + remark42?: Remark42Config +} + +export type WebmentionConfig = { + /** username you got when you signed up webmention.io. */ + username: string + /** number of results per page. */ + perPage?: number + /** sorting mechanism to return the list of mentions. */ + sortBy?: 'created' | 'updated' | 'published' | 'rsvp' + /** control the ordering. */ + sortDir?: 'down' | 'up' + /** find links of a specific type. */ + property?: ('in-reply-to' | 'like-of' | 'repost-of' | 'bookmark-of' | 'mention-of' | 'rsvp')[] + /** URL array of a webmention you'd like to block. */ + blockList?: string[] + /** show the form for sending the webmention. */ + form?: boolean + /** show `or comment anonymously` label text. */ + commentParade?: boolean +} + +export type GiscusConfig = { + /** self-hosted giscus url. */ + src?: string + /** a public GitHub repository. this repo is where the discussions will be linked to. */ + repo: string + /** a public GitHub repository. this repo is where the discussions will be linked to. */ + repoID: string + /** fill in here if only search for discussions in this category. */ + category?: string + /** choose the discussion category where new discussions will be created. */ + categoryID: string + /** the reactions for the discussion's main post will be shown before the comments. */ + reactionsEnabled?: boolean + /** discussion metadata will be sent periodically to the parent window (the embedding page). */ + emitMetadata?: boolean + /** the comment input box will be placed above the comments, so that users can leave a comment without scrolling to the bottom of the discussion. */ + inputPosition?: 'top' | 'bottom' + /** choose a theme that matches your website. */ + theme?: string + /** choose the language giscus will be displayed in. */ + lang?: string + /** loading of the comments will be deferred until the user scrolls near the comments container. */ + loading?: 'lazy' +} + +export type UtterancesConfig = { + /** self-hosted utterances url. */ + src?: string + /** choose the repository utterances will connect to. */ + repo: string + /** choose the label that will be assigned to issues created by utterances. */ + label?: string + /** choose an utterances theme that matches your blog. */ + theme?: string +} + +export type Remark42Config = { + /** hostname of Remark42 server, same as REMARK_URL in backend config, e.g. "https://demo.remark42.com" */ + host: string + /** the SITE that you passed to Remark42 instance on start of backend. (default: remark) */ + site_id?: string + /** url to the page with comments*/ + url?: string + /** an array of widgets that should be rendered on a page (default: ['embed'] )*/ + components?: ['embed' | 'last-comments' | 'counter'] + /** maximum number of comments that is rendered on mobile version (default: 15 )*/ + max_shown_comments?: number + /** maximum number of comments in the last comments widget (default: 15 )*/ + max_last_comments?: number + /** changes UI theme, (default: light )*/ + theme?: 'light' | 'dark' + /** title for current comments page (default: document.title)*/ + page_title?: string + /** + * interface localization, + * English (en), Belarusian (be), Brazilian Portuguese (bp), Bulgarian (bg), Chinese (zh), Finnish (fi), French (fr), German (de), Japanese (ja), Korean (ko), Polish (pl), Russian (ru), Spanish (es), Turkish (tr), Ukrainian (ua), Italian (it) and Vietnamese (vi) + * default: en + */ + locale?: 'en' | 'be' | 'bp' | 'bg' | 'zh' | 'fi' | 'fr' | 'de' | 'ja' | 'ko' | 'pl' | 'ru' | 'es' | 'tr' | 'ua' | 'it' | 'vi' + /** enables email subscription (default: true) */ + show_email_subscription?: boolean + /** enables RSS subscription, (default: true) */ + show_rss_subscription?: boolean + /** minimized UI with basic info only, (default: false) */ + simple_view?: boolean + /** hides footer with signature and links to Remark42,(default: false) */ + no_footer?: boolean +} diff --git a/src/lib/types/site.ts b/src/lib/types/site.ts new file mode 100644 index 0000000..0426e62 --- /dev/null +++ b/src/lib/types/site.ts @@ -0,0 +1,43 @@ +import type { FFFAuthor } from 'fff-flavored-frontmatter' + +export type SiteConfig = { + /** site protocol. for example: `https://` */ + protocol: string + /** site domain. for example: `example.com` */ + domain: string + /** site title. */ + title: string + /** site subtitle. */ + subtitle?: string + /** site lang. `` */ + lang?: string + /** site description. `` */ + description?: string + /** site keywords. `` */ + keywords?: string[] + author: Omit & { + status?: string + bio?: string + metadata?: ( + | { + text: string + icon?: string + link?: string + rel?: string + } + | { + text?: string + icon: string + link?: string + rel?: string + } + )[] + } + /** for web app manifest only. + * ``` + * "background_color": {site.themeColor}, + * "theme_color": {site.themeColor} + * ``` + */ + themeColor?: string +} diff --git a/src/lib/utils/case.ts b/src/lib/utils/case.ts new file mode 100644 index 0000000..53956c9 --- /dev/null +++ b/src/lib/utils/case.ts @@ -0,0 +1,8 @@ +export const toSnake = (str: string) => + str.charAt(0).toLowerCase() + + str + .slice(1) + .replace(/([A-Z]+)/g, '_$1') + .toLowerCase() + +export const toCamel = (str: string) => str.toLowerCase().replace(/([-_][a-z])/g, g => g.slice(-1).toUpperCase()) diff --git a/src/lib/utils/color.ts b/src/lib/utils/color.ts new file mode 100644 index 0000000..8ba3a0f --- /dev/null +++ b/src/lib/utils/color.ts @@ -0,0 +1,11 @@ +export const hslToHex = ( + h: number, + s: number, + l: number, + ll = (l /= 100), + a = (s * Math.min(ll, 1 - ll)) / 100, + f = (n: number, k = (n + h / 30) % 12) => + Math.round(255 * (ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1))) + .toString(16) + .padStart(2, '0') +) => `#${f(0)}${f(8)}${f(4)}` diff --git a/src/lib/utils/helpers.ts b/src/lib/utils/helpers.ts deleted file mode 100644 index afe8174..0000000 --- a/src/lib/utils/helpers.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { readable } from 'svelte/store'; - -/** - * Determines if the current timezone is between 0 and +3 hours UTC. - * @returns {boolean} True if the timezone is between 0 and +3 hours UTC, false otherwise. - */ -export const isEurope = () => { - const offset = new Date().getTimezoneOffset(); - return offset <= 0 && offset >= -180; // Returns true if the timezone is between 0 and +3 hours UTC, false otherwise -}; -/** - * Takes a string and returns a beautified version of it. - * @param str The input string to be beautified. - * @returns The beautified string. - */ -export const stringToBeautifiedFragment = (str = '') => - (str || '').toLocaleLowerCase().replace(/\s/g, '-').replace(/\?/g, '').replace(/,/g, ''); -/** - * Toggles the 'overflow-y-hidden' class on the 'html' element of the document. - * @param bool A boolean value indicating whether to show or hide the overflow-y scrollbar. - */ -export const showHideOverflowY = (bool: boolean) => { - const html = document.querySelector('html'); - if (html) { - if (bool) { - html.classList.add('overflow-y-hidden'); - } else { - html.classList.remove('overflow-y-hidden'); - } - } -}; - -/** - * Scrolls to the first element that matches the given selector within the provided element. - * @param element The element to search within. - * @param selector The selector to match against. - */ -export const scrollToElement = async (element: HTMLElement, selector: string) => { - const firstElement: HTMLElement | null = element.querySelector(selector); - if (!firstElement) { - return; - } - firstElement.scrollIntoView({ - behavior: 'smooth' - }); -}; - -/** - * Checks if a given URL is an external link. - * @param href - The URL to check. - * @returns True if the URL is an external link, false otherwise. - */ -export const isAnExternalLink = (href: string) => href.startsWith('http'); - -/** - * Checks if the user agent is running on a Mac or iPad. - * @returns {boolean} Returns true if the user agent is running on a Mac or iPad, false otherwise. - */ -export const isMac = () => - navigator.userAgent.includes('Macintosh') || navigator.userAgent.includes('iPad'); - -/** - * Removes the trailing slash from a given string. - * @param site - The string to remove the trailing slash from. - * @returns The string without the trailing slash. - */ -export const removeTrailingSlash = (site: string) => { - return site.replace(/\/$/, ''); -}; - -/** - * Returns a readable store that tracks whether the media query string matches the current viewport. - * @param mediaQueryString - The media query string to match against the viewport. - * @returns A readable store that tracks whether the media query string matches the current viewport. - */ -export const useMediaQuery = (mediaQueryString: string) => { - const matches = readable(undefined, (set) => { - if (typeof globalThis['window'] === 'undefined') return; - - const match = window.matchMedia(mediaQueryString); - set(match.matches); - const element = (event: MediaQueryListEvent) => set(event.matches); - match.addEventListener('change', element); - return () => { - match.removeEventListener('change', element); - }; - }); - return matches; -}; - -/** - * Scrolls the page to the nearest element matching the given selector. - * @param selector - The CSS selector of the element to scroll to. - */ -export const scrollIntoView = (selector: string) => { - const scrollToElement = document.querySelector(selector); - - if (!scrollToElement) return; - - const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); - - scrollToElement.scrollIntoView({ - block: 'nearest', - inline: 'start', - behavior: mediaQuery.matches ? 'auto' : 'smooth' - }); -}; diff --git a/src/lib/utils/posts.ts b/src/lib/utils/posts.ts new file mode 100644 index 0000000..dab6532 --- /dev/null +++ b/src/lib/utils/posts.ts @@ -0,0 +1,84 @@ +import type { FFFFlavoredFrontmatter } from 'fff-flavored-frontmatter' + +interface GenPostsOptions { + /** import.meta.glob https://vitejs.dev/guide/features.html#glob-import */ + modules?: { [path: string]: Urara.Post.Module } + /** set to true to output html */ + postHtml?: boolean + /** limit a certain number of posts */ + postLimit?: number + /** hide posts with 'unlisted' flag */ + filterUnlisted?: boolean +} + +type GenPostsFunction = (options?: GenPostsOptions) => Urara.Post[] + +type GenTagsFunction = (posts: Urara.Post[]) => string[] + +/** + * Detect Post Type + * @param fm - post frontmatter + * @returns - post type string + */ +export const typeOfPost = ( + fm: FFFFlavoredFrontmatter +): 'note' | 'article' | 'reply' | 'photo' | 'like' | 'video' | 'repost' | 'bookmark' | 'audio' => + fm.title + ? 'article' + : fm.image + ? 'photo' + : fm.audio + ? 'audio' + : fm.video + ? 'video' + : fm.bookmark_of + ? 'bookmark' + : fm.like_of + ? 'like' + : fm.repost_of + ? 'repost' + : fm.in_reply_to + ? 'reply' + : 'note' + +/** + * Generate Posts List + * @param options - An optional configuration object + * @returns - posts list + */ +export const genPosts: GenPostsFunction = ({ + modules = import.meta.glob('/src/routes/**/*.{md,svelte.md}', { eager: true }), + postHtml = false, + postLimit = undefined, + filterUnlisted = false +} = {}) => + Object.entries(modules) + .map(([, module]) => ({ + ...module.metadata, + type: typeOfPost(module.metadata), + html: + postHtml || typeOfPost(module.metadata) !== 'article' + ? module.default + .render() + .html // eslint-disable-next-line no-control-regex + .replace(/[\u0000-\u001F]/g, '') + .replace(/[\r\n]/g, '') + .match(/
]+>(.*?)<\/main>/gi)?.[0] + .replace(/
]+>(.*?)<\/main>/gi, '$1') + // .replace(/( class=")(.*?)(")/gi, '') + .replace(/( style=")(.*?)(")/gi, '') + .replace(/()(.*?)(<\/span>)/gi, '$2') + .replace(/(
)(.*?)(<\/main>)/gi, '$2') + : '' + })) + .filter((post, index) => (!filterUnlisted || !post.flags?.includes('unlisted')) && (!postLimit || index < postLimit)) + .sort((a, b) => Date.parse(b.published ?? b.created) - Date.parse(a.published ?? a.created)) + +/** + * Generate Tags List + * @param posts - posts list + * @returns - tags list + */ +export const genTags: GenTagsFunction = posts => [ + ...new Set(posts.reduce((acc, posts) => (posts.tags ? [...acc, ...posts.tags] : acc), ['']).slice(1)) +] diff --git a/src/lib/utils/remark-link-with-image-as-only-child.js b/src/lib/utils/remark-link-with-image-as-only-child.js deleted file mode 100644 index 27c8f46..0000000 --- a/src/lib/utils/remark-link-with-image-as-only-child.js +++ /dev/null @@ -1,22 +0,0 @@ -import { visit } from 'unist-util-visit'; - -const visitor = (node) => { - node.data = node.data || {}; - node.data.hProperties = node.data.hProperties || {}; - if (node.type === 'link') { - if ( - node.children && - node.children.length && - node.children.length === 1 - ) { - if (node.children[0].type === 'image') { - node.data.hProperties.class = 'after:hidden'; - } - } - } -}; - -export default () => async (tree) => { - visit(tree, visitor); - return tree; -}; diff --git a/src/lib/utils/remark-set-image-path.js b/src/lib/utils/remark-set-image-path.js deleted file mode 100644 index 44aeb8d..0000000 --- a/src/lib/utils/remark-set-image-path.js +++ /dev/null @@ -1,19 +0,0 @@ -import { visit } from 'unist-util-visit'; - -const imagesRelativeUrlPattern = '/images/'; - -const visitor = (node) => { - if (node.type === 'image' && node.url.indexOf(imagesRelativeUrlPattern) > 0) { - node.url = node.url.substring(node.url.indexOf(imagesRelativeUrlPattern) + ''.length); - } -}; - -export default () => async (tree, vFile) => { - if ( - vFile.filename.indexOf('src/routes/blog/') > 0 || - vFile.filename.indexOf('src/routes/projects/') > 0 - ) { - visit(tree, visitor); - } - return tree; -}; diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte deleted file mode 100644 index 1af3d73..0000000 --- a/src/routes/+error.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - -{#if $page.status === 404} -
-
- Travolta Confused gif - -

404

-
-

- ? My God, what are you doing here, this page doesn't exist and so on and so on. -

-

- This site either never existed or just suddenly committed disappearance from this - universe. -

-
-{:else} -

{$page.status}

- - {#if $page.error} -

{$page.error.message}

- {/if} - -

Sorry! A grave error has occured. Maybe try one of these links?

- -{/if} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c4dffae..17e5c53 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,147 +1,40 @@ - - - + - - - - - - - - - - - - - +
- - - + + + diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index a0a46f8..c49c6d8 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,2 +1,7 @@ -export const prerender = true; -export const trailingSlash = 'never'; \ No newline at end of file +import type { LayoutLoad } from './$types' +export const prerender = true +export const trailingSlash = 'always' +export const load: LayoutLoad = async ({ url, fetch }) => ({ + path: url.pathname, + res: await fetch('/posts.json').then(res => res.json()) +}) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 58026c4..64ff0af 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,48 +1,124 @@ - - Matt Morin - - - - - - - - + -
- - - - - - - +
+
+ +
+
+ {#if allTags && Object.keys(allTags).length > 0} +
+ {#each allTags as tag} + + {/each} +
+ {/if} +
+
+ {#key posts} + + {#if loaded && posts.length === 0} +
+
+

+ Not found: [{#each tags as tag, i} + '{tag}'{#if i + 1 < tags.length},{/if} + {/each}] +

+ +
+
+ {/if} +
+ {#each posts as post, index} + {@const year = new Date(post.published ?? post.created).getFullYear()} + {#if !years.includes(year)} +
+ {years.push(year) && year} +
+ {/if} +
+ +
+ {/each} +
+
+ + {/key} +
diff --git a/src/routes/api/[user]/[year]/+server.ts b/src/routes/api/[user]/[year]/+server.ts deleted file mode 100644 index 048b615..0000000 --- a/src/routes/api/[user]/[year]/+server.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { parseHTML } from 'linkedom'; -import type { RouteParams } from './$types'; -import { json } from '@sveltejs/kit'; - -export async function GET({ params }) { - const html = await getContributions(params); - return json(parseContributions(html)); -} -/** - * Scrape function that fetches the HTML response from GitHub. - * The response contains a table with all the contributions for the given year. - */ -async function getContributions({ user, year }: RouteParams) { - const api = `https://github.com/users/${user}/contributions?from=${year}-12-01&to=${year}-12-31`; - const response = await fetch(api); - - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.status}`); - } - - return await response.text(); -} -/** - * This function parses the HTML response from a GitHub account page and returns an array of a user's contributions. - ** Each contribution is an object with a date and a level. - ** This function may stop working if GitHub changes the HTML structure, like they did in somewhen in October 2023. - */ -function parseContributions(html: string) { - const { document } = parseHTML(html); - const rows = document.querySelectorAll('tbody > tr'); - const contributions: any[] = []; - - for (const row of rows) { - const days = row.querySelectorAll('td.ContributionCalendar-day'); - const currentRow: any[] = []; - - for (const day of days) { - const date = day.getAttribute('data-date'); - const level = day.getAttribute('data-level'); - - if (date && level) { - const contribution = { - date, - level: parseInt(level, 10) - }; - currentRow.push(contribution); - } else { - currentRow.push(null); - } - } - contributions.push(currentRow); - } - - return contributions; -} diff --git a/src/routes/atom.xml/+server.ts b/src/routes/atom.xml/+server.ts new file mode 100644 index 0000000..7dfc759 --- /dev/null +++ b/src/routes/atom.xml/+server.ts @@ -0,0 +1,50 @@ +import type { RequestHandler } from './$types' +import { site } from '$lib/config/site' +import { feed } from '$lib/config/general' +import { favicon } from '$lib/config/icon' +import { genPosts, genTags } from '$lib/utils/posts' + +const render = (posts = genPosts({ postHtml: true, postLimit: feed.limit, filterUnlisted: true })): string => + ` + + ${site.protocol + site.domain}/ + <![CDATA[${site.title}]]>${site.subtitle ? `\n ` : ''}${ + favicon ? `\n ${favicon.src}` : '' + } + + ${ + feed.hubs?.map(hub => `\n `).join('') ?? '' + } + ${new Date().toJSON()} + + + ${genTags(posts) + .map(tag => `\n `) + .join('')}${posts + .map( + post => `\n + <![CDATA[${post.title}]]> + + ${site.protocol + site.domain + post.path} + ${new Date(post.published ?? post.created).toJSON()} + ${new Date(post.updated ?? post.published ?? post.created).toJSON()}${ + post.summary ? `\n ` : '' + } + + + ${post.tags + ?.map(tag => `\n `) + .join('')} + ` + ) + .join('')} +`.trim() + +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = async () => + new Response(render(), { + headers: { + 'content-type': 'application/atom+xml; charset=utf-8' + } + }) diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts deleted file mode 100644 index 8fca76d..0000000 --- a/src/routes/blog/+page.server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { listPosts } from '$content/blog'; - -export const load = async () => { - return { - posts: listPosts() - }; -}; diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte deleted file mode 100644 index dbb0034..0000000 --- a/src/routes/blog/+page.svelte +++ /dev/null @@ -1,60 +0,0 @@ - - -
-
-
-

Blog

- -
-
- {#each posts.slice(0, displayAmount) as post} -
- -
- {/each} -
-
- - {#if posts.slice(displayAmount).length > 0} -
-

Previous posts

- -
- {/if} -
- - diff --git a/src/routes/blog/[slug]/+layout.server.ts b/src/routes/blog/[slug]/+layout.server.ts deleted file mode 100644 index f639cc2..0000000 --- a/src/routes/blog/[slug]/+layout.server.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { listPosts } from '$content/blog'; -import { error } from '@sveltejs/kit'; - -function shuffle(array: T[]) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} - -export async function load({ params }) { - const posts = listPosts(); - const currentPost = posts.find((post) => post.slug == params.slug); - - if (!currentPost) { - throw error(404, `Unable to find blog post "${params.slug}"`); - } - - shuffle(posts); - - return { - featuredPosts: posts - .filter((post) => post.slug != params.slug) - .filter((p) => p.tags?.some((t) => currentPost.tags?.includes(t))) - .slice(0, 3) - }; -} diff --git a/src/routes/blog/[slug]/+page.svelte b/src/routes/blog/[slug]/+page.svelte deleted file mode 100644 index 9eb2451..0000000 --- a/src/routes/blog/[slug]/+page.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/src/routes/blog/[slug]/+page.ts b/src/routes/blog/[slug]/+page.ts deleted file mode 100644 index 1472be1..0000000 --- a/src/routes/blog/[slug]/+page.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getPost, listPosts } from '$content/blog'; -import type { PageLoad } from './$types'; - -export const entries = async () => { - const posts = await listPosts(); - return posts - .filter((post) => post.slug !== undefined) - .map((post) => ({ slug: post.slug as string })); -}; - -export const load: PageLoad = async ({ params, parent }) => { - await parent(); - return await getPost(params.slug); -}; diff --git a/src/routes/blog/feed/+page.server.ts b/src/routes/blog/feed/+page.server.ts deleted file mode 100644 index 8a0d6d2..0000000 --- a/src/routes/blog/feed/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export async function load() { - throw redirect(301, '/blog/rss.xml'); -} diff --git a/src/routes/blog/rss.xml/+server.ts b/src/routes/blog/rss.xml/+server.ts deleted file mode 100644 index 651a27f..0000000 --- a/src/routes/blog/rss.xml/+server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { listPosts } from '$content/blog'; -import RSS from 'rss'; -import type { Post } from '$lib/types/post'; - -export const GET = async () => { - const posts = listPosts(); - - /* - The RSS feed is a JavaScript object that contains information about the blog feed. - It has a title, description, copyright, and other properties. - It also has an array of categories that can be used to filter the blog feed. - The pubDate property indicates when the feed was last updated. - */ - - const feed = new RSS({ - title: "Matt's Tech Basement", - description: 'A personal blog about ventures in tech.', - copyright: `Copyright © ${new Date().getFullYear()} Matt Morin. All rights reserved`, - ttl: 1800, - feed_url: 'https://www.mattmor.in/blog', - site_url: 'https://www.mattmor.in', - image_url: 'https://www.mattmor.in/favicon192.png', - language: 'en', - categories: ["Matt's Tech Basement updates", 'Tech', 'Disruption', 'DevOps', 'Ventures'], - pubDate: new Date().toUTCString(), - generator: 'Matt Morin' - }); - - // This creates an RSS feed. It does so by iterating over all posts and - // adding each post to the feed. - - posts.forEach((post: Post) => { - feed.item({ - title: post.title, - description: post.excerpt, - url: `https://www.mattmor.in/blog/${post.slug}`, - guid: `https://www.mattmor.in/blog/${post.slug}`, - categories: post.tags, - date: post.date, - enclosure: { - url: `https://www.mattmor.in/images/blog/${post.slug}/${post.image}`, - type: 'image/webp' - }, - author: post.author - }); - }); - - return new Response(feed.xml(), { - headers: { - 'Cache-Control': 'max-age=0, s-max-age=3600', - 'Content-Type': 'application/xml' - } - }); -}; diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte deleted file mode 100644 index e02a5ca..0000000 --- a/src/routes/contact/+page.svelte +++ /dev/null @@ -1 +0,0 @@ -

There is nothing to see yet

diff --git a/src/routes/feed.json/+server.ts b/src/routes/feed.json/+server.ts new file mode 100644 index 0000000..57d173c --- /dev/null +++ b/src/routes/feed.json/+server.ts @@ -0,0 +1,52 @@ +import type { RequestHandler } from './$types' +import { json } from '@sveltejs/kit' +import { site } from '$lib/config/site' +import { feed } from '$lib/config/general' +import { favicon, any } from '$lib/config/icon' +import { genPosts } from '$lib/utils/posts' + +const render = (posts = genPosts({ postHtml: true, postLimit: feed.limit, filterUnlisted: true })) => ({ + version: 'https://jsonfeed.org/version/1.1', + title: site.title, + home_page_url: site.protocol + site.domain, + feed_url: site.protocol + site.domain + '/feed.json', + description: site.description, + icon: any['512'].src ?? any['192'].src, + favicon: favicon?.src, + authors: [ + { + name: site.author.name, + url: site.protocol + site.domain, + avatar: site.author.avatar + } + ], + language: site.lang ?? 'en', + hubs: feed.hubs?.map(hub => ({ + type: 'WebSub', + url: hub + })), + items: posts.map(post => ({ + id: post.path.slice(1), + url: site.protocol + site.domain + post.path, + title: post.title, + content_html: post.html, + summary: post['summary'], + image: post['image'], + date_published: post.published ?? post.created, + date_modified: post.updated ?? post.published ?? post.created, + tags: post.tags, + _indieweb: { + type: post.type, + 'in-reply-to': post.in_reply_to + } + })) +}) + +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = async () => + json(render(), { + headers: { + 'content-type': 'application/feed+json; charset=utf-8' + } + }) diff --git a/src/routes/manifest.webmanifest/+server.ts b/src/routes/manifest.webmanifest/+server.ts index 57057fb..5541ee7 100644 --- a/src/routes/manifest.webmanifest/+server.ts +++ b/src/routes/manifest.webmanifest/+server.ts @@ -1,29 +1,37 @@ -import conf from '$lib/config'; -import { RequestHandler } from '$lib/types'; +import type { RequestHandler } from './$types' +import { site } from '$lib/config/site' +import { any, maskable } from '$lib/config/icon' -export const prerender = true; - -/** @type {import('./$types').RequestHandler} */ -export const GET: RequestHandler = ({ setHeaders }) => { - const { backgroundColor, description, /* siteShortTitle, */ title, themeColor } = conf; - - const manifest = { - name: title, - // short_name: siteShortTitle, - description, - start_url: '/', - background_color: backgroundColor, - theme_color: themeColor, - display: 'standalone', - icons: [ - { src: '/profile-pic.png', type: 'image/png', sizes: '540x540' }, - { src: '/icon-512.png', type: 'image/png', sizes: '512x512' } - ] - }; - - setHeaders({ - 'content-type': 'application/json' - }); - - return new Response(JSON.stringify(manifest)); -}; +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = () => + new Response( + JSON.stringify( + { + name: site.title, + short_name: site.title, + lang: site.lang, + description: site.description, + id: site.protocol + site.domain + '/', + start_url: '/', + scope: '/', + display: 'standalone', + orientation: 'portrait', + background_color: site.themeColor, + theme_color: site.themeColor, + icons: [ + ...Object.values(any) + .filter(icon => icon.sizes !== '180x180') + .map(icon => ({ ...icon, purpose: 'any' })), + ...Object.values(maskable).map(icon => ({ ...icon, purpose: 'maskable' })) + ] + }, + null, + 2 + ), + { + headers: { + 'Content-Type': 'application/manifest+json; charset=utf-8' + } + } + ) diff --git a/src/routes/posts.json/+server.ts b/src/routes/posts.json/+server.ts new file mode 100644 index 0000000..d394dcf --- /dev/null +++ b/src/routes/posts.json/+server.ts @@ -0,0 +1,7 @@ +import type { RequestHandler } from './$types' +import { json } from '@sveltejs/kit' +import { genPosts } from '$lib/utils/posts' + +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = async () => json(genPosts()) diff --git a/src/routes/projects/+page.server.ts b/src/routes/projects/+page.server.ts deleted file mode 100644 index b9caa36..0000000 --- a/src/routes/projects/+page.server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { listProjects } from '$content/projects'; - -export const load = () => { - return { - projects: listProjects() - }; -}; diff --git a/src/routes/projects/+page.svelte b/src/routes/projects/+page.svelte deleted file mode 100644 index a4ffd85..0000000 --- a/src/routes/projects/+page.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
-
-

Projects

-
-
- {#each data.projects as post} -
- -
- {/each} -
-
- - diff --git a/src/routes/projects/[slug]/+layout.server.ts b/src/routes/projects/[slug]/+layout.server.ts deleted file mode 100644 index cf77940..0000000 --- a/src/routes/projects/[slug]/+layout.server.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { listProjects } from '$content/projects'; -import { error } from '@sveltejs/kit'; - -function shuffle(array: T[]) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} - -export async function load({ params }) { - const posts = listProjects(); - const currentPost = posts.find((post) => post.slug == params.slug); - - if (!currentPost) { - throw error(404, `Unable to find blog post "${params.slug}"`); - } - - shuffle(posts); - - return { - featuredPosts: posts - .filter((post) => post.slug != params.slug) - .filter((p) => p.tags?.some((t) => currentPost.tags?.includes(t))) - .slice(0, 3) - }; -} diff --git a/src/routes/projects/[slug]/+page.svelte b/src/routes/projects/[slug]/+page.svelte deleted file mode 100644 index a7ae0ba..0000000 --- a/src/routes/projects/[slug]/+page.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/src/routes/projects/[slug]/+page.ts b/src/routes/projects/[slug]/+page.ts deleted file mode 100644 index 69e72fc..0000000 --- a/src/routes/projects/[slug]/+page.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getProject, listProjects } from '$content/projects'; -import type { PageLoad } from './$types'; - -export const entries = () => listProjects().map((post) => ({ slug: post.slug })); - -export const load: PageLoad = async ({ params }) => { - return await getProject(params.slug); -}; diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts index 4f68590..299f6a1 100644 --- a/src/routes/sitemap.xml/+server.ts +++ b/src/routes/sitemap.xml/+server.ts @@ -1,56 +1,36 @@ -import { removeTrailingSlash } from '$lib/utils/helpers'; -import { listPosts } from '$content/blog'; +import type { RequestHandler } from './$types' +import { site } from '$lib/config/site' +import { genPosts } from '$lib/utils/posts' -// prettier-ignore -const sitemap = (pages: string[]) => ` - - ${pages.map((page) => `${removeTrailingSlash(page)}`).join('')} - -`; +const render = (): string => + ` + + + ${site.protocol + site.domain} + + ${genPosts() + .map( + post => ` + + ${site.protocol + site.domain + post.path} + ${new Date(post.updated ?? post.published ?? post.created).toISOString()} + 0.5 + ` + ) + .join('')} + `.trim() -export const GET = async () => { - const staticPages = Object.keys( - // For other static pages. Except content pages - changelogs, guides, blog posts, guides etc. - import.meta.glob('/src/routes/**/!(_)*.{svelte,md,svx}') - ) - .filter((page) => { - const filters = [ - '/src/routes/index.svelte', - '_', - '404', - 'slug]', - 'title]', - 'unsubscribe', - 'subscribe', - '+error', - '+layout' - ]; - return !filters.find((filter) => page.includes(filter)); - }) - .map((page) => { - return page - .replace('/src/routes', 'https://mattmor.in') - .replace('/index.md', '/') - .replace('.md', '/') - .replace('/index.svelte', '/') - .replace('.svelte', '/') - .replace('/+page', ''); - }); - - const Posts = listPosts().map((post) => `https://www.mattmor.in/blog/${post.slug}`); - const renderedSitemap = sitemap([...staticPages, ...Posts]); - - return new Response(renderedSitemap, { - headers: { - 'Cache-Control': 'max-age=0, s-maxage=3600', - 'Content-Type': 'application/xml' - } - }); -}; +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = async () => + new Response(render(), { + headers: { + 'content-type': 'application/xml; charset=utf-8' + } + }) diff --git a/src/routes/tags.json/+server.ts b/src/routes/tags.json/+server.ts new file mode 100644 index 0000000..0caa1d3 --- /dev/null +++ b/src/routes/tags.json/+server.ts @@ -0,0 +1,7 @@ +import type { RequestHandler } from './$types' +import { json } from '@sveltejs/kit' +import { genPosts, genTags } from '$lib/utils/posts' + +export const prerender = true +export const trailingSlash = 'never' +export const GET: RequestHandler = async () => json(genTags(genPosts())) diff --git a/static/CV_Matthieu_Morin.pdf b/static/CV_Matthieu_Morin.pdf deleted file mode 100644 index 1685126..0000000 Binary files a/static/CV_Matthieu_Morin.pdf and /dev/null differ diff --git a/static/Logo.png b/static/Logo.png deleted file mode 100644 index 6ccfea8..0000000 Binary files a/static/Logo.png and /dev/null differ diff --git a/static/animations/animation.gif b/static/animations/animation.gif deleted file mode 100644 index 9302f28..0000000 Binary files a/static/animations/animation.gif and /dev/null differ diff --git a/static/animations/infinity-loop-icon.svg b/static/animations/infinity-loop-icon.svg deleted file mode 100644 index 4b5306c..0000000 --- a/static/animations/infinity-loop-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/animations/server-icon.svg b/static/animations/server-icon.svg deleted file mode 100644 index 5c6b298..0000000 --- a/static/animations/server-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/animations/shield-lock-black-icon.svg b/static/animations/shield-lock-black-icon.svg deleted file mode 100644 index 5c15fdf..0000000 --- a/static/animations/shield-lock-black-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/animations/shield-sedo-line-icon.svg b/static/animations/shield-sedo-line-icon.svg deleted file mode 100644 index 8e69c69..0000000 --- a/static/animations/shield-sedo-line-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/animations/travolta_confused.webp b/static/animations/travolta_confused.webp deleted file mode 100644 index 25813ba..0000000 Binary files a/static/animations/travolta_confused.webp and /dev/null differ diff --git a/static/animations/web-code-icon.svg b/static/animations/web-code-icon.svg deleted file mode 100644 index a047040..0000000 --- a/static/animations/web-code-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/animations/web-security-icon.svg b/static/animations/web-security-icon.svg deleted file mode 100644 index 4262c20..0000000 --- a/static/animations/web-security-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/assets/any@180.png b/static/assets/any@180.png new file mode 100644 index 0000000..d70c667 Binary files /dev/null and b/static/assets/any@180.png differ diff --git a/static/assets/any@192.png b/static/assets/any@192.png new file mode 100644 index 0000000..d9f36e4 Binary files /dev/null and b/static/assets/any@192.png differ diff --git a/static/assets/any@512.png b/static/assets/any@512.png new file mode 100644 index 0000000..950a045 Binary files /dev/null and b/static/assets/any@512.png differ diff --git a/static/assets/maskable@192.png b/static/assets/maskable@192.png new file mode 100644 index 0000000..7c357e2 Binary files /dev/null and b/static/assets/maskable@192.png differ diff --git a/static/assets/maskable@512.png b/static/assets/maskable@512.png new file mode 100644 index 0000000..3a33462 Binary files /dev/null and b/static/assets/maskable@512.png differ diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..eab8206 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/fonts/MagilioRegular.ttf b/static/fonts/MagilioRegular.ttf deleted file mode 100644 index 1f9ac6a..0000000 Binary files a/static/fonts/MagilioRegular.ttf and /dev/null differ diff --git a/static/fonts/Quicksand.ttf b/static/fonts/Quicksand.ttf deleted file mode 100644 index 0ec2219..0000000 Binary files a/static/fonts/Quicksand.ttf and /dev/null differ diff --git a/static/hello-world/urara.webp b/static/hello-world/urara.webp new file mode 100644 index 0000000..a133d61 Binary files /dev/null and b/static/hello-world/urara.webp differ diff --git a/static/images/Ellipse 61.png b/static/images/Ellipse 61.png deleted file mode 100644 index 46a77fb..0000000 Binary files a/static/images/Ellipse 61.png and /dev/null differ diff --git a/static/images/blog/Logo.png b/static/images/blog/Logo.png deleted file mode 100644 index 6ccfea8..0000000 Binary files a/static/images/blog/Logo.png and /dev/null differ diff --git a/static/images/blog/first-post/Feature.jpg b/static/images/blog/first-post/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/blog/first-post/Feature.jpg and /dev/null differ diff --git a/static/images/blog/first-post/Feature.png b/static/images/blog/first-post/Feature.png deleted file mode 100644 index e69de29..0000000 diff --git a/static/images/blog/first-posthey/Feature.jpg b/static/images/blog/first-posthey/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/blog/first-posthey/Feature.jpg and /dev/null differ diff --git a/static/images/blog/first-posthey/Feature.png b/static/images/blog/first-posthey/Feature.png deleted file mode 100644 index e69de29..0000000 diff --git a/static/images/blog/postwa/Feature.jpg b/static/images/blog/postwa/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/blog/postwa/Feature.jpg and /dev/null differ diff --git a/static/images/blog/second-post/Feature.jpg b/static/images/blog/second-post/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/blog/second-post/Feature.jpg and /dev/null differ diff --git a/static/images/blog/second-post/Feature.png b/static/images/blog/second-post/Feature.png deleted file mode 100644 index e69de29..0000000 diff --git a/static/images/profile-pic.png b/static/images/profile-pic.png deleted file mode 100644 index a34997c..0000000 Binary files a/static/images/profile-pic.png and /dev/null differ diff --git a/static/images/projects/erant/Feature.jpg b/static/images/projects/erant/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/projects/erant/Feature.jpg and /dev/null differ diff --git a/static/images/projects/seedling/Feature.jpg b/static/images/projects/seedling/Feature.jpg deleted file mode 100644 index 4c59b98..0000000 Binary files a/static/images/projects/seedling/Feature.jpg and /dev/null differ diff --git a/static/logos/GitHub_Logo.png b/static/logos/GitHub_Logo.png deleted file mode 100644 index e03d8dd..0000000 Binary files a/static/logos/GitHub_Logo.png and /dev/null differ diff --git a/static/logos/GitHub_Logo_White.png b/static/logos/GitHub_Logo_White.png deleted file mode 100644 index c61ab9d..0000000 Binary files a/static/logos/GitHub_Logo_White.png and /dev/null differ diff --git a/static/logos/LI-In-Bug.png b/static/logos/LI-In-Bug.png deleted file mode 100644 index 8bc2d53..0000000 Binary files a/static/logos/LI-In-Bug.png and /dev/null differ diff --git a/static/logos/LI-Logo.png b/static/logos/LI-Logo.png deleted file mode 100644 index 72bb8a0..0000000 Binary files a/static/logos/LI-Logo.png and /dev/null differ diff --git a/static/logos/github-mark-white.png b/static/logos/github-mark-white.png deleted file mode 100644 index 50b8175..0000000 Binary files a/static/logos/github-mark-white.png and /dev/null differ diff --git a/static/logos/github-mark-white.svg b/static/logos/github-mark-white.svg deleted file mode 100644 index 6cb2f2c..0000000 --- a/static/logos/github-mark-white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/logos/github-mark.png b/static/logos/github-mark.png deleted file mode 100644 index 6cb3b70..0000000 Binary files a/static/logos/github-mark.png and /dev/null differ diff --git a/static/logos/github-mark.svg b/static/logos/github-mark.svg deleted file mode 100644 index 37fa923..0000000 --- a/static/logos/github-mark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/static/robots.txt b/static/robots.txt deleted file mode 100644 index c1d139f..0000000 --- a/static/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: shodan.io \ No newline at end of file diff --git a/svelte.config.js b/svelte.config.js index 620199e..90dcbc3 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,54 +1,41 @@ -import adapter from '@sveltejs/adapter-node'; -import preprocess from 'svelte-preprocess'; +// svelte adapter +import adapterAuto from '@sveltejs/adapter-auto' +import adapterNode from '@sveltejs/adapter-node' +import adapterStatic from '@sveltejs/adapter-static' +// svelte preprocessor +import { mdsvex } from 'mdsvex' +import mdsvexConfig from './mdsvex.config.js' +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' -// config extensions -import { mdsvex } from 'mdsvex'; -import mdsvexConfig from './mdsvex.config.js'; +const adapter = { + auto: adapterAuto(), + node: adapterNode(), + static: adapterStatic({ + pages: 'build', + assets: 'build', + fallback: undefined + }) +} -/** @type {import('@sveltejs/kit').Config} */ -const config = { - extensions: ['.svelte', ...(mdsvexConfig.extensions || [])], - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: [preprocess({ postcss: true }), mdsvex(mdsvexConfig)], - - vitePlugin: { - inspector: true - }, - kit: { - adapter: adapter({ - out: 'build', - precompress: false - }), - - alias: { - $lib: './src/lib', - $root: './', - $src: './src', - $routes: './src/routes', - $content: './src/content' - }, - csrf: { - checkOrigin: process.env.NODE_ENV === 'development' ? false : true - }, - prerender: { - crawl: true, - handleMissingId: 'warn', - handleHttpError: (details) => { - // 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}`); - return; - } - - throw new Error(`${details.status} ${details.path} from ${details.referrer}`); - } - } - } -}; - -export default config; +/** @type {import("@svletejs/kit".Config)} */ +export default { + extensions: ['.svelte', ...mdsvexConfig.extensions], + preprocess: [mdsvex(mdsvexConfig), vitePreprocess()], + kit: { + adapter: + process.env.ADAPTER + ? adapter[process.env.ADAPTER.toLowerCase()] + : Object.keys(process.env).some(key => ['VERCEL', 'NETLIFY'].includes(key)) + ? adapter['auto'] + : adapter['static'], + prerender: { + handleMissingId: 'warn' + }, + csp: { + mode: 'auto', + directives: { + 'style-src': ['self', 'unsafe-inline', 'https://giscus.app'] + } + } + } +} diff --git a/tailwind.config.ts b/tailwind.config.ts index a776fd4..29c3f47 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,30 +1,30 @@ -import { join } from 'path'; -import type { Config } from 'tailwindcss'; -import forms from '@tailwindcss/forms'; -import typography from '@tailwindcss/typography'; -import { skeleton } from '@skeletonlabs/tw-plugin'; +import type { Config } from 'tailwindcss' +import typography from '@tailwindcss/typography' +import daisyui from 'daisyui' + +import { theme } from './src/lib/config/general' export default { - darkMode: 'class', - content: [ - './src/**/*.{html,js,svelte,ts}', - join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}') - ], - theme: { - extend: {} - }, - plugins: [ - forms, - typography, - skeleton({ - themes: { - preset: [ - { - name: 'wintry', - enhancements: true - } - ] - } - }) - ] -} satisfies Config; + content: ['./src/**/*.{html,md,js,svelte,ts}'], + theme: { + extend: { + typography: { + DEFAULT: { + css: { + 'ul:has(li):has(input[type="checkbox"])': { + padding: 0 + }, + 'ul > li:has(input[type="checkbox"])': { + listStyle: 'none' + }, + 'ul > li:has(input[type="checkbox"]) ul li': { + paddingLeft: 30 + } + } + } + } + } + }, + plugins: [typography, daisyui], + daisyui: { themes: theme.map(({ name }) => name) } +} satisfies Config diff --git a/tests-examples/demo-todo-app.spec.ts b/tests-examples/demo-todo-app.spec.ts deleted file mode 100644 index 2fd6016..0000000 --- a/tests-examples/demo-todo-app.spec.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { test, expect, type Page } from '@playwright/test'; - -test.beforeEach(async ({ page }) => { - await page.goto('https://demo.playwright.dev/todomvc'); -}); - -const TODO_ITEMS = [ - 'buy some cheese', - 'feed the cat', - 'book a doctors appointment' -]; - -test.describe('New Todo', () => { - test('should allow me to add todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create 1st todo. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Make sure the list only has one todo item. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0] - ]); - - // Create 2nd todo. - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - - // Make sure the list now has two todo items. - await expect(page.getByTestId('todo-title')).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[1] - ]); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); - - test('should clear text input field when an item is added', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create one todo item. - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - // Check that input is empty. - await expect(newTodo).toBeEmpty(); - await checkNumberOfTodosInLocalStorage(page, 1); - }); - - test('should append new items to the bottom of the list', async ({ page }) => { - // Create 3 items. - await createDefaultTodos(page); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - // Check test using different methods. - await expect(page.getByText('3 items left')).toBeVisible(); - await expect(todoCount).toHaveText('3 items left'); - await expect(todoCount).toContainText('3'); - await expect(todoCount).toHaveText(/3/); - - // Check all items in one call. - await expect(page.getByTestId('todo-title')).toHaveText(TODO_ITEMS); - await checkNumberOfTodosInLocalStorage(page, 3); - }); -}); - -test.describe('Mark all as completed', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test.afterEach(async ({ page }) => { - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should allow me to mark all items as completed', async ({ page }) => { - // Complete all todos. - await page.getByLabel('Mark all as complete').check(); - - // Ensure all todos have 'completed' class. - await expect(page.getByTestId('todo-item')).toHaveClass(['completed', 'completed', 'completed']); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - }); - - test('should allow me to clear the complete state of all items', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - // Check and then immediately uncheck. - await toggleAll.check(); - await toggleAll.uncheck(); - - // Should be no completed classes. - await expect(page.getByTestId('todo-item')).toHaveClass(['', '', '']); - }); - - test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { - const toggleAll = page.getByLabel('Mark all as complete'); - await toggleAll.check(); - await expect(toggleAll).toBeChecked(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Uncheck first todo. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').uncheck(); - - // Reuse toggleAll locator and make sure its not checked. - await expect(toggleAll).not.toBeChecked(); - - await firstTodo.getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 3); - - // Assert the toggle all is checked again. - await expect(toggleAll).toBeChecked(); - }); -}); - -test.describe('Item', () => { - - test('should allow me to mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - // Check first item. - const firstTodo = page.getByTestId('todo-item').nth(0); - await firstTodo.getByRole('checkbox').check(); - await expect(firstTodo).toHaveClass('completed'); - - // Check second item. - const secondTodo = page.getByTestId('todo-item').nth(1); - await expect(secondTodo).not.toHaveClass('completed'); - await secondTodo.getByRole('checkbox').check(); - - // Assert completed class. - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).toHaveClass('completed'); - }); - - test('should allow me to un-mark items as complete', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // Create two items. - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const firstTodo = page.getByTestId('todo-item').nth(0); - const secondTodo = page.getByTestId('todo-item').nth(1); - const firstTodoCheckbox = firstTodo.getByRole('checkbox'); - - await firstTodoCheckbox.check(); - await expect(firstTodo).toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await firstTodoCheckbox.uncheck(); - await expect(firstTodo).not.toHaveClass('completed'); - await expect(secondTodo).not.toHaveClass('completed'); - await checkNumberOfCompletedTodosInLocalStorage(page, 0); - }); - - test('should allow me to edit an item', async ({ page }) => { - await createDefaultTodos(page); - - const todoItems = page.getByTestId('todo-item'); - const secondTodo = todoItems.nth(1); - await secondTodo.dblclick(); - await expect(secondTodo.getByRole('textbox', { name: 'Edit' })).toHaveValue(TODO_ITEMS[1]); - await secondTodo.getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await secondTodo.getByRole('textbox', { name: 'Edit' }).press('Enter'); - - // Explicitly assert the new text value. - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2] - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); -}); - -test.describe('Editing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should hide other controls when editing', async ({ page }) => { - const todoItem = page.getByTestId('todo-item').nth(1); - await todoItem.dblclick(); - await expect(todoItem.getByRole('checkbox')).not.toBeVisible(); - await expect(todoItem.locator('label', { - hasText: TODO_ITEMS[1], - })).not.toBeVisible(); - await checkNumberOfTodosInLocalStorage(page, 3); - }); - - test('should save edits on blur', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).dispatchEvent('blur'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should trim entered text', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(' buy some sausages '); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - 'buy some sausages', - TODO_ITEMS[2], - ]); - await checkTodosInLocalStorage(page, 'buy some sausages'); - }); - - test('should remove the item if an empty text string was entered', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill(''); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Enter'); - - await expect(todoItems).toHaveText([ - TODO_ITEMS[0], - TODO_ITEMS[2], - ]); - }); - - test('should cancel edits on escape', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).dblclick(); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).fill('buy some sausages'); - await todoItems.nth(1).getByRole('textbox', { name: 'Edit' }).press('Escape'); - await expect(todoItems).toHaveText(TODO_ITEMS); - }); -}); - -test.describe('Counter', () => { - test('should display the current number of todo items', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - // create a todo count locator - const todoCount = page.getByTestId('todo-count') - - await newTodo.fill(TODO_ITEMS[0]); - await newTodo.press('Enter'); - - await expect(todoCount).toContainText('1'); - - await newTodo.fill(TODO_ITEMS[1]); - await newTodo.press('Enter'); - await expect(todoCount).toContainText('2'); - - await checkNumberOfTodosInLocalStorage(page, 2); - }); -}); - -test.describe('Clear completed button', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - }); - - test('should display the correct text', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeVisible(); - }); - - test('should remove completed items when clicked', async ({ page }) => { - const todoItems = page.getByTestId('todo-item'); - await todoItems.nth(1).getByRole('checkbox').check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(todoItems).toHaveCount(2); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should be hidden when there are no items that are completed', async ({ page }) => { - await page.locator('.todo-list li .toggle').first().check(); - await page.getByRole('button', { name: 'Clear completed' }).click(); - await expect(page.getByRole('button', { name: 'Clear completed' })).toBeHidden(); - }); -}); - -test.describe('Persistence', () => { - test('should persist its data', async ({ page }) => { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS.slice(0, 2)) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } - - const todoItems = page.getByTestId('todo-item'); - const firstTodoCheck = todoItems.nth(0).getByRole('checkbox'); - await firstTodoCheck.check(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - - // Ensure there is 1 completed item. - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - // Now reload. - await page.reload(); - await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); - await expect(firstTodoCheck).toBeChecked(); - await expect(todoItems).toHaveClass(['completed', '']); - }); -}); - -test.describe('Routing', () => { - test.beforeEach(async ({ page }) => { - await createDefaultTodos(page); - // make sure the app had a chance to save updated todos in storage - // before navigating to a new view, otherwise the items can get lost :( - // in some frameworks like Durandal - await checkTodosInLocalStorage(page, TODO_ITEMS[0]); - }); - - test('should allow me to display active items', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await expect(todoItem).toHaveCount(2); - await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); - }); - - test('should respect the back button', async ({ page }) => { - const todoItem = page.getByTestId('todo-item'); - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - - await test.step('Showing all items', async () => { - await page.getByRole('link', { name: 'All' }).click(); - await expect(todoItem).toHaveCount(3); - }); - - await test.step('Showing active items', async () => { - await page.getByRole('link', { name: 'Active' }).click(); - }); - - await test.step('Showing completed items', async () => { - await page.getByRole('link', { name: 'Completed' }).click(); - }); - - await expect(todoItem).toHaveCount(1); - await page.goBack(); - await expect(todoItem).toHaveCount(2); - await page.goBack(); - await expect(todoItem).toHaveCount(3); - }); - - test('should allow me to display completed items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Completed' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(1); - }); - - test('should allow me to display all items', async ({ page }) => { - await page.getByTestId('todo-item').nth(1).getByRole('checkbox').check(); - await checkNumberOfCompletedTodosInLocalStorage(page, 1); - await page.getByRole('link', { name: 'Active' }).click(); - await page.getByRole('link', { name: 'Completed' }).click(); - await page.getByRole('link', { name: 'All' }).click(); - await expect(page.getByTestId('todo-item')).toHaveCount(3); - }); - - test('should highlight the currently applied filter', async ({ page }) => { - await expect(page.getByRole('link', { name: 'All' })).toHaveClass('selected'); - - //create locators for active and completed links - const activeLink = page.getByRole('link', { name: 'Active' }); - const completedLink = page.getByRole('link', { name: 'Completed' }); - await activeLink.click(); - - // Page change - active items. - await expect(activeLink).toHaveClass('selected'); - await completedLink.click(); - - // Page change - completed items. - await expect(completedLink).toHaveClass('selected'); - }); -}); - -async function createDefaultTodos(page: Page) { - // create a new todo locator - const newTodo = page.getByPlaceholder('What needs to be done?'); - - for (const item of TODO_ITEMS) { - await newTodo.fill(item); - await newTodo.press('Enter'); - } -} - -async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).length === e; - }, expected); -} - -async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { - return await page.waitForFunction(e => { - return JSON.parse(localStorage['react-todos']).filter((todo: any) => todo.completed).length === e; - }, expected); -} - -async function checkTodosInLocalStorage(page: Page, title: string) { - return await page.waitForFunction(t => { - return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); - }, title); -} diff --git a/tests/example.spec.ts b/tests/example.spec.ts deleted file mode 100644 index 821a748..0000000 --- a/tests/example.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('has title', async ({ page }) => { - await page.goto('/'); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test('get started link', async ({ page }) => { - await page.goto('/'); - - // Click the Look at my code link. - await page.getByRole('link', { name: 'Look at my code' }).click(); - // Click the get cv link. - await page.getByRole('link', { name: 'Get my CV' }).click(); - - // Expects page to have a heading with the name of Installation. - await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); -}); - -// Blog tests -test('Blog tests', async ({ page }) => { - await page.goto('/blog'); - await page.click('a[href="/about"]'); - expect(page.url()).toBe('http://localhost:513/about'); -}); - -beforeAll(async () => { - browser = await chromium.launch(); -}); - -afterAll(async () => { - await browser.close(); -}); - -beforeEach(async () => { - page = await browser.newPage(); - await page.goto('http://localhost:5000/skills'); // replace with the URL of your skills page -}); - -afterEach(async () => { - await page.close(); -}); - -it('should display the correct skill levels', async () => { - const skills = await page.$$eval('.chip', (elements) => elements.map((el) => el.textContent)); - for (const skill of skills) { - expect(skill).toMatch(/^(Proficient|Experienced|Limited Experience):/); - } -}); diff --git a/tsconfig.json b/tsconfig.json index 694b253..47fa8ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,37 +1,14 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - // custom compiler options - "noEmit": true, - "target": "ES2018", - "module": "ES2022", - "moduleResolution": "Bundler", - "allowSyntheticDefaultImports": true - }, - "include": [ - "./scripts/**/*", - "./test/*.js", - "./*.js", - "./src/**/*.d.ts", - "./src/**/*.js", - "./src/**/*.svelte", - "./src/**/*.ts", - ".svelte-kit/ambient.d.ts", - ".svelte-kit/types/**/$types.d.ts", - "./csp-directives.ts", - "./src/content/**/*" - ], - "exclude": ["node_modules/*"] - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": false, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "types": ["vite/client", "vite-plugin-pwa/client"] + } } diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..75685a6 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Node", + "declaration": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true + }, + "include": ["urara.ts"] +} diff --git a/urara.ts b/urara.ts new file mode 100644 index 0000000..6b21fc8 --- /dev/null +++ b/urara.ts @@ -0,0 +1,168 @@ +/** + * Urara.TS + * Version: Any + */ + +import { promises as fs } from 'fs' +import * as path from 'path' +import chokidar from 'chokidar' +import chalk from 'chalk' + +const config = { + extensions: { + posts: ['md'], + images: ['jpg', 'png', 'webp', 'avif'] + }, + images: [''], + catch: ['ENOENT', 'EEXIST'] +} + +const check = (ext: string) => (config.extensions.posts.includes(ext) ? 'src/routes' : 'static') + +const log = (color: string, msg: string, dest?: string | Error) => + console.log( + chalk.dim(new Date().toLocaleTimeString() + ' ') + + chalk.magentaBright.bold('[urara] ') + + chalk[color](msg + ' ') + + chalk.dim(dest ?? '') + ) + +const error = (err: { code: string; message: unknown }) => { + if (config.catch.includes(err.code)) { + console.log( + chalk.dim(new Date().toLocaleTimeString() + ' ') + + chalk.redBright.bold('[urara] ') + + chalk.red('error ') + + chalk.dim(err.message) + ) + } else { + throw err + } +} + +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 + .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 + .rm(dest) + .then(() => log('yellow', 'remove file', dest)) + .catch(error) + +const cpDir = (src: string) => + fs.readdir(src, { withFileTypes: true }).then(files => + files.forEach(file => { + const dest = path.join(src, file.name) + if (file.isDirectory()) { + mkDir(dest) + cpDir(dest) + } else if (file.name.startsWith('.')) { + log('cyan', 'ignore file', dest) + } else { + cpFile(dest) + } + }) + ) + +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))] + } = {} +) => { + dest.forEach(path => + fs + .mkdir(path) + .then(() => log('green', 'make dir', path)) + .catch(error) + ) +} + +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))] + } = {} +) => { + dest.forEach(path => + fs + .rm(path, { force: true, recursive: true }) + .then(() => log('yellow', 'remove dir', path)) + .catch(error) + ) +} + +const cleanDir = (src: string) => + fs.readdir(src, { withFileTypes: true }).then(files => { + files.forEach(file => { + const dest = path.join(src, file.name) + file.isDirectory() ? rmDir(dest) : file.name.startsWith('.') ? log('cyan', 'ignore file', dest) : rmFile(dest) + }) + }) + +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]) { + case 'watch': + { + const watcher = chokidar.watch('urara', { + ignored: (file: string) => path.basename(file).startsWith('.') + }) + watcher + .on('add', file => cpFile(file)) + .on('change', file => cpFile(file, { stat: 'update' })) + .on('unlink', file => rmFile(file)) + .on('addDir', dir => mkDir(dir)) + .on('unlinkDir', dir => rmDir(dir)) + .on('error', error => log('red', 'error', error)) + .on('ready', () => log('cyan', 'copy complete. ready for changes')) + process + .on('SIGINT', () => { + log('red', 'sigint') + clean() + watcher?.close() + }) + .on('SIGTERM', () => { + log('red', 'sigterm') + watcher?.close() + }) + .on('exit', () => { + log('red', 'exit') + }) + } + break + case 'build': + build() + break + case 'clean': + clean() + break + default: + log('red', 'error', 'invalid arguments') + break +} diff --git a/urara/assets/any@180.png b/urara/assets/any@180.png new file mode 100644 index 0000000..d70c667 Binary files /dev/null and b/urara/assets/any@180.png differ diff --git a/urara/assets/any@192.png b/urara/assets/any@192.png new file mode 100644 index 0000000..d9f36e4 Binary files /dev/null and b/urara/assets/any@192.png differ diff --git a/urara/assets/any@512.png b/urara/assets/any@512.png new file mode 100644 index 0000000..950a045 Binary files /dev/null and b/urara/assets/any@512.png differ diff --git a/urara/assets/maskable@192.png b/urara/assets/maskable@192.png new file mode 100644 index 0000000..7c357e2 Binary files /dev/null and b/urara/assets/maskable@192.png differ diff --git a/urara/assets/maskable@512.png b/urara/assets/maskable@512.png new file mode 100644 index 0000000..3a33462 Binary files /dev/null and b/urara/assets/maskable@512.png differ diff --git a/urara/favicon.png b/urara/favicon.png new file mode 100644 index 0000000..eab8206 Binary files /dev/null and b/urara/favicon.png differ diff --git a/urara/hello-world/+page.svelte.md b/urara/hello-world/+page.svelte.md new file mode 100644 index 0000000..85e6cf0 --- /dev/null +++ b/urara/hello-world/+page.svelte.md @@ -0,0 +1,45 @@ +--- +title: 'Hello World' +image: '/hello-world/urara.webp' +alt: 'Urara' +created: 2021-11-01 +updated: 2021-12-12 +tags: + - 'Hello World' + - 'Urara' +--- + +Welcome to Urara! this article contains some basic operations to help you quickly get started. + +## Developing + +Start a development server: + +```bash +# http://127.0.0.1:5173 +pnpm dev +``` + +or listen to different IP and port: + +```bash +# http://127.0.0.1:3000 +pnpm dev --port 3000 + +# http://0.0.0.0:3000 +nr dev --host 0.0.0.0 --port 3000 +``` + +## Building + +Create a production version of ur blog: + +```bash +pnpm build +``` + +u can preview the built app with `pnpm preview`. + +## Documentation + +For full documentation, visit [urara-docs.netlify.app](https://urara-docs.netlify.app). diff --git a/urara/hello-world/elements/+page.svelte.md b/urara/hello-world/elements/+page.svelte.md new file mode 100644 index 0000000..a0fc273 --- /dev/null +++ b/urara/hello-world/elements/+page.svelte.md @@ -0,0 +1,155 @@ +--- +title: Elements +created: 2021-12-12 +tags: ['Urara', 'Elements'] +--- + +## Headings + +### Heading + +#### Heading + +##### Heading + +###### Heading + +--- + +## Paragraphs + +||spoiler|| + +**_The_** _quick_ brown [fox](https://www.foxnews.com/) `jumps` ~~over~~ the lazy **dog**. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +建格的何另始養離腳合兒現各談花車是都無處與費別、信善行修覺自!壓總談下市率應次司公母兒用什一線送用標地倒直作任老數年白安足個後引使名隊懷持日落異今特族? + +一士我像衣買了人義,計念? + +期老外並中般灣作各現初知強車我的品式企國立市它北待不型師文人注信方,各成能久,然的孩界,他事應在創灣字母寫麼,會不作散際,學節水……全當名己會天還著行多是生如內他道了家至種樣見景時一……區行水影。滿用機!野於不他北軍沒企國了安巴考治連,用然手些裡像是晚,法無走,教西單不假家這廣邊務土至行氣們個身王沒影,進的客動習外因國說,大傷生出壓統發信全一非爾證。被明快至一子的劇成,義定種刻戲立日發民!出安大是養下裡的,認放官時外的:的富你排說物展年定實兒良吃乎、陸般動後,不力在理校感……顧眼王長力老。 + +重多一?生光聯……動說麼了:起形市般我題臉事級。 + +> In solitude, where we are least alone. + +私は絶対いよいよ漠然たる相当心に対してはずの他にできるならな。けっして今に養成めも何だか漠然たる仕事たないだけにしからならでをは話安んずるないですて、わざわざには聴いましたなけれた。権力を気がつきなのはどうしても先刻が毫もらしくでう。 + +ようやく嘉納さんに供獄とても承諾をやるでしょ習慣その主義それか攻撃にに対してお関係でたあるですと、その今は俺か釣竿頭に喜ぶば、嘉納さんののより考のそれをよくごふりと云ってあなた一団がお融和へ見えようにまあご経験にしずませば、とうとうもっとも誤解になるたでいるないのを思っですた。しかもしかしながら大首へする気もずいぶん美味と移ろたて、この言葉にもいうですてってがたにしてしまえたない。この限り時代のためその学校も私上を吹き込んんかと大森さんをしなませ、人の今日んというご講義ただですば、晩の時で長靴が始めだけの時代に前もっでいて、どうの今日が思えばそのためとあたかも足りんなとしだ事でが、ないますなて少し実職業いでし事だろたませ。 + +さて学校か不幸か話に教えですけれども、今中力が起るて得るですためがご講演の今に訊かたです。多年をはどうしても思うでいうんたずでと、まるで何とも繰り返しが周旋はさっそく悪いうので。 + +[scrollToTop](#headings) + +--- + +## Lists + +### Definition List (dl) + +
+
Definition List Title
+
This is a definition list division.
+
+ +### Ordered List (ol) + +1. List Item 1 +2. List Item 2 +3. List Item 3 + +### Unordered List (ul) + +- List Item 1 +- List Item 2 +- List Item 3 + +### Checkbox List (ul) + +- [ ] List Item 1 unchecked +- [x] List Item 2 checked +- [x] List Item 3 checked + - [ ] Sub List Item 1 unchecked + - [x] Sub List Item 1 checked + +## Table + +| Table Header 1 | Table Header 2 | Table Header 3 | +| -------------- | -------------- | -------------- | +| Division 1 | Division 2 | Division 3 | +| Division 1 | Division 2 | Division 3 | +| Division 1 | Division 2 | Division 3 | + +| Table Header 1 | Table Header 2 | Table Header 3 | +| :------------- | :------------: | -------------: | +| Division 1 | Division 2 | Division 3 | +| Division 1 | Division 2 | Division 3 | +| Division 1 | Division 2 | Division 3 | + +[scrollToTop](#headings) + +## Footnotes + +Here is a footnote reference. [^1] + +This is a long note. [^longnote] + +This is an inline note. ^[You can type footnotes inline, so you don’t have to pick an identifier manually.] + +[^1]: Here is the footnote. +[^longnote]: Here's one footnote with longer identifier. + +[scrollToTop](#headings) + +## Code + +```ts twoslash title="examples/index.ts" +for (let x in [0]) console.log(x) +``` + +```ts twoslash {1-6} +interface IdLabel { + id: number /* some fields */ +} +interface NameLabel { + name: string /* other fields */ +} +type NameOrId = T extends number ? IdLabel : NameLabel +// This comment should not be included + +// ---cut--- +function createLabel(idOrName: T): NameOrId { + throw 'unimplemented' +} + +let a = createLabel('typescript') +``` + +## Misc + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+ +リバースカードオープン、エネミーコントローラー! + +ライフを 1000 払い、コマンド入力、 A B! + +このコマンドにより、全てのアニヲタを破壊する! + +[scrollToTop](#headings) + +## Svelte Components + + + + + +
+ + + +
+ + diff --git a/urara/hello-world/toc-disabled/+page.md b/urara/hello-world/toc-disabled/+page.md new file mode 100644 index 0000000..d1aa18f --- /dev/null +++ b/urara/hello-world/toc-disabled/+page.md @@ -0,0 +1,17 @@ +--- +title: ToC Disabled +summary: This post has disabled table of contents. +created: 2021-12-12 +tags: ['Front-Matter'] +toc: false +--- + +This post has disabled table of contents. + +When the `toc: false` on Front-Matter, toc should not be displayed. + +## Heading + +### Heading + +#### Heading diff --git a/urara/hello-world/urara.webp b/urara/hello-world/urara.webp new file mode 100644 index 0000000..a133d61 Binary files /dev/null and b/urara/hello-world/urara.webp differ diff --git a/vite.config.ts b/vite.config.ts index 36be9c6..cbb8b1d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,53 +1,52 @@ -import { sentrySvelteKit } from '@sentry/sveltekit'; -import { purgeCss } from 'vite-plugin-tailwind-purgecss'; -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vitest/config'; -import { threeMinifier } from '@yushijinhun/three-minifier-rollup'; -import path from 'path'; +// vite define config +import { defineConfig } from 'vite' +// vite plugin +import UnoCSS from 'unocss/vite' +import { presetTagify, presetIcons } from 'unocss' +import extractorSvelte from '@unocss/extractor-svelte' +import { imagetools } from 'vite-imagetools' +import { sveltekit as SvelteKit } from '@sveltejs/kit/vite' +import { SvelteKitPWA } from '@vite-pwa/sveltekit' +// postcss & tailwindcss +import TailwindCSS from 'tailwindcss' +import tailwindConfig from './tailwind.config' +// @ts-expect-error ts(7016) +import LightningCSS from 'postcss-lightningcss' export default defineConfig({ - server: { - host: 'localhost', - port: 5174 - }, - build: { - // to resolve https://github.com/vitejs/vite/issues/6985 - target: 'esnext' - }, - envPrefix: 'PUBLIC_', - plugins: [ - sentrySvelteKit({ - sourceMapsUploadOptions: { - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT - }, - telemetry: false - }), - sveltekit(), - purgeCss({ - safelist: { - // any selectors that begin with "hljs-" will not be purged - greedy: [/^hljs-/] - } - }), - { ...threeMinifier(), enforce: 'pre' } - ], - define: { - 'process.env.VITE_BUILD_TIME': JSON.stringify(new Date().toISOString()) - }, - ssr: { - noExternal: ['three'] - }, - - test: { - include: ['src/**/*.{test,spec}.{js,ts}'] - }, - resolve: { - alias: { - $lib: path.resolve(__dirname, 'src', 'lib'), - $root: path.resolve(__dirname), - $src: path.resolve(__dirname, 'src'), - $routes: path.resolve(__dirname, 'src', 'routes') - } - } -}); + envPrefix: 'URARA_', + build: { + sourcemap: false, + rollupOptions: { + cache: false + } + }, + css: { + postcss: { + plugins: [TailwindCSS(tailwindConfig), LightningCSS()] + } + }, + plugins: [ + UnoCSS({ + content: { pipeline: { include: [/\.svelte$/, /\.md?$/, /\.ts$/] } }, + extractors: [extractorSvelte], + presets: [ + presetTagify({ + extraProperties: (matched: string) => (matched.startsWith('i-') ? { display: 'inline-block' } : {}) + }), + presetIcons({ scale: 1.5 }) + ] + }), + imagetools(), + SvelteKit(), + SvelteKitPWA({ + registerType: 'autoUpdate', + manifest: false, + scope: '/', + workbox: { + globPatterns: ['posts.json', '**/*.{js,css,html,svg,ico,png,webp,avif}'], + globIgnores: ['**/sw*', '**/workbox-*'] + } + }) + ] +})