Compare commits

..

No commits in common. "f1447049e47d170beb07b92002ac8efd80e3a369" and "069ce389ea8f2e1ff353faa18378a7db8c6c2483" have entirely different histories.

10 changed files with 8 additions and 107 deletions

View file

@ -54,8 +54,5 @@
<?php endif; ?>
</p>
</footer>
<?php if (!empty($pageJsUrl)): ?>
<script defer src="<?= htmlspecialchars($pageJsUrl) ?>?v=<?= htmlspecialchars($pageJsHash ?? '') ?>"></script>
<?php endif; ?>
</body>
</html>

View file

@ -132,22 +132,6 @@ function findPageCss(string $dirPath, string $contentDir): ?array {
];
}
function findPageJs(string $dirPath, string $contentDir): ?array {
$jsFile = "$dirPath/script.js";
if (!file_exists($jsFile) || !is_file($jsFile)) {
return null;
}
$relativePath = str_replace($contentDir, '', $dirPath);
$relativePath = trim($relativePath, '/');
$jsUrl = '/' . ($relativePath ? $relativePath . '/' : '') . 'script.js';
return [
'url' => $jsUrl,
'hash' => hash_file('md5', $jsFile)
];
}
function extractMetaDescription(string $dirPath, ?array $metadata): ?string {
// 1. Check for search_description in metadata
if ($metadata && isset($metadata['search_description'])) {

View file

@ -57,8 +57,6 @@ function renderTemplate(Context $ctx, string $content, int $statusCode = 200): v
'metaDescription' => $ctx->get('metaDescription'),
'pageCssUrl' => $ctx->get('pageCssUrl'),
'pageCssHash' => $ctx->get('pageCssHash'),
'pageJsUrl' => $ctx->get('pageJsUrl'),
'pageJsHash' => $ctx->get('pageJsHash'),
'feedUrl' => $ctx->get('feedUrl')
], $ctx);
@ -90,10 +88,6 @@ function renderMultipleFiles(Context $ctx, array $files, string $pageDir): void
$pageCssUrl = $pageCss['url'] ?? null;
$pageCssHash = $pageCss['hash'] ?? null;
$pageJs = findPageJs($pageDir, $ctx->contentDir);
$pageJsUrl = $pageJs['url'] ?? null;
$pageJsHash = $pageJs['hash'] ?? null;
$coverImage = findCoverImage($pageDir);
$socialImageUrl = null;
if ($coverImage) {
@ -110,8 +104,6 @@ function renderMultipleFiles(Context $ctx, array $files, string $pageDir): void
'metaDescription' => $metaDescription,
'pageCssUrl' => $pageCssUrl,
'pageCssHash' => $pageCssHash,
'pageJsUrl' => $pageJsUrl,
'pageJsHash' => $pageJsHash,
'socialImageUrl' => $socialImageUrl
], $ctx);

View file

@ -42,7 +42,6 @@ if (file_exists($contentAssetPath) && is_file($contentAssetPath)) {
'woff2' => 'font/woff2',
'ttf' => 'font/ttf',
'otf' => 'font/otf',
'js' => 'application/javascript',
];
$extLower = strtolower($ext);
@ -206,15 +205,11 @@ switch ($parsedPath['type']) {
$pageTitle = $metadata['title'] ?? null;
$metaDescription = extractMetaDescription($dir, $metadata);
// Check for page-specific CSS and JS
// Check for page-specific CSS
$pageCss = findPageCss($dir, $ctx->contentDir);
$pageCssUrl = $pageCss['url'] ?? null;
$pageCssHash = $pageCss['hash'] ?? null;
$pageJs = findPageJs($dir, $ctx->contentDir);
$pageJsUrl = $pageJs['url'] ?? null;
$pageJsHash = $pageJs['hash'] ?? null;
// Build feed URL if feed is enabled
$langPrefix = $ctx->get('langPrefix', '');
$feedUrl = (isset($metadata['feed']) && $metadata['feed'])
@ -226,8 +221,6 @@ switch ($parsedPath['type']) {
$ctx->set('metaDescription', $metaDescription);
$ctx->set('pageCssUrl', $pageCssUrl);
$ctx->set('pageCssHash', $pageCssHash);
$ctx->set('pageJsUrl', $pageJsUrl);
$ctx->set('pageJsHash', $pageJsHash);
$ctx->set('feedUrl', $feedUrl);
// Let plugins add template variables
@ -238,8 +231,6 @@ switch ($parsedPath['type']) {
'metaDescription' => $metaDescription,
'pageCssUrl' => $pageCssUrl,
'pageCssHash' => $pageCssHash,
'pageJsUrl' => $pageJsUrl,
'pageJsHash' => $pageJsHash,
'items' => $items,
'pageContent' => $pageContent,
'feedUrl' => $feedUrl

View file

@ -241,33 +241,6 @@ FolderWeb automatically loads and includes page-specific styles with cache-busti
<link rel="stylesheet" href="/portfolio/styles.css?v=abc123def">
```
## Page-Specific Scripts
For small progressive enhancements, you can add a `script.js` file to a content directory:
```
content/portfolio/
├── index.md
├── styles.css
└── script.js
```
**script.js:**
```js
// Small enhancement for this page only
document.querySelector('.portfolio-grid')?.addEventListener('click', (e) => {
// ...
});
```
FolderWeb automatically loads the script with `defer` (non-blocking) and cache-busting:
```html
<script defer src="/portfolio/script.js?v=abc123def"></script>
```
The script tag is placed before `</body>`, so it runs after the page has been parsed. This is ideal for progressive enhancement — the page works without JavaScript, but gets enhanced when it's available.
## Dark Mode
Add dark mode with CSS variables and `prefers-color-scheme`:

View file

@ -128,10 +128,6 @@ Let's modify `base.php` to add your site name and custom navigation:
<small>Generated in <?= number_format($pageLoadTime, 4) ?>s</small>
</p>
</footer>
<?php if (!empty($pageJsUrl)): ?>
<script defer src="<?= htmlspecialchars($pageJsUrl) ?>?v=<?= $pageJsHash ?? '' ?>"></script>
<?php endif; ?>
</body>
</html>
```
@ -305,7 +301,6 @@ $languageUrls // Links to other language versions
$translations // Translated UI strings
$cssHash // Cache-busting hash for CSS
$pageCssUrl // Page-specific CSS URL (if exists)
$pageJsUrl // Page-specific JS URL (if exists)
$pageLoadTime // Page generation time
```

View file

@ -211,27 +211,6 @@ MD5 hash of page-specific CSS for cache busting.
**Optional:** Only set if `$pageCssUrl` exists
**Example:** See `$pageCssUrl` above
### `$pageJsUrl`
URL to page-specific JavaScript file.
**Type:** String (URL)
**Optional:** Only set if `script.js` exists in content directory
**Example:**
```php
<?php if (!empty($pageJsUrl)): ?>
<script defer src="<?= htmlspecialchars($pageJsUrl) ?>?v=<?= htmlspecialchars($pageJsHash ?? '') ?>"></script>
<?php endif; ?>
```
### `$pageJsHash`
MD5 hash of page-specific JavaScript for cache busting.
**Type:** String (MD5 hash)
**Optional:** Only set if `$pageJsUrl` exists
**Example:** See `$pageJsUrl` above
### `$feedUrl`
URL to the Atom feed for the current list page.
@ -436,8 +415,6 @@ Then use in templates:
| `$translations` | ✓ | — | — |
| `$pageCssUrl` | ✓ | — | — |
| `$pageCssHash` | ✓ | — | — |
| `$pageJsUrl` | ✓ | — | — |
| `$pageJsHash` | ✓ | — | — |
| `$feedUrl` | ✓ | — | — |
| `$metadata` | — | ✓ | ✓ |
| `$pageContent` | — | — | ✓ |

View file

@ -126,12 +126,6 @@ Checks extensions in `COVER_IMAGE_EXTENSIONS` order: `jpg`, `jpeg`, `png`, `webp
Returns `['url' => string, 'hash' => string]` or null. Hash is MD5 of file content for cache busting.
## Page-Specific JavaScript
**`findPageJs(string $dirPath, string $contentDir): ?array`** — Checks for `script.js` in content directory.
Returns `['url' => string, 'hash' => string]` or null. Hash is MD5 of file content for cache busting. The script is loaded with the `defer` attribute in `base.php`, placed before `</body>` for non-blocking progressive enhancement.
## Meta Description Extraction
**`extractMetaDescription(string $dirPath, ?array $metadata): ?string`**

View file

@ -59,8 +59,6 @@ Variables are injected via `extract()` — each array key becomes a local variab
| `$homeLabel` | string | yes | Home link text |
| `$pageCssUrl` | ?string | no | Page-specific CSS URL |
| `$pageCssHash` | ?string | no | CSS cache-bust hash |
| `$pageJsUrl` | ?string | no | Page-specific JS URL |
| `$pageJsHash` | ?string | no | JS cache-bust hash |
| `$feedUrl` | ?string | no | Atom feed URL (only on lists with `feed = true`) |
| `$currentLang` | string | plugin | Language code (from languages plugin) |
| `$langPrefix` | string | plugin | URL language prefix |

View file

@ -23,7 +23,7 @@ Used for both frontpage and page views:
1. Load metadata for `$pageDir`
2. Load page plugins (from metadata `plugins` field)
3. Render all content files, concatenate HTML
4. Compute: `$pageTitle`, `$metaDescription`, `$pageCssUrl`/`$pageCssHash`, `$pageJsUrl`/`$pageJsHash`, `$socialImageUrl`
4. Compute: `$pageTitle`, `$metaDescription`, `$pageCssUrl`/`$pageCssHash`, `$socialImageUrl`
5. Fire `Hook::TEMPLATE_VARS` with all variables
6. `extract()` variables → render `page.php` → capture output as `$content`
7. Render `base.php` with `$content` + base variables
@ -37,12 +37,12 @@ Handled directly in `router.php` (not a separate function):
2. Load metadata, check `hide_list`
3. Select list template from `page_template` metadata
4. Call `buildListItems()` from `helpers.php` (builds + sorts items)
5. Store `pageTitle`, `metaDescription`, `pageCssUrl`, `pageCssHash`, `pageJsUrl`, `pageJsHash`, `feedUrl` on context
5. Store `pageTitle`, `metaDescription`, `pageCssUrl`, `pageCssHash`, `feedUrl` on context
6. Fire `Hook::TEMPLATE_VARS`
7. Render list template → capture as `$content`
8. Pass to `renderTemplate()` which renders `base.php`
**`renderTemplate(Context $ctx, string $content, int $statusCode = 200): void`** — Wraps content in base template. Used for list views and error pages. Reads `pageTitle`, `metaDescription`, `pageCssUrl`, `pageCssHash`, `pageJsUrl`, `pageJsHash`, and `feedUrl` from the context object (set by the list case in `router.php`). For error pages, these context keys are unset, so base.php receives nulls.
**`renderTemplate(Context $ctx, string $content, int $statusCode = 200): void`** — Wraps content in base template. Used for list views and error pages. Reads `pageTitle`, `metaDescription`, `pageCssUrl`, `pageCssHash`, and `feedUrl` from the context object (set by the list case in `router.php`). For error pages, these context keys are unset, so base.php receives nulls.
## Atom Feed Rendering
@ -79,9 +79,9 @@ setCachedMarkdown(string $filePath, string $html, string $langPrefix = ''): void
Before content routing, the router serves static files from content directory with an explicit MIME type allowlist:
`css`, `jpg`, `jpeg`, `png`, `gif`, `webp`, `svg`, `pdf`, `woff`, `woff2`, `ttf`, `otf`, `js`
`css`, `jpg`, `jpeg`, `png`, `gif`, `webp`, `svg`, `pdf`, `woff`, `woff2`, `ttf`, `otf`
Files not in this list are not served as static assets.
Files not in this list are not served as static assets. Notably, `.js` files are excluded — JavaScript must be placed in `custom/assets/` to be served (at the document root URL), or linked from an external source.
### Custom Assets (router.php)
@ -100,9 +100,9 @@ Files in `custom/assets/` are served at the document root URL. Example: `custom/
MIME types resolved from extension map, falling back to `mime_content_type()`.
## CSS and JS Cache Busting
## CSS Cache Busting
Page-specific CSS and JS get an MD5 hash appended: `?v={hash}`. Computed by `findPageCss()` and `findPageJs()` respectively. The default theme's CSS is linked directly without hash (uses browser caching).
Page-specific CSS gets an MD5 hash appended: `?v={hash}`. Computed by `findPageCss()`. The default theme's CSS is linked directly without hash (uses browser caching).
## Parsedown