Compare commits
10 Commits
2c34622207
...
3a1d508c65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a1d508c65 | ||
|
|
0c4e6715de | ||
|
|
38baeb86e8 | ||
|
|
df6f488ccd | ||
|
|
1206387246 | ||
|
|
9995edb14d | ||
|
|
8a5439d2aa | ||
|
|
e3224e321e | ||
|
|
1c47537b54 | ||
|
|
c940743fd8 |
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
extended: true
|
||||
|
||||
- name: Build site
|
||||
run: hugo --minify
|
||||
run: hugo build --minify
|
||||
|
||||
- name: Deploy via rsync
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Hugo build output
|
||||
public/
|
||||
resources/_gen/
|
||||
|
||||
# Hugo lock file
|
||||
.hugo_build.lock
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor files
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
541
CUSTOMIZATION.md
Normal file
541
CUSTOMIZATION.md
Normal file
@@ -0,0 +1,541 @@
|
||||
# Hugo + PaperMod Customization Guide
|
||||
|
||||
This guide covers how to customize your Hugo portfolio site using the PaperMod theme.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Project Structure](#project-structure)
|
||||
2. [Development Workflow](#development-workflow)
|
||||
3. [Configuration (hugo.yaml)](#configuration-hugoyaml)
|
||||
4. [Adding Content](#adding-content)
|
||||
5. [Creating New Sections](#creating-new-sections)
|
||||
6. [Customizing Styles (CSS)](#customizing-styles-css)
|
||||
7. [Overriding Layouts](#overriding-layouts)
|
||||
8. [Common Customizations](#common-customizations)
|
||||
9. [Deployment](#deployment)
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
MyPortfolio/
|
||||
├── archetypes/ # Templates for new content
|
||||
├── assets/
|
||||
│ └── css/
|
||||
│ └── extended/
|
||||
│ └── custom.css # YOUR custom CSS (this gets merged)
|
||||
├── content/
|
||||
│ ├── posts/ # Long-form articles
|
||||
│ │ └── _index.md # Section config
|
||||
│ ├── notes/ # Short notes/TILs
|
||||
│ │ └── _index.md # Section config
|
||||
│ └── search.md # Search page
|
||||
├── layouts/
|
||||
│ ├── index.html # Custom home page (no recent posts)
|
||||
│ ├── partials/ # Override theme partials
|
||||
│ │ ├── footer.html # Custom footer
|
||||
│ │ └── templates/
|
||||
│ │ └── schema_json.html # Fixed JSON-LD schema
|
||||
│ ├── posts/
|
||||
│ │ └── list.html # Custom posts list (compact style)
|
||||
│ └── notes/
|
||||
│ └── list.html # Custom notes list (compact style)
|
||||
├── static/
|
||||
│ └── files/ # Static files (CV.pdf, images, etc.)
|
||||
├── themes/
|
||||
│ └── PaperMod/ # Theme (git submodule - DON'T edit)
|
||||
├── hugo.yaml # Main configuration
|
||||
└── .gitignore # Ignores public/, etc.
|
||||
```
|
||||
|
||||
### Key Principle: Never Edit the Theme Directly
|
||||
|
||||
The `themes/PaperMod/` folder is a git submodule. To customize:
|
||||
- **CSS**: Add to `assets/css/extended/custom.css`
|
||||
- **Layouts**: Copy the file from `themes/PaperMod/layouts/` to `layouts/` and modify
|
||||
- **Config**: Everything in `hugo.yaml`
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Start dev server (watches for changes)
|
||||
hugo serve
|
||||
|
||||
# With drafts visible
|
||||
hugo serve -D
|
||||
|
||||
# Build for production (outputs to public/)
|
||||
hugo build --minify
|
||||
```
|
||||
|
||||
### Why `public/` is Gitignored
|
||||
|
||||
- `public/` is generated output - it's rebuilt fresh every time
|
||||
- Your CI/CD (GitHub Actions) builds it during deployment
|
||||
- Never commit build artifacts to git
|
||||
|
||||
### Git Workflow
|
||||
|
||||
```bash
|
||||
# After making changes
|
||||
git add .
|
||||
git commit -m "Add new post about X"
|
||||
git push origin master # Triggers deployment
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration (hugo.yaml)
|
||||
|
||||
### Key Sections
|
||||
|
||||
#### Site Basics
|
||||
```yaml
|
||||
baseURL: "/" # Use "/" for relative URLs (works on localhost + prod)
|
||||
relativeURLs: true # Makes all URLs relative
|
||||
title: Saurav Dhakal
|
||||
languageCode: en-us
|
||||
theme: ["PaperMod"]
|
||||
```
|
||||
|
||||
#### Menu (Header Navigation)
|
||||
```yaml
|
||||
menu:
|
||||
main:
|
||||
- identifier: posts # Internal ID
|
||||
name: Posts # Display name
|
||||
url: /posts/ # URL path
|
||||
weight: 10 # Order (lower = first)
|
||||
- identifier: notes
|
||||
name: Notes
|
||||
url: /notes/
|
||||
weight: 20
|
||||
- identifier: tags
|
||||
name: Tags
|
||||
url: /tags/
|
||||
weight: 30
|
||||
- identifier: search
|
||||
name: Search
|
||||
url: /search/
|
||||
weight: 40
|
||||
```
|
||||
|
||||
#### Home Page Info
|
||||
```yaml
|
||||
params:
|
||||
homeInfoParams:
|
||||
Title: "Hi there 👋, I'm Saurav!"
|
||||
Content: >
|
||||
I'm a software engineer...
|
||||
```
|
||||
|
||||
#### Social Icons
|
||||
```yaml
|
||||
params:
|
||||
socialIcons:
|
||||
- name: github
|
||||
url: "https://github.com/yourusername"
|
||||
- name: linkedin
|
||||
url: "https://linkedin.com/in/yourusername"
|
||||
- name: x
|
||||
url: "https://x.com/yourusername"
|
||||
```
|
||||
|
||||
Available icons: github, linkedin, x, twitter, email, rss, youtube, instagram, facebook, stackoverflow, etc.
|
||||
|
||||
---
|
||||
|
||||
## Adding Content
|
||||
|
||||
### New Post
|
||||
|
||||
```bash
|
||||
hugo new posts/my-new-post.md
|
||||
```
|
||||
|
||||
Or manually create `content/posts/my-new-post.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "My New Post"
|
||||
date: 2025-08-20
|
||||
summary: "A brief description for the list view"
|
||||
tags: ["tag1", "tag2"]
|
||||
categories: ["Category"]
|
||||
draft: false
|
||||
ShowToc: true
|
||||
TocOpen: false
|
||||
---
|
||||
|
||||
Your content here...
|
||||
```
|
||||
|
||||
### New Note
|
||||
|
||||
Create `content/notes/my-note.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Quick TIL"
|
||||
date: 2025-08-20
|
||||
summary: "Today I learned about X"
|
||||
tags: ["til"]
|
||||
---
|
||||
|
||||
Short content here...
|
||||
```
|
||||
|
||||
### Front Matter Options
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `title` | Post title |
|
||||
| `date` | Publication date (YYYY-MM-DD) |
|
||||
| `summary` | Short description for list views |
|
||||
| `tags` | Array of tags |
|
||||
| `categories` | Array of categories |
|
||||
| `draft` | If `true`, won't be published |
|
||||
| `ShowToc` | Show table of contents |
|
||||
| `TocOpen` | TOC expanded by default |
|
||||
| `ShowReadingTime` | Override global setting |
|
||||
| `ShowWordCount` | Override global setting |
|
||||
| `cover.image` | Cover image path |
|
||||
|
||||
---
|
||||
|
||||
## Creating New Sections
|
||||
|
||||
### Example: Adding a "Projects" Section
|
||||
|
||||
1. **Add to menu** in `hugo.yaml`:
|
||||
```yaml
|
||||
menu:
|
||||
main:
|
||||
# ... existing items
|
||||
- identifier: projects
|
||||
name: Projects
|
||||
url: /projects/
|
||||
weight: 25
|
||||
```
|
||||
|
||||
2. **Create content directory**:
|
||||
```bash
|
||||
mkdir -p content/projects
|
||||
```
|
||||
|
||||
3. **Create section index** `content/projects/_index.md`:
|
||||
```markdown
|
||||
---
|
||||
title: "Projects"
|
||||
description: "Things I've built"
|
||||
---
|
||||
```
|
||||
|
||||
4. **Add project pages** `content/projects/my-project.md`:
|
||||
```markdown
|
||||
---
|
||||
title: "My Cool Project"
|
||||
date: 2025-01-15
|
||||
summary: "A brief description"
|
||||
tags: ["golang", "cli"]
|
||||
---
|
||||
|
||||
Project description...
|
||||
```
|
||||
|
||||
5. **(Optional) Custom layout** - If you want projects to look different, create `layouts/projects/list.html`
|
||||
|
||||
---
|
||||
|
||||
## Customizing Styles (CSS)
|
||||
|
||||
### Where to Add CSS
|
||||
|
||||
**Only edit**: `assets/css/extended/custom.css`
|
||||
|
||||
PaperMod automatically includes this file. You don't need to import it anywhere.
|
||||
|
||||
### CSS Variables (Theme Colors)
|
||||
|
||||
PaperMod uses CSS variables. Override them in your custom.css:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Light mode */
|
||||
--primary: #282828; /* Main text */
|
||||
--secondary: #3c3836; /* Secondary text */
|
||||
--tertiary: rgb(214, 214, 214); /* Borders, etc */
|
||||
--theme: rgb(255, 255, 255); /* Background */
|
||||
--entry: rgb(255, 255, 255); /* Card background */
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
/* Dark mode */
|
||||
--primary: #fbf1c7;
|
||||
--secondary: #ebdbb2;
|
||||
--theme: #181818;
|
||||
--entry: rgb(46, 46, 51);
|
||||
}
|
||||
```
|
||||
|
||||
### Common CSS Customizations
|
||||
|
||||
#### Change fonts
|
||||
```css
|
||||
@import url("https://fonts.googleapis.com/css2?family=Your+Font&display=swap");
|
||||
|
||||
body {
|
||||
font-family: "Your Font", sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
#### Style links
|
||||
```css
|
||||
main a {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--green);
|
||||
}
|
||||
|
||||
main a:hover {
|
||||
background-color: var(--green);
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
#### Customize post cards
|
||||
```css
|
||||
main .post-entry {
|
||||
border: 2px solid #383838;
|
||||
background-color: var(--entry);
|
||||
border-radius: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Overriding Layouts
|
||||
|
||||
### How Layout Override Works
|
||||
|
||||
Hugo looks for templates in this order:
|
||||
1. `layouts/` (your overrides)
|
||||
2. `themes/PaperMod/layouts/` (theme defaults)
|
||||
|
||||
### To Override a Template
|
||||
|
||||
1. Find the file in `themes/PaperMod/layouts/`
|
||||
2. Copy it to the same path in `layouts/`
|
||||
3. Modify your copy
|
||||
|
||||
### Common Files to Override
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `layouts/partials/header.html` | Site header/nav |
|
||||
| `layouts/partials/footer.html` | Site footer |
|
||||
| `layouts/partials/post_meta.html` | Post metadata (date, read time) |
|
||||
| `layouts/_default/list.html` | List pages (posts, tags) |
|
||||
| `layouts/_default/single.html` | Individual post/page |
|
||||
|
||||
### Example: Simpler Footer
|
||||
|
||||
Your `layouts/partials/footer.html` already overrides the theme's footer.
|
||||
|
||||
### Section-Specific Layouts
|
||||
|
||||
Create `layouts/SECTION_NAME/list.html` for a custom list layout for that section.
|
||||
|
||||
**Current custom layouts:**
|
||||
|
||||
- `layouts/index.html` - Home page with just intro + social icons (no recent posts)
|
||||
- `layouts/posts/list.html` - Compact list: `# Title` + date + 2-line description
|
||||
- `layouts/notes/list.html` - Compact list: `# Title` + date (no description)
|
||||
|
||||
---
|
||||
|
||||
## Common Customizations
|
||||
|
||||
### Disable Reading Time for a Section
|
||||
|
||||
In the section's `_index.md`:
|
||||
```markdown
|
||||
---
|
||||
title: "Notes"
|
||||
ShowReadingTime: false
|
||||
---
|
||||
```
|
||||
|
||||
Or per-post in front matter.
|
||||
|
||||
### Add a Static Page (About, Contact)
|
||||
|
||||
Create `content/about.md`:
|
||||
```markdown
|
||||
---
|
||||
title: "About"
|
||||
layout: "single"
|
||||
url: "/about/"
|
||||
---
|
||||
|
||||
About me content...
|
||||
```
|
||||
|
||||
Add to menu:
|
||||
```yaml
|
||||
menu:
|
||||
main:
|
||||
- identifier: about
|
||||
name: About
|
||||
url: /about/
|
||||
weight: 50
|
||||
```
|
||||
|
||||
### Add Favicon
|
||||
|
||||
1. Place favicon files in `static/`:
|
||||
- `static/favicon.ico`
|
||||
- `static/favicon-16x16.png`
|
||||
- `static/favicon-32x32.png`
|
||||
- `static/apple-touch-icon.png`
|
||||
|
||||
2. Update `hugo.yaml`:
|
||||
```yaml
|
||||
params:
|
||||
assets:
|
||||
favicon: "/favicon.ico"
|
||||
favicon16x16: "/favicon-16x16.png"
|
||||
favicon32x32: "/favicon-32x32.png"
|
||||
apple_touch_icon: "/apple-touch-icon.png"
|
||||
```
|
||||
|
||||
### Enable Comments (Disqus)
|
||||
|
||||
```yaml
|
||||
params:
|
||||
comments: true
|
||||
|
||||
disqusShortname: "your-disqus-shortname"
|
||||
```
|
||||
|
||||
### Add Google Analytics
|
||||
|
||||
Already configured in your `hugo.yaml`:
|
||||
```yaml
|
||||
googleAnalytics: "G-XXXXXXXXXX"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Current Setup (GitHub Actions → SSH/SCP)
|
||||
|
||||
Your `.github/workflows/deploy.yml`:
|
||||
1. Triggers on push to `master`
|
||||
2. Builds site with `hugo build --minify`
|
||||
3. Deploys `public/` via SCP to your server
|
||||
|
||||
### Required GitHub Secrets
|
||||
|
||||
Set these in your repo's Settings → Secrets:
|
||||
- `SSH_HOST` - Your server hostname/IP
|
||||
- `SSH_USER` - SSH username
|
||||
- `SSH_KEY` - Private SSH key
|
||||
- `SSH_PORT` - SSH port (usually 22)
|
||||
|
||||
### Alternative: GitHub Pages
|
||||
|
||||
If you want to use GitHub Pages instead:
|
||||
|
||||
1. Change workflow to use `peaceiris/actions-gh-pages`
|
||||
2. Set `baseURL` to `https://yourusername.github.io/repo-name/`
|
||||
|
||||
### Alternative: Netlify/Vercel
|
||||
|
||||
These platforms auto-detect Hugo and build for you:
|
||||
1. Connect your GitHub repo
|
||||
2. Set build command: `hugo --minify`
|
||||
3. Set publish directory: `public`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Local dev
|
||||
hugo serve -D # Serve with drafts
|
||||
|
||||
# Create content
|
||||
hugo new posts/title.md # New post
|
||||
hugo new notes/title.md # New note
|
||||
|
||||
# Build
|
||||
hugo build --minify # Production build
|
||||
|
||||
# Debug
|
||||
hugo config # Show full config
|
||||
hugo list all # List all content
|
||||
```
|
||||
|
||||
### File Locations
|
||||
|
||||
| What | Where |
|
||||
|------|-------|
|
||||
| Config | `hugo.yaml` |
|
||||
| Custom CSS | `assets/css/extended/custom.css` |
|
||||
| Posts | `content/posts/` |
|
||||
| Notes | `content/notes/` |
|
||||
| Static files | `static/` |
|
||||
| Layout overrides | `layouts/` |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Links Go to Production URL in Dev
|
||||
|
||||
Fixed by setting:
|
||||
```yaml
|
||||
baseURL: "/"
|
||||
relativeURLs: true
|
||||
```
|
||||
|
||||
### Changes Not Showing
|
||||
|
||||
1. Hard refresh browser (Ctrl+Shift+R)
|
||||
2. Clear `public/` folder: `rm -rf public/`
|
||||
3. Restart hugo serve
|
||||
|
||||
### Theme Not Loading
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Search Not Working
|
||||
|
||||
Ensure `hugo.yaml` has:
|
||||
```yaml
|
||||
outputs:
|
||||
home:
|
||||
- HTML
|
||||
- RSS
|
||||
- JSON # Required for search
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Hugo Documentation](https://gohugo.io/documentation/)
|
||||
- [PaperMod Wiki](https://github.com/adityatelange/hugo-PaperMod/wiki)
|
||||
- [PaperMod Demo](https://adityatelange.github.io/hugo-PaperMod/)
|
||||
@@ -50,63 +50,124 @@
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--theme: #181818;
|
||||
--entry: rgb(46, 46, 51);
|
||||
--primary: #fbf1c7;
|
||||
--secondary: #ebdbb2;
|
||||
--tertiary: rgb(65, 66, 68);
|
||||
--content: rgb(196, 196, 197);
|
||||
--code-block-bg: rgb(46, 46, 51);
|
||||
--code-bg: rgb(55, 56, 62);
|
||||
--border: rgb(151, 51, 51);
|
||||
--theme: #1d2021;
|
||||
--entry: #282828;
|
||||
--primary: #ebdbb2;
|
||||
--secondary: #a89984;
|
||||
--tertiary: #3c3836;
|
||||
--content: #ebdbb2;
|
||||
--code-block-bg: #282828;
|
||||
--code-bg: #3c3836;
|
||||
--border: #3c3836;
|
||||
color-scheme: dark;
|
||||
|
||||
--post-entry-bg: #181818; /* bg1 */
|
||||
--post-entry-bg: #282828;
|
||||
--post-entry-fg: #ebdbb2;
|
||||
|
||||
--green: #b8bb26;
|
||||
--greenl: #8ec07c;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
}
|
||||
|
||||
nav #menu span {
|
||||
padding: 0px 2px;
|
||||
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--green);
|
||||
font-family: "Space Grotesk", monospace;
|
||||
}
|
||||
|
||||
/* *
|
||||
* NAV SECTION
|
||||
* UNIVERSAL HOVER ANIMATION (Fill-up Effect)
|
||||
* Target: Nav spans, main links (except entries), social links, and share icons.
|
||||
* */
|
||||
|
||||
nav #menu span:hover {
|
||||
|
||||
nav #menu a span {
|
||||
position: relative;
|
||||
text-decoration: none !important;
|
||||
color: inherit;
|
||||
transition: color 0.3s ease;
|
||||
z-index: 1;
|
||||
padding: 8px; /* Reduced vertical padding */
|
||||
}
|
||||
|
||||
.entry-content {
|
||||
color: var(--primary)
|
||||
}
|
||||
|
||||
main a:not(.entry-link, .anchor),
|
||||
.social-icons a,
|
||||
.share-buttons a,
|
||||
.note-item a {
|
||||
position: relative;
|
||||
text-decoration: none !important;
|
||||
color: inherit;
|
||||
transition: color 0.3s ease;
|
||||
z-index: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Note items are full-width flex containers */
|
||||
.note-item a {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
margin: 0 -12px; /* Offset padding for full-row effect */
|
||||
}
|
||||
|
||||
/* The fill background */
|
||||
nav #menu a span::before,
|
||||
main a:not(.entry-link, .anchor)::before,
|
||||
.social-icons a::before,
|
||||
.share-buttons a::before,
|
||||
.note-item a::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px; /* Static underline state */
|
||||
background-color: var(--green);
|
||||
color: white;
|
||||
z-index: -1;
|
||||
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
nav #menu .active {
|
||||
border: 0px;
|
||||
color: var(--green);
|
||||
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--green);
|
||||
/* Notes list starts with 0 height to stay clean */
|
||||
.note-item a::before,
|
||||
.share-buttons a::before {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* *
|
||||
* MAIN SECTION
|
||||
* */
|
||||
main a {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--green);
|
||||
/* Hover state: Fill up the background */
|
||||
nav #menu a:hover span::before,
|
||||
main a:hover:not(.entry-link, .anchor)::before,
|
||||
.social-icons a:hover::before,
|
||||
.share-buttons a:hover::before,
|
||||
.note-item a:hover::before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main a:hover:not(.entry-link) {
|
||||
background-color: var(--green);
|
||||
color: white;
|
||||
/* CONTRAST FIX: Switch text/icon color on hover */
|
||||
nav #menu a:hover span,
|
||||
main a:hover:not(.entry-link, .anchor),
|
||||
.social-icons a:hover,
|
||||
.share-buttons a:hover,
|
||||
#searchResults a:hover,
|
||||
.note-item a:hover .note-title,
|
||||
.note-item a:hover .note-date {
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
|
||||
/* Icon specific color switch */
|
||||
.social-icons a:hover svg,
|
||||
.share-buttons a:hover svg {
|
||||
fill: var(--theme) !important;
|
||||
stroke: var(--theme) !important;
|
||||
}
|
||||
|
||||
/* Active Nav State - remain thick underline */
|
||||
nav #menu .active span::before {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
nav #menu .active a:hover span::before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main h1 {
|
||||
@@ -117,12 +178,64 @@ main h1 {
|
||||
.toc,
|
||||
.post-tags a {
|
||||
border: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
.post-tags a {
|
||||
background: var(--tertiary);
|
||||
color: var(--primary);
|
||||
padding: 0px 8px;
|
||||
border-radius: 0px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.post-tags a:hover {
|
||||
background: var(--green);
|
||||
color: var(--theme);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.share-buttons {
|
||||
margin-top: 40px;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid var(--tertiary);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.share-buttons a {
|
||||
padding: 10px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.anchor {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
margin-bottom: 2rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.post-header .post-title {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--green);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.post-description {
|
||||
font-style: italic;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* main h1::before { */
|
||||
/* content: "#"; */
|
||||
/* color: var(--red); */
|
||||
@@ -134,8 +247,248 @@ main h1 {
|
||||
/* } */
|
||||
|
||||
main .post-entry {
|
||||
border: 0px;
|
||||
border: 1px solid var(--tertiary);
|
||||
background-color: var(--post-entry-bg);
|
||||
color: var(--post-entry-fg);
|
||||
border: 2px solid #383838;
|
||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
main .post-entry:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--green);
|
||||
}
|
||||
|
||||
/* *
|
||||
* NOTES SECTION - Compact list style
|
||||
* */
|
||||
.notes-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.note-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--tertiary);
|
||||
}
|
||||
|
||||
.note-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.note-item a:hover .note-title,
|
||||
.note-item a:hover .note-date {
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.note-date {
|
||||
font-size: 0.85rem;
|
||||
color: var(--secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.note-summary {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 0.96rem;
|
||||
color: var(--secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.delimiter {
|
||||
margin: 0 10px;
|
||||
font-weight: 800;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* *
|
||||
* POSTS SECTION - Same compact style as notes but with description
|
||||
* */
|
||||
.posts-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.post-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--tertiary);
|
||||
}
|
||||
|
||||
.post-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.post-item a {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
text-decoration: none !important;
|
||||
gap: 16px;
|
||||
padding: 8px 12px;
|
||||
margin: 0 -12px;
|
||||
color: inherit;
|
||||
transition: color 0.3s ease;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.post-item a::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
background-color: var(--green);
|
||||
z-index: -1;
|
||||
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.post-item a:hover::before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.post-item a:hover .post-title,
|
||||
.post-item a:hover .post-date {
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.post-date {
|
||||
font-size: 0.85rem;
|
||||
color: var(--secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.post-summary {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 0.96rem;
|
||||
color: var(--secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Common list styles */
|
||||
.note-item a,
|
||||
.post-item a {
|
||||
display: flex !important;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* *
|
||||
* POST CONTENT SPACINGS
|
||||
* */
|
||||
.post-content {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.post-content p,
|
||||
.post-content ol,
|
||||
.post-content ul,
|
||||
.post-content dl {
|
||||
margin-bottom: 1.6em;
|
||||
}
|
||||
|
||||
.post-content h1,
|
||||
.post-content h2,
|
||||
.post-content h3,
|
||||
.post-content h4 {
|
||||
margin-top: 1.8em;
|
||||
margin-bottom: 0.8em;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.post-content blockquote {
|
||||
margin: 2em 0;
|
||||
padding: 0.5em 1.5em;
|
||||
border-left: 4px solid var(--green);
|
||||
background-color: var(--entry);
|
||||
font-style: italic;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.post-content code {
|
||||
font-size: 16px;
|
||||
background-color: var(--tertiary);
|
||||
color: var(--primary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
padding: 0.6em;
|
||||
}
|
||||
|
||||
.post-content pre code {
|
||||
background-color: var(--entry) !important;
|
||||
}
|
||||
|
||||
.post-content blockquote p {
|
||||
margin: 0;
|
||||
padding: 0.5em 0em;
|
||||
}
|
||||
|
||||
/* *
|
||||
* SEARCH PAGE FIXES
|
||||
* */
|
||||
#searchResults .post-entry {
|
||||
border-radius: 0; /* Square borders like notes */
|
||||
padding: 0;
|
||||
border: 1px solid var(--tertiary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#searchResults a {
|
||||
position: relative;
|
||||
display: flex !important;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
text-decoration: none !important;
|
||||
color: var(--primary);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#searchResults a .search-meta {
|
||||
margin-left: auto; /* Push date to the right */
|
||||
font-size: 0.85rem;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
#searchResults a:hover .search-title,
|
||||
#searchResults a:hover .search-meta {
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
|
||||
#searchResults a::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
background-color: var(--green);
|
||||
z-index: -1;
|
||||
transition: height 0.3s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
#searchResults a:hover::before {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#searchResults a:hover {
|
||||
color: var(--theme) !important;
|
||||
}
|
||||
|
||||
153
assets/js/fastsearch.js
Normal file
153
assets/js/fastsearch.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as params from '@params';
|
||||
|
||||
let fuse; // holds our search engine
|
||||
let resList = document.getElementById('searchResults');
|
||||
let sInput = document.getElementById('searchInput');
|
||||
let first, last, current_elem = null
|
||||
let resultsAvailable = false;
|
||||
|
||||
// load our search index
|
||||
window.onload = function () {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
if (data) {
|
||||
// fuse.js options; check fuse.js website for details
|
||||
let options = {
|
||||
distance: 100,
|
||||
threshold: 0.4,
|
||||
ignoreLocation: true,
|
||||
keys: [
|
||||
'title',
|
||||
'permalink',
|
||||
'summary',
|
||||
'content'
|
||||
]
|
||||
};
|
||||
if (params.fuseOpts) {
|
||||
options = {
|
||||
isCaseSensitive: params.fuseOpts.iscasesensitive ?? false,
|
||||
includeScore: params.fuseOpts.includescore ?? false,
|
||||
includeMatches: params.fuseOpts.includematches ?? false,
|
||||
minMatchCharLength: params.fuseOpts.minmatchcharlength ?? 1,
|
||||
shouldSort: params.fuseOpts.shouldsort ?? true,
|
||||
findAllMatches: params.fuseOpts.findallmatches ?? false,
|
||||
keys: params.fuseOpts.keys ?? ['title', 'permalink', 'summary', 'content'],
|
||||
location: params.fuseOpts.location ?? 0,
|
||||
threshold: params.fuseOpts.threshold ?? 0.4,
|
||||
distance: params.fuseOpts.distance ?? 100,
|
||||
ignoreLocation: params.fuseOpts.ignorelocation ?? true
|
||||
}
|
||||
}
|
||||
fuse = new Fuse(data, options); // build the index from the json file
|
||||
}
|
||||
} else {
|
||||
console.log(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open('GET', "../index.json");
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function activeToggle(ae) {
|
||||
document.querySelectorAll('.focus').forEach(function (element) {
|
||||
// rm focus class
|
||||
element.classList.remove("focus")
|
||||
});
|
||||
if (ae) {
|
||||
ae.focus()
|
||||
document.activeElement = current_elem = ae;
|
||||
ae.parentElement.classList.add("focus")
|
||||
} else {
|
||||
document.activeElement.parentElement.classList.add("focus")
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resultsAvailable = false;
|
||||
resList.innerHTML = sInput.value = ''; // clear inputbox and searchResults
|
||||
sInput.focus(); // shift focus to input box
|
||||
}
|
||||
|
||||
// execute search as each character is typed
|
||||
sInput.onkeyup = function (e) {
|
||||
// run a search query (for "term") every time a letter is typed
|
||||
// in the search box
|
||||
if (fuse) {
|
||||
let results;
|
||||
if (params.fuseOpts) {
|
||||
results = fuse.search(this.value.trim(), {limit: params.fuseOpts.limit}); // the actual query being run using fuse.js along with options
|
||||
} else {
|
||||
results = fuse.search(this.value.trim()); // the actual query being run using fuse.js
|
||||
}
|
||||
if (results.length !== 0) {
|
||||
// build our html if result exists
|
||||
let resultSet = ''; // our results bucket
|
||||
|
||||
for (let item in results) {
|
||||
resultSet += `<li class="post-entry"><a href="${results[item].item.permalink}" aria-label="${results[item].item.title}">` +
|
||||
`<div class="search-title"># ${results[item].item.title}</div>` +
|
||||
`<div class="search-meta"><span class="delimiter">•</span> ${results[item].item.date}</div></a></li>`
|
||||
}
|
||||
|
||||
resList.innerHTML = resultSet;
|
||||
resultsAvailable = true;
|
||||
first = resList.firstChild;
|
||||
last = resList.lastChild;
|
||||
} else {
|
||||
resultsAvailable = false;
|
||||
resList.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sInput.addEventListener('search', function (e) {
|
||||
// clicked on x
|
||||
if (!this.value) reset()
|
||||
})
|
||||
|
||||
// kb bindings
|
||||
document.onkeydown = function (e) {
|
||||
let key = e.key;
|
||||
let ae = document.activeElement;
|
||||
|
||||
let inbox = document.getElementById("searchbox").contains(ae)
|
||||
|
||||
if (ae === sInput) {
|
||||
let elements = document.getElementsByClassName('focus');
|
||||
while (elements.length > 0) {
|
||||
elements[0].classList.remove('focus');
|
||||
}
|
||||
} else if (current_elem) ae = current_elem;
|
||||
|
||||
if (key === "Escape") {
|
||||
reset()
|
||||
} else if (!resultsAvailable || !inbox) {
|
||||
return
|
||||
} else if (key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
if (ae == sInput) {
|
||||
// if the currently focused element is the search input, focus the <a> of first <li>
|
||||
activeToggle(resList.firstChild.lastChild);
|
||||
} else if (ae.parentElement != last) {
|
||||
// if the currently focused element's parent is last, do nothing
|
||||
// otherwise select the next search result
|
||||
activeToggle(ae.parentElement.nextSibling.lastChild);
|
||||
}
|
||||
} else if (key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
if (ae.parentElement == first) {
|
||||
// if the currently focused element is first item, go to input box
|
||||
activeToggle(sInput);
|
||||
} else if (ae != sInput) {
|
||||
// if the currently focused element is input box, do nothing
|
||||
// otherwise select the previous search result
|
||||
activeToggle(ae.parentElement.previousSibling.lastChild);
|
||||
}
|
||||
} else if (key === "ArrowRight") {
|
||||
ae.click(); // click on active link
|
||||
}
|
||||
}
|
||||
4
content/notes/_index.md
Normal file
4
content/notes/_index.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Notes"
|
||||
description: "Quick notes, TILs, and short thoughts"
|
||||
---
|
||||
11
content/notes/demo.md
Normal file
11
content/notes/demo.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "Sample Note"
|
||||
date: 2025-08-20
|
||||
# summary: "This is a sample note to demonstrate the notes section"
|
||||
tags: ["example"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
This is a sample note. Notes are meant to be short, quick thoughts or TILs (Today I Learned).
|
||||
|
||||
Delete this file and add your own notes here!
|
||||
4
content/posts/_index.md
Normal file
4
content/posts/_index.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Posts"
|
||||
description: "Long-form articles on software development, architecture, and engineering"
|
||||
---
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
author: ["Saurav Dhakal"]
|
||||
title: "Understanding Separation of Concerns (SoC) in NestJS"
|
||||
date: "2025-08-19"
|
||||
summary: "A guide to understanding Separation of Concerns in NestJS using modules, services, and controllers."
|
||||
tags: ["nestjs", "typescript", "architecture"]
|
||||
categories: ["Backend Development", "NestJS"]
|
||||
series: ["NestJS"]
|
||||
ShowToc: true
|
||||
TocOpen: false
|
||||
---
|
||||
|
||||
When building applications, one of the most important design principles to keep in mind is **Separation of Concerns (SoC)**. NestJS, with its modular architecture, makes applying SoC almost effortless — but understanding _why_ it matters and _how_ to use it properly will help you write cleaner, testable, and future-proof code.
|
||||
|
||||
## What is Separation of Concerns and Why it Matters?
|
||||
|
||||
The basic idea is:
|
||||
|
||||
> A program should be divided into distinct sections, where each section addresses a single responsibility.
|
||||
26
content/posts/my-first-post.md
Normal file
26
content/posts/my-first-post.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "The Art of the Digital Garden"
|
||||
date: 2026-04-01T08:55:00+05:45
|
||||
draft: false
|
||||
tags: ["philosophy", "gruvbox", "hugo"]
|
||||
description: "Exploring the concept of a digital garden and how a refined Gruvbox theme enhances the experience."
|
||||
---
|
||||
|
||||
Welcome to my digital garden. This is a place where I share my thoughts, projects, and lessons learned along the way. Unlike a traditional blog, a digital garden is always growing and evolving.
|
||||
|
||||
### Why Gruvbox?
|
||||
|
||||
Gruvbox is more than just a color scheme; it's a philosophy of contrast and harmony. By using the "Hard" contrast variant, we achieve a focus that is both easy on the eyes and aesthetically pleasing.
|
||||
|
||||
```javascript
|
||||
const theme = "Gruvbox Dark Hard";
|
||||
console.log(`Current theme: ${theme}`);
|
||||
```
|
||||
|
||||
### The Beauty of Hugo
|
||||
|
||||
Hugo allows for lightning-fast build times and complete control over the structure of our content. Paired with a minimal theme like PaperMod, it provides the perfect canvas for technical writing.
|
||||
|
||||
> "A garden is never finished."
|
||||
|
||||
I hope you enjoy exploring these notes and posts as much as I enjoy writing them.
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
title: "Search" # in any language you want
|
||||
layout: "search" # necessary for search
|
||||
# url: "/archive"
|
||||
# description: "Description for Search"
|
||||
summary: "search"
|
||||
placeholder: "placeholder text in search input box"
|
||||
title: "Search"
|
||||
layout: "search"
|
||||
url: "/search/"
|
||||
summary: "Search posts and notes"
|
||||
placeholder: "Search..."
|
||||
---
|
||||
|
||||
34
hugo.yaml
34
hugo.yaml
@@ -1,5 +1,5 @@
|
||||
# DEmo
|
||||
baseURL: https://example.org/
|
||||
baseURL: "/"
|
||||
relativeURLs: true
|
||||
languageCode: en-us
|
||||
title: Saurav Dhakal
|
||||
theme: ["PaperMod"]
|
||||
@@ -77,14 +77,12 @@ params:
|
||||
|
||||
# home-info mode
|
||||
homeInfoParams:
|
||||
Title: "Hi there \U0001F44B, I'm Saurav!"
|
||||
Title: "Hi there \U0001F44B, I’m Saurav!"
|
||||
Content: >
|
||||
I’m a software engineer who enjoys building thoughtful systems and learning how things really work.
|
||||
<br />
|
||||
<br /><br />
|
||||
This is my digital garden - notes, projects, and lessons along the way.
|
||||
|
||||
- <br />
|
||||
|
||||
<br /><br />
|
||||
Checkout my [CV](files/CV.pdf) for my works and projects.
|
||||
|
||||
socialIcons:
|
||||
@@ -97,7 +95,7 @@ params:
|
||||
|
||||
analytics:
|
||||
google:
|
||||
SiteVerificationTag: "G-V0CXG8ZEG2"
|
||||
SiteVerificationTag: ""
|
||||
|
||||
cover:
|
||||
hidden: true # hide everywhere but not in structured data
|
||||
@@ -123,16 +121,27 @@ params:
|
||||
menu:
|
||||
main:
|
||||
- identifier: posts
|
||||
name: posts
|
||||
name: Posts
|
||||
url: /posts/
|
||||
weight: 10
|
||||
- identifier: tags
|
||||
name: tags
|
||||
url: /tags/
|
||||
- identifier: notes
|
||||
name: Notes
|
||||
url: /notes/
|
||||
weight: 20
|
||||
- identifier: tags
|
||||
name: Tags
|
||||
url: /tags/
|
||||
weight: 30
|
||||
- identifier: search
|
||||
name: Search
|
||||
url: /search/
|
||||
weight: 40
|
||||
# Read: https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#using-hugos-syntax-highlighter-chroma
|
||||
pygmentsUseClasses: true
|
||||
markup:
|
||||
goldmark:
|
||||
renderer:
|
||||
unsafe: true
|
||||
highlight:
|
||||
noClasses: false
|
||||
# anchorLineNos: true
|
||||
@@ -140,3 +149,4 @@ markup:
|
||||
# guessSyntax: true
|
||||
# lineNos: true
|
||||
# style: monokai
|
||||
googleAnalytics: "G-V0CXG8ZEG2"
|
||||
|
||||
7
layouts/_default/index.json
Normal file
7
layouts/_default/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{{- $.Scratch.Add "index" slice -}}
|
||||
{{- range site.RegularPages -}}
|
||||
{{- if and (not .Params.searchHidden) (ne .Layout `archives`) (ne .Layout `search`) }}
|
||||
{{- $.Scratch.Add "index" (dict "title" .Title "content" .Plain "permalink" .Permalink "summary" .Summary "date" (.Date.Format "Jan 2, 2006")) -}}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- $.Scratch.Get "index" | jsonify -}}
|
||||
19
layouts/index.html
Normal file
19
layouts/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{{- define "main" }}
|
||||
|
||||
{{- if .Site.Params.homeInfoParams }}
|
||||
<article class="first-entry home-info">
|
||||
<header class="entry-header">
|
||||
<h1>{{ .Site.Params.homeInfoParams.Title | markdownify }}</h1>
|
||||
</header>
|
||||
<div class="entry-content">
|
||||
{{ .Site.Params.homeInfoParams.Content | markdownify }}
|
||||
</div>
|
||||
<footer class="entry-footer">
|
||||
{{- partial "social_icons.html" (dict "align" site.Params.homeInfoParams.AlignSocialIconsTo) }}
|
||||
</footer>
|
||||
</article>
|
||||
{{- end }}
|
||||
|
||||
{{- /* No recent posts/notes - clean home page */ -}}
|
||||
|
||||
{{- end }}{{/* end main */}}
|
||||
22
layouts/notes/list.html
Normal file
22
layouts/notes/list.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{{- define "main" }}
|
||||
|
||||
<header class="page-header">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{- if .Description }}
|
||||
<div class="post-description">{{ .Description }}</div>
|
||||
{{- end }}
|
||||
</header>
|
||||
|
||||
<ul class="notes-list">
|
||||
{{- range .Pages }}
|
||||
<li class="note-item">
|
||||
<a href="{{ .Permalink }}">
|
||||
<span class="note-title"># {{ .Title }}</span>
|
||||
<span class="delimiter">•</span>
|
||||
<span class="note-date">{{ .Date.Format "Jan 2, 2006" }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
|
||||
{{- end }}{{/* end main */}}
|
||||
@@ -12,7 +12,7 @@
|
||||
{{- end }}
|
||||
|
||||
{{- if (not site.Params.disableScrollToTop) }}
|
||||
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
|
||||
<a href="#top" aria-label="go to top" title="Go to Top" class="top-link" id="top-link" accesskey="g">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
|
||||
<path d="M12 6H0l6-6z" />
|
||||
</svg>
|
||||
|
||||
113
layouts/partials/templates/schema_json.html
Normal file
113
layouts/partials/templates/schema_json.html
Normal file
@@ -0,0 +1,113 @@
|
||||
{{ if .IsHome }}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "{{- ( site.Params.schema.publisherType | default "Organization") | title -}}",
|
||||
"name": {{ site.Title | jsonify }},
|
||||
"url": {{ site.Home.Permalink | jsonify }},
|
||||
"description": {{ site.Params.description | plainify | truncate 180 | jsonify }},
|
||||
{{- if (eq site.Params.schema.publisherType "Person") }}
|
||||
"image": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }},
|
||||
{{- else }}
|
||||
"logo": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }},
|
||||
{{- end }}
|
||||
"sameAs": [
|
||||
{{- if site.Params.schema.sameAs }}
|
||||
{{ range $i, $e := site.Params.schema.sameAs }}{{ if $i }}, {{ end }}{{ trim $e " " | jsonify }}{{ end }}
|
||||
{{- else}}
|
||||
{{ range $i, $e := site.Params.SocialIcons }}{{ if $i }}, {{ end }}{{ trim $e.url " " | jsonify }}{{ end }}
|
||||
{{- end}}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{{- else if (or .IsPage .IsSection) }}
|
||||
{{/* BreadcrumbList */}}
|
||||
{{- $url := replace .Parent.Permalink ( printf "%s" site.Home.Permalink) "" }}
|
||||
{{- $lang_url := strings.TrimPrefix ( printf "%s/" .Lang) $url }}
|
||||
{{- $bc_list := (split $lang_url "/")}}
|
||||
|
||||
{{- $scratch := newScratch }}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{{- range $index, $element := $bc_list }}
|
||||
|
||||
{{- $scratch.Add "path" (printf "%s/" $element ) | safeJS }}
|
||||
{{- $bc_pg := site.GetPage ($scratch.Get "path") -}}
|
||||
|
||||
{{- if (and ($bc_pg) (gt (len . ) 0))}}
|
||||
{{- if $index }}, {{end }}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": {{ add 1 $index }},
|
||||
"name": {{ $bc_pg.Name | jsonify }},
|
||||
"item": {{ $bc_pg.Permalink | jsonify }}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{- end }}
|
||||
{{- /* self-page addition */ -}}
|
||||
, {
|
||||
"@type": "ListItem",
|
||||
"position": {{ add (len $bc_list) 1 }},
|
||||
"name": {{ .Name | jsonify }},
|
||||
"item": {{ .Permalink | jsonify }}
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{{- if .IsPage }}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BlogPosting",
|
||||
"headline": {{ .Title | plainify | jsonify }},
|
||||
"name": {{ .Title | plainify | jsonify }},
|
||||
"description": {{ with .Description | plainify }}{{ . | jsonify }}{{ else }}{{ .Summary | plainify | truncate 150 | jsonify }}{{ end }},
|
||||
"keywords": [
|
||||
{{- if .Params.keywords }}
|
||||
{{ range $i, $e := .Params.keywords }}{{ if $i }}, {{ end }}{{ $e | jsonify }}{{ end }}
|
||||
{{- else }}
|
||||
{{ range $i, $e := .Params.tags }}{{ if $i }}, {{ end }}{{ $e | jsonify }}{{ end }}
|
||||
{{- end }}
|
||||
],
|
||||
"wordCount": {{ .WordCount }},
|
||||
"inLanguage": {{ .Language.Lang | default "en-us" | jsonify }},
|
||||
"datePublished": {{ .PublishDate.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
|
||||
"dateModified": {{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | jsonify }},
|
||||
{{- with (.Params.author | default site.Params.author) }}
|
||||
"author":
|
||||
{{- if (or (eq (printf "%T" .) "[]string") (eq (printf "%T" .) "[]interface {}")) -}}
|
||||
[{{- range $i, $v := . -}}
|
||||
{{- if $i }}, {{end -}}
|
||||
{
|
||||
"@type": "Person",
|
||||
"name": {{ $v | jsonify }}
|
||||
}
|
||||
{{- end }}],
|
||||
{{- else -}}
|
||||
{
|
||||
"@type": "Person",
|
||||
"name": {{ . | jsonify }}
|
||||
},
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": {{ .Permalink | jsonify }}
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "{{- ( site.Params.schema.publisherType | default "Organization") | title -}}",
|
||||
"name": {{ site.Title | jsonify }},
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": {{ site.Params.assets.favicon | default "favicon.ico" | absURL | jsonify }}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{{- end }}{{/* .IsPage end */}}
|
||||
|
||||
{{- end -}}
|
||||
97
layouts/partials/toc.html
Normal file
97
layouts/partials/toc.html
Normal file
@@ -0,0 +1,97 @@
|
||||
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
|
||||
{{- $has_headers := ge (len $headers) 1 -}}
|
||||
{{- if $has_headers -}}
|
||||
<div class="toc">
|
||||
<details {{if (.Param "TocOpen") }} open{{ end }}>
|
||||
<summary accesskey="c" title="">
|
||||
<span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
|
||||
</summary>
|
||||
|
||||
<div class="inner">
|
||||
{{- if (.Param "UseHugoToc") }}
|
||||
{{- .TableOfContents -}}
|
||||
{{- else }}
|
||||
{{- $largest := 6 -}}
|
||||
{{- range $headers -}}
|
||||
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
|
||||
{{- $headerLevel := len (seq $headerLevel) -}}
|
||||
{{- if lt $headerLevel $largest -}}
|
||||
{{- $largest = $headerLevel -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}
|
||||
|
||||
{{- $.Scratch.Set "bareul" slice -}}
|
||||
<ul>
|
||||
{{- range seq (sub $firstHeaderLevel $largest) -}}
|
||||
<ul>
|
||||
{{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
|
||||
{{- end -}}
|
||||
{{- range $i, $header := $headers -}}
|
||||
{{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
|
||||
{{- $headerLevel := len (seq $headerLevel) -}}
|
||||
|
||||
{{/* get id="xyz" */}}
|
||||
{{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}
|
||||
|
||||
{{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
|
||||
{{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
|
||||
{{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}
|
||||
|
||||
{{- if ne $i 0 -}}
|
||||
{{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
|
||||
{{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
|
||||
{{- if gt $headerLevel $prevHeaderLevel -}}
|
||||
{{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
|
||||
<ul>
|
||||
{{/* the first should not be recorded */}}
|
||||
{{- if ne $prevHeaderLevel . -}}
|
||||
{{- $.Scratch.Add "bareul" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
</li>
|
||||
{{- if lt $headerLevel $prevHeaderLevel -}}
|
||||
{{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
|
||||
{{- if in ($.Scratch.Get "bareul") . -}}
|
||||
</ul>
|
||||
{{/* manually do pop item */}}
|
||||
{{- $tmp := $.Scratch.Get "bareul" -}}
|
||||
{{- $.Scratch.Delete "bareul" -}}
|
||||
{{- $.Scratch.Set "bareul" slice}}
|
||||
{{- range seq (sub (len $tmp) 1) -}}
|
||||
{{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
</ul>
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
<li>
|
||||
<a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify | safeHTML -}}">{{- $header | plainify | safeHTML -}}</a>
|
||||
{{- else }}
|
||||
<li>
|
||||
<a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify | safeHTML -}}">{{- $header | plainify | safeHTML -}}</a>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} -->
|
||||
{{- $firstHeaderLevel := $largest }}
|
||||
{{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
|
||||
</li>
|
||||
{{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
|
||||
{{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
|
||||
</ul>
|
||||
{{- else }}
|
||||
</ul>
|
||||
</li>
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{- end }}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{{- end }}
|
||||
25
layouts/posts/list.html
Normal file
25
layouts/posts/list.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{{- define "main" }}
|
||||
|
||||
<header class="page-header">
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{- if .Description }}
|
||||
<div class="post-description">{{ .Description }}</div>
|
||||
{{- end }}
|
||||
</header>
|
||||
|
||||
<ul class="posts-list">
|
||||
{{- range .Pages }}
|
||||
<li class="post-item">
|
||||
<a href="{{ .Permalink }}">
|
||||
<span class="post-title"># {{ .Title }}</span>
|
||||
<span class="delimiter">•</span>
|
||||
<span class="post-date">{{ .Date.Format "Jan 2, 2006" }}</span>
|
||||
</a>
|
||||
{{- if .Summary }}
|
||||
<p class="post-summary">{{ .Summary | plainify | truncate 150 }}</p>
|
||||
{{- end }}
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
|
||||
{{- end }}{{/* end main */}}
|
||||
@@ -1,2 +0,0 @@
|
||||
<!doctype html><html lang=en dir=auto data-theme=dark><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="index, follow"><title>404 Page not found | Saurav Dhakal</title><meta name=keywords content><meta name=description content="A personal blog"><meta name=author content="saurav"><link rel=canonical href=http://localhost:1313/404.html><meta name=google-site-verification content="G-V0CXG8ZEG2"><link crossorigin=anonymous href=/assets/css/stylesheet.1819d1b52fd9f2af4d88316fde3f9b918c48d08dac9a2e1ef0a7ca49d1f18ddb.css integrity="sha256-GBnRtS/Z8q9NiDFv3j+bkYxI0I2smi4e8KfKSdHxjds=" rel="preload stylesheet" as=style><link rel=icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=icon type=image/png sizes=16x16 href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=icon type=image/png sizes=32x32 href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=apple-touch-icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><link rel=mask-icon href=http://localhost:1313/%3Clink%20/%20abs%20url%3E><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate hreflang=en href=http://localhost:1313/404.html><noscript><style>#theme-toggle,.top-link{display:none}</style></noscript><meta property="og:url" content="http://localhost:1313/404.html"><meta property="og:site_name" content="Saurav Dhakal"><meta property="og:title" content="404 Page not found"><meta property="og:description" content="A personal blog"><meta property="og:locale" content="en-us"><meta property="og:type" content="website"><meta name=twitter:card content="summary"><meta name=twitter:title content="404 Page not found"><meta name=twitter:description content="A personal blog"></head><body class=list id=top><header class=header><nav class=nav><div class=logo><a href=http://localhost:1313/ accesskey=h title="SauravDhakal (Alt + H)">SauravDhakal</a><div class=logo-switches></div></div><ul id=menu><li><a href=http://localhost:1313/posts/ title=posts><span>posts</span></a></li><li><a href=http://localhost:1313/tags/ title=tags><span>tags</span></a></li></ul></nav></header><main class=main><div class=not-found>404</div></main><footer class=footer><span>Copyright © 2025 SauravDhakal</span></footer><a href=#top aria-label="go to top" title="Go to Top (Alt + G)" class=top-link id=top-link accesskey=g><svg viewBox="0 0 12 6" fill="currentColor"><path d="M12 6H0l6-6z"/></svg>
|
||||
</a><script>let menu=document.getElementById("menu");if(menu){const e=localStorage.getItem("menu-scroll-position");e&&(menu.scrollLeft=parseInt(e,10)),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}}document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script><script>var mybutton=document.getElementById("top-link");window.onscroll=function(){document.body.scrollTop>800||document.documentElement.scrollTop>800?(mybutton.style.visibility="visible",mybutton.style.opacity="1"):(mybutton.style.visibility="hidden",mybutton.style.opacity="0")}</script></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user