Allow referencing other snips in snip
This commit is contained in:
parent
980ea6af94
commit
9f5bb66ae9
@ -44,25 +44,25 @@ class SnipController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/single/{snip}', name: '_single')]
|
||||
public function single(Snip $snip): Response
|
||||
public function single(Snip $snip, Pipeline $pl): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||
|
||||
$snipService = $this->snipServiceFactory->create($snip);
|
||||
return $this->render('snip/single.html.twig', [
|
||||
'snip' => $snip,
|
||||
'content' => (new Pipeline())->parse($snipService->get()),
|
||||
'content' => $pl->parse($snipService->get()),
|
||||
'branch' => $snipService->getRepo()->getCurrentBranchName(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/raw/{snip}', name: '_raw')]
|
||||
public function raw(Snip $snip, Request $request): Response
|
||||
public function raw(Snip $snip, Pipeline $pl, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::VIEW, $snip);
|
||||
|
||||
$response = new Response(
|
||||
(new Pipeline())->clean($this->snipServiceFactory->create($snip)->get()),
|
||||
$pl->clean($this->snipServiceFactory->create($snip)->get()),
|
||||
Response::HTTP_OK,
|
||||
['Content-Type' => 'text/html']
|
||||
);
|
||||
@ -77,10 +77,6 @@ class SnipController extends AbstractController
|
||||
}
|
||||
|
||||
return $response;
|
||||
// return $this->render('snip/single.html.twig', [
|
||||
// 'snip' => $snip,
|
||||
// 'content' => $this->snipServiceFactory->create($snip)->get(),
|
||||
// ]);
|
||||
}
|
||||
|
||||
#[Route('/edit/{snip}', name: '_edit')]
|
||||
|
@ -3,19 +3,28 @@
|
||||
namespace App\Service\SnipParser;
|
||||
|
||||
use App\Service\SnipParser\Stages\HtmlEscapeStage;
|
||||
use App\Service\SnipParser\Stages\UrlReferenceStage;
|
||||
use App\Service\SnipParser\Stages\ReplaceBlocksStage;
|
||||
use App\Service\SnipParser\Stages\ReplaceStage;
|
||||
use League\Pipeline\PipelineBuilder;
|
||||
|
||||
class Pipeline
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UrlReferenceStage $referenceStage,
|
||||
) {}
|
||||
|
||||
public function parse(string $payload): string
|
||||
{
|
||||
$builder = new PipelineBuilder();
|
||||
$pipeline = $builder
|
||||
->add(new HtmlEscapeStage())
|
||||
->add(new ReplaceStage(PHP_EOL, '<br>'))
|
||||
->add(new ReplaceBlocksStage('<pre><code class="hljs">', '</code></pre>', '```'))
|
||||
->add(new ReplaceBlocksStage('<code class="hljs">', '</code>', '``'))
|
||||
->build();
|
||||
->add($this->referenceStage)
|
||||
->build()
|
||||
;
|
||||
|
||||
return $pipeline->process($payload);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ class HtmlEscapeStage implements StageInterface
|
||||
{
|
||||
public function __invoke(mixed $payload): string
|
||||
{
|
||||
return htmlspecialchars($payload, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
// https://www.php.net/manual/en/function.htmlspecialchars.php
|
||||
return htmlspecialchars($payload, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
|
||||
}
|
||||
}
|
19
src/Service/SnipParser/Stages/ReplaceStage.php
Normal file
19
src/Service/SnipParser/Stages/ReplaceStage.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipParser\Stages;
|
||||
|
||||
use League\Pipeline\StageInterface;
|
||||
|
||||
class ReplaceStage implements StageInterface
|
||||
{
|
||||
// replaces a string with another string
|
||||
public function __construct(
|
||||
public readonly string $search,
|
||||
public readonly string $replace,
|
||||
) {}
|
||||
|
||||
public function __invoke(mixed $payload): string
|
||||
{
|
||||
return str_replace($this->search, $this->replace, $payload);
|
||||
}
|
||||
}
|
43
src/Service/SnipParser/Stages/UrlReferenceStage.php
Normal file
43
src/Service/SnipParser/Stages/UrlReferenceStage.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipParser\Stages;
|
||||
|
||||
use App\Repository\SnipRepository;
|
||||
use App\Security\Voter\SnipVoter;
|
||||
use League\Pipeline\StageInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class UrlReferenceStage implements StageInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UrlGeneratorInterface $router,
|
||||
private readonly Security $security,
|
||||
private readonly SnipRepository $snipRepository,
|
||||
) {}
|
||||
|
||||
public function __invoke(mixed $payload): string
|
||||
{
|
||||
return $this->replaceReferences($payload);
|
||||
}
|
||||
|
||||
private function replaceReferences(mixed $payload): string
|
||||
{
|
||||
// replaces all references (#n) to other snips with links
|
||||
$pattern = '/#(\d+)/';
|
||||
|
||||
return preg_replace_callback($pattern, function ($matches) {
|
||||
dump($matches);
|
||||
$snip = $this->snipRepository->find($matches[1]);
|
||||
if ($snip === null) {
|
||||
return sprintf('<span title="not found">%s</span>', $matches[0]);
|
||||
}
|
||||
if (!$this->security->isGranted(SnipVoter::VIEW, $snip)) {
|
||||
return sprintf('<span title="access denied">%s</span>', $matches[0]);
|
||||
}
|
||||
|
||||
$url = $this->router->generate('snip_single', ['snip' => $snip->getId()]);
|
||||
return sprintf('<a href="%s" title="Owner: %s">#%s</a>', $url, $snip->getCreatedBy(), $snip->getId());
|
||||
}, $payload);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
<div class="card" style="width: 100%;">
|
||||
<h4 class="card-header">
|
||||
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||
{{ snip }}
|
||||
{{ snip }} <small class="text-muted">#{{ snip.id }}</small>
|
||||
</h4>
|
||||
<div class="card-header">
|
||||
<p class="card-text">Current branch: {{ branch }}</p>
|
||||
|
Loading…
Reference in New Issue
Block a user