Compare commits
5 commits
68227f1188
...
2acc1aae3c
| Author | SHA1 | Date | |
|---|---|---|---|
| 2acc1aae3c | |||
| b2ca03d2c7 | |||
| 62aa324e16 | |||
| e357f2ed68 | |||
| 9c0ae88705 |
16 changed files with 235 additions and 148 deletions
|
|
@ -3,6 +3,15 @@ FROM node:lts-bookworm-slim
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
# SSH server for TRAMP /ssh: access
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends openssh-server python3-venv python3-pip \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/run/sshd /root/.ssh \
|
||||
&& chmod 700 /root/.ssh \
|
||||
&& ssh-keygen -A \
|
||||
&& python3 -m venv /opt/rass && /opt/rass/bin/pip install --no-cache-dir rassumfrassum \
|
||||
&& ln -s /opt/rass/bin/rass /usr/local/bin/rass
|
||||
|
||||
# Install dependencies first (better layer caching)
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
|
@ -10,6 +19,6 @@ RUN npm ci
|
|||
# Copy the rest of the project
|
||||
COPY . .
|
||||
|
||||
EXPOSE 4321
|
||||
EXPOSE 4321 22
|
||||
|
||||
CMD ["npm","run","dev","--","--host","0.0.0.0","--port","4321"]
|
||||
CMD ["/bin/sh","-lc","/usr/sbin/sshd && npm run dev -- --host 0.0.0.0 --port 4321"]
|
||||
|
|
|
|||
|
|
@ -44,11 +44,14 @@ This repo can be developed inside a Podman container to keep your host clean and
|
|||
### Using podman compose
|
||||
|
||||
```sh
|
||||
export SSH_PUBKEY_PATH="$HOME/.ssh/id_ed25519.pub"
|
||||
podman compose up --build
|
||||
```
|
||||
|
||||
On WSL2/rootless Podman, if you hit netavark/nftables errors, this repo defaults to `slirp4netns` via `network_mode` in `compose.yml`.
|
||||
|
||||
If you want SSH access (for Emacs TRAMP `/ssh:`), `SSH_PUBKEY_PATH` must point to your public key and port 2222 will be exposed for SSH.
|
||||
|
||||
Then open: http://localhost:4321
|
||||
|
||||
Run other commands in the running container:
|
||||
|
|
|
|||
|
|
@ -6,5 +6,14 @@ import preact from "@astrojs/preact";
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://blog.n-daisuke897.com/",
|
||||
integrations: [preact()]
|
||||
integrations: [preact()],
|
||||
markdown: {
|
||||
shikiConfig: {
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark',
|
||||
},
|
||||
wrap: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -7,10 +7,12 @@ services:
|
|||
network_mode: "slirp4netns:allow_host_loopback=true"
|
||||
ports:
|
||||
- "4321:4321"
|
||||
- "2222:22"
|
||||
volumes:
|
||||
- .:/app
|
||||
- node_modules:/app/node_modules
|
||||
command: npm run dev -- --host 0.0.0.0 --port 4321
|
||||
- ${SSH_PUBKEY_PATH}:/root/.ssh/authorized_keys:ro
|
||||
command: /bin/sh -lc "/usr/sbin/sshd && npm run dev -- --host 0.0.0.0 --port 4321"
|
||||
|
||||
volumes:
|
||||
node_modules:
|
||||
|
|
|
|||
61
flake.lock
generated
61
flake.lock
generated
|
|
@ -1,61 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769598131,
|
||||
"narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
23
flake.nix
23
flake.nix
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
description = "Naputo blog devshell";
|
||||
|
||||
inputs = {
|
||||
# Use stable nixpkgs for a more predictable toolchain.
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
forgejo-cli
|
||||
nodejs
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -12,11 +12,19 @@ const { title, url, datetime } = Astro.props;
|
|||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
:global(.dark) .new-article-date {
|
||||
color: #94a3b8;
|
||||
}
|
||||
.new-article-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333f;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.new-article-title:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* Removed specific dark mode color to inherit var(--text-color) */
|
||||
</style>
|
||||
<div class="new-article">
|
||||
<div class="new-article-date">{datetime}</div>
|
||||
|
|
|
|||
|
|
@ -9,3 +9,11 @@ import Navigation from "./Navigation.astro";
|
|||
<Navigation />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
|
||||
import ThemeIcon from "./ThemeIcon.astro";
|
||||
---
|
||||
|
||||
<div class="nav-links">
|
||||
<ThemeIcon />
|
||||
<a href="/">Home</a>
|
||||
<a href="/about/">About</a>
|
||||
<a href="/blog/">Articles</a>
|
||||
|
|
|
|||
36
src/components/ThemeIcon.astro
Normal file
36
src/components/ThemeIcon.astro
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
---
|
||||
<button id="themeToggle">
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="sun" d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z" />
|
||||
<path class="moon" d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.27-4.4 2.27-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
#themeToggle {
|
||||
border: 0;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.sun { fill: transparent; }
|
||||
.moon { fill: transparent; }
|
||||
|
||||
:global(.dark) .moon { fill: white; }
|
||||
:global(html:not(.dark)) .sun { fill: black; }
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
const handleToggleClick = () => {
|
||||
const element = document.documentElement;
|
||||
element.classList.toggle("dark");
|
||||
|
||||
const isDark = element.classList.contains("dark");
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
}
|
||||
|
||||
document.getElementById("themeToggle").addEventListener("click", handleToggleClick);
|
||||
</script>
|
||||
|
|
@ -12,6 +12,22 @@ const { pageTitle } = Astro.props;
|
|||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{pageTitle + " | Naputo"}</title>
|
||||
<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 === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
|
|
|
|||
|
|
@ -30,7 +30,11 @@ const optionsForDate = {
|
|||
|
||||
<style>
|
||||
a {
|
||||
color: #00539f;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
:global(.dark) a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.tags {
|
||||
|
|
@ -47,6 +51,11 @@ const optionsForDate = {
|
|||
background-color: #f8fcfd;
|
||||
}
|
||||
|
||||
:global(.dark) .tag {
|
||||
background-color: #1e293b;
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
font-family: sans-serif;
|
||||
font-size: 1.1rem;
|
||||
|
|
|
|||
|
|
@ -77,15 +77,28 @@ const pageTitle = "Articles";
|
|||
text-decoration: none;
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
:global(.dark) nav.pagination a,
|
||||
:global(.dark) nav.pagination span {
|
||||
background-color: #1e293b;
|
||||
color: #f8fafc;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
nav.pagination a:hover {
|
||||
background-color: #cacaca;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
:global(.dark) nav.pagination a:hover {
|
||||
background-color: #334155;
|
||||
}
|
||||
nav.pagination a.active {
|
||||
background-color: #cacaca;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
}
|
||||
:global(.dark) nav.pagination a.active {
|
||||
background-color: #334155;
|
||||
color: #f8fafc;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
nav.pagination {
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -8,55 +8,63 @@ const sortedPosts = allPosts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pu
|
|||
---
|
||||
|
||||
<style>
|
||||
.new-articles {
|
||||
text-align: start;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.header-new-posts {
|
||||
text-align: center;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.list-of-articles-button {
|
||||
display: inline-block;
|
||||
background-color: #e0e0e0;
|
||||
color: #333333;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-of-articles-button:hover {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
.new-articles {
|
||||
text-align: start;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.header-new-posts {
|
||||
text-align: center;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.list-of-articles-button {
|
||||
display: inline-block;
|
||||
background-color: #e0e0e0;
|
||||
color: #333333;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease, transform 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
:global(.dark) .list-of-articles-button {
|
||||
background-color: #1e293b;
|
||||
color: #f8fafc;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
.list-of-articles-button:hover {
|
||||
background-color: #cacaca;
|
||||
}
|
||||
:global(.dark) .list-of-articles-button:hover {
|
||||
background-color: #334155;
|
||||
}
|
||||
</style>
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<h3 class="header-new-posts">New Posts</h3>
|
||||
<div class="new-articles">
|
||||
{
|
||||
sortedPosts.slice(0, 5).map((post: any) => (
|
||||
<BlogPost
|
||||
url={`/posts/${post.id}/`}
|
||||
title={post.data.title}
|
||||
datetime={post.data.pubDate.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<h3 class="header-new-posts">New Posts</h3>
|
||||
<div class="new-articles">
|
||||
{
|
||||
sortedPosts.slice(0, 5).map((post: any) => (
|
||||
<BlogPost
|
||||
url={`/posts/${post.id}/`}
|
||||
title={post.data.title}
|
||||
datetime={post.data.pubDate.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<a href="/blog/" class="list-of-articles-button">List of Article</a>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<a href="/blog/" class="list-of-articles-button">List of Article</a>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const pageTitle = "Tag List";
|
|||
|
||||
<style>
|
||||
a {
|
||||
color: #00539f;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.tags {
|
||||
|
|
@ -36,4 +36,13 @@ const pageTitle = "Tag List";
|
|||
font-size: 1.15em;
|
||||
background-color: #f8fcfd;
|
||||
}
|
||||
|
||||
:global(.dark) .tag {
|
||||
background-color: #1e293b;
|
||||
border-color: #475569;
|
||||
}
|
||||
|
||||
:global(.dark) .tag a {
|
||||
color: #f8fafc;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
/* Dark Mode support */
|
||||
:root {
|
||||
--bg-color: #f1f5f9;
|
||||
--text-color: #333333;
|
||||
--nav-hover: #e0e0e0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--bg-color: #0f172a;
|
||||
--text-color: #f8fafc;
|
||||
--nav-hover: #1e293b;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #f1f5f9;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +35,14 @@ h1 {
|
|||
}
|
||||
|
||||
/* nav styles */
|
||||
a {
|
||||
color: #3b82f6; /* Blue 500 - vivid but readable on light */
|
||||
}
|
||||
|
||||
html.dark a {
|
||||
color: #60a5fa; /* Blue 400 - lighter and softer for dark mode */
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
padding-right: 20px;
|
||||
cursor: pointer;
|
||||
|
|
@ -31,17 +53,22 @@ h1 {
|
|||
width: 32px;
|
||||
height: 4px;
|
||||
margin-bottom: 8px;
|
||||
background-color: #333333;
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-links.expanded {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
|
@ -51,12 +78,12 @@ h1 {
|
|||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
border-radius: 4px;
|
||||
color: #333333;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.nav-links a:hover,
|
||||
.nav-links a:focus {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--nav-hover);
|
||||
}
|
||||
|
||||
.nav-links img {
|
||||
|
|
@ -66,14 +93,16 @@ h1 {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
display: unset;
|
||||
html.dark .nav-links img {
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
/* .expanded removed as it is handled by .nav-links.expanded */
|
||||
|
||||
@media screen and (min-width: 636px) {
|
||||
.nav-links {
|
||||
margin-left: 5em;
|
||||
display: block;
|
||||
display: flex;
|
||||
position: static;
|
||||
width: auto;
|
||||
background: none;
|
||||
|
|
@ -94,3 +123,14 @@ pre,
|
|||
code {
|
||||
font-family: "JetBrains Mono", "Fira Code", "Menlo", "Consolas", monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
html.dark pre {
|
||||
background-color: #1e293b !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue