Compare commits
20 Commits
2c34622207
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab36028ccf | ||
|
|
c50baca85a | ||
|
|
23a033acc3 | ||
|
|
570afd621d | ||
|
|
af7e549fc7 | ||
|
|
12f56ff11f | ||
|
|
caca000c7f | ||
|
|
2007e1f7b5 | ||
|
|
663e8c8d62 | ||
|
|
2dd65b7b48 | ||
|
|
3a1d508c65 | ||
|
|
0c4e6715de | ||
|
|
38baeb86e8 | ||
|
|
df6f488ccd | ||
|
|
1206387246 | ||
|
|
9995edb14d | ||
|
|
8a5439d2aa | ||
|
|
e3224e321e | ||
|
|
1c47537b54 | ||
|
|
c940743fd8 |
34
.github/workflows/deploy.yml
vendored
34
.github/workflows/deploy.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Deploy Hugo Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v3
|
||||
with:
|
||||
hugo-version: "latest"
|
||||
extended: true
|
||||
|
||||
- name: Build site
|
||||
run: hugo --minify
|
||||
|
||||
- name: Deploy via rsync
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.SSH_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
port: ${{ secrets.SSH_PORT }}
|
||||
source: "public/*"
|
||||
target: "/var/www/portfolio"
|
||||
strip_components: 1
|
||||
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
|
||||
12
.woodpecker.yml
Normal file
12
.woodpecker.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Test
|
||||
when:
|
||||
event: push
|
||||
branch: master
|
||||
|
||||
steps:
|
||||
build:
|
||||
image: hugomods/hugo:latest
|
||||
commands:
|
||||
- hugo --minify --destination /site/public
|
||||
volumes:
|
||||
- /home/saurav/site:/site # writes directly to where Caddy serves from
|
||||
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;
|
||||
}
|
||||
|
||||
BIN
assets/favicon.png
Normal file
BIN
assets/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
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"
|
||||
---
|
||||
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.
|
||||
105
content/posts/polymorphic-table-psql.md
Normal file
105
content/posts/polymorphic-table-psql.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: "The Likes Table Problem: Why We Went Polymorphic."
|
||||
date: 2026-04-01T21:50:00+05:45
|
||||
draft: false
|
||||
tags: ["architecture", "backend", "PSQL"]
|
||||
description: "Concept of polymorphic table in an SQL database"
|
||||
---
|
||||
|
||||
A few days ago, I was working on adding a **Community** section to an application. The idea was simple, users should be able to:
|
||||
|
||||
- Create posts
|
||||
- Leave comments
|
||||
- Like posts
|
||||
- Like comments
|
||||
|
||||
We also had a separate News section. The new requirement was users should be able to like news articles as well.
|
||||
|
||||
Building model for `posts` and `comments` was pretty straight forward. The real challenge was to model the`likes`table.
|
||||
|
||||
## The Problem: How Do We Store Likes?
|
||||
|
||||
We’re using PostgreSQL, so enforcing relationships with foreign keys is easy and clean.
|
||||
|
||||
If only **one** thing could be liked (say, News), the schema would be simple, we would have a `news_like` table which could look something like this:
|
||||
|
||||
| user_id | news_id | timestamp |
|
||||
| --- | --- | --- |
|
||||
| (foreign key) | (foreign key) | 1775059642 |
|
||||
|
||||
But we didn’t have one entity. We had three. `posts`, `comments` and `news`.
|
||||
|
||||
We had to decide:
|
||||
|
||||
> Do we create three separate like tables? Or do we design one flexible solution?
|
||||
|
||||
|
||||
## Option 1: Three Separate Tables
|
||||
|
||||
We could create three tables: `post_likes` , `comment_likes` and `news_likes` . Each table would have proper foreign key relationships. This approach would be a clean relational way of doing things. It:
|
||||
|
||||
- Keeps strong relational integrity
|
||||
- Makes joins easy
|
||||
- Keeps structure explicit
|
||||
|
||||
This is the most “pure relational” approach. But it felt repetitive. The schema grows horizontally.
|
||||
|
||||
And if tomorrow we add something else that can be liked, we’d need yet another table.
|
||||
|
||||
It works, but it doesn’t scale elegantly.
|
||||
|
||||
## Option 2: A Polymorphic Table (What We Chose)
|
||||
|
||||
Instead of multiple tables, we created a **single polymorphic likes table**.
|
||||
|
||||
### What is a Polymorphic Table?
|
||||
|
||||
A polymorphic table can reference multiple types of resources using a shared structure.
|
||||
|
||||
We designed our `likes` table to look something like this:
|
||||
|
||||
| user_id | resource_id | resource_type | timestamp |
|
||||
| --- | --- | --- | --- |
|
||||
| (foreign key) | (uuid) | (POST / COMMENT / NEWS) | |
|
||||
|
||||
Here’s how it works:
|
||||
|
||||
- `user_id`: who liked, foreign key to `users` table.
|
||||
- `resource_id` : the UUID of the item, just the uuid, no foreign key relation.
|
||||
- `resource_type` : what type of item it is
|
||||
- `timestamp` : timestamp
|
||||
|
||||
Instead of a strict foreign key to one table, we store:
|
||||
|
||||
- The ID
|
||||
- The type of resource
|
||||
|
||||
Together, they uniquely identify what was liked.
|
||||
|
||||
With this approach, we had one clean and centralized table to store all kinds of likes. It’s much easier to expand and flexible. Since “like” is a feature common to many parts of the system, this design keeps it generic and reusable.
|
||||
|
||||
But it does has a major downside. We lose direct foreign key enforcement on `resource_id`. Because PostgreSQL can’t enforce a foreign key that dynamically points to multiple tables, referential integrity must be handled at the application level. We cannot write a simple join query to join from `comments` table or `posts` table.
|
||||
|
||||
For example, to fetch likes for a resource:
|
||||
|
||||
```tsx
|
||||
SELECT COUNT(*)
|
||||
FROM likes
|
||||
WHERE resource_id = 'some-uuid'
|
||||
AND resource_type = 'POST';
|
||||
```
|
||||
|
||||
Now, if we need resource details plus likes, we may need separate queries or application-side logic.
|
||||
|
||||
For our use case though, that trade-off was acceptable. We don’t perform heavy cross-entity joins on likes, so the downside was minimal.
|
||||
|
||||
## Why This Design Felt Right
|
||||
|
||||
The key insight was this:
|
||||
|
||||
> “Like” is not tightly coupled to Posts, Comments, or News. It’s a behavior shared across resources.
|
||||
>
|
||||
|
||||
By modeling it polymorphically, we treated “like” as a reusable system capability rather than a feature embedded in each entity.
|
||||
|
||||
And as our application grows, this decision will likely save us refactoring time.
|
||||
@@ -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..."
|
||||
---
|
||||
|
||||
61
hugo.yaml
61
hugo.yaml
@@ -1,5 +1,5 @@
|
||||
# DEmo
|
||||
baseURL: https://example.org/
|
||||
baseURL: "/"
|
||||
relativeURLs: true
|
||||
languageCode: en-us
|
||||
title: Saurav Dhakal
|
||||
theme: ["PaperMod"]
|
||||
@@ -32,6 +32,7 @@ params:
|
||||
DateFormat: "January 2, 2006"
|
||||
defaultTheme: dark # dark, light
|
||||
disableThemeToggle: true
|
||||
googleAnalytics: "G-V0CXG8ZEG2"
|
||||
|
||||
ShowReadingTime: true
|
||||
ShowShareButtons: true
|
||||
@@ -51,11 +52,11 @@ params:
|
||||
assets:
|
||||
# disableHLJS: true # to disable highlight.js
|
||||
# disableFingerprinting: true
|
||||
favicon: "<link / abs url>"
|
||||
favicon16x16: "<link / abs url>"
|
||||
favicon32x32: "<link / abs url>"
|
||||
apple_touch_icon: "<link / abs url>"
|
||||
safari_pinned_tab: "<link / abs url>"
|
||||
favicon: ./favicon.png
|
||||
favicon16x16: ./favicon.png
|
||||
favicon32x32: ./favicon.png
|
||||
apple_touch_icon: ./favicon.png
|
||||
safari_pinned_tab: ./favicon.png
|
||||
|
||||
label:
|
||||
text: "SauravDhakal"
|
||||
@@ -77,27 +78,31 @@ 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 />
|
||||
This is my digital garden - notes, projects, and lessons along the way.
|
||||
<br /><br />
|
||||
Here I write about things I learn, find interesting, or want to remember. I'm a self-hosting and cloud enthusiast, actively experimenting with both.
|
||||
<br /><br />
|
||||
In my free time, I'm either reading or tinkering with something.
|
||||
<br /><br />
|
||||
Checkout my [CV](CV.pdf) for my works and projects.
|
||||
|
||||
- <br />
|
||||
|
||||
Checkout my [CV](files/CV.pdf) for my works and projects.
|
||||
#demo
|
||||
|
||||
socialIcons:
|
||||
- name: github
|
||||
url: "https://github.com/sauravdhakal12"
|
||||
- name: gitea
|
||||
url: "https://gitea.sauravdhakal.com.np/explore"
|
||||
- name: linkedin
|
||||
url: "https://www.linkedin.com/in/saurav-dhakal-9a8b27220/"
|
||||
- name: x
|
||||
url: "https://x.com/s0x1495"
|
||||
- name: dev
|
||||
url: "https://dev.to/sauravdhakal12/"
|
||||
|
||||
analytics:
|
||||
google:
|
||||
SiteVerificationTag: "G-V0CXG8ZEG2"
|
||||
SiteVerificationTag: ""
|
||||
|
||||
cover:
|
||||
hidden: true # hide everywhere but not in structured data
|
||||
@@ -123,16 +128,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 +156,8 @@ markup:
|
||||
# guessSyntax: true
|
||||
# lineNos: true
|
||||
# style: monokai
|
||||
googleAnalytics: "G-V0CXG8ZEG2"
|
||||
|
||||
services:
|
||||
googleAnalytics:
|
||||
id: "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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user