Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
25c565dfb3
|
|||
c1714e9654
|
|||
409c343a1b
|
|||
fa58967164
|
|||
e0908c57a8
|
|||
7030f70b50
|
|||
5e7100c1e2
|
|||
82d1eea4f7
|
|||
8d5c61cbaa
|
|||
45226136f3
|
|||
a859439353
|
|||
969660abc8
|
|||
99aced7367
|
|||
78b333e9f7
|
|||
1d04befff1
|
2
.env
2
.env
@ -1,3 +1,3 @@
|
|||||||
DOMAIN="martials.no"
|
DOMAIN="martials.no"
|
||||||
GIT_URL=https://git.$DOMAIN
|
GIT_URL=https://code.$DOMAIN
|
||||||
STATUS_URL="https://status.$DOMAIN/status/home"
|
STATUS_URL="https://status.$DOMAIN/status/home"
|
@ -4,9 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
- develop
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -15,5 +13,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Run docker-compose
|
- name: Build production
|
||||||
run: docker compose up -d --build
|
run: docker compose up -d --build prod
|
||||||
|
if: gitea.ref == 'refs/heads/master'
|
||||||
|
- name: Build develop
|
||||||
|
run: docker compose up -d --build dev
|
||||||
|
if: gitea.ref == 'refs/heads/develop'
|
8
TODO.md
8
TODO.md
@ -3,11 +3,17 @@
|
|||||||
- [ ] License
|
- [ ] License
|
||||||
|
|
||||||
## Code
|
## Code
|
||||||
- [ ] Nix Shell
|
- [x] Nix Shell
|
||||||
- [ ] Analytics
|
- [ ] Analytics
|
||||||
- [ ] Organize code better
|
- [ ] Organize code better
|
||||||
- [ ] Type slug of project
|
- [ ] Type slug of project
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
- [x] Staging environment
|
||||||
|
- [x] Deploy to staging environment on push to develop
|
||||||
|
- [x] Deploy to production environment on push to master
|
||||||
|
- [ ] Staging environment .env file
|
||||||
|
|
||||||
## SEO
|
## SEO
|
||||||
- [ ] Meta tags on each page
|
- [ ] Meta tags on each page
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig, envField } from "astro/config"
|
import { defineConfig, envField } from "astro/config"
|
||||||
import paraglide from "@inlang/paraglide-astro"
|
|
||||||
import tailwindcss from "@tailwindcss/vite"
|
import tailwindcss from "@tailwindcss/vite"
|
||||||
import sitemap from "@astrojs/sitemap"
|
import sitemap from "@astrojs/sitemap"
|
||||||
import svelte from "@astrojs/svelte"
|
import svelte from "@astrojs/svelte"
|
||||||
@ -9,6 +8,7 @@ import mdx from "@astrojs/mdx"
|
|||||||
import icon from "astro-icon"
|
import icon from "astro-icon"
|
||||||
|
|
||||||
import { loadEnv } from "vite"
|
import { loadEnv } from "vite"
|
||||||
|
import { paraglideVitePlugin } from "@inlang/paraglide-js"
|
||||||
|
|
||||||
const { URL } = process.env.NODE_ENV
|
const { URL } = process.env.NODE_ENV
|
||||||
? loadEnv(process.env.NODE_ENV, process.cwd(), "")
|
? loadEnv(process.env.NODE_ENV, process.cwd(), "")
|
||||||
@ -20,35 +20,37 @@ export default defineConfig({
|
|||||||
output: "server",
|
output: "server",
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: "nb",
|
defaultLocale: "nb",
|
||||||
locales: ["nb", "en"],
|
locales: ["nb", "en"]
|
||||||
},
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
sitemap(),
|
sitemap(),
|
||||||
mdx(),
|
mdx(),
|
||||||
svelte(),
|
svelte(),
|
||||||
icon(),
|
icon()
|
||||||
paraglide({
|
|
||||||
project: "./project.inlang",
|
|
||||||
outdir: "./src/paraglide",
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: "standalone",
|
mode: "standalone"
|
||||||
}),
|
}),
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [
|
||||||
|
tailwindcss(),
|
||||||
|
paraglideVitePlugin({
|
||||||
|
project: "./project.inlang",
|
||||||
|
outdir: "./src/paraglide"
|
||||||
|
})
|
||||||
|
]
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
theme: "catppuccin-mocha",
|
theme: "catppuccin-mocha"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
schema: {
|
schema: {
|
||||||
DOMAIN: envField.string({ context: "client", access: "public" }),
|
DOMAIN: envField.string({ context: "client", access: "public" }),
|
||||||
URL: envField.string({ context: "client", access: "public" }),
|
URL: envField.string({ context: "client", access: "public" }),
|
||||||
GIT_URL: envField.string({ context: "client", access: "public" }),
|
GIT_URL: envField.string({ context: "client", access: "public" }),
|
||||||
STATUS_URL: envField.string({ context: "client", access: "public" }),
|
STATUS_URL: envField.string({ context: "client", access: "public" })
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
57
biome.jsonc
Normal file
57
biome.jsonc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": false,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": false
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"ignoreUnknown": false,
|
||||||
|
"ignore": ["./src/paraglide"],
|
||||||
|
"include": ["./src/**"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space"
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"recommended": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "double",
|
||||||
|
"semicolons": "asNeeded",
|
||||||
|
"arrowParentheses": "asNeeded"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"include": ["*.astro"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useConst": "off",
|
||||||
|
"useImportType": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": ["*.svelte"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useConst": "off",
|
||||||
|
"useImportType": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,9 +1,20 @@
|
|||||||
services:
|
services:
|
||||||
web:
|
prod:
|
||||||
container_name: martials.no
|
hostname: martials.no
|
||||||
|
container_name: "martials.no"
|
||||||
restart: always
|
restart: always
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "4321:4321"
|
- "4321:4321"
|
||||||
|
|
||||||
|
dev:
|
||||||
|
hostname: dev.martials.no
|
||||||
|
container_name: "dev.martials.no"
|
||||||
|
restart: always
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ports:
|
||||||
|
- "4322:4321"
|
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1751211869,
|
||||||
|
"narHash": "sha256-1Cu92i1KSPbhPCKxoiVG5qnoRiKTgR5CcGSRyLpOd7Y=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b43c397f6c213918d6cfe6e3550abfe79b5d1c51",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-25.05",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
37
flake.nix
Normal file
37
flake.nix
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
description = "martials.no Development environment";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default =
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
git
|
||||||
|
] ++ [
|
||||||
|
# Node
|
||||||
|
nodejs_22
|
||||||
|
pnpm
|
||||||
|
] ++ [
|
||||||
|
nodePackages.prettier
|
||||||
|
biome
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
pnpm install
|
||||||
|
fish
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
52
package.json
52
package.json
@ -10,49 +10,31 @@
|
|||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
"type-check": "astro check",
|
"type-check": "astro check",
|
||||||
"postinstall": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide",
|
"postinstall": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide",
|
||||||
"format": "prettier --write \"./src/**/*.{js,mjs,ts,astro,svelte,css,md,json}\"",
|
"format": "biome format --write .",
|
||||||
|
"lint": "biome lint --write .",
|
||||||
|
"lint:fix": "biome check --write .",
|
||||||
"watch-messages": "paraglide-js compile --watch --project ./project.inlang --outdir ./src/paraglide"
|
"watch-messages": "paraglide-js compile --watch --project ./project.inlang --outdir ./src/paraglide"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/mdx": "^4.0.8",
|
"@astrojs/mdx": "^4.3.0",
|
||||||
"@astrojs/node": "9.1.1",
|
"@astrojs/node": "9.2.2",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.4.1",
|
||||||
"@astrojs/svelte": "^7.0.4",
|
"@astrojs/svelte": "^7.1.0",
|
||||||
"@iconify-json/pajamas": "^1.2.5",
|
"@iconify-json/pajamas": "^1.2.11",
|
||||||
"@inlang/paraglide-astro": "^0.3.5",
|
"@inlang/paraglide-js": "2.1.0",
|
||||||
"@inlang/paraglide-js": "1.11.8",
|
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tailwindcss/vite": "^4.0.9",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"astro": "^5.4.1 ",
|
"astro": "^5.10.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.34.2",
|
||||||
"svelte": "^5.20.4",
|
"svelte": "^5.34.9",
|
||||||
"tailwindcss": "^4.0.9",
|
"tailwindcss": "^4.1.11",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"daisyui": "^5.0.0",
|
"daisyui": "^5.0.43",
|
||||||
"prettier": "^3.5.2",
|
"vite": "^7.0.0"
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
|
||||||
"vite": "^6.2.0"
|
|
||||||
},
|
|
||||||
"prettier": {
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": false,
|
|
||||||
"plugins": [
|
|
||||||
"prettier-plugin-astro",
|
|
||||||
"prettier-plugin-svelte"
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "**/*.astro",
|
|
||||||
"options": {
|
|
||||||
"parser": "astro"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2843
pnpm-lock.yaml
generated
2843
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://inlang.com/schema/project-settings",
|
"$schema": "https://inlang.com/schema/project-settings",
|
||||||
"sourceLanguageTag": "nb",
|
"baseLocale": "nb",
|
||||||
"languageTags": ["nb", "en"],
|
"locales": [
|
||||||
|
"nb",
|
||||||
|
"en"
|
||||||
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
|
||||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
|
|
||||||
],
|
],
|
||||||
"plugin.inlang.messageFormat": {
|
"plugin.inlang.messageFormat": {
|
||||||
"pathPattern": "./messages/{languageTag}.json"
|
"pathPattern": "./messages/{locale}.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
---
|
---
|
||||||
import { type NavLink, resolvePathname } from "@/utils/linking"
|
import { type NavLink } from "@/utils/linking"
|
||||||
import LocaleLink from "@/components/links/LocaleLink.astro"
|
import LocaleLink from "@/components/links/LocaleLink.astro"
|
||||||
|
import { deLocalizeHref } from "@/paraglide/runtime.js"
|
||||||
|
|
||||||
const pathname = resolvePathname(Astro.originPathname)
|
const pathname = deLocalizeHref(Astro.originPathname)
|
||||||
|
|
||||||
let paths: string[]
|
let paths: string[]
|
||||||
if (pathname === "/") {
|
if (pathname === "/") {
|
||||||
paths = ["~"]
|
paths = ["~"]
|
||||||
} else {
|
} else {
|
||||||
paths = ["~", ...pathname.split("/").slice(1)]
|
paths = ["~", ...pathname.split("/").filter(x => x)]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLink(path: string): NavLink {
|
function getLink(path: string): NavLink {
|
||||||
@ -25,13 +26,13 @@ function getLink(path: string): NavLink {
|
|||||||
{
|
{
|
||||||
paths.map((path, index) => (
|
paths.map((path, index) => (
|
||||||
<span>
|
<span>
|
||||||
{index != paths.length - 1 ? (
|
{ index != paths.length - 1 ? (
|
||||||
<span>
|
<span>
|
||||||
<LocaleLink to={getLink(path)}>{path}</LocaleLink>/
|
<LocaleLink to={ getLink(path) }>{ path }</LocaleLink>/
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
path
|
path
|
||||||
)}
|
) }
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
import GiteaLink from "./links/GiteaLink.astro"
|
import GiteaLink from "./links/GiteaLink.astro"
|
||||||
import PajamasIcon from "./icons/PajamasIcon.astro"
|
import PajamasIcon from "./icons/PajamasIcon.astro"
|
||||||
import ExternalLink from "./links/ExternalLink.astro"
|
import ExternalLink from "./links/ExternalLink.astro"
|
||||||
import LanguageButtonGroup from "./LanguageButtonGroup.astro"
|
import LanguageButtonGroup from "./LanguageButtonGroup.svelte"
|
||||||
import { GIT_URL, STATUS_URL } from "astro:env/client"
|
import { GIT_URL, STATUS_URL } from "astro:env/client"
|
||||||
import * as m from "@/paraglide/messages"
|
import * as m from "@/paraglide/messages"
|
||||||
|
|
||||||
const giteaLink = `${GIT_URL}/martials/martials.no`
|
const giteaLink = `${GIT_URL}/martials/martials.no`
|
||||||
|
const pathname = Astro.url.pathname
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="divider bg-inherit"></div>
|
<div class="divider bg-inherit"></div>
|
||||||
@ -28,5 +29,5 @@ const giteaLink = `${GIT_URL}/martials/martials.no`
|
|||||||
{m.status()}
|
{m.status()}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</div>
|
</div>
|
||||||
<LanguageButtonGroup />
|
<LanguageButtonGroup client:load />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import LocaleLink from "./links/LocaleLink.astro"
|
import LocaleLink from "./links/LocaleLink.astro"
|
||||||
import { type NavLink, resolvePathname } from "@/utils/linking"
|
import { type NavLink } from "@/utils/linking"
|
||||||
|
import { deLocalizeHref } from "@/paraglide/runtime"
|
||||||
|
|
||||||
const pathname = Astro.url.pathname
|
const pathname = Astro.url.pathname
|
||||||
const currentPath = resolvePathname(pathname)
|
const currentPath = deLocalizeHref(pathname)
|
||||||
const isEnglish = pathname.startsWith("/en")
|
const isEnglish = pathname.startsWith("/en")
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ const isEnglish = pathname.startsWith("/en")
|
|||||||
class:list={[
|
class:list={[
|
||||||
"btn join-item !text-cat-text border-cat-surface0",
|
"btn join-item !text-cat-text border-cat-surface0",
|
||||||
!isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
!isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
||||||
]}>Norsk</LocaleLink
|
]} client:load>Norsk</LocaleLink
|
||||||
>
|
>
|
||||||
<LocaleLink
|
<LocaleLink
|
||||||
to={currentPath as NavLink}
|
to={currentPath as NavLink}
|
||||||
@ -22,6 +23,6 @@ const isEnglish = pathname.startsWith("/en")
|
|||||||
class:list={[
|
class:list={[
|
||||||
"btn join-item !text-cat-text border-cat-surface0",
|
"btn join-item !text-cat-text border-cat-surface0",
|
||||||
isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
||||||
]}>English</LocaleLink
|
]} client:load>English</LocaleLink
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
29
src/components/LanguageButtonGroup.svelte
Normal file
29
src/components/LanguageButtonGroup.svelte
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getLocale, type Locale, setLocale } from "@/paraglide/runtime"
|
||||||
|
|
||||||
|
const isEnglish = getLocale() === "en"
|
||||||
|
|
||||||
|
function updateLocale(lang: Locale) {
|
||||||
|
setLocale(lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="join">
|
||||||
|
<button
|
||||||
|
onclick={() => updateLocale("nb")}
|
||||||
|
class={[
|
||||||
|
"btn join-item !text-cat-text border-cat-surface0",
|
||||||
|
!isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
||||||
|
]}>Norsk
|
||||||
|
</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onclick={() => updateLocale("en")}
|
||||||
|
class={[
|
||||||
|
"btn join-item !text-cat-text border-cat-surface0",
|
||||||
|
isEnglish ? "bg-cat-mantle" : "bg-cat-base",
|
||||||
|
]}>English
|
||||||
|
</button
|
||||||
|
>
|
||||||
|
</div>
|
@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// TODO move to types?
|
// TODO move to types?
|
||||||
interface Option<Key> {
|
interface Option<Key> {
|
||||||
key: Key
|
key: Key
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props<Key = string> {
|
interface Props<Key = string> {
|
||||||
selected: Key
|
selected: Key
|
||||||
options?: Option<Key>[]
|
options?: Option<Key>[]
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
let { selected = $bindable(), options = [], class: clazz }: Props = $props()
|
let { selected = $bindable(), options = [], class: clazz }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte"
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let history: string[] = []
|
let history: string[] = []
|
||||||
let currentDir = "~"
|
let currentDir = "~"
|
||||||
|
|
||||||
type Command = "help" | "about" | "skills" | "projects" | "contact" | "clear"
|
type Command = "help" | "about" | "skills" | "projects" | "contact" | "clear"
|
||||||
|
|
||||||
const commands: Record<Command, () => string> = {
|
const commands: Record<Command, () => string> = {
|
||||||
help: () => `Available commands:
|
help: () => `Available commands:
|
||||||
about - Display information about me
|
about - Display information about me
|
||||||
skills - List my technical skills
|
skills - List my technical skills
|
||||||
@ -34,47 +34,43 @@ LinkedIn: linkedin.com/in/johndoe`,
|
|||||||
history = []
|
history = []
|
||||||
return ""
|
return ""
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const executeCommand = (input: string) => {
|
const executeCommand = (input: string) => {
|
||||||
const [command, ...args] = input.trim().split(" ")
|
const [command, ...args] = input.trim().split(" ")
|
||||||
if (command in commands) {
|
if (command in commands) {
|
||||||
return commands[command as Command]()
|
return commands[command as Command]()
|
||||||
}
|
}
|
||||||
return `Command not found: ${command}. Type 'help' for available commands.`
|
return `Command not found: ${command}. Type 'help' for available commands.`
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = ""
|
let input = ""
|
||||||
let inputRef: HTMLInputElement | null = null
|
let inputRef: HTMLInputElement | null = null
|
||||||
|
|
||||||
const handleSubmit = (e: Event) => {
|
const handleSubmit = (e: Event) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (input.trim()) {
|
if (input.trim()) {
|
||||||
if (input === "clear") {
|
if (input === "clear") {
|
||||||
history = []
|
history = []
|
||||||
} else {
|
} else {
|
||||||
history = [
|
history = [...history, `${currentDir} $ ${input}`, executeCommand(input)]
|
||||||
...history,
|
|
||||||
`${currentDir} $ ${input}`,
|
|
||||||
executeCommand(input),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
input = ""
|
input = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
history = [
|
history = [
|
||||||
"Welcome to John Doe's Terminal Portfolio!",
|
"Welcome to John Doe's Terminal Portfolio!",
|
||||||
"Type 'help' to see available commands.",
|
"Type 'help' to see available commands.",
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (inputRef) {
|
if (inputRef) {
|
||||||
inputRef.scrollIntoView({ behavior: "smooth" })
|
inputRef.scrollIntoView({ behavior: "smooth" })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-h-screen bg-black text-green-500 p-4 font-mono">
|
<div class="min-h-screen bg-black text-green-500 p-4 font-mono">
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte"
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string
|
title?: string
|
||||||
children: Snippet
|
children: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title = "", children }: Props = $props()
|
const { title = "", children }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<details class="collapse collapse-arrow bg-base-200">
|
<details class="collapse collapse-arrow bg-base-200">
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Collapse from "@/components/collapse/Collapse.svelte"
|
import Collapse from "@/components/collapse/Collapse.svelte"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
items?: string[]
|
items?: string[]
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const { items = [], title = "" }: Props = $props()
|
const { items = [], title = "" }: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Collapse {title}>
|
<Collapse {title}>
|
||||||
|
@ -3,9 +3,9 @@ import Navbar from "./Navbar.astro"
|
|||||||
import NavbarDrawer from "./NavbarDrawer.astro"
|
import NavbarDrawer from "./NavbarDrawer.astro"
|
||||||
import HamburgerMenuButton from "./HamburgerMenuButton.astro"
|
import HamburgerMenuButton from "./HamburgerMenuButton.astro"
|
||||||
import Breadcrumb from "../Breadcrumb.astro"
|
import Breadcrumb from "../Breadcrumb.astro"
|
||||||
import { resolvePathname } from "@/utils/linking"
|
import { deLocalizeHref } from "@/paraglide/runtime"
|
||||||
|
|
||||||
const currentPath = `~${resolvePathname(Astro.originPathname)}`
|
const currentPath = `~${deLocalizeHref(Astro.originPathname)}`
|
||||||
const drawerToggleId = "header-drawer"
|
const drawerToggleId = "header-drawer"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
---
|
---
|
||||||
import { languageTag, type AvailableLanguageTag } from "@/paraglide/runtime"
|
import { localizeHref, getLocale, type Locale } from "@/paraglide/runtime"
|
||||||
import { localizePathname, type NavLink } from "@/utils/linking"
|
import { type NavLink } from "@/utils/linking"
|
||||||
import type { ComponentProps } from "@/types/props"
|
import type { ComponentProps } from "@/types/props"
|
||||||
|
|
||||||
interface Props extends ComponentProps {
|
interface Props extends ComponentProps {
|
||||||
to: NavLink
|
to: NavLink
|
||||||
lang?: AvailableLanguageTag
|
lang?: Locale
|
||||||
}
|
}
|
||||||
|
|
||||||
const { to, class: clazz, lang = languageTag() } = Astro.props
|
const { to, class: clazz, lang = getLocale() } = Astro.props
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href={localizePathname(to, lang)} class={clazz}>
|
<!-- TODO currently not working on Paraglide 2 https://github.com/opral/inlang-paraglide-js/issues/472 -->
|
||||||
|
<a href={localizeHref(to, { locale: lang })} class={clazz}>
|
||||||
<slot />
|
<slot />
|
||||||
</a>
|
</a>
|
||||||
|
@ -3,7 +3,7 @@ import * as m from "@/paraglide/messages"
|
|||||||
import Layout from "@/layouts/Layout.astro"
|
import Layout from "@/layouts/Layout.astro"
|
||||||
import BadgeList from "@/components/badge/BadgeList.astro"
|
import BadgeList from "@/components/badge/BadgeList.astro"
|
||||||
import GiteaLink from "@/components/links/GiteaLink.astro"
|
import GiteaLink from "@/components/links/GiteaLink.astro"
|
||||||
import { languageTag } from "@/paraglide/runtime"
|
import { getLocale } from "@/paraglide/runtime"
|
||||||
import { getEntry, render } from "astro:content"
|
import { getEntry, render } from "astro:content"
|
||||||
import { Image } from "astro:assets"
|
import { Image } from "astro:assets"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
@ -16,7 +16,10 @@ interface Props {
|
|||||||
const { project } = Astro.props
|
const { project } = Astro.props
|
||||||
|
|
||||||
const entry = await getEntry("projects", project)
|
const entry = await getEntry("projects", project)
|
||||||
const { Content } = await render(entry!)
|
if (!entry) {
|
||||||
|
throw new Error("Project not found")
|
||||||
|
}
|
||||||
|
const { Content } = await render(entry)
|
||||||
const {
|
const {
|
||||||
lang,
|
lang,
|
||||||
title,
|
title,
|
||||||
@ -28,14 +31,14 @@ const {
|
|||||||
source,
|
source,
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
} = entry!.data
|
} = entry.data
|
||||||
|
|
||||||
function localeDateString(isoString: string): string {
|
function localeDateString(isoString: string): string {
|
||||||
let template = "DD-MM-YYYY"
|
let template = "DD-MM-YYYY"
|
||||||
if (languageTag() === "nb") {
|
if (getLocale() === "nb") {
|
||||||
template = "DD/MM/YYYY"
|
template = "DD/MM/YYYY"
|
||||||
}
|
}
|
||||||
return dayjs(isoString).locale(languageTag()).format(template)
|
return dayjs(isoString).locale(getLocale()).format(template)
|
||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { defineCollection, z } from "astro:content"
|
|||||||
import { glob } from "astro/loaders"
|
import { glob } from "astro/loaders"
|
||||||
|
|
||||||
const projectCollection = defineCollection({
|
const projectCollection = defineCollection({
|
||||||
loader: glob({ pattern: "**\/*.mdx", base: "./src/content/projects" }),
|
loader: glob({ pattern: "**/*.mdx", base: "./src/content/projects" }),
|
||||||
schema: ({ image }) =>
|
schema: ({ image }) =>
|
||||||
z.object({
|
z.object({
|
||||||
lang: z.union([z.literal("en"), z.literal("nb")]),
|
lang: z.union([z.literal("en"), z.literal("nb")]),
|
||||||
@ -19,7 +19,7 @@ const projectCollection = defineCollection({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const usesCollection = defineCollection({
|
const usesCollection = defineCollection({
|
||||||
loader: glob({ pattern: "**\/*.yaml", base: "./src/content/uses" }),
|
loader: glob({ pattern: "**/*.yaml", base: "./src/content/uses" }),
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
accessories: z.optional(z.array(z.string())),
|
accessories: z.optional(z.array(z.string())),
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import Footer from "@/components/Footer.astro"
|
import Footer from "@/components/Footer.astro"
|
||||||
import Header from "@/components/header/Header.astro"
|
import Header from "@/components/header/Header.astro"
|
||||||
import Breadcrumb from "@/components/Breadcrumb.astro"
|
import Breadcrumb from "@/components/Breadcrumb.astro"
|
||||||
import { languageTag } from "@/paraglide/runtime"
|
import { getLocale } from "@/paraglide/runtime"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string
|
title: string
|
||||||
@ -16,7 +16,7 @@ const mainClass =
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang={languageTag()} dir={"ltr"}>
|
<html lang={getLocale()} dir={"ltr"}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="author" content="Martin Berg Alstad" />
|
<meta name="author" content="Martin Berg Alstad" />
|
||||||
|
6
src/middleware.ts
Normal file
6
src/middleware.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { paraglideMiddleware } from "@/paraglide/server"
|
||||||
|
import { defineMiddleware } from "astro/middleware"
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware((context, next) => {
|
||||||
|
return paraglideMiddleware(context.request, () => next());
|
||||||
|
});
|
@ -1,4 +1,3 @@
|
|||||||
import type { AvailableLanguageTag } from "@/paraglide/runtime.js"
|
|
||||||
import type { AbsolutePathname, Project } from "@/types/types.ts"
|
import type { AbsolutePathname, Project } from "@/types/types.ts"
|
||||||
|
|
||||||
interface TranslatedPathnames {
|
interface TranslatedPathnames {
|
||||||
@ -22,8 +21,6 @@ const paths: Set<NavLink> = new Set([
|
|||||||
"/uses",
|
"/uses",
|
||||||
])
|
])
|
||||||
|
|
||||||
const projectPaths: Set<string> = new Set<string>(["homepage", "sb1budget"])
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the localized pathnames for the site.
|
* Defines the localized pathnames for the site.
|
||||||
* The key must be used to navigate to the correct path.
|
* The key must be used to navigate to the correct path.
|
||||||
@ -38,49 +35,3 @@ for (const path of paths) {
|
|||||||
en: `/en${path}`,
|
en: `/en${path}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function localizePathname(
|
|
||||||
pathname: NavLink,
|
|
||||||
locale: AvailableLanguageTag,
|
|
||||||
): string {
|
|
||||||
const pathnameParts = pathname.split("/")
|
|
||||||
const firstSegment: AbsolutePathname = `/${pathnameParts[1]}`
|
|
||||||
|
|
||||||
if (pathnames[firstSegment]) {
|
|
||||||
const localizedPathname = pathnames[firstSegment][locale]
|
|
||||||
|
|
||||||
const rest = pathnameParts.slice(2)
|
|
||||||
if (rest.length > 0) {
|
|
||||||
return `${localizedPathname}/${rest.join("/")}`
|
|
||||||
} else {
|
|
||||||
return localizedPathname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pathname
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolvePathname(pathname: string): AbsolutePathname {
|
|
||||||
if (pathname.startsWith("/en")) {
|
|
||||||
return pathname.slice(3) as AbsolutePathname
|
|
||||||
}
|
|
||||||
return pathname as AbsolutePathname
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAbsolutePathname(path: string): path is AbsolutePathname {
|
|
||||||
return path.startsWith("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isNavLink(path: string): path is NavLink {
|
|
||||||
if (path.startsWith("/en")) {
|
|
||||||
path = path.slice(2)
|
|
||||||
}
|
|
||||||
if (paths.has(path as NavLink)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const pathSplit = path.split("/").slice(1)
|
|
||||||
return (
|
|
||||||
pathSplit.length === 2 &&
|
|
||||||
pathSplit[0] === "projects" &&
|
|
||||||
projectPaths.has(pathSplit[1])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user