chore: update to latest astro ink version
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -21,5 +21,10 @@ yarn-error.log*
|
||||
.netlify
|
||||
netlify
|
||||
|
||||
# Nix
|
||||
.astro
|
||||
.idea
|
||||
.vercel
|
||||
|
||||
# nix
|
||||
result
|
||||
.std
|
||||
|
||||
@@ -1,49 +1,56 @@
|
||||
import path, { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import svelte from '@astrojs/svelte'
|
||||
import tailwind from '@astrojs/tailwind'
|
||||
import sitemap from '@astrojs/sitemap'
|
||||
import mdx from '@astrojs/mdx'
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import svelte from '@astrojs/svelte';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import { defineConfig } from "astro/config";
|
||||
import vercel from "@astrojs/vercel/serverless";
|
||||
import markdoc from "@astrojs/markdoc";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
import remarkCodeTitles from 'remark-code-titles'
|
||||
import decapCmsOauth from "astro-decap-cms-oauth";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
// Full Astro Configuration API Documentation:
|
||||
// https://docs.astro.build/reference/configuration-reference
|
||||
|
||||
// @type-check enabled!
|
||||
// VSCode and other TypeScript-enabled text editors will provide auto-completion,
|
||||
// helpful tooltips, and warnings if your exported object is invalid.
|
||||
// You can disable this by removing "@ts-check" and `@type` comments below.
|
||||
|
||||
// @ts-check
|
||||
export default /** @type {import('astro').AstroUserConfig} */ ({
|
||||
// root: '.', // Where to resolve all URLs relative to. Useful if you have a monorepo project.
|
||||
// outDir: './dist', // When running `astro build`, path to final static output
|
||||
// publicDir: './public', // A folder of static files Astro will copy to the root. Useful for favicons, images, and other files that don’t need processing.
|
||||
|
||||
site: 'https://nrd.sh',
|
||||
// https://astro.build/config
|
||||
export default defineConfig( /** @type {import('astro').AstroUserConfig} */{
|
||||
output: 'server',
|
||||
site: 'https://nrd.sh', // Your public domain, e.g.: https://my-site.dev/. Used to generate sitemaps and canonical URLs.
|
||||
server: {
|
||||
// port: 3000, // The port to run the dev server on.
|
||||
// port: 4321, // The port to run the dev server on.
|
||||
},
|
||||
markdown: {
|
||||
syntaxHighlight: 'shiki',
|
||||
shikiConfig: {
|
||||
theme: 'css-variables',
|
||||
},
|
||||
remarkPlugins: [
|
||||
remarkCodeTitles
|
||||
]
|
||||
},
|
||||
integrations: [
|
||||
mdx(),
|
||||
svelte(),
|
||||
mdx(),
|
||||
markdoc(),
|
||||
svelte(),
|
||||
tailwind({
|
||||
config: {
|
||||
applyBaseStyles: false
|
||||
},
|
||||
}),
|
||||
sitemap()
|
||||
applyBaseStyles: false,
|
||||
}),
|
||||
sitemap(),
|
||||
decapCmsOauth()
|
||||
],
|
||||
vite: {
|
||||
plugins: [],
|
||||
resolve: {
|
||||
alias: {
|
||||
'$': path.resolve(__dirname, './src'),
|
||||
},
|
||||
alias: {
|
||||
$: path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
allowNodeBuiltins: true
|
||||
allowNodeBuiltins: true
|
||||
}
|
||||
}
|
||||
},
|
||||
adapter: vercel()
|
||||
});
|
||||
|
||||
12
biome.json
Normal file
12
biome.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
}
|
||||
416
flake.lock
generated
416
flake.lock
generated
@@ -15,6 +15,31 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1676162383,
|
||||
"narHash": "sha256-krUCKdz7ebHlFYm/A7IbKDnj2ZmMMm3yIEQcooqm7+E=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "6fb400ec631b22ccdbc7090b38207f7fb5cfb5f2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
@@ -40,28 +65,85 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"dmerge": {
|
||||
"devshell_2": {
|
||||
"inputs": {
|
||||
"nixlib": [
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1687173957,
|
||||
"narHash": "sha256-GOds2bAQcZ94fb9/Nl/aM+r+0wGSi4EKYuZYR8Dw4R8=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "2cf83bb31720fcc29a999aee28d6da101173e66a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"dmerge": {
|
||||
"inputs": {
|
||||
"haumea": "haumea",
|
||||
"nixlib": "nixlib",
|
||||
"yants": [
|
||||
"std",
|
||||
"yants"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1659548052,
|
||||
"narHash": "sha256-fzI2gp1skGA8mQo/FBFrUAtY0GQkAIAaV/V127TJPyY=",
|
||||
"lastModified": 1686862774,
|
||||
"narHash": "sha256-ojGtRQ9pIOUrxsQEuEPerUkqIJEuod9hIflfNkY+9CE=",
|
||||
"owner": "divnix",
|
||||
"repo": "data-merge",
|
||||
"rev": "d160d18ce7b1a45b88344aa3f13ed1163954b497",
|
||||
"repo": "dmerge",
|
||||
"rev": "9f7f7a8349d33d7bd02e0f2b484b1f076e503a96",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "data-merge",
|
||||
"ref": "0.2.1",
|
||||
"repo": "dmerge",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1677306201,
|
||||
"narHash": "sha256-VZ9x7qdTosFvVsrpgFHrtYfT6PU3yMIs7NRYn9ELapI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "0923f0c162f65ae40261ec940406049726cfeab4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -80,19 +162,78 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"mdbook-kroki-preprocessor": {
|
||||
"flake": false,
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1661755005,
|
||||
"narHash": "sha256-1TJuUzfyMycWlOQH67LR63/ll2GDZz25I3JfScy/Jnw=",
|
||||
"owner": "JoelCourtney",
|
||||
"repo": "mdbook-kroki-preprocessor",
|
||||
"rev": "93adb5716d035829efed27f65f2f0833a7d3e76f",
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "JoelCourtney",
|
||||
"repo": "mdbook-kroki-preprocessor",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"locked": {
|
||||
"lastModified": 1653893745,
|
||||
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"haumea": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"dmerge",
|
||||
"nixlib"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685133229,
|
||||
"narHash": "sha256-FePm/Gi9PBSNwiDFq3N+DWdfxFq0UKsVVTJS3cQPn94=",
|
||||
"owner": "nix-community",
|
||||
"repo": "haumea",
|
||||
"rev": "34dd58385092a23018748b50f9b23de6266dffc2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "v0.2.2",
|
||||
"repo": "haumea",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"incl": {
|
||||
"inputs": {
|
||||
"nixlib": [
|
||||
"std",
|
||||
"dmerge",
|
||||
"nixlib"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1693483555,
|
||||
"narHash": "sha256-Beq4WhSeH3jRTZgC1XopTSU10yLpK1nmMcnGoXO0XYo=",
|
||||
"owner": "divnix",
|
||||
"repo": "incl",
|
||||
"rev": "526751ad3d1e23b07944b14e3f6b7a5948d3007b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "incl",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -150,13 +291,55 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixago_2": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixago-exts": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1687381756,
|
||||
"narHash": "sha256-IUMIlYfrvj7Yli4H2vvyig8HEPpfCeMaE6+kBGPzFyk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixago",
|
||||
"rev": "dacceb10cace103b3e66552ec9719fa0d33c0dc9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixago",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixlib": {
|
||||
"locked": {
|
||||
"lastModified": 1719708727,
|
||||
"narHash": "sha256-XFNKtyirrGNdehpg7lMNm1skEcBApjqGhaHc/OI95HY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "1bba8a624b3b9d4f68db94fb63aaeb46039ce9e6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1665087388,
|
||||
"narHash": "sha256-FZFPuW9NWHJteATOf79rZfwfRn5fE0wi9kRzvGfDHPA=",
|
||||
"lastModified": 1720181791,
|
||||
"narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "95fda953f6db2e9496d2682c4fc7b82f959878f7",
|
||||
"rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -166,55 +349,234 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1677063315,
|
||||
"narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "988cc958c57ce4350ec248d2d53087777f9e1949",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nosys": {
|
||||
"locked": {
|
||||
"lastModified": 1668010795,
|
||||
"narHash": "sha256-JBDVBnos8g0toU7EhIIqQ1If5m/nyBqtHhL3sicdPwI=",
|
||||
"owner": "divnix",
|
||||
"repo": "nosys",
|
||||
"rev": "feade0141487801c71ff55623b421ed535dbdefa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"repo": "nosys",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"paisano": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nosys": "nosys",
|
||||
"yants": [
|
||||
"std",
|
||||
"yants"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1686862844,
|
||||
"narHash": "sha256-m8l/HpRBJnZ3c0F1u0IyQ3nYGWE0R9V5kfORuqZPzgk=",
|
||||
"owner": "paisano-nix",
|
||||
"repo": "core",
|
||||
"rev": "6674b3d3577212c1eeecd30d62d52edbd000e726",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "paisano-nix",
|
||||
"ref": "0.1.1",
|
||||
"repo": "core",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"paisano-mdbook-preprocessor": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"devshell": "devshell_2",
|
||||
"fenix": "fenix",
|
||||
"nixago": "nixago_2",
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"nixpkgs"
|
||||
],
|
||||
"std": [
|
||||
"std"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694819355,
|
||||
"narHash": "sha256-rUPXjsUNUjpd3c+2M0KtbMHrlZ1sDyAY6ASJZNk0TQE=",
|
||||
"owner": "paisano-nix",
|
||||
"repo": "mdbook-paisano-preprocessor",
|
||||
"rev": "f84fc450aaf4824b4d64d957349553c0f8acc243",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "paisano-nix",
|
||||
"repo": "mdbook-paisano-preprocessor",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"paisano-tui": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"std": [
|
||||
"std"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681847764,
|
||||
"narHash": "sha256-mdd7PJW1BZvxy0cIKsPfAO+ohVl/V7heE5ZTAHzTdv8=",
|
||||
"owner": "paisano-nix",
|
||||
"repo": "tui",
|
||||
"rev": "3096bad91cae73ab8ab3367d31f8a143d248a244",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "paisano-nix",
|
||||
"ref": "0.1.1",
|
||||
"repo": "tui",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"std": "std"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1677221702,
|
||||
"narHash": "sha256-1M+58rC4eTCWNmmX0hQVZP20t3tfYNunl9D/PrGUyGE=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "f5401f620699b26ed9d47a1d2e838143a18dbe3b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor",
|
||||
"crane",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"paisano-mdbook-preprocessor",
|
||||
"crane",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1675391458,
|
||||
"narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"std": {
|
||||
"inputs": {
|
||||
"arion": [
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"blank": "blank",
|
||||
"devshell": "devshell",
|
||||
"dmerge": "dmerge",
|
||||
"flake-utils": "flake-utils",
|
||||
"incl": "incl",
|
||||
"makes": [
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"mdbook-kroki-preprocessor": "mdbook-kroki-preprocessor",
|
||||
"microvm": [
|
||||
"std",
|
||||
"blank"
|
||||
],
|
||||
"n2c": "n2c",
|
||||
"nixago": "nixago",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"paisano": "paisano",
|
||||
"paisano-mdbook-preprocessor": "paisano-mdbook-preprocessor",
|
||||
"paisano-tui": "paisano-tui",
|
||||
"yants": "yants"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1666470773,
|
||||
"narHash": "sha256-ev+qGyXfF0sLeCusPNa743YIV8yP7+PQUF0YwFgucOM=",
|
||||
"lastModified": 1686877329,
|
||||
"narHash": "sha256-A/SU8KqlLP2MBuhi9wmt6gDhXyp+chCeDZ4OBxfSWBI=",
|
||||
"owner": "divnix",
|
||||
"repo": "std",
|
||||
"rev": "b32d94cbd78fc212c9a47c0f0f8e68987bc54595",
|
||||
"rev": "aa6d423b82b7b7c2a4545693dea9ed1db14676e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "divnix",
|
||||
"ref": "nixpkgs-config",
|
||||
"ref": "v0.23.2",
|
||||
"repo": "std",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"yants": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"std",
|
||||
"nixpkgs"
|
||||
"dmerge",
|
||||
"nixlib"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
inputs.std.url = "github:divnix/std/nixpkgs-config";
|
||||
inputs.nixpkgs.follows = "std/nixpkgs";
|
||||
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
inputs.std.url = "github:divnix/std/v0.23.2";
|
||||
inputs.std.inputs.nixpkgs.follows = "nixpkgs";
|
||||
outputs = inputs @ {std, ...}:
|
||||
std.growOn {
|
||||
inherit inputs;
|
||||
|
||||
4
markdoc.config.mjs
Normal file
4
markdoc.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
import { defineMarkdocConfig } from '@astrojs/markdoc/config'
|
||||
import { config as markdocConfig } from './src/utils/mdoc/mdoc.config'
|
||||
|
||||
export default defineMarkdocConfig(markdocConfig)
|
||||
@@ -3,11 +3,11 @@
|
||||
cell,
|
||||
}: let
|
||||
inherit (inputs.nixpkgs) pkgs;
|
||||
inherit (inputs.std) std;
|
||||
inherit (inputs) std;
|
||||
in {
|
||||
default = std.lib.mkShell {
|
||||
default = std.lib.dev.mkShell {
|
||||
name = "nrd.sh";
|
||||
imports = [std.devshellProfiles.default];
|
||||
imports = [std.std.devshellProfiles.default];
|
||||
commands = [
|
||||
{package = cell.packages.astro;}
|
||||
{package = pkgs.nodejs_latest;}
|
||||
|
||||
12083
package-lock.json
generated
Normal file
12083
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
package.json
57
package.json
@@ -2,32 +2,53 @@
|
||||
"name": "astro-ink",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"check": "astro check",
|
||||
"postbuild": "node ./scripts/search/prepare-index.js",
|
||||
"preview": "astro preview"
|
||||
"preview": "astro preview",
|
||||
"check:type": "tsc --project tsconfig.json --pretty --noEmit",
|
||||
"lint": "biome lint src",
|
||||
"format": "biome format src --write",
|
||||
"cz": "cz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/mdx": "^0.11.1",
|
||||
"@astrojs/rss": "^1.0.0",
|
||||
"@astrojs/sitemap": "^1.0.0",
|
||||
"@astrojs/svelte": "^1.0.0",
|
||||
"@astrojs/tailwind": "^1.0.0",
|
||||
"@fontsource/fira-sans": "^4.5.9",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.0",
|
||||
"@tailwindcss/forms": "^0.5.0",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@types/node": "^18.7.14",
|
||||
"astro": "1.1.5",
|
||||
"globby": "^13.1.2",
|
||||
"@astrojs/mdx": "^2.0.0",
|
||||
"@astrojs/rss": "^4.0.1",
|
||||
"@astrojs/sitemap": "^3.0.3",
|
||||
"@astrojs/svelte": "^5.0.0",
|
||||
"@astrojs/tailwind": "^5.0.3",
|
||||
"@biomejs/biome": "1.4.1",
|
||||
"@fontsource/fira-sans": "^5.0.18",
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/node": "^18.19.3",
|
||||
"astro": "^4.0.3",
|
||||
"astro-icon": "^0.8.2",
|
||||
"commitizen": "^4.3.0",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"globby": "^14.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"lunr": "^2.3.9",
|
||||
"mdx": "^0.2.3",
|
||||
"svelte": "^3.50.0",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"typescript": "^4.3.5"
|
||||
"mdx": "^0.3.1",
|
||||
"remark-code-titles": "^0.1.2",
|
||||
"svelte": "^4.2.8",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/markdoc": "^0.8.0",
|
||||
"@astrojs/vercel": "^6.0.1",
|
||||
"@libsql/client": "^0.4.0-pre.5",
|
||||
"astro-decap-cms-oauth": "^0.2.9",
|
||||
"ioredis": "^5.3.2"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,8 +7,8 @@ import grayMatter from 'gray-matter'
|
||||
// prepare the dirs
|
||||
const srcDir = path.join(process.cwd(), 'src')
|
||||
const publicDir = path.join(process.cwd(), 'public')
|
||||
const contentDir = path.join(srcDir, 'pages', 'blog')
|
||||
const contentFilePattern = path.join(contentDir, '*.md')
|
||||
const contentBlogDir = path.join(srcDir, 'content', 'blog')
|
||||
const contentFilePattern = path.join(contentBlogDir, '*.md')
|
||||
const indexFile = path.join(publicDir, 'search-index.json')
|
||||
const getSlugFromPathname = (pathname) => path.basename(pathname, path.extname(pathname))
|
||||
|
||||
@@ -31,7 +31,7 @@ import grayMatter from 'gray-matter'
|
||||
i++
|
||||
}
|
||||
await fs.writeFile(indexFile, JSON.stringify(index))
|
||||
console.log(`Indexed ${index.length} documents from ${contentDir} to ${indexFile}`)
|
||||
console.log(`Indexed ${index.length} documents from ${contentBlogDir} to ${indexFile}`)
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,65 +1,74 @@
|
||||
---
|
||||
import '@fontsource/fira-sans'
|
||||
import { SITE } from '$/config'
|
||||
import '../styles/global.css'
|
||||
import "@fontsource/fira-sans";
|
||||
import { ViewTransitions } from 'astro:transitions';
|
||||
import { SITE } from "$/config";
|
||||
import "../styles/global.css";
|
||||
|
||||
export type Props = {
|
||||
title: string
|
||||
description: string
|
||||
permalink: string
|
||||
image: string
|
||||
}
|
||||
export type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
permalink: string;
|
||||
image: string;
|
||||
};
|
||||
|
||||
const { title = SITE.title , description, permalink, image } = Astro.props as Props
|
||||
const { title = SITE.title, description, permalink, image } = Astro.props;
|
||||
---
|
||||
<!-- Use Google Fonts, if you don't wanna prefer a self-hosted version -->
|
||||
<!-- <link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
|
||||
<!-- Use Google Fonts, if you don't wanna prefer a self-hosted version --><!-- <link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap" rel="stylesheet"> -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title}/>
|
||||
{description &&
|
||||
<meta name="description" content={description}/>
|
||||
}
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
{description && <meta name="description" content={description} />}
|
||||
<ViewTransitions />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="shortcut icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml"/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<!-- Open Graph Tags (Facebook) -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={title} />
|
||||
{permalink &&
|
||||
<meta property="og:url" content={permalink} />
|
||||
}
|
||||
{description &&
|
||||
<meta property="og:description" content={description} />
|
||||
}
|
||||
{image &&
|
||||
<meta property="og:image" content={image} />
|
||||
}
|
||||
<!-- Open Graph Tags (Facebook) -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={title} />
|
||||
{permalink && <meta property="og:url" content={permalink} />}
|
||||
{description && <meta property="og:description" content={description} />}
|
||||
{image && <meta property="og:image" content={image} />}
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content={title} />
|
||||
{permalink &&
|
||||
<meta property="twitter:url" content={permalink} />
|
||||
}
|
||||
{description &&
|
||||
<meta property="twitter:description" content={description} />
|
||||
}
|
||||
{image &&
|
||||
<meta property="twitter:image" content={image} />
|
||||
}
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:title" content={title} />
|
||||
{permalink && <meta property="twitter:url" content={permalink} />}
|
||||
{description && <meta property="twitter:description" content={description} />}
|
||||
{image && <meta property="twitter:image" content={image} />}
|
||||
|
||||
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script is:inline>
|
||||
const theme = (() => {
|
||||
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
|
||||
return localStorage.getItem("theme");
|
||||
}
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark";
|
||||
}
|
||||
return "light";
|
||||
})();
|
||||
|
||||
if (theme === "light") {
|
||||
document.documentElement.classList.remove("dark");
|
||||
} else {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<body class="font-sans antialiased min-h-screen">
|
||||
<div class="bg-gray-100 dark:bg-gray-800 transition-colors">
|
||||
<body class="font-sans antialiased min-h-screen bg-gray-100 dark:bg-gray-800">
|
||||
<svg class="absolute w-full fill-theme-primary dark:fill-theme-dark-primary opacity-10 -z-10" viewBox="0 0 960 540" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><path d="M0 81L26.7 86.5C53.3 92 106.7 103 160 99.3C213.3 95.7 266.7 77.3 320 66.3C373.3 55.3 426.7 51.7 480 49.7C533.3 47.7 586.7 47.3 640 45.2C693.3 43 746.7 39 800 51C853.3 63 906.7 91 933.3 105L960 119L960 0L933.3 0C906.7 0 853.3 0 800 0C746.7 0 693.3 0 640 0C586.7 0 533.3 0 480 0C426.7 0 373.3 0 320 0C266.7 0 213.3 0 160 0C106.7 0 53.3 0 26.7 0L0 0Z" stroke-linecap="round" stroke-linejoin="miter"></path></svg>
|
||||
<div class="transition-colors">
|
||||
<main class="mx-auto max-w-4xl px-4 md:px-0">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
31
src/components/Callout.astro
Normal file
31
src/components/Callout.astro
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon'
|
||||
export type CalloutType = 'check' | 'error' | 'note' | 'warning'
|
||||
interface Props {
|
||||
title: string
|
||||
type: CalloutType
|
||||
}
|
||||
|
||||
const ICON_MAP: Record<CalloutType, string> = {
|
||||
'check': 'check-circle',
|
||||
'error': 'close-circle',
|
||||
'note': 'note',
|
||||
'warning': 'warning-circle'
|
||||
}
|
||||
|
||||
const COLOR_MAP: Record<CalloutType, string> = {
|
||||
'check': 'text-green-700',
|
||||
'error': 'text-red-700',
|
||||
'note': ' text-gray-700',
|
||||
'warning': 'text-orange-700'
|
||||
}
|
||||
|
||||
const { title, type = 'note' } = Astro.props
|
||||
---
|
||||
<div class="callout flex gap-2 w-full bg-gray-50 my-1 px-5 py-2 rounded-sm shadow-sm">
|
||||
<Icon class={`w-8 h-8 inline-block ${COLOR_MAP[type]}`} pack="mdi" name={ICON_MAP[type]} />
|
||||
<div class="copy flex flex-col">
|
||||
<h3 class={`title m-0 ${COLOR_MAP[type]}`}>{title}</h3>
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
7
src/components/Code.astro
Normal file
7
src/components/Code.astro
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
import CodeCopy from './CodeCopy.svelte'
|
||||
---
|
||||
<CodeCopy client:load/>
|
||||
<code class="astro-ink__code">
|
||||
<slot/>
|
||||
</code>
|
||||
50
src/components/CodeCopy.svelte
Normal file
50
src/components/CodeCopy.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { fade, blur } from "svelte/transition";
|
||||
import { onMount } from "svelte";
|
||||
const COPIED_TIMEOUT = 2 * 1000
|
||||
|
||||
export let stayCopied = COPIED_TIMEOUT
|
||||
|
||||
let copied = false
|
||||
let selfElm: HTMLButtonElement;
|
||||
let isCodeBlock = true;
|
||||
|
||||
const copy = async () => {
|
||||
if(selfElm) {
|
||||
const preElm = selfElm.parentElement?.parentElement
|
||||
const codeElm = preElm?.querySelector('code')
|
||||
if(preElm?.tagName === 'PRE' && codeElm) {
|
||||
await navigator.clipboard.writeText(codeElm.innerText);
|
||||
copied = true
|
||||
|
||||
setTimeout(() => {
|
||||
copied = false
|
||||
}, stayCopied);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const preElm = selfElm.parentElement?.parentElement
|
||||
if(preElm && preElm.tagName === 'PRE') {
|
||||
isCodeBlock = true
|
||||
} else {
|
||||
isCodeBlock = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<button
|
||||
bind:this={selfElm}
|
||||
on:click={copy}
|
||||
class="absolute px-2 text-theme-primary dark:text-theme-dark-primary border-1 rounded-lg"
|
||||
style="top: 6px; right: 8px;"
|
||||
style:display={isCodeBlock ? 'inline-block' : 'none'}
|
||||
disabled={copied}
|
||||
>
|
||||
{#if copied}
|
||||
<span transition:blur={{ amount: 50, opacity: 50 }}>✓Copied</span>
|
||||
{:else}
|
||||
<span transition:fade>Copy</span>
|
||||
{/if}
|
||||
</button>
|
||||
12
src/components/DateTime.astro
Normal file
12
src/components/DateTime.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
interface Props {
|
||||
value: Date;
|
||||
title?: string;
|
||||
lang?: Intl.LocalesArgument,
|
||||
dateStyle?: "full" | "long" | "medium" | "short"
|
||||
}
|
||||
const { value, title, lang = 'en-US', dateStyle = 'full' } = Astro.props
|
||||
---
|
||||
<time title={title} datetime={value.toISOString()}>
|
||||
{value.toLocaleDateString(lang, { dateStyle, timeZone: 'UTC' })}
|
||||
</time>
|
||||
19
src/components/EditLink.astro
Normal file
19
src/components/EditLink.astro
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon'
|
||||
|
||||
interface Props {
|
||||
label?: string,
|
||||
editUrl: string
|
||||
}
|
||||
|
||||
const { editUrl, label = 'Edit this page' } = Astro.props
|
||||
|
||||
---
|
||||
{
|
||||
editUrl && (
|
||||
<a href={editUrl} title={label} class=" font-thin text-theme-primary dark:text-theme-dark-primary text-sm">
|
||||
<Icon class="w-4 h-4 inline-block" pack="mdi" name={'pencil'} />
|
||||
{label}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
import { SITE } from '$/config'
|
||||
import ModeLabel from './ModeLabel.svelte'
|
||||
import NetlifyIdentity from './NetlifyIdentity.svelte'
|
||||
---
|
||||
<footer class="footer">
|
||||
<nav class="nav">
|
||||
<div>2021 © Copyright notice | <a href={ SITE.githubUrl } title={`${ SITE.ghUser }'s Github URL'`}>{ SITE.name }</a>
|
||||
<ModeLabel client:load/> built with <a href="https://astro.build/">Astro</a> | <a href=https://github.com/nrdxp/nrd.sh title=source-code>src</a></div>
|
||||
<NetlifyIdentity client:load/>
|
||||
<div>2021 © Copyright notice | <a href={ SITE.githubUrl } title={`${ SITE.name }'s Github URL'`}>{ SITE.name }</a>
|
||||
<ModeLabel client:load/> theme on <a href="https://astro.build/">Astro</a></div>
|
||||
</nav>
|
||||
</footer>
|
||||
<style>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<div class="header__meta flex-1">
|
||||
<h3 class="header__title dark:text-theme-dark-secondary">
|
||||
<a href="">{ SITE.name }</a>
|
||||
<a href="/">{ SITE.name }</a>
|
||||
</h3>
|
||||
<div class="header__meta-more flex">
|
||||
<p class="header__desc">
|
||||
@@ -26,7 +26,7 @@
|
||||
<SearchBtn client:visible />
|
||||
</li>
|
||||
<li>
|
||||
<a href={ SITE.githubUrl } title={`${ SITE.ghUser }'s Github URL'`}>
|
||||
<a href={ SITE.githubUrl } title={`${ SITE.name }'s Github URL'`}>
|
||||
<SvgIcon>
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
||||
</SvgIcon>
|
||||
@@ -55,7 +55,7 @@
|
||||
@apply flex gap-4 border-b py-3 /* border-gray-200 dark:border-gray-700 - check styles/global.css */
|
||||
}
|
||||
.header__logo-img {
|
||||
@apply w-24 h-24 rounded-full overflow-hidden
|
||||
@apply w-16 h-16 rounded-full overflow-hidden
|
||||
}
|
||||
.header__title {
|
||||
@apply text-4xl font-extrabold md:text-5xl text-theme-secondary dark:text-theme-dark-secondary
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import { getMonthName } from '$/utils'
|
||||
import { USE_MEDIA_THUMBNAIL } from '$/config'
|
||||
const { post } = Astro.props
|
||||
---
|
||||
<div class="post-preview">
|
||||
@@ -9,15 +10,19 @@ const { post } = Astro.props
|
||||
<span class="post-preview__date__month-n-year">{ `${getMonthName(post.date)} ${new Date(post.date).getFullYear()}` }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class={`flex-1 ${USE_MEDIA_THUMBNAIL && post.thumbnail ? 'flex flex-row gap-4' : ''}`}>
|
||||
{ USE_MEDIA_THUMBNAIL && post.thumbnail && <img class="post-preview__media" src= {post.thumbnail} alt="media thumbnail" />}
|
||||
<div class="flex flex-col mb-2">
|
||||
<h4 class="post-preview__title">
|
||||
<h4 class="post-preview__title dark:text-theme-dark-primary">
|
||||
<a href={post.url} title={post.title} target="_blank">{post.title}</a>
|
||||
</h4>
|
||||
<div>
|
||||
<strong>{post.host}</strong>
|
||||
{
|
||||
post.participants.length > 0 && ` <em>with</em> ${post.participants.join(', ')}`
|
||||
post.participants.length > 0 && <em>with</em>
|
||||
}
|
||||
{
|
||||
post.participants.length > 0 && `${post.participants.join(', ')}`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,9 +45,12 @@ const { post } = Astro.props
|
||||
@apply text-gray-400
|
||||
}
|
||||
.post-preview__title {
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary /* this doesn't works here */ hover:underline
|
||||
}
|
||||
.post-preview__desc {
|
||||
@apply text-lg leading-6 dark:text-white line-clamp-2
|
||||
@apply text-lg leading-6 dark:text-white line-clamp-2 hyphens-auto
|
||||
}
|
||||
.post-preview__media {
|
||||
@apply w-48 rounded-md shadow-lg shadow-theme-accent-gray-light dark:shadow-theme-accent-gray-dark
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
|
||||
function toggleTheme() {
|
||||
window.document.body.classList.toggle(THEME_DARK)
|
||||
window.document.documentElement.classList.toggle(THEME_DARK)
|
||||
currTheme = localStorage.getItem('theme') === THEME_DARK ? THEME_LIGHT : THEME_DARK
|
||||
// Update Storage
|
||||
localStorage.setItem('theme', currTheme)
|
||||
@@ -20,10 +20,10 @@
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem('theme') === THEME_DARK || (!('theme' in localStorage) && window.matchMedia(`(prefers-color-scheme: ${THEME_DARK})`).matches)) {
|
||||
window.document.body.classList.add(THEME_DARK)
|
||||
window.document.documentElement.classList.add(THEME_DARK)
|
||||
currTheme = THEME_DARK
|
||||
} else {
|
||||
window.document.body.classList.remove(THEME_DARK)
|
||||
window.document.documentElement.classList.remove(THEME_DARK)
|
||||
currTheme = THEME_LIGHT
|
||||
}
|
||||
// Update Store
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { draw } from 'svelte/transition';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import ModeSwitcher from './ModeSwitcher.svelte'
|
||||
import SvgIcon from './SvgIcon.svelte'
|
||||
</script>
|
||||
<ModeSwitcher let:theme>
|
||||
<SvgIcon>
|
||||
{#if theme === 'dark'}
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
<circle cx="12" cy="12" r="5" transition:draw={{ duration: 1000, delay: 200, easing: quintOut }}></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3" transition:draw={{ duration: 100, delay: 30, easing: quintOut }}></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23" transition:draw={{ duration: 100, delay: 40, easing: quintOut }}></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" transition:draw={{ duration: 100, delay: 50, easing: quintOut }}></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" transition:draw={{ duration: 100, delay: 60, easing: quintOut }}></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12" transition:draw={{ duration: 100, delay: 70, easing: quintOut }}></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12" transition:draw={{ duration: 100, delay: 80, easing: quintOut }}></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" transition:draw={{ duration: 100, delay: 90, easing: quintOut }}></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" transition:draw={{ duration: 100, delay: 100, easing: quintOut }}></line>
|
||||
{:else}
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
|
||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" transition:draw={{ duration: 500, delay: 100, easing: quintOut }}></path>
|
||||
{/if}
|
||||
</SvgIcon>
|
||||
</ModeSwitcher>
|
||||
|
||||
@@ -3,16 +3,20 @@ import { toTitleCase } from '$/utils'
|
||||
import { NAV_ITEMS } from '$/config'
|
||||
---
|
||||
<nav class="nav py-3">
|
||||
<ul class="nav-list dark:text-theme-dark-secondary">
|
||||
<ul class="nav-list dark:text-theme-dark-secondary" transition:animate="fade">
|
||||
{
|
||||
Object.keys(NAV_ITEMS).map(navItemKey => <li>
|
||||
<a class="hover:underline" href={NAV_ITEMS[navItemKey].path} title={NAV_ITEMS[navItemKey].title}>{toTitleCase(NAV_ITEMS[navItemKey].title)}</a>
|
||||
<a class:list={[
|
||||
`pb-1 border-b-2 hover:border-gray-400 hover:dark:border-gray-700 `,
|
||||
Astro.url.pathname !== NAV_ITEMS[navItemKey].path ? 'border-gray-100 dark:border-gray-800': '',
|
||||
Astro.url.pathname === NAV_ITEMS[navItemKey].path ? 'border-theme-primary' : ''
|
||||
]} href={NAV_ITEMS[navItemKey].path} title={NAV_ITEMS[navItemKey].title}>{toTitleCase(NAV_ITEMS[navItemKey].title)}</a>
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
<style>
|
||||
.nav-list {
|
||||
@apply inline-flex list-none gap-8 text-xl font-semibold text-theme-secondary dark:text-theme-dark-secondary py-2 flex-wrap
|
||||
@apply inline-flex list-none gap-8 text-xl font-semibold text-theme-secondary dark:text-theme-dark-secondary py-2 flex-wrap mb-8
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount} from 'svelte'
|
||||
|
||||
onMount(() => {
|
||||
if (window.netlifyIdentity) {
|
||||
window.netlifyIdentity.on('init', (user) => {
|
||||
if (!user) {
|
||||
window.netlifyIdentity.on('login', () => {
|
||||
document.location.href = '/admin/';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -10,7 +10,7 @@ const { frontmatter: post, file } = Astro.props.post
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="post-draft-preview__title">
|
||||
<h4 class="post-draft-preview__title dark:text-theme-dark-primary">
|
||||
<a href={`/drafts/${getSlugFromPathname(file)}`} title={post.title}>{post.title}</a>
|
||||
</h4>
|
||||
<p class="post-draft-preview__desc">
|
||||
@@ -32,9 +32,9 @@ const { frontmatter: post, file } = Astro.props.post
|
||||
@apply text-gray-400
|
||||
}
|
||||
.post-draft-preview__title {
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline mb-2
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary /* this doesn't works here */ hover:underline mb-2
|
||||
}
|
||||
.post-draft-preview__desc {
|
||||
@apply text-lg leading-6 dark:text-white line-clamp-2
|
||||
@apply text-lg leading-6 dark:text-white line-clamp-2 hyphens-auto
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import { getMonthName } from '$/utils'
|
||||
const { frontmatter: post, url } = Astro.props.post
|
||||
|
||||
interface Props {
|
||||
post: CollectionEntry<'blog'>,
|
||||
asCard?: boolean
|
||||
}
|
||||
|
||||
const { post: { data: post, slug }, asCard = false } = Astro.props
|
||||
---
|
||||
<div class="post-preview">
|
||||
<div class="sm:w-20 md:w-32">
|
||||
<div class={`post-preview ${asCard && 'post-preview--card'}`}>
|
||||
<div class="post-preview__date-box">
|
||||
<div class="post-preview__date">
|
||||
<span class="post-preview__date__day">{ new Date(post.date).getDate() }</span>
|
||||
<span class="post-preview__date__month-n-year">{ `${getMonthName(post.date)} ${new Date(post.date).getFullYear()}` }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="post-preview__title">
|
||||
<a href={url} title={post.title}>{post.title}</a>
|
||||
<h4 class="post-preview__title dark:text-theme-dark-primary">
|
||||
<a href={`/blog/${slug}`} title={post.title}>{post.title}</a>
|
||||
</h4>
|
||||
<p class="post-preview__desc">
|
||||
{post.description}
|
||||
@@ -20,21 +27,36 @@ const { frontmatter: post, url } = Astro.props.post
|
||||
</div>
|
||||
<style>
|
||||
.post-preview {
|
||||
@apply flex gap-6;
|
||||
@apply flex gap-6;
|
||||
}
|
||||
.post-preview--card {
|
||||
@apply flex flex-col-reverse gap-4 sm:w-72 md:w-60 lg:w-64;
|
||||
}
|
||||
.post-preview__date-box {
|
||||
@apply sm:w-20 md:w-32
|
||||
}
|
||||
.post-preview--card .post-preview__date-box {
|
||||
@apply w-full
|
||||
}
|
||||
.post-preview__date {
|
||||
@apply flex flex-col w-full text-center;
|
||||
}
|
||||
.post-preview--card .post-preview__date {
|
||||
@apply text-left flex flex-row gap-1
|
||||
}
|
||||
.post-preview__date__day {
|
||||
@apply text-6xl font-semibold text-gray-500 dark:text-gray-300;
|
||||
}
|
||||
.post-preview--card .post-preview__date__day {
|
||||
@apply text-4xl
|
||||
}
|
||||
.post-preview__date__month-n-year {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
.post-preview__title {
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary hover:underline mb-2;
|
||||
@apply text-2xl font-semibold text-theme-primary dark:text-theme-dark-primary /* this doesn't works here */ hover:underline mb-2;
|
||||
}
|
||||
.post-preview__desc {
|
||||
@apply text-lg leading-6 line-clamp-2 dark:text-white;
|
||||
@apply text-lg leading-6 line-clamp-2 dark:text-white hyphens-auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import PostPreview from './PostPreview.astro'
|
||||
const { posts } = Astro.props
|
||||
const sortedPosts = posts.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
|
||||
interface Props {
|
||||
posts: CollectionEntry<'blog'>
|
||||
heading?: string
|
||||
mode?: 'row' | 'col'
|
||||
}
|
||||
|
||||
const { posts, heading, mode = 'col' } = Astro.props
|
||||
const sortedPosts = posts.sort((a, b) => new Date(b.date).valueOf() - new Date(a.date).valueOf());
|
||||
---
|
||||
<section class="post-preview__list">
|
||||
{ heading ? <h5 class={`post-preview__heading post-preview__heading--${mode} ink-h`}>{heading}</h5> : ''}
|
||||
<section class={`post-preview__list post-preview__list--${mode}`}>
|
||||
{sortedPosts.map((post) => (
|
||||
<PostPreview post={post}/>
|
||||
<PostPreview post={post} asCard={mode === 'row' ? true : false }/>
|
||||
))}
|
||||
</section>
|
||||
<style>
|
||||
.post-preview__heading {
|
||||
@apply pl-0 sm:pl-6
|
||||
}
|
||||
.post-preview__heading--row {
|
||||
@apply pl-0
|
||||
}
|
||||
.post-preview__list {
|
||||
@apply flex flex-col gap-12
|
||||
}
|
||||
.post-preview__list--row {
|
||||
@apply flex-row flex-wrap md:px-6 lg:px-0 gap-12 md:gap-8 sm:gap-10 lg:gap-12
|
||||
}
|
||||
.post-preview__list--col {
|
||||
@apply flex-col gap-12
|
||||
}
|
||||
</style>
|
||||
|
||||
31
src/components/PostStats.svelte
Normal file
31
src/components/PostStats.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
|
||||
export let slug: string = ''
|
||||
|
||||
let loading: boolean = false
|
||||
let views: number = 0
|
||||
|
||||
onMount(async () => {
|
||||
if(slug && slug.trim() !== '') {
|
||||
try {
|
||||
loading = true
|
||||
const resp = await fetch(`/api/blog/views/${slug}.json`)
|
||||
const stats = await resp.json()
|
||||
views = stats.views
|
||||
} catch(e) {
|
||||
console.error('PostStats', e)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<span class="post-stats__views">{ views } views</span>
|
||||
<style>
|
||||
.post-stats__views {
|
||||
@apply px-1 mx-1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
</script>
|
||||
{#if $isSearchVisible}
|
||||
<div class="modal__backdrop" on:click={dismissModal} on:keydown={handleEsc} transition:fade></div>
|
||||
<div class="modal">
|
||||
<div class="modal__backdrop" role="button" tabindex="0" on:click={dismissModal} on:keydown={handleEsc} transition:fade></div>
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal__cnt" transition:fly="{{ y: 200, duration: 300 }}">
|
||||
<Search />
|
||||
</div>
|
||||
|
||||
51
src/components/mdoc/Badge.astro
Normal file
51
src/components/mdoc/Badge.astro
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
export type BadgeType = 'success' | 'danger' | 'note' | 'warning' | 'tip'
|
||||
|
||||
interface Props {
|
||||
text: string
|
||||
type: BadgeType
|
||||
outline?: boolean
|
||||
}
|
||||
|
||||
const { text, type = 'note', outline = false } = Astro.props
|
||||
---
|
||||
<span class:list={['ink-badge', `ink-badge__${type}${outline && '--outline'}`, `ink-badge__${type}`]} set:html={text} />
|
||||
|
||||
<style>
|
||||
.ink-badge {
|
||||
@apply inline-block font-xs font-normal text-inherit bg-theme-primary dark:bg-theme-dark-primary px-3 py-1 border-1 rounded-b-md
|
||||
}
|
||||
.ink-badge--outline {
|
||||
@apply bg-transparent
|
||||
}
|
||||
.ink-badge__success {
|
||||
@apply bg-green-300
|
||||
}
|
||||
.ink-badge__danger {
|
||||
@apply bg-red-300
|
||||
}
|
||||
.ink-badge__note {
|
||||
@apply bg-gray-300
|
||||
}
|
||||
.ink-badge__warning {
|
||||
@apply bg-orange-300
|
||||
}
|
||||
.ink-badge__tip {
|
||||
@apply bg-yellow-300
|
||||
}
|
||||
.ink-badge--outline.ink-badge__success {
|
||||
@apply border-green-300
|
||||
}
|
||||
.ink-badge--outline.ink-badge__danger {
|
||||
@apply border-red-300
|
||||
}
|
||||
.ink-badge--outline.ink-badge__note {
|
||||
@apply border-gray-300
|
||||
}
|
||||
.ink-badge--outline.ink-badge__warning {
|
||||
@apply border-orange-300
|
||||
}
|
||||
.ink-badge--outline.ink-badge__tip {
|
||||
@apply border-yellow-300
|
||||
}
|
||||
</style>
|
||||
31
src/components/mdoc/Callout.astro
Normal file
31
src/components/mdoc/Callout.astro
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon'
|
||||
export type CalloutType = 'check' | 'error' | 'note' | 'warning'
|
||||
interface Props {
|
||||
title: string
|
||||
type: CalloutType
|
||||
}
|
||||
|
||||
const ICON_MAP: Record<CalloutType, string> = {
|
||||
'check': 'check-circle',
|
||||
'error': 'close-circle',
|
||||
'note': 'note',
|
||||
'warning': 'warning-circle'
|
||||
}
|
||||
|
||||
const COLOR_MAP: Record<CalloutType, string> = {
|
||||
'check': 'text-green-700',
|
||||
'error': 'text-red-700',
|
||||
'note': ' text-gray-700',
|
||||
'warning': 'text-orange-700'
|
||||
}
|
||||
|
||||
const { title, type = 'note' } = Astro.props
|
||||
---
|
||||
<div class="callout flex gap-2 w-full bg-gray-200/75 dark:bg-gray-600/75 p-4 rounded-sm shadow-sm">
|
||||
<Icon class={`w-6 h-6 inline-block ${COLOR_MAP[type]}`} pack="mdi" name={ICON_MAP[type]} />
|
||||
<div class="copy flex flex-col">
|
||||
<h4 class={`title m-0 ${COLOR_MAP[type]}`}>{title}</h4>
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
12
src/components/mdoc/Link.astro
Normal file
12
src/components/mdoc/Link.astro
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
interface Props {
|
||||
href: string
|
||||
title: string
|
||||
target: string
|
||||
}
|
||||
|
||||
const { href, title, target } = Astro.props
|
||||
---
|
||||
<a class="site-link" href={href} title={title} target={target}>
|
||||
<slot/>
|
||||
</a>
|
||||
23
src/components/mdoc/Tabs/Tab.svelte
Normal file
23
src/components/mdoc/Tabs/Tab.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
import { TABS } from './Tabs.svelte';
|
||||
|
||||
const tab = {};
|
||||
const { registerTab, selectTab, selectedTab } = getContext(TABS);
|
||||
|
||||
registerTab(tab);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
button {
|
||||
@apply bg-none border-b-2 border-solid border-white m-0 text-gray-400 px-4 py-1;
|
||||
}
|
||||
|
||||
.selected {
|
||||
@apply border-b-2 border-solid border-gray-700 text-gray-700;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class:selected="{$selectedTab === tab}" on:click="{() => selectTab(tab)}">
|
||||
<slot></slot>
|
||||
</button>
|
||||
9
src/components/mdoc/Tabs/TabList.svelte
Normal file
9
src/components/mdoc/Tabs/TabList.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="tab-list">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab-list {
|
||||
@apply border-b border-solid border-gray-500;
|
||||
}
|
||||
</style>
|
||||
13
src/components/mdoc/Tabs/TabPanel.svelte
Normal file
13
src/components/mdoc/Tabs/TabPanel.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
import { getContext } from 'svelte';
|
||||
import { TABS } from './Tabs.svelte';
|
||||
|
||||
const panel = {};
|
||||
const { registerPanel, selectedPanel } = getContext(TABS);
|
||||
|
||||
registerPanel(panel);
|
||||
</script>
|
||||
|
||||
{#if $selectedPanel === panel}
|
||||
<slot></slot>
|
||||
{/if}
|
||||
11
src/components/mdoc/Tabs/Tabs.astro
Normal file
11
src/components/mdoc/Tabs/Tabs.astro
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
import Tabs from './index.svelte'
|
||||
import type { TabItem } from './tabs'
|
||||
|
||||
interface Props {
|
||||
tabs: TabItem[]
|
||||
}
|
||||
|
||||
const { tabs } = Astro.props
|
||||
---
|
||||
<Tabs tabs={tabs} client:visible/>
|
||||
50
src/components/mdoc/Tabs/Tabs.svelte
Normal file
50
src/components/mdoc/Tabs/Tabs.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script context="module">
|
||||
export const TABS = {};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { setContext, onDestroy } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const tabs = [];
|
||||
const panels = [];
|
||||
const selectedTab = writable(null);
|
||||
const selectedPanel = writable(null);
|
||||
|
||||
setContext(TABS, {
|
||||
registerTab: tab => {
|
||||
tabs.push(tab);
|
||||
selectedTab.update(current => current || tab);
|
||||
|
||||
onDestroy(() => {
|
||||
const i = tabs.indexOf(tab);
|
||||
tabs.splice(i, 1);
|
||||
selectedTab.update(current => current === tab ? (tabs[i] || tabs[tabs.length - 1]) : current);
|
||||
});
|
||||
},
|
||||
|
||||
registerPanel: panel => {
|
||||
panels.push(panel);
|
||||
selectedPanel.update(current => current || panel);
|
||||
|
||||
onDestroy(() => {
|
||||
const i = panels.indexOf(panel);
|
||||
panels.splice(i, 1);
|
||||
selectedPanel.update(current => current === panel ? (panels[i] || panels[panels.length - 1]) : current);
|
||||
});
|
||||
},
|
||||
|
||||
selectTab: tab => {
|
||||
const i = tabs.indexOf(tab);
|
||||
selectedTab.set(tab);
|
||||
selectedPanel.set(panels[i]);
|
||||
},
|
||||
|
||||
selectedTab,
|
||||
selectedPanel
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="tabs">
|
||||
<slot></slot>
|
||||
</div>
|
||||
24
src/components/mdoc/Tabs/index.svelte
Normal file
24
src/components/mdoc/Tabs/index.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Tabs, TabList, TabPanel, Tab, type TabItem } from './tabs';
|
||||
export let tabs: TabItem[] = []
|
||||
</script>
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
{#each tabs as tab}
|
||||
<Tab>{tab.title}</Tab>
|
||||
{/each}
|
||||
</TabList>
|
||||
|
||||
{#each tabs as tab}
|
||||
<TabPanel>
|
||||
<div class="body">{tab.body}</div>
|
||||
</TabPanel>
|
||||
{/each}
|
||||
</Tabs>
|
||||
|
||||
<style>
|
||||
.body {
|
||||
@apply px-4 py-1;
|
||||
}
|
||||
</style>
|
||||
9
src/components/mdoc/Tabs/tabs.ts
Normal file
9
src/components/mdoc/Tabs/tabs.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as Tabs } from "./Tabs.svelte";
|
||||
export { default as TabList } from "./TabList.svelte";
|
||||
export { default as TabPanel } from "./TabPanel.svelte";
|
||||
export { default as Tab } from "./Tab.svelte";
|
||||
|
||||
export interface TabItem {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
27
src/components/mdoc/TweetEmbed.astro
Normal file
27
src/components/mdoc/TweetEmbed.astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
interface Props {
|
||||
url: string
|
||||
}
|
||||
|
||||
const { url } = Astro.props
|
||||
---
|
||||
<div
|
||||
class="twitter-embed flex flex-col items-center justify-center relative"
|
||||
>
|
||||
<blockquote
|
||||
class="twitter-tweet"
|
||||
data-conversation="none"
|
||||
data-theme="light"
|
||||
data-lang="en"
|
||||
data-dnt="true"
|
||||
>
|
||||
<a class="unset no-underline text-current absolute top-0 left-0" href={url}>Loading embedded tweet...</a>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<script
|
||||
async
|
||||
defer
|
||||
src="https://platform.twitter.com/widgets.js"
|
||||
charset="utf-8"
|
||||
></script>
|
||||
28
src/components/mdoc/YTVideoEmbed.astro
Normal file
28
src/components/mdoc/YTVideoEmbed.astro
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const { url, title } = Astro.props;
|
||||
---
|
||||
|
||||
<div>
|
||||
<iframe
|
||||
class="yt-iframe"
|
||||
width="560"
|
||||
height="315"
|
||||
src={url}
|
||||
title={title}
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
>
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.yt-iframe {
|
||||
@apply w-full aspect-[16/9]
|
||||
}
|
||||
</style>
|
||||
@@ -1,36 +1,58 @@
|
||||
import type{ NavItems } from './types'
|
||||
import type { NavItems } from "./types";
|
||||
|
||||
export const NAV_ITEMS: NavItems = {
|
||||
home: {
|
||||
path: '/',
|
||||
title: 'home'
|
||||
},
|
||||
blog: {
|
||||
path: '/blog',
|
||||
title: 'blog'
|
||||
},
|
||||
tags: {
|
||||
path: '/tags',
|
||||
title: 'tags'
|
||||
},
|
||||
media: {
|
||||
path: '/media',
|
||||
title: 'media'
|
||||
},
|
||||
about: {
|
||||
path: '/about',
|
||||
title: 'about'
|
||||
}
|
||||
}
|
||||
home: {
|
||||
path: "/",
|
||||
title: "home",
|
||||
},
|
||||
blog: {
|
||||
path: "/blog",
|
||||
title: "blog",
|
||||
},
|
||||
tags: {
|
||||
path: "/tags",
|
||||
title: "tags",
|
||||
},
|
||||
media: {
|
||||
path: "/media",
|
||||
title: "media",
|
||||
},
|
||||
about: {
|
||||
path: "/about",
|
||||
title: "about",
|
||||
},
|
||||
};
|
||||
|
||||
export const SITE = {
|
||||
name: 'nrdlg',
|
||||
title: 'nrdlg',
|
||||
description: 'Personal blog of Tim DeHerrera',
|
||||
url: 'https://nrd.sh',
|
||||
ghUser: 'nrdxp',
|
||||
githubUrl: 'https://github.com/nrdxp',
|
||||
listDrafts: false
|
||||
}
|
||||
// Your site's detail?
|
||||
name: "nrdlg",
|
||||
title: "nrdlg",
|
||||
description: "Personal blog of nrdxp",
|
||||
url: "https://nrd.sh",
|
||||
githubUrl: "https://github.com/nrdxp/nrd.sh",
|
||||
listDrafts: false,
|
||||
image:
|
||||
"https://raw.githubusercontent.com/one-aalam/astro-ink/main/public/astro-banner.png",
|
||||
// YT video channel Id (used in media.astro)
|
||||
ytChannelId: "",
|
||||
// Optional, user/author settings (example)
|
||||
// Author: name
|
||||
author: "nrdxp", // Example: Fred K. Schott
|
||||
// Author: Twitter handler
|
||||
authorTwitter: "", // Example: FredKSchott
|
||||
// Author: Image external source
|
||||
authorImage: "", // Example: https://pbs.twimg.com/profile_images/1272979356529221632/sxvncugt_400x400.jpg, https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png
|
||||
// Author: Bio
|
||||
authorBio:
|
||||
"hackermans",
|
||||
};
|
||||
|
||||
export const PAGE_SIZE = 8
|
||||
// Ink - Theme configuration
|
||||
export const PAGE_SIZE = 8;
|
||||
export const USE_POST_IMG_OVERLAY = false;
|
||||
export const USE_MEDIA_THUMBNAIL = true;
|
||||
|
||||
export const USE_AUTHOR_CARD = true;
|
||||
export const USE_SUBSCRIPTION = false; /* works only when USE_AUTHOR_CARD is true */
|
||||
|
||||
export const USE_VIEW_STATS = true;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
layout: $/layouts/post.astro
|
||||
title: "Political Bikeshedding: NixOS Edition"
|
||||
description: On Social Dynamics and Leadership
|
||||
tags:
|
||||
@@ -7,7 +6,10 @@ tags:
|
||||
- politics
|
||||
author: Tim D
|
||||
authorGithub: nrdxp
|
||||
date: 2024-07-02
|
||||
authorImage: https://avatars.githubusercontent.com/u/34083928?v=4
|
||||
authorTwitter: nrdxp52262
|
||||
date: "2024-07-02"
|
||||
category: politics
|
||||
---
|
||||
|
||||
This piece offers a perspective on recent NixOS project challenges from
|
||||
@@ -1,12 +1,14 @@
|
||||
---
|
||||
layout: $/layouts/post.astro
|
||||
title: NixOS, Flakes and KISS
|
||||
description: A simpler way to manage the OS Layer
|
||||
tags:
|
||||
- nix
|
||||
author: Tim D
|
||||
authorGithub: nrdxp
|
||||
date: 2020-12-19
|
||||
authorImage: https://avatars.githubusercontent.com/u/34083928?v=4
|
||||
authorTwitter: nrdxp52262
|
||||
date: "2020-12-19"
|
||||
category: dev
|
||||
---
|
||||
|
||||
## Introduction
|
||||
@@ -1,16 +1,17 @@
|
||||
---
|
||||
layout: $/layouts/post.astro
|
||||
title: Standard Action
|
||||
description: Do it once, do it right.
|
||||
tags:
|
||||
- std
|
||||
- nix
|
||||
- devops
|
||||
- GitHub
|
||||
- actions
|
||||
- github actions
|
||||
author: Tim D
|
||||
authorGithub: nrdxp
|
||||
date: 2022-12-09
|
||||
authorImage: https://avatars.githubusercontent.com/u/34083928?v=4
|
||||
authorTwitter: nrdxp52262
|
||||
date: "2022-12-09"
|
||||
category: dev
|
||||
---
|
||||
|
||||
## CI Should be Simple
|
||||
@@ -69,6 +70,9 @@ Notice in this particular example that CI exited in 2 minutes. That's because ev
|
||||
represented by these builds is already cached in the specified action input `cache`, so no work is
|
||||
required, we simply report that the artifacts already exist and exit quickly.
|
||||
|
||||
There is a run phase that typically starts after this build step which runs the Standard action,
|
||||
but since the "build" actions only duty is building, it is also skipped here.
|
||||
|
||||
This is partially enabled by use of the GH action cache. The cache key is set using the following
|
||||
format: [divnix/std-action/discover/action.yml#key][key]. Coupled with the guarantees nix already
|
||||
gives us, this is enough to ensure the evaluation will only be used on runners using a matching OS,
|
||||
@@ -82,7 +86,7 @@ on their target derivation file instead of doing any more evaluation.
|
||||
|
||||
Caching is also a first class citizen, and even in the event that a given task fails (even
|
||||
discovery itself), any of its nix dependencies built during the process leading up to that failure
|
||||
will be cached making sure no nix build _or_ evaluation is ever repeated. The user doesn't have
|
||||
will be cached, making sure no nix build _or_ evaluation is ever repeated. The user doesn't have
|
||||
to set a cache, but if they do, they can be rest assured their results will be well cached, we
|
||||
make a point to cache the entire build time closure, and not just the runtime closure, which is
|
||||
important for active developement in projects using a shared cache.
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
layout: $/layouts/post.astro
|
||||
title: From DevOS to Standard
|
||||
description: Why we made Standard, and what it has done for us.
|
||||
tags:
|
||||
@@ -8,7 +7,10 @@ tags:
|
||||
- devops
|
||||
author: Tim D
|
||||
authorGithub: nrdxp
|
||||
date: 2022-10-31
|
||||
authorImage: https://avatars.githubusercontent.com/u/34083928?v=4
|
||||
authorTwitter: nrdxp52262
|
||||
date: "2022-10-31"
|
||||
category: dev
|
||||
---
|
||||
|
||||
## Update: A Video is Worth 1000 Blogs
|
||||
21
src/content/config.ts
Normal file
21
src/content/config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { z, defineCollection } from "astro:content";
|
||||
|
||||
const blogCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z
|
||||
.string()
|
||||
.max(100, "The title length must be less than or equal to 100 chars"),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
author: z.string(),
|
||||
authorImage: z.string().optional(),
|
||||
authorTwitter: z.string(),
|
||||
date: z.string(),
|
||||
image: z.string().optional(),
|
||||
category: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
blog: blogCollection,
|
||||
};
|
||||
@@ -1,35 +1,34 @@
|
||||
[
|
||||
|
||||
{
|
||||
"title": "Standard — Introduction",
|
||||
"description": "A first principles introduction to Standard.",
|
||||
"url": "https://www.loom.com/share/cf9d5d1a10514d65bf6b8287f7ddc7d6",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Cell Blocks: Deep Dive",
|
||||
"description": "Going in depth on Standard's key abstraction.",
|
||||
"url": "https://www.loom.com/share/04fa1d578fd044059b02c9c052d87b77",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Operables & OCI",
|
||||
"description": "An entrypoint into building reproducible containers with Standard; batteries included.",
|
||||
"url": "https://www.loom.com/share/27d91aa1eac24bcaaaed18ea6d6d03ca",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Nixago",
|
||||
"description": "Generating configuration files dynamically at shell load time & more.",
|
||||
"url": "https://www.loom.com/share/5c1badd77ab641d3b8e256ddbba69042",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
}
|
||||
]
|
||||
{
|
||||
"title": "Standard — Introduction",
|
||||
"description": "A first principles introduction to Standard.",
|
||||
"url": "https://www.loom.com/share/cf9d5d1a10514d65bf6b8287f7ddc7d6",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Cell Blocks: Deep Dive",
|
||||
"description": "Going in depth on Standard's key abstraction.",
|
||||
"url": "https://www.loom.com/share/04fa1d578fd044059b02c9c052d87b77",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Operables & OCI",
|
||||
"description": "An entrypoint into building reproducible containers with Standard; batteries included.",
|
||||
"url": "https://www.loom.com/share/27d91aa1eac24bcaaaed18ea6d6d03ca",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
},
|
||||
{
|
||||
"title": "Standard — Nixago",
|
||||
"description": "Generating configuration files dynamically at shell load time & more.",
|
||||
"url": "https://www.loom.com/share/5c1badd77ab641d3b8e256ddbba69042",
|
||||
"host": "Joshua Gilman",
|
||||
"participants": [],
|
||||
"date": "2022-11-22"
|
||||
}
|
||||
]
|
||||
12
src/env.d.ts
vendored
Normal file
12
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly REDIS_URI: string;
|
||||
readonly SITE_URI: string;
|
||||
// more env variables...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ const { content, showPageHeader = true } = Astro.props
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
|
||||
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description } image={SITE.image}/>
|
||||
</head>
|
||||
<MainLayout>
|
||||
{showPageHeader &&
|
||||
|
||||
@@ -9,7 +9,7 @@ const { content } = Astro.props
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
|
||||
<BaseHead {...content} title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title }/>
|
||||
</head>
|
||||
<MainLayout>
|
||||
<div class="post__header">
|
||||
@@ -18,7 +18,7 @@ const { content } = Astro.props
|
||||
</div>
|
||||
<h1 class="post__title">{ content.title }</h1>
|
||||
<h5 class="post__desc">
|
||||
<a class="post__author" href={`https://github.com/${content.authorGitHub}`} title={`${content.author + "'s"} GitHub`} target="_blank" rel="external">{ content.author }</a> |
|
||||
<a class="post__author" href={`https://twitter.com/${content.authorTwitter}`} title={`${content.author + "'s"} twitter`} target="_blank" rel="external">{ content.author }</a> |
|
||||
<span class="post__date">{ new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date(content.date))}</span>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
---
|
||||
import { SITE } from '$/config'
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import Icon from 'astro-icon';
|
||||
import { SITE, USE_POST_IMG_OVERLAY, USE_AUTHOR_CARD, USE_SUBSCRIPTION, USE_VIEW_STATS } from '$/config'
|
||||
import MainLayout from '$/components/MainLayout.astro'
|
||||
import BaseHead from '$/components/BaseHead.astro'
|
||||
import Prose from '$/components/Prose.astro'
|
||||
import PostStats from '$/components/PostStats.svelte'
|
||||
import EditUrl from '$/components/EditLink.astro'
|
||||
|
||||
const { content } = Astro.props
|
||||
|
||||
interface Props {
|
||||
meta?: {
|
||||
id: string,
|
||||
slug: string,
|
||||
collection: string
|
||||
},
|
||||
content: CollectionEntry<'blog'>['data'],
|
||||
stats?: {
|
||||
views: number
|
||||
}
|
||||
}
|
||||
const { content, meta } = Astro.props
|
||||
|
||||
const AUTHOR_NAME = content.author ? content.author : SITE?.author ? SITE?.author : "Author"
|
||||
const AUTHOR_TWITTER = content.authorTwitter ? content.authorTwitter : SITE?.authorTwitter ? SITE?.authorTwitter : ""
|
||||
const AUTHOR_AVATAR = content.authorImage ? content.authorImage : SITE?.authorImage ? SITE?.authorImage : ""
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title } description={ content.description }/>
|
||||
<BaseHead {...content} title={ content.title ? `${ SITE.title } | ${content.title}` : SITE.title }/>
|
||||
</head>
|
||||
<MainLayout>
|
||||
<div class="post__header">
|
||||
@@ -17,31 +37,93 @@ const { content } = Astro.props
|
||||
{ content.tags.length > 0 && content.tags.map(tag => <a class="post__tag" href={`/tags/${tag}`} title={tag}>{tag}</a>) }
|
||||
</div>
|
||||
<h1 class="post__title">{ content.title }</h1>
|
||||
<h5 class="post__desc">
|
||||
<a class="post__author" href={`https://github.com/${content.authorGithub}`} title={`${content.author + "'s"} GitHub`} target="_blank" rel="external">{ content.author }</a> |
|
||||
<span class="post__date">{ new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date(content.date))}</span>
|
||||
<h5 class={`post__desc ${AUTHOR_AVATAR ? 'flex flex-row gap-2' : ''}`}>
|
||||
{ AUTHOR_AVATAR ? <img class="avatar" src={AUTHOR_AVATAR} alt={`${ AUTHOR_NAME }'s avatar`} /> : ''}
|
||||
<div class={AUTHOR_AVATAR ? 'flex flex-col border-l-2 pl-2' : ''}>
|
||||
{
|
||||
AUTHOR_TWITTER ?
|
||||
<a class="post__author" href={`https://twitter.com/${AUTHOR_TWITTER}`} title={`${AUTHOR_NAME}'s twitter`} target="_blank" rel="external">{ AUTHOR_NAME }</a>
|
||||
:
|
||||
<span class="post__author">{ AUTHOR_NAME }</span>
|
||||
}
|
||||
{!AUTHOR_AVATAR ? ' | ' : ''}
|
||||
<span class="post__date">
|
||||
<!-- post creation/updation data -->
|
||||
{ new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).format(new Date(content.date))}
|
||||
</span>
|
||||
<span class="post__stats">
|
||||
{ USE_VIEW_STATS && ` | `}
|
||||
{ USE_VIEW_STATS &&
|
||||
<Icon class="w-5 h-5 inline-block" pack="mdi" name="eye" />
|
||||
<PostStats slug={meta?.slug} client:load />
|
||||
}
|
||||
<!-- | <Icon class="w-5 h-5 inline-block" pack="mdi" name="clock" /> 2 mins -->
|
||||
</span>
|
||||
</div>
|
||||
</h5>
|
||||
</div>
|
||||
<!--<img src={content.image} alt={content.title} />-->
|
||||
{
|
||||
content.image ?
|
||||
USE_POST_IMG_OVERLAY ?
|
||||
<div class="img__outer">
|
||||
<img src={content.image} alt={content.title} />
|
||||
<div class="img_gradient"></div>
|
||||
</div><br/>
|
||||
:
|
||||
<img class="img__outer" src={content.image} alt={content.title} /><br/>
|
||||
: ""
|
||||
}
|
||||
<Prose>
|
||||
<slot />
|
||||
</Prose>
|
||||
<div class="post__footer">
|
||||
{ USE_AUTHOR_CARD &&
|
||||
<br/>
|
||||
<div class="author-card">
|
||||
{ AUTHOR_AVATAR ? <img class="author-card__img avatar avatar--lg" src={AUTHOR_AVATAR} alt={`${ AUTHOR_NAME }'s avatar`} /> : ''}
|
||||
<div class="author-card__meta">
|
||||
{
|
||||
AUTHOR_TWITTER ?
|
||||
<a class="author-card__author" href={`https://twitter.com/${AUTHOR_TWITTER}`} title={`${AUTHOR_NAME}'s twitter`} target="_blank" rel="external">{ AUTHOR_NAME }</a>
|
||||
:
|
||||
<span class="author-card__author">{ AUTHOR_NAME }</span>
|
||||
}
|
||||
<p class="author-card__bio">{ SITE.authorBio }</p>
|
||||
<br/>
|
||||
{
|
||||
USE_SUBSCRIPTION ?
|
||||
<form action="" class="subscription-form">
|
||||
<label for="email"></label>
|
||||
<input type="email" name="email" class="flex-grow border-0 text-theme-accent-gray-dark" required="true">
|
||||
<button type="submit">Subscribe</button>
|
||||
</form> :
|
||||
<a class="author-card__follow-btn button" target="_blank" href={`https://twitter.com/intent/follow?screen_name=${AUTHOR_TWITTER}`}><Icon class="w-5 h-5 inline-block" pack="mdi" name="twitter" /> Follow on Twitter</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
}
|
||||
{
|
||||
meta?.collection && meta?.id &&
|
||||
<EditUrl label=" Suggest changes on GitHub" editUrl={`${SITE.githubUrl}/tree/main/src/content/${meta?.collection}/${meta?.id}`}/>
|
||||
}
|
||||
</div>
|
||||
</MainLayout>
|
||||
</html>
|
||||
<style>
|
||||
.post__header {
|
||||
@apply py-4 mb-1
|
||||
@apply py-4 mb-1 text-center md:text-left
|
||||
}
|
||||
.post__title {
|
||||
@apply text-5xl font-extrabold text-theme-primary dark:text-theme-dark-primary
|
||||
}
|
||||
.post__desc {
|
||||
@apply text-gray-500 dark:text-gray-100
|
||||
@apply text-gray-500 dark:text-gray-100 flex justify-center text-left md:flex-none md:justify-start
|
||||
}
|
||||
.post__author {
|
||||
@apply no-underline dark:text-white hover:text-theme-primary
|
||||
}
|
||||
.post__date {
|
||||
.post__date,.post__stats {
|
||||
@apply text-gray-400
|
||||
}
|
||||
.post__tags {
|
||||
@@ -50,4 +132,44 @@ const { content } = Astro.props
|
||||
.post__tag {
|
||||
@apply text-gray-400 hover:text-theme-primary dark:hover:text-theme-dark-primary
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@apply w-12 h-12 rounded-full object-cover p-1 border-2 border-solid border-theme-dark-primary dark:border-theme-primary
|
||||
}
|
||||
.avatar--lg {
|
||||
@apply w-32 h-32
|
||||
}
|
||||
|
||||
.img__outer {
|
||||
@apply relative rounded-lg shadow-xl overflow-hidden
|
||||
}
|
||||
.img_gradient {
|
||||
@apply absolute z-10 w-full bottom-0 left-0 h-full bg-gradient-to-tr from-theme-primary dark:from-theme-dark-primary
|
||||
}
|
||||
|
||||
.author-card {
|
||||
@apply text-gray-500 dark:text-gray-100 flex flex-row gap-4 justify-start text-left
|
||||
}
|
||||
.author-card__meta {
|
||||
@apply border-l pl-4
|
||||
}
|
||||
.author-card__author {
|
||||
@apply text-2xl mb-1
|
||||
}
|
||||
.author-card__bio {
|
||||
@apply text-gray-400
|
||||
}
|
||||
|
||||
.subscription-form {
|
||||
@apply w-4/6 mt-2 flex flex-row rounded-lg overflow-hidden shadow-lg
|
||||
}
|
||||
.subscription-form input {
|
||||
@apply flex-grow border-0 text-theme-accent-gray-dark
|
||||
}
|
||||
.subscription-form button, .button {
|
||||
@apply px-4 py-2 uppercase font-bold text-white bg-gradient-to-tr from-theme-primary to-theme-dark-secondary dark:from-theme-dark-secondary dark:to-theme-primary
|
||||
}
|
||||
.author-card__follow-btn {
|
||||
@apply rounded-md shadow-md shadow-theme-dark-secondary dark:shadow-theme-primary hover:shadow-theme-secondary hover:dark:shadow-theme-secondary hover:shadow-lg transition-all
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,9 +7,9 @@ import Prose from '$/components/Prose.astro'
|
||||
|
||||
<DefaultPageLayout content={{ title: frontmatter.title, description: frontmatter.description }}>
|
||||
<Prose>
|
||||
Drinks waaay too much coffee and sometimes it helps; currently an SRE at [IOG](https://iog.io).
|
||||
Currently an SRE at [IOG](https://iog.io).
|
||||
|
||||
Timothy DeHerrera // [@timdeh:matrix.org](https://matrix.org) // [nrdxp](https://github.com/nrdxp)
|
||||
Timothy D // [@timdeh:matrix.org](https://matrix.org) // [nrdxp](https://github.com/nrdxp)
|
||||
|
||||
##
|
||||
<div class="author">
|
||||
|
||||
19
src/pages/api/blog/views/[slug].json.ts
Normal file
19
src/pages/api/blog/views/[slug].json.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { APIRoute } from "astro";
|
||||
// import { getViewsBySlug } from "src/utils/views/turso";
|
||||
// import { getViewsBySlug } from "src/utils/views/ioredis";
|
||||
import { getViewsBySlug } from "src/utils/views/in-memory";
|
||||
|
||||
// In development/HMR, you can accidentally make this call numerous times and exceed your quota...
|
||||
// thus, the in-memory version of `getViewsBySlug` is used
|
||||
|
||||
// When deploying, and you have either `ioredis` or `turso` configured with your cloned version -
|
||||
// please uncomment the respective line
|
||||
|
||||
|
||||
export const GET: APIRoute = async ({ params, request }) => {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
views: params.slug ? await getViewsBySlug(params.slug) : 0,
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
---
|
||||
export const prerender = true
|
||||
|
||||
import { getCollection } from 'astro:content'
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
import PostPreviewList from '$/components/PostPreviewList.astro'
|
||||
import Paginator from '$/components/Paginator.astro'
|
||||
@@ -8,8 +11,8 @@ let title = 'Blog'
|
||||
let description = 'All the articles posted so far...'
|
||||
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
const allPosts = await Astro.glob('./*.md');
|
||||
const sortedPosts = allPosts.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
const allPosts = await getCollection('blog');
|
||||
const sortedPosts = allPosts.sort((a, b) => new Date(b.data.date) - new Date(a.data.date));
|
||||
|
||||
return paginate(sortedPosts, {
|
||||
pageSize: PAGE_SIZE
|
||||
@@ -17,6 +20,7 @@ export async function getStaticPaths({ paginate }) {
|
||||
}
|
||||
|
||||
const { page } = Astro.props
|
||||
|
||||
---
|
||||
<DefaultPageLayout content={{ title, description }}>
|
||||
<PostPreviewList posts={page.data} />
|
||||
|
||||
27
src/pages/blog/[slug].astro
Normal file
27
src/pages/blog/[slug].astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
export const prerender = true
|
||||
|
||||
import { getEntryBySlug, getCollection } from "astro:content";
|
||||
import PostLayout from '$/layouts/post.astro';
|
||||
import Code from '../../components/Code.astro'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const allPosts = await getCollection('blog');
|
||||
return allPosts.map(post => ({
|
||||
params: {
|
||||
slug: post.slug
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
const { slug } = Astro.params
|
||||
const entry = await getEntryBySlug('blog', slug!)
|
||||
|
||||
const { id, collection, data } = entry
|
||||
const { Content } = await entry.render()
|
||||
---
|
||||
<PostLayout meta={{id, collection, slug }} content={data} >
|
||||
<Content components={{
|
||||
code: Code
|
||||
}}/>
|
||||
</PostLayout>
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
export const prerender = true;
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
import PostDraftPreviewList from '$/components/PostDraftPreviewList.astro'
|
||||
import Paginator from '$/components/Paginator.astro'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
import { getSlugFromPathname } from '$/utils'
|
||||
export const prerender = true;
|
||||
|
||||
import PostDraftPageLayout from '$/layouts/post-draft.astro'
|
||||
import { getSlugFromPathname } from '$/utils'
|
||||
|
||||
export async function getStaticPaths({ }) {
|
||||
let allPosts = []
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
---
|
||||
export const prerender = true
|
||||
|
||||
import { getCollection, getEntryBySlug } from 'astro:content'
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
import PostPreviewList from '$/components/PostPreviewList.astro'
|
||||
|
||||
import Prose from '$/components/Prose.astro'
|
||||
import { SITE } from "$/config";
|
||||
|
||||
const title = 'Home'
|
||||
const description = 'Astro-Ink is a crisp, minimal, personal blog theme for Astro'
|
||||
const description = SITE.description
|
||||
|
||||
const posts = await Astro.glob('./blog/*.md')
|
||||
const posts = await getCollection('blog')
|
||||
---
|
||||
<DefaultPageLayout content={{ title, description }} showPageHeader={false}>
|
||||
<PostPreviewList posts={posts} />
|
||||
<PostPreviewList posts={posts.slice(0, 3)} heading="recent posts"/>
|
||||
<div class="page__actions">
|
||||
<a class="action__go-to-blog" href="/blog" title="All Posts">All Posts →</a>
|
||||
<a class="action__go-to-blog ink-h" href="/blog" title="All Posts">All Posts →</a>
|
||||
</div>
|
||||
</DefaultPageLayout>
|
||||
|
||||
<style>
|
||||
.hero {
|
||||
@apply flex flex-col md:flex-row gap-8 w-full transition-all pb-4
|
||||
}
|
||||
.hero__face {
|
||||
@apply flex md:flex-none justify-center
|
||||
}
|
||||
.hero__says {
|
||||
@apply flex-1 text-center md:text-left
|
||||
}
|
||||
.author-card {
|
||||
@apply h-48 w-48 md:h-56 bg-theme-primary dark:bg-theme-dark-primary rounded-full md:rounded-md shadow-lg
|
||||
}
|
||||
.author-card img {
|
||||
@apply rounded-full h-48 w-48 md:h-56
|
||||
}
|
||||
.page__actions {
|
||||
@apply flex justify-center md:justify-end py-6
|
||||
}
|
||||
.action__go-to-blog {
|
||||
@apply text-base uppercase text-gray-500 dark:text-gray-400 hover:underline
|
||||
@apply py-4 hover:underline
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import rss from '@astrojs/rss';
|
||||
import { SITE } from '../config'
|
||||
import rss from "@astrojs/rss";
|
||||
import { getCollection } from "astro:content";
|
||||
import { SITE } from "../config";
|
||||
|
||||
const allPosts = import.meta.glob('./**/*.md', { eager: true })
|
||||
const sortedPosts = Object.values(allPosts).sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
const allPosts = await getCollection("blog");
|
||||
const sortedPosts = Object.values(allPosts).sort(
|
||||
(a, b) => new Date(b.data.date).valueOf() - new Date(a.data.date).valueOf(),
|
||||
);
|
||||
|
||||
export const get = () => rss({
|
||||
// `<title>` field in output xml
|
||||
title: `${SITE.name} | Blog`,
|
||||
// `<description>` field in output xml
|
||||
description: SITE.description,
|
||||
// base URL for RSS <item> links
|
||||
// SITE will use "site" from your project's astro.config.
|
||||
site: import.meta.env.SITE,
|
||||
// list of `<item>`s in output xml
|
||||
// simple example: generate items for every md file in /src/pages
|
||||
// see "Generating items" section for required frontmatter and advanced use cases
|
||||
items: sortedPosts.map(item => ({
|
||||
title: item.frontmatter.title,
|
||||
description: item.frontmatter.description,
|
||||
link: item.url,
|
||||
pubDate: item.frontmatter.date,
|
||||
})),
|
||||
// (optional) inject custom xml
|
||||
customData: `<language>en-us</language>`,
|
||||
});
|
||||
export const get = () =>
|
||||
rss({
|
||||
// `<title>` field in output xml
|
||||
title: `${SITE.name} | Blog`,
|
||||
// `<description>` field in output xml
|
||||
description: SITE.description,
|
||||
// base URL for RSS <item> links
|
||||
// SITE will use "site" from your project's astro.config.
|
||||
site: import.meta.env.SITE,
|
||||
// list of `<item>`s in output xml
|
||||
// simple example: generate items for every md file in /src/pages
|
||||
// see "Generating items" section for required frontmatter and advanced use cases
|
||||
items: sortedPosts.map((item) => ({
|
||||
title: item.data.title,
|
||||
description: item.data.description,
|
||||
link: `blog/${item.slug}`,
|
||||
pubDate: new Date(item.data.date),
|
||||
})),
|
||||
// (optional) inject custom xml
|
||||
customData: `<language>en-us</language>`,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
---
|
||||
export const prerender = true
|
||||
import type { InferGetStaticParamsType, InferGetStaticPropsType } from 'astro'
|
||||
import { getCollection } from 'astro:content'
|
||||
import { PAGE_SIZE } from '$/config'
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
import PostPreviewList from '$/components/PostPreviewList.astro'
|
||||
@@ -8,14 +11,14 @@ let title = 'Posts By Tags'
|
||||
let description = 'All the articles posted so far...'
|
||||
|
||||
export async function getStaticPaths({ paginate }) {
|
||||
const allPosts = await Astro.glob('../../blog/*.md')
|
||||
const allTags = new Set()
|
||||
const allPosts = await getCollection('blog')
|
||||
const allTags = new Set<string>()
|
||||
allPosts.map(post => {
|
||||
post.frontmatter.tags && post.frontmatter.tags.map(tag => allTags.add(tag.toLowerCase()))
|
||||
post.data.tags && post.data.tags.map(tag => allTags.add(tag.toLowerCase()))
|
||||
})
|
||||
|
||||
return Array.from(allTags).map((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag))
|
||||
return Array.from(allTags).flatMap((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) => post.data.tags.includes(tag))
|
||||
return paginate(filteredPosts, {
|
||||
params: { tag },
|
||||
pageSize: PAGE_SIZE
|
||||
@@ -23,11 +26,14 @@ export async function getStaticPaths({ paginate }) {
|
||||
});
|
||||
}
|
||||
|
||||
const { page } = Astro.props
|
||||
const { tag } = Astro.params
|
||||
type Params = InferGetStaticParamsType<typeof getStaticPaths>;
|
||||
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
||||
|
||||
const { page } = Astro.props as Props
|
||||
const { tag } = Astro.params as Params
|
||||
---
|
||||
|
||||
<DefaultPageLayout content={{ title: `Posts by Tag: ${tag}`, description: `all of the articles we have posted and linked so far under the tag: ${tag}` }}>
|
||||
<PostPreviewList posts={page.data} />
|
||||
<PostPreviewList posts={page.data} />
|
||||
<Paginator page={page} />
|
||||
</DefaultPageLayout>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
---
|
||||
export const prerender = true
|
||||
|
||||
import { getCollection } from 'astro:content'
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
import PostPreviewList from '$/components/PostPreviewList.astro'
|
||||
|
||||
export async function getStaticPaths({ }) {
|
||||
const allPosts = await Astro.glob('../../blog/*.md')
|
||||
const allPosts = await getCollection('blog')
|
||||
const allTags = new Set()
|
||||
allPosts.map(post => {
|
||||
post.frontmatter.tags && post.frontmatter.tags.map(tag => allTags.add(tag.toLowerCase()))
|
||||
post.data.tags && post.data.tags.map(tag => allTags.add(tag.toLowerCase()))
|
||||
})
|
||||
|
||||
return Array.from(allTags).map((tag) => {
|
||||
const filteredPosts = allPosts.filter((post) => post.frontmatter.tags.includes(tag))
|
||||
const filteredPosts = allPosts.filter((post) => post.data.tags.includes(tag))
|
||||
return {
|
||||
params: { tag },
|
||||
props: {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
---
|
||||
export const prerender = true
|
||||
|
||||
import { getCollection } from 'astro:content';
|
||||
import DefaultPageLayout from '$/layouts/default.astro'
|
||||
|
||||
let title = 'All Tags'
|
||||
let description = 'All the tags used so far...'
|
||||
|
||||
|
||||
const allPosts = await Astro.glob('../blog/*.md');
|
||||
const tags = [...new Set([].concat.apply([], allPosts.map(post => post.frontmatter.tags)))]
|
||||
const allPosts = await getCollection('blog');
|
||||
const tags = [...new Set([].concat.apply([], allPosts.map(post => post.data.tags)))]
|
||||
---
|
||||
|
||||
<DefaultPageLayout content={{ title, description }}>
|
||||
<ul class="tag-list">
|
||||
{tags.map((tag) => (
|
||||
<li><a class="tag" href={`/tags/${tag}`} title={`View posts tagged under "${tag}"`}>{tag}</a></li>
|
||||
<li><a class="tag" href={`/tags/${tag}`} title={`View posts tagged under "${tag}"`} transition:animate="slide">{tag}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</DefaultPageLayout>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const isSearchVisible = writable<boolean>(false)
|
||||
export const isSearchVisible = writable<boolean>(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writable } from 'svelte/store'
|
||||
type ThemeType = 'dark' | 'light'
|
||||
import { writable } from "svelte/store";
|
||||
type ThemeType = "dark" | "light";
|
||||
|
||||
export const theme = writable<ThemeType>('dark')
|
||||
export const theme = writable<ThemeType>("dark");
|
||||
|
||||
@@ -1,14 +1,72 @@
|
||||
@tailwind base;
|
||||
/* https://github.com/tailwindlabs/tailwindcss/discussions/2917 */
|
||||
@layer base {
|
||||
body {
|
||||
html {
|
||||
&.dark {
|
||||
@apply text-gray-200;
|
||||
header, footer {
|
||||
@apply text-gray-400 border-gray-700;
|
||||
}
|
||||
strong {
|
||||
@apply text-inherit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Ink specific styles */
|
||||
.ink-h {
|
||||
@apply inline-block text-sm font-bold uppercase drop-shadow-lg py-4 tracking-wider opacity-40 dark:opacity-70
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@apply w-3;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-theme-dark-primary dark:bg-theme-primary bg-opacity-20;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-theme-primary dark:bg-theme-dark-primary bg-opacity-20 dark:bg-opacity-100 shadow-2xl rounded-full;
|
||||
}
|
||||
|
||||
/** Code block **/
|
||||
.astro-code {
|
||||
@apply relative shadow-inner shadow-theme-primary/20 dark:shadow-theme-dark-primary/20 mt-0;
|
||||
}
|
||||
.remark-code-title {
|
||||
@apply inline-block relative top-4 px-2 pt-1 pb-5 text-sm text-theme-primary dark:text-theme-dark-primary bg-gradient-to-br from-theme-primary/30 dark:from-theme-dark-primary/30 to-theme-dark-primary/30 dark:to-theme-primary/30 rounded-t-md shadow-sm;
|
||||
}
|
||||
|
||||
/** Shiki theme - Light/Dark mode **/
|
||||
|
||||
:root {
|
||||
--astro-code-color-text: #24292f;
|
||||
--astro-code-color-background: #ffffff;
|
||||
--astro-code-token-constant: #0550ae;
|
||||
--astro-code-token-string: #24292f;
|
||||
--astro-code-token-comment: #6e7781;
|
||||
--astro-code-token-keyword: #cf222e;
|
||||
--astro-code-token-parameter: #24292f;
|
||||
--astro-code-token-function: #8250df;
|
||||
--astro-code-token-string-expression: #0a3069;
|
||||
--astro-code-token-punctuation: #24292f;
|
||||
--astro-code-token-link: #000012;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--astro-code-color-text: #c9d1d9;
|
||||
--astro-code-color-background: #0d1117;
|
||||
--astro-code-token-constant: #79c0ff;
|
||||
--astro-code-token-string: #a5d6ff;
|
||||
--astro-code-token-comment: #8b949e;
|
||||
--astro-code-token-keyword: #ff7b72;
|
||||
--astro-code-token-parameter: #c9d1d9;
|
||||
--astro-code-token-function: #d2a8ff;
|
||||
--astro-code-token-string-expression: #a5d6ff;
|
||||
--astro-code-token-punctuation: #c9d1d9;
|
||||
--astro-code-token-link: #000012;
|
||||
}
|
||||
|
||||
10
src/types.ts
10
src/types.ts
@@ -1,8 +1,8 @@
|
||||
export type NavItems = {
|
||||
[key: string]: NavItem
|
||||
}
|
||||
[key: string]: NavItem;
|
||||
};
|
||||
|
||||
export type NavItem = {
|
||||
path: string
|
||||
title: string
|
||||
}
|
||||
path: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import path from 'path'
|
||||
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
import path from "path";
|
||||
const MONTHS = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
export const toTitleCase = (str: string) =>
|
||||
str.replace(/\w\S*/g, function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
});
|
||||
|
||||
export const toTitleCase = (str: string) => str.replace(
|
||||
/\w\S*/g,
|
||||
function(txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
}
|
||||
)
|
||||
export const getMonthName = (date: Date) => MONTHS[new Date(date).getMonth()];
|
||||
|
||||
export const getMonthName = (date: Date) => MONTHS[new Date(date).getMonth()]
|
||||
|
||||
export const getSlugFromPathname = (pathname: string) => path.basename(pathname, path.extname(pathname))
|
||||
export const getSlugFromPathname = (pathname: string) =>
|
||||
path.basename(pathname, path.extname(pathname));
|
||||
|
||||
29
src/utils/mdoc/mdoc.config.ts
Normal file
29
src/utils/mdoc/mdoc.config.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { callout } from "./schema/callout.mdoc";
|
||||
import { link } from "./schema/link.mdoc";
|
||||
import { tweetEmbed } from "./schema/tweet-embed.mdoc";
|
||||
import { tabs } from "./schema/tabs.mdoc";
|
||||
import { ytEmbed } from "./schema/yt-embed.mdoc";
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Config} */
|
||||
export const config = {
|
||||
tags: {
|
||||
callout,
|
||||
link,
|
||||
tweet: tweetEmbed,
|
||||
yt: ytEmbed,
|
||||
tabs,
|
||||
},
|
||||
functions: {
|
||||
getCountryEmoji: {
|
||||
transform(parameters) {
|
||||
const [country] = Object.values(parameters);
|
||||
const countryToEmojiMap = {
|
||||
japan: "🇯🇵",
|
||||
spain: "🇪🇸",
|
||||
france: "🇫🇷",
|
||||
};
|
||||
return countryToEmojiMap[country as string] ?? "🏳";
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
18
src/utils/mdoc/schema/callout.mdoc.ts
Normal file
18
src/utils/mdoc/schema/callout.mdoc.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { component } from "@astrojs/markdoc/config";
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Schema} */
|
||||
export const callout = {
|
||||
render: component("./src/components/mdoc/Callout.astro"),
|
||||
children: ["paragraph", "tag", "list"],
|
||||
attributes: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "note goes here...",
|
||||
matches: ["error", "check", "note", "warning"],
|
||||
errorLevel: "critical",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
34
src/utils/mdoc/schema/link.mdoc.ts
Normal file
34
src/utils/mdoc/schema/link.mdoc.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { component } from "@astrojs/markdoc/config";
|
||||
|
||||
const SITE_DOMAIN = "astro-ink.vercel.app";
|
||||
function getHrefTarget(attributes) {
|
||||
const href = attributes.href;
|
||||
if (
|
||||
href.includes(SITE_DOMAIN) ||
|
||||
href.startsWith("/") ||
|
||||
href.startsWith("#") ||
|
||||
href.startsWith("?")
|
||||
) {
|
||||
return "_self";
|
||||
} else {
|
||||
return "_blank";
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Schema} */
|
||||
export const link = {
|
||||
render: component("./src/components/mdoc/Link.astro"),
|
||||
children: ["strong", "em", "s", "code", "text", "tag"],
|
||||
attributes: {
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
15
src/utils/mdoc/schema/tabs.mdoc.ts
Normal file
15
src/utils/mdoc/schema/tabs.mdoc.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { component } from "@astrojs/markdoc/config";
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Schema} */
|
||||
export const tabs = {
|
||||
render: component("./src/components/mdoc/Tabs/Tabs.astro"),
|
||||
children: ["paragraph", "tag", "list"],
|
||||
attributes: {
|
||||
tabs: {
|
||||
type: Array,
|
||||
},
|
||||
heading: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
12
src/utils/mdoc/schema/tweet-embed.mdoc.ts
Normal file
12
src/utils/mdoc/schema/tweet-embed.mdoc.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { component } from "@astrojs/markdoc/config";
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Schema} */
|
||||
export const tweetEmbed = {
|
||||
render: component("./src/components/mdoc/TweetEmbed.astro"),
|
||||
attributes: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
16
src/utils/mdoc/schema/yt-embed.mdoc.ts
Normal file
16
src/utils/mdoc/schema/yt-embed.mdoc.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { component } from "@astrojs/markdoc/config";
|
||||
|
||||
/** @type {import('@markdoc/markdoc').Schema} */
|
||||
export const ytEmbed = {
|
||||
render: component("./src/components/mdoc/YTVideoEmbed.astro"),
|
||||
attributes: {
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
34
src/utils/media-yt-feed.ts
Normal file
34
src/utils/media-yt-feed.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { MediaExternallyHostedVideo } from "./media";
|
||||
|
||||
export type Feed2JsonYtFeedItem = {
|
||||
guid: `yt:video:${string}`;
|
||||
url: string;
|
||||
title: string;
|
||||
date_published: string;
|
||||
author: {
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function toFeedToJsonUrl(ytVideoChannelId: string) {
|
||||
return `https://feed2json.org/convert?url=https://www.youtube.com/feeds/videos.xml?channel_id=${ytVideoChannelId}`;
|
||||
}
|
||||
|
||||
export function toMediaFormatFromFeed2JsonUrl(posts: {
|
||||
items: Array<Feed2JsonYtFeedItem>;
|
||||
}): Array<MediaExternallyHostedVideo> {
|
||||
return posts?.items?.length
|
||||
? posts.items.map((post) => ({
|
||||
title: post.title,
|
||||
description: "",
|
||||
url: post.url,
|
||||
participants: [],
|
||||
date: post.date_published,
|
||||
host: post.author.name,
|
||||
thumbnail: `https://img.youtube.com/vi/${post.guid.substring(
|
||||
post.guid.lastIndexOf(":") + 1,
|
||||
post.guid.length,
|
||||
)}/0.jpg`,
|
||||
}))
|
||||
: [];
|
||||
}
|
||||
15
src/utils/media.ts
Normal file
15
src/utils/media.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export type GithubContentURL =
|
||||
`https://api.github.com/repos/${string}/contents/${string}`;
|
||||
|
||||
export type MediaExternallyHostedVideo = {
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
host: string;
|
||||
participants: Array<string>;
|
||||
date: string;
|
||||
thumbnail?: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_MEDIA_URL: GithubContentURL =
|
||||
"https://api.github.com/repos/one-aalam/astro-ink/contents/src/data/astro-media.json";
|
||||
16
src/utils/views/in-memory.ts
Normal file
16
src/utils/views/in-memory.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const client = new Map<string, number>();
|
||||
|
||||
export const getViewsBySlug = async (slug: string) => {
|
||||
if (slug) {
|
||||
const prevValue = client.get(slug);
|
||||
let newValue = 1;
|
||||
if (prevValue) {
|
||||
newValue = parseInt(`${prevValue}`) + 1;
|
||||
client.set(slug, newValue);
|
||||
} else {
|
||||
client.set(slug, 1);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
17
src/utils/views/ioredis.ts
Normal file
17
src/utils/views/ioredis.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import Redis from 'ioredis'
|
||||
const client = new Redis(import.meta.env.REDIS_URI)
|
||||
|
||||
export const getViewsBySlug = async (slug: string) => {
|
||||
if (slug) {
|
||||
const prevValue = await client.get(slug);
|
||||
let newValue = 1;
|
||||
if (prevValue) {
|
||||
newValue = parseInt(`${prevValue}`) + 1;
|
||||
await client.set(slug, newValue);
|
||||
} else {
|
||||
await client.set(slug, 1);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
32
src/utils/views/turso.ts
Normal file
32
src/utils/views/turso.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createClient } from "@libsql/client";
|
||||
|
||||
export const client = createClient({
|
||||
url: import.meta.env.TURSO_DB_URL,
|
||||
authToken: import.meta.env.TURSO_DB_AUTH_TOKEN
|
||||
});
|
||||
|
||||
export const getViewsBySlug = async (slug: string) => {
|
||||
if(!slug) return 0;
|
||||
try {
|
||||
const initialViewCount = 0
|
||||
const transaction = await client.transaction("write");
|
||||
const rsSelected = await transaction.execute({
|
||||
sql: 'SELECT * FROM post_stats WHERE slug = :slug',
|
||||
args: { slug }
|
||||
});
|
||||
const prevViewCount = rsSelected?.rows?.length ? rsSelected.rows[0].views as number : initialViewCount;
|
||||
const rsUpdated = await transaction.execute({
|
||||
sql: 'INSERT INTO post_stats (uid, slug, views) VALUES (:uid, :slug, :views) ON CONFLICT(slug) DO UPDATE SET views = :views RETURNING views',
|
||||
args: {
|
||||
uid: crypto.randomUUID(),
|
||||
slug,
|
||||
views: prevViewCount + 1
|
||||
}
|
||||
});
|
||||
await transaction.commit()
|
||||
return rsUpdated.rows[0].views as number
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
const { fontFamily } = require('tailwindcss/defaultTheme')
|
||||
const config = require('./tailwind.theme.config')
|
||||
const config = require('./tailwind.theme.config.cjs')
|
||||
/**
|
||||
* Find the applicable theme color palette, or use the default one
|
||||
*/
|
||||
@@ -26,6 +26,13 @@ module.exports = {
|
||||
dark: {
|
||||
css: {
|
||||
color: theme("colors.gray.200"),
|
||||
blockquote: {
|
||||
color: colors.dark.primary,
|
||||
borderColor: colors.primary
|
||||
},
|
||||
'blockquote > p::before, p::after': {
|
||||
color: colors.primary,
|
||||
},
|
||||
},
|
||||
},
|
||||
DEFAULT: {
|
||||
@@ -38,7 +45,8 @@ module.exports = {
|
||||
},
|
||||
blockquote: {
|
||||
color: colors.primary,
|
||||
borderColor: colors.dark.primary
|
||||
fontSize: theme("fontSize.2xl"),
|
||||
borderColor: colors.dark.primary,
|
||||
},
|
||||
'blockquote > p::before, p::after': {
|
||||
color: colors.dark.primary,
|
||||
@@ -63,7 +71,6 @@ module.exports = {
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/line-clamp'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
]
|
||||
};
|
||||
|
||||
@@ -146,16 +146,19 @@ module.exports = {
|
||||
*/
|
||||
default: {
|
||||
colors: {
|
||||
primary: colors.amber[700],
|
||||
secondary: colors.amber[800],
|
||||
primary: "#171622",
|
||||
secondary: "#7A6B72",
|
||||
dark: {
|
||||
primary: colors.amber[300],
|
||||
secondary: colors.amber[500]
|
||||
// primary: "#E1E6EA",
|
||||
primary: "#6E6C79",
|
||||
secondary: "#918D7A"
|
||||
// secondary: colors.rose[500]
|
||||
},
|
||||
accent: {
|
||||
gray: {
|
||||
light: colors.gray[300],
|
||||
dark: colors.gray[500]
|
||||
light: "#6E6C79",
|
||||
// light: colors.purple[700],
|
||||
dark: "#E1E6EA"
|
||||
},
|
||||
default: colors.blue[700]
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "ES2022",
|
||||
"lib": ["es2020"],
|
||||
"target": "es2019",
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
@@ -18,7 +17,8 @@
|
||||
"$components": ["./src/components"],
|
||||
"$": ["./src"]
|
||||
},
|
||||
"types": ["astro/env"]
|
||||
"types": ["astro/env"],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user