Compare commits
2 Commits
master
...
feature/br
Author | SHA1 | Date | |
---|---|---|---|
283c9ecb27 | |||
0e5d92258d |
@ -14,7 +14,6 @@
|
||||
"league/pipeline": "^1.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.1",
|
||||
"symfony/asset": "7.2.*",
|
||||
"symfony/console": "*",
|
||||
"symfony/dotenv": "*",
|
||||
"symfony/flex": "^2",
|
||||
@ -30,7 +29,6 @@
|
||||
"symfony/uid": "*",
|
||||
"symfony/validator": "*",
|
||||
"symfony/yaml": "*",
|
||||
"tempest/highlight": "^2.11",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^3.0"
|
||||
},
|
||||
|
129
composer.lock
generated
129
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6bdfa9b2a81e0169ca083ef4d344b876",
|
||||
"content-hash": "638cbc9841226f386ba27215e24c5410",
|
||||
"packages": [
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
@ -2370,75 +2370,6 @@
|
||||
},
|
||||
"time": "2024-09-11T13:17:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/asset",
|
||||
"version": "v7.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/asset.git",
|
||||
"reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/asset/zipball/cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
|
||||
"reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/http-foundation": "<6.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Asset\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/asset/tree/v7.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-25T15:15:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/cache",
|
||||
"version": "v7.2.6",
|
||||
@ -6505,64 +6436,6 @@
|
||||
],
|
||||
"time": "2025-04-04T10:10:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tempest/highlight",
|
||||
"version": "2.11.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tempestphp/highlight.git",
|
||||
"reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tempestphp/highlight/zipball/5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd",
|
||||
"reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"assertchris/ellison": "^1.0.2",
|
||||
"friendsofphp/php-cs-fixer": "^3.21",
|
||||
"league/commonmark": "^2.4",
|
||||
"phpstan/phpstan": "^1.10.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"assertchris/ellison": "Allows you to analyse sentence complexity",
|
||||
"league/commonmark": "Adds markdown support"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Tempest\\Highlight\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Brent Roose",
|
||||
"email": "brendt@stitcher.io"
|
||||
}
|
||||
],
|
||||
"description": "Fast, extensible, server-side code highlighting",
|
||||
"support": {
|
||||
"issues": "https://github.com/tempestphp/highlight/issues",
|
||||
"source": "https://github.com/tempestphp/highlight/tree/2.11.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/brendt",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-19T05:38:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/extra-bundle",
|
||||
"version": "v3.21.0",
|
||||
|
@ -1,87 +0,0 @@
|
||||
pre, code {
|
||||
color: #1f2328;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.hl-keyword {
|
||||
color: #cf222e;
|
||||
}
|
||||
|
||||
.hl-property {
|
||||
color: #8250df;
|
||||
}
|
||||
|
||||
.hl-attribute {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hl-type {
|
||||
color: #EA4334;
|
||||
}
|
||||
|
||||
.hl-generic {
|
||||
color: #9d3af6;
|
||||
}
|
||||
|
||||
.hl-value {
|
||||
color: #0a3069;
|
||||
}
|
||||
|
||||
.hl-literal {
|
||||
color: #0a3069;
|
||||
}
|
||||
|
||||
.hl-number {
|
||||
color: #0a3069;
|
||||
}
|
||||
|
||||
.hl-variable {
|
||||
color: #953800;
|
||||
}
|
||||
|
||||
.hl-comment {
|
||||
color: #6e7781;
|
||||
}
|
||||
|
||||
.hl-blur {
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.hl-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hl-em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hl-addition {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
background-color: #00FF0022;
|
||||
}
|
||||
|
||||
.hl-deletion {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
background-color: #FF000011;
|
||||
}
|
||||
|
||||
.hl-gutter {
|
||||
display: inline-block;
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
padding: 0 1ch;
|
||||
margin-right: 1ch;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hl-gutter-addition {
|
||||
background-color: #34A853;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.hl-gutter-deletion {
|
||||
background-color: #EA4334;
|
||||
color: #fff;
|
||||
}
|
BIN
public/snips.png
BIN
public/snips.png
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB |
@ -22,8 +22,7 @@ class SnipController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SnipRepository $repository,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
#[Route('/', name: '_index')]
|
||||
public function index(#[MapQueryCached] SnipFilterRequest $request): Response
|
||||
@ -67,7 +66,8 @@ class SnipController extends AbstractController
|
||||
->setVary(['Accept', 'Accept-Encoding'])
|
||||
->setEtag(md5($response->getContent()))
|
||||
->setTtl(3600)
|
||||
->setClientTtl(300);
|
||||
->setClientTtl(300)
|
||||
;
|
||||
|
||||
if (!$request->isNoCache()) {
|
||||
$response->isNotModified($request);
|
||||
@ -81,26 +81,14 @@ class SnipController extends AbstractController
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||
|
||||
/**
|
||||
* Temporary solution to prevent editing of old versions
|
||||
* It technically fully works, but rendering the version history needs an update first
|
||||
*/
|
||||
$isLatest = $snip->getActiveVersion() === $snip->getLatestVersion();
|
||||
if (!$isLatest) {
|
||||
$this->addFlash('error', 'Snip is not the latest version, changes will not be saved.');
|
||||
$form = $this->createForm(SnipType::class, $snip);
|
||||
$form->add('Save', SubmitType::class);
|
||||
if ($snip->getId()) {
|
||||
$form->get('content')->setData($snip->getActiveText());
|
||||
}
|
||||
|
||||
$form = $this->createForm(SnipType::class, $snip)
|
||||
->add('Save', SubmitType::class);
|
||||
$form->get('content')->setData($snip->getActiveText());
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
if (!$isLatest) {
|
||||
return $this->redirectToRoute('snip_single', [
|
||||
'snip' => $snip->getId(),
|
||||
]);
|
||||
}
|
||||
$this->repository->save($snip);
|
||||
$contentService->update(
|
||||
$snip,
|
||||
@ -110,9 +98,7 @@ class SnipController extends AbstractController
|
||||
|
||||
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
||||
|
||||
return $this->redirectToRoute('snip_single', [
|
||||
'snip' => $snip->getId(),
|
||||
]);
|
||||
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('snip/edit.html.twig', [
|
||||
@ -126,31 +112,10 @@ class SnipController extends AbstractController
|
||||
{
|
||||
$snip = new Snip();
|
||||
$snip->setCreatedAtNow()
|
||||
->setCreatedBy($this->getUser());
|
||||
->setCreatedBy($this->getUser())
|
||||
;
|
||||
|
||||
$form = $this->createForm(SnipType::class, $snip);
|
||||
$form->add('Create', SubmitType::class);
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->repository->save($snip);
|
||||
$contentService->update(
|
||||
$snip,
|
||||
$form->get('content')->getData(),
|
||||
$form->get('contentName')->getData()
|
||||
);
|
||||
|
||||
$this->addFlash('success', sprintf('Snip "%s" created', $snip));
|
||||
|
||||
return $this->redirectToRoute('snip_single', [
|
||||
'snip' => $snip->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('snip/create.html.twig', [
|
||||
'snip' => $snip,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
return $this->edit($snip, $request, $contentService);
|
||||
}
|
||||
|
||||
#[Route('/delete/{snip}', name: '_delete')]
|
||||
@ -186,6 +151,6 @@ class SnipController extends AbstractController
|
||||
$this->addFlash('success', sprintf('Snip "%s" unarchived', $snip));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('snip_edit', ['snip' => $snip->getId()]);
|
||||
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
||||
use App\Entity\Snip;
|
||||
use App\Entity\SnipContent;
|
||||
use App\Security\Voter\SnipVoter;
|
||||
use App\Service\SnipContent\FlowChartTreeBuilder;
|
||||
use App\Service\SnipContent\SnipContentService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@ -18,12 +19,15 @@ class VersionController extends AbstractController
|
||||
) {}
|
||||
|
||||
#[Route('/', name: '_index')]
|
||||
public function index(Snip $snip): Response
|
||||
public function index(Snip $snip, FlowChartTreeBuilder $builder): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||
|
||||
$buildTree = $builder->buildTree($snip);
|
||||
return $this->render('version/index.html.twig', [
|
||||
'snip' => $snip,
|
||||
// 'versions' => new GitTreeBuilder($snip)->buildTree($snip->getSnipContents()->first()),
|
||||
'versions' => $buildTree,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,11 @@ class SnipContent
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name ?? $this->id->toBase32();
|
||||
}
|
||||
|
||||
public function getId(): ?Ulid
|
||||
{
|
||||
return $this->id;
|
||||
|
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipContent;
|
||||
|
||||
use App\Entity\Snip;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
readonly class FlowChartTreeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private RouterInterface $router,
|
||||
) {}
|
||||
|
||||
public function buildTree(Snip $snip): array
|
||||
{
|
||||
$tree = [];
|
||||
|
||||
foreach ($snip->getSnipContents() as $content) {
|
||||
if ($content->getParent()) {
|
||||
$tree[] = sprintf('%s --> %s', $content->getParent(), $content);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($snip->getSnipContents() as $content) {
|
||||
$tree[] = sprintf(
|
||||
'click %s href "%s"',
|
||||
$content,
|
||||
$this->router->generate('version_set', ['snip' => $snip->getId(), 'version' => $content->getId()])
|
||||
);
|
||||
$tree[] = sprintf('%s@{ shape: rounded }', $content);
|
||||
}
|
||||
|
||||
$tree[] = sprintf('class %s active', $snip->getActiveVersion());
|
||||
|
||||
return $tree;
|
||||
}
|
||||
}
|
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipContent;
|
||||
|
||||
use App\Entity\Snip;
|
||||
use App\Entity\SnipContent;
|
||||
|
||||
class GitTreeBuilder
|
||||
{
|
||||
private string $activeBranch = 'main';
|
||||
|
||||
public function __construct(private readonly Snip $snip) {}
|
||||
|
||||
public function buildTree(SnipContent $content, string $branch = 'main'): array
|
||||
{
|
||||
$tree = [];
|
||||
if ($this->activeBranch !== $branch) {
|
||||
$tree[] = $this->checkout($branch);
|
||||
}
|
||||
$commit = sprintf('commit id:"%s"', $content);
|
||||
if ($this->snip->getActiveVersion() === $content) {
|
||||
$commit .= ' tag: "active" type: REVERSE';
|
||||
}
|
||||
$tree[] = $commit;
|
||||
|
||||
$first = true;
|
||||
foreach ($content->getChildren() as $child) {
|
||||
if (!$first) {
|
||||
$tree[] = $this->branch($child);
|
||||
$tree[] = $this->checkout($branch);
|
||||
}
|
||||
$first = false;
|
||||
}
|
||||
|
||||
$first = true;
|
||||
foreach ($content->getChildren() as $child) {
|
||||
if ($first) {
|
||||
$myBranch = $branch;
|
||||
} else {
|
||||
$tree[] = $this->checkout($child);
|
||||
$myBranch = $child;
|
||||
}
|
||||
$tree = array_merge($tree, self::buildTree($child, $myBranch));
|
||||
$first = false;
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private function branch(string $branch): string
|
||||
{
|
||||
$this->activeBranch = $branch;
|
||||
return sprintf('branch %s', $branch);
|
||||
}
|
||||
|
||||
private function checkout(string $branch): string
|
||||
{
|
||||
$this->activeBranch = $branch;
|
||||
return sprintf('checkout %s', $branch);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ abstract class AbstractParser implements ParserInterface
|
||||
try {
|
||||
return $this->safeParseView($content);
|
||||
} catch (\Exception $exception) {
|
||||
return sprintf('<pre><code>%s</code></pre>', htmlspecialchars($exception->getMessage()));
|
||||
return sprintf('<pre><code class="hljs">%s</code></pre>', htmlspecialchars($exception->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,9 @@ class GenericParser extends AbstractParser
|
||||
$builder = new PipelineBuilder();
|
||||
$pipeline = $builder
|
||||
->add(new HtmlEscapeStage())
|
||||
// ->add(new ReplaceBlocksStage('<pre>', '</pre>', '```'))
|
||||
// ->add(new ReplaceBlocksStage('<code>', '</code>', '``'))
|
||||
->add(new ReplaceStage(PHP_EOL, '<br>'))
|
||||
->add(new ReplaceBlocksStage('<pre><code class="hljs">', '</code></pre>', '```'))
|
||||
->add(new ReplaceBlocksStage('<code class="hljs">', '</code>', '``'))
|
||||
->add($this->referenceStage)
|
||||
->add($this->includeStage)
|
||||
->build()
|
||||
@ -27,4 +27,13 @@ class GenericParser extends AbstractParser
|
||||
|
||||
return $pipeline->process($content);
|
||||
}
|
||||
|
||||
public function parseRaw(string $content): string
|
||||
{
|
||||
return str_replace(
|
||||
['```', '``'],
|
||||
'',
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
@ -4,14 +4,13 @@ namespace App\Service\SnipParser\Generic;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use League\Pipeline\StageInterface;
|
||||
use Tempest\Highlight\Highlighter;
|
||||
|
||||
readonly class ReplaceBlocksStage implements StageInterface
|
||||
class ReplaceBlocksStage implements StageInterface
|
||||
{
|
||||
public function __construct(
|
||||
public string $openTag = '<pre><code>',
|
||||
public string $closeTag = '</code></pre>',
|
||||
public string $delimiter = '```'
|
||||
public readonly string $openTag = '<pre><code>',
|
||||
public readonly string $closeTag = '</code></pre>',
|
||||
public readonly string $delimiter = '```'
|
||||
) {}
|
||||
|
||||
public function __invoke(mixed $payload): string
|
||||
@ -27,9 +26,8 @@ readonly class ReplaceBlocksStage implements StageInterface
|
||||
{
|
||||
$pattern = sprintf('/%s(.+?)%s/s', preg_quote($this->delimiter), preg_quote($this->delimiter));
|
||||
|
||||
$highlighter = new Highlighter()->withGutter();
|
||||
return preg_replace_callback($pattern, function ($matches) use ($highlighter) {
|
||||
return $this->openTag . $highlighter->parse(trim($matches[1]), 'php') . $this->closeTag;
|
||||
return preg_replace_callback($pattern, function ($matches) {
|
||||
return $this->openTag . trim($matches[1]) . $this->closeTag;
|
||||
}, $text);
|
||||
}
|
||||
}
|
@ -3,14 +3,11 @@
|
||||
namespace App\Service\SnipParser\Html;
|
||||
|
||||
use App\Service\SnipParser\AbstractParser;
|
||||
use Tempest\Highlight\Highlighter;
|
||||
|
||||
class HtmlParser extends AbstractParser
|
||||
{
|
||||
public function safeParseView(string $content): string
|
||||
{
|
||||
$highlighter = new Highlighter()->withGutter();
|
||||
|
||||
return '<pre data-lang="html" class="notranslate">' . $highlighter->parse($content, 'html') . '</pre>';
|
||||
return sprintf('<pre><code class="hljs">%s</code></pre>', htmlspecialchars($content));
|
||||
}
|
||||
}
|
@ -6,16 +6,11 @@ use App\Repository\SnipRepository;
|
||||
use App\Service\SnipParser\AbstractParser;
|
||||
use League\CommonMark\Event\DocumentParsedEvent;
|
||||
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
|
||||
use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
|
||||
use League\CommonMark\Extension\Footnote\FootnoteExtension;
|
||||
use League\CommonMark\Extension\Table\Table;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
use League\CommonMark\Node\Inline\Text;
|
||||
use League\CommonMark\Node\Query;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Tempest\Highlight\CommonMark\HighlightExtension;
|
||||
use Tempest\Highlight\Highlighter;
|
||||
|
||||
class MarkdownParser extends AbstractParser
|
||||
{
|
||||
@ -26,25 +21,8 @@ class MarkdownParser extends AbstractParser
|
||||
|
||||
public function safeParseView(string $content): string
|
||||
{
|
||||
$config = [
|
||||
'default_attributes' => [
|
||||
Table::class => [
|
||||
'class' => 'table table-hover',
|
||||
],
|
||||
Link::class => [
|
||||
'class' => 'btn btn-sm btn-secondary',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$converter = new GithubFlavoredMarkdownConverter($config);
|
||||
$converter
|
||||
->getEnvironment()
|
||||
->addExtension(new HighlightExtension(new Highlighter()->withGutter()))
|
||||
->addExtension(new FootnoteExtension())
|
||||
->addExtension(new DefaultAttributesExtension())
|
||||
->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...))
|
||||
;
|
||||
$converter = new GithubFlavoredMarkdownConverter();
|
||||
$converter->getEnvironment()->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...));
|
||||
return $converter->convert($content);
|
||||
}
|
||||
|
||||
@ -54,17 +32,11 @@ class MarkdownParser extends AbstractParser
|
||||
|
||||
$linkNodes = new Query()
|
||||
->where(Query::type(Link::class))
|
||||
->findAll($document)
|
||||
;
|
||||
->findAll($document);
|
||||
|
||||
/** @var Link $linkNode */
|
||||
foreach ($linkNodes as $linkNode) {
|
||||
$url = $linkNode->getUrl();
|
||||
|
||||
if (!is_numeric($url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$snip = $this->snipRepo->find($url);
|
||||
if ($snip === null) {
|
||||
continue;
|
||||
|
@ -3,11 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
{% if app.environment == 'dev' %}D{% endif %}
|
||||
{% if title is defined %}{{ title }}{% else %}SNIPS{% endif %}
|
||||
</title>
|
||||
<link rel="shortcut icon" type="image/jpg" href="/snips.png">
|
||||
<title>{% if title is defined %}{{ title }}{% else %}SNIPS{% endif %}</title>
|
||||
<link rel="shortcut icon" type="image/jpg" href="/favicon.png">
|
||||
|
||||
{% block css %}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||
@ -46,7 +43,9 @@
|
||||
{# javascript block #}
|
||||
{% block js %}
|
||||
<script src="https://kit.fontawesome.com/3471b6556e.js" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
|
||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||
crossorigin="anonymous"></script>
|
||||
|
@ -1,9 +1,6 @@
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark" style="z-index: 1;">
|
||||
<div class="container-fluid">
|
||||
<a title="Snips" class="navbar-brand" href="{{ path('home') }}">
|
||||
<img src="/snips.png" width="30" height="30" class="d-inline-block align-top rounded" alt="">
|
||||
SNIPS
|
||||
</a>
|
||||
<a title="Snips" class="navbar-brand" href="{{ path('home') }}">SNIPS</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
|
||||
aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
|
@ -1,56 +0,0 @@
|
||||
{% extends 'base/one.column.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
{% if app.user and app.user is same as(snip.createdBy) %}
|
||||
<a href="{{ path('snip_index') }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> Index
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ path('snip_public') }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> Index
|
||||
</a>
|
||||
{% endif %}
|
||||
{% block buttons %}{% endblock %}
|
||||
|
||||
<br><br>
|
||||
<div class="card" style="width: 100%;">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
<ul class="nav nav-tabs card-header-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active == 'single' %}active{% endif %}"
|
||||
href="{{ path('snip_single', {'snip': snip.id}) }}">View</a>
|
||||
</li>
|
||||
{% if is_granted('edit', snip) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active == 'edit' %}active{% endif %}"
|
||||
href="{{ path('snip_edit', {'snip': snip.id}) }}">Edit</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active == 'versions' %}active{% endif %}"
|
||||
href="{{ path('version_index', {'snip': snip.id}) }}">Versions</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<span>
|
||||
<span class="badge bg-secondary">
|
||||
<i class="fa fa-hashtag"></i> {{ snip.id }}
|
||||
</span>
|
||||
{% for tag in snip.tags %}
|
||||
<span class="badge bg-secondary">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
{{ include('user/badge.html.twig', {user: snip.createdBy}) }}
|
||||
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% block cardbody %}{% endblock %}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<p class="card-text text-muted">
|
||||
Current version: {{ snip.activeVersion.id }}
|
||||
{% if snip.activeVersion == snip.latestVersion %}(latest){% endif %}
|
||||
Created at {{ include('generic/datetime.badge.html.twig', {datetime: snip.activeVersion.id.dateTime}) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends 'base/one.column.html.twig' %}
|
||||
|
||||
{% set title = 'Create Snip' %}
|
||||
|
||||
{% block body %}
|
||||
{{ form(form) }}
|
||||
{% endblock %}
|
@ -1,23 +1,21 @@
|
||||
{% extends 'snip/base.html.twig' %}
|
||||
{% extends 'base/one.column.html.twig' %}
|
||||
|
||||
{% set title %}{{ snip }} - Edit{% endset %}
|
||||
{% set active = 'edit' %}
|
||||
{% if snip.id %}
|
||||
{% set title = 'Edit Snip ' ~ snip %}
|
||||
{% else %}
|
||||
{% set title = 'Create Snip' %}
|
||||
{% endif %}
|
||||
|
||||
{% block buttons %}
|
||||
{% if is_granted('edit', snip) %}
|
||||
<a href="{{ path('snip_archive', {snip: snip.id}) }}" class="btn btn-secondary">
|
||||
{% if snip.archived %}
|
||||
<i class="fa fa-undo"></i> Unarchive
|
||||
{% else %}
|
||||
<i class="fa fa-archive"></i> Archive
|
||||
{% endif %}
|
||||
</a>
|
||||
<a href="{{ path('snip_delete', {snip: snip.id}) }}" class="btn btn-danger">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
{% block body %}
|
||||
{% if snip.id %}
|
||||
<a href="{{ path('snip_single', {snip: snip.id}) }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
Back
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block cardbody %}
|
||||
<a href="{{ path('snip_index') }}" class="btn btn-info">
|
||||
<i class="fa fa-list"></i>
|
||||
Index
|
||||
</a><br><br>
|
||||
{{ form(form) }}
|
||||
{% endblock %}
|
@ -1,19 +1,79 @@
|
||||
{% extends 'snip/base.html.twig' %}
|
||||
{% extends 'base/one.column.html.twig' %}
|
||||
|
||||
{% set title %}{{ snip }} - View{% endset %}
|
||||
{% set active = 'single' %}
|
||||
{% set title %}Snip {{ snip }}{% endset %}
|
||||
|
||||
{% block buttons %}
|
||||
<a href="{{ path('snip_raw', {snip: snip.id}) }}" class="btn btn-info">
|
||||
{% block body %}
|
||||
{% if app.user %}
|
||||
<a href="{{ path('snip_index') }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> Back
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ path('snip_public') }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> Index
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if is_granted('edit', snip) %}
|
||||
<a class="btn btn-info" href="{{ path('version_index', {snip: snip.id}) }}">
|
||||
<i class="fa fa-history" aria-hidden="true"></i> Versions
|
||||
</a>
|
||||
<a class="btn btn-warning" href="{{ path('snip_edit', {snip: snip.id}) }}">
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i> Edit
|
||||
</a>
|
||||
<a href="{{ path('snip_archive', {snip: snip.id}) }}" class="btn btn-secondary">
|
||||
{% if snip.archived %}
|
||||
<i class="fa fa-undo"></i> Unarchive
|
||||
{% else %}
|
||||
<i class="fa fa-archive"></i> Archive
|
||||
{% endif %}
|
||||
</a>
|
||||
<a href="{{ path('snip_delete', {snip: snip.id}) }}" class="btn btn-danger">
|
||||
<i class="fa fa-trash"></i> Delete
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ path('snip_raw', {snip: snip.id}) }}" class="btn btn-primary">
|
||||
<i class="fa fa-eye"></i> Raw
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block cardbody %}
|
||||
{{ content|raw }}
|
||||
<br><br>
|
||||
<div class="card" style="width: 100%;">
|
||||
<h4 class="card-header d-flex justify-content-between">
|
||||
<span>
|
||||
{{ snip }} <small class="text-muted">#{{ snip.id }}</small>
|
||||
</span>
|
||||
<span>
|
||||
{% for tag in snip.tags %}
|
||||
<span class="badge bg-secondary">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
{{ include('user/badge.html.twig', {user: snip.createdBy}) }}
|
||||
{{ include('snip/badge.html.twig', {snip: snip}) }}
|
||||
</span>
|
||||
</h4>
|
||||
<div class="card-body">
|
||||
{{ content|raw }}
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<p class="card-text text-muted">
|
||||
Current version: {{ snip.activeVersion.id }}
|
||||
{% if snip.activeVersion == snip.latestVersion %}(latest){% endif %}
|
||||
Created at {{ include('generic/datetime.badge.html.twig', {datetime: snip.activeVersion.id.dateTime}) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<link rel="stylesheet" href="{{ asset('github-light-default.css') }}">
|
||||
<link rel="stylesheet"
|
||||
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
||||
<script>
|
||||
const codeBlocks = document.querySelectorAll('code.hljs');
|
||||
|
||||
codeBlocks.forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,3 +1 @@
|
||||
<span class="badge {% if user == app.user %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
<i class="fa fa-user"></i> {{ user }}
|
||||
</span>
|
||||
<span class="badge {% if user == app.user %}bg-success{% else %}bg-secondary{% endif %}">{{ user }}</span>
|
@ -1,28 +1,46 @@
|
||||
{% extends 'snip/base.html.twig' %}
|
||||
{% extends 'base/one.column.html.twig' %}
|
||||
|
||||
{% set title %}{{ snip }} - Versions{% endset %}
|
||||
{% set active = 'versions' %}
|
||||
{% set title = 'Snip ' ~ snip %}
|
||||
|
||||
{% block buttons %}
|
||||
{% block body %}
|
||||
<a href="{{ path('snip_single', {snip: snip.id}) }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> Back
|
||||
</a>
|
||||
<a href="{{ path('version_set', {version: snip.latestVersion.id, snip: snip.id}) }}" class="btn btn-warning">
|
||||
<i class="fa fa-refresh"></i> Latest
|
||||
</a>
|
||||
<a href="{{ path('content_compare', {to: snip.activeVersion.id}) }}" class="btn btn-info">
|
||||
<i class="fa fa-left-right"></i> Compare
|
||||
</a>
|
||||
<pre class="mermaid">
|
||||
flowchart BT
|
||||
{% for versionData in versions %}
|
||||
{{~ versionData ~}}
|
||||
{% endfor %}
|
||||
</pre>
|
||||
{% endblock %}
|
||||
|
||||
{% block cardbody %}
|
||||
<div class="list-group">
|
||||
{% for version in snip.snipContents|reverse %}
|
||||
<a class="list-group-item {% if version.id == snip.activeVersion.id %}list-group-item-success{% endif %} d-flex justify-content-between"
|
||||
href="{{ path('version_set', {version: version.id, snip: snip.id}) }}">
|
||||
<span>
|
||||
{{ include('generic/datetime.badge.html.twig', {datetime: version.id.dateTime}) }}
|
||||
{% if version.name %}{{ version.name }}{% endif %}
|
||||
</span>
|
||||
<span class="text-muted">{{ version.id }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11.6.0/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({startOnLoad: true});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
.node rect {
|
||||
fill: var(--bs-secondary) !important;
|
||||
stroke: var(--bs-secondary-text-emphasis) !important;
|
||||
}
|
||||
.node span {
|
||||
color: var(--bs-light) !important;
|
||||
}
|
||||
.active rect {
|
||||
fill: var(--bs-success) !important;
|
||||
stroke: var(--bs-success-text-emphasis) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user