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
3 changes: 2 additions & 1 deletion HYDEPHP_V3_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Having this document in code lets us know the devlopment state at any given poin
### Feature Changes

- Fenced code blocks are now rendered through a publishable Blade view, `components/markdown/code-block.blade.php`, in the same way terminal blocks are. The view receives the rendered code block markup as `$contents`, along with `$language` and `$label`, and decides what goes around it, so changing what surrounds a code block is a view change instead of a framework change. Highlighting itself is unaffected: the fence stays in the syntax tree as the wrapper's child, and is rendered by whichever renderer the environment already had for it, be it Torchlight, a third-party extension, or CommonMark's own.
- Code block labels are now set with a `title="…"` modifier on the fence, using the same attribute syntax as terminal block titles, such as ` ```php title="app/Model.php" `. The language is optional, so ` ``` title=".env" ` labels a block that declares none, which is then treated as `plaintext`. The label is no longer tied to file paths, so a block can be titled with anything. The v2 `// filepath:` comment syntax is removed.
- Code block labels are now set with a `title="…"` modifier on the fence, using the same attribute syntax as terminal block titles, such as ` ```php title="app/Model.php" `. The language is optional, so ` ``` title=".env" ` labels a block that declares none, which is then treated as `plaintext`. The label is no longer tied to file paths, so a block can be titled with anything. Labels use a responsive header by default instead of the v2 top-right badge; set `markdown.code_block_label_style` to `badge` to retain the previous presentation. The v2 `// filepath:` comment syntax is removed.
- Blade in Markdown is now enabled by default. The `markdown.enable_blade` option controls both `[Blade]:` directives and executable Blade Blocks. Hyde sites generally treat project content as trusted and reviewed; sites that compile untrusted or unreviewed Markdown can disable both forms with this option.
- Raw HTML in Markdown is now enabled by default. Hyde sites generally treat project content as trusted and reviewed; sites that compile untrusted or unreviewed Markdown can set `markdown.allow_html` to `false` to strip potentially unsafe HTML tags.
- `InMemoryPage` contents now accept lazy closures in addition to literal strings. Closures are invoked each time contents are requested with the current page as their first argument, without being rebound.
Expand Down Expand Up @@ -81,6 +81,7 @@ Please fill in UPGRADE.md as you make changes.
- Rename `$fileExtension` to `$sourceExtension` in custom page classes, and update any calls to `fileExtension()` or `setFileExtension()` to `sourceExtension()` and `setSourceExtension()`.
- If you referenced the removed `GenerateSitemap` or `GenerateRssFeed` build task classes (for example to override one with a same-basename user-land task), customize the output by binding a replacement `SitemapGenerator` or `RssFeedGenerator` in the `register()` method of a service provider.
- Replace `// filepath:` code block comments with the `title="…"` fence modifier, including the `#`, `/* */`, and `<!-- -->` comment variants.
- Set `markdown.code_block_label_style` to `badge` to retain the v2 top-right code block label presentation.
- Compare a few pages against your old site if you have custom CSS for code blocks or their labels, since the generated markup changed. The `hyde-code-block` and `hyde-code-block-label` classes are stable hooks to target instead of the markup structure.
- Port any customizations from a published `filepath-label.blade.php` to `markdown/code-block.blade.php`. The old file is ignored after upgrading, so the site renders with the shipped label until they are moved.

Expand Down
8 changes: 8 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,13 @@ Search your source files for `filepath` to find the blocks to convert. All the d
so also check for `#`, `/* */`, and `<!-- -->` comments. A blank line left between the old comment and the code can be
removed with it.

HydePHP v3 displays code block labels in a responsive header instead of the v2 top-right badge. To retain the previous
appearance, add this option to `config/markdown.php`:

```php
'code_block_label_style' => 'badge',
```

## Step 10: Move Your Filepath Label Customizations

Fenced code blocks are now rendered through the `components/markdown/code-block.blade.php` view, which also holds the
Expand Down Expand Up @@ -442,6 +449,7 @@ Use this checklist to track your upgrade progress:
- [ ] Replaced any references to the removed `GenerateSitemap` and `GenerateRssFeed` build tasks with generator implementations bound in a service provider
- [ ] Renamed `$fileExtension`, `fileExtension()`, and `setFileExtension()` to `$sourceExtension`, `sourceExtension()`, and `setSourceExtension()` in custom page classes and call sites
- [ ] Replaced `// filepath:` code block comments with the `title="…"` fence modifier
- [ ] Set `markdown.code_block_label_style` to `badge` if retaining the v2 code block label presentation
- [ ] Ported any `filepath-label.blade.php` customizations to `markdown/code-block.blade.php`, and deleted the old file
- [ ] Compared pages against your old site if you have custom CSS for code blocks or their labels
- [ ] Checked `_posts` for drafts and blog posts dated in the future, and set up recurring builds if scheduling posts
Expand Down
2 changes: 1 addition & 1 deletion _media/app.css

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions config/markdown.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@

'enable_blade' => true,

/*
|--------------------------------------------------------------------------
| Code Block Label Style
|--------------------------------------------------------------------------
|
| Titled code blocks display their label in a responsive header by default.
| Set this to "badge" to use the top-right badge presentation.
|
*/

'code_block_label_style' => 'header',

/*
|--------------------------------------------------------------------------
| Tailwind Typography Prose Classes
Expand Down
14 changes: 8 additions & 6 deletions docs/digging-deeper/advanced-markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ Note that these currently do not support multi-line blockquotes.

## Code Block Titles

When browsing these documentation pages you may have noticed a label in the top right corner of code blocks naming the
file the code belongs to. Add a `title` modifier to a fenced code block to label it like that.
Add a `title` modifier to a fenced code block to show a title or filename in a header above the code.

### Usage

Expand All @@ -284,6 +283,13 @@ Which becomes:
echo 'Hello World!';
```

Code block titles use a responsive header by default. To use the compact badge presentation, set the label style in
`config/markdown.php`:

```php title="config/markdown.php"
'code_block_label_style' => 'badge',
```

### Advanced usage

Since HTML in Markdown is enabled by default, anything within the title will be rendered as HTML. This means you
Expand All @@ -295,10 +301,6 @@ Hello World!
```
````

### Limitations

The titles are hidden on mobile devices using CSS to prevent them from overlapping with the code block.

### Customizations

See [Composable Markdown Blocks](composable-markdown-blocks#code-blocks) for the code block view contract, its class
Expand Down
12 changes: 6 additions & 6 deletions docs/digging-deeper/composable-markdown-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,23 +172,23 @@ See [Advanced Markdown](advanced-markdown#code-block-titles) for the `title` mod

| Class | Targets |
|---------------------------|--------------------------------------------|
| `hyde-code-block` | The outer `<div>` wrapping the code block |
| `hyde-code-block` | The outer wrapper around the code block |
| `hyde-code-block-label` | The block's title label |

### Customization example

Say you want a header bar above the code, showing the language next to the label:
Say you want to show the language next to the label in the header:

```blade title="resources/views/vendor/hyde/components/markdown/code-block.blade.php"
<div class="hyde-code-block not-prose my-4 overflow-hidden rounded">
<figure class="hyde-code-block not-prose my-4 overflow-hidden rounded">
@if($label || $language)
<div class="flex items-center justify-between bg-gray-800 px-4 py-2 font-mono text-xs text-gray-300">
<figcaption class="flex items-center justify-between bg-gray-800 px-4 py-2 font-mono text-xs text-gray-300">
<span>{{ $label }}</span>
<span class="uppercase">{{ $language }}</span>
</div>
</figcaption>
@endif
{!! $contents !!}
</div>
</figure>
```

## Terminal Blocks
Expand Down
12 changes: 12 additions & 0 deletions packages/framework/config/markdown.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@

'enable_blade' => true,

/*
|--------------------------------------------------------------------------
| Code Block Label Style
|--------------------------------------------------------------------------
|
| Titled code blocks display their label in a responsive header by default.
| Set this to "badge" to use the top-right badge presentation.
|
*/

'code_block_label_style' => 'header',

/*
|--------------------------------------------------------------------------
| Tailwind Typography Prose Classes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
@php($hasLabel = isset($label) && (string) $label !== '')
@php($labelStyle = $hasLabel ? \Hyde\Facades\Config::getString('markdown.code_block_label_style', 'header') : 'header')
@if ($hasLabel)
<figure @class([
'hyde-code-block my-4 [&>pre]:my-0',
'overflow-hidden rounded-lg [&>pre]:rounded-none' => $labelStyle === 'header',
'relative' => $labelStyle === 'badge',
])>
@if ($labelStyle === 'header')
<figcaption class="hyde-code-block-label not-prose bg-[#212529] px-4 py-2.5 font-sans text-xs leading-none text-[#A6ACCD] [overflow-wrap:anywhere]">{{ $label }}</figcaption>
@else
<figcaption class="hyde-code-block-label not-prose absolute right-4 top-3 z-10 hidden font-mono text-xs text-[color:var(--tw-prose-pre-code)] opacity-50 transition-opacity duration-250 hover:opacity-100 md:block">{{ $label }}</figcaption>
@endif
{!! $contents !!}
</figure>
@else
<div class="hyde-code-block relative my-4 [&>pre]:my-0">
@isset($label)
<small class="hyde-code-block-label not-prose absolute right-4 top-3 z-10 hidden font-mono text-xs text-[color:var(--tw-prose-pre-code)] opacity-50 transition-opacity duration-250 hover:opacity-100 md:block"><span class="sr-only">Title: </span>{{ $label }}</small>
@endisset
{!! $contents !!}
</div>
@endif
20 changes: 20 additions & 0 deletions packages/framework/src/Markdown/Extensions/CodeBlockViewModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace Hyde\Markdown\Extensions;

use Hyde\Facades\Config;
use Hyde\Framework\Exceptions\InvalidConfigurationException;
use Illuminate\Support\HtmlString;

use function in_array;
use function view;

/** @internal */
Expand All @@ -21,6 +24,10 @@ public function __construct(

public function render(): string
{
if ($this->label !== null) {
$this->validateLabelStyle();
}

return view('hyde::components.markdown.code-block', $this->viewData())->render();
}

Expand All @@ -33,4 +40,17 @@ protected function viewData(): array
'label' => $this->label,
];
}

protected function validateLabelStyle(): void
{
$style = Config::getString('markdown.code_block_label_style', 'header');

if (! in_array($style, ['header', 'badge'], true)) {
throw new InvalidConfigurationException(
"Invalid code block label style [$style]. Supported styles are [header] and [badge].",
'markdown',
'code_block_label_style',
);
}
}
}
4 changes: 2 additions & 2 deletions packages/framework/tests/Feature/BladeBlocksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function testTitledBladeBlockIsRenderedAsALabelledCodeBlock()

$this->assertStringNotContainsString('blade-block', $html);
$this->assertStringContainsString('{{', $html);
$this->assertStringContainsString('>components/alert.blade.php</small>', $html);
$this->assertStringContainsString('>components/alert.blade.php</figcaption>', $html);
}

public function testBladeBlockTitledAfterAnotherModifierIsRenderedAsALabelledCodeBlock()
Expand All @@ -118,7 +118,7 @@ public function testBladeBlockTitledAfterAnotherModifierIsRenderedAsALabelledCod

$this->assertStringNotContainsString('blade-block', $html);
$this->assertStringContainsString('{{', $html);
$this->assertStringContainsString('>components/alert.blade.php</small>', $html);
$this->assertStringContainsString('>components/alert.blade.php</figcaption>', $html);
}

public function testTitledBladeRenderBlockIsStillExecutedAndDropsTheTitle()
Expand Down
50 changes: 37 additions & 13 deletions packages/framework/tests/Feature/CodeBlocksTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,45 @@ public function testTitleModifierBecomesALabel(): void
{
$html = Markdown::render("```php title=\"app/Model.php\"\necho 'Hi';\n```");

$this->assertStringContainsString('<span class="sr-only">Title: </span>app/Model.php</small>', $html);
$this->assertStringContainsString('<figure class="hyde-code-block ', $html);
$this->assertStringContainsString('<figcaption class="hyde-code-block-label ', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
}

public function testLegacyBadgeLabelStyleCanBeConfigured(): void
{
config(['markdown.code_block_label_style' => 'badge']);

$html = Markdown::render("```php title=\"app/Model.php\"\necho 'Hi';\n```");

$this->assertStringContainsString('<figure class="hyde-code-block ', $html);
$this->assertStringContainsString('<figcaption class="hyde-code-block-label ', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
$this->assertStringContainsString('absolute right-4 top-3', $html);
}

public function testUnsupportedLabelStyleThrows(): void
{
config(['markdown.code_block_label_style' => 'unsupported']);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid code block label style [unsupported]. Supported styles are [header] and [badge].');

Markdown::render("```php title=\"app/Model.php\"\necho 'Hi';\n```");
}

public function testTitleModifierAcceptsSingleQuotes(): void
{
$html = Markdown::render("```php title='app/Model.php'\necho 'Hi';\n```");

$this->assertStringContainsString('>app/Model.php</small>', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
}

public function testTitleModifierMayContainWhitespace(): void
{
$html = Markdown::render("```php title=\"My File.php\"\necho 'Hi';\n```");

$this->assertStringContainsString('>My File.php</small>', $html);
$this->assertStringContainsString('>My File.php</figcaption>', $html);
}

public function testTitleModifierDoesNotAffectTheLanguageClass(): void
Expand All @@ -121,7 +145,7 @@ public function testTitleModifierCanBeSetOnAFenceThatDeclaresNoLanguage(): void
{
$html = Markdown::render("``` title=\"app/Model.php\"\necho 'Hi';\n```");

$this->assertStringContainsString('>app/Model.php</small>', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
$this->assertStringContainsString("<pre><code class=\"language-plaintext\">echo 'Hi';", $html);
}

Expand All @@ -144,7 +168,7 @@ public function testFallbackLanguageIsRenderedAsTheLanguage(): void
{
$html = Markdown::render("``` title=\"app/Model.php\" theme:github-dark\necho 'Hi';\n```");

$this->assertStringContainsString('>app/Model.php</small>', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
$this->assertStringContainsString('<pre><code class="language-plaintext">', $html);
$this->assertStringNotContainsString('theme:github-dark', $html);
}
Expand Down Expand Up @@ -189,12 +213,12 @@ public function testTheLastOfSeveralTitleModifiersWins(): void
{
$html = Markdown::render("```php title=\"first.php\" title=\"second.php\"\necho 'Hi';\n```");

$this->assertStringContainsString('>second.php</small>', $html);
$this->assertStringContainsString('>second.php</figcaption>', $html);
$this->assertStringNotContainsString('first.php', $html);
$this->assertSame('php', $this->fenceInfoSeenByHighlighter('php title="first.php" title="second.php"'));
}

public function testAnEmptyTitleIsNoTitle(): void
public function testEmptyTitleDoesNotRenderALabel(): void
{
$html = Markdown::render("```php title=\"\"\necho 'Hi';\n```");

Expand Down Expand Up @@ -229,7 +253,7 @@ public function testLabelIsNotEscapedWhenHtmlIsAllowed(): void
{
$html = Markdown::render("```php title='<a href=\"#\">Link</a>'\necho 'Hi';\n```");

$this->assertStringContainsString('<a href="#">Link</a></small>', $html);
$this->assertStringContainsString('<a href="#">Link</a></figcaption>', $html);
}

public function testLabelIsEscapedWhenHtmlIsDisabled(): void
Expand All @@ -238,7 +262,7 @@ public function testLabelIsEscapedWhenHtmlIsDisabled(): void

$html = Markdown::render("```php title='<a href=\"#\">Link</a>'\necho 'Hi';\n```");

$this->assertStringContainsString(e('<a href="#">Link</a>').'</small>', $html);
$this->assertStringContainsString(e('<a href="#">Link</a>').'</figcaption>', $html);
}

public function testTorchlightStillRendersTheHighlightedMarkup(): void
Expand All @@ -256,7 +280,7 @@ public function testTorchlightHighlightedBlocksStillGetTheirLabel(): void
{
$html = $this->renderWithTorchlight("```php title=\"hello-world.php\"\necho 'Hello World!';\n```");

$this->assertStringContainsString('<span class="sr-only">Title: </span>hello-world.php</small>', $html);
$this->assertStringContainsString('>hello-world.php</figcaption>', $html);
}

public function testTerminalBlocksAreStillNotSubmittedToTorchlight(): void
Expand Down Expand Up @@ -318,7 +342,7 @@ public function testAHighlighterListenerSeesThePreparedCodeHoweverEarlyItRegiste
$html = Markdown::render("```php title=\"app/Model.php\"\necho 'Hi';\n```");

$this->assertSame([['php', "echo 'Hi';"]], ListeningHighlighterExtension::$collected);
$this->assertStringContainsString('>app/Model.php</small>', $html);
$this->assertStringContainsString('>app/Model.php</figcaption>', $html);
}

public function testTerminalFencesKeepTheirOwnTitleModifier(): void
Expand Down Expand Up @@ -370,8 +394,8 @@ public function testViewModelRendersTheCodeBlockView(): void
{
$model = new CodeBlockViewModel('<pre><code>Hello World!</code></pre>', 'php', 'foo.php');

$this->assertStringContainsString('<div class="hyde-code-block ', $model->render());
$this->assertStringContainsString('>foo.php</small>', $model->render());
$this->assertStringContainsString('<figure class="hyde-code-block ', $model->render());
$this->assertStringContainsString('>foo.php</figcaption>', $model->render());
$this->assertStringContainsString('<pre><code>Hello World!</code></pre>', $model->render());
}

Expand Down
Loading
Loading