From 0be0dd7456e26590c720d6d29e8a47a984cf3ed2 Mon Sep 17 00:00:00 2001
From: root
Date: Fri, 13 Feb 2026 10:51:06 +0000
Subject: [PATCH] okay, pretty smooth. checkpoint here
---
cache.go | 40 -------------------
content/default.css | 54 ++++++++++++++++++++++++++
main.go | 5 +--
parse.go | 95 +++++++++++++++++++--------------------------
server.go | 63 ++++++++++++++++++++++--------
5 files changed, 143 insertions(+), 114 deletions(-)
delete mode 100644 cache.go
diff --git a/cache.go b/cache.go
deleted file mode 100644
index a7c063e..0000000
--- a/cache.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// cache.go
-package main
-
-import (
- "crypto/md5"
- "encoding/hex"
- "os"
- "path/filepath"
- "sync"
-)
-
-var (
- cacheDir = "cache"
- cacheMutex sync.Mutex
- TestMode bool
-)
-
-// InitCache ensures the cache directory exists
-func InitCache() {
- if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
- os.Mkdir(cacheDir, 0755)
- }
-}
-
-// ClearCache wipes the cache directory (used for Test Mode)
-func ClearCache() {
- os.RemoveAll(cacheDir)
- InitCache()
-}
-
-// GetCacheFilename generates the hashed filename preserving the extension
-func GetCacheFilename(key string) string {
- hash := md5.Sum([]byte(key))
- ext := filepath.Ext(key)
- // Default to .html if no extension (e.g. for processed markdown)
- if ext == "" || ext == ".md" {
- ext = ".html"
- }
- return filepath.Join(cacheDir, hex.EncodeToString(hash[:])+ext)
-}
diff --git a/content/default.css b/content/default.css
index 2e16ab0..651915b 100644
--- a/content/default.css
+++ b/content/default.css
@@ -306,4 +306,58 @@ blockquote {
.main-header h1 {
font-size: 2rem;
}
+}
+
+/* Latest Post Preview Section */
+.latest-post-preview {
+ margin-top: 4rem;
+ padding-top: 2rem;
+ border-top: 1px solid var(--sidebar-border);
+}
+
+.latest-post-preview h3 {
+ margin-top: 0;
+ margin-bottom: 2rem;
+ font-size: 1.5rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.preview-content {
+ position: relative;
+ max-height: 400px;
+ /* Limit height for the fade effect */
+ overflow: hidden;
+ margin-bottom: 2rem;
+
+ /* Fade Effect at the bottom */
+ -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
+ mask-image: linear-gradient(to bottom, black 60%, transparent 100%);
+}
+
+.read-more-container {
+ text-align: center;
+ margin-top: -2rem;
+ /* Pull up to cover the faded bottom area slightly or just sit below */
+ position: relative;
+ z-index: 10;
+}
+
+.read-more-btn {
+ display: inline-block;
+ padding: 10px 20px;
+ border: 1px solid var(--text-color);
+ text-decoration: none;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 0.9rem;
+ transition: all 0.3s ease;
+ background: var(--bg-color);
+ color: var(--text-color);
+}
+
+.read-more-btn:hover {
+ background: var(--text-color);
+ color: var(--bg-color);
+ border-color: var(--text-color);
}
\ No newline at end of file
diff --git a/main.go b/main.go
index 259c9b8..d2a8ce6 100644
--- a/main.go
+++ b/main.go
@@ -13,10 +13,7 @@ func main() {
InstallService()
return
case "test":
- fmt.Println("Starting in TEST MODE (Caching Disabled, Cache Cleared)")
- TestMode = true
- // Wipe the cache so we don't see old files
- ClearCache()
+ fmt.Println("Starting blog server (Test Mode: Caching Disabled via architecture)")
StartServer()
return
case "addTag":
diff --git a/parse.go b/parse.go
index 8b0ee1e..191a90f 100644
--- a/parse.go
+++ b/parse.go
@@ -21,16 +21,16 @@ import (
"gopkg.in/yaml.v3"
)
-// GetContentPath ensures the content is cached and returns the path to the cached file.
-func GetContentPath(file ContentFile) (string, error) {
- cachePath := GetCacheFilename(file.OriginalPath)
+// RenderContent parses the file and returns the rendered HTML content as bytes.
+// It performs no caching.
+func RenderContent(file ContentFile) ([]byte, error) {
// Read Raw File
raw, err := ReadRaw(file.OriginalPath)
if err != nil {
- return "", fmt.Errorf("failed to read file: %w", err)
+ return nil, fmt.Errorf("failed to read file: %w", err)
}
var dataToWrite []byte
@@ -55,7 +55,7 @@ func GetContentPath(file ContentFile) (string, error) {
var buf bytes.Buffer
ctx := parser.NewContext()
if err := md.Convert(raw, &buf, parser.WithContext(ctx)); err != nil {
- return "", fmt.Errorf("failed to parse markdown: %w", err)
+ return nil, fmt.Errorf("failed to parse markdown: %w", err)
}
// Extract Frontmatter
@@ -148,8 +148,6 @@ func GetContentPath(file ContentFile) (string, error) {
postRaw, err := ReadRaw(latestPost.OriginalPath)
if err == nil {
// Parse the post to HTML to display as preview
- // We need to parse it fully to get the HTML content
- // Re-using the MD parser config from above would be cleaner but let's instantiate for now
mdPreview := goldmark.New(
goldmark.WithExtensions(
extension.GFM,
@@ -164,43 +162,51 @@ func GetContentPath(file ContentFile) (string, error) {
var previewBuf bytes.Buffer
ctxPreview := parser.NewContext()
if err := mdPreview.Convert(postRaw, &previewBuf, parser.WithContext(ctxPreview)); err == nil {
- // Extract Metadata of the post to update Page Title?
- // User said: "the [title] be big text embedded in the black area."
- // So if we are previewing the latest post, the Home Page Title should probably match the Post Title on the Home Page?
- // Or should it say "Latest: Title"?
- // The main header h1 is where {{Title}} goes.
- // Getting metadata from preview context
+ // Extract Metadata of the post (unused for now, but kept for future reference)
var postMeta templates.PageMetadata
dPost := frontmatter.Get(ctxPreview)
if dPost != nil {
dPost.Decode(&postMeta)
}
- // Fallback title logic
- if postMeta.Title == "" {
- base := filepath.Base(latestPost.OriginalPath)
- postMeta.Title = strings.TrimSuffix(base, filepath.Ext(base))
- }
-
- // Update the meta for the HOME PAGE render
- meta.Title = postMeta.Title // Use the post's title
-
- // Extract Preview (First Paragraph?)
- // "preview of the latest post, then read more"
+ // DO NOT overwrite meta.Title for the Home Page. Keep it as "Index" or whatever index.md has.
+
fullHTML := previewBuf.String()
- // Simple strategy: take everything up to the first
or limit chars?
- // Or explicit split?
- // Let's take the first paragraph.
+ // Extract Preview (First 3 Paragraphs?)
+ // Strategy: count tags?
+ // Let's capture approx 1500 chars or 3 paragraphs, whichever is shorter/better.
+ // Actually, splitting by
is safer for HTML validation.
+
parts := strings.Split(fullHTML, "")
- if len(parts) > 0 {
- previewHTML := parts[0] + ""
- // Add Read More Link
- readMore := fmt.Sprintf(`Read More...
`, latestPost.RoutePath)
- htmlContent = previewHTML + readMore
- } else {
- htmlContent = fullHTML
+ var previewHTML string
+ limit := 3
+ if len(parts) < limit {
+ limit = len(parts)
}
+
+ for i := 0; i < limit; i++ {
+ if strings.TrimSpace(parts[i]) != "" {
+ previewHTML += parts[i] + ""
+ }
+ }
+
+ // Add Fade/Blur Container
+ // Append to existing htmlContent (which currently holds index.md content)
+ // Add a header for the preview?
+
+ previewBlock := fmt.Sprintf(`
+
+
Latest: %s
+
+ %s
+
+
+
`, postMeta.Title, previewHTML, latestPost.RoutePath)
+
+ htmlContent += previewBlock
}
}
}
@@ -407,26 +413,7 @@ func GetContentPath(file ContentFile) (string, error) {
dataToWrite = templates.BuildFullPage([]byte(htmlContent), meta, latestPostsHTML)
}
- // 4. Return Content Object or Path?
- // The function signature returns (string, error), which is the path to the cached file.
- // Since we are disabling cache, we should probably change the architecture to return ([]byte, error)
- // or write to a temporary file if the rest of the app expects a file path.
- // The rest of the app (server.go) does http.ServeFile(w, r, cachePath).
- // So we MUST return a file path.
-
- // For now, we will continue to write to the "cache" location, but since we removed the read check,
- // it basically acts as a "render to temp file" on every request.
- // This satisfies the "disable caching" requirement (always fresh) while keeping the file-based serving architecture.
-
- tmpPath := cachePath + ".tmp"
- if err := os.WriteFile(tmpPath, dataToWrite, 0644); err != nil {
- return "", fmt.Errorf("failed to write cache: %w", err)
- }
- if err := os.Rename(tmpPath, cachePath); err != nil {
- return "", fmt.Errorf("failed to commit cache: %w", err)
- }
-
- return cachePath, nil
+ return dataToWrite, nil
}
// loadExternalMetadata checks for .yml, .yaml, .toml files and loads them.
diff --git a/server.go b/server.go
index f6b4968..9364eb4 100644
--- a/server.go
+++ b/server.go
@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"net/http"
+ "path/filepath"
"strings"
)
@@ -12,9 +13,9 @@ var globalRoutes RouteMap
// StartServer initializes the routing and starts the HTTP listener.
func StartServer() {
- InitCache()
+ // InitCache() - Removed
- // Initial Scan
+ // Initial Scan (Optional, just to valid startup)
var err error
globalRoutes, err = ScanContent("content")
if err != nil {
@@ -31,34 +32,64 @@ func StartServer() {
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
- // 1. Handle Test Mode (Re-scan on every request)
- if TestMode {
- w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
- routes, err := ScanContent("content")
- if err == nil {
- globalRoutes = routes
- }
+ // 1. Scan Content on Every Request (Dynamic)
+ // This ensures we always have the latest file list.
+ // In a high-traffic production env, we'd use fsnotify, but for a personal blog, this is fine.
+ routes, err := ScanContent("content")
+ if err != nil {
+ http.Error(w, "Failed to scan content", http.StatusInternalServerError)
+ log.Printf("Scan error: %v", err)
+ return
}
+ globalRoutes = routes
// 2. Normalize Request Path
reqPath := strings.ToLower(r.URL.Path)
-
+
+ // Handle /Favicon.svg directly if needed, or rely on it being in content?
+ // If Favicon.svg is in content/Favicon.svg, it will be in routes as /favicon.svg
+
// 3. Look up in Route Map
file, found := globalRoutes[reqPath]
if !found {
+ // Try default file if this is a directory?
+ // Global routes scan handles index.md -> /
http.NotFound(w, r)
return
}
- // 4. Get Content Path (Ensures cache is fresh)
- cachePath, err := GetContentPath(file)
+ // 4. Render Content
+ contentBytes, err := RenderContent(file)
if err != nil {
- log.Printf("Error serving %s: %v", file.OriginalPath, err)
+ log.Printf("Error rendering %s: %v", file.OriginalPath, err)
http.Error(w, "Internal Server Error", 500)
return
}
- // 5. Serve File
- // http.ServeFile handles Content-Type, Range requests, and Caching headers automatically.
- http.ServeFile(w, r, cachePath)
+ // 5. Serve Content
+ // Detect Content-Type based on extension for non-markdown
+ if !file.IsMarkdown {
+ ext := strings.ToLower(filepath.Ext(file.OriginalPath))
+ switch ext {
+ case ".css":
+ w.Header().Set("Content-Type", "text/css")
+ case ".js":
+ w.Header().Set("Content-Type", "application/javascript")
+ case ".svg":
+ w.Header().Set("Content-Type", "image/svg+xml")
+ case ".png":
+ w.Header().Set("Content-Type", "image/png")
+ case ".jpg", ".jpeg":
+ w.Header().Set("Content-Type", "image/jpeg")
+ }
+ } else {
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ }
+
+ // Disable browser caching to ensure changes are seen immediately
+ w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
+
+ if _, err := w.Write(contentBytes); err != nil {
+ log.Printf("Error writing response: %v", err)
+ }
}