From 81ffa53d70e8cfde849a9c1c0f2b3ac2e274d674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stig-=C3=98rjan=20Smelror?= Date: Sat, 31 May 2025 03:00:50 +0200 Subject: [PATCH] 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 --- THEMES-HOWTO.md | 822 ++++++++++++++++++++++++++++++++++ bin/qsgen3 | 298 +++++------- how-it-works.md | 610 +------------------------ scripts/migrate_qs2_to_qs3.py | 6 +- 4 files changed, 971 insertions(+), 765 deletions(-) create mode 100644 THEMES-HOWTO.md diff --git a/THEMES-HOWTO.md b/THEMES-HOWTO.md new file mode 100644 index 0000000..38232ab --- /dev/null +++ b/THEMES-HOWTO.md @@ -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 + + + + + + $site_name$ - $title$ + + + $if(date)$$endif$ + $for(css)$ + + $endfor$ + + +
+

$site_name$

+

$site_tagline$

+
+
+
+
+

$title$

+ $if(author)$

By: $author$

$endif$ + $if(date)$

Published: $date$

$endif$ +
+ $body$ +
+
+ + + +``` + +#### Index Page Layout (`layouts/index.html`) + +```html + + + + + + $site_name$ - $site_tagline$ + + $for(css)$ + + $endfor$ + + +
+

$site_name$

+

$site_tagline$

+
+
+ $body$ +
+ + + +``` + +## 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 + +``` + +#### 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 +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 + +``` + +## 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 + + + + $site_name$ + $site_tagline$ + $site_url$ + + en-us + $build_date$ + + $for(posts)$ + + $it.post_title$ + $it.post_description$ + $it.post_url$ + $it.post_url$ + $it.post_date_rfc$ + + $endfor$ + + +``` + +## 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 + + + + + $title$ + + + +

$title$

+ $body$ + + +``` + +#### 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 + + + + + + $site_name$ - $title$ + + + $if(date)$$endif$ + $for(css)$ + + $endfor$ + + +
+
+

$site_name$

+

$site_tagline$

+
+
+
+
+
+
+

$title$

+
+ $if(author)$By: $author$$endif$ + $if(date)$Published: $date$$endif$ +
+
+ $body$ +
+
+
+ + + +``` + +### 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. diff --git a/bin/qsgen3 b/bin/qsgen3 index c3af879..6f43bcb 100755 --- a/bin/qsgen3 +++ b/bin/qsgen3 @@ -620,11 +620,13 @@ _clean_output_dir() { _log INFO "Found preserve file: $preserve_file" _log DEBUG "Performing selective cleaning with file preservation" - # Create temporary directory to store preserved files - local temp_preserve_dir=$(mktemp -d) + # Use in-memory arrays to store file paths and content + local files_to_preserve=() + local preserved_file_paths=() + local preserved_file_contents=() 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 # Skip empty lines and comments [[ -z "$pattern" || "$pattern" == \#* ]] && continue @@ -640,17 +642,16 @@ _clean_output_dir() { if [[ -f "$file" ]]; then # Get relative path from output directory local rel_path="${file#$output_dir/}" - local preserve_path="$temp_preserve_dir/$rel_path" - # Create directory structure in temp location - mkdir -p "$(dirname "$preserve_path")" - - # Copy file to preserve location - if cp "$file" "$preserve_path"; then + # Read file content into memory + local file_content + if file_content=$(<"$file"); then + preserved_file_paths+=("$rel_path") + preserved_file_contents+=("$file_content") files_preserved=$((files_preserved + 1)) - _log DEBUG "Preserved file: $rel_path" + _log DEBUG "Preserved file in memory: $rel_path" else - _log WARNING "Failed to preserve file: $rel_path" + _log WARNING "Failed to read file for preservation: $rel_path" fi fi done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null) @@ -658,30 +659,37 @@ _clean_output_dir() { done < "$preserve_file" # 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" # Recreate output directory mkdir -p "$output_dir" if [[ $? -ne 0 ]]; then _log ERROR "Failed to recreate output directory: $output_dir" - rm -rf "$temp_preserve_dir" return 1 fi - # Restore preserved files + # Restore preserved files from memory if [[ $files_preserved -gt 0 ]]; then - _log DEBUG "Restoring $files_preserved preserved files" - if [[ -d "$temp_preserve_dir" ]]; then - # Copy preserved files back, maintaining directory structure - (cd "$temp_preserve_dir" && find . -type f -exec cp --parents {} "$output_dir/" \; 2>/dev/null) - _log INFO "Restored $files_preserved preserved files" - fi + _log DEBUG "Restoring $files_preserved preserved files from memory" + for ((i=0; i<${#preserved_file_paths[@]}; i++)); do + local rel_path="${preserved_file_paths[i]}" + local file_content="${preserved_file_contents[i]}" + local full_path="$output_dir/$rel_path" + + # Create directory structure if needed + 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 - # Clean up temporary directory - rm -rf "$temp_preserve_dir" - else # No preserve file - do complete cleaning as before _log DEBUG "No preserve file found - performing complete cleaning" @@ -794,14 +802,6 @@ _copy_static_files() { _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..." local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts" local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html" @@ -892,119 +892,87 @@ _generate_index_page() { if [[ ${#sorted_posts[@]} -eq 0 ]]; then _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 - for detail_line in "${sorted_posts[@]}"; do - # Original detail_line format: "${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}" - # Using Zsh specific split for robustness with special characters in fields - local parts_array=( ${(s[|])detail_line} ) - - local post_title_val="${parts_array[2]}" - local post_url_val="${parts_array[3]}" - local post_date_val="${parts_array[4]}" - local post_summary_val="${parts_array[5]}" + for post_line in "${sorted_posts[@]}"; do + # Parse the post_line: sort_key|title|url|date|summary + IFS='|' read -r sort_key title url date summary <<< "$post_line" - 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")")" ) + 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: "%s"' "$(_yaml_escape_val "$date")")" ) + yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$summary")")" ) done fi - local temp_yaml_content - temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")" - temp_yaml_content+=$'\n' # Ensure a final trailing newline for the whole block - _log DEBUG "Generated comprehensive YAML for index (first 300 chars):\n${temp_yaml_content:0:300}..." + # Join YAML lines into a single string + local yaml_content + OIFS="$IFS"; IFS=$'\n' + yaml_content="${yaml_lines[*]}" + IFS="$OIFS" - # Write YAML to temporary file - _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}" + _log DEBUG "Generated YAML content for index (first 300 chars): ${yaml_content:0:300}..." - if ! printf '%s' "$temp_yaml_content" > "$temp_yaml_file"; then - _log ERROR "Failed to write comprehensive YAML to temporary file: $temp_yaml_file" - return 1 # Trap will clean up temp_yaml_file - fi - _log DEBUG "Successfully wrote comprehensive YAML to $temp_yaml_file" + # Build Pandoc command array + local pandoc_cmd_index=("pandoc") - - _log DEBUG "In _generate_index_page: PROJECT_ROOT='$PROJECT_ROOT'" - local pandoc_cmd_index=( - pandoc - "--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]}") - _log DEBUG "Using CSS for index: ${QSG_CONFIG[pandoc_css_path_arg]}" + # Add CSS if theme CSS is available + if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then + local expected_css_file="${QSG_CONFIG[paths_output_dir]}${QSG_CONFIG[pandoc_css_path_arg]}" + if [[ -f "$expected_css_file" ]]; then + pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}") + _log DEBUG "Added CSS to index page: ${QSG_CONFIG[pandoc_css_path_arg]}" + else + _log WARNING "Expected CSS file not found: $expected_css_file. CSS will not be linked in index page." + fi else - _log DEBUG "No CSS specified for index page." + _log DEBUG "No theme CSS path configured. Index page will not include CSS." fi - # Add remaining Pandoc options for index page - pandoc_cmd_index+=( - "--template=${layout_index_file}" - "--output=${output_index_file}" - "--standalone" - ) - - _log INFO "Generating $output_index_file using template $layout_index_file with YAML from $temp_yaml_file" + # Add metadata via process substitution instead of temporary file + pandoc_cmd_index+=("--metadata-file" "/dev/stdin") + pandoc_cmd_index+=("--template=${layout_index_file}") + pandoc_cmd_index+=("--output=${output_index_file}") + pandoc_cmd_index+=("--standalone") _log DEBUG "Pandoc command for index page will be constructed as follows:" _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 "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 "Output arg: --output=${output_index_file}" _log DEBUG "Other args: --standalone" _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 - # 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..." set -x # Enable command tracing - if "${pandoc_cmd_index[@]}" 2> "$pandoc_run_stderr_file"; then - pandoc_exec_status=0 - else - pandoc_exec_status=$? - fi + + # Use process substitution to capture stderr without temporary files + exec 3>&1 # Save stdout to fd 3 + stderr_content=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_index[@]}" 1>&3; } 2>&1 ) + pandoc_exit_code=$? + exec 3>&- # Close fd 3 + 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="" - if [[ -s "$pandoc_run_stderr_file" ]]; then - stderr_content=$(<"$pandoc_run_stderr_file") - fi - rm -f "$pandoc_run_stderr_file" + _log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exit_code" if [[ $pandoc_exit_code -ne 0 ]]; then _log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code." if [[ -n "$stderr_content" ]]; then _log ERROR "Pandoc stderr:\n$stderr_content" fi - _log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file" - _log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')" + _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}" return $pandoc_exit_code # Return Pandoc's non-zero exit code elif [[ -n "$stderr_content" ]]; then # 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 _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 "YAML content passed to Pandoc was in: $temp_yaml_file" - _log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')" + _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}" return 1 # Force a failure status else _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 _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 relative_path_from_content_root=$(basename "$source_file") elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then @@ -1220,14 +1188,6 @@ _process_markdown_files() { _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 _log INFO "RSS feed generation is disabled in configuration. Skipping." return 0 @@ -1316,92 +1276,78 @@ _generate_rss_feed() { yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For 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_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:')" ) - 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")")" ) - yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$p_url")")" ) - yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$p_date_rfc822")")" ) - yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$p_summary")")" ) - # Add other fields like guid if needed, e.g., using post_url as guid - yaml_lines+=( "$(printf ' post_guid: "%s"' "$(_yaml_escape_val "$p_url")")" ) + if [[ ${#sorted_posts[@]} -eq 0 ]]; then + _log DEBUG "No posts found to add to RSS YAML." + yaml_lines+=( "$(printf ' [] # Explicit empty list for posts')" ) + else + for post_line in "${sorted_posts[@]}"; do + # 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 fi - local temp_yaml_content - temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")" - temp_yaml_content+=$'\n' # Ensure a final trailing newline + # Join YAML lines into a single string + local yaml_content + 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}..." - - 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" + _log DEBUG "Generated YAML content for RSS (first 300 chars): ${yaml_content:0:300}..." + # Build Pandoc command array for RSS local pandoc_cmd_rss=( - pandoc - "--metadata-file" "$temp_rss_yaml_file" + "pandoc" + "--to" "html5" + "--metadata" "pagetitle=RSS Feed" + "--metadata-file" "/dev/stdin" "--template=${layout_rss_file}" "--output=${output_rss_file}" - "--to" "html5" # Use HTML5 writer for custom XML template processing "--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 - pandoc_run_rss_stderr_file=$(mktemp -t pandoc_stderr_rss.XXXXXX) - if [[ -z "$pandoc_run_rss_stderr_file" || ! -e "$pandoc_run_rss_stderr_file" ]]; then - _log CRITICAL "Failed to create temporary file for Pandoc stderr (RSS). mktemp failed." - if [[ -n "$temp_rss_yaml_file" && -f "$temp_rss_yaml_file" ]]; then - _log DEBUG "Cleaning up temporary RSS YAML file ($temp_rss_yaml_file) due to mktemp failure for stderr file." - rm -f "$temp_rss_yaml_file" - fi - return 1 - fi + _log DEBUG "Pandoc command for RSS feed will be constructed as follows:" + _log DEBUG "Base command: pandoc --to html5 --metadata pagetitle=\"RSS Feed\"" + _log DEBUG "Metadata file arg: --metadata-file /dev/stdin" + _log DEBUG "Template arg: --template=${layout_rss_file}" + _log DEBUG "Output arg: --output=${output_rss_file}" + _log DEBUG "Other args: --standalone" + _log DEBUG "Final pandoc_cmd_rss array: ${(q+)pandoc_cmd_rss}" 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="" - if [[ -s "$pandoc_run_rss_stderr_file" ]]; then - stderr_content_rss=$(<"$pandoc_run_rss_stderr_file") - fi - rm -f "$pandoc_run_rss_stderr_file" + + # Execute Pandoc with YAML content piped to stdin, capturing stderr in memory + _log DEBUG "Executing Pandoc command for RSS feed with set -x..." + 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 _log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss." if [[ -n "$stderr_content_rss" ]]; then _log ERROR "Pandoc stderr:\n$stderr_content_rss" fi - _log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file" - _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')" + _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}" return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code 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 _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 "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file" - _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')" + _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}" return 1 # Force a failure status else _log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss" diff --git a/how-it-works.md b/how-it-works.md index 25eead7..69f1e0c 100644 --- a/how-it-works.md +++ b/how-it-works.md @@ -5,16 +5,14 @@ 1. [Philosophy and Design Principles](#philosophy-and-design-principles) 2. [Project Structure](#project-structure) 3. [Configuration System](#configuration-system) -4. [Theme System](#theme-system) -5. [Creating New Themes](#creating-new-themes) -6. [Content Processing Pipeline](#content-processing-pipeline) -7. [Static File Handling](#static-file-handling) -8. [Template System](#template-system) -9. [Output Generation](#output-generation) -10. [Command Line Interface](#command-line-interface) -11. [Dependencies and Requirements](#dependencies-and-requirements) -12. [Detailed Workflow](#detailed-workflow) -13. [Troubleshooting and Debugging](#troubleshooting-and-debugging) +4. [Content Processing Pipeline](#content-processing-pipeline) +5. [Static File Handling](#static-file-handling) +6. [Template System](#template-system) +7. [Output Generation](#output-generation) +8. [Command Line Interface](#command-line-interface) +9. [Dependencies and Requirements](#dependencies-and-requirements) +10. [Detailed Workflow](#detailed-workflow) +11. [Troubleshooting and Debugging](#troubleshooting-and-debugging) ## Philosophy and Design Principles @@ -54,11 +52,7 @@ project-root/ ├── static/ # Static assets (CSS, images, etc.) │ ├── css/ │ └── images/ -├── themes/ # Theme directory -│ └── theme-name/ # Individual theme -│ ├── layouts/ # Theme-specific templates (optional) -│ └── css/ # Alternative theme asset location -└── output/ # Generated site (created by qsgen3) +├── output/ # Generated site (created by qsgen3) ├── index.html ├── rss.xml ├── posts/ @@ -77,10 +71,6 @@ site_name="My Awesome Site" site_tagline="A brief description of my site" site_url="http://localhost:8000" -# Theme Configuration -site_theme="minimal" -site_theme_css_file="css/style.css" - # Directory Paths paths_content_dir="content" paths_output_dir="output" @@ -103,535 +93,11 @@ build_options_process_drafts=false ### Key Configuration Variables -- **`site_theme`**: Name of the active theme (directory name in `themes/`) -- **`site_theme_css_file`**: Path to main CSS file relative to theme's static assets +- **`site_name`**: Name of the site - **`site_url`**: Base URL for the site (used in RSS and absolute links) - **`paths_*`**: Directory paths (can be relative or absolute) - **`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$ - $if(css)$$endif$ - - -
-

$site_name$

- $if(site_tagline)$

$site_tagline$

$endif$ -
- -
-
-

Recent Posts

- $if(posts)$ -
    - $for(posts)$ -
  • -

    $it.post_title$

    -
    - $if(it.post_date)$$endif$ - $if(it.post_author)$ by $it.post_author$$endif$ -
    - $if(it.post_description)$

    $it.post_description$

    $endif$ -
  • - $endfor$ -
- $else$ -

No posts available.

- $endif$ -
-
- - - - -``` - -**Post Template (`layouts/post.html`)** -```html - - - - - - $title$ - $site_name$ - $if(css)$$endif$ - $if(description)$$endif$ - - -
-

$site_name$

- $if(site_tagline)$

$site_tagline$

$endif$ -
- -
-
-
-

$title$

-
- $if(date)$$endif$ - $if(author)$ by $author$$endif$ -
-
- -
- $body$ -
-
- - -
- - - - -``` - -#### 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 ` -``` - -#### 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 - -$if(description)$$endif$ -$if(author)$$endif$ - - -
-
-
-

$title$

-
-
- $body$ -
-
-
-``` - -#### 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 `