diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..292e66b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,211 @@ +# FolderWeb Documentation + +Welcome to the FolderWeb documentation! This comprehensive guide covers everything you need to know about building and maintaining websites with FolderWeb. + +## πŸ“š Documentation Organization + +This documentation follows the [Diataxis framework](https://diataxis.fr/), organizing content into four distinct types to help you find exactly what you need: + +### πŸŽ“ Tutorial (Learning-Oriented) + +**Purpose**: Learn by doing +**For**: Newcomers to FolderWeb + +- **[Getting Started](tutorial/00-getting-started.md)** - Build your first FolderWeb site from scratch in 10 minutes + +Start here if you're new to FolderWeb. This hands-on tutorial walks you through creating a complete website with pages, blog posts, and custom styling. + +### πŸ“‹ How-To Guides (Task-Oriented) + +**Purpose**: Solve specific problems +**For**: Users with a specific goal + +- **[Custom Templates](how-to/custom-templates.md)** - Override default templates with your own designs +- **[Custom Styles](how-to/custom-styles.md)** - Customize appearance using CSS +- **[Multi-Language Sites](how-to/multi-language.md)** - Set up and manage multiple languages +- **[Working with Metadata](how-to/working-with-metadata.md)** - Control content with metadata.ini files + +Use these guides when you know what you want to accomplish and need step-by-step instructions. + +### πŸ“– Reference (Information-Oriented) + +**Purpose**: Look up technical details +**For**: Users who need precise information + +- **[File Structure](reference/file-structure.md)** - Complete directory layout and file conventions +- **[Metadata](reference/metadata.md)** - All available metadata fields and their usage +- **[Templates](reference/templates.md)** - Template types and available variables +- **[Configuration](reference/configuration.md)** - Configuration options and format +- **[CSS Variables](reference/css-variables.md)** - All CSS custom properties for styling + +Consult these documents when you need to look up specific technical details or API information. + +### πŸ’‘ Explanation (Understanding-Oriented) + +**Purpose**: Understand concepts and design decisions +**For**: Users who want to understand the "why" + +- **[Philosophy](explanation/philosophy.md)** - Design principles and thinking behind FolderWeb +- **[Architecture](explanation/architecture.md)** - How FolderWeb works under the hood + +Read these to gain deeper understanding of FolderWeb's design and architecture. + +## πŸš€ Quick Start + +```bash +# 1. Create project directory +mkdir my-website && cd my-website + +# 2. Copy framework files +cp -r /path/to/folderweb/app ./app + +# 3. Create your first page +mkdir content +echo "# Welcome" > content/index.md + +# 4. Start development server +php -S localhost:8000 -t . app/router.php + +# 5. Open http://localhost:8000 +``` + +**Next**: Follow the complete [Getting Started Tutorial](tutorial/00-getting-started.md) + +## 🎯 Common Tasks + +Quick links to frequently needed guides: + +| Task | Guide | +|------|-------| +| Create a custom template | [Custom Templates](how-to/custom-templates.md) | +| Change colors and fonts | [Custom Styles](how-to/custom-styles.md) | +| Add multiple languages | [Multi-Language Sites](how-to/multi-language.md) | +| Configure page metadata | [Working with Metadata](how-to/working-with-metadata.md) | +| Look up all metadata fields | [Metadata Reference](reference/metadata.md) | +| Find template variables | [Templates Reference](reference/templates.md) | +| Understand file organization | [File Structure Reference](reference/file-structure.md) | + +## πŸ’‘ Key Concepts + +- **File-based routing**: Your folder structure defines your URL structure +- **Template fallback**: Custom templates automatically override defaults +- **Content types**: Single-file pages, multi-file pages, or list views +- **Language support**: Built-in multi-language with URL prefixes +- **Metadata control**: Configure behavior with simple INI files +- **No build process**: Save and refresh - see changes immediately + +## πŸ“‹ Requirements + +- **PHP**: 8.4 or higher +- **Web server**: Apache, Nginx, or PHP's built-in server +- **Extensions**: Standard PHP (no special extensions required) + +## πŸ—‚οΈ Documentation Files + +### Complete File List + +``` +docs/ +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ index.md # Documentation homepage +β”‚ +β”œβ”€β”€ tutorial/ +β”‚ └── 00-getting-started.md # Step-by-step tutorial +β”‚ +β”œβ”€β”€ how-to/ +β”‚ β”œβ”€β”€ custom-templates.md # Override templates +β”‚ β”œβ”€β”€ custom-styles.md # Customize CSS +β”‚ β”œβ”€β”€ multi-language.md # Multi-language setup +β”‚ └── working-with-metadata.md # Metadata usage +β”‚ +β”œβ”€β”€ reference/ +β”‚ β”œβ”€β”€ file-structure.md # Directory layout +β”‚ β”œβ”€β”€ metadata.md # Metadata fields +β”‚ β”œβ”€β”€ templates.md # Template reference +β”‚ β”œβ”€β”€ configuration.md # Config options +β”‚ └── css-variables.md # CSS customization +β”‚ +└── explanation/ + β”œβ”€β”€ philosophy.md # Design principles + └── architecture.md # Technical architecture +``` + +## πŸ“– Reading Paths + +Choose your path based on your needs: + +### Path 1: Complete Beginner + +1. [Getting Started Tutorial](tutorial/00-getting-started.md) +2. [Custom Styles](how-to/custom-styles.md) +3. [Working with Metadata](how-to/working-with-metadata.md) +4. [Philosophy](explanation/philosophy.md) + +### Path 2: Experienced Developer + +1. [Philosophy](explanation/philosophy.md) +2. [Architecture](explanation/architecture.md) +3. [File Structure Reference](reference/file-structure.md) +4. Browse How-To Guides as needed + +### Path 3: Specific Task + +1. Find your task in [How-To Guides](how-to/) +2. Consult [Reference](reference/) for details +3. Return to task completion + +## πŸ€” Getting Help + +### Documentation Not Enough? + +1. **Check the code**: FolderWeb is deliberately simple - reading the source is encouraged +2. **Review examples**: Look at the demo content in `/app/default/content/` +3. **Test locally**: Experiment with a test site to understand behavior + +### Common Issues + +| Problem | Solution | +|---------|----------| +| Styles not loading | Hard refresh browser (Ctrl+Shift+R) | +| 404 errors | Verify folder exists with content files | +| Language not working | Check `available` in config.ini | +| Metadata not showing | Validate INI syntax with PHP parser | +| Custom template ignored | Ensure file is in `/custom/templates/` | + +## 🌟 Philosophy Highlights + +FolderWeb embraces: + +- **Simplicity**: Just enough, nothing more +- **Longevity**: Works today, works in 2035 +- **Transparency**: Readable code, clear behavior +- **Files**: Your content, fully portable +- **No build**: Save and refresh workflow + +Read the complete [Philosophy](explanation/philosophy.md) to understand FolderWeb's design principles. + +## πŸ“ Contributing to Documentation + +Documentation improvements are welcome: + +- Fix typos or unclear explanations +- Add missing examples +- Improve existing guides +- Suggest new how-to guides + +Keep documentation: +- Clear and concise +- Accurate and tested +- Organized according to Diataxis principles + +## πŸ”— External Resources + +- [Diataxis Framework](https://diataxis.fr/) - Documentation organization system +- [PHP 8.4 Documentation](https://www.php.net/manual/en/) - PHP reference +- [Markdown Guide](https://www.markdownguide.org/) - Markdown syntax +- [MDN Web Docs](https://developer.mozilla.org/) - HTML and CSS reference + +--- + +**Start here**: [Getting Started Tutorial](tutorial/00-getting-started.md) +**Main index**: [Documentation Index](index.md) diff --git a/docs/explanation/architecture.md b/docs/explanation/architecture.md new file mode 100644 index 0000000..08809d8 --- /dev/null +++ b/docs/explanation/architecture.md @@ -0,0 +1,739 @@ +# Architecture + +Understanding how FolderWeb works under the hood. + +## High-Level Overview + +FolderWeb follows a simple request-response flow: + +``` +HTTP Request + ↓ +router.php (entry point) + ↓ +Parse request path + ↓ +Find content files + ↓ +Determine content type (page/list/file) + ↓ +Render content + ↓ +Wrap in templates + ↓ +HTTP Response +``` + +## Core Components + +### 1. Router (`app/router.php`) + +**Purpose**: Entry point for all requests, determines what to serve + +**Responsibilities**: +- Receive HTTP requests +- Check for root-level assets (`/custom/assets/`) +- Parse request path +- Dispatch to appropriate renderer +- Handle redirects (trailing slashes) +- Serve 404 for missing content + +**Key Flow**: +```php +// 1. Check for root-level assets +if (file_exists("/custom/assets/$path")) { + serve_static_file(); +} + +// 2. Empty path = home page (render all root content files) +if (empty($path)) { + render_all_files_in_root(); +} + +// 3. Parse request path +$result = parseRequestPath($ctx); + +// 4. Handle based on type +match($result['type']) { + 'page' => renderMultipleFiles(...), + 'file' => renderFile(...), + 'directory' => renderListView(...), + 'not_found' => show_404() +}; +``` + +**Location**: `/app/router.php` (lines 1-100+) + +### 2. Content Discovery (`app/content.php`) + +**Purpose**: Find and parse content files and directories + +**Key Functions**: + +#### `parseRequestPath($ctx)` +Analyzes request path and determines content type. + +**Returns**: +```php +[ + 'type' => 'page' | 'file' | 'directory' | 'not_found', + 'path' => '/full/system/path', + 'files' => [...], // For page type + // ... other data +] +``` + +**Logic**: +1. Resolve translated slugs to real paths +2. Check if path exists +3. If directory: + - Has subdirectories? β†’ `type: 'directory'` (list view) + - Has content files only? β†’ `type: 'page'` (multi-file) +4. If matches file? β†’ `type: 'file'` +5. Otherwise β†’ `type: 'not_found'` + +#### `findAllContentFiles($dir, $lang, $defaultLang, $availableLangs)` +Scans directory for content files. + +**Process**: +1. Read directory contents +2. Filter for valid extensions (`.md`, `.html`, `.php`) +3. Parse filenames for language suffix +4. Filter by current language: + - Show `.{lang}.ext` files for that language + - Show default files (no suffix) only if no language variant +5. Sort alphanumerically +6. Return array of file paths + +**Example**: +```php +// Directory contains: +// - index.md +// - index.no.md +// - about.md + +// English request (lang=en, default=en): +findAllContentFiles() β†’ ['index.md', 'about.md'] + +// Norwegian request (lang=no): +findAllContentFiles() β†’ ['index.no.md', 'about.md'] +``` + +#### `loadMetadata($dirPath, $lang, $defaultLang)` +Loads and merges metadata for a directory. + +**Process**: +1. Check for `metadata.ini` in directory +2. Parse INI file with sections +3. Start with base values +4. Override with language-specific section if exists +5. Return merged array + +**Example**: +```ini +title = "About" +summary = "Learn about us" + +[no] +title = "Om" +summary = "LΓ¦r om oss" +``` + +For Norwegian request: +```php +loadMetadata(..., 'no', 'en') β†’ [ + 'title' => 'Om', // Overridden + 'summary' => 'LΓ¦r om oss' // Overridden +] +``` + +#### `resolveTranslatedPath($ctx, $requestPath)` +Maps translated slugs back to real directory names. + +**Example**: +```ini +; In content/about/metadata.ini: +[no] +slug = "om-oss" +``` + +Request to `/no/om-oss/` resolves to `content/about/`. + +**Process**: +1. Split path into segments +2. For each segment: + - Load metadata of parent directory + - Check if any subdirectory has matching translated slug + - Replace segment with real directory name +3. Return resolved path + +### 3. Rendering Engine (`app/rendering.php`) + +**Purpose**: Convert content to HTML and wrap in templates + +**Key Functions**: + +#### `renderContentFile($filePath)` +Converts a single content file to HTML. + +**Process**: +```php +switch (extension) { + case 'md': + return Parsedown->text(file_contents); + case 'html': + return file_contents; + case 'php': + ob_start(); + include $filePath; // $ctx available + return ob_get_clean(); +} +``` + +#### `renderFile($ctx, $filePath)` +Renders single file wrapped in templates. + +**Process**: +1. Convert file to HTML +2. Load metadata +3. Wrap in page template +4. Wrap in base template +5. Return HTML + +#### `renderMultipleFiles($ctx, $filePaths, $pageDir)` +Renders multiple files as single page. + +**Process**: +1. Convert each file to HTML +2. Concatenate HTML (in order) +3. Load metadata +4. Wrap in page template +5. Wrap in base template +6. Return HTML + +**Used for**: Multi-file pages (documentation, long articles) + +#### `renderTemplate($ctx, $content, $statusCode = 200)` +Wraps content in base template. + +**Process**: +1. Extract variables for template +2. Set HTTP status code +3. Include base template +4. Return HTML + +**Variables provided**: +- `$content` - Rendered HTML +- `$ctx` - Context object +- `$currentLang`, `$navigation`, `$homeLabel`, etc. + +### 4. Context Object (`app/context.php`) + +**Purpose**: Immutable request context with computed properties + +**Implementation**: +```php +readonly class Context { + public function __construct( + public private(set) string $contentDir, + public private(set) string $currentLang, + // ... other properties + ) {} + + // Computed property (PHP 8.4 hook) + public string $langPrefix { + get => $this->currentLang !== $this->defaultLang + ? "/{$this->currentLang}" + : ''; + } + + // Lazy-loaded computed property + public array $navigation { + get => buildNavigation($this); + } +} +``` + +**Benefits**: +- **Immutability**: Cannot be changed after creation +- **Type safety**: All properties typed +- **Computed values**: Calculated on-demand +- **No globals**: Passed explicitly + +**Creation**: +```php +$ctx = createContext(); +``` + +This function: +1. Loads configuration +2. Extracts language from URL +3. Determines content directory +4. Resolves template paths +5. Returns readonly Context object + +### 5. Configuration (`app/config.php`) + +**Purpose**: Load and merge configuration + +**Process**: +1. Parse `/app/config.ini` (defaults) +2. Parse `/custom/config.ini` if exists +3. Merge arrays (custom overrides defaults) +4. Extract language settings +5. Validate configuration + +**Configuration Used**: +```ini +[languages] +default = "en" +available = "en,no,fr" +``` + +### 6. Helper Functions (`app/helpers.php`) + +**Purpose**: Utility functions used throughout + +**Key Helpers**: + +| Function | Purpose | +|----------|---------| +| `resolveTemplate($name, $type)` | Find custom or default template | +| `getSubdirectories($dir)` | List subdirectories only | +| `extractTitle($filePath, $lang, $defaultLang)` | Extract H1 from content | +| `formatNorwegianDate($date)` | Format date as "2. november 2025" | +| `extractDateFromFolder($name)` | Parse date from folder name | +| `findCoverImage($dir)` | Locate cover image | +| `findPdfFile($dir)` | Find first PDF | + +### 7. Static File Server (`app/static.php`) + +**Purpose**: Serve CSS, fonts, and other static assets + +**Process**: +1. Validate path (prevent directory traversal) +2. Resolve real path +3. Check file exists and is readable +4. Determine MIME type +5. Set headers +6. Output file contents + +**Routes**: +- `/app/styles/base.css` β†’ Custom or default CSS +- `/app/default-styles/base.css` β†’ Default CSS +- `/custom/fonts/*` β†’ Custom fonts + +## Data Flow + +### Request Flow Diagram + +``` +1. HTTP Request: /blog/2025-11-02-post/ + ↓ +2. router.php receives request + ↓ +3. createContext() + β”œβ”€ Load config + β”œβ”€ Extract language from URL + β”œβ”€ Determine content directory + └─ Return Context object + ↓ +4. parseRequestPath($ctx) + β”œβ”€ resolveTranslatedPath() - map slug to real path + β”œβ”€ Check path exists + β”œβ”€ findAllContentFiles() - scan for content + └─ Return ['type' => 'file', 'path' => '...'] + ↓ +5. renderFile($ctx, $filePath) + β”œβ”€ renderContentFile() - convert to HTML + β”œβ”€ loadMetadata() - get metadata + β”œβ”€ Apply page template + └─ Apply base template + ↓ +6. HTTP Response: HTML +``` + +### List View Flow + +``` +1. Request: /blog/ + ↓ +2. parseRequestPath() β†’ type: 'directory' + ↓ +3. Load directory metadata + β”œβ”€ Get page_template setting + └─ Get other directory metadata + ↓ +4. getSubdirectories() - find all subdirs + ↓ +5. For each subdirectory: + β”œβ”€ loadMetadata() - get title, date, summary + β”œβ”€ findCoverImage() - locate cover + β”œβ”€ findPdfFile() - locate PDF + └─ Build item array + ↓ +6. Render list template with $items + ↓ +7. Wrap in base template + ↓ +8. Return HTML +``` + +## File Organization + +### Separation of Concerns + +``` +app/ +β”œβ”€β”€ router.php # Entry point, request handling +β”œβ”€β”€ content.php # Content discovery, parsing +β”œβ”€β”€ rendering.php # HTML generation, templates +β”œβ”€β”€ context.php # Request context +β”œβ”€β”€ config.php # Configuration loading +β”œβ”€β”€ helpers.php # Utility functions +β”œβ”€β”€ constants.php # Constants (extensions) +└── static.php # Static file serving +``` + +Each file has a single responsibility. + +### Template Resolution + +Templates use fallback chain: + +``` +1. /custom/templates/{name}.php + ↓ (if not found) +2. /app/default/templates/{name}.php +``` + +**Implementation**: +```php +function resolveTemplate($name, $type = 'templates') { + $custom = __DIR__ . "/../custom/$type/$name"; + $default = __DIR__ . "/default/$type/$name"; + return file_exists($custom) ? $custom : $default; +} +``` + +This pattern applies to: +- Templates +- Styles +- Languages +- Any overridable resource + +## Language Handling + +### URL Structure + +``` +/ β†’ Default language +/no/ β†’ Norwegian +/fr/page/ β†’ French page +``` + +### Language Extraction + +From URL path: +```php +// Request: /no/blog/post/ +$segments = explode('/', trim($path, '/')); +$firstSegment = $segments[0] ?? ''; + +if (in_array($firstSegment, $availableLangs)) { + $currentLang = $firstSegment; + $pathWithoutLang = implode('/', array_slice($segments, 1)); +} else { + $currentLang = $defaultLang; + $pathWithoutLang = $path; +} +``` + +### Content Filtering + +Files with language suffixes (`.{lang}.ext`) are filtered: + +```php +// Parse filename +$parts = explode('.', $filename); +$lastPart = $parts[count($parts) - 2] ?? null; + +// Check if second-to-last part is a language +if (in_array($lastPart, $availableLangs)) { + $fileLang = $lastPart; +} else { + $fileLang = $defaultLang; +} + +// Include file if: +// - It matches current language, OR +// - It's default language AND no specific variant exists +``` + +## Navigation Building + +### Process + +```php +function buildNavigation($ctx) { + $items = []; + + // 1. Scan content root for directories + $dirs = getSubdirectories($ctx->contentDir); + + // 2. For each directory + foreach ($dirs as $dir) { + // Load metadata + $metadata = loadMetadata($dir, $ctx->currentLang, $ctx->defaultLang); + + // Skip if menu = false + if (!($metadata['menu'] ?? false)) continue; + + // Build item + $items[] = [ + 'title' => $metadata['title'] ?? basename($dir), + 'url' => $ctx->langPrefix . '/' . basename($dir) . '/', + 'order' => $metadata['menu_order'] ?? 999 + ]; + } + + // 3. Sort by menu_order + usort($items, fn($a, $b) => $a['order'] <=> $b['order']); + + return $items; +} +``` + +### Caching + +Navigation is a computed property, calculated once per request: + +```php +public array $navigation { + get => buildNavigation($this); +} +``` + +PHP memoizes the result automatically. + +## Performance Characteristics + +### Time Complexity + +| Operation | Complexity | Notes | +|-----------|------------|-------| +| Route resolution | O(1) | Direct file checks | +| Content file scan | O(n) | n = files in directory | +| Metadata loading | O(1) | Single file read | +| Template rendering | O(m) | m = content size | +| Navigation build | O(d) | d = top-level directories | + +### Space Complexity + +- **Memory**: O(c) where c = content size +- **No caching**: Each request independent +- **Stateless**: No session storage + +### Optimization Points + +1. **OPcache**: PHP bytecode caching (biggest impact) +2. **Web server cache**: Serve cached HTML +3. **Reverse proxy**: Varnish, Cloudflare +4. **Minimize file reads**: Context created once per request + +## Security Architecture + +### Path Validation + +Multiple layers prevent directory traversal: + +```php +// 1. Remove .. segments +$path = str_replace('..', '', $path); + +// 2. Resolve to real path +$realPath = realpath($path); + +// 3. Ensure within content directory +if (!str_starts_with($realPath, $contentDir)) { + return 404; +} + +// 4. Check readable +if (!is_readable($realPath)) { + return 404; +} +``` + +### Output Escaping + +All user-generated content escaped: + +```php + +``` + +This prevents XSS attacks. + +### MIME Type Validation + +Static files served with correct MIME types: + +```php +$mimeTypes = [ + 'css' => 'text/css', + 'woff2' => 'font/woff2', + 'jpg' => 'image/jpeg', + // ... +]; + +header('Content-Type: ' . $mimeTypes[$extension]); +``` + +## Error Handling + +### HTTP Status Codes + +- **200 OK**: Successful content render +- **301 Moved Permanently**: Missing trailing slash +- **404 Not Found**: Content doesn't exist + +### 404 Handling + +When content not found: + +```php +renderTemplate($ctx, '

404 Not Found

', 404); +``` + +Base template rendered with 404 status. + +## Extension Points + +### Custom Templates + +Override any template: + +```php +// Framework checks: +$custom = '/custom/templates/list-my-custom.php'; +if (file_exists($custom)) { + include $custom; +} else { + include '/app/default/templates/list.php'; +} +``` + +### Custom Functions + +Add your own in `/custom/functions.php`: + +```php + + +# Dynamic Content + +Current time: + +The language is: currentLang ?> +``` + +## Testing Architecture + +### Manual Testing + +1. Create test content +2. Start dev server: `php -S localhost:8000 -t . app/router.php` +3. Visit URLs, verify output +4. Check different languages +5. Test edge cases (missing files, invalid paths) + +### Automated Testing (Future) + +Possible test structure: + +```php +// tests/RouterTest.php +test('renders home page', function() { + $response = request('/'); + expect($response->status)->toBe(200); + expect($response->body)->toContain('

'); +}); + +test('handles 404', function() { + $response = request('/nonexistent/'); + expect($response->status)->toBe(404); +}); +``` + +## Deployment Architecture + +### Simple Deployment + +```bash +# 1. Clone repository +git clone https://github.com/you/your-site + +# 2. Point web server to directory +# Document root: /path/to/site +# Rewrite all requests to: /app/router.php + +# 3. Done +``` + +### With Build Step (Optional) + +```bash +# 1. Clone and build +git clone ... +cd site + +# 2. Process custom styles (optional) +# E.g., PostCSS, autoprefixer + +# 3. Deploy +rsync -av . server:/var/www/site/ +``` + +### Zero-Downtime Deployment + +```bash +# 1. Deploy to new directory +rsync -av . server:/var/www/site-new/ + +# 2. Symlink switch +ln -sfn /var/www/site-new /var/www/site-current + +# 3. Web server serves from /var/www/site-current +``` + +## Related + +- [Philosophy](philosophy.md) +- [Getting Started Tutorial](../tutorial/00-getting-started.md) +- [File Structure Reference](../reference/file-structure.md) +- [Template Reference](../reference/templates.md) diff --git a/docs/explanation/philosophy.md b/docs/explanation/philosophy.md new file mode 100644 index 0000000..cace2af --- /dev/null +++ b/docs/explanation/philosophy.md @@ -0,0 +1,496 @@ +# Philosophy + +Understanding the principles and thinking behind FolderWeb. + +## Core Idea + +**Your file system is your content management system.** + +FolderWeb embraces the simplest possible approach to web publishing: create a folder structure that mirrors your site hierarchy, drop files into folders, and they immediately become pages. No database, no admin panel, no build process. + +## Design Principles + +### 1. Just Enough, Nothing More + +FolderWeb applies minimal PHP to enable modern conveniences while remaining maintainable for years or decades. Every feature must justify its existence by solving a real problem without creating new complexity. + +**What this means:** +- No frameworks that might be abandoned +- No build tools that need maintenance +- No package managers introducing dependencies +- No abstractions unless they provide lasting value + +**Example**: Instead of using a routing library, FolderWeb uses PHP's native file functions to map folders to URLs. This will work identically in 2025 and 2035. + +### 2. Longevity Over Novelty + +Code should outlive trends. FolderWeb prioritizes stability and backward compatibility over cutting-edge features. + +**What this means:** +- Standard PHP (no exotic extensions) +- Plain HTML and CSS (no JavaScript required) +- Simple file formats (Markdown, INI) +- Conventions over configuration + +**Why it matters**: A site built today should still work in 10 years without updates. The web's foundational technologies (HTML, CSS, PHP) change slowly and maintain backward compatibility. + +### 3. Transparent and Readable + +You should be able to open any file and immediately understand what it does. No magic, no hidden behavior. + +**What this means:** +- Sparse, meaningful comments +- Descriptive function names +- Simple control flow +- Minimal abstraction layers + +**Example**: Want to know how templates work? Open `app/rendering.php` and read 100 lines of straightforward PHP. No framework documentation needed. + +### 4. Files Are Content + +Your content lives in plain text files you can edit with any text editor. You own your content completely. + +**What this means:** +- Content is portable (copy files, migrate easily) +- Version control friendly (Git tracks changes) +- No lock-in (files work without FolderWeb) +- Backup-friendly (copy a folder) + +**Example**: Your entire site is a folder structure. Zip it, move it to another server, extract it, and it works. No database export/import, no migration scripts. + +## What FolderWeb Is + +### A File-Based Router + +FolderWeb maps your folder structure to URLs: +``` +content/blog/2025-11-02-post/ β†’ yoursite.com/blog/2025-11-02-post/ +``` + +That's it. No route definitions, no controllers, no configuration. + +### A Content Renderer + +FolderWeb converts Markdown to HTML, wraps it in templates, and serves it. Three steps: +1. Find content files +2. Convert to HTML +3. Wrap in template + +### A Minimal Template System + +FolderWeb provides just enough templating to avoid repetition: +- Base template for site structure +- Page template for content wrapper +- List templates for directory views + +All using plain PHP includes. No template language to learn. + +### A Convention Framework + +FolderWeb establishes conventions that eliminate configuration: +- `metadata.ini` for structured data +- `cover.jpg` for images +- `YYYY-MM-DD-slug` for dates +- `filename.lang.ext` for translations + +Learn the conventions once, apply them everywhere. + +## What FolderWeb Is Not + +### Not a CMS + +No admin panel. Edit files directly with your text editor, commit to Git, deploy. + +**Why**: Admin panels add complexity, require maintenance, create security risks, and limit what you can do. Text files are simpler and more powerful. + +### Not a Static Site Generator + +FolderWeb renders pages on request, not at build time. + +**Why**: No build step means immediate feedback. Save a file, refresh your browser, see changes. No waiting for builds, no deployment pipelines required (though you can add them). + +### Not a JavaScript Framework + +Zero JavaScript in the framework. HTML and CSS only. + +**Why**: JavaScript adds complexity, breaks without it, requires builds/transpilation, and changes rapidly. HTML and CSS are stable and sufficient for content sites. + +### Not Opinionated About Design + +FolderWeb provides minimal default styles. Your design is your own. + +**Why**: Design trends change. FolderWeb gives you a clean foundation and gets out of the way. Override everything in `/custom/`. + +## Design Decisions Explained + +### Why PHP 8.4+? + +**Modern features without complexity.** + +PHP 8.4 provides: +- Readonly classes (immutability) +- Property hooks (computed properties) +- Arrow functions (concise code) +- Modern array functions +- Asymmetric visibility (controlled access) + +These features make code clearer without adding dependencies or build steps. PHP 8.4 will be supported for years. + +**Tradeoff**: Requires newer PHP, but gains clarity and performance. + +### Why No Database? + +**Files are simpler.** + +Databases add: +- Setup complexity +- Backup complexity +- Migration complexity +- Performance tuning +- Additional failure points + +For content sites, files provide: +- Version control integration +- Simple backups (copy folder) +- Portability +- Transparent storage +- No setup required + +**When you might need a database**: User-generated content, real-time updates, complex queries, thousands of pages. For those cases, use a different tool. + +### Why INI Files for Metadata? + +**Simple, readable, PHP-native.** + +INI format: +- No parsing library needed (built into PHP) +- Human-readable and editable +- Supports sections for languages +- Familiar format + +**Alternatives considered**: +- **YAML**: Requires library, complex syntax +- **JSON**: Not as human-friendly, no comments +- **TOML**: Requires library, less familiar +- **Frontmatter**: Mixes content and metadata + +### Why Markdown? + +**Readable as plain text, converts to HTML.** + +Markdown is: +- Easy to learn (15 minutes) +- Readable without rendering +- Widely supported +- Future-proof (plain text) +- Version control friendly + +**Alternatives supported**: HTML (for complex layouts), PHP (for dynamic content). + +### Why No Build Tools? + +**Immediate feedback, zero setup.** + +Build tools add: +- Installation steps +- Configuration files +- Waiting for builds +- Build failures to debug +- Another thing that can break + +Without builds: +- Save file β†’ refresh browser β†’ see result +- No setup (just PHP) +- Nothing to configure +- Nothing to break + +**Tradeoff**: Can't use Sass, TypeScript, etc. But you can use modern CSS, which is very capable. + +### Why Trailing Slashes? + +**Consistency and clarity.** + +``` +/blog/ # Directory (list view) +/blog # Redirects to /blog/ +``` + +Trailing slashes clarify that URLs represent directories, not files. Consistent URLs prevent duplicate content and simplify routing. + +### Why Language Prefixes? + +**Clear, hackable URLs.** + +``` +yoursite.com/en/about/ # English +yoursite.com/no/about/ # Norwegian +``` + +Language in URL: +- User sees current language +- Can manually change URL +- Bookmarkable per language +- SEO-friendly (clear language signal) + +**Default language has no prefix** (shorter, cleaner URLs for primary audience). + +## Architectural Patterns + +### Immutable Context + +The `Context` object is readonly (PHP 8.4): +```php +readonly class Context { + public function __construct( + public private(set) string $contentDir, + public private(set) string $currentLang, + // ... + ) {} +} +``` + +**Why**: Prevents accidental mutation, makes code predictable, enables safe sharing of state. + +### Computed Properties + +Properties calculate values on-demand: +```php +public string $langPrefix { + get => $this->currentLang !== $this->defaultLang + ? "/{$this->currentLang}" + : ''; +} +``` + +**Why**: Keeps related logic together, avoids storing derived data, updates automatically. + +### Function-Based API + +Core functionality exposed as functions, not classes: +```php +renderFile($ctx, $filePath); +findAllContentFiles($dir, $lang, $defaultLang, $availableLangs); +``` + +**Why**: Simple to understand, easy to test, no object lifecycle to manage. + +### Template Fallback + +Check custom, fall back to default: +```php +$custom = "/custom/templates/$name.php"; +$default = "/app/default/templates/$name.php"; +return file_exists($custom) ? $custom : $default; +``` + +**Why**: Never modify defaults, always override. Clean separation between framework and customization. + +## Performance Philosophy + +### Performance Through Simplicity + +FolderWeb is fast because it does less: +- No database queries +- No heavy frameworks +- No JavaScript parsing +- Minimal file reads +- Direct PHP includes + +**Measured performance**: Page load time displayed in footer. Pride in speed through simplicity. + +### Caching Strategy + +FolderWeb doesn't implement caching. Instead: +- Use OPcache (PHP bytecode cache) +- Use web server caching (Apache/Nginx) +- Use reverse proxy (Varnish, Cloudflare) +- CSS versioned automatically (MD5 hash) + +**Why**: Let specialized tools handle caching. FolderWeb focuses on core functionality. + +### Optimization Priorities + +1. **Avoid work**: Don't render what's not needed +2. **Use native functions**: PHP's file functions are optimized +3. **Minimal abstraction**: Fewer layers = less overhead + +## Security Philosophy + +### Defense in Depth + +Multiple security layers: +- Path validation (prevent directory traversal) +- Realpath checks (resolve symlinks, verify paths) +- Content root enforcement (files must be in document root) +- Output escaping (prevent XSS) +- MIME type validation (proper content types) + +### Simplicity Is Security + +Less code = smaller attack surface: +- No database (no SQL injection) +- No user input rendering (no XSS in content) +- No file uploads (no upload vulnerabilities) +- No authentication (no auth bypasses) + +**For user-generated content**: Use a different tool. FolderWeb is for static content you control. + +## Maintenance Philosophy + +### Code Should Age Gracefully + +FolderWeb aims to require zero maintenance: +- Standard PHP (no exotic dependencies) +- Minimal third-party code (one library: Parsedown) +- Stable APIs (PHP doesn't break backward compatibility) +- No framework upgrades needed + +**Goal**: Deploy once, forget about it. Check in years later, it still works. + +### Convention Over Configuration + +Fewer configuration options = less to maintain: +- File conventions replace config +- Sensible defaults for everything +- Only configure what's necessary (languages) + +### Documentation Is Core + +Documentation is part of the project: +- Comprehensive reference +- Clear examples +- Explanation of decisions +- How-to guides for common tasks + +**Why**: Future you (or someone else) will thank present you. + +## When to Use FolderWeb + +### Ideal For + +- **Blogs**: Write Markdown, publish immediately +- **Documentation**: Multi-file pages, clear structure +- **Portfolios**: Grid layouts, cover images +- **Marketing sites**: Static content, fast loading +- **Personal sites**: Simple, maintainable +- **Long-term projects**: Will work for decades + +### Not Ideal For + +- **User-generated content**: No database, no auth +- **E-commerce**: Needs dynamic inventory, checkout +- **Social networks**: Real-time updates, complex data +- **SPAs**: JavaScript-heavy, API-driven +- **Large-scale sites**: Thousands of pages (consider static generation) + +### Perfect Fit Scenario + +You want a blog or content site that: +- You control all content +- Loads fast +- Requires minimal maintenance +- Will work for years without updates +- Integrates with Git workflow +- Gives you complete control + +## Comparison to Alternatives + +### vs WordPress + +**WordPress**: Full-featured CMS, database-driven, plugin ecosystem, admin panel, requires regular updates + +**FolderWeb**: File-based, no database, no plugins, no admin, zero maintenance + +**Choose WordPress if**: You need plugins, non-technical editors, or a proven ecosystem + +**Choose FolderWeb if**: You want simplicity, longevity, and complete control + +### vs Jekyll/Hugo (Static Generators) + +**Static Generators**: Build at deploy time, generate HTML files, fast serving, requires builds + +**FolderWeb**: Renders on request, no build step, immediate feedback, simpler workflow + +**Choose Static Generator if**: You want maximum performance, have build infrastructure + +**Choose FolderWeb if**: You want immediate feedback, simpler deployment, dynamic capabilities + +### vs Laravel/Symfony (PHP Frameworks) + +**Frameworks**: Full-stack, MVC architecture, ORM, routing, complex features + +**FolderWeb**: Minimal, file-based routing, no ORM, single purpose + +**Choose Framework if**: You're building a complex web application + +**Choose FolderWeb if**: You're publishing content and want simplicity + +## Future Direction + +### Stability Over Features + +FolderWeb aims to reach "done" status: +- Core functionality complete +- No major features needed +- Focus on documentation and examples +- Bug fixes and security updates only + +### Possible Additions + +Only if they maintain simplicity: +- More template examples +- Additional default styles (opt-in) +- Performance optimizations +- Better error messages + +### Will Never Add + +Features that contradict philosophy: +- JavaScript requirement +- Database integration +- Build process +- Admin panel +- User authentication +- Complex plugin system + +## Contributing to FolderWeb + +### Align With Philosophy + +Proposed changes should: +- Maintain simplicity +- Avoid new dependencies +- Work with PHP 8.4+ +- Be maintainable long-term +- Solve real problems + +### Ideal Contributions + +- Bug fixes +- Performance improvements +- Better documentation +- Example templates +- Test cases +- Clarification of existing code + +### Before Adding Features + +Ask: +1. Can this be solved in userland (custom templates/code)? +2. Does this add complexity for all users? +3. Will this need maintenance in 5 years? +4. Is this truly necessary? + +## Conclusion + +FolderWeb is deliberately simple. It does one thingβ€”publishes content from filesβ€”and does it well. It resists feature creep, embraces constraints, and prioritizes longevity. + +This isn't the right tool for every project. But for content sites that value simplicity, maintainability, and longevity, it might be perfect. + +The code you write today should work in 2035. FolderWeb is built on that principle. + +## Related + +- [Getting Started Tutorial](../tutorial/00-getting-started.md) +- [Architecture Overview](architecture.md) +- [File Structure Reference](../reference/file-structure.md) diff --git a/docs/how-to/custom-styles.md b/docs/how-to/custom-styles.md new file mode 100644 index 0000000..761de27 --- /dev/null +++ b/docs/how-to/custom-styles.md @@ -0,0 +1,398 @@ +# How to Customize Styles + +This guide shows you how to override the default styles with your own CSS. + +## Overview + +FolderWeb uses a fallback system for styles: +1. Check `/custom/styles/base.css` +2. Fall back to `/app/default/styles/base.css` + +The framework automatically versions CSS files with MD5 hashes for cache busting. + +## Quick Start + +### Step 1: Create Custom Stylesheet + +```bash +mkdir -p custom/styles +touch custom/styles/base.css +``` + +### Step 2: Override CSS Variables + +The easiest way to customize is to override CSS custom properties: + +```css +:root { + --color-primary: oklch(0.65 0.20 30); /* Orange */ + --color-secondary: oklch(0.50 0.18 30); /* Dark orange */ + --color-light: oklch(0.98 0.01 30); /* Warm white */ + --color-grey: oklch(0.40 0 0); /* Grey */ + + --font-body: "Helvetica Neue", Arial, sans-serif; + --font-heading: "Georgia", serif; + + --spacing-unit: 1.5rem; + --border-radius: 8px; +} +``` + +### Step 3: Test Your Changes + +Refresh your browser. If changes don't appear, do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R). + +## Available CSS Variables + +### Colors + +```css +--color-primary: oklch(0.65 0.15 250); /* Primary blue */ +--color-secondary: oklch(0.50 0.12 250); /* Dark blue */ +--color-light: oklch(0.97 0.01 250); /* Off-white */ +--color-grey: oklch(0.37 0 0); /* Dark grey */ +``` + +**Note**: FolderWeb uses OKLCH colors for perceptually uniform color spaces. You can also use hex, rgb, or hsl if preferred. + +### Typography + +```css +--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +--font-heading: Georgia, "Times New Roman", serif; + +--font-size-base: 1.125rem; /* 18px */ +--font-size-small: 0.875rem; /* 14px */ +--line-height-base: 1.6; +--line-height-heading: 1.2; +``` + +### Spacing + +```css +--spacing-unit: 1.5rem; /* Base spacing (24px) */ +--spacing-small: 0.75rem; /* 12px */ +--spacing-large: 3rem; /* 48px */ +``` + +### Layout + +```css +--max-width: 70rem; /* Content max-width */ +--border-radius: 4px; /* Corner rounding */ +``` + +## Adding Custom Fonts + +### Step 1: Add Font Files + +```bash +mkdir -p custom/fonts +# Copy your .woff2 files here +cp ~/Downloads/MyFont-Regular.woff2 custom/fonts/ +cp ~/Downloads/MyFont-Bold.woff2 custom/fonts/ +``` + +### Step 2: Declare Font Faces + +In `custom/styles/base.css`: + +```css +@font-face { + font-family: 'MyFont'; + src: url('/custom/fonts/MyFont-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'MyFont'; + src: url('/custom/fonts/MyFont-Bold.woff2') format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +:root { + --font-body: 'MyFont', sans-serif; +} +``` + +**Note**: Font files are automatically served by FolderWeb's static file handler. + +## Page-Specific Styling + +FolderWeb adds dynamic CSS classes to the `` element: + +```html + +``` + +Use these for targeted styling: + +```css +/* Style all blog pages */ +.section-blog { + --color-primary: oklch(0.60 0.15 150); /* Green for blog */ +} + +/* Style a specific page */ +.page-about { + font-size: 1.25rem; +} + +/* Combine for precision */ +.section-docs.page-installation { + background: var(--color-light); +} +``` + +## Responsive Design + +FolderWeb uses modern CSS features for responsiveness. Use `clamp()` for fluid typography: + +```css +:root { + --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem); + --spacing-unit: clamp(1rem, 0.8rem + 1vw, 2rem); +} + +h1 { + font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem); +} +``` + +Use container queries for component responsiveness: + +```css +.card-grid { + container-type: inline-size; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--spacing-unit); +} + +@container (min-width: 600px) { + .card { + padding: var(--spacing-large); + } +} +``` + +## Dark Mode + +Add a dark mode using CSS custom properties and media queries: + +```css +:root { + --color-bg: oklch(0.97 0.01 250); + --color-text: oklch(0.20 0 0); +} + +@media (prefers-color-scheme: dark) { + :root { + --color-bg: oklch(0.20 0 0); + --color-text: oklch(0.95 0 0); + --color-light: oklch(0.25 0 0); + --color-primary: oklch(0.70 0.15 250); + } +} + +body { + background: var(--color-bg); + color: var(--color-text); +} +``` + +## List Template Styling + +Style the different list templates: + +### Grid Layout + +```css +.list-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: var(--spacing-unit); +} + +.list-grid article { + border: 1px solid var(--color-light); + border-radius: var(--border-radius); + overflow: hidden; +} + +.list-grid img { + aspect-ratio: 16 / 9; + object-fit: cover; + width: 100%; +} +``` + +### Card Grid + +```css +.card-grid .card { + background: var(--color-light); + padding: var(--spacing-unit); + border-radius: var(--border-radius); + transition: transform 0.2s ease; +} + +.card-grid .card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); +} +``` + +### FAQ Layout + +```css +.faq details { + border: 1px solid var(--color-light); + border-radius: var(--border-radius); + padding: var(--spacing-unit); + margin-block-end: var(--spacing-small); +} + +.faq summary { + cursor: pointer; + font-weight: 700; + user-select: none; +} + +.faq summary:hover { + color: var(--color-primary); +} +``` + +## Modern CSS Features + +FolderWeb encourages use of modern CSS: + +### CSS Nesting + +```css +.article { + padding: var(--spacing-unit); + + & h2 { + color: var(--color-primary); + margin-block-start: var(--spacing-large); + } + + & a { + color: var(--color-secondary); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } +} +``` + +### Logical Properties + +Use logical properties for internationalization: + +```css +/* Instead of: margin-left, margin-right */ +article { + margin-inline: auto; + padding-inline: var(--spacing-unit); + padding-block: var(--spacing-large); +} + +/* Instead of: text-align: left */ +.content { + text-align: start; +} +``` + +### Modern Color Functions + +```css +:root { + /* OKLCH: lightness, chroma, hue */ + --primary: oklch(0.65 0.15 250); + + /* Adjust lightness for hover */ + --primary-hover: oklch(0.55 0.15 250); + + /* Or use color-mix */ + --primary-light: color-mix(in oklch, var(--primary), white 20%); +} +``` + +## Performance Tips + +### Minimize Custom Styles + +Override only what's necessary. The default stylesheet is already optimized. + +### Use CSS Variables + +Variables reduce repetition and improve maintainability: + +```css +/* Good */ +:root { + --card-padding: var(--spacing-unit); +} + +.card { padding: var(--card-padding); } +.box { padding: var(--card-padding); } + +/* Less maintainable */ +.card { padding: 1.5rem; } +.box { padding: 1.5rem; } +``` + +### Avoid `!important` + +FolderWeb uses low-specificity selectors, so you shouldn't need `!important`. + +## Debugging Styles + +### Check Which Stylesheet is Loaded + +View source and look for: + +```html + +``` + +If you see `/app/styles/`, your custom stylesheet is being used. +If you see `/app/default-styles/`, the default is being used. + +### Browser DevTools + +1. Right-click element β†’ Inspect +2. Check "Computed" tab to see which properties are applied +3. Check "Sources" tab to verify your CSS file is loaded +4. Use "Network" tab to ensure CSS isn't cached with old version + +### Hard Refresh + +Always do a hard refresh after CSS changes: +- **Chrome/Firefox**: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac) +- **Safari**: Cmd+Option+R + +## Complete Override + +If you want complete control, you can replace the entire stylesheet. Copy the default: + +```bash +cp app/default/styles/base.css custom/styles/base.css +``` + +Then edit freely. Remember: you're responsible for all styles when you do this. + +## Related + +- [Custom Templates](custom-templates.md) +- [CSS Reference](../reference/css-variables.md) +- [File Structure Reference](../reference/file-structure.md) diff --git a/docs/how-to/custom-templates.md b/docs/how-to/custom-templates.md new file mode 100644 index 0000000..9aaec69 --- /dev/null +++ b/docs/how-to/custom-templates.md @@ -0,0 +1,288 @@ +# How to Create Custom Templates + +This guide shows you how to override default templates with your own custom designs. + +## Overview + +FolderWeb uses a template fallback system: +1. Check `/custom/templates/` for custom version +2. Fall back to `/app/default/templates/` if not found + +**Important**: Never modify files in `/app/default/` β€” always create custom versions in `/custom/`. + +## Available Templates + +- **base.php** - HTML wrapper (header, navigation, footer) +- **page.php** - Single page/article wrapper +- **list.php** - Simple list view (default) +- **list-grid.php** - Grid layout with images +- **list-card-grid.php** - Card grid (supports PDFs, external links) +- **list-faq.php** - Expandable FAQ/Q&A format + +## Customizing the Base Template + +The base template controls your entire site layout. + +### Step 1: Copy the Default + +```bash +mkdir -p custom/templates +cp app/default/templates/base.php custom/templates/base.php +``` + +### Step 2: Edit Your Copy + +The base template has access to these variables: + +```php +$content // The rendered page content (HTML) +$currentLang // Current language code (e.g., "en", "no") +$navigation // Array of navigation items +$homeLabel // Site title +$translations // Translation strings +$pageTitle // Current page title +$dirName // Parent directory name (for CSS classes) +$pageName // Current page name (for CSS classes) +``` + +### Example: Add a Custom Header + +```php + +``` + +## Customizing the Page Template + +The page template wraps individual articles and pages. + +### Step 1: Copy the Default + +```bash +cp app/default/templates/page.php custom/templates/page.php +``` + +### Step 2: Customize + +Available variables: + +```php +$content // Main content HTML +$pageMetadata // Array of metadata (tags, categories, etc.) +$translations // Translation strings +``` + +### Example: Add Author Information + +```php +
+ +
+

Written by

+
+ + + + + +
+ : + + + +
+ +
+``` + +## Creating a Custom List Template + +List templates control how directories with subdirectories are displayed. + +### Step 1: Create Your Template + +```bash +touch custom/templates/list-custom.php +``` + +### Step 2: Use List Template Variables + +All list templates receive: + +```php +$items // Array of subdirectories +$metadata // Directory metadata +$pageContent // Optional intro content +$translations // Translation strings +``` + +Each item in `$items` has: + +```php +[ + 'title' => 'Post Title', + 'date' => '2. november 2025', + 'url' => '/blog/2025-11-02-post/', + 'cover' => '/blog/2025-11-02-post/cover.jpg', + 'summary' => 'Brief description', + 'pdf' => '/blog/2025-11-02-post/document.pdf', + 'redirect' => 'https://external-site.com' +] +``` + +### Example: Timeline Template + +```php + +
+ +
+ + +
+ +
+ +

+ +

+ +

+ +
+ +
+``` + +### Step 3: Apply Your Template + +Create a `metadata.ini` in the directory: + +```ini +page_template = "list-custom" +``` + +## Template Best Practices + +### Always Escape Output + +Prevent XSS attacks by escaping user-generated content: + +```php + +``` + +### Use Short Echo Tags + +FolderWeb uses modern PHP, so short tags are always available: + +```php + // Good + // Also works, but verbose +``` + +### Check Before Using + +Always check if variables exist: + +```php + + + +``` + +### Leverage CSS Classes + +The base template adds dynamic classes to ``: + +```php + +``` + +Use these for page-specific styling without JavaScript. + +## Advanced: Accessing the Context Object + +Templates can access the full context object `$ctx`: + +```php +contentDir // Path to content directory +$ctx->currentLang // Current language +$ctx->defaultLang // Default language +$ctx->availableLangs // Array of available languages +$ctx->langPrefix // URL prefix (e.g., "/en" or "") +$ctx->requestPath // Current request path +$ctx->hasTrailingSlash // Boolean +$ctx->navigation // Navigation array (computed property) +$ctx->homeLabel // Site title (computed property) +$ctx->translations // Translation array (computed property) +?> +``` + +## Example: Breadcrumb Navigation + +Add breadcrumbs to your page template: + +```php + +``` + +## Testing Your Templates + +1. Clear your browser cache +2. Reload the page +3. Check browser console for errors +4. Validate HTML with W3C validator + +## Reverting Changes + +To revert to default templates, simply delete your custom version: + +```bash +rm custom/templates/base.php +``` + +FolderWeb will automatically fall back to the default. + +## Related + +- [Customizing Styles](custom-styles.md) +- [Template Reference](../reference/templates.md) +- [Metadata Reference](../reference/metadata.md) diff --git a/docs/how-to/multi-language.md b/docs/how-to/multi-language.md new file mode 100644 index 0000000..4464c8b --- /dev/null +++ b/docs/how-to/multi-language.md @@ -0,0 +1,425 @@ +# How to Create a Multi-Language Site + +This guide shows you how to set up and manage a multi-language website with FolderWeb. + +## Overview + +FolderWeb supports multiple languages through: +- Language prefixes in URLs +- Language-specific content files +- Translated slugs and metadata +- Translation files for UI strings + +## Configuration + +### Step 1: Configure Available Languages + +Create or edit `custom/config.ini`: + +```ini +[languages] +default = "en" +available = "en,no,fr" +``` + +- **default**: The primary language (no URL prefix) +- **available**: Comma-separated list of all supported languages + +### Step 2: Create Translation Files + +Create translation files for each language in `custom/languages/`: + +```bash +mkdir -p custom/languages +``` + +**English** (`custom/languages/en.ini`): +```ini +home = "Home" +read_more = "Read more" +categories = "Categories" +tags = "Tags" +footer_text = "Made with FolderWeb" +footer_handcoded = "Generated in" +footer_page_time = "ms" +``` + +**Norwegian** (`custom/languages/no.ini`): +```ini +home = "Hjem" +read_more = "Les mer" +categories = "Kategorier" +tags = "Stikkord" +footer_text = "Laget med FolderWeb" +footer_handcoded = "Generert pΓ₯" +footer_page_time = "ms" +``` + +**French** (`custom/languages/fr.ini`): +```ini +home = "Accueil" +read_more = "Lire la suite" +categories = "CatΓ©gories" +tags = "Γ‰tiquettes" +footer_text = "Créé avec FolderWeb" +footer_handcoded = "GΓ©nΓ©rΓ© en" +footer_page_time = "ms" +``` + +## URL Structure + +With the configuration above: + +- **English** (default): `yoursite.com/about/` +- **Norwegian**: `yoursite.com/no/about/` +- **French**: `yoursite.com/fr/about/` + +The default language never has a URL prefix. + +## Creating Language-Specific Content + +### Method 1: Separate Files Per Language + +Use language suffixes in filenames: `filename.{lang}.ext` + +**Example structure**: +``` +content/about/ +β”œβ”€β”€ index.md # Default language (English) +β”œβ”€β”€ index.no.md # Norwegian version +└── index.fr.md # French version +``` + +**Rules**: +- Language-specific files (`.lang.ext`) show only for that language +- Default files (no language suffix) show only if no language variant exists +- Files are automatically filtered based on current language + +### Example Content + +**content/about/index.md** (English): +```markdown +# About Us + +We are a company dedicated to simplicity. +``` + +**content/about/index.no.md** (Norwegian): +```markdown +# Om Oss + +Vi er et selskap dedikert til enkelhet. +``` + +**content/about/index.fr.md** (French): +```markdown +# Γ€ Propos + +Nous sommes une entreprise dΓ©diΓ©e Γ  la simplicitΓ©. +``` + +Now when users visit: +- `/about/` β†’ Shows English (index.md) +- `/no/about/` β†’ Shows Norwegian (index.no.md) +- `/fr/about/` β†’ Shows French (index.fr.md) + +### Method 2: Language-Specific Folders + +For blog posts and articles, you can create separate folders: + +``` +content/blog/ +β”œβ”€β”€ 2025-11-01-english-post/ +β”‚ └── index.md +β”œβ”€β”€ 2025-11-01-norsk-innlegg/ +β”‚ └── index.no.md +└── 2025-11-01-article-francais/ + └── index.fr.md +``` + +## Translated Slugs and Titles + +Use `metadata.ini` to provide translated slugs and metadata: + +**content/about/metadata.ini**: +```ini +; Default (English) +title = "About Us" +slug = "about" + +[no] +title = "Om Oss" +slug = "om-oss" + +[fr] +title = "Γ€ Propos" +slug = "a-propos" +``` + +Now URLs become: +- English: `/about/` +- Norwegian: `/no/om-oss/` +- French: `/fr/a-propos/` + +The actual folder is still named `about/`, but FolderWeb maps the translated slug to the real folder. + +## Blog Posts with Translations + +**Structure**: +``` +content/blog/ +└── 2025-11-02-my-post/ + β”œβ”€β”€ index.md + β”œβ”€β”€ index.no.md + β”œβ”€β”€ index.fr.md + β”œβ”€β”€ cover.jpg + └── metadata.ini +``` + +**metadata.ini**: +```ini +; Default language +title = "My First Post" +summary = "An introduction to multilingual blogging." +date = "2025-11-02" + +[no] +title = "Mitt FΓΈrste Innlegg" +summary = "En introduksjon til flersprΓ₯klig blogging." + +[fr] +title = "Mon Premier Article" +summary = "Une introduction au blogging multilingue." +``` + +**Important**: Date is global, cover image is shared across languages. + +## Navigation and Menus + +Navigation is automatically built with translations. In `metadata.ini` for each top-level directory: + +**content/blog/metadata.ini**: +```ini +menu = true +menu_order = 1 + +title = "Blog" +[no] +title = "Blogg" +[fr] +title = "Blog" +``` + +**content/about/metadata.ini**: +```ini +menu = true +menu_order = 2 + +title = "About" +[no] +title = "Om" +[fr] +title = "Γ€ Propos" +``` + +Navigation automatically includes language prefix in URLs. + +## Using Translations in Templates + +### In Default Templates + +Translations are automatically available as `$translations` array: + +```php + + + + +

+``` + +### In Custom Templates + +Access translations the same way: + +```php + +``` + +### Access Current Language + +```php +currentLang === 'no'): ?> +

Dette er norsk innhold.

+ +

This is English content.

+ +``` + +## Language Switcher + +Create a language switcher in your custom base template: + +```php + +``` + +Style it: + +```css +.language-switcher { + display: flex; + gap: 0.5rem; +} + +.language-switcher a { + padding: 0.25rem 0.75rem; + border-radius: var(--border-radius); + text-decoration: none; +} + +.language-switcher a.active { + background: var(--color-primary); + color: white; +} +``` + +## List Views with Multiple Languages + +When displaying blog listings, FolderWeb automatically filters items by language: + +``` +content/blog/ +β”œβ”€β”€ 2025-11-01-english-article/ +β”‚ └── index.md # Shows in English +β”œβ”€β”€ 2025-11-02-norsk-artikkel/ +β”‚ └── index.no.md # Shows only in Norwegian +└── 2025-11-03-universal/ + β”œβ”€β”€ index.md # Shows in English + β”œβ”€β”€ index.no.md # Shows in Norwegian + └── index.fr.md # Shows in French +``` + +When viewing `/blog/`: +- Shows "english-article" and "universal" + +When viewing `/no/blog/`: +- Shows "norsk-artikkel" and "universal" + +When viewing `/fr/blog/`: +- Shows only "universal" + +## Handling Missing Translations + +### Default Fallback + +If a translation is missing, FolderWeb uses the default language automatically. + +### Show Different Content + +You can use PHP in your content files: + +```php +currentLang === 'en'): ?> +# Welcome +This page is only in English. + +# Under Construction +This page is not yet translated. + +``` + +## SEO Considerations + +### Add hreflang Tags + +In your custom base template: + +```php + + + + availableLangs as $lang): ?> + defaultLang + ? 'https://yoursite.com/' . trim($ctx->requestPath, '/') + : 'https://yoursite.com/' . $lang . '/' . trim($ctx->requestPath, '/'); + ?> + + + + + +``` + +### Language-Specific Metadata + +Add language attributes: + +```php + +``` + +## Testing Your Multi-Language Site + +1. **Visit default language**: `http://localhost:8000/about/` +2. **Visit Norwegian**: `http://localhost:8000/no/about/` +3. **Visit French**: `http://localhost:8000/fr/about/` +4. **Check navigation**: Ensure links include language prefix +5. **Test translation strings**: Verify UI text changes per language +6. **Check blog listings**: Confirm language-specific posts appear correctly + +## Common Patterns + +### Blog in Multiple Languages + +Structure: +``` +content/blog/ +β”œβ”€β”€ metadata.ini # List template config +└── [date]-[slug]/ + β”œβ”€β”€ index.{lang}.md # One file per language + β”œβ”€β”€ cover.jpg # Shared assets + └── metadata.ini # Translated metadata +``` + +### Documentation in Multiple Languages + +Structure: +``` +content/docs/ +β”œβ”€β”€ metadata.ini # Template config +β”œβ”€β”€ 00-intro.md # Default language +β”œβ”€β”€ 00-intro.no.md # Norwegian +β”œβ”€β”€ 01-setup.md +β”œβ”€β”€ 01-setup.no.md +└── ... +``` + +### Mixed Content Strategy + +Not everything needs translation. You can have: +- English-only blog posts (no language suffix) +- Multi-language main pages (with language suffixes) +- Shared images and assets + +## Related + +- [Metadata Reference](../reference/metadata.md) +- [Configuration Reference](../reference/configuration.md) +- [Template Variables Reference](../reference/templates.md) diff --git a/docs/how-to/working-with-metadata.md b/docs/how-to/working-with-metadata.md new file mode 100644 index 0000000..ad224b5 --- /dev/null +++ b/docs/how-to/working-with-metadata.md @@ -0,0 +1,481 @@ +# How to Work with Metadata + +This guide shows you how to use `metadata.ini` files to control page behavior, appearance, and content. + +## What is Metadata? + +Metadata provides structured information about your content directories without cluttering your content files. It's stored in `metadata.ini` files using the INI format. + +## Basic Metadata File + +Create `metadata.ini` in any content directory: + +```ini +title = "My Page Title" +date = "2025-11-02" +summary = "A brief description of this page." +``` + +## Common Metadata Fields + +### Title + +Controls the displayed title (overrides automatic title extraction): + +```ini +title = "Custom Page Title" +``` + +If not provided, FolderWeb extracts the title from: +1. First H1 heading in content (`# Title` in Markdown) +2. Folder name (as fallback) + +### Date + +Set an explicit date (overrides folder name date extraction): + +```ini +date = "2025-11-02" +``` + +Format: `YYYY-MM-DD` + +FolderWeb automatically formats this in Norwegian style: "2. november 2025" + +### Summary + +Add a summary for list views: + +```ini +summary = "This appears in blog listings and card grids." +``` + +Summaries are displayed in: +- List views +- Grid layouts +- Card grids + +## Navigation Control + +### Adding to Menu + +```ini +menu = true +menu_order = 1 +``` + +- **menu**: Set to `true` to include in site navigation +- **menu_order**: Controls order (lower numbers appear first) + +**Example** - Setting up main navigation: + +**content/blog/metadata.ini**: +```ini +menu = true +menu_order = 1 +title = "Blog" +``` + +**content/about/metadata.ini**: +```ini +menu = true +menu_order = 2 +title = "About" +``` + +**content/contact/metadata.ini**: +```ini +menu = true +menu_order = 3 +title = "Contact" +``` + +Result: Navigation shows "Blog", "About", "Contact" in that order. + +## Template Control + +### Choosing List Template + +For directories with subdirectories, control which list template is used: + +```ini +page_template = "list-grid" +``` + +Available templates: +- `list` - Simple list (default) +- `list-grid` - Grid with cover images +- `list-card-grid` - Card-style grid (supports PDFs, external links) +- `list-faq` - Expandable FAQ format + +**Example** - Blog with grid layout: + +**content/blog/metadata.ini**: +```ini +title = "Blog" +page_template = "list-grid" +``` + +## External Redirects + +Make a directory item link externally (used with `list-card-grid`): + +```ini +redirect = "https://example.com" +``` + +**Example** - Portfolio with external links: + +**content/portfolio/project-live-site/metadata.ini**: +```ini +title = "Visit Live Site" +summary = "Check out the deployed project." +redirect = "https://myproject.com" +``` + +When using the `list-card-grid` template, this creates a card that links to the external URL instead of an internal page. + +## Multi-Language Metadata + +Use sections for language-specific overrides: + +```ini +; Default language values +title = "About Us" +summary = "Learn more about our company." + +[no] +title = "Om Oss" +summary = "LΓ¦r mer om vΓ₯rt selskap." +slug = "om-oss" + +[fr] +title = "Γ€ Propos" +summary = "DΓ©couvrez notre entreprise." +slug = "a-propos" +``` + +Language sections override base values for that language. + +### Translated Slugs + +The `slug` field in language sections changes the URL: + +```ini +[no] +slug = "om-oss" +``` + +Now the Norwegian version is accessible at `/no/om-oss/` instead of `/no/about/`. + +## Custom Metadata Fields + +You can add any custom fields you need: + +```ini +title = "Article Title" +author = "Jane Doe" +reading_time = "5 min" +difficulty = "intermediate" +featured = true +``` + +Access these in custom templates: + +```php + +

By

+ + + + + +``` + +## Arrays in Metadata + +INI format supports arrays using repeated keys: + +```ini +tags[] = "PHP" +tags[] = "Web Development" +tags[] = "Tutorial" + +categories[] = "Programming" +categories[] = "Backend" +``` + +Access in templates: + +```php + +
+ + + +
+ +``` + +## Boolean Values + +Use `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off`: + +```ini +menu = true +featured = yes +draft = false +``` + +## Comments + +Add comments with `;` or `#`: + +```ini +; This is a comment +title = "My Page" + +# This is also a comment +date = "2025-11-02" +``` + +## Metadata Inheritance + +Metadata does **not** inherit from parent directories. Each directory needs its own `metadata.ini`. + +## Metadata for List Items + +When a directory is displayed in a list view, FolderWeb loads its metadata: + +**content/blog/2025-11-01-first-post/metadata.ini**: +```ini +title = "My First Blog Post" +date = "2025-11-01" +summary = "An introduction to blogging with FolderWeb." +``` + +This metadata appears in the blog listing at `/blog/`. + +## Complete Example: Blog Setup + +### Blog Directory Metadata + +**content/blog/metadata.ini**: +```ini +title = "Blog" +menu = true +menu_order = 1 +page_template = "list-grid" + +[no] +title = "Blogg" +slug = "blogg" +``` + +### Individual Post Metadata + +**content/blog/2025-11-02-web-performance/metadata.ini**: +```ini +title = "Optimizing Web Performance" +date = "2025-11-02" +summary = "Learn techniques to make your website faster." +author = "Jane Developer" +reading_time = "8 min" + +tags[] = "Performance" +tags[] = "Web Development" +tags[] = "Optimization" + +categories[] = "Technical" +categories[] = "Tutorial" + +[no] +title = "Optimalisering av Nettsideytelse" +summary = "LΓ¦r teknikker for Γ₯ gjΓΈre nettsiden din raskere." +``` + +## Accessing Metadata in Templates + +### In Page Templates + +```php + + +
+
+

+

By

+
+ + + + +
+ + + +
+ +
+``` + +### In List Templates + +Each item in `$items` includes its metadata: + +```php + +
+

+ + + +

+ + + + + + +

+ + + + + + + + Download PDF + + + + + Visit External Link + + +
+ +``` + +## Debugging Metadata + +### Check if Metadata is Loaded + +In your template, dump the metadata: + +```php +
+``` + +Or for list items: + +```php +
+``` + +### Verify INI Syntax + +Use PHP to test your INI file: + +```bash +php -r "print_r(parse_ini_file('content/blog/metadata.ini', true));" +``` + +This shows parsed values and helps identify syntax errors. + +## Best Practices + +### Use Consistent Field Names + +Stick to standard fields for common data: +- `title` for titles +- `date` for dates +- `summary` for summaries +- `author` for authors + +### Escape Output + +Always escape metadata in templates: + +```php + +``` + +### Provide Defaults + +Use null coalescing for missing fields: + +```php +$author = $metadata['author'] ?? 'Anonymous'; +$date = $metadata['date'] ?? 'Unknown date'; +``` + +### Keep It Simple + +Only add metadata fields you actually use. Don't over-engineer. + +### Use Comments + +Document non-obvious metadata: + +```ini +; Featured articles appear at the top of the homepage +featured = true + +; Legacy field kept for backwards compatibility +old_url = "/blog/old-slug/" +``` + +## Common Patterns + +### Blog Post + +```ini +title = "Post Title" +date = "2025-11-02" +summary = "Brief description" +author = "Author Name" +tags[] = "tag1" +tags[] = "tag2" +``` + +### Documentation Page + +```ini +title = "API Reference" +menu = true +menu_order = 3 +page_template = "list" +``` + +### Portfolio Item + +```ini +title = "Project Name" +date = "2025-11-02" +summary = "Project description" +redirect = "https://live-demo.com" +``` + +### FAQ Section + +```ini +title = "Frequently Asked Questions" +menu = true +menu_order = 4 +page_template = "list-faq" +``` + +## Related + +- [Multi-Language Guide](multi-language.md) +- [Custom Templates](custom-templates.md) +- [Metadata Reference](../reference/metadata.md) +- [Template Variables Reference](../reference/templates.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..2a0e5b2 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,309 @@ +# FolderWeb Documentation + +Complete documentation for FolderWeb, a minimalist file-based PHP framework for content websites. + +## What is FolderWeb? + +FolderWeb is a file-based content publishing framework. Drop Markdown files in folders, and they become pages. No database, no build process, no JavaScript required. Just PHP, HTML, and CSS. + +**Core principle**: Your file system is your content management system. + +## Documentation Structure + +This documentation follows the [Diataxis framework](https://diataxis.fr/), organizing content into four types: + +### πŸŽ“ Tutorial + +**Learning-oriented**: Get started with FolderWeb + +- [Getting Started](tutorial/00-getting-started.md) - Build your first site in 10 minutes + +**Start here** if you're new to FolderWeb. + +### πŸ“‹ How-To Guides + +**Task-oriented**: Solve specific problems + +- [Custom Templates](how-to/custom-templates.md) - Override default templates +- [Custom Styles](how-to/custom-styles.md) - Customize appearance with CSS +- [Multi-Language Sites](how-to/multi-language.md) - Set up multiple languages +- [Working with Metadata](how-to/working-with-metadata.md) - Use metadata.ini files + +**Use these** when you need to accomplish a specific task. + +### πŸ“– Reference + +**Information-oriented**: Look up technical details + +- [File Structure](reference/file-structure.md) - Complete directory layout +- [Metadata](reference/metadata.md) - All metadata fields +- [Templates](reference/templates.md) - Template variables and usage +- [Configuration](reference/configuration.md) - Configuration options +- [CSS Variables](reference/css-variables.md) - Styling customization + +**Consult these** when you need precise technical information. + +### πŸ’‘ Explanation + +**Understanding-oriented**: Understand concepts and design + +- [Philosophy](explanation/philosophy.md) - Design principles and thinking +- [Architecture](explanation/architecture.md) - How FolderWeb works + +**Read these** to understand why FolderWeb works the way it does. + +## Quick Links + +### Common Tasks + +- **Create a page**: Drop `index.md` in a folder +- **Create a blog**: Make a folder with subdirectories +- **Add navigation**: Set `menu = true` in `metadata.ini` +- **Customize look**: Override `/custom/styles/base.css` +- **Use custom template**: Set `page_template = "template-name"` in metadata +- **Multi-language**: Configure languages and add `.{lang}.md` files + +### Key Concepts + +- **File-based routing**: `content/blog/post/` β†’ `yoursite.com/blog/post/` +- **Template fallback**: Custom templates override defaults +- **Language prefixes**: `/en/page/` for English, `/no/page/` for Norwegian +- **Metadata inheritance**: None - each directory has its own `metadata.ini` +- **Content types**: Single-file, multi-file, or list view + +## Quick Start + +```bash +# 1. Create project +mkdir my-site && cd my-site + +# 2. Copy framework files +cp -r /path/to/folderweb/app ./app + +# 3. Create content +mkdir content +echo "# Hello World" > content/index.md + +# 4. Start server +php -S localhost:8000 -t . app/router.php + +# 5. Visit http://localhost:8000 +``` + +## System Requirements + +- **PHP**: 8.4 or higher +- **Web server**: Apache, Nginx, or PHP built-in server +- **Extensions**: Standard PHP (no special extensions needed) + +## File Structure Overview + +``` +project/ +β”œβ”€β”€ app/ # Framework (never modify) +β”‚ β”œβ”€β”€ router.php # Entry point +β”‚ β”œβ”€β”€ content.php # Content discovery +β”‚ β”œβ”€β”€ rendering.php # Template rendering +β”‚ └── default/ # Default templates, styles, languages +β”œβ”€β”€ content/ # Your website content +β”‚ β”œβ”€β”€ index.md # Home page +β”‚ β”œβ”€β”€ about/ # About page +β”‚ └── blog/ # Blog with posts +└── custom/ # Your customizations + β”œβ”€β”€ templates/ # Custom templates + β”œβ”€β”€ styles/ # Custom CSS + β”œβ”€β”€ languages/ # Custom translations + └── config.ini # Configuration overrides +``` + +## Core Features + +### File-Based Routing + +Your folder structure defines your URLs: + +``` +content/blog/2025-11-02-post/ β†’ /blog/2025-11-02-post/ +``` + +No route configuration needed. + +### Multiple Content Types + +- **Single-file page**: One file in a directory +- **Multi-file page**: Multiple files combined into one page +- **List view**: Directory with subdirectories becomes a listing + +### Template System + +Six templates included: +- `base.php` - HTML wrapper +- `page.php` - Page wrapper +- `list.php` - Simple list +- `list-grid.php` - Grid with images +- `list-card-grid.php` - Card grid +- `list-faq.php` - Expandable FAQ + +Override any template in `/custom/templates/`. + +### Multi-Language Support + +Configure languages: +```ini +[languages] +default = "en" +available = "en,no,fr" +``` + +Create language-specific files: +``` +index.md # English (default) +index.no.md # Norwegian +index.fr.md # French +``` + +URLs automatically prefixed: `/`, `/no/`, `/fr/` + +### Metadata System + +Control behavior with `metadata.ini`: + +```ini +title = "My Page" +date = "2025-11-02" +summary = "Page description" +menu = true +menu_order = 1 +page_template = "list-grid" +``` + +### Modern CSS + +Default styles use: +- CSS custom properties (variables) +- CSS nesting +- OKLCH colors +- Grid layouts +- Fluid typography with `clamp()` +- Logical properties + +Override in `/custom/styles/base.css`. + +## Philosophy Highlights + +- **Just enough, nothing more**: Minimal, maintainable code +- **Longevity over novelty**: Works today, works in 2035 +- **Files are content**: Portable, version-controllable +- **No JavaScript required**: Pure HTML and CSS +- **No build process**: Immediate feedback + +Read the full [Philosophy](explanation/philosophy.md) for more. + +## Example Use Cases + +### Personal Blog + +``` +content/ +β”œβ”€β”€ index.md # About me +β”œβ”€β”€ blog/ # Blog posts +β”‚ β”œβ”€β”€ metadata.ini # page_template = "list-grid" +β”‚ β”œβ”€β”€ 2025-11-01-post/ +β”‚ └── 2025-11-02-post/ +└── contact/ # Contact page + └── index.md +``` + +### Documentation Site + +``` +content/ +β”œβ”€β”€ index.md # Introduction +β”œβ”€β”€ getting-started/ # Multi-file tutorial +β”‚ β”œβ”€β”€ 00-install.md +β”‚ β”œβ”€β”€ 01-setup.md +β”‚ └── 02-first-steps.md +└── reference/ # API reference + β”œβ”€β”€ metadata.ini # page_template = "list" + β”œβ”€β”€ functions/ + └── classes/ +``` + +### Portfolio + +``` +content/ +β”œβ”€β”€ index.md # Homepage +β”œβ”€β”€ projects/ # Project grid +β”‚ β”œβ”€β”€ metadata.ini # page_template = "list-card-grid" +β”‚ β”œβ”€β”€ project-1/ +β”‚ β”‚ β”œβ”€β”€ index.md +β”‚ β”‚ β”œβ”€β”€ cover.jpg +β”‚ β”‚ └── metadata.ini # redirect = "https://project.com" +β”‚ └── project-2/ +└── about/ + └── index.md +``` + +## When to Use FolderWeb + +### βœ… Ideal For + +- Blogs and content sites +- Documentation +- Portfolios +- Marketing sites +- Personal websites +- Projects requiring longevity + +### ❌ Not Ideal For + +- User-generated content (no database/auth) +- E-commerce (use dedicated platform) +- Social networks (need real-time features) +- JavaScript-heavy SPAs +- Sites with thousands of pages (consider static generators) + +## Getting Help + +### Documentation + +- Follow the [Tutorial](tutorial/00-getting-started.md) step-by-step +- Check [How-To Guides](how-to/) for specific tasks +- Consult [Reference](reference/) for technical details +- Read [Explanation](explanation/) for concepts + +### Common Issues + +**Styles not loading**: Hard refresh (Ctrl+Shift+R or Cmd+Shift+R) + +**404 errors**: Check folder exists and has content files + +**Language not working**: Ensure language is in `available` config + +**Metadata not appearing**: Verify INI syntax with `parse_ini_file()` + +**Templates not found**: Check file exists in `/custom/templates/` + +## Contributing + +FolderWeb prioritizes stability and simplicity. Contributions should: +- Maintain simplicity +- Avoid dependencies +- Solve real problems +- Be maintainable long-term + +## License + +Check the project repository for license information. + +## Next Steps + +1. **New user?** Start with the [Getting Started Tutorial](tutorial/00-getting-started.md) +2. **Need to do something specific?** Browse [How-To Guides](how-to/) +3. **Want technical details?** Explore [Reference Documentation](reference/) +4. **Curious about design?** Read [Philosophy](explanation/philosophy.md) and [Architecture](explanation/architecture.md) + +--- + +**FolderWeb**: Simple, file-based content publishing for the long term. diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md new file mode 100644 index 0000000..038eeaa --- /dev/null +++ b/docs/reference/configuration.md @@ -0,0 +1,400 @@ +# Configuration Reference + +Complete reference for FolderWeb configuration options. + +## Configuration File + +**Location**: `/custom/config.ini` (or `/app/config.ini` for defaults) + +**Format**: INI format with sections + +**Override Behavior**: Custom config values override defaults + +## Configuration Sections + +### [languages] + +Controls language support and defaults. + +```ini +[languages] +default = "en" +available = "en,no,fr" +``` + +#### default + +**Type**: String +**Required**: Yes +**Default**: `"en"` + +The primary language of your site. This language: +- Has no URL prefix +- Is used as fallback for missing translations +- Should match a translation file in `/custom/languages/` or `/app/default/languages/` + +**Examples**: +```ini +default = "en" ; English +default = "no" ; Norwegian +default = "fr" ; French +default = "de" ; German +``` + +#### available + +**Type**: Comma-separated string +**Required**: Yes +**Default**: `"en,no"` + +List of all languages supported by your site. Must include the default language. + +**Examples**: +```ini +; English only +available = "en" + +; English and Norwegian +available = "en,no" + +; Multiple languages +available = "en,no,fr,de,es" +``` + +**URL Structure**: +- Default language: `yoursite.com/page/` +- Other languages: `yoursite.com/fr/page/`, `yoursite.com/de/page/` + +## File Structure + +### Default Configuration + +**Location**: `/app/config.ini` + +```ini +[languages] +default = "no" +available = "no,en" +``` + +**Note**: Never modify `/app/config.ini` directly. + +### Custom Configuration + +**Location**: `/custom/config.ini` + +Create this file to override defaults: + +```ini +[languages] +default = "en" +available = "en,fr,de" +``` + +Only include settings you want to override. + +## Configuration Loading + +Configuration is loaded in this order: + +1. Load `/app/config.ini` (defaults) +2. Load `/custom/config.ini` if exists +3. Merge, with custom values overriding defaults + +Example: + +**app/config.ini**: +```ini +[languages] +default = "no" +available = "no,en" +``` + +**custom/config.ini**: +```ini +[languages] +default = "en" +``` + +**Result**: +```ini +[languages] +default = "en" ; From custom +available = "no,en" ; From default (not overridden) +``` + +## Complete Configuration Examples + +### Single Language Site + +```ini +[languages] +default = "en" +available = "en" +``` + +URLs: All at root level (`/page/`, `/blog/`, etc.) + +### Bilingual Site (English/Norwegian) + +```ini +[languages] +default = "en" +available = "en,no" +``` + +URLs: +- English: `/page/`, `/blog/` +- Norwegian: `/no/page/`, `/no/blog/` + +### Multilingual Site + +```ini +[languages] +default = "en" +available = "en,no,fr,de,es" +``` + +URLs: +- English (default): `/page/` +- Norwegian: `/no/page/` +- French: `/fr/page/` +- German: `/de/page/` +- Spanish: `/es/page/` + +## Language Codes + +Use ISO 639-1 two-letter codes: + +| Code | Language | +|------|----------| +| `en` | English | +| `no` | Norwegian | +| `fr` | French | +| `de` | German | +| `es` | Spanish | +| `it` | Italian | +| `pt` | Portuguese | +| `nl` | Dutch | +| `sv` | Swedish | +| `da` | Danish | +| `fi` | Finnish | +| `pl` | Polish | +| `ru` | Russian | +| `ja` | Japanese | +| `zh` | Chinese | +| `ko` | Korean | +| `ar` | Arabic | + +Full list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + +## Related Configuration + +### Translation Files + +For each language in `available`, create a translation file: + +**Pattern**: `/custom/languages/{lang}.ini` + +**Example** (with `available = "en,no,fr"`): +``` +custom/languages/ +β”œβ”€β”€ en.ini +β”œβ”€β”€ no.ini +└── fr.ini +``` + +See [Translation Reference](translations.md) for details. + +### Content Files + +Language-specific content uses the same codes: + +**Pattern**: `filename.{lang}.ext` + +**Examples**: +- `index.md` - Default language +- `index.no.md` - Norwegian +- `index.fr.md` - French + +See [Multi-Language Guide](../how-to/multi-language.md) for details. + +## Validation + +### Check Configuration + +Verify configuration is loaded correctly: + +**test-config.php**: +```php +defaultLang . "\n"; +echo "Available languages: " . implode(', ', $ctx->availableLangs) . "\n"; +echo "Current language: " . $ctx->currentLang . "\n"; +echo "Language prefix: " . $ctx->langPrefix . "\n"; +``` + +Run: +```bash +php test-config.php +``` + +### Common Errors + +**Missing default in available**: +```ini +; Wrong - default must be in available +[languages] +default = "en" +available = "no,fr" + +; Correct +[languages] +default = "en" +available = "en,no,fr" +``` + +**Invalid language codes**: +```ini +; Avoid - use ISO codes +available = "english,norwegian" + +; Correct - ISO 639-1 codes +available = "en,no" +``` + +**Typos in section names**: +```ini +; Wrong +[language] +default = "en" + +; Correct +[languages] +default = "en" +``` + +## Future Configuration Options + +FolderWeb is minimal by design. Currently, only language settings are configurable. + +Possible future additions: +- Date format preferences +- Timezone settings +- Content directory override +- Cache settings + +For now, these are handled through code or conventions. + +## Environment-Specific Configuration + +To use different configs per environment: + +**Option 1: Conditional Loading** + +**custom/config.ini**: +```ini +[languages] +default = "en" +available = "en,no" +``` + +**custom/config.dev.ini**: +```ini +[languages] +default = "en" +available = "en" +``` + +Modify `app/config.php` to load based on environment. + +**Option 2: Separate Deployments** + +Use different `custom/config.ini` files per deployment: +- Development: `/custom/config.ini` with dev settings +- Production: Different `/custom/config.ini` with prod settings + +## Configuration in Templates + +Access configuration through context object: + +```php + + + + + + + +currentLang === $ctx->defaultLang): ?> +

Viewing in default language

+ + + +About +``` + +## Best Practices + +### Keep It Simple + +Only configure what's necessary. FolderWeb embraces sensible defaults. + +### Match Translation Files + +Ensure translation files exist for all languages: + +```ini +[languages] +available = "en,no,fr" +``` + +Requires: +- `custom/languages/en.ini` +- `custom/languages/no.ini` +- `custom/languages/fr.ini` + +### Choose Appropriate Default + +Your default language should be: +- Your primary audience's language +- The language with most content +- The language you'll maintain long-term + +### Document Your Choices + +Add comments to explain configuration: + +```ini +; Site uses English as primary language (most content) +; Norwegian and French are secondary translations +[languages] +default = "en" +available = "en,no,fr" +``` + +## Testing Configuration Changes + +After changing configuration: + +1. **Clear browser cache** (Ctrl+Shift+R or Cmd+Shift+R) +2. **Test default language**: Visit `/` +3. **Test other languages**: Visit `/no/`, `/fr/`, etc. +4. **Check navigation**: Ensure menu links include language prefix +5. **Verify translations**: Check UI strings change per language +6. **Test language switcher**: Confirm switching works + +## Related + +- [Multi-Language Guide](../how-to/multi-language.md) +- [Translation Reference](translations.md) +- [Metadata Reference](metadata.md) +- [Context Object Reference](templates.md#context-object) diff --git a/docs/reference/css-variables.md b/docs/reference/css-variables.md new file mode 100644 index 0000000..135978d --- /dev/null +++ b/docs/reference/css-variables.md @@ -0,0 +1,538 @@ +# CSS Variables Reference + +Complete reference for all CSS custom properties available in FolderWeb. + +## Overview + +FolderWeb uses CSS custom properties (variables) for theming. Override these in `/custom/styles/base.css` to customize your site's appearance. + +## Color Variables + +### Primary Colors + +```css +:root { + --color-primary: oklch(0.65 0.15 250); + --color-secondary: oklch(0.50 0.12 250); + --color-light: oklch(0.97 0.01 250); + --color-grey: oklch(0.37 0 0); +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--color-primary` | Blue (OKLCH) | Primary brand color, links, buttons | +| `--color-secondary` | Dark blue (OKLCH) | Secondary accents, hover states | +| `--color-light` | Off-white (OKLCH) | Background, light sections | +| `--color-grey` | Dark grey | Body text, headings | + +### OKLCH Color Space + +FolderWeb uses OKLCH for perceptually uniform colors: + +```css +oklch(lightness chroma hue) +``` + +- **Lightness**: 0 (black) to 1 (white) +- **Chroma**: 0 (grey) to ~0.4 (vibrant) +- **Hue**: 0-360 degrees + +**Examples**: +```css +/* Blue hues (250Β°) */ +--color-primary: oklch(0.65 0.15 250); + +/* Orange hues (30Β°) */ +--color-primary: oklch(0.65 0.20 30); + +/* Green hues (150Β°) */ +--color-primary: oklch(0.60 0.15 150); + +/* Red hues (0Β°) */ +--color-primary: oklch(0.60 0.20 0); + +/* Purple hues (300Β°) */ +--color-primary: oklch(0.60 0.18 300); +``` + +### Alternative Color Formats + +You can use hex, rgb, or hsl instead: + +```css +:root { + /* Hex */ + --color-primary: #4169E1; + --color-secondary: #1E3A8A; + + /* RGB */ + --color-primary: rgb(65, 105, 225); + --color-secondary: rgb(30, 58, 138); + + /* HSL */ + --color-primary: hsl(225, 73%, 57%); + --color-secondary: hsl(225, 64%, 33%); +} +``` + +## Typography Variables + +### Font Families + +```css +:root { + --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-heading: Georgia, "Times New Roman", serif; +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--font-body` | System sans-serif stack | Body text, paragraphs | +| `--font-heading` | Serif stack | Headings (h1-h6) | + +**Custom Fonts**: +```css +@font-face { + font-family: 'MyFont'; + src: url('/custom/fonts/MyFont.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +:root { + --font-body: 'MyFont', sans-serif; +} +``` + +### Font Sizes + +```css +:root { + --font-size-base: 1.125rem; /* 18px */ + --font-size-small: 0.875rem; /* 14px */ +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--font-size-base` | 1.125rem (18px) | Body text size | +| `--font-size-small` | 0.875rem (14px) | Small text, metadata | + +**Responsive Sizing**: +```css +:root { + /* Fluid typography */ + --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem); +} + +h1 { + font-size: clamp(2rem, 1.5rem + 2vw, 3.5rem); +} +``` + +### Line Heights + +```css +:root { + --line-height-base: 1.6; + --line-height-heading: 1.2; +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--line-height-base` | 1.6 | Body text line height | +| `--line-height-heading` | 1.2 | Heading line height | + +## Spacing Variables + +```css +:root { + --spacing-unit: 1.5rem; /* 24px */ + --spacing-small: 0.75rem; /* 12px */ + --spacing-large: 3rem; /* 48px */ +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--spacing-unit` | 1.5rem (24px) | Base spacing unit | +| `--spacing-small` | 0.75rem (12px) | Small gaps | +| `--spacing-large` | 3rem (48px) | Large gaps, section spacing | + +**Usage**: +```css +.card { + padding: var(--spacing-unit); + margin-block-end: var(--spacing-large); +} + +.tag { + padding: var(--spacing-small); +} +``` + +**Responsive Spacing**: +```css +:root { + --spacing-unit: clamp(1rem, 0.8rem + 1vw, 2rem); +} +``` + +## Layout Variables + +```css +:root { + --max-width: 70rem; /* 1120px */ + --border-radius: 4px; +} +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `--max-width` | 70rem (1120px) | Content max-width | +| `--border-radius` | 4px | Corner rounding | + +**Usage**: +```css +.contain { + max-inline-size: var(--max-width); + margin-inline: auto; +} + +.card { + border-radius: var(--border-radius); +} +``` + +## Complete Variable List + +```css +:root { + /* Colors */ + --color-primary: oklch(0.65 0.15 250); + --color-secondary: oklch(0.50 0.12 250); + --color-light: oklch(0.97 0.01 250); + --color-grey: oklch(0.37 0 0); + + /* Typography */ + --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-heading: Georgia, "Times New Roman", serif; + --font-size-base: 1.125rem; + --font-size-small: 0.875rem; + --line-height-base: 1.6; + --line-height-heading: 1.2; + + /* Spacing */ + --spacing-unit: 1.5rem; + --spacing-small: 0.75rem; + --spacing-large: 3rem; + + /* Layout */ + --max-width: 70rem; + --border-radius: 4px; +} +``` + +## Customization Examples + +### Orange Theme + +```css +:root { + --color-primary: oklch(0.65 0.20 30); + --color-secondary: oklch(0.50 0.18 30); + --color-light: oklch(0.97 0.01 30); +} +``` + +### Dark Mode + +```css +@media (prefers-color-scheme: dark) { + :root { + --color-primary: oklch(0.70 0.15 250); + --color-secondary: oklch(0.80 0.12 250); + --color-light: oklch(0.25 0 0); + --color-grey: oklch(0.90 0 0); + } +} +``` + +### Large Text + +```css +:root { + --font-size-base: 1.25rem; /* 20px */ + --line-height-base: 1.7; + --spacing-unit: 2rem; +} +``` + +### Tight Layout + +```css +:root { + --max-width: 50rem; /* 800px */ + --spacing-unit: 1rem; /* 16px */ + --spacing-large: 2rem; /* 32px */ +} +``` + +### Rounded Design + +```css +:root { + --border-radius: 12px; +} +``` + +## Using Variables + +### In Your Styles + +```css +.card { + background: var(--color-light); + color: var(--color-grey); + padding: var(--spacing-unit); + border-radius: var(--border-radius); +} + +.button { + background: var(--color-primary); + color: white; + padding: var(--spacing-small) var(--spacing-unit); + border-radius: var(--border-radius); +} + +.button:hover { + background: var(--color-secondary); +} +``` + +### With Fallbacks + +Provide fallbacks for older browsers: + +```css +.card { + background: #F5F5F5; /* Fallback */ + background: var(--color-light); /* Variable */ +} +``` + +### With calc() + +Combine with calculations: + +```css +.card { + padding: calc(var(--spacing-unit) * 2); + margin-block-end: calc(var(--spacing-large) - 1rem); +} +``` + +### With color-mix() + +Create variations: + +```css +.button { + background: var(--color-primary); +} + +.button:hover { + background: color-mix(in oklch, var(--color-primary), black 10%); +} + +.button-light { + background: color-mix(in oklch, var(--color-primary), white 80%); +} +``` + +## Adding Custom Variables + +Define your own variables: + +```css +:root { + /* Custom color palette */ + --color-accent: oklch(0.70 0.15 180); + --color-warning: oklch(0.70 0.20 60); + --color-danger: oklch(0.60 0.20 10); + --color-success: oklch(0.65 0.15 140); + + /* Custom spacing */ + --spacing-xs: 0.25rem; + --spacing-xl: 4rem; + --spacing-2xl: 6rem; + + /* Custom typography */ + --font-mono: 'Monaco', 'Courier New', monospace; + --font-size-large: 1.5rem; + --font-size-xlarge: 2rem; + + /* Custom layout */ + --sidebar-width: 20rem; + --header-height: 4rem; + --content-gap: 2rem; +} +``` + +Use them: + +```css +.sidebar { + width: var(--sidebar-width); + background: var(--color-light); +} + +code { + font-family: var(--font-mono); + background: var(--color-accent); + padding: var(--spacing-xs); +} +``` + +## Scoped Variables + +Override variables for specific sections: + +```css +/* Global defaults */ +:root { + --color-primary: oklch(0.65 0.15 250); +} + +/* Blog section uses green */ +.section-blog { + --color-primary: oklch(0.60 0.15 150); +} + +/* About page uses orange */ +.page-about { + --color-primary: oklch(0.65 0.20 30); +} + +/* Variables cascade to children */ +.section-blog .button { + background: var(--color-primary); /* Green in blog */ +} +``` + +## Responsive Variables + +Change variables at breakpoints: + +```css +:root { + --spacing-unit: 1rem; + --font-size-base: 1rem; + --max-width: 60rem; +} + +@media (min-width: 768px) { + :root { + --spacing-unit: 1.5rem; + --font-size-base: 1.125rem; + --max-width: 70rem; + } +} + +@media (min-width: 1200px) { + :root { + --spacing-unit: 2rem; + --font-size-base: 1.25rem; + --max-width: 80rem; + } +} +``` + +## Browser Support + +CSS custom properties are supported in all modern browsers: +- Chrome 49+ +- Firefox 31+ +- Safari 9.1+ +- Edge 15+ + +For older browsers, provide fallbacks or use PostCSS with custom properties plugin. + +## Debugging Variables + +Inspect variables in browser DevTools: + +1. Right-click element β†’ Inspect +2. Check "Computed" tab +3. Scroll to custom properties section +4. See resolved values + +Or log in console: + +```javascript +getComputedStyle(document.documentElement).getPropertyValue('--color-primary') +``` + +## Best Practices + +### Use Semantic Names + +```css +/* Good - semantic */ +--color-primary +--color-text +--color-background + +/* Avoid - non-semantic */ +--color-blue +--color-444 +``` + +### Group Related Variables + +```css +:root { + /* Colors */ + --color-primary: ...; + --color-secondary: ...; + + /* Typography */ + --font-body: ...; + --font-heading: ...; + + /* Spacing */ + --spacing-unit: ...; +} +``` + +### Document Your Variables + +```css +:root { + /* Brand colors from design system */ + --color-primary: oklch(0.65 0.15 250); /* Blue - primary CTA */ + --color-secondary: oklch(0.50 0.12 250); /* Dark blue - accents */ + + /* Layout constraints */ + --max-width: 70rem; /* 1120px - content max width */ +} +``` + +### Provide Fallbacks + +```css +.card { + background: #F5F5F5; + background: var(--color-light); +} +``` + +## Related + +- [Custom Styles Guide](../how-to/custom-styles.md) +- [Template Reference](templates.md) +- [File Structure Reference](file-structure.md) diff --git a/docs/reference/file-structure.md b/docs/reference/file-structure.md new file mode 100644 index 0000000..9e86833 --- /dev/null +++ b/docs/reference/file-structure.md @@ -0,0 +1,394 @@ +# File Structure Reference + +Complete reference for FolderWeb's file and directory structure. + +## Root Structure + +``` +project/ +β”œβ”€β”€ app/ # Framework core (never modify) +β”œβ”€β”€ content/ # Your website content +β”œβ”€β”€ custom/ # Your customizations +└── .htaccess # Web server configuration (optional) +``` + +## App Directory (Framework Core) + +``` +app/ +β”œβ”€β”€ router.php # Main entry point and request router +β”œβ”€β”€ content.php # Content discovery and parsing functions +β”œβ”€β”€ rendering.php # Template rendering engine +β”œβ”€β”€ context.php # Context object (readonly class) +β”œβ”€β”€ config.php # Configuration loader +β”œβ”€β”€ helpers.php # Utility functions +β”œβ”€β”€ constants.php # File extension constants +β”œβ”€β”€ static.php # Static file server +β”œβ”€β”€ config.ini # Default configuration +β”œβ”€β”€ default/ # Default files (fallback) +β”‚ β”œβ”€β”€ templates/ # Default templates +β”‚ β”‚ β”œβ”€β”€ base.php # HTML wrapper +β”‚ β”‚ β”œβ”€β”€ page.php # Page wrapper +β”‚ β”‚ β”œβ”€β”€ list.php # Simple list +β”‚ β”‚ β”œβ”€β”€ list-grid.php # Grid layout +β”‚ β”‚ β”œβ”€β”€ list-card-grid.php # Card grid +β”‚ β”‚ └── list-faq.php # FAQ layout +β”‚ β”œβ”€β”€ styles/ # Default CSS +β”‚ β”‚ └── base.css # Main stylesheet +β”‚ β”œβ”€β”€ languages/ # Default translations +β”‚ β”‚ β”œβ”€β”€ en.ini # English +β”‚ β”‚ └── no.ini # Norwegian +β”‚ └── content/ # Demo content (fallback) +└── vendor/ # Third-party libraries + └── Parsedown.php # Markdown parser +``` + +**Important**: Never modify files in `/app/`. All customization goes in `/custom/`. + +## Custom Directory + +``` +custom/ +β”œβ”€β”€ templates/ # Override templates +β”‚ β”œβ”€β”€ base.php # Custom base template +β”‚ β”œβ”€β”€ page.php # Custom page template +β”‚ β”œβ”€β”€ list-*.php # Custom list templates +β”‚ └── [custom].php # Your custom templates +β”œβ”€β”€ styles/ # Override styles +β”‚ └── base.css # Custom stylesheet +β”œβ”€β”€ languages/ # Override translations +β”‚ β”œβ”€β”€ en.ini # English translations +β”‚ β”œβ”€β”€ no.ini # Norwegian translations +β”‚ └── [lang].ini # Other languages +β”œβ”€β”€ fonts/ # Custom web fonts +β”‚ └── *.woff2 # Font files +β”œβ”€β”€ assets/ # Root-level assets +β”‚ β”œβ”€β”€ favicon.ico # Site favicon +β”‚ β”œβ”€β”€ robots.txt # Robots file +β”‚ β”œβ”€β”€ logo.svg # Logo +β”‚ └── [any file] # Served at root level +└── config.ini # Configuration overrides +``` + +## Content Directory + +Your content directory contains all your website pages and assets. + +### Basic Structure + +``` +content/ +β”œβ”€β”€ index.md # Home page +β”œβ”€β”€ about/ # About page +β”‚ β”œβ”€β”€ index.md # Page content +β”‚ β”œβ”€β”€ metadata.ini # Page metadata +β”‚ └── team-photo.jpg # Page asset +β”œβ”€β”€ blog/ # Blog (list view) +β”‚ β”œβ”€β”€ metadata.ini # Blog configuration +β”‚ β”œβ”€β”€ 2025-11-01-first-post/ +β”‚ β”‚ β”œβ”€β”€ index.md # Post content +β”‚ β”‚ β”œβ”€β”€ cover.jpg # Cover image +β”‚ β”‚ └── metadata.ini # Post metadata +β”‚ └── 2025-11-02-second-post/ +β”‚ β”œβ”€β”€ index.md +β”‚ β”œβ”€β”€ cover.webp +β”‚ └── metadata.ini +└── docs/ # Multi-file page + β”œβ”€β”€ 00-intro.md # Section 1 + β”œβ”€β”€ 01-setup.md # Section 2 + β”œβ”€β”€ 02-usage.md # Section 3 + └── metadata.ini # Page metadata +``` + +### Content Types + +#### Single-File Page + +``` +content/about/ +└── index.md +``` + +URL: `/about/` + +#### Multi-File Page + +``` +content/docs/ +β”œβ”€β”€ 00-intro.md +β”œβ”€β”€ 01-setup.md +└── 02-usage.md +``` + +URL: `/docs/` (all files render as one page) + +#### List View (Directory with Subdirectories) + +``` +content/blog/ +β”œβ”€β”€ metadata.ini +β”œβ”€β”€ 2025-11-01-post/ +β”‚ └── index.md +└── 2025-11-02-post/ + └── index.md +``` + +URL: `/blog/` (shows list of posts) + +## File Naming Conventions + +### Content Files + +Supported extensions: +- `.md` - Markdown (parsed with Parsedown) +- `.html` - HTML (included as-is) +- `.php` - PHP (executed with access to `$ctx`) + +### Language-Specific Files + +Format: `filename.{lang}.ext` + +Examples: +- `index.md` - Default language +- `index.no.md` - Norwegian +- `index.fr.md` - French +- `about.en.md` - English + +### Date Prefixes + +Format: `YYYY-MM-DD-slug` + +Examples: +- `2025-11-01-my-post` +- `2025-11-02-another-post` + +Dates are automatically extracted and formatted. + +### Cover Images + +Filename: `cover.{ext}` + +Supported formats: +- `cover.jpg` +- `cover.jpeg` +- `cover.png` +- `cover.webp` +- `cover.gif` + +Automatically detected in list views. + +### PDF Files + +Any `.pdf` file in a directory is automatically linked in grid layouts. + +### Metadata Files + +Filename: `metadata.ini` + +Format: INI with optional language sections. + +## File Discovery Order + +### Content File Priority + +For multi-file pages, files are rendered in alphanumerical order: + +``` +content/docs/ +β”œβ”€β”€ 00-intro.md # First +β”œβ”€β”€ 01-setup.md # Second +β”œβ”€β”€ 02-usage.md # Third +└── 99-appendix.md # Last +``` + +Use numerical prefixes to control order. + +### Template Resolution + +Templates are resolved with custom fallback: + +1. `/custom/templates/{name}.php` +2. `/app/default/templates/{name}.php` + +### CSS Resolution + +Stylesheets are resolved with custom fallback: + +1. `/custom/styles/base.css` +2. `/app/default/styles/base.css` + +### Translation Resolution + +Translations are resolved with custom fallback: + +1. `/custom/languages/{lang}.ini` +2. `/app/default/languages/{lang}.ini` + +### Configuration Resolution + +Configuration is merged: + +1. Load `/app/config.ini` +2. Merge with `/custom/config.ini` if exists + +Custom values override defaults. + +## URL Mapping + +### Basic Mapping + +``` +/content/about/index.md β†’ /about/ +/content/blog/ β†’ /blog/ +/content/docs/ β†’ /docs/ +``` + +### Language Prefixes + +Default language (no prefix): +``` +/content/about/index.md β†’ /about/ +``` + +Other languages (with prefix): +``` +/content/about/index.no.md β†’ /no/about/ +/content/about/index.fr.md β†’ /fr/about/ +``` + +### Translated Slugs + +With metadata slug overrides: +``` +content/about/metadata.ini: + [no] + slug = "om-oss" + + [fr] + slug = "a-propos" +``` + +URLs become: +- `/about/` (English) +- `/no/om-oss/` (Norwegian) +- `/fr/a-propos/` (French) + +### Trailing Slashes + +FolderWeb requires trailing slashes for directories. Missing slashes trigger 301 redirects: + +``` +/blog β†’ 301 redirect to β†’ /blog/ +``` + +## Special Files and Directories + +### System Files (Ignored) + +These files are automatically ignored: +- `.htaccess` +- `.git/` +- `.DS_Store` +- `node_modules/` +- Hidden files/directories (starting with `.`) + +### Index Files + +`index.md`, `index.html`, `index.php` are treated as directory content, not separate routes. + +### Metadata Files + +`metadata.ini` files are configuration, never rendered as content. + +## Asset Serving + +### Root-Level Assets + +Files in `/custom/assets/` are served at site root: + +``` +/custom/assets/robots.txt β†’ yoursite.com/robots.txt +/custom/assets/favicon.ico β†’ yoursite.com/favicon.ico +/custom/assets/logo.svg β†’ yoursite.com/logo.svg +``` + +### Content Assets + +Files in content directories are accessible at their directory URL: + +``` +/content/blog/2025-11-01-post/cover.jpg + β†’ yoursite.com/blog/2025-11-01-post/cover.jpg + +/content/about/team-photo.jpg + β†’ yoursite.com/about/team-photo.jpg +``` + +### CSS Files + +CSS is served with version hashing: + +``` +/custom/styles/base.css + β†’ yoursite.com/app/styles/base.css?v=abc123def456 +``` + +### Font Files + +Fonts in `/custom/fonts/` are accessible: + +``` +/custom/fonts/MyFont.woff2 + β†’ yoursite.com/custom/fonts/MyFont.woff2 +``` + +## File Permissions + +### Readable Files + +The web server must have read access to: +- All files in `/app/` +- All files in `/content/` +- All files in `/custom/` + +### Writable Files + +FolderWeb is read-only. No files require write access. + +### Security + +- Path validation prevents directory traversal +- Files must be within document root +- Realpath checks ensure proper resolution + +## Size Limits + +- **Read Tool**: Files larger than 50KB are truncated +- **No upload limits**: FolderWeb doesn't handle uploads +- **No execution limits**: Standard PHP limits apply + +## Caching + +### CSS Versioning + +CSS files are versioned with MD5 hash: + +```html + +``` + +Hash updates when file content changes. + +### No Built-in Cache + +FolderWeb doesn't implement content caching. Use: +- Web server caching (Apache, Nginx) +- Reverse proxy (Varnish, Cloudflare) +- PHP OPcache for code + +## Related + +- [Metadata Reference](metadata.md) +- [Configuration Reference](configuration.md) +- [How to Customize Templates](../how-to/custom-templates.md) +- [How to Customize Styles](../how-to/custom-styles.md) diff --git a/docs/reference/metadata.md b/docs/reference/metadata.md new file mode 100644 index 0000000..3bbb381 --- /dev/null +++ b/docs/reference/metadata.md @@ -0,0 +1,610 @@ +# Metadata Reference + +Complete reference for all metadata fields and their usage in `metadata.ini` files. + +## File Format + +Metadata files use INI format: + +```ini +; Comments start with semicolon +key = "value" +array[] = "value1" +array[] = "value2" + +[section] +key = "section value" +``` + +## Standard Fields + +### title + +**Type**: String +**Used in**: All content types +**Purpose**: Override automatic title extraction + +```ini +title = "Custom Page Title" +``` + +If not provided, FolderWeb extracts title from: +1. First H1 heading (`# Title` in Markdown) +2. Folder name (as fallback) + +**Multi-language**: +```ini +title = "English Title" + +[no] +title = "Norsk Tittel" + +[fr] +title = "Titre FranΓ§ais" +``` + +### date + +**Type**: Date string (YYYY-MM-DD) +**Used in**: Blog posts, articles +**Purpose**: Override automatic date extraction + +```ini +date = "2025-11-02" +``` + +If not provided, FolderWeb extracts date from folder names like `2025-11-02-post-title`. + +Dates are automatically formatted in Norwegian style: "2. november 2025" + +### summary + +**Type**: String +**Used in**: List views +**Purpose**: Brief description for cards and listings + +```ini +summary = "A concise description that appears in blog listings." +``` + +**Multi-language**: +```ini +summary = "English summary" + +[no] +summary = "Norsk sammendrag" +``` + +### menu + +**Type**: Boolean +**Used in**: Top-level directories +**Purpose**: Include in site navigation + +```ini +menu = true +``` + +Accepted values: `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off` + +### menu_order + +**Type**: Integer +**Used in**: Navigation items +**Purpose**: Control navigation order (lower numbers first) + +```ini +menu = true +menu_order = 1 +``` + +### page_template + +**Type**: String +**Used in**: Directories with subdirectories +**Purpose**: Choose list template + +```ini +page_template = "list-grid" +``` + +Available values: +- `list` - Simple list (default) +- `list-grid` - Grid with cover images +- `list-card-grid` - Card-style grid +- `list-faq` - Expandable FAQ format +- Any custom template name (without `.php` extension) + +### slug + +**Type**: String +**Used in**: Language sections +**Purpose**: Translate URL segments + +```ini +[no] +slug = "om-oss" + +[fr] +slug = "a-propos" +``` + +The actual folder is `about/`, but URLs become: +- `/about/` (English) +- `/no/om-oss/` (Norwegian) +- `/fr/a-propos/` (French) + +### redirect + +**Type**: URL string +**Used in**: List items (with `list-card-grid` template) +**Purpose**: Link to external site instead of internal page + +```ini +redirect = "https://example.com" +``` + +Creates an external link card in card grid layouts. + +## Custom Fields + +You can add any custom fields for use in your templates: + +### Common Custom Fields + +```ini +; Author information +author = "Jane Doe" +author_email = "jane@example.com" +author_url = "https://janedoe.com" + +; Content metadata +reading_time = "5 min" +difficulty = "intermediate" +featured = true + +; Categorization +tags[] = "PHP" +tags[] = "Tutorial" +tags[] = "Web Development" + +categories[] = "Programming" +categories[] = "Backend" + +; SEO +meta_description = "Complete guide to FolderWeb metadata" +meta_keywords = "metadata, ini, folderweb" + +; Social sharing +og_image = "/blog/post/social-card.jpg" +twitter_card = "summary_large_image" + +; Version tracking +version = "1.2.0" +last_updated = "2025-11-02" + +; Display options +hide_date = true +hide_author = false +show_toc = true + +; External references +github_url = "https://github.com/user/repo" +demo_url = "https://demo.example.com" +download_url = "/files/document.pdf" +``` + +## Array Fields + +Use `[]` syntax for array values: + +```ini +tags[] = "PHP" +tags[] = "Web Development" +tags[] = "Tutorial" + +authors[] = "Jane Doe" +authors[] = "John Smith" + +related_posts[] = "/blog/post-1/" +related_posts[] = "/blog/post-2/" +``` + +Access in templates: + +```php + + + +``` + +## Boolean Values + +Accepted boolean formats: + +```ini +; True values +featured = true +featured = 1 +featured = yes +featured = on + +; False values +draft = false +draft = 0 +draft = no +draft = off +``` + +## Language Sections + +Use `[lang]` sections for multi-language overrides: + +```ini +; Base values (default language) +title = "About Us" +summary = "Learn about our company" +slug = "about" + +; Norwegian overrides +[no] +title = "Om Oss" +summary = "LΓ¦r om vΓ₯rt selskap" +slug = "om-oss" + +; French overrides +[fr] +title = "Γ€ Propos" +summary = "DΓ©couvrez notre entreprise" +slug = "a-propos" + +; Fields not overridden inherit base values +``` + +Language-specific fields override base fields for that language. + +## Comments + +Use `;` or `#` for comments: + +```ini +; This is a comment +title = "My Page" + +# This is also a comment +date = "2025-11-02" + +; Comments can be on same line as values +menu = true ; Include in navigation +``` + +## Special Characters + +### Quotes + +Use quotes for values with special characters: + +```ini +; Optional for simple values +title = Simple Title +title = "Simple Title" + +; Required for values with spaces at start/end +title = " Padded Title " + +; Required for values with special characters +summary = "Use \"quotes\" for nested quotes" +summary = 'Single quotes work too' +``` + +### Escape Sequences + +Standard INI escape sequences: + +```ini +; Newline +text = "First line\nSecond line" + +; Tab +text = "Indented\ttext" + +; Quote +text = "He said \"Hello\"" +``` + +## Metadata Location + +### Directory Metadata + +Place `metadata.ini` in the directory it describes: + +``` +content/blog/metadata.ini # Blog configuration +content/about/metadata.ini # About page metadata +``` + +### Item Metadata + +Place `metadata.ini` in each subdirectory: + +``` +content/blog/2025-11-01-post/metadata.ini # Post metadata +content/blog/2025-11-02-post/metadata.ini # Post metadata +``` + +## Metadata Scope + +Metadata applies only to its directory. **No inheritance** from parent directories. + +## Complete Examples + +### Blog Configuration + +**content/blog/metadata.ini**: +```ini +; Display settings +title = "Blog" +page_template = "list-grid" + +; Navigation +menu = true +menu_order = 1 + +; Multi-language +[no] +title = "Blogg" +slug = "blogg" + +[fr] +title = "Blog" +slug = "blog" +``` + +### Blog Post + +**content/blog/2025-11-02-web-performance/metadata.ini**: +```ini +; Basic information +title = "Optimizing Web Performance" +date = "2025-11-02" +summary = "Learn techniques to make your website faster." + +; Author information +author = "Jane Developer" +author_url = "https://jane.dev" + +; Content metadata +reading_time = "8 min" +difficulty = "intermediate" +featured = true + +; Categorization +tags[] = "Performance" +tags[] = "Web Development" +tags[] = "Optimization" + +categories[] = "Technical" +categories[] = "Tutorial" + +; SEO +meta_description = "Complete guide to web performance optimization" + +; Multi-language versions +[no] +title = "Optimalisering av Nettsideytelse" +summary = "LΓ¦r teknikker for Γ₯ gjΓΈre nettsiden din raskere." + +[fr] +title = "Optimisation des Performances Web" +summary = "Apprenez Γ  accΓ©lΓ©rer votre site web." +``` + +### Documentation Page + +**content/docs/metadata.ini**: +```ini +title = "Documentation" +menu = true +menu_order = 2 +page_template = "list" + +; Custom fields +show_toc = true +github_url = "https://github.com/user/repo" + +[no] +title = "Dokumentasjon" +slug = "dokumentasjon" +``` + +### Portfolio Project + +**content/portfolio/project-name/metadata.ini**: +```ini +title = "Project Name" +date = "2025-11-02" +summary = "Brief project description" + +; External links +redirect = "https://project-demo.com" +github_url = "https://github.com/user/project" + +; Project details +client = "Company Name" +role = "Lead Developer" +technologies[] = "PHP" +technologies[] = "HTML" +technologies[] = "CSS" + +[no] +title = "Prosjektnavn" +summary = "Kort prosjektbeskrivelse" +``` + +### FAQ Section + +**content/faq/metadata.ini**: +```ini +title = "Frequently Asked Questions" +menu = true +menu_order = 4 +page_template = "list-faq" + +[no] +title = "Ofte Stilte SpΓΈrsmΓ₯l" +slug = "oss" + +[fr] +title = "Questions FrΓ©quemment PosΓ©es" +slug = "faq" +``` + +## Accessing Metadata in Templates + +### In Page Templates + +Variable: `$pageMetadata` + +```php + + +
+

+ + +

By

+ + + + + +
+ + + +
+ +
+``` + +### In List Templates + +Variable: `$metadata` (directory metadata), `$items` (item metadata) + +```php + +

+ + + +
+

+ + + +

+ + + + + + +

+ +
+ +``` + +## Validation + +### Check Syntax + +Test INI file parsing: + +```bash +php -r "print_r(parse_ini_file('content/blog/metadata.ini', true));" +``` + +### Common Errors + +**Unquoted special characters**: +```ini +; Wrong +title = Title with: special characters + +; Correct +title = "Title with: special characters" +``` + +**Missing array brackets**: +```ini +; Wrong (only last value kept) +tags = "PHP" +tags = "Tutorial" + +; Correct (array created) +tags[] = "PHP" +tags[] = "Tutorial" +``` + +**Invalid section names**: +```ini +; Wrong +[language.no] + +; Correct +[no] +``` + +## Best Practices + +### Always Escape Output + +```php + +``` + +### Provide Defaults + +```php +$author = $metadata['author'] ?? 'Anonymous'; +$tags = $metadata['tags'] ?? []; +``` + +### Check Before Using + +```php + +

By

+ +``` + +### Use Consistent Field Names + +Stick to standard names across your site: +- `author` not `writer` or `by` +- `tags` not `keywords` or `topics` +- `summary` not `description` or `excerpt` + +### Document Custom Fields + +Add comments explaining non-obvious fields: + +```ini +; Featured articles appear at top of homepage +featured = true + +; External demo link (overrides internal page) +demo_url = "https://demo.example.com" +``` + +## Related + +- [Multi-Language Guide](../how-to/multi-language.md) +- [Working with Metadata](../how-to/working-with-metadata.md) +- [Template Variables Reference](templates.md) +- [Configuration Reference](configuration.md) diff --git a/docs/reference/templates.md b/docs/reference/templates.md new file mode 100644 index 0000000..0fe7beb --- /dev/null +++ b/docs/reference/templates.md @@ -0,0 +1,608 @@ +# Template Reference + +Complete reference for all templates and available variables in FolderWeb. + +## Template System Overview + +FolderWeb uses a fallback template system: +1. Check `/custom/templates/{name}.php` +2. Fall back to `/app/default/templates/{name}.php` + +Templates are plain PHP files with access to specific variables and the context object. + +## Core Templates + +### base.php + +**Purpose**: HTML wrapper for all pages (header, navigation, footer) +**Used**: On every page render +**Customizable**: Yes + +**Available Variables**: + +| Variable | Type | Description | +|----------|------|-------------| +| `$content` | string | Rendered page content (HTML) | +| `$ctx` | Context | Full context object | +| `$currentLang` | string | Current language code (e.g., "en", "no") | +| `$navigation` | array | Navigation menu items | +| `$homeLabel` | string | Site title | +| `$translations` | array | UI translation strings | +| `$pageTitle` | string | Current page title | +| `$dirName` | string | Parent directory name | +| `$pageName` | string | Current page filename | + +**Example**: +```php + + + + + <?= htmlspecialchars($pageTitle) ?> | <?= htmlspecialchars($homeLabel) ?> + + + +
+ +
+ +
+ +
+ + + + +``` + +### page.php + +**Purpose**: Wrapper for single pages and articles +**Used**: For file and multi-file pages +**Customizable**: Yes + +**Available Variables**: + +| Variable | Type | Description | +|----------|------|-------------| +| `$content` | string | Rendered content (HTML) | +| `$pageMetadata` | array | Page metadata from metadata.ini | +| `$translations` | array | UI translation strings | + +**Example**: +```php +
+ + + + + +
+``` + +## List Templates + +### list.php + +**Purpose**: Simple list view (default) +**Used**: Directories with subdirectories +**Customizable**: Yes + +**Available Variables**: + +| Variable | Type | Description | +|----------|------|-------------| +| `$items` | array | Array of subdirectory items | +| `$metadata` | array | Directory metadata | +| `$pageContent` | string | Optional intro content (HTML) | +| `$translations` | array | UI translation strings | + +**Item Structure**: + +Each item in `$items` has: + +```php +[ + 'title' => 'Item Title', // From metadata or H1 + 'date' => '2. november 2025', // Formatted date + 'url' => '/blog/post-slug/', // Full URL with language prefix + 'cover' => '/path/to/cover.jpg', // Cover image path or null + 'summary' => 'Brief description', // From metadata or null + 'pdf' => '/path/to/file.pdf', // PDF file path or null + 'redirect' => 'https://...', // External URL or null +] +``` + +**Example**: +```php + +
+ +
+ + +
+ +
+

+ + + +

+ + + + +

+ +
+ +
+``` + +### list-grid.php + +**Purpose**: Grid layout with cover images +**Used**: Visual blog/portfolio listings +**Customizable**: Yes + +**Same variables as list.php** + +Features: +- Grid layout +- Cover images +- PDF download links +- "Read more" buttons + +**Example**: +```php +
+ +
+ + + + +

+ + + +

+ + + + + + +

+ + + +
+ +
+``` + +### list-card-grid.php + +**Purpose**: Card-style grid with external link support +**Used**: Portfolios, resource lists +**Customizable**: Yes + +**Same variables as list.php** + +Features: +- Card-style layout +- PDF download support +- External redirect support +- Cover images + +**Example**: +```php +
+ +
+ + + + +

+ + +

+ + + + + Visit Site + + + + Download PDF + + + + View Details + + +
+ +
+``` + +### list-faq.php + +**Purpose**: Expandable FAQ/Q&A format +**Used**: FAQ sections, documentation +**Customizable**: Yes + +**Same variables as list.php** + +Features: +- Collapsible `
` elements +- Semantic HTML +- Keyboard accessible + +**Example**: +```php + +
+ +
+ + +
+ +
+ + + +

+ + + + + +
+ +
+``` + +## Context Object + +All templates have access to `$ctx` (Context object): + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `$ctx->contentDir` | string | Path to content directory | +| `$ctx->currentLang` | string | Current language code | +| `$ctx->defaultLang` | string | Default language code | +| `$ctx->availableLangs` | array | Available language codes | +| `$ctx->langPrefix` | string | URL language prefix (e.g., "/en" or "") | +| `$ctx->requestPath` | string | Current request path | +| `$ctx->hasTrailingSlash` | bool | Whether path has trailing slash | +| `$ctx->navigation` | array | Navigation menu items (computed) | +| `$ctx->homeLabel` | string | Site title (computed) | +| `$ctx->translations` | array | UI translations (computed) | + +### Example Usage + +```php + +availableLangs as $lang): ?> + defaultLang + ? '/' . trim($ctx->requestPath, '/') + : '/' . $lang . '/' . trim($ctx->requestPath, '/'); + ?> + currentLang ? 'aria-current="true"' : '' ?>> + + + + + + +``` + +## Navigation Array + +Structure of `$navigation` items: + +```php +[ + [ + 'title' => 'Blog', + 'url' => '/blog/', + 'order' => 1 + ], + [ + 'title' => 'About', + 'url' => '/about/', + 'order' => 2 + ], + // ... +] +``` + +Already sorted by `menu_order` field. + +## Translation Array + +Structure of `$translations`: + +```php +[ + 'home' => 'Home', + 'read_more' => 'Read more', + 'categories' => 'Categories', + 'tags' => 'Tags', + 'footer_text' => 'Made with FolderWeb', + 'footer_handcoded' => 'Generated in', + 'footer_page_time' => 'ms', + // ... custom translations +] +``` + +## Helper Functions Available in Templates + +### resolveTemplate() + +Find custom or default template: + +```php +$templatePath = resolveTemplate('base', 'templates'); +$cssPath = resolveTemplate('base.css', 'styles'); +``` + +### htmlspecialchars() + +Escape output (always use for user content): + +```php + +``` + +### Other PHP Functions + +All standard PHP functions are available: +- `isset()`, `empty()` +- `count()`, `array_filter()` +- `date()`, `time()` +- String functions +- etc. + +## Creating Custom Templates + +### Step 1: Create Template File + +```bash +touch custom/templates/my-custom-list.php +``` + +### Step 2: Use Standard Variables + +Custom list templates receive `$items`, `$metadata`, `$pageContent`, `$translations`. + +### Step 3: Apply in Metadata + +**content/my-section/metadata.ini**: +```ini +page_template = "my-custom-list" +``` + +Note: Omit `.php` extension. + +## Template Best Practices + +### Always Escape Output + +```php + + + + + +``` + +### Check Variables Before Use + +```php + + + +``` + +### Use Null Coalescing + +```php +$author = $metadata['author'] ?? 'Anonymous'; +``` + +### Semantic HTML + +```php + +
+

+ +
+ + +
+ + +
+``` + +### Accessibility + +```php + +<?= htmlspecialchars($item['title']) ?> + + +