From b6d6f426221f4e6787624d3526d1d91803c99d2d Mon Sep 17 00:00:00 2001 From: MrSpoocy Date: Fri, 21 Oct 2022 11:12:37 +0200 Subject: [PATCH 1/4] Allows to append the anchor to the headline --- docs/2.3/extensions/heading-permalinks.md | 10 +++++ .../HeadingPermalink/HeadingPermalink.php | 23 +++++++++++- .../HeadingPermalinkExtension.php | 2 + .../HeadingPermalinkProcessor.php | 20 ++++++++-- .../HeadingPermalinkRenderer.php | 12 +++--- .../HeadingPermalinkExtensionTest.php | 37 +++++++++++++++++++ 6 files changed, 92 insertions(+), 12 deletions(-) diff --git a/docs/2.3/extensions/heading-permalinks.md b/docs/2.3/extensions/heading-permalinks.md index 937fc5c2f8..e016270b47 100644 --- a/docs/2.3/extensions/heading-permalinks.md +++ b/docs/2.3/extensions/heading-permalinks.md @@ -38,6 +38,8 @@ $config = [ 'heading_permalink' => [ 'html_class' => 'heading-permalink', 'id_prefix' => 'content', + 'attach_heading' => false, + 'heading_class' => 'heading-anchor' 'fragment_prefix' => 'content', 'insert' => 'before', 'min_heading_level' => 1, @@ -72,6 +74,14 @@ The value of this nested configuration option should be a `string` that you want This should be a `string` you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (`-`) will be added between the prefix and the ID. You can instead set this to an empty string (`''`) if you don't want a prefix. +### `attach_heading` (since 2.3.6) + +If this value is `true`, the `id` attributes will be written to the tag instead of the . + +### `heading_class` (since 2.3.6) + +If attach_heading is `true`, this class is added to the tag. + ### `fragment_prefix` This should be a `string` you want prepended to the URL fragment in the link's `href` attribute. **This should typically be set to the same value as `id_prefix` for links to work properly.** However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to "rewrite" them. diff --git a/src/Extension/HeadingPermalink/HeadingPermalink.php b/src/Extension/HeadingPermalink/HeadingPermalink.php index df9bded01e..0b1d6e9758 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalink.php +++ b/src/Extension/HeadingPermalink/HeadingPermalink.php @@ -13,6 +13,7 @@ namespace League\CommonMark\Extension\HeadingPermalink; +use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Node\Inline\AbstractInline; /** @@ -23,15 +24,33 @@ final class HeadingPermalink extends AbstractInline /** @psalm-readonly */ private string $slug; - public function __construct(string $slug) + /** @psalm-readonly */ + private bool $attachedHeading; + + /** @psalm-readonly */ + private string $idPrefix; + + public function __construct(string $slug, string $idPrefix, bool $attachedHeading) { parent::__construct(); - $this->slug = $slug; + $this->slug = $slug; + $this->idPrefix = $idPrefix; + $this->attachedHeading = $attachedHeading; } public function getSlug(): string { return $this->slug; } + + public function getIdPrefix(): string + { + return $this->idPrefix; + } + + public function isAttachedHeading(): bool + { + return $this->attachedHeading; + } } diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php b/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php index 1f7d386407..ecddd9d4c3 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php @@ -31,6 +31,8 @@ public function configureSchema(ConfigurationBuilderInterface $builder): void 'max_heading_level' => Expect::int()->min(1)->max(6)->default(6), 'insert' => Expect::anyOf(HeadingPermalinkProcessor::INSERT_BEFORE, HeadingPermalinkProcessor::INSERT_AFTER)->default(HeadingPermalinkProcessor::INSERT_BEFORE), 'id_prefix' => Expect::string()->default('content'), + 'attach_heading' => Expect::bool()->default(false), + 'heading_class' => Expect::string()->default('heading-anchor'), 'fragment_prefix' => Expect::string()->default('content'), 'html_class' => Expect::string()->default('heading-permalink'), 'title' => Expect::string()->default('Permalink'), diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php index d0c7aadfd5..f0d010696e 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php @@ -48,17 +48,22 @@ public function __invoke(DocumentParsedEvent $e): void { $min = (int) $this->config->get('heading_permalink/min_heading_level'); $max = (int) $this->config->get('heading_permalink/max_heading_level'); - + $attachHeading = (bool) $this->config->get('heading_permalink/attach_heading'); + $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); + if ($idPrefix !== '') { + $idPrefix .= '-'; + } $slugLength = (int) $this->config->get('slug_normalizer/max_length'); + $headingClass = $this->config->get('heading_permalink/heading_class'); foreach ($e->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if ($node instanceof Heading && $node->getLevel() >= $min && $node->getLevel() <= $max) { - $this->addHeadingLink($node, $slugLength); + $this->addHeadingLink($node, $slugLength, $idPrefix, $attachHeading, $headingClass); } } } - private function addHeadingLink(Heading $heading, int $slugLength): void + private function addHeadingLink(Heading $heading, int $slugLength, string $idPrefix, bool $attachHeading, string $headingClass): void { $text = StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]); $slug = $this->slugNormalizer->normalize($text, [ @@ -66,7 +71,14 @@ private function addHeadingLink(Heading $heading, int $slugLength): void 'length' => $slugLength, ]); - $headingLinkAnchor = new HeadingPermalink($slug); + if ($attachHeading) { + $heading->data->set('attributes/id', $idPrefix . $slug); + if ('' !== $headingClass) { + $heading->data->append('attributes/class', $this->config->get('heading_permalink/heading_class')); + } + } + + $headingLinkAnchor = new HeadingPermalink($slug, $idPrefix, $attachHeading); switch ($this->config->get('heading_permalink/insert')) { case self::INSERT_BEFORE: diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php index 7cc872e526..7ada8a4ed3 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php @@ -48,11 +48,7 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \ HeadingPermalink::assertInstanceOf($node); $slug = $node->getSlug(); - - $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); - if ($idPrefix !== '') { - $idPrefix .= '-'; - } + $idPrefix = $node->getIdPrefix(); $fragmentPrefix = (string) $this->config->get('heading_permalink/fragment_prefix'); if ($fragmentPrefix !== '') { @@ -60,7 +56,11 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \ } $attrs = $node->data->getData('attributes'); - $attrs->set('id', $idPrefix . $slug); + + if (!$node->isAttachedHeading()) { + $attrs->set('id', $idPrefix . $slug); + } + $attrs->set('href', '#' . $fragmentPrefix . $slug); $attrs->append('class', $this->config->get('heading_permalink/html_class')); diff --git a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php index 94e77d730a..ccf182e3a0 100644 --- a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php +++ b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php @@ -164,6 +164,43 @@ public function testWithCustomLevels(): void $this->assertEquals($expected, \trim((string) $converter->convert($input))); } + public function testHeadingPermalinksWithAttachHeading(): void + { + $environment = new Environment([ + 'heading_permalink' => [ + 'attach_heading' => true, + ], + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new HeadingPermalinkExtension()); + + $converter = new MarkdownConverter($environment); + + $input = '# Hello World!'; + $expected = \sprintf('

Hello World!

', HeadingPermalinkRenderer::DEFAULT_SYMBOL); + + $this->assertEquals($expected, \trim((string) $converter->convert($input))); + } + + public function testHeadingPermalinksWithAttachHeadingAndEmptyClass(): void + { + $environment = new Environment([ + 'heading_permalink' => [ + 'attach_heading' => true, + 'heading_class' => '' + ], + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new HeadingPermalinkExtension()); + + $converter = new MarkdownConverter($environment); + + $input = '# Hello World!'; + $expected = \sprintf('

Hello World!

', HeadingPermalinkRenderer::DEFAULT_SYMBOL); + + $this->assertEquals($expected, \trim((string) $converter->convert($input))); + } + public function testXml(): void { $md = '# Hello *World*'; From ffc11a4cc35e1d5e79c144210068a2ddf53f3636 Mon Sep 17 00:00:00 2001 From: MrSpoocy Date: Fri, 21 Oct 2022 14:25:07 +0200 Subject: [PATCH 2/4] fix BC & phpcs --- .../HeadingPermalink/HeadingPermalink.php | 12 +----------- .../HeadingPermalinkProcessor.php | 15 ++++++++------- .../HeadingPermalink/HeadingPermalinkRenderer.php | 9 +++++++-- .../HeadingPermalinkExtensionTest.php | 2 +- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Extension/HeadingPermalink/HeadingPermalink.php b/src/Extension/HeadingPermalink/HeadingPermalink.php index 0b1d6e9758..6cdff4c68f 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalink.php +++ b/src/Extension/HeadingPermalink/HeadingPermalink.php @@ -13,7 +13,6 @@ namespace League\CommonMark\Extension\HeadingPermalink; -use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Node\Inline\AbstractInline; /** @@ -27,15 +26,11 @@ final class HeadingPermalink extends AbstractInline /** @psalm-readonly */ private bool $attachedHeading; - /** @psalm-readonly */ - private string $idPrefix; - - public function __construct(string $slug, string $idPrefix, bool $attachedHeading) + public function __construct(string $slug, bool $attachedHeading = false) { parent::__construct(); $this->slug = $slug; - $this->idPrefix = $idPrefix; $this->attachedHeading = $attachedHeading; } @@ -44,11 +39,6 @@ public function getSlug(): string return $this->slug; } - public function getIdPrefix(): string - { - return $this->idPrefix; - } - public function isAttachedHeading(): bool { return $this->attachedHeading; diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php index f0d010696e..a0c6d96982 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php @@ -46,15 +46,16 @@ public function setEnvironment(EnvironmentInterface $environment): void public function __invoke(DocumentParsedEvent $e): void { - $min = (int) $this->config->get('heading_permalink/min_heading_level'); - $max = (int) $this->config->get('heading_permalink/max_heading_level'); + $min = (int) $this->config->get('heading_permalink/min_heading_level'); + $max = (int) $this->config->get('heading_permalink/max_heading_level'); $attachHeading = (bool) $this->config->get('heading_permalink/attach_heading'); - $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); + $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); + $slugLength = (int) $this->config->get('slug_normalizer/max_length'); + $headingClass = $this->config->get('heading_permalink/heading_class'); + if ($idPrefix !== '') { $idPrefix .= '-'; } - $slugLength = (int) $this->config->get('slug_normalizer/max_length'); - $headingClass = $this->config->get('heading_permalink/heading_class'); foreach ($e->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if ($node instanceof Heading && $node->getLevel() >= $min && $node->getLevel() <= $max) { @@ -73,12 +74,12 @@ private function addHeadingLink(Heading $heading, int $slugLength, string $idPre if ($attachHeading) { $heading->data->set('attributes/id', $idPrefix . $slug); - if ('' !== $headingClass) { + if ($headingClass !== '') { $heading->data->append('attributes/class', $this->config->get('heading_permalink/heading_class')); } } - $headingLinkAnchor = new HeadingPermalink($slug, $idPrefix, $attachHeading); + $headingLinkAnchor = new HeadingPermalink($slug, $attachHeading); switch ($this->config->get('heading_permalink/insert')) { case self::INSERT_BEFORE: diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php index 7ada8a4ed3..7553eb2bae 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php @@ -48,7 +48,6 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \ HeadingPermalink::assertInstanceOf($node); $slug = $node->getSlug(); - $idPrefix = $node->getIdPrefix(); $fragmentPrefix = (string) $this->config->get('heading_permalink/fragment_prefix'); if ($fragmentPrefix !== '') { @@ -57,7 +56,13 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \ $attrs = $node->data->getData('attributes'); - if (!$node->isAttachedHeading()) { + if (! $node->isAttachedHeading()) { + $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); + + if ($idPrefix !== '') { + $idPrefix .= '-'; + } + $attrs->set('id', $idPrefix . $slug); } diff --git a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php index ccf182e3a0..2920c827f3 100644 --- a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php +++ b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php @@ -187,7 +187,7 @@ public function testHeadingPermalinksWithAttachHeadingAndEmptyClass(): void $environment = new Environment([ 'heading_permalink' => [ 'attach_heading' => true, - 'heading_class' => '' + 'heading_class' => '', ], ]); $environment->addExtension(new CommonMarkCoreExtension()); From 006a0d62f0babeb4c177d55947e487bb50493548 Mon Sep 17 00:00:00 2001 From: MrSpoocy Date: Fri, 21 Oct 2022 17:06:39 +0200 Subject: [PATCH 3/4] apply change request --- docs/2.3/extensions/heading-permalinks.md | 10 ------ docs/2.4/extensions/heading-permalinks.md | 12 ++++++- .../HeadingPermalink/HeadingPermalink.php | 13 ++----- .../HeadingPermalinkExtension.php | 6 ++-- .../HeadingPermalinkProcessor.php | 32 +++++++++-------- .../HeadingPermalinkRenderer.php | 5 +-- .../HeadingPermalinkExtensionTest.php | 35 +++++++++++++++---- 7 files changed, 65 insertions(+), 48 deletions(-) diff --git a/docs/2.3/extensions/heading-permalinks.md b/docs/2.3/extensions/heading-permalinks.md index e016270b47..937fc5c2f8 100644 --- a/docs/2.3/extensions/heading-permalinks.md +++ b/docs/2.3/extensions/heading-permalinks.md @@ -38,8 +38,6 @@ $config = [ 'heading_permalink' => [ 'html_class' => 'heading-permalink', 'id_prefix' => 'content', - 'attach_heading' => false, - 'heading_class' => 'heading-anchor' 'fragment_prefix' => 'content', 'insert' => 'before', 'min_heading_level' => 1, @@ -74,14 +72,6 @@ The value of this nested configuration option should be a `string` that you want This should be a `string` you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (`-`) will be added between the prefix and the ID. You can instead set this to an empty string (`''`) if you don't want a prefix. -### `attach_heading` (since 2.3.6) - -If this value is `true`, the `id` attributes will be written to the tag instead of the . - -### `heading_class` (since 2.3.6) - -If attach_heading is `true`, this class is added to the tag. - ### `fragment_prefix` This should be a `string` you want prepended to the URL fragment in the link's `href` attribute. **This should typically be set to the same value as `id_prefix` for links to work properly.** However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to "rewrite" them. diff --git a/docs/2.4/extensions/heading-permalinks.md b/docs/2.4/extensions/heading-permalinks.md index 1f29b98261..576e3b5fc8 100644 --- a/docs/2.4/extensions/heading-permalinks.md +++ b/docs/2.4/extensions/heading-permalinks.md @@ -38,6 +38,8 @@ $config = [ 'heading_permalink' => [ 'html_class' => 'heading-permalink', 'id_prefix' => 'content', + 'apply_id_to_heading' => false, + 'heading_class' => '' 'fragment_prefix' => 'content', 'insert' => 'before', 'min_heading_level' => 1, @@ -72,6 +74,14 @@ The value of this nested configuration option should be a `string` that you want This should be a `string` you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (`-`) will be added between the prefix and the ID. You can instead set this to an empty string (`''`) if you don't want a prefix. +### `apply_id_to_heading` + +If this value is `true`, the `id` attributes will be written to the `` tag instead of the ``. + +### `heading_class` + +The class will be added to the `` tag (no matter if `apply_id_to_heading` is set true or false) + ### `fragment_prefix` This should be a `string` you want prepended to the URL fragment in the link's `href` attribute. **This should typically be set to the same value as `id_prefix` for links to work properly.** However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to "rewrite" them. @@ -94,7 +104,7 @@ if (window.location.hash) { ### `insert` -This controls whether the anchor is added to the beginning of the `

`, `

` etc. tag or to the end. Can be set to either `'before'` or `'after'`. +This controls whether the anchor is added to the beginning of the heading tag (`before`), the end of the tag (`after`), or not added at all (`none`). ### `min_heading_level` and `max_heading_level` diff --git a/src/Extension/HeadingPermalink/HeadingPermalink.php b/src/Extension/HeadingPermalink/HeadingPermalink.php index 6cdff4c68f..df9bded01e 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalink.php +++ b/src/Extension/HeadingPermalink/HeadingPermalink.php @@ -23,24 +23,15 @@ final class HeadingPermalink extends AbstractInline /** @psalm-readonly */ private string $slug; - /** @psalm-readonly */ - private bool $attachedHeading; - - public function __construct(string $slug, bool $attachedHeading = false) + public function __construct(string $slug) { parent::__construct(); - $this->slug = $slug; - $this->attachedHeading = $attachedHeading; + $this->slug = $slug; } public function getSlug(): string { return $this->slug; } - - public function isAttachedHeading(): bool - { - return $this->attachedHeading; - } } diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php b/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php index ecddd9d4c3..96473a2987 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php @@ -29,10 +29,10 @@ public function configureSchema(ConfigurationBuilderInterface $builder): void $builder->addSchema('heading_permalink', Expect::structure([ 'min_heading_level' => Expect::int()->min(1)->max(6)->default(1), 'max_heading_level' => Expect::int()->min(1)->max(6)->default(6), - 'insert' => Expect::anyOf(HeadingPermalinkProcessor::INSERT_BEFORE, HeadingPermalinkProcessor::INSERT_AFTER)->default(HeadingPermalinkProcessor::INSERT_BEFORE), + 'insert' => Expect::anyOf(HeadingPermalinkProcessor::INSERT_BEFORE, HeadingPermalinkProcessor::INSERT_AFTER, HeadingPermalinkProcessor::INSERT_NONE)->default(HeadingPermalinkProcessor::INSERT_BEFORE), 'id_prefix' => Expect::string()->default('content'), - 'attach_heading' => Expect::bool()->default(false), - 'heading_class' => Expect::string()->default('heading-anchor'), + 'apply_id_to_heading' => Expect::bool()->default(false), + 'heading_class' => Expect::string()->default(''), 'fragment_prefix' => Expect::string()->default('content'), 'html_class' => Expect::string()->default('heading-permalink'), 'title' => Expect::string()->default('Permalink'), diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php index a0c6d96982..871aa21e26 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php @@ -31,6 +31,7 @@ final class HeadingPermalinkProcessor implements EnvironmentAwareInterface { public const INSERT_BEFORE = 'before'; public const INSERT_AFTER = 'after'; + public const INSERT_NONE = 'none'; /** @psalm-readonly-allow-private-mutation */ private TextNormalizerInterface $slugNormalizer; @@ -46,12 +47,12 @@ public function setEnvironment(EnvironmentInterface $environment): void public function __invoke(DocumentParsedEvent $e): void { - $min = (int) $this->config->get('heading_permalink/min_heading_level'); - $max = (int) $this->config->get('heading_permalink/max_heading_level'); - $attachHeading = (bool) $this->config->get('heading_permalink/attach_heading'); - $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); - $slugLength = (int) $this->config->get('slug_normalizer/max_length'); - $headingClass = $this->config->get('heading_permalink/heading_class'); + $min = (int) $this->config->get('heading_permalink/min_heading_level'); + $max = (int) $this->config->get('heading_permalink/max_heading_level'); + $applyToHeading = (bool) $this->config->get('heading_permalink/apply_id_to_heading'); + $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); + $slugLength = (int) $this->config->get('slug_normalizer/max_length'); + $headingClass = (string) $this->config->get('heading_permalink/heading_class'); if ($idPrefix !== '') { $idPrefix .= '-'; @@ -59,12 +60,12 @@ public function __invoke(DocumentParsedEvent $e): void foreach ($e->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if ($node instanceof Heading && $node->getLevel() >= $min && $node->getLevel() <= $max) { - $this->addHeadingLink($node, $slugLength, $idPrefix, $attachHeading, $headingClass); + $this->addHeadingLink($node, $slugLength, $idPrefix, $applyToHeading, $headingClass); } } } - private function addHeadingLink(Heading $heading, int $slugLength, string $idPrefix, bool $attachHeading, string $headingClass): void + private function addHeadingLink(Heading $heading, int $slugLength, string $idPrefix, bool $applyToHeading, string $headingClass): void { $text = StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]); $slug = $this->slugNormalizer->normalize($text, [ @@ -72,14 +73,15 @@ private function addHeadingLink(Heading $heading, int $slugLength, string $idPre 'length' => $slugLength, ]); - if ($attachHeading) { + if ($applyToHeading) { $heading->data->set('attributes/id', $idPrefix . $slug); - if ($headingClass !== '') { - $heading->data->append('attributes/class', $this->config->get('heading_permalink/heading_class')); - } } - $headingLinkAnchor = new HeadingPermalink($slug, $attachHeading); + if ($headingClass !== '') { + $heading->data->append('attributes/class', $headingClass); + } + + $headingLinkAnchor = new HeadingPermalink($slug); switch ($this->config->get('heading_permalink/insert')) { case self::INSERT_BEFORE: @@ -89,9 +91,11 @@ private function addHeadingLink(Heading $heading, int $slugLength, string $idPre case self::INSERT_AFTER: $heading->appendChild($headingLinkAnchor); + return; + case self::INSERT_NONE: return; default: - throw new InvalidConfigurationException("Invalid configuration value for heading_permalink/insert; expected 'before' or 'after'"); + throw new InvalidConfigurationException("Invalid configuration value for heading_permalink/insert; expected 'before', 'after', or 'none'"); } } } diff --git a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php index 7553eb2bae..59a86a136b 100644 --- a/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php +++ b/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php @@ -54,9 +54,10 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \ $fragmentPrefix .= '-'; } - $attrs = $node->data->getData('attributes'); + $attrs = $node->data->getData('attributes'); + $appendId = ! $this->config->get('heading_permalink/apply_id_to_heading'); - if (! $node->isAttachedHeading()) { + if ($appendId) { $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); if ($idPrefix !== '') { diff --git a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php index 2920c827f3..8bf772073d 100644 --- a/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php +++ b/tests/functional/Extension/HeadingPermalink/HeadingPermalinkExtensionTest.php @@ -16,6 +16,7 @@ use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension; +use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkProcessor; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer; use League\CommonMark\MarkdownConverter; use League\CommonMark\Parser\MarkdownParser; @@ -60,7 +61,7 @@ public function testHeadingPermalinksWithCustomOptions(string $input, string $ex 'fragment_prefix' => 'custom-fragment-prefix', // Ensure multiple characters are allowed (including multibyte) and special HTML characters are escaped. 'symbol' => '¶ 🦄️ <3 You', - 'insert' => 'after', + 'insert' => HeadingPermalinkProcessor::INSERT_AFTER, 'title' => 'Link', 'aria_hidden' => false, ], @@ -164,11 +165,30 @@ public function testWithCustomLevels(): void $this->assertEquals($expected, \trim((string) $converter->convert($input))); } - public function testHeadingPermalinksWithAttachHeading(): void + public function testHeadingPermalinksWithApplyIdToHeading(): void { $environment = new Environment([ 'heading_permalink' => [ - 'attach_heading' => true, + 'apply_id_to_heading' => true, + ], + ]); + $environment->addExtension(new CommonMarkCoreExtension()); + $environment->addExtension(new HeadingPermalinkExtension()); + + $converter = new MarkdownConverter($environment); + + $input = '# Hello World!'; + $expected = \sprintf('

Hello World!

', HeadingPermalinkRenderer::DEFAULT_SYMBOL); + + $this->assertEquals($expected, \trim((string) $converter->convert($input))); + } + + public function testHeadingPermalinksWithApplyIdToHeadingAndClass(): void + { + $environment = new Environment([ + 'heading_permalink' => [ + 'apply_id_to_heading' => true, + 'heading_class' => 'heading-anchor', ], ]); $environment->addExtension(new CommonMarkCoreExtension()); @@ -182,12 +202,13 @@ public function testHeadingPermalinksWithAttachHeading(): void $this->assertEquals($expected, \trim((string) $converter->convert($input))); } - public function testHeadingPermalinksWithAttachHeadingAndEmptyClass(): void + public function testHeadingPermalinksWithApplyIdToHeadingWithoutLink(): void { $environment = new Environment([ 'heading_permalink' => [ - 'attach_heading' => true, - 'heading_class' => '', + 'insert' => HeadingPermalinkProcessor::INSERT_NONE, + 'apply_id_to_heading' => true, + 'heading_class' => 'heading-anchor', ], ]); $environment->addExtension(new CommonMarkCoreExtension()); @@ -196,7 +217,7 @@ public function testHeadingPermalinksWithAttachHeadingAndEmptyClass(): void $converter = new MarkdownConverter($environment); $input = '# Hello World!'; - $expected = \sprintf('

Hello World!

', HeadingPermalinkRenderer::DEFAULT_SYMBOL); + $expected = '

Hello World!

'; $this->assertEquals($expected, \trim((string) $converter->convert($input))); } From 446fc112486789ee8a26fd6926852256bc79e5b9 Mon Sep 17 00:00:00 2001 From: Colin O'Dell Date: Sun, 30 Oct 2022 09:36:44 -0400 Subject: [PATCH 4/4] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6544ed9cb..d47df73dc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) princi - `MissingDependencyException` - `NoMatchingRendererException` - `ParserLogicException` +- Added more configuration options to the Heading Permalinks extension (#939): + - `heading_permalink/apply_id_to_heading` - When `true`, the `id` attribute will be applied to the heading element itself instead of the `` tag + - `heading_permalink/heading_class` - class to apply to the heading element + - `heading_permalink/insert` - now accepts `none` to prevent the creation of the `` link ### Changed