├── phpstan.neon.dist ├── phpstan-baseline.neon ├── LICENSE.md ├── CHANGELOG.md ├── src ├── ShouldWireNavigate.php └── WireNavigateExtension.php ├── composer.json └── README.md /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 8 6 | paths: 7 | - src 8 | - tests 9 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - 4 | message: "#^Call to an undefined method Pest\\\\PendingCalls\\\\TestCall\\:\\:expect\\(\\)\\.$#" 5 | count: 1 6 | path: tests/ArchTest.php 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `commonmark-wire-navigate` will be documented in this file. 4 | 5 | ## 1.1.0 - 2024-08-27 6 | 7 | ### What's Changed 8 | 9 | * Bump aglipanci/laravel-pint-action from 2.3.1 to 2.4 by @dependabot in https://github.com/spatie/commonmark-wire-navigate/pull/4 10 | * Add Laravel-markdown reference by @francoism90 in https://github.com/spatie/commonmark-wire-navigate/pull/5 11 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.2.0 by @dependabot in https://github.com/spatie/commonmark-wire-navigate/pull/7 12 | * Add support for enable/disable on fragments of the current page. by @GeertHauwaerts in https://github.com/spatie/commonmark-wire-navigate/pull/8 13 | 14 | ### New Contributors 15 | 16 | * @dependabot made their first contribution in https://github.com/spatie/commonmark-wire-navigate/pull/4 17 | * @francoism90 made their first contribution in https://github.com/spatie/commonmark-wire-navigate/pull/5 18 | * @GeertHauwaerts made their first contribution in https://github.com/spatie/commonmark-wire-navigate/pull/8 19 | 20 | **Full Changelog**: https://github.com/spatie/commonmark-wire-navigate/compare/1.0.0...1.1.0 21 | 22 | ## 1.0.0 - 2024-03-15 23 | 24 | First release! 25 | -------------------------------------------------------------------------------- /src/ShouldWireNavigate.php: -------------------------------------------------------------------------------- 1 | baseUrl = $domain 18 | ? Url::fromString(preg_match('/^https?:\/\//', $domain) ? $domain : ('https://'.$domain)) 19 | : Url::create(); 20 | } 21 | 22 | public function __invoke(string $url): bool 23 | { 24 | // Ensure same page fragment match 25 | if (str_starts_with($url, '#')) { 26 | return $this->fragment; 27 | } 28 | 29 | $url = Url::fromString($url); 30 | 31 | // Ensure hosts match 32 | if ($url->getHost()) { 33 | if (strtolower($url->getHost()) !== strtolower($this->baseUrl->getHost())) { 34 | return false; 35 | } 36 | } 37 | 38 | // Ensure base paths match 39 | if (! str_starts_with(strtolower($url->getPath()), strtolower($this->baseUrl->getPath()))) { 40 | return false; 41 | } 42 | 43 | if ($this->paths === null) { 44 | return true; 45 | } 46 | 47 | foreach ($this->paths as $path) { 48 | if (str_starts_with(trim($url->getPath(), '/').'/', trim($path, '/').'/')) { 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/commonmark-wire-navigate", 3 | "description": "Add a wire:navigate attribute to links rendered in Markdown", 4 | "keywords": [ 5 | "spatie", 6 | "commmonmark", 7 | "markdown", 8 | "livewire", 9 | "commonmark-wire-navigate" 10 | ], 11 | "homepage": "https://github.com/spatie/commonmark-wire-navigate", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "sebastiandedeyne", 16 | "email": "sebastian@spatie.be", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.1", 22 | "league/commonmark": "^2.4", 23 | "spatie/url": "^2.4" 24 | }, 25 | "require-dev": { 26 | "laravel/pint": "^1.0", 27 | "pestphp/pest": "^2.34", 28 | "pestphp/pest-plugin-type-coverage": "^2.8", 29 | "phpstan/phpstan": "^1.10", 30 | "spatie/ray": "^1.41" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Spatie\\CommonMarkWireNavigate\\": "src" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Spatie\\CommonMarkWireNavigate\\Tests\\": "tests" 40 | } 41 | }, 42 | "scripts": { 43 | "test": "vendor/bin/pest", 44 | "test-coverage": "vendor/bin/pest --coverage", 45 | "format": "vendor/bin/pint", 46 | "analyse": "vendor/bin/phpstan analyse", 47 | "baseline": "vendor/bin/phpstan analyse --generate-baseline" 48 | }, 49 | "config": { 50 | "sort-packages": true, 51 | "allow-plugins": { 52 | "pestphp/pest-plugin": true, 53 | "phpstan/extension-installer": true 54 | } 55 | }, 56 | "minimum-stability": "dev", 57 | "prefer-stable": true 58 | } 59 | -------------------------------------------------------------------------------- /src/WireNavigateExtension.php: -------------------------------------------------------------------------------- 1 | addRenderer(Link::class, $this, 10); 30 | } 31 | 32 | public function render(Node $node, ChildNodeRendererInterface $childRenderer): Stringable|string|null 33 | { 34 | if (! $node instanceof Link) { 35 | throw new Exception('Unsupported node'); 36 | } 37 | 38 | $this->shouldWireNavigate ??= new ShouldWireNavigate( 39 | domain: $this->configuration->get('wire_navigate/domain'), 40 | paths: $this->configuration->get('wire_navigate/paths'), 41 | fragment: $this->configuration->get('wire_navigate/fragment'), 42 | ); 43 | 44 | $this->attribute ??= $this->configuration->get('wire_navigate/hover') 45 | ? 'wire:navigate.hover' 46 | : 'wire:navigate'; 47 | 48 | if (($this->shouldWireNavigate)($node->getUrl())) { 49 | $node->data->set( 50 | 'attributes', 51 | array_merge($node->data->get('attributes'), [ 52 | $this->attribute => true, 53 | ]), 54 | ); 55 | } 56 | 57 | $renderer = new LinkRenderer; 58 | $renderer->setConfiguration($this->configuration); 59 | 60 | return $renderer->render($node, $childRenderer); 61 | } 62 | 63 | public function setConfiguration(ConfigurationInterface $configuration): void 64 | { 65 | $this->configuration = $configuration; 66 | } 67 | 68 | public function configureSchema(ConfigurationBuilderInterface $builder): void 69 | { 70 | $builder->addSchema('wire_navigate', Expect::structure([ 71 | 'domain' => Expect::string(''), 72 | 'paths' => Expect::anyOf(Expect::null(), Expect::arrayOf(Expect::string()))->default(null), 73 | 'hover' => Expect::bool(false), 74 | 'fragment' => Expect::bool(false), 75 | ])); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Add a wire:navigate attribute to links rendered in Markdown 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/commonmark-wire-navigate.svg?style=flat-square)](https://packagist.org/packages/spatie/commonmark-wire-navigate) 4 | [![Tests](https://img.shields.io/github/actions/workflow/status/spatie/commonmark-wire-navigate/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/spatie/commonmark-wire-navigate/actions/workflows/run-tests.yml) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/commonmark-wire-navigate.svg?style=flat-square)](https://packagist.org/packages/spatie/commonmark-wire-navigate) 6 | 7 | An extension for [league/commonmark](https://github.com/thephpleague/commonmark) to add a `wire:navigate` attribute to links rendered in Markdown and enable [SPA mode](https://livewire.laravel.com/docs/navigate) in Livewire. 8 | 9 | ## Support us 10 | 11 | [](https://spatie.be/github-ad-click/commonmark-wire-navigate) 12 | 13 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 14 | 15 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 16 | 17 | ## Installation 18 | 19 | You can install the package via composer: 20 | 21 | ```bash 22 | composer require spatie/commonmark-wire-navigate 23 | ``` 24 | 25 | ## Usage 26 | 27 | Register `CommonMarkWireNavigate` as a CommonMark extension. 28 | 29 | ```php 30 | use League\CommonMark\Environment\Environment; 31 | use League\CommonMark\CommonMarkConverter; 32 | use Spatie\CommonMarkWireNavigate\WireNavigateExtension; 33 | 34 | $converter = new CommonMarkConverter($environment); 35 | $converter->getEnvironment()->addExtension(new WireNavigateExtension()); 36 | 37 | echo $converter->convert('[About](/about)'); 38 | //

About

39 | ``` 40 | 41 | For more information on CommonMark extensions and environments, refer to the [CommonMark documentation](https://commonmark.thephpleague.com/2.4/basic-usage/). 42 | 43 | ### Laravel-markdown 44 | 45 | When using the [Laravel-markdown](https://github.com/spatie/laravel-markdown/) package, you may register the extension in `config/markdown.php`: 46 | 47 | ```php 48 | /* 49 | * These extensions should be added to the markdown environment. A valid 50 | * extension implements League\CommonMark\Extension\ExtensionInterface 51 | * 52 | * More info: https://commonmark.thephpleague.com/2.4/extensions/overview/ 53 | */ 54 | 'extensions' => [ 55 | Spatie\CommonMarkWireNavigate\WireNavigateExtension::class, 56 | ], 57 | ``` 58 | 59 | ### Choosing which links to enhance 60 | 61 | By default, the extension will add `wire:navigate` to all internal links except fragments of the current page. To know which link is internal, you must specify your application's base URL. 62 | 63 | ```php 64 | $converter = new CommonMarkConverter($environment); 65 | $converter->getEnvironment()->addExtension(new WireNavigateExtension( 66 | baseUrl: 'https://example.app', 67 | )); 68 | 69 | 70 | echo $converter->convert('[About](/about)'); 71 | //

About 72 | 73 | echo $converter->convert('[About](https://example.app/about)'); 74 | //

About 75 | 76 | echo $converter->convert('[Twitter](https://twitter.com/spatie_be)'); 77 | // Twitter

78 | ``` 79 | 80 | Additionally, you can configure whether the attribute will be added using an array of patterns or a callback. 81 | 82 | Using an array to specify a root path in your application: 83 | 84 | ```php 85 | $converter = new CommonMarkConverter($environment); 86 | $converter->getEnvironment()->addExtension(new WireNavigateExtension( 87 | baseUrl: 'https://example.app', 88 | enabled: ['docs', 'guide'], 89 | )); 90 | 91 | echo $converter->convert('[Installation](/docs/installation)'); 92 | //

Installation 93 | 94 | echo $converter->convert('[Guide](/guide)'); 95 | //

Guide 96 | 97 | echo $converter->convert('[About](/about)'); 98 | //

About 99 | ``` 100 | 101 | Using a callback: 102 | 103 | ```php 104 | $converter = new CommonMarkConverter($environment); 105 | $converter->getEnvironment()->addExtension(new WireNavigateExtension( 106 | baseUrl: 'https://example.app', 107 | enabled: fn (string $url) => preg_match('/\/docs\//', $url), 108 | hover: true, 109 | )); 110 | ``` 111 | 112 | Enable on fragments of the current page: 113 | 114 | ```php 115 | $converter = new CommonMarkConverter($environment); 116 | $converter->getEnvironment()->addExtension(new WireNavigateExtension( 117 | fragment: true, 118 | )); 119 | ``` 120 | 121 | ### Prefetching pages on hover 122 | 123 | If you want to have Livewire prefetch pages when a link is hovered, enable the `hover` option. 124 | 125 | ```php 126 | $converter = new CommonMarkConverter($environment); 127 | $converter->getEnvironment()->addExtension(new WireNavigateExtension( 128 | baseUrl: 'https://example.app', 129 | hover: true, 130 | )); 131 | ``` 132 | 133 | Now the extension will add `wire:navigate.hover` to links instead. 134 | 135 | ```php 136 | echo $converter->convert('[About](/about)'); 137 | //

About

138 | ``` 139 | 140 | ## Testing 141 | 142 | ```bash 143 | composer test 144 | ``` 145 | 146 | ## Changelog 147 | 148 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 149 | 150 | ## Contributing 151 | 152 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 153 | 154 | ## Security Vulnerabilities 155 | 156 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 157 | 158 | ## Credits 159 | 160 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 161 | - [All Contributors](../../contributors) 162 | 163 | ## License 164 | 165 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 166 | --------------------------------------------------------------------------------