@kirigami/php-prepros 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2059 -2064
- package/package.json +59 -59
- package/src/libraries/html.class.php +56 -4
- package/src/libraries/ld.class.php +3 -2
- package/src/libraries/md.class.php +57 -57
- package/src/libraries/md.plugins.php +79 -102
- package/src/libraries/meta.class.php +3 -2
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@kirigami/php-prepros",
|
|
3
|
-
"version": "3.2.
|
|
4
|
-
"description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"kirigami",
|
|
7
|
-
"php",
|
|
8
|
-
"preprocessor",
|
|
9
|
-
"static-site-generator",
|
|
10
|
-
"ssg",
|
|
11
|
-
"wasm",
|
|
12
|
-
"php-wasm",
|
|
13
|
-
"markdown",
|
|
14
|
-
"yaml",
|
|
15
|
-
"github-pages",
|
|
16
|
-
"pages"
|
|
17
|
-
],
|
|
18
|
-
"license": "GPL-3.0-or-later",
|
|
19
|
-
"author": "Maxime Larrivée-Roy",
|
|
20
|
-
"type": "module",
|
|
21
|
-
"main": "index.js",
|
|
22
|
-
"types": "index.d.ts",
|
|
23
|
-
"files": [
|
|
24
|
-
"index.js",
|
|
25
|
-
"index.d.ts",
|
|
26
|
-
"src/",
|
|
27
|
-
"LICENSE",
|
|
28
|
-
"README.md"
|
|
29
|
-
],
|
|
30
|
-
"engines": {
|
|
31
|
-
"node": ">=24.0.0",
|
|
32
|
-
"npm": ">=10.2.3"
|
|
33
|
-
},
|
|
34
|
-
"exports": {
|
|
35
|
-
".": {
|
|
36
|
-
"import": "./index.js"
|
|
37
|
-
},
|
|
38
|
-
"./package.json": "./package.json"
|
|
39
|
-
},
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"test": "node --test --test-concurrency=1 \"test/*.test.js\""
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@kirigami/php-wasm": "8.5.11-1",
|
|
48
|
-
"@kirigami/struct-walker": "1.0.6",
|
|
49
|
-
"picomatch": "^4.0.7"
|
|
50
|
-
},
|
|
51
|
-
"repository": {
|
|
52
|
-
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/php-kirigami/kirigami.git"
|
|
54
|
-
},
|
|
55
|
-
"homepage": "https://php-kirigami.github.io",
|
|
56
|
-
"bugs": {
|
|
57
|
-
"url": "https://github.com/php-kirigami/kirigami/issues"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@kirigami/php-prepros",
|
|
3
|
+
"version": "3.2.2",
|
|
4
|
+
"description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kirigami",
|
|
7
|
+
"php",
|
|
8
|
+
"preprocessor",
|
|
9
|
+
"static-site-generator",
|
|
10
|
+
"ssg",
|
|
11
|
+
"wasm",
|
|
12
|
+
"php-wasm",
|
|
13
|
+
"markdown",
|
|
14
|
+
"yaml",
|
|
15
|
+
"github-pages",
|
|
16
|
+
"pages"
|
|
17
|
+
],
|
|
18
|
+
"license": "GPL-3.0-or-later",
|
|
19
|
+
"author": "Maxime Larrivée-Roy",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "index.js",
|
|
22
|
+
"types": "index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"index.d.ts",
|
|
26
|
+
"src/",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=24.0.0",
|
|
32
|
+
"npm": ">=10.2.3"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": "./index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "node --test --test-concurrency=1 \"test/*.test.js\""
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@kirigami/php-wasm": "8.5.11-1",
|
|
48
|
+
"@kirigami/struct-walker": "1.0.6",
|
|
49
|
+
"picomatch": "^4.0.7"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/php-kirigami/kirigami.git"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://php-kirigami.github.io",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/php-kirigami/kirigami/issues"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -25,6 +25,7 @@ class HTML
|
|
|
25
25
|
'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map',
|
|
26
26
|
'mark', 'object', 'output', 'q', 's', 'samp', 'select', 'small',
|
|
27
27
|
'span', 'strong', 'sub', 'sup', 'textarea', 'time', 'tt', 'u', 'var',
|
|
28
|
+
'bdi', 'data', 'del', 'font', 'ins', 'rp', 'rt', 'ruby', 'strike', 'wbr',
|
|
28
29
|
];
|
|
29
30
|
|
|
30
31
|
// The HTML namespace URI Lexbor assigns to plain HTML elements (foreign
|
|
@@ -188,20 +189,63 @@ class HTML
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
$inner = '';
|
|
191
|
-
foreach ($children as $
|
|
192
|
-
|
|
192
|
+
foreach (static::groupInlineRuns($children) as $group) {
|
|
193
|
+
if (is_array($group)) {
|
|
194
|
+
// Text and inline elements that follow each other: one line, so
|
|
195
|
+
// the spaces between them survive ("<b>bold</b>, next").
|
|
196
|
+
$inner .= str_repeat(' ', ($depth + 1) * self::INDENT) . trim(static::renderInlineNodes($group)) . "\n";
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
$inner .= static::renderNode($group, $depth + 1);
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
return "{$pad}<{$tag}{$attrs}>\n{$inner}{$pad}</{$tag}>\n";
|
|
196
203
|
}
|
|
197
204
|
|
|
205
|
+
// Splits a block's children into single nodes and runs (arrays) of
|
|
206
|
+
// consecutive text/inline nodes that contain real text. A run without text
|
|
207
|
+
// (a row of <a><img></a>, say) stays as separate nodes, one per line.
|
|
208
|
+
private static function groupInlineRuns(array $children): array
|
|
209
|
+
{
|
|
210
|
+
$groups = [];
|
|
211
|
+
$run = [];
|
|
212
|
+
$flush = static function () use (&$groups, &$run) {
|
|
213
|
+
$hasText = false;
|
|
214
|
+
foreach ($run as $node) {
|
|
215
|
+
if ($node->nodeType === XML_TEXT_NODE && trim($node->nodeValue) !== '') $hasText = true;
|
|
216
|
+
}
|
|
217
|
+
if ($hasText) $groups[] = $run;
|
|
218
|
+
else foreach ($run as $node) $groups[] = $node;
|
|
219
|
+
$run = [];
|
|
220
|
+
};
|
|
221
|
+
foreach ($children as $child) {
|
|
222
|
+
$isInline = $child->nodeType === XML_TEXT_NODE
|
|
223
|
+
|| ($child->nodeType === XML_ELEMENT_NODE
|
|
224
|
+
&& static::isInline($child)
|
|
225
|
+
&& static::hasOnlyInlineChildren($child));
|
|
226
|
+
if ($isInline) {
|
|
227
|
+
$run[] = $child;
|
|
228
|
+
} else {
|
|
229
|
+
$flush();
|
|
230
|
+
$groups[] = $child;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
$flush();
|
|
234
|
+
return $groups;
|
|
235
|
+
}
|
|
236
|
+
|
|
198
237
|
// Serializes a node's inline content on a single line, collapsing any run
|
|
199
238
|
// of spaces/tabs/newlines in the source text to a single space (equivalent
|
|
200
239
|
// to HTML's whitespace-collapsing behavior).
|
|
201
240
|
private static function renderInline(Dom\Node $node): string
|
|
241
|
+
{
|
|
242
|
+
return static::renderInlineNodes($node->childNodes);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private static function renderInlineNodes(iterable $nodes): string
|
|
202
246
|
{
|
|
203
247
|
$out = '';
|
|
204
|
-
foreach ($
|
|
248
|
+
foreach ($nodes as $child) {
|
|
205
249
|
if ($child->nodeType === XML_TEXT_NODE) {
|
|
206
250
|
$text = preg_replace('/\s+/', ' ', $child->nodeValue);
|
|
207
251
|
$out .= htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
@@ -244,6 +288,14 @@ class HTML
|
|
|
244
288
|
return $elementCount <= 1;
|
|
245
289
|
}
|
|
246
290
|
|
|
291
|
+
// A known inline element, or a custom element (its name always has a
|
|
292
|
+
// hyphen), which browsers lay out inline unless a stylesheet says otherwise.
|
|
293
|
+
private static function isInline(Dom\Node $node): bool
|
|
294
|
+
{
|
|
295
|
+
$name = strtolower($node->nodeName);
|
|
296
|
+
return in_array($name, self::INLINE, true) || str_contains($name, '-');
|
|
297
|
+
}
|
|
298
|
+
|
|
247
299
|
// Checks that every direct descendant is inline (text, inline void, inline elements)
|
|
248
300
|
// The inline elements themselves must not contain block elements
|
|
249
301
|
private static function hasOnlyInlineChildren(Dom\Node $node): bool
|
|
@@ -255,7 +307,7 @@ class HTML
|
|
|
255
307
|
if ($child->nodeType !== XML_ELEMENT_NODE) {
|
|
256
308
|
return false;
|
|
257
309
|
}
|
|
258
|
-
if (!
|
|
310
|
+
if (!static::isInline($child)) {
|
|
259
311
|
return false;
|
|
260
312
|
}
|
|
261
313
|
// Recursive: the inline element must not contain block elements
|
|
@@ -746,8 +746,9 @@ final class LD
|
|
|
746
746
|
if ($url === null || $url === '') return null;
|
|
747
747
|
if (preg_match('#^(https?:)?//#', $url) || str_starts_with($url, 'data:')) return $url;
|
|
748
748
|
|
|
749
|
-
|
|
750
|
-
|
|
749
|
+
// Against the whole baseurl, path included: a site on `user.github.io/repo` keeps `/repo`.
|
|
750
|
+
$base = rtrim((string) (self::data()->baseurl ?? ''), '/');
|
|
751
|
+
return $base . '/' . ltrim($url, '/');
|
|
751
752
|
}
|
|
752
753
|
|
|
753
754
|
/** @return array<int,string> */
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MD — Markdown renderer backed by the native `mdhtml` extension (CommonMark
|
|
5
|
-
* + GFM via cmark-gfm 0.29.0.gfm.13, statically built into @kirigami/php-wasm
|
|
6
|
-
* — see php-kirigami/php-mdhtml).
|
|
7
|
-
*
|
|
8
|
-
* Switched from the hand-written recursive-descent/regex renderer to this
|
|
9
|
-
* native extension — see docs/DECISIONS.md and docs/STATUS.md in the repo
|
|
10
|
-
* root for the full reasoning. Every MD::toHtml() extension (plugins, emoji,
|
|
11
|
-
* GFM alerts, definition lists, heading anchors, ==highlight==/^sup^/~sub~)
|
|
12
|
-
* is reimplemented in C, not just the CommonMark/GFM core — see php-mdhtml's
|
|
13
|
-
* README for exactly what's covered.
|
|
14
|
-
*
|
|
15
|
-
* Kept the old implementation as MD_LEGACY (md-legacy.class.php) — untouched,
|
|
16
|
-
* still autoloadable — as a rollback path.
|
|
17
|
-
*
|
|
18
|
-
* Usage:
|
|
19
|
-
* $html = MD::toHtml($markdown);
|
|
20
|
-
* MD::registerPlugin('name', function (array $args, string $body): string { ... });
|
|
21
|
-
* MD::registerEmoji('kirigami', '📐');
|
|
22
|
-
*/
|
|
23
|
-
class MD
|
|
24
|
-
{
|
|
25
|
-
public static function registerPlugin(string $name, callable $callback): void
|
|
26
|
-
{
|
|
27
|
-
\MDHtml\RegisterPlugin($name, $callback);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public static function unregisterPlugin(string $name): void
|
|
31
|
-
{
|
|
32
|
-
\MDHtml\UnregisterPlugin($name);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public static function getRegisteredPlugins(): array
|
|
36
|
-
{
|
|
37
|
-
return \MDHtml\GetRegisteredPlugins();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public static function registerEmoji(string $shortcode, string $char): void
|
|
41
|
-
{
|
|
42
|
-
\MDHtml\RegisterEmoji($shortcode, $char);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public static function toHtml(string $markdown): string
|
|
46
|
-
{
|
|
47
|
-
return \MDHtml\Render($markdown);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Default Markdown plugins ({% codepen %}, {%
|
|
53
|
-
// {% img-asset %}) — "registered out of the box" per the README. Loaded here
|
|
54
|
-
// so every entrypoint (prepros.php, runenv.php, imagebatch.php) gets them
|
|
55
|
-
// without an explicit include. A project can still MD::unregisterPlugin() any
|
|
56
|
-
// of them, or MD::registerPlugin() its own with the same name to override.
|
|
57
|
-
include_once(__DIR__ . '/md.plugins.php');
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MD — Markdown renderer backed by the native `mdhtml` extension (CommonMark
|
|
5
|
+
* + GFM via cmark-gfm 0.29.0.gfm.13, statically built into @kirigami/php-wasm
|
|
6
|
+
* — see php-kirigami/php-mdhtml).
|
|
7
|
+
*
|
|
8
|
+
* Switched from the hand-written recursive-descent/regex renderer to this
|
|
9
|
+
* native extension — see docs/DECISIONS.md and docs/STATUS.md in the repo
|
|
10
|
+
* root for the full reasoning. Every MD::toHtml() extension (plugins, emoji,
|
|
11
|
+
* GFM alerts, definition lists, heading anchors, ==highlight==/^sup^/~sub~)
|
|
12
|
+
* is reimplemented in C, not just the CommonMark/GFM core — see php-mdhtml's
|
|
13
|
+
* README for exactly what's covered.
|
|
14
|
+
*
|
|
15
|
+
* Kept the old implementation as MD_LEGACY (md-legacy.class.php) — untouched,
|
|
16
|
+
* still autoloadable — as a rollback path.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* $html = MD::toHtml($markdown);
|
|
20
|
+
* MD::registerPlugin('name', function (array $args, string $body): string { ... });
|
|
21
|
+
* MD::registerEmoji('kirigami', '📐');
|
|
22
|
+
*/
|
|
23
|
+
class MD
|
|
24
|
+
{
|
|
25
|
+
public static function registerPlugin(string $name, callable $callback): void
|
|
26
|
+
{
|
|
27
|
+
\MDHtml\RegisterPlugin($name, $callback);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static function unregisterPlugin(string $name): void
|
|
31
|
+
{
|
|
32
|
+
\MDHtml\UnregisterPlugin($name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static function getRegisteredPlugins(): array
|
|
36
|
+
{
|
|
37
|
+
return \MDHtml\GetRegisteredPlugins();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public static function registerEmoji(string $shortcode, string $char): void
|
|
41
|
+
{
|
|
42
|
+
\MDHtml\RegisterEmoji($shortcode, $char);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public static function toHtml(string $markdown): string
|
|
46
|
+
{
|
|
47
|
+
return \MDHtml\Render($markdown);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// Default Markdown plugins ({% codepen %}, {% callout %},
|
|
53
|
+
// {% img-asset %}) — "registered out of the box" per the README. Loaded here
|
|
54
|
+
// so every entrypoint (prepros.php, runenv.php, imagebatch.php) gets them
|
|
55
|
+
// without an explicit include. A project can still MD::unregisterPlugin() any
|
|
56
|
+
// of them, or MD::registerPlugin() its own with the same name to override.
|
|
57
|
+
include_once(__DIR__ . '/md.plugins.php');
|
|
@@ -1,103 +1,80 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// =============================================================================
|
|
6
|
-
// DEFAULT MARKDOWN PLUGINS
|
|
7
|
-
//
|
|
8
|
-
// Auto-loaded from md.class.php, so these are available in every render without
|
|
9
|
-
// any setup ("registered out of the box"). To drop one, call
|
|
10
|
-
// MD::unregisterPlugin('name') (or md_unregister_plugin('name')) from an
|
|
11
|
-
// `includes` file; to change one, MD::registerPlugin() your own under the same
|
|
12
|
-
// name.
|
|
13
|
-
// =============================================================================
|
|
14
|
-
|
|
15
|
-
// --- CodePen (inline) ---
|
|
16
|
-
// {% codepen PENid %}
|
|
17
|
-
// {% codepen PENid user height %}
|
|
18
|
-
MD::registerPlugin('codepen', function (array $args, string $body): string {
|
|
19
|
-
$id = htmlspecialchars($args[0] ?? '', ENT_QUOTES, 'UTF-8');
|
|
20
|
-
$user = htmlspecialchars($args[1] ?? 'anonymous', ENT_QUOTES, 'UTF-8');
|
|
21
|
-
$height = intval($args[2] ?? 400);
|
|
22
|
-
if ($id === '') return '<!-- codepen: missing id -->';
|
|
23
|
-
return "<iframe height=\"{$height}\" style=\"width:100%\" "
|
|
24
|
-
. "scrolling=\"no\" "
|
|
25
|
-
. "src=\"https://codepen.io/{$user}/embed/{$id}?default-tab=result\" "
|
|
26
|
-
. "frameborder=\"no\" loading=\"lazy\" allowfullscreen>"
|
|
27
|
-
. "</iframe>";
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// ---
|
|
31
|
-
// {%
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
$
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// {% img-asset photo.jpg %}
|
|
81
|
-
// {% img-asset photo.jpg 800 %}
|
|
82
|
-
// {% img-asset photo.jpg 800 600 %}
|
|
83
|
-
// {% img-asset photo.jpg 800 600 cover %}
|
|
84
|
-
MD::registerPlugin('img-asset', function (array $args, string $body): string {
|
|
85
|
-
$path = trim($args[0] ?? '');
|
|
86
|
-
if ($path === '') return '<!-- img-asset: missing path -->';
|
|
87
|
-
|
|
88
|
-
$width = isset($args[1]) ? (int) $args[1] : 0;
|
|
89
|
-
$height = isset($args[2]) ? (int) $args[2] : 0;
|
|
90
|
-
$cover = isset($args[3]) && strtolower($args[3]) === 'cover';
|
|
91
|
-
|
|
92
|
-
// A resolvable path is required to actually call IMG::asset() — this
|
|
93
|
-
// also protects a literal `{% img-asset path … %}` written as Markdown
|
|
94
|
-
// *documentation* (inside a code span): the plugin still runs even when
|
|
95
|
-
// its output ends up discarded for the literal text, so a placeholder
|
|
96
|
-
// path must not throw.
|
|
97
|
-
try {
|
|
98
|
-
$src = IMG::asset($path, $width, $height, $cover, PREPROS::$file);
|
|
99
|
-
} catch (Throwable $e) {
|
|
100
|
-
return '<!-- img-asset: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . ' -->';
|
|
101
|
-
}
|
|
102
|
-
return '<img src="' . htmlspecialchars($src, ENT_QUOTES, 'UTF-8') . '" alt="">';
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// DEFAULT MARKDOWN PLUGINS
|
|
7
|
+
//
|
|
8
|
+
// Auto-loaded from md.class.php, so these are available in every render without
|
|
9
|
+
// any setup ("registered out of the box"). To drop one, call
|
|
10
|
+
// MD::unregisterPlugin('name') (or md_unregister_plugin('name')) from an
|
|
11
|
+
// `includes` file; to change one, MD::registerPlugin() your own under the same
|
|
12
|
+
// name.
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
// --- CodePen (inline) ---
|
|
16
|
+
// {% codepen PENid %}
|
|
17
|
+
// {% codepen PENid user height %}
|
|
18
|
+
MD::registerPlugin('codepen', function (array $args, string $body): string {
|
|
19
|
+
$id = htmlspecialchars($args[0] ?? '', ENT_QUOTES, 'UTF-8');
|
|
20
|
+
$user = htmlspecialchars($args[1] ?? 'anonymous', ENT_QUOTES, 'UTF-8');
|
|
21
|
+
$height = intval($args[2] ?? 400);
|
|
22
|
+
if ($id === '') return '<!-- codepen: missing id -->';
|
|
23
|
+
return "<iframe height=\"{$height}\" style=\"width:100%\" "
|
|
24
|
+
. "scrolling=\"no\" "
|
|
25
|
+
. "src=\"https://codepen.io/{$user}/embed/{$id}?default-tab=result\" "
|
|
26
|
+
. "frameborder=\"no\" loading=\"lazy\" allowfullscreen>"
|
|
27
|
+
. "</iframe>";
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// --- Custom callout (inline or block) ---
|
|
31
|
+
// Inline: {% callout warning "Warning!" Content on one line %}
|
|
32
|
+
// Block : {% callout danger "Title"
|
|
33
|
+
// Paragraph 1
|
|
34
|
+
//
|
|
35
|
+
// Paragraph 2
|
|
36
|
+
// %}
|
|
37
|
+
MD::registerPlugin('callout', function (array $args, string $body): string {
|
|
38
|
+
$validTypes = ['info', 'success', 'warning', 'danger'];
|
|
39
|
+
$type = in_array($args[0] ?? '', $validTypes, true) ? $args[0] : 'info';
|
|
40
|
+
$title = !empty($args[1]) ? htmlspecialchars($args[1], ENT_QUOTES, 'UTF-8') : '';
|
|
41
|
+
|
|
42
|
+
// Content: $body for a block tag, otherwise the remaining args on the opening line
|
|
43
|
+
if ($body !== '') {
|
|
44
|
+
$content = htmlspecialchars($body, ENT_QUOTES, 'UTF-8');
|
|
45
|
+
// Replace double line breaks with <br><br> to preserve paragraphs
|
|
46
|
+
$content = str_replace("\n\n", '<br><br>', $content);
|
|
47
|
+
$content = str_replace("\n", '<br>', $content);
|
|
48
|
+
} else {
|
|
49
|
+
$content = htmlspecialchars(implode(' ', array_slice($args, $title ? 2 : 1)), ENT_QUOTES, 'UTF-8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
$titleHtml = $title ? "<strong>{$title}</strong><br>" : '';
|
|
53
|
+
return "<div class=\"callout callout-{$type}\">{$titleHtml}{$content}</div>";
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// --- Image asset (inline) — same pipeline as <img asset>, via IMG::asset() ---
|
|
57
|
+
// {% img-asset photo.jpg %}
|
|
58
|
+
// {% img-asset photo.jpg 800 %}
|
|
59
|
+
// {% img-asset photo.jpg 800 600 %}
|
|
60
|
+
// {% img-asset photo.jpg 800 600 cover %}
|
|
61
|
+
MD::registerPlugin('img-asset', function (array $args, string $body): string {
|
|
62
|
+
$path = trim($args[0] ?? '');
|
|
63
|
+
if ($path === '') return '<!-- img-asset: missing path -->';
|
|
64
|
+
|
|
65
|
+
$width = isset($args[1]) ? (int) $args[1] : 0;
|
|
66
|
+
$height = isset($args[2]) ? (int) $args[2] : 0;
|
|
67
|
+
$cover = isset($args[3]) && strtolower($args[3]) === 'cover';
|
|
68
|
+
|
|
69
|
+
// A resolvable path is required to actually call IMG::asset() — this
|
|
70
|
+
// also protects a literal `{% img-asset path … %}` written as Markdown
|
|
71
|
+
// *documentation* (inside a code span): the plugin still runs even when
|
|
72
|
+
// its output ends up discarded for the literal text, so a placeholder
|
|
73
|
+
// path must not throw.
|
|
74
|
+
try {
|
|
75
|
+
$src = IMG::asset($path, $width, $height, $cover, PREPROS::$file);
|
|
76
|
+
} catch (Throwable $e) {
|
|
77
|
+
return '<!-- img-asset: ' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . ' -->';
|
|
78
|
+
}
|
|
79
|
+
return '<img src="' . htmlspecialchars($src, ENT_QUOTES, 'UTF-8') . '" alt="">';
|
|
103
80
|
});
|
|
@@ -458,8 +458,9 @@ final class META
|
|
|
458
458
|
$url = trim($url);
|
|
459
459
|
if (preg_match('#^(https?:)?//#', $url) || str_starts_with($url, 'data:')) return $url;
|
|
460
460
|
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
// Against the whole baseurl, path included: a site on `user.github.io/repo` keeps `/repo`.
|
|
462
|
+
$base = rtrim((string) (self::data()->baseurl ?? ''), '/');
|
|
463
|
+
return $base === '' ? $url : $base . '/' . ltrim($url, '/');
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
/** `@user` / `user` / `https://twitter.com/user` → `@user`. */
|