Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This serves two purposes:
### Added
- Added a configuration option to disable the footer scroll-to-top button independently of the footer in https://github.com/hydephp/develop/pull/2459
- Added Blade Blocks for rendering Blade and Blade components from fenced code blocks in Markdown pages. They are controlled by the existing `markdown.enable_blade` option. ([#2504](https://github.com/hydephp/develop/pull/2504))
- Added built-in `terminal` fenced code blocks with command prompt styling, an optional window title using the `title="…"` modifier, and optional Symfony Console formatting using the `terminal xml` info string. ([#2188](https://github.com/hydephp/develop/issues/2188), [#2485](https://github.com/hydephp/develop/issues/2485))
- Added built-in `terminal` fenced code blocks with command prompt styling, an optional window title using the `title="…"` modifier, and optional XML style formatting using the `terminal xml` info string. ([#2188](https://github.com/hydephp/develop/issues/2188), [#2485](https://github.com/hydephp/develop/issues/2485))
- Added support for lazy `InMemoryPage` contents closures. The current page is passed as the first argument whenever the contents are requested.

### Changed
Expand Down
2 changes: 1 addition & 1 deletion HYDEPHP_V3_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Having this document in code lets us know the devlopment state at any given poin
- Redirects can now be declared as source and destination path pairs in the `hyde.redirects` configuration array. Hyde registers them with the kernel, includes them in `route:list`, and generates them through the normal site build.
- Blog posts can now be kept out of the published site through two zero-configuration publication states. Setting `draft: true` in front matter excludes a post indefinitely, until the property is removed or set to `false`, which suits content that is unfinished or awaiting approval. Setting a date in the future schedules a post, excluding it until that date has passed. Drafts and scheduled posts are skipped during auto-discovery when building the site: they get no route, are not present in the kernel's page and route collections, and are left out of post listings, the sitemap, and the RSS feed. The date rule supports both front matter dates and filename date prefixes, and an explicit draft outranks the date, so a draft stays excluded even after its date passes. Posts are published by default, making `draft: false` a no-op. Both states remain served by the realtime compiler, which is treated as an authoring preview, so posts can be written and proofread at their normal URL without editing their front matter: `serve` shows everything you are working on, while `build` publishes only what is eligible. Since Hyde is a static site generator, a scheduled post does not publish itself once its date passes: it is included in the first site build run after that point, so recurring builds (for example a cron-scheduled GitHub Actions workflow) are needed for a post to go live on its own. The new `MarkdownPost::isDraft()` and `MarkdownPost::isScheduled()` methods expose the checks. ([#2441](https://github.com/hydephp/develop/issues/2441), [#2572](https://github.com/hydephp/develop/pull/2572))
- Added Blade Blocks for rendering Blade and Blade components from fenced code blocks in Markdown pages. The supported directives are `blade render` and `blade component="name"`, and the feature is controlled by `markdown.enable_blade`. ([#2504](https://github.com/hydephp/develop/pull/2504))
- Added built-in terminal code blocks using the `terminal` fence language. Command prompts are styled for selection-free copying, and `terminal xml` supports four Symfony-style Console formatter tags. The window's title bar can be titled per block with `terminal title="Installing Hyde"`, which the terminal view receives as a `$title` variable, falling back to the `Terminal` label when a block sets no title. The modifiers are order-independent, so they can be combined as either `terminal xml title="Build output"` or `terminal title="Build output" xml`. ([#2188](https://github.com/hydephp/develop/issues/2188), [#2485](https://github.com/hydephp/develop/issues/2485))
- Added built-in terminal code blocks using the `terminal` fence language. Command prompts are styled for selection-free copying, and `terminal xml` supports four XML style formatter tags. The window's title bar can be titled per block with `terminal title="Installing Hyde"`, which the terminal view receives as a `$title` variable, falling back to the `Terminal` label when a block sets no title. The modifiers are order-independent, so they can be combined as either `terminal xml title="Build output"` or `terminal title="Build output" xml`. ([#2188](https://github.com/hydephp/develop/issues/2188), [#2485](https://github.com/hydephp/develop/issues/2485))

### Feature Changes

Expand Down
5 changes: 2 additions & 3 deletions docs/digging-deeper/advanced-markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@ quote. The title is HTML-escaped when rendered.
The `title` modifier must use a quoted value with no whitespace around the `=`, such as `title="Build output"`.
Unquoted values, unclosed quotes, and whitespace around the `=` are reported as errors instead of being guessed at.

### Symfony Console formatting
### XML style formatting

Add the `xml` modifier to style four commonly used Symfony Console formatter tags using colors from Hyde's terminal
theme:
Add the `xml` modifier to style four formatter tags using colors from Hyde's terminal theme:

````markdown
```terminal xml
Expand Down
18 changes: 9 additions & 9 deletions docs/digging-deeper/composable-markdown-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,10 @@ An empty title (`title=""`) is respected as written, leaving the title bar with
modifier must otherwise use a quoted value with no whitespace around the `=`. Malformed title syntax causes the build
to fail rather than being silently ignored.

#### Symfony formatting
#### XML style formatting

The `xml` modifier renders the four [Symfony Console formatter tags](https://symfony.com/doc/current/console/coloring.html)
(`<info>`, `<comment>`, `<question>`, and `<error>`) as coloured output, letting you paste console output verbatim.
The `xml` modifier renders four formatter tags (`<info>`, `<comment>`, `<question>`, and `<error>`) as coloured
output, letting you paste console-style output verbatim.

````markdown
```terminal xml title="Build output"
Expand All @@ -273,8 +273,8 @@ usual, including unknown tags and tags that are not closed in the order they wer
| `$title` | `string`/`null` | The title set by the block, or `null` when it did not set one. |

The renderer does the per-line work before the view is involved: it escapes the raw text, wraps `$ ` prompts in
`hyde-terminal-command`/`hyde-terminal-prompt` spans, and — when the `xml` modifier is present — converts Symfony
Console formatter tags into coloured spans. The view receives a single finished string.
`hyde-terminal-command`/`hyde-terminal-prompt` spans, and — when the `xml` modifier is present — converts the
formatter tags into coloured spans. The view receives a single finished string.

The title is passed through as it was written, so the view is what decides both how it is displayed and what an
untitled block falls back to. The shipped view escapes it with `{{ }}` and falls back to `Terminal`.
Expand All @@ -291,10 +291,10 @@ untitled block falls back to. The shipped view escapes it with `{{ }}` and falls
| `hyde-terminal-body` | The `<pre>` output area |
| `hyde-terminal-command` | A line beginning with a `$ ` prompt |
| `hyde-terminal-prompt` | The `$ ` prompt itself |
| `hyde-terminal-info` | Symfony `<info>` output |
| `hyde-terminal-comment` | Symfony `<comment>` output |
| `hyde-terminal-question` | Symfony `<question>` output |
| `hyde-terminal-error` | Symfony `<error>` output |
| `hyde-terminal-info` | `<info>` output |
| `hyde-terminal-comment` | `<comment>` output |
| `hyde-terminal-question` | `<question>` output |
| `hyde-terminal-error` | `<error>` output |

### Customization example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,25 @@ public function __invoke(DocumentParsedEvent $event): void

protected function makeViewModel(FencedCode $node): TerminalBlockViewModel
{
[$usesSymfonyFormatting, $title] = $this->parseModifiers($node->getInfo() ?? '');
[$usesFormatting, $title] = $this->parseModifiers($node->getInfo() ?? '');

return new TerminalBlockViewModel($node->getLiteral(), $title, $usesSymfonyFormatting);
return new TerminalBlockViewModel($node->getLiteral(), $title, $usesFormatting);
}

/**
* Parse the modifiers following the language, which are order-independent.
*
* @return array{0: bool, 1: string|null} Whether Symfony formatting is used, and the window title.
* @return array{0: bool, 1: string|null} Whether formatting is used, and the window title.
*/
protected function parseModifiers(string $info): array
{
$tokens = $this->tokenizeModifiers($info);

return [$this->usesSymfonyFormatting($tokens), $this->parseTitleModifier($tokens, 'terminal block')];
return [$this->usesFormatting($tokens), $this->parseTitleModifier($tokens, 'terminal block')];
}

/** @param array<int, array{key: ?string, double: ?string, single: ?string, word: ?string}> $tokens */
protected function usesSymfonyFormatting(array $tokens): bool
protected function usesFormatting(array $tokens): bool
{
foreach ($tokens as $token) {
if ($token['word'] !== null && strtolower($token['word']) === 'xml') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class TerminalBlockViewModel
public function __construct(
public readonly string $literal,
public readonly ?string $title = null,
public readonly bool $usesSymfonyFormatting = false,
public readonly bool $usesFormatting = false,
) {
$this->contents = $this->formatContents();
}
Expand Down Expand Up @@ -67,7 +67,7 @@ protected function formatLine(string $line): string

protected function formatText(string $text): string
{
if (! $this->usesSymfonyFormatting) {
if (! $this->usesFormatting) {
return e($text);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ public function testTheRendererWrapsPromptLinesInCommandAndPromptSpans()
);
}

public function testTheXmlModifierConvertsSymfonyConsoleFormatterTagsIntoColouredSpans()
public function testTheXmlModifierConvertsFormatterTagsIntoColouredSpans()
{
$html = Markdown::render("```terminal xml\n<info>Info</info> <comment>Comment</comment> <question>Question</question> <error>Error</error>\n```");

Expand Down Expand Up @@ -558,7 +558,7 @@ public function testModifiersAreOrderIndependent()
$html = Markdown::render("```terminal $modifiers\n<info>Hyde was installed successfully.</info>\n```");

$this->assertStringContainsString('<span>Build output</span>', $html, "The modifiers [$modifiers] did not set the title.");
$this->assertStringContainsString('<span class="hyde-terminal-info', $html, "The modifiers [$modifiers] did not enable Symfony formatting.");
$this->assertStringContainsString('<span class="hyde-terminal-info', $html, "The modifiers [$modifiers] did not enable XML style formatting.");
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/framework/tests/Feature/TerminalCodeBlocksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function testCommandPromptIsStyledAndExcludedFromSelection(): void
$this->assertStringContainsString("\n\$VARIABLE\n", $html);
}

public function testXmlModifierRendersSymfonyFormatterTags(): void
public function testXmlModifierRendersFormatterTags(): void
{
$html = Markdown::render(
"```terminal xml\n<info>Ready</info> <comment>Wait</comment> <question>Continue?</question> <error>Failed</error>\n```"
Expand Down Expand Up @@ -286,7 +286,7 @@ public function testParsedBlocksCarryTheViewModelTheyWereParsedInto(): void

$this->assertSame('$ php hyde build', $node->viewModel->literal);
$this->assertSame('Build output', $node->viewModel->title);
$this->assertTrue($node->viewModel->usesSymfonyFormatting);
$this->assertTrue($node->viewModel->usesFormatting);
}

public function testViewModelRendersTheTerminalView(): void
Expand All @@ -307,7 +307,7 @@ public function testViewModelGivesTheViewTheSameDataAsBefore(): void

public function testViewModelContentsAreFinishedMarkup(): void
{
$viewModel = new TerminalBlockViewModel('<info>Ready</info> <b>Bold</b>', usesSymfonyFormatting: true);
$viewModel = new TerminalBlockViewModel('<info>Ready</info> <b>Bold</b>', usesFormatting: true);

$this->assertSame(
'<span class="hyde-terminal-info text-[#C3E88D]">Ready</span> &lt;b&gt;Bold&lt;/b&gt;',
Expand Down
22 changes: 11 additions & 11 deletions packages/framework/tests/Unit/TerminalBlockViewModelUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testCanConstructWithOnlyLiteral()

$this->assertSame('Output', $model->literal);
$this->assertNull($model->title);
$this->assertFalse($model->usesSymfonyFormatting);
$this->assertFalse($model->usesFormatting);
}

public function testCanConstructWithAllArguments()
Expand All @@ -38,7 +38,7 @@ public function testCanConstructWithAllArguments()

$this->assertSame('Output', $model->literal);
$this->assertSame('Console', $model->title);
$this->assertTrue($model->usesSymfonyFormatting);
$this->assertTrue($model->usesFormatting);
}

public function testContentsAreFormattedOnConstruction()
Expand Down Expand Up @@ -97,13 +97,13 @@ public function testOnlyCommandLinesAreWrapped()
);
}

public function testFormatterTagsAreEscapedWithoutSymfonyFormatting()
public function testFormatterTagsAreEscapedWithoutFormatting()
{
$this->assertSame('&lt;info&gt;Ready&lt;/info&gt;', (new TerminalBlockViewModel('<info>Ready</info>'))->contents);
}

#[\PHPUnit\Framework\Attributes\DataProvider('formatterTagProvider')]
public function testSymfonyFormatterTagsAreConvertedToSpans(string $literal, string $expected)
public function testFormatterTagsAreConvertedToSpans(string $literal, string $expected)
{
$this->assertSame($expected, (new TerminalBlockViewModel($literal, null, true))->contents);
}
Expand All @@ -118,49 +118,49 @@ public static function formatterTagProvider(): array
];
}

public function testSymfonyFormatterTagsCanBeNested()
public function testFormatterTagsCanBeNested()
{
$this->assertSame(
'<span class="hyde-terminal-info text-[#C3E88D]">Ready <span class="hyde-terminal-comment text-[#FFCB6B]">soon</span></span>',
(new TerminalBlockViewModel('<info>Ready <comment>soon</comment></info>', null, true))->contents
);
}

public function testUnclosedSymfonyFormatterTagsAreClosedAtTheEndOfTheLine()
public function testUnclosedFormatterTagsAreClosedAtTheEndOfTheLine()
{
$this->assertSame(
'<span class="hyde-terminal-info text-[#C3E88D]">Ready <span class="hyde-terminal-comment text-[#FFCB6B]">soon</span></span>',
(new TerminalBlockViewModel('<info>Ready <comment>soon', null, true))->contents
);
}

public function testMismatchedSymfonyFormatterTagsAreEscaped()
public function testMismatchedFormatterTagsAreEscaped()
{
$this->assertSame(
'<span class="hyde-terminal-info text-[#C3E88D]">Ready&lt;/comment&gt;</span>',
(new TerminalBlockViewModel('<info>Ready</comment>', null, true))->contents
);
}

public function testUnopenedSymfonyFormatterTagsAreEscaped()
public function testUnopenedFormatterTagsAreEscaped()
{
$this->assertSame('Ready&lt;/info&gt;', (new TerminalBlockViewModel('Ready</info>', null, true))->contents);
}

public function testUnknownTagsAreEscapedWithSymfonyFormatting()
public function testUnknownTagsAreEscapedWithFormatting()
{
$this->assertSame('&lt;unknown&gt;text&lt;/unknown&gt;', (new TerminalBlockViewModel('<unknown>text</unknown>', null, true))->contents);
}

public function testSymfonyFormattingIsAppliedWithinCommandLines()
public function testFormattingIsAppliedWithinCommandLines()
{
$this->assertSame(
'<span class="hyde-terminal-command text-[#C3E88D]"><span class="hyde-terminal-prompt select-none" aria-hidden="true">$ </span>php hyde build <span class="hyde-terminal-comment text-[#FFCB6B]">--force</span></span>',
(new TerminalBlockViewModel('$ php hyde build <comment>--force</comment>', null, true))->contents
);
}

public function testSymfonyFormatterTagsDoNotSpanMultipleLines()
public function testFormatterTagsDoNotSpanMultipleLines()
{
$this->assertSame(
'<span class="hyde-terminal-info text-[#C3E88D]">Ready</span>'."\n".'Done&lt;/info&gt;',
Expand Down
Loading