chore: update to latest astro ink version

This commit is contained in:
Timothy DeHerrera
2024-07-06 14:41:28 -06:00
parent 97aa45c32e
commit e4620874f9
84 changed files with 13949 additions and 4620 deletions

7
.gitignore vendored
View File

@@ -21,5 +21,10 @@ yarn-error.log*
.netlify
netlify
# Nix
.astro
.idea
.vercel
# nix
result
.std

View File

@@ -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 dont 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(),
markdoc(),
svelte(),
tailwind({
config: {
applyBaseStyles: false
},
applyBaseStyles: false,
}),
sitemap()
sitemap(),
decapCmsOauth()
],
vite: {
plugins: [],
resolve: {
alias: {
'$': path.resolve(__dirname, './src'),
},
$: path.resolve(__dirname, './src')
}
},
optimizeDeps: {
allowNodeBuiltins: true
}
}
},
adapter: vercel()
});

12
biome.json Normal file
View 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
View File

@@ -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": {

View File

@@ -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
View 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)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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}`)
}
})();

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -0,0 +1,7 @@
---
import CodeCopy from './CodeCopy.svelte'
---
<CodeCopy client:load/>
<code class="astro-ink__code">
<slot/>
</code>

View 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 }}>&check;Copied</span>
{:else}
<span transition:fade>Copy</span>
{/if}
</button>

View 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>

View 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>
)
}

View File

@@ -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 &copy; 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 &copy; 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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}
@@ -22,19 +29,34 @@ const { frontmatter: post, url } = Astro.props.post
.post-preview {
@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>

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,9 @@
<div class="tab-list">
<slot></slot>
</div>
<style>
.tab-list {
@apply border-b border-solid border-gray-500;
}
</style>

View 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}

View 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/>

View 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>

View 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>

View 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;
}

View 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>

View 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>

View File

@@ -1,36 +1,58 @@
import type{ NavItems } from './types'
import type { NavItems } from "./types";
export const NAV_ITEMS: NavItems = {
home: {
path: '/',
title: 'home'
path: "/",
title: "home",
},
blog: {
path: '/blog',
title: 'blog'
path: "/blog",
title: "blog",
},
tags: {
path: '/tags',
title: 'tags'
path: "/tags",
title: "tags",
},
media: {
path: '/media',
title: 'media'
path: "/media",
title: "media",
},
about: {
path: '/about',
title: '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;

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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
View 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,
};

View File

@@ -1,5 +1,4 @@
[
{
"title": "Standard — Introduction",
"description": "A first principles introduction to Standard.",

12
src/env.d.ts vendored Normal file
View 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;
}

View File

@@ -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 &&

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View 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,
}),
);
};

View File

@@ -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} />

View 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>

View File

@@ -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'

View File

@@ -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 = []

View File

@@ -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 &rarr;</a>
<a class="action__go-to-blog ink-h" href="/blog" title="All Posts">All Posts &rarr;</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>

View File

@@ -1,10 +1,14 @@
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({
export const get = () =>
rss({
// `<title>` field in output xml
title: `${SITE.name} | Blog`,
// `<description>` field in output xml
@@ -15,12 +19,12 @@ export const get = () => rss({
// 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,
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>`,
});
});

View File

@@ -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,8 +26,11 @@ 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}` }}>

View File

@@ -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: {

View File

@@ -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>

View File

@@ -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);

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -1,8 +1,8 @@
export type NavItems = {
[key: string]: NavItem
}
[key: string]: NavItem;
};
export type NavItem = {
path: string
title: string
}
path: string;
title: string;
};

View File

@@ -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) {
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));

View 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] ?? "🏳";
},
},
},
};

View 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,
},
},
};

View 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,
},
},
};

View 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,
},
},
};

View 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,
},
},
};

View 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,
},
},
};

View 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
View 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";

View 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;
};

View 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
View 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;
}
}

View File

@@ -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'),
]
};

View File

@@ -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]
}

View File

@@ -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"],
}

4243
yarn.lock

File diff suppressed because it is too large Load Diff