Add comprehensive theme documentation and improve migration script
- Add THEMES-HOWTO.md: Complete guide for creating and customizing themes - Remove theme sections from how-it-works.md to avoid duplication - Update migration script to place all blog posts in single directory - Streamline documentation structure for better organization
This commit is contained in:
parent
f72fe18873
commit
81ffa53d70
822
THEMES-HOWTO.md
Normal file
822
THEMES-HOWTO.md
Normal file
@ -0,0 +1,822 @@
|
|||||||
|
# qsgen3 Themes How-To Guide
|
||||||
|
|
||||||
|
This guide explains how to create and customize themes for qsgen3, based on how the code actually interprets and processes themes.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Theme Basics](#theme-basics)
|
||||||
|
2. [Theme Directory Structure](#theme-directory-structure)
|
||||||
|
3. [Creating Your First Theme](#creating-your-first-theme)
|
||||||
|
4. [Layout Templates](#layout-templates)
|
||||||
|
5. [Static Assets (CSS, JS, Images)](#static-assets-css-js-images)
|
||||||
|
6. [Theme Configuration](#theme-configuration)
|
||||||
|
7. [Advanced Theme Features](#advanced-theme-features)
|
||||||
|
8. [Theme Best Practices](#theme-best-practices)
|
||||||
|
9. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Theme Basics
|
||||||
|
|
||||||
|
### What is a Theme?
|
||||||
|
|
||||||
|
A qsgen3 theme is a collection of layout templates and static assets (CSS, JavaScript, images) that define the visual appearance and structure of your generated website. Themes allow you to:
|
||||||
|
|
||||||
|
- Customize the HTML structure of your pages
|
||||||
|
- Apply custom CSS styling
|
||||||
|
- Include JavaScript functionality
|
||||||
|
- Override default layouts with theme-specific designs
|
||||||
|
|
||||||
|
### How qsgen3 Processes Themes
|
||||||
|
|
||||||
|
When you specify a theme in your `site.conf`, qsgen3 follows this processing order:
|
||||||
|
|
||||||
|
1. **Theme Detection**: Looks for the theme directory at `themes/{theme_name}/`
|
||||||
|
2. **Layout Override**: If the theme has a `layouts/` directory, it replaces the default layouts
|
||||||
|
3. **Static File Copying**: Copies static files from both root `static/` and theme `static/` directories
|
||||||
|
4. **CSS Linking**: Automatically links the theme's main CSS file in generated HTML
|
||||||
|
|
||||||
|
## Theme Directory Structure
|
||||||
|
|
||||||
|
### Standard Theme Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
themes/
|
||||||
|
└── your-theme-name/
|
||||||
|
├── layouts/ # Optional: Custom layout templates
|
||||||
|
│ ├── index.html # Homepage layout
|
||||||
|
│ ├── post.html # Blog post layout
|
||||||
|
│ ├── page.html # Static page layout
|
||||||
|
│ └── rss.xml # RSS feed template
|
||||||
|
└── static/ # Theme's static assets
|
||||||
|
├── css/
|
||||||
|
│ └── style.css # Main theme CSS
|
||||||
|
├── js/
|
||||||
|
│ └── theme.js # Theme JavaScript
|
||||||
|
└── images/
|
||||||
|
└── logo.png # Theme images
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative Structure (Legacy Support)
|
||||||
|
|
||||||
|
For themes that don't use the `static/` subdirectory:
|
||||||
|
|
||||||
|
```
|
||||||
|
themes/
|
||||||
|
└── your-theme-name/
|
||||||
|
├── layouts/ # Optional: Custom layout templates
|
||||||
|
├── css/ # CSS files directly in theme root
|
||||||
|
│ └── style.css
|
||||||
|
├── js/ # JavaScript files
|
||||||
|
└── images/ # Image files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Your First Theme
|
||||||
|
|
||||||
|
### Step 1: Create the Theme Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p themes/my-theme/static/css
|
||||||
|
mkdir -p themes/my-theme/static/js
|
||||||
|
mkdir -p themes/my-theme/layouts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Create a Basic CSS File
|
||||||
|
|
||||||
|
Create `themes/my-theme/static/css/style.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Basic theme styles */
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Configure Your Site
|
||||||
|
|
||||||
|
Update your `site.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Theme configuration
|
||||||
|
site_theme="my-theme"
|
||||||
|
site_theme_css_file="css/style.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Test Your Theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/qsgen3
|
||||||
|
```
|
||||||
|
|
||||||
|
Your site should now use your custom theme!
|
||||||
|
|
||||||
|
## Layout Templates
|
||||||
|
|
||||||
|
### Understanding Pandoc Templates
|
||||||
|
|
||||||
|
qsgen3 uses Pandoc templates with special variable syntax:
|
||||||
|
|
||||||
|
- `$variable$` - Simple variable substitution
|
||||||
|
- `$if(variable)$...$endif$` - Conditional blocks
|
||||||
|
- `$for(list)$...$endfor$` - Loop over lists
|
||||||
|
- `$body$` - The main content (converted from Markdown)
|
||||||
|
|
||||||
|
### Available Variables
|
||||||
|
|
||||||
|
#### Site-wide Variables (from site.conf)
|
||||||
|
- `$site_name$` - Your site's name
|
||||||
|
- `$site_tagline$` - Your site's tagline
|
||||||
|
- `$site_url$` - Your site's URL
|
||||||
|
- `$current_year$` - Current year (auto-generated)
|
||||||
|
|
||||||
|
#### Content Variables (from Markdown frontmatter)
|
||||||
|
- `$title$` - Page/post title
|
||||||
|
- `$author$` - Content author
|
||||||
|
- `$date$` - Publication date
|
||||||
|
- `$description$` - Page description
|
||||||
|
- `$body$` - The converted Markdown content
|
||||||
|
|
||||||
|
#### Special Variables
|
||||||
|
- `$css$` - CSS file paths (handled automatically)
|
||||||
|
- `$math$` - Math rendering support (if enabled)
|
||||||
|
|
||||||
|
### Creating Custom Layouts
|
||||||
|
|
||||||
|
#### Basic Post Layout (`layouts/post.html`)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$site_name$ - $title$</title>
|
||||||
|
<meta name="author" content="$author$">
|
||||||
|
<meta name="description" content="$description$">
|
||||||
|
$if(date)$<meta name="date" content="$date$">$endif$
|
||||||
|
$for(css)$
|
||||||
|
<link rel="stylesheet" href="$css$">
|
||||||
|
$endfor$
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1><a href="/">$site_name$</a></h1>
|
||||||
|
<p>$site_tagline$</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>$title$</h1>
|
||||||
|
$if(author)$<p class="author">By: $author$</p>$endif$
|
||||||
|
$if(date)$<p class="date">Published: $date$</p>$endif$
|
||||||
|
</header>
|
||||||
|
$body$
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Index Page Layout (`layouts/index.html`)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$site_name$ - $site_tagline$</title>
|
||||||
|
<meta name="description" content="$site_tagline$">
|
||||||
|
$for(css)$
|
||||||
|
<link rel="stylesheet" href="$css$">
|
||||||
|
$endfor$
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>$site_name$</h1>
|
||||||
|
<p>$site_tagline$</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
$body$
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Static Assets (CSS, JS, Images)
|
||||||
|
|
||||||
|
### How Static Files Are Processed
|
||||||
|
|
||||||
|
qsgen3 copies static files in this order:
|
||||||
|
|
||||||
|
1. **Root Static Files**: Copies from `static/` to `output/static/`
|
||||||
|
2. **Theme Static Files**: Copies from `themes/{theme}/static/` to `output/static/` (overwrites root files)
|
||||||
|
|
||||||
|
This means theme files take precedence over root static files.
|
||||||
|
|
||||||
|
### CSS File Linking
|
||||||
|
|
||||||
|
The main theme CSS file is automatically linked in all generated HTML pages using the `--css` flag passed to Pandoc. The CSS path is determined by:
|
||||||
|
|
||||||
|
1. `site_theme_css_file` setting in `site.conf` (recommended)
|
||||||
|
2. `site_theme_css_path` setting (legacy, deprecated)
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
```bash
|
||||||
|
site_theme="my-theme"
|
||||||
|
site_theme_css_file="css/style.css" # Relative to theme's static/ directory
|
||||||
|
```
|
||||||
|
|
||||||
|
This results in:
|
||||||
|
- CSS file copied to: `output/static/css/style.css`
|
||||||
|
- HTML links to: `/static/css/style.css`
|
||||||
|
|
||||||
|
### Adding Additional Assets
|
||||||
|
|
||||||
|
#### JavaScript Files
|
||||||
|
|
||||||
|
Create `themes/my-theme/static/js/theme.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Theme-specific JavaScript
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('My theme loaded!');
|
||||||
|
|
||||||
|
// Add smooth scrolling
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||||
|
anchor.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.querySelector(this.getAttribute('href')).scrollIntoView({
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Include in your layout:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="/static/js/theme.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Images and Other Assets
|
||||||
|
|
||||||
|
Place images in `themes/my-theme/static/images/` and reference them in your CSS or templates:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.logo {
|
||||||
|
background-image: url('/static/images/logo.png');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="/static/images/hero.jpg" alt="Hero image">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Configuration
|
||||||
|
|
||||||
|
### Required Configuration
|
||||||
|
|
||||||
|
In your `site.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Minimum theme configuration
|
||||||
|
site_theme="theme-name" # Name of theme directory
|
||||||
|
site_theme_css_file="css/main.css" # Path to main CSS file
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Optional: Override default paths
|
||||||
|
paths_layouts_dir="layouts" # Will be overridden if theme has layouts/
|
||||||
|
paths_static_dir="static" # Root static directory
|
||||||
|
paths_output_dir="output" # Output directory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme-Specific Variables
|
||||||
|
|
||||||
|
You can add custom variables to your `site.conf` and use them in templates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Custom theme variables
|
||||||
|
theme_color_primary="#3498db"
|
||||||
|
theme_color_secondary="#2c3e50"
|
||||||
|
theme_font_family="'Roboto', sans-serif"
|
||||||
|
```
|
||||||
|
|
||||||
|
Use in templates:
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: $theme_color_primary$;
|
||||||
|
--secondary-color: $theme_color_secondary$;
|
||||||
|
--font-family: $theme_font_family$;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Theme Features
|
||||||
|
|
||||||
|
### Responsive Design
|
||||||
|
|
||||||
|
Create responsive themes using CSS media queries:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Mobile-first approach */
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
max-width: 750px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.container {
|
||||||
|
max-width: 970px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dark Mode Support
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Default (light) theme */
|
||||||
|
:root {
|
||||||
|
--bg-color: #ffffff;
|
||||||
|
--text-color: #333333;
|
||||||
|
--accent-color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg-color: #1a1a1a;
|
||||||
|
--text-color: #e0e0e0;
|
||||||
|
--accent-color: #5dade2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography and Web Fonts
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Import Google Fonts */
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom RSS Template
|
||||||
|
|
||||||
|
Create `layouts/rss.xml` for custom RSS styling:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>$site_name$</title>
|
||||||
|
<description>$site_tagline$</description>
|
||||||
|
<link>$site_url$</link>
|
||||||
|
<atom:link href="$site_url$/rss.xml" rel="self" type="application/rss+xml"/>
|
||||||
|
<language>en-us</language>
|
||||||
|
<lastBuildDate>$build_date$</lastBuildDate>
|
||||||
|
|
||||||
|
$for(posts)$
|
||||||
|
<item>
|
||||||
|
<title>$it.post_title$</title>
|
||||||
|
<description>$it.post_description$</description>
|
||||||
|
<link>$it.post_url$</link>
|
||||||
|
<guid>$it.post_url$</guid>
|
||||||
|
<pubDate>$it.post_date_rfc$</pubDate>
|
||||||
|
</item>
|
||||||
|
$endfor$
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Best Practices
|
||||||
|
|
||||||
|
### 1. Design Principles
|
||||||
|
|
||||||
|
- **Mobile-First**: Design for mobile devices first, then enhance for larger screens
|
||||||
|
- **Accessibility**: Use semantic HTML, proper contrast ratios, and keyboard navigation
|
||||||
|
- **Performance**: Optimize images, minimize CSS/JS, use efficient selectors
|
||||||
|
- **Consistency**: Maintain consistent spacing, typography, and color schemes
|
||||||
|
|
||||||
|
### 2. File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
themes/my-theme/
|
||||||
|
├── static/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── main.css # Main theme styles
|
||||||
|
│ │ ├── components.css # Component-specific styles
|
||||||
|
│ │ └── utilities.css # Utility classes
|
||||||
|
│ ├── js/
|
||||||
|
│ │ ├── theme.js # Main theme JavaScript
|
||||||
|
│ │ └── components/ # Component-specific JS
|
||||||
|
│ └── images/
|
||||||
|
│ ├── icons/ # Icon files
|
||||||
|
│ └── backgrounds/ # Background images
|
||||||
|
└── layouts/
|
||||||
|
├── index.html # Homepage layout
|
||||||
|
├── post.html # Blog post layout
|
||||||
|
├── page.html # Static page layout
|
||||||
|
└── rss.xml # RSS feed template
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. CSS Architecture
|
||||||
|
|
||||||
|
Use a modular approach:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* main.css */
|
||||||
|
@import url('base.css'); /* Reset and base styles */
|
||||||
|
@import url('layout.css'); /* Layout components */
|
||||||
|
@import url('components.css'); /* UI components */
|
||||||
|
@import url('utilities.css'); /* Utility classes */
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Performance Optimization
|
||||||
|
|
||||||
|
- **Minimize HTTP Requests**: Combine CSS/JS files when possible
|
||||||
|
- **Optimize Images**: Use appropriate formats (WebP, SVG) and sizes
|
||||||
|
- **Use CSS Custom Properties**: For maintainable theming
|
||||||
|
- **Lazy Load**: Implement lazy loading for images and non-critical resources
|
||||||
|
|
||||||
|
### 5. Browser Compatibility
|
||||||
|
|
||||||
|
Test your theme across different browsers and devices:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Provide fallbacks for modern CSS features */
|
||||||
|
.card {
|
||||||
|
background: #ffffff; /* Fallback */
|
||||||
|
background: var(--card-bg, #ffffff); /* Custom property */
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: block; /* Fallback */
|
||||||
|
display: grid; /* Modern */
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### 1. CSS Not Loading
|
||||||
|
|
||||||
|
**Problem**: Your theme's CSS isn't being applied.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check that `site_theme_css_file` path is correct relative to theme's `static/` directory
|
||||||
|
- Verify the CSS file exists at `themes/{theme}/static/{css_path}`
|
||||||
|
- Check qsgen3 logs for CSS-related warnings
|
||||||
|
- Ensure the theme directory name matches `site_theme` setting
|
||||||
|
|
||||||
|
#### 2. Layouts Not Being Used
|
||||||
|
|
||||||
|
**Problem**: Your custom layouts aren't being applied.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Verify layouts exist in `themes/{theme}/layouts/`
|
||||||
|
- Check that layout filenames match expected names (`index.html`, `post.html`, `page.html`)
|
||||||
|
- Review qsgen3 logs for layout-related messages
|
||||||
|
|
||||||
|
#### 3. Static Files Not Copying
|
||||||
|
|
||||||
|
**Problem**: Images, JS, or other static files aren't appearing in output.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check that files are in `themes/{theme}/static/` directory
|
||||||
|
- Verify file permissions are readable
|
||||||
|
- Look for rsync/cp errors in qsgen3 logs
|
||||||
|
- Ensure no .gitignore rules are excluding files
|
||||||
|
|
||||||
|
#### 4. Template Variables Not Working
|
||||||
|
|
||||||
|
**Problem**: Variables like `$site_name$` aren't being substituted.
|
||||||
|
|
||||||
|
**Solutions**:
|
||||||
|
- Check that variables are defined in `site.conf`
|
||||||
|
- Verify variable names match exactly (case-sensitive)
|
||||||
|
- Ensure Pandoc template syntax is correct
|
||||||
|
- Test with a minimal template to isolate issues
|
||||||
|
|
||||||
|
### Debugging Tips
|
||||||
|
|
||||||
|
#### 1. Enable Debug Logging
|
||||||
|
|
||||||
|
Run qsgen3 with verbose output to see detailed processing information:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable debug logging (if supported by your qsgen3 version)
|
||||||
|
QSG_LOG_LEVEL=DEBUG ./bin/qsgen3
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Test with Minimal Theme
|
||||||
|
|
||||||
|
Create a minimal theme to isolate issues:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- minimal-test.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>$title$</title>
|
||||||
|
<style>body { font-family: Arial; margin: 2rem; }</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>$title$</h1>
|
||||||
|
$body$
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Validate Generated HTML
|
||||||
|
|
||||||
|
Check that your generated HTML is valid:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use HTML validator tools
|
||||||
|
htmlhint output/*.html
|
||||||
|
# or
|
||||||
|
w3c-validator output/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Check File Permissions
|
||||||
|
|
||||||
|
Ensure qsgen3 can read your theme files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find themes/my-theme -type f -not -perm -644
|
||||||
|
find themes/my-theme -type d -not -perm -755
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
If you encounter issues not covered here:
|
||||||
|
|
||||||
|
1. Check the qsgen3 documentation and examples
|
||||||
|
2. Review existing themes in the `themes/` directory
|
||||||
|
3. Examine the qsgen3 source code for theme processing logic
|
||||||
|
4. Create a minimal reproduction case
|
||||||
|
5. Report issues with detailed logs and configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example: Complete Theme Creation
|
||||||
|
|
||||||
|
Here's a complete example of creating a modern, responsive theme called "modern-blog":
|
||||||
|
|
||||||
|
### 1. Create Directory Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p themes/modern-blog/{layouts,static/{css,js,images}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Main CSS (`themes/modern-blog/static/css/style.css`)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Modern Blog Theme */
|
||||||
|
:root {
|
||||||
|
--primary-color: #2563eb;
|
||||||
|
--secondary-color: #64748b;
|
||||||
|
--background-color: #ffffff;
|
||||||
|
--text-color: #1e293b;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--background-color);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 a {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header p {
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article header h1 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
color: var(--secondary-color);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta .author,
|
||||||
|
.meta .date {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: var(--text-color);
|
||||||
|
color: var(--background-color);
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem 0;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Post Layout (`themes/modern-blog/layouts/post.html`)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$site_name$ - $title$</title>
|
||||||
|
<meta name="author" content="$author$">
|
||||||
|
<meta name="description" content="$description$">
|
||||||
|
$if(date)$<meta name="date" content="$date$">$endif$
|
||||||
|
$for(css)$
|
||||||
|
<link rel="stylesheet" href="$css$">
|
||||||
|
$endfor$
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="container">
|
||||||
|
<h1><a href="/">$site_name$</a></h1>
|
||||||
|
<p>$site_tagline$</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="container">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h1>$title$</h1>
|
||||||
|
<div class="meta">
|
||||||
|
$if(author)$<span class="author">By: $author$</span>$endif$
|
||||||
|
$if(date)$<span class="date">Published: $date$</span>$endif$
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
$body$
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<p>© $current_year$ $site_name$. Generated by qsgen3.</p>
|
||||||
|
<p><a href="$site_url$">$site_url$</a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Configure Site (`site.conf`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
site_theme="modern-blog"
|
||||||
|
site_theme_css_file="css/style.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Generate Site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/qsgen3
|
||||||
|
```
|
||||||
|
|
||||||
|
Your modern, responsive blog theme is now ready!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This guide covers everything you need to know about creating themes for qsgen3. Remember that themes are processed in a specific order, and understanding this processing flow is key to creating effective themes that work reliably across different configurations.
|
296
bin/qsgen3
296
bin/qsgen3
@ -620,11 +620,13 @@ _clean_output_dir() {
|
|||||||
_log INFO "Found preserve file: $preserve_file"
|
_log INFO "Found preserve file: $preserve_file"
|
||||||
_log DEBUG "Performing selective cleaning with file preservation"
|
_log DEBUG "Performing selective cleaning with file preservation"
|
||||||
|
|
||||||
# Create temporary directory to store preserved files
|
# Use in-memory arrays to store file paths and content
|
||||||
local temp_preserve_dir=$(mktemp -d)
|
local files_to_preserve=()
|
||||||
|
local preserved_file_paths=()
|
||||||
|
local preserved_file_contents=()
|
||||||
local files_preserved=0
|
local files_preserved=0
|
||||||
|
|
||||||
# Read preserve patterns and backup matching files
|
# Read preserve patterns and collect matching files
|
||||||
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
|
while IFS= read -r pattern || [[ -n "$pattern" ]]; do
|
||||||
# Skip empty lines and comments
|
# Skip empty lines and comments
|
||||||
[[ -z "$pattern" || "$pattern" == \#* ]] && continue
|
[[ -z "$pattern" || "$pattern" == \#* ]] && continue
|
||||||
@ -640,17 +642,16 @@ _clean_output_dir() {
|
|||||||
if [[ -f "$file" ]]; then
|
if [[ -f "$file" ]]; then
|
||||||
# Get relative path from output directory
|
# Get relative path from output directory
|
||||||
local rel_path="${file#$output_dir/}"
|
local rel_path="${file#$output_dir/}"
|
||||||
local preserve_path="$temp_preserve_dir/$rel_path"
|
|
||||||
|
|
||||||
# Create directory structure in temp location
|
# Read file content into memory
|
||||||
mkdir -p "$(dirname "$preserve_path")"
|
local file_content
|
||||||
|
if file_content=$(<"$file"); then
|
||||||
# Copy file to preserve location
|
preserved_file_paths+=("$rel_path")
|
||||||
if cp "$file" "$preserve_path"; then
|
preserved_file_contents+=("$file_content")
|
||||||
files_preserved=$((files_preserved + 1))
|
files_preserved=$((files_preserved + 1))
|
||||||
_log DEBUG "Preserved file: $rel_path"
|
_log DEBUG "Preserved file in memory: $rel_path"
|
||||||
else
|
else
|
||||||
_log WARNING "Failed to preserve file: $rel_path"
|
_log WARNING "Failed to read file for preservation: $rel_path"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null)
|
done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null)
|
||||||
@ -658,29 +659,36 @@ _clean_output_dir() {
|
|||||||
done < "$preserve_file"
|
done < "$preserve_file"
|
||||||
|
|
||||||
# Remove the output directory
|
# Remove the output directory
|
||||||
_log DEBUG "Removing output directory contents (preserving $files_preserved files)"
|
_log DEBUG "Removing output directory contents (preserving $files_preserved files in memory)"
|
||||||
rm -rf "$output_dir"
|
rm -rf "$output_dir"
|
||||||
|
|
||||||
# Recreate output directory
|
# Recreate output directory
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
_log ERROR "Failed to recreate output directory: $output_dir"
|
_log ERROR "Failed to recreate output directory: $output_dir"
|
||||||
rm -rf "$temp_preserve_dir"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Restore preserved files
|
# Restore preserved files from memory
|
||||||
if [[ $files_preserved -gt 0 ]]; then
|
if [[ $files_preserved -gt 0 ]]; then
|
||||||
_log DEBUG "Restoring $files_preserved preserved files"
|
_log DEBUG "Restoring $files_preserved preserved files from memory"
|
||||||
if [[ -d "$temp_preserve_dir" ]]; then
|
for ((i=0; i<${#preserved_file_paths[@]}; i++)); do
|
||||||
# Copy preserved files back, maintaining directory structure
|
local rel_path="${preserved_file_paths[i]}"
|
||||||
(cd "$temp_preserve_dir" && find . -type f -exec cp --parents {} "$output_dir/" \; 2>/dev/null)
|
local file_content="${preserved_file_contents[i]}"
|
||||||
_log INFO "Restored $files_preserved preserved files"
|
local full_path="$output_dir/$rel_path"
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up temporary directory
|
# Create directory structure if needed
|
||||||
rm -rf "$temp_preserve_dir"
|
mkdir -p "$(dirname "$full_path")"
|
||||||
|
|
||||||
|
# Write file content back
|
||||||
|
if printf '%s' "$file_content" > "$full_path"; then
|
||||||
|
_log DEBUG "Restored file from memory: $rel_path"
|
||||||
|
else
|
||||||
|
_log WARNING "Failed to restore file from memory: $rel_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
_log INFO "Restored $files_preserved preserved files from memory"
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
# No preserve file - do complete cleaning as before
|
# No preserve file - do complete cleaning as before
|
||||||
@ -794,14 +802,6 @@ _copy_static_files() {
|
|||||||
_generate_index_page() {
|
_generate_index_page() {
|
||||||
_log DEBUG "Entered _generate_index_page"
|
_log DEBUG "Entered _generate_index_page"
|
||||||
|
|
||||||
local temp_yaml_file
|
|
||||||
temp_yaml_file=$(mktemp -t qsgen3_index_yaml.XXXXXX)
|
|
||||||
if [[ $? -ne 0 || -z "$temp_yaml_file" || ! -f "$temp_yaml_file" ]]; then
|
|
||||||
_log ERROR "Failed to create temporary file for index YAML. Exiting."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
trap "_log DEBUG \"Cleaning up temporary index YAML file: ${temp_yaml_file}\"; rm -f \"${temp_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
|
|
||||||
_log DEBUG "Created temporary YAML file for index: $temp_yaml_file"
|
|
||||||
_log INFO "Generating index page..."
|
_log INFO "Generating index page..."
|
||||||
local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
|
local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
|
||||||
local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html"
|
local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html"
|
||||||
@ -892,119 +892,87 @@ _generate_index_page() {
|
|||||||
|
|
||||||
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
|
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
|
||||||
_log DEBUG "No posts found to add to index YAML."
|
_log DEBUG "No posts found to add to index YAML."
|
||||||
yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
|
yaml_lines+=( "$(printf ' [] # Explicit empty list for posts')" )
|
||||||
else
|
else
|
||||||
for detail_line in "${sorted_posts[@]}"; do
|
for post_line in "${sorted_posts[@]}"; do
|
||||||
# Original detail_line format: "${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}"
|
# Parse the post_line: sort_key|title|url|date|summary
|
||||||
# Using Zsh specific split for robustness with special characters in fields
|
IFS='|' read -r sort_key title url date summary <<< "$post_line"
|
||||||
local parts_array=( ${(s[|])detail_line} )
|
|
||||||
|
|
||||||
local post_title_val="${parts_array[2]}"
|
yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
|
||||||
local post_url_val="${parts_array[3]}"
|
yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$url")")" )
|
||||||
local post_date_val="${parts_array[4]}"
|
yaml_lines+=( "$(printf ' post_date: "%s"' "$(_yaml_escape_val "$date")")" )
|
||||||
local post_summary_val="${parts_array[5]}"
|
yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
|
||||||
|
|
||||||
yaml_lines+=( "$(printf ' - title: "%s"' "$(_yaml_escape_val "$post_title_val")")" )
|
|
||||||
yaml_lines+=( "$(printf ' url: "%s"' "$(_yaml_escape_val "$post_url_val")")" )
|
|
||||||
yaml_lines+=( "$(printf ' date: "%s"' "$(_yaml_escape_val "$post_date_val")")" )
|
|
||||||
yaml_lines+=( "$(printf ' summary: "%s"' "$(_yaml_escape_val "$post_summary_val")")" )
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local temp_yaml_content
|
# Join YAML lines into a single string
|
||||||
temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
|
local yaml_content
|
||||||
temp_yaml_content+=$'\n' # Ensure a final trailing newline for the whole block
|
OIFS="$IFS"; IFS=$'\n'
|
||||||
_log DEBUG "Generated comprehensive YAML for index (first 300 chars):\n${temp_yaml_content:0:300}..."
|
yaml_content="${yaml_lines[*]}"
|
||||||
|
IFS="$OIFS"
|
||||||
|
|
||||||
# Write YAML to temporary file
|
_log DEBUG "Generated YAML content for index (first 300 chars): ${yaml_content:0:300}..."
|
||||||
_log DEBUG "Full comprehensive YAML for index before writing to file (raw, first 1000 chars to avoid excessive logging):\n${temp_yaml_content:0:1000}"
|
|
||||||
|
|
||||||
if ! printf '%s' "$temp_yaml_content" > "$temp_yaml_file"; then
|
# Build Pandoc command array
|
||||||
_log ERROR "Failed to write comprehensive YAML to temporary file: $temp_yaml_file"
|
local pandoc_cmd_index=("pandoc")
|
||||||
return 1 # Trap will clean up temp_yaml_file
|
|
||||||
fi
|
|
||||||
_log DEBUG "Successfully wrote comprehensive YAML to $temp_yaml_file"
|
|
||||||
|
|
||||||
|
# Add CSS if theme CSS is available
|
||||||
_log DEBUG "In _generate_index_page: PROJECT_ROOT='$PROJECT_ROOT'"
|
if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then
|
||||||
local pandoc_cmd_index=(
|
local expected_css_file="${QSG_CONFIG[paths_output_dir]}${QSG_CONFIG[pandoc_css_path_arg]}"
|
||||||
pandoc
|
if [[ -f "$expected_css_file" ]]; then
|
||||||
"--metadata-file" "$temp_yaml_file"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add CSS if specified (pandoc_css_path_arg is derived in main)
|
|
||||||
if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]:-}" ]]; then
|
|
||||||
pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
|
pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
|
||||||
_log DEBUG "Using CSS for index: ${QSG_CONFIG[pandoc_css_path_arg]}"
|
_log DEBUG "Added CSS to index page: ${QSG_CONFIG[pandoc_css_path_arg]}"
|
||||||
else
|
else
|
||||||
_log DEBUG "No CSS specified for index page."
|
_log WARNING "Expected CSS file not found: $expected_css_file. CSS will not be linked in index page."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_log DEBUG "No theme CSS path configured. Index page will not include CSS."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add remaining Pandoc options for index page
|
# Add metadata via process substitution instead of temporary file
|
||||||
pandoc_cmd_index+=(
|
pandoc_cmd_index+=("--metadata-file" "/dev/stdin")
|
||||||
"--template=${layout_index_file}"
|
pandoc_cmd_index+=("--template=${layout_index_file}")
|
||||||
"--output=${output_index_file}"
|
pandoc_cmd_index+=("--output=${output_index_file}")
|
||||||
"--standalone"
|
pandoc_cmd_index+=("--standalone")
|
||||||
)
|
|
||||||
|
|
||||||
_log INFO "Generating $output_index_file using template $layout_index_file with YAML from $temp_yaml_file"
|
|
||||||
|
|
||||||
_log DEBUG "Pandoc command for index page will be constructed as follows:"
|
_log DEBUG "Pandoc command for index page will be constructed as follows:"
|
||||||
_log DEBUG "Base command: pandoc"
|
_log DEBUG "Base command: pandoc"
|
||||||
_log DEBUG "CSS arg if theme applies: --css ${QSG_CONFIG[pandoc_css_path_arg]:-(not set or expected CSS file not found in output)}"
|
_log DEBUG "CSS arg if theme applies: --css ${QSG_CONFIG[pandoc_css_path_arg]:-(not set or expected CSS file not found in output)}"
|
||||||
_log DEBUG "Metadata file arg: --metadata-file ${temp_yaml_file}"
|
_log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
|
||||||
_log DEBUG "Template arg: --template=${layout_index_file}"
|
_log DEBUG "Template arg: --template=${layout_index_file}"
|
||||||
_log DEBUG "Output arg: --output=${output_index_file}"
|
_log DEBUG "Output arg: --output=${output_index_file}"
|
||||||
_log DEBUG "Other args: --standalone"
|
_log DEBUG "Other args: --standalone"
|
||||||
_log DEBUG "Final pandoc_cmd_index array: ${(q+)pandoc_cmd_index}"
|
_log DEBUG "Final pandoc_cmd_index array: ${(q+)pandoc_cmd_index}"
|
||||||
|
|
||||||
local pandoc_run_stderr_file
|
|
||||||
pandoc_run_stderr_file=$(mktemp -t pandoc_stderr_index.XXXXXX)
|
|
||||||
if [[ -z "$pandoc_run_stderr_file" || ! -e "$pandoc_run_stderr_file" ]]; then # -e to check existence, could be pipe not file
|
|
||||||
_log CRITICAL "Failed to create temporary file for Pandoc stderr (index). mktemp failed."
|
|
||||||
# Attempt to clean up the main temp YAML file before exiting, if it was created.
|
|
||||||
if [[ -n "$temp_yaml_file" && -f "$temp_yaml_file" ]]; then
|
|
||||||
_log DEBUG "Cleaning up temporary index YAML file ($temp_yaml_file) due to mktemp failure for stderr file."
|
|
||||||
rm -f "$temp_yaml_file"
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
# This temp stderr file is short-lived, trap for it isn't strictly necessary if cleaned up reliably.
|
|
||||||
|
|
||||||
local pandoc_exit_code=0
|
local pandoc_exit_code=0
|
||||||
# Execute Pandoc, redirecting its stderr to the temp file
|
local stderr_content=""
|
||||||
|
|
||||||
|
# Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
|
||||||
_log DEBUG "Executing Pandoc command for index page with set -x..."
|
_log DEBUG "Executing Pandoc command for index page with set -x..."
|
||||||
set -x # Enable command tracing
|
set -x # Enable command tracing
|
||||||
if "${pandoc_cmd_index[@]}" 2> "$pandoc_run_stderr_file"; then
|
|
||||||
pandoc_exec_status=0
|
|
||||||
else
|
|
||||||
pandoc_exec_status=$?
|
|
||||||
fi
|
|
||||||
set +x # Disable command tracing
|
|
||||||
_log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exec_status"
|
|
||||||
pandoc_exit_code=$pandoc_exec_status # Use the captured status
|
|
||||||
|
|
||||||
local stderr_content=""
|
# Use process substitution to capture stderr without temporary files
|
||||||
if [[ -s "$pandoc_run_stderr_file" ]]; then
|
exec 3>&1 # Save stdout to fd 3
|
||||||
stderr_content=$(<"$pandoc_run_stderr_file")
|
stderr_content=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_index[@]}" 1>&3; } 2>&1 )
|
||||||
fi
|
pandoc_exit_code=$?
|
||||||
rm -f "$pandoc_run_stderr_file"
|
exec 3>&- # Close fd 3
|
||||||
|
|
||||||
|
set +x # Disable command tracing
|
||||||
|
_log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exit_code"
|
||||||
|
|
||||||
if [[ $pandoc_exit_code -ne 0 ]]; then
|
if [[ $pandoc_exit_code -ne 0 ]]; then
|
||||||
_log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code."
|
_log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code."
|
||||||
if [[ -n "$stderr_content" ]]; then
|
if [[ -n "$stderr_content" ]]; then
|
||||||
_log ERROR "Pandoc stderr:\n$stderr_content"
|
_log ERROR "Pandoc stderr:\n$stderr_content"
|
||||||
fi
|
fi
|
||||||
_log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
|
_log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
|
||||||
_log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
|
|
||||||
return $pandoc_exit_code # Return Pandoc's non-zero exit code
|
return $pandoc_exit_code # Return Pandoc's non-zero exit code
|
||||||
elif [[ -n "$stderr_content" ]]; then
|
elif [[ -n "$stderr_content" ]]; then
|
||||||
# Pandoc exited 0 but wrote to stderr. Check for known fatal error patterns.
|
# Pandoc exited 0 but wrote to stderr. Check for known fatal error patterns.
|
||||||
if echo "$stderr_content" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
|
if echo "$stderr_content" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
|
||||||
_log ERROR "Pandoc reported a critical error for '$output_index_file' (exit code 0). Treating as failure."
|
_log ERROR "Pandoc reported a critical error for '$output_index_file' (exit code 0). Treating as failure."
|
||||||
_log ERROR "Pandoc stderr:\n$stderr_content"
|
_log ERROR "Pandoc stderr:\n$stderr_content"
|
||||||
_log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
|
_log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
|
||||||
_log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
|
|
||||||
return 1 # Force a failure status
|
return 1 # Force a failure status
|
||||||
else
|
else
|
||||||
_log WARNING "Pandoc succeeded for '$output_index_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content"
|
_log WARNING "Pandoc succeeded for '$output_index_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content"
|
||||||
@ -1039,7 +1007,7 @@ _process_markdown_files() {
|
|||||||
if [[ -z "$source_file" ]]; then continue; fi
|
if [[ -z "$source_file" ]]; then continue; fi
|
||||||
_log DEBUG "Processing Markdown file: $source_file"
|
_log DEBUG "Processing Markdown file: $source_file"
|
||||||
|
|
||||||
local relative_path_from_content_root="${source_file#$content_dir/}"
|
local relative_path_from_content_root="${source_file#${QSG_CONFIG[paths_content_dir]}/}"
|
||||||
if [[ "$content_dir" == "$source_file" ]]; then
|
if [[ "$content_dir" == "$source_file" ]]; then
|
||||||
relative_path_from_content_root=$(basename "$source_file")
|
relative_path_from_content_root=$(basename "$source_file")
|
||||||
elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then
|
elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then
|
||||||
@ -1220,14 +1188,6 @@ _process_markdown_files() {
|
|||||||
_generate_rss_feed() {
|
_generate_rss_feed() {
|
||||||
_log DEBUG "Entered _generate_rss_feed"
|
_log DEBUG "Entered _generate_rss_feed"
|
||||||
|
|
||||||
local temp_rss_yaml_file
|
|
||||||
temp_rss_yaml_file=$(mktemp -t qsgen3_rss_yaml.XXXXXX)
|
|
||||||
if [[ $? -ne 0 || -z "$temp_rss_yaml_file" || ! -f "$temp_rss_yaml_file" ]]; then
|
|
||||||
_log ERROR "Failed to create temporary file for RSS YAML. Exiting."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
trap "_log DEBUG \"Cleaning up temporary RSS YAML file: ${temp_rss_yaml_file}\"; rm -f \"${temp_rss_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
|
|
||||||
_log DEBUG "Created temporary YAML file for RSS: $temp_rss_yaml_file"
|
|
||||||
if [[ "${QSG_CONFIG[build_options_generate_rss]}" != "true" ]]; then
|
if [[ "${QSG_CONFIG[build_options_generate_rss]}" != "true" ]]; then
|
||||||
_log INFO "RSS feed generation is disabled in configuration. Skipping."
|
_log INFO "RSS feed generation is disabled in configuration. Skipping."
|
||||||
return 0
|
return 0
|
||||||
@ -1316,92 +1276,78 @@ _generate_rss_feed() {
|
|||||||
yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For <title> in RSS
|
yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For <title> in RSS
|
||||||
yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For <pubDate> of the feed itself
|
yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For <pubDate> of the feed itself
|
||||||
yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
|
yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
|
||||||
yaml_lines+=( "$(printf 'pagetitle: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For Pandoc's HTML writer
|
|
||||||
|
|
||||||
yaml_lines+=( "$(printf 'posts:')" )
|
yaml_lines+=( "$(printf 'posts:')" )
|
||||||
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
|
|
||||||
_log INFO "No posts found to include in RSS feed after filtering."
|
|
||||||
yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
|
|
||||||
else
|
|
||||||
for detail_line in "${sorted_posts[@]}"; do
|
|
||||||
local parts=( ${(s[|])detail_line} ) # Zsh specific split
|
|
||||||
local p_title="${parts[2]:-}"
|
|
||||||
local p_url="${parts[3]:-}"
|
|
||||||
local p_date_rfc822="${parts[4]:-}"
|
|
||||||
local p_summary="${parts[5]:-}"
|
|
||||||
|
|
||||||
yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$p_title")")" )
|
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
|
||||||
yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$p_url")")" )
|
_log DEBUG "No posts found to add to RSS YAML."
|
||||||
yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$p_date_rfc822")")" )
|
yaml_lines+=( "$(printf ' [] # Explicit empty list for posts')" )
|
||||||
yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$p_summary")")" )
|
else
|
||||||
# Add other fields like guid if needed, e.g., using post_url as guid
|
for post_line in "${sorted_posts[@]}"; do
|
||||||
yaml_lines+=( "$(printf ' post_guid: "%s"' "$(_yaml_escape_val "$p_url")")" )
|
# Parse the post_line: sort_key|title|url|rfc822_date|summary
|
||||||
|
IFS='|' read -r sort_key title url rfc822_date summary <<< "$post_line"
|
||||||
|
|
||||||
|
yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
|
||||||
|
yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$url")")" )
|
||||||
|
yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$rfc822_date")")" )
|
||||||
|
yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local temp_yaml_content
|
# Join YAML lines into a single string
|
||||||
temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
|
local yaml_content
|
||||||
temp_yaml_content+=$'\n' # Ensure a final trailing newline
|
OIFS="$IFS"; IFS=$'\n'
|
||||||
|
yaml_content="${yaml_lines[*]}"
|
||||||
|
IFS="$OIFS"
|
||||||
|
|
||||||
_log DEBUG "Generated comprehensive YAML for RSS (first 300 chars):\n${temp_yaml_content:0:300}..."
|
_log DEBUG "Generated YAML content for RSS (first 300 chars): ${yaml_content:0:300}..."
|
||||||
|
|
||||||
if ! printf '%s' "$temp_yaml_content" > "$temp_rss_yaml_file"; then
|
|
||||||
_log ERROR "Failed to write comprehensive RSS YAML to temporary file: $temp_rss_yaml_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
_log DEBUG "Successfully wrote comprehensive RSS YAML to $temp_rss_yaml_file"
|
|
||||||
|
|
||||||
|
# Build Pandoc command array for RSS
|
||||||
local pandoc_cmd_rss=(
|
local pandoc_cmd_rss=(
|
||||||
pandoc
|
"pandoc"
|
||||||
"--metadata-file" "$temp_rss_yaml_file"
|
"--to" "html5"
|
||||||
|
"--metadata" "pagetitle=RSS Feed"
|
||||||
|
"--metadata-file" "/dev/stdin"
|
||||||
"--template=${layout_rss_file}"
|
"--template=${layout_rss_file}"
|
||||||
"--output=${output_rss_file}"
|
"--output=${output_rss_file}"
|
||||||
"--to" "html5" # Use HTML5 writer for custom XML template processing
|
|
||||||
"--standalone"
|
"--standalone"
|
||||||
)
|
)
|
||||||
_log INFO "Generating $output_rss_file using template $layout_rss_file with YAML from $temp_rss_yaml_file"
|
|
||||||
|
|
||||||
local pandoc_run_rss_stderr_file
|
_log DEBUG "Pandoc command for RSS feed will be constructed as follows:"
|
||||||
pandoc_run_rss_stderr_file=$(mktemp -t pandoc_stderr_rss.XXXXXX)
|
_log DEBUG "Base command: pandoc --to html5 --metadata pagetitle=\"RSS Feed\""
|
||||||
if [[ -z "$pandoc_run_rss_stderr_file" || ! -e "$pandoc_run_rss_stderr_file" ]]; then
|
_log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
|
||||||
_log CRITICAL "Failed to create temporary file for Pandoc stderr (RSS). mktemp failed."
|
_log DEBUG "Template arg: --template=${layout_rss_file}"
|
||||||
if [[ -n "$temp_rss_yaml_file" && -f "$temp_rss_yaml_file" ]]; then
|
_log DEBUG "Output arg: --output=${output_rss_file}"
|
||||||
_log DEBUG "Cleaning up temporary RSS YAML file ($temp_rss_yaml_file) due to mktemp failure for stderr file."
|
_log DEBUG "Other args: --standalone"
|
||||||
rm -f "$temp_rss_yaml_file"
|
_log DEBUG "Final pandoc_cmd_rss array: ${(q+)pandoc_cmd_rss}"
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local pandoc_exit_code_rss=0
|
local pandoc_exit_code_rss=0
|
||||||
# Provide a dummy input to Pandoc since content is from metadata, redirect stderr
|
|
||||||
local pandoc_exec_status_rss
|
|
||||||
if echo "<!-- RSS feed generated by qsgen3 -->" | "${pandoc_cmd_rss[@]}" 2> "$pandoc_run_rss_stderr_file"; then
|
|
||||||
pandoc_exec_status_rss=0
|
|
||||||
else
|
|
||||||
pandoc_exec_status_rss=$?
|
|
||||||
fi
|
|
||||||
pandoc_exit_code_rss=$pandoc_exec_status_rss
|
|
||||||
|
|
||||||
local stderr_content_rss=""
|
local stderr_content_rss=""
|
||||||
if [[ -s "$pandoc_run_rss_stderr_file" ]]; then
|
|
||||||
stderr_content_rss=$(<"$pandoc_run_rss_stderr_file")
|
# Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
|
||||||
fi
|
_log DEBUG "Executing Pandoc command for RSS feed with set -x..."
|
||||||
rm -f "$pandoc_run_rss_stderr_file"
|
set -x # Enable command tracing
|
||||||
|
|
||||||
|
# Use process substitution to capture stderr without temporary files
|
||||||
|
exec 3>&1 # Save stdout to fd 3
|
||||||
|
stderr_content_rss=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_rss[@]}" 1>&3; } 2>&1 )
|
||||||
|
pandoc_exit_code_rss=$?
|
||||||
|
exec 3>&- # Close fd 3
|
||||||
|
|
||||||
|
set +x # Disable command tracing
|
||||||
|
_log DEBUG "Pandoc execution finished for RSS. Original exit status from Pandoc: $pandoc_exit_code_rss"
|
||||||
|
|
||||||
if [[ $pandoc_exit_code_rss -ne 0 ]]; then
|
if [[ $pandoc_exit_code_rss -ne 0 ]]; then
|
||||||
_log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss."
|
_log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss."
|
||||||
if [[ -n "$stderr_content_rss" ]]; then
|
if [[ -n "$stderr_content_rss" ]]; then
|
||||||
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
|
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
|
||||||
fi
|
fi
|
||||||
_log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
|
_log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
|
||||||
_log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
|
|
||||||
return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code
|
return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code
|
||||||
elif [[ -n "$stderr_content_rss" ]]; then
|
elif [[ -n "$stderr_content_rss" ]]; then
|
||||||
if echo "$stderr_content_rss" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
|
if echo "$stderr_content_rss" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
|
||||||
_log ERROR "Pandoc reported a critical error for '$output_rss_file' (exit code 0). Treating as failure."
|
_log ERROR "Pandoc reported a critical error for '$output_rss_file' (exit code 0). Treating as failure."
|
||||||
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
|
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
|
||||||
_log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
|
_log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
|
||||||
_log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
|
|
||||||
return 1 # Force a failure status
|
return 1 # Force a failure status
|
||||||
else
|
else
|
||||||
_log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss"
|
_log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss"
|
||||||
|
610
how-it-works.md
610
how-it-works.md
@ -5,16 +5,14 @@
|
|||||||
1. [Philosophy and Design Principles](#philosophy-and-design-principles)
|
1. [Philosophy and Design Principles](#philosophy-and-design-principles)
|
||||||
2. [Project Structure](#project-structure)
|
2. [Project Structure](#project-structure)
|
||||||
3. [Configuration System](#configuration-system)
|
3. [Configuration System](#configuration-system)
|
||||||
4. [Theme System](#theme-system)
|
4. [Content Processing Pipeline](#content-processing-pipeline)
|
||||||
5. [Creating New Themes](#creating-new-themes)
|
5. [Static File Handling](#static-file-handling)
|
||||||
6. [Content Processing Pipeline](#content-processing-pipeline)
|
6. [Template System](#template-system)
|
||||||
7. [Static File Handling](#static-file-handling)
|
7. [Output Generation](#output-generation)
|
||||||
8. [Template System](#template-system)
|
8. [Command Line Interface](#command-line-interface)
|
||||||
9. [Output Generation](#output-generation)
|
9. [Dependencies and Requirements](#dependencies-and-requirements)
|
||||||
10. [Command Line Interface](#command-line-interface)
|
10. [Detailed Workflow](#detailed-workflow)
|
||||||
11. [Dependencies and Requirements](#dependencies-and-requirements)
|
11. [Troubleshooting and Debugging](#troubleshooting-and-debugging)
|
||||||
12. [Detailed Workflow](#detailed-workflow)
|
|
||||||
13. [Troubleshooting and Debugging](#troubleshooting-and-debugging)
|
|
||||||
|
|
||||||
## Philosophy and Design Principles
|
## Philosophy and Design Principles
|
||||||
|
|
||||||
@ -54,11 +52,7 @@ project-root/
|
|||||||
├── static/ # Static assets (CSS, images, etc.)
|
├── static/ # Static assets (CSS, images, etc.)
|
||||||
│ ├── css/
|
│ ├── css/
|
||||||
│ └── images/
|
│ └── images/
|
||||||
├── themes/ # Theme directory
|
├── output/ # Generated site (created by qsgen3)
|
||||||
│ └── theme-name/ # Individual theme
|
|
||||||
│ ├── layouts/ # Theme-specific templates (optional)
|
|
||||||
│ └── css/ # Alternative theme asset location
|
|
||||||
└── output/ # Generated site (created by qsgen3)
|
|
||||||
├── index.html
|
├── index.html
|
||||||
├── rss.xml
|
├── rss.xml
|
||||||
├── posts/
|
├── posts/
|
||||||
@ -77,10 +71,6 @@ site_name="My Awesome Site"
|
|||||||
site_tagline="A brief description of my site"
|
site_tagline="A brief description of my site"
|
||||||
site_url="http://localhost:8000"
|
site_url="http://localhost:8000"
|
||||||
|
|
||||||
# Theme Configuration
|
|
||||||
site_theme="minimal"
|
|
||||||
site_theme_css_file="css/style.css"
|
|
||||||
|
|
||||||
# Directory Paths
|
# Directory Paths
|
||||||
paths_content_dir="content"
|
paths_content_dir="content"
|
||||||
paths_output_dir="output"
|
paths_output_dir="output"
|
||||||
@ -103,535 +93,11 @@ build_options_process_drafts=false
|
|||||||
|
|
||||||
### Key Configuration Variables
|
### Key Configuration Variables
|
||||||
|
|
||||||
- **`site_theme`**: Name of the active theme (directory name in `themes/`)
|
- **`site_name`**: Name of the site
|
||||||
- **`site_theme_css_file`**: Path to main CSS file relative to theme's static assets
|
|
||||||
- **`site_url`**: Base URL for the site (used in RSS and absolute links)
|
- **`site_url`**: Base URL for the site (used in RSS and absolute links)
|
||||||
- **`paths_*`**: Directory paths (can be relative or absolute)
|
- **`paths_*`**: Directory paths (can be relative or absolute)
|
||||||
- **`build_options_*`**: Boolean flags for optional features
|
- **`build_options_*`**: Boolean flags for optional features
|
||||||
|
|
||||||
## Theme System
|
|
||||||
|
|
||||||
### Theme Architecture
|
|
||||||
|
|
||||||
Themes in qsgen3 provide two main components:
|
|
||||||
|
|
||||||
1. **Templates**: Pandoc HTML templates for different page types
|
|
||||||
2. **Static Assets**: CSS, JavaScript, images, and other resources
|
|
||||||
|
|
||||||
### Theme Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
themes/theme-name/
|
|
||||||
├── layouts/ # Optional: Override default templates
|
|
||||||
│ ├── index.html # Homepage template
|
|
||||||
│ ├── post.html # Post template
|
|
||||||
│ └── rss.xml # RSS template
|
|
||||||
├── static/ # Preferred: Standard static assets location
|
|
||||||
│ ├── css/
|
|
||||||
│ └── js/
|
|
||||||
└── css/ # Alternative: Direct CSS location
|
|
||||||
└── style.css
|
|
||||||
```
|
|
||||||
|
|
||||||
### Theme Resolution Logic
|
|
||||||
|
|
||||||
1. **Theme Selection**: Based on `site_theme` in `site.conf`
|
|
||||||
2. **Layout Override**: If `themes/theme-name/layouts/` exists, it overrides `paths_layouts_dir`
|
|
||||||
3. **Static Asset Source**:
|
|
||||||
- First checks for `themes/theme-name/static/`
|
|
||||||
- Falls back to `themes/theme-name/` (for themes with assets at root level)
|
|
||||||
4. **CSS File Location**: Determined by `site_theme_css_file` relative to theme's static source
|
|
||||||
|
|
||||||
### Theme Switching
|
|
||||||
|
|
||||||
Switching themes is accomplished by:
|
|
||||||
|
|
||||||
1. Changing `site_theme` in `site.conf`
|
|
||||||
2. Updating `site_theme_css_file` to match the new theme's CSS structure
|
|
||||||
3. Running qsgen3 to regenerate the site
|
|
||||||
|
|
||||||
## Creating New Themes
|
|
||||||
|
|
||||||
### Theme Development Overview
|
|
||||||
|
|
||||||
Creating a new theme for qsgen3 involves designing templates and static assets that work together to provide a cohesive visual and functional experience. Themes can range from minimal CSS-only styling to complex designs with custom layouts and interactive elements.
|
|
||||||
|
|
||||||
### Step-by-Step Theme Creation
|
|
||||||
|
|
||||||
#### 1. Create Theme Directory Structure
|
|
||||||
|
|
||||||
Start by creating a new directory in the `themes/` folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p themes/my-theme
|
|
||||||
cd themes/my-theme
|
|
||||||
```
|
|
||||||
|
|
||||||
Choose one of these organizational approaches:
|
|
||||||
|
|
||||||
**Option A: Standard Structure (Recommended)**
|
|
||||||
```
|
|
||||||
themes/my-theme/
|
|
||||||
├── layouts/ # Custom templates (optional)
|
|
||||||
│ ├── index.html # Homepage template
|
|
||||||
│ ├── post.html # Blog post template
|
|
||||||
│ └── rss.xml # RSS feed template
|
|
||||||
└── static/ # Static assets
|
|
||||||
├── css/
|
|
||||||
│ └── style.css # Main stylesheet
|
|
||||||
└── js/ # JavaScript files (optional)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: CSS-Only Structure**
|
|
||||||
```
|
|
||||||
themes/my-theme/
|
|
||||||
└── css/
|
|
||||||
└── style.css # Main stylesheet only
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Create the Main Stylesheet
|
|
||||||
|
|
||||||
Create your theme's primary CSS file. This is the only asset that qsgen3 will automatically link:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* themes/my-theme/static/css/style.css */
|
|
||||||
|
|
||||||
/* Reset and base styles */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header styles */
|
|
||||||
header {
|
|
||||||
background: #2c3e50;
|
|
||||||
color: white;
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main content */
|
|
||||||
main {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Post styles */
|
|
||||||
article {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
article h1, article h2 {
|
|
||||||
color: #2c3e50;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
article .meta {
|
|
||||||
color: #666;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation and links */
|
|
||||||
nav ul {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a {
|
|
||||||
color: #3498db;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive design */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
main {
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav ul {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Create Custom Templates (Optional)
|
|
||||||
|
|
||||||
If you want to override the default templates, create custom Pandoc templates:
|
|
||||||
|
|
||||||
**Homepage Template (`layouts/index.html`)**
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>$site_name$</title>
|
|
||||||
$if(css)$<link rel="stylesheet" href="$css$">$endif$
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>$site_name$</h1>
|
|
||||||
$if(site_tagline)$<p>$site_tagline$</p>$endif$
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section class="posts">
|
|
||||||
<h2>Recent Posts</h2>
|
|
||||||
$if(posts)$
|
|
||||||
<ul class="post-list">
|
|
||||||
$for(posts)$
|
|
||||||
<li class="post-item">
|
|
||||||
<h3><a href="$it.post_url$">$it.post_title$</a></h3>
|
|
||||||
<div class="meta">
|
|
||||||
$if(it.post_date)$<time>$it.post_date$</time>$endif$
|
|
||||||
$if(it.post_author)$ by $it.post_author$$endif$
|
|
||||||
</div>
|
|
||||||
$if(it.post_description)$<p>$it.post_description$</p>$endif$
|
|
||||||
</li>
|
|
||||||
$endfor$
|
|
||||||
</ul>
|
|
||||||
$else$
|
|
||||||
<p>No posts available.</p>
|
|
||||||
$endif$
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>© 2024 $site_name$. Generated with qsgen3.</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Post Template (`layouts/post.html`)**
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>$title$ - $site_name$</title>
|
|
||||||
$if(css)$<link rel="stylesheet" href="$css$">$endif$
|
|
||||||
$if(description)$<meta name="description" content="$description$">$endif$
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1><a href="/">$site_name$</a></h1>
|
|
||||||
$if(site_tagline)$<p>$site_tagline$</p>$endif$
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<article>
|
|
||||||
<header class="post-header">
|
|
||||||
<h1>$title$</h1>
|
|
||||||
<div class="meta">
|
|
||||||
$if(date)$<time datetime="$date$">$date$</time>$endif$
|
|
||||||
$if(author)$ by <span class="author">$author$</span>$endif$
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="post-content">
|
|
||||||
$body$
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<nav class="post-nav">
|
|
||||||
<a href="/">← Back to Home</a>
|
|
||||||
</nav>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>© 2024 $site_name$. Generated with qsgen3.</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. Add JavaScript (Optional)
|
|
||||||
|
|
||||||
If your theme requires JavaScript functionality, add it to the static assets:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// themes/my-theme/static/js/theme.js
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Add smooth scrolling
|
|
||||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
||||||
anchor.addEventListener('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const target = document.querySelector(this.getAttribute('href'));
|
|
||||||
if (target) {
|
|
||||||
target.scrollIntoView({
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add copy code button functionality
|
|
||||||
document.querySelectorAll('pre code').forEach(block => {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = 'Copy';
|
|
||||||
button.className = 'copy-button';
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
navigator.clipboard.writeText(block.textContent);
|
|
||||||
button.textContent = 'Copied!';
|
|
||||||
setTimeout(() => button.textContent = 'Copy', 2000);
|
|
||||||
});
|
|
||||||
block.parentNode.appendChild(button);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: Remember that qsgen3 will not automatically include JavaScript files. You must add `<script>` tags to your templates:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Add to your template's <head> or before </body> -->
|
|
||||||
<script src="/static/js/theme.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5. Configure Theme Usage
|
|
||||||
|
|
||||||
Update your `site.conf` to use the new theme:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Theme Configuration
|
|
||||||
site_theme="my-theme"
|
|
||||||
site_theme_css_file="css/style.css" # Path relative to theme's static source
|
|
||||||
```
|
|
||||||
|
|
||||||
For CSS-only themes using the alternative structure:
|
|
||||||
```bash
|
|
||||||
site_theme="my-theme"
|
|
||||||
site_theme_css_file="style.css" # Direct path if CSS is at theme root
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 6. Test Your Theme
|
|
||||||
|
|
||||||
Generate your site to test the theme:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./bin/qsgen3
|
|
||||||
```
|
|
||||||
|
|
||||||
Check the output:
|
|
||||||
- Verify CSS is applied correctly
|
|
||||||
- Test responsive design on different screen sizes
|
|
||||||
- Validate HTML structure
|
|
||||||
- Check that all assets are copied correctly
|
|
||||||
|
|
||||||
### Theme Development Best Practices
|
|
||||||
|
|
||||||
#### CSS Guidelines
|
|
||||||
|
|
||||||
1. **Use Relative Units**: Prefer `rem`, `em`, and percentages over fixed pixels
|
|
||||||
2. **Mobile-First Design**: Start with mobile styles, then add desktop enhancements
|
|
||||||
3. **Semantic Selectors**: Use class names that describe content, not appearance
|
|
||||||
4. **CSS Custom Properties**: Use CSS variables for consistent theming
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
--primary-color: #2c3e50;
|
|
||||||
--secondary-color: #3498db;
|
|
||||||
--text-color: #333;
|
|
||||||
--background-color: #fff;
|
|
||||||
--border-color: #eee;
|
|
||||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
color: var(--text-color);
|
|
||||||
background-color: var(--background-color);
|
|
||||||
font-family: var(--font-family);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Template Guidelines
|
|
||||||
|
|
||||||
1. **Conditional Content**: Use Pandoc's conditional syntax for optional elements
|
|
||||||
2. **Semantic HTML**: Use appropriate HTML5 semantic elements
|
|
||||||
3. **Accessibility**: Include proper ARIA labels and alt text
|
|
||||||
4. **Meta Tags**: Include essential meta tags for SEO and social sharing
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Good conditional usage -->
|
|
||||||
$if(description)$<meta name="description" content="$description$">$endif$
|
|
||||||
$if(author)$<meta name="author" content="$author$">$endif$
|
|
||||||
|
|
||||||
<!-- Semantic structure -->
|
|
||||||
<main role="main">
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h1>$title$</h1>
|
|
||||||
</header>
|
|
||||||
<div class="content">
|
|
||||||
$body$
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Asset Organization
|
|
||||||
|
|
||||||
1. **Logical Structure**: Group related assets in appropriate directories
|
|
||||||
2. **Naming Conventions**: Use consistent, descriptive file names
|
|
||||||
3. **Optimization**: Optimize images and minimize CSS/JS when possible
|
|
||||||
4. **Dependencies**: Document any external dependencies clearly
|
|
||||||
|
|
||||||
### Advanced Theme Features
|
|
||||||
|
|
||||||
#### Dark Mode Support
|
|
||||||
|
|
||||||
Add CSS custom properties and media queries for dark mode:
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
--bg-color: #fff;
|
|
||||||
--text-color: #333;
|
|
||||||
--border-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--bg-color: #1a1a1a;
|
|
||||||
--text-color: #e0e0e0;
|
|
||||||
--border-color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--bg-color);
|
|
||||||
color: var(--text-color);
|
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Print Styles
|
|
||||||
|
|
||||||
Include print-specific styles:
|
|
||||||
|
|
||||||
```css
|
|
||||||
@media print {
|
|
||||||
header, footer, nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 12pt;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[href]:after {
|
|
||||||
content: " (" attr(href) ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Custom Fonts
|
|
||||||
|
|
||||||
If using custom fonts, include them properly:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* Load fonts */
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
|
|
||||||
|
|
||||||
/* Or use local fonts */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'CustomFont';
|
|
||||||
src: url('/static/fonts/custom-font.woff2') format('woff2'),
|
|
||||||
url('/static/fonts/custom-font.woff') format('woff');
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Theme Distribution
|
|
||||||
|
|
||||||
#### Documentation
|
|
||||||
|
|
||||||
Create a `README.md` for your theme:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# My Theme
|
|
||||||
|
|
||||||
A clean, minimal theme for qsgen3.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Responsive design
|
|
||||||
- Dark mode support
|
|
||||||
- Clean typography
|
|
||||||
- Fast loading
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
1. Copy theme to `themes/my-theme/`
|
|
||||||
2. Update `site.conf`:
|
|
||||||
```
|
|
||||||
site_theme="my-theme"
|
|
||||||
site_theme_css_file="css/style.css"
|
|
||||||
```
|
|
||||||
3. Run `./bin/qsgen3`
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
- Edit CSS custom properties in `style.css`
|
|
||||||
- Modify templates in `layouts/` directory
|
|
||||||
- Add custom JavaScript in `static/js/`
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
- Modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
|
|
||||||
- Graceful degradation for older browsers
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Version Control
|
|
||||||
|
|
||||||
If sharing your theme:
|
|
||||||
1. Use semantic versioning
|
|
||||||
2. Tag releases appropriately
|
|
||||||
3. Include a changelog
|
|
||||||
4. Provide example configurations
|
|
||||||
|
|
||||||
### Troubleshooting Theme Development
|
|
||||||
|
|
||||||
#### Common Issues
|
|
||||||
|
|
||||||
1. **CSS Not Loading**: Check `site_theme_css_file` path matches actual file location
|
|
||||||
2. **Templates Not Found**: Ensure template files are in `layouts/` directory
|
|
||||||
3. **Assets Missing**: Verify static files are in correct directory structure
|
|
||||||
4. **JavaScript Errors**: Remember to include `<script>` tags in templates
|
|
||||||
|
|
||||||
#### Testing Checklist
|
|
||||||
|
|
||||||
- [ ] CSS loads correctly on all page types
|
|
||||||
- [ ] Templates render without Pandoc errors
|
|
||||||
- [ ] Responsive design works on mobile devices
|
|
||||||
- [ ] All static assets are accessible
|
|
||||||
- [ ] JavaScript functionality works (if applicable)
|
|
||||||
- [ ] Print styles are appropriate
|
|
||||||
- [ ] Accessibility standards are met
|
|
||||||
- [ ] Performance is acceptable
|
|
||||||
|
|
||||||
## Content Processing Pipeline
|
## Content Processing Pipeline
|
||||||
|
|
||||||
### Markdown Processing
|
### Markdown Processing
|
||||||
@ -740,7 +206,8 @@ output/
|
|||||||
│ └── post-name.html
|
│ └── post-name.html
|
||||||
├── static/ # All static assets
|
├── static/ # All static assets
|
||||||
│ ├── css/ # Stylesheets
|
│ ├── css/ # Stylesheets
|
||||||
│ └── js/ # JavaScript (if provided by theme)
|
│ ├── js/ # JavaScript (if provided by theme)
|
||||||
|
│ └── images/ # Images and media
|
||||||
└── css/ # Legacy: Index-specific CSS location
|
└── css/ # Legacy: Index-specific CSS location
|
||||||
└── theme.css # Copy of main theme CSS for index page
|
└── theme.css # Copy of main theme CSS for index page
|
||||||
```
|
```
|
||||||
@ -845,23 +312,7 @@ Check Dependencies
|
|||||||
└── Exit with error if dependencies missing
|
└── Exit with error if dependencies missing
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Theme Processing
|
### 4. Output Preparation
|
||||||
|
|
||||||
```
|
|
||||||
Process Theme Configuration
|
|
||||||
├── Read site_theme from configuration
|
|
||||||
├── Determine theme base path: themes/$site_theme
|
|
||||||
├── Check for theme layouts directory
|
|
||||||
│ ├── If exists: Override paths_layouts_dir
|
|
||||||
│ └── If not: Use default layouts
|
|
||||||
├── Determine theme static source
|
|
||||||
│ ├── Check themes/$site_theme/static/
|
|
||||||
│ ├── Fallback to themes/$site_theme/
|
|
||||||
│ └── Set QSG_CONFIG[theme_static_source_dir]
|
|
||||||
└── Log theme processing decisions
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Output Preparation
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Prepare Output Directory
|
Prepare Output Directory
|
||||||
@ -874,7 +325,8 @@ Prepare Output Directory
|
|||||||
│ ├── Recreate clean output directory
|
│ ├── Recreate clean output directory
|
||||||
│ ├── Restore preserved files maintaining directory structure
|
│ ├── Restore preserved files maintaining directory structure
|
||||||
│ └── Clean up temporary backup directory
|
│ └── Clean up temporary backup directory
|
||||||
│ └── Remove entire output directory
|
├── If no preserve file:
|
||||||
|
│ ├── Remove entire output directory
|
||||||
│ └── Create fresh output directory
|
│ └── Create fresh output directory
|
||||||
└── Log preservation and cleaning operations
|
└── Log preservation and cleaning operations
|
||||||
```
|
```
|
||||||
@ -911,7 +363,7 @@ downloads/*
|
|||||||
- Flexible pattern matching for various preservation needs
|
- Flexible pattern matching for various preservation needs
|
||||||
- Backward compatible (no preserve file = complete cleaning)
|
- Backward compatible (no preserve file = complete cleaning)
|
||||||
|
|
||||||
### 6. Static File Processing
|
### 5. Static File Processing
|
||||||
|
|
||||||
```
|
```
|
||||||
Copy Static Files
|
Copy Static Files
|
||||||
@ -924,7 +376,7 @@ Copy Static Files
|
|||||||
└── Log copy operations and results
|
└── Log copy operations and results
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. CSS Path Determination
|
### 6. CSS Path Determination
|
||||||
|
|
||||||
```
|
```
|
||||||
Determine CSS Linking
|
Determine CSS Linking
|
||||||
@ -935,7 +387,7 @@ Determine CSS Linking
|
|||||||
└── Log CSS path decisions and warnings
|
└── Log CSS path decisions and warnings
|
||||||
```
|
```
|
||||||
|
|
||||||
### 8. Content Processing
|
### 7. Content Processing
|
||||||
|
|
||||||
```
|
```
|
||||||
Process Markdown Content
|
Process Markdown Content
|
||||||
@ -949,7 +401,7 @@ Process Markdown Content
|
|||||||
└── Collect metadata for index and RSS generation
|
└── Collect metadata for index and RSS generation
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9. Index Generation
|
### 8. Index Generation
|
||||||
|
|
||||||
```
|
```
|
||||||
Generate Index Page
|
Generate Index Page
|
||||||
@ -961,7 +413,7 @@ Generate Index Page
|
|||||||
└── Clean up temporary files
|
└── Clean up temporary files
|
||||||
```
|
```
|
||||||
|
|
||||||
### 10. RSS Generation
|
### 9. RSS Generation
|
||||||
|
|
||||||
```
|
```
|
||||||
Generate RSS Feed
|
Generate RSS Feed
|
||||||
@ -973,7 +425,7 @@ Generate RSS Feed
|
|||||||
└── Clean up temporary files
|
└── Clean up temporary files
|
||||||
```
|
```
|
||||||
|
|
||||||
### 11. Finalization
|
### 10. Finalization
|
||||||
|
|
||||||
```
|
```
|
||||||
Complete Generation
|
Complete Generation
|
||||||
@ -999,19 +451,7 @@ Complete Generation
|
|||||||
- Check theme directory structure
|
- Check theme directory structure
|
||||||
- Enable debug logging to trace CSS path resolution
|
- Enable debug logging to trace CSS path resolution
|
||||||
|
|
||||||
#### 2. Theme Not Found
|
#### 2. Template Errors
|
||||||
**Symptoms**: Warning about theme directory not found
|
|
||||||
**Causes**:
|
|
||||||
- Typo in `site_theme` configuration
|
|
||||||
- Theme directory doesn't exist
|
|
||||||
- Incorrect theme directory structure
|
|
||||||
|
|
||||||
**Solutions**:
|
|
||||||
- Verify theme name spelling in site.conf
|
|
||||||
- Check themes/ directory exists and contains named theme
|
|
||||||
- Ensure theme directory has expected structure
|
|
||||||
|
|
||||||
#### 3. Template Errors
|
|
||||||
**Symptoms**: Pandoc errors during HTML generation
|
**Symptoms**: Pandoc errors during HTML generation
|
||||||
**Causes**:
|
**Causes**:
|
||||||
- Missing required templates
|
- Missing required templates
|
||||||
@ -1023,7 +463,7 @@ Complete Generation
|
|||||||
- Check Pandoc template syntax
|
- Check Pandoc template syntax
|
||||||
- Review template variable usage
|
- Review template variable usage
|
||||||
|
|
||||||
#### 4. Static File Copy Issues
|
#### 3. Static File Copy Issues
|
||||||
**Symptoms**: Assets missing from output directory
|
**Symptoms**: Assets missing from output directory
|
||||||
**Causes**:
|
**Causes**:
|
||||||
- Permission issues
|
- Permission issues
|
||||||
@ -1035,7 +475,7 @@ Complete Generation
|
|||||||
- Verify available disk space
|
- Verify available disk space
|
||||||
- Review path configurations for absolute vs. relative paths
|
- Review path configurations for absolute vs. relative paths
|
||||||
|
|
||||||
#### 5. File Preservation Issues
|
#### 4. File Preservation Issues
|
||||||
**Symptoms**: Expected files not preserved during cleaning, or preservation not working
|
**Symptoms**: Expected files not preserved during cleaning, or preservation not working
|
||||||
**Causes**:
|
**Causes**:
|
||||||
- Incorrect patterns in `.qsgen3_preserve` file
|
- Incorrect patterns in `.qsgen3_preserve` file
|
||||||
@ -1086,5 +526,3 @@ Ensure site.conf follows the correct format:
|
|||||||
- Comments start with `#`
|
- Comments start with `#`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*This document reflects the current implementation of qsgen3 and its design philosophy of remaining completely design-agnostic while providing flexible theme and content management capabilities.*
|
|
||||||
|
@ -206,10 +206,10 @@ def process_blog_file(file_path, output_dir_base):
|
|||||||
sanitized_title = sanitize_filename(metadata['title'])
|
sanitized_title = sanitize_filename(metadata['title'])
|
||||||
if not metadata['date']:
|
if not metadata['date']:
|
||||||
# Fallback if date couldn't be parsed, though unlikely for .blog files
|
# Fallback if date couldn't be parsed, though unlikely for .blog files
|
||||||
output_subdir = Path(output_dir_base) / "blog" / "unknown_date"
|
output_subdir = Path(output_dir_base) / "blog"
|
||||||
else:
|
else:
|
||||||
year, month, day = metadata['date'].split('-')
|
# Put all blog posts in a single directory instead of year/month/day structure
|
||||||
output_subdir = Path(output_dir_base) / "blog" / year / month / day
|
output_subdir = Path(output_dir_base) / "blog"
|
||||||
|
|
||||||
output_subdir.mkdir(parents=True, exist_ok=True)
|
output_subdir.mkdir(parents=True, exist_ok=True)
|
||||||
output_file_path = output_subdir / f"{sanitized_title}.md"
|
output_file_path = output_subdir / f"{sanitized_title}.md"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user