From 0cba81c0ecc71f411153b1f2a2952c07125217f9 Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Wed, 10 Jul 2019 10:30:10 +0200 Subject: Enable section anchors --- .../64070-asciidoctor-enable-section-anchors.yml | 5 ++ lib/banzai/filter/ascii_doc_sanitization_filter.rb | 30 +++++++++- lib/gitlab/asciidoc.rb | 1 + spec/lib/gitlab/asciidoc_spec.rb | 69 ++++++++++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml diff --git a/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml new file mode 100644 index 00000000000..51c1537a159 --- /dev/null +++ b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml @@ -0,0 +1,5 @@ +--- +title: "Enable section anchors in Asciidoctor" +merge_request: 30666 +author: Guillaume Grossetie +type: added \ No newline at end of file diff --git a/lib/banzai/filter/ascii_doc_sanitization_filter.rb b/lib/banzai/filter/ascii_doc_sanitization_filter.rb index a78bb60103c..d8d63075752 100644 --- a/lib/banzai/filter/ascii_doc_sanitization_filter.rb +++ b/lib/banzai/filter/ascii_doc_sanitization_filter.rb @@ -6,6 +6,9 @@ module Banzai # # Extends Banzai::Filter::BaseSanitizationFilter with specific rules. class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter + # Section anchor link pattern + SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze + # Classes used by Asciidoctor to style components ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze CALLOUT_CLASSES = ['conum'].freeze @@ -19,14 +22,17 @@ module Banzai td: ['icon'].freeze, i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES, ul: LIST_CLASSES, - ol: LIST_CLASSES + ol: LIST_CLASSES, + a: ['anchor'].freeze }.freeze + ALLOWED_HEADERS = %w(h2 h3 h4 h5 h6).freeze + def customize_whitelist(whitelist) # Allow marks whitelist[:elements].push('mark') - # Allow any classes in `span`, `i`, `div`, `td`, `ul` and `ol` elements + # Allow any classes in `span`, `i`, `div`, `td`, `ul`, `ol` and `a` elements # but then remove any unknown classes whitelist[:attributes]['span'] = %w(class) whitelist[:attributes]['div'].push('class') @@ -34,12 +40,32 @@ module Banzai whitelist[:attributes]['i'] = %w(class) whitelist[:attributes]['ul'] = %w(class) whitelist[:attributes]['ol'] = %w(class) + whitelist[:attributes]['a'].push('class') whitelist[:transformers].push(self.class.remove_element_classes) + # Allow `id` in heading elements for section anchors + ALLOWED_HEADERS.each do |header| + whitelist[:attributes][header] = %w(id) + end + whitelist[:transformers].push(self.class.remove_non_heading_ids) + whitelist end class << self + def remove_non_heading_ids + lambda do |env| + node = env[:node] + + return unless ALLOWED_HEADERS.any?(node.name) + return unless node.has_attribute?('id') + + return if node['id'] =~ SECTION_LINK_REF_PATTERN + + node.remove_attribute('id') + end + end + def remove_element_classes lambda do |env| node = env[:node] diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index 00c87cce7b6..da65caa6c9c 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -13,6 +13,7 @@ module Gitlab MAX_INCLUDE_DEPTH = 5 DEFAULT_ADOC_ATTRS = { 'showtitle' => true, + 'sectanchors' => true, 'idprefix' => 'user-content-', 'idseparator' => '-', 'env' => 'gitlab', diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index ff002acbd35..5293732ead1 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -96,6 +96,75 @@ module Gitlab end end + context 'with passthrough' do + it 'removes non heading ids' do + input = <<~ADOC + ++++ +

Title

+ ++++ + ADOC + + output = <<~HTML +

Title

+ HTML + + expect(render(input, context)).to include(output.strip) + end + end + + context 'with section anchors' do + it 'preserves ids and links' do + input = <<~ADOC + = Title + + == First section + + This is the first section. + + == Second section + + This is the second section. + + == Thunder ⚡ ! + + This is the third section. + ADOC + + output = <<~HTML +

Title

+
+

+ First section

+
+
+

This is the first section.

+
+
+
+
+

+ Second section

+
+
+

This is the second section.

+
+
+
+
+

+ Thunder ⚡ !

+
+
+

This is the third section.

+
+
+
+ HTML + + expect(render(input, context)).to include(output.strip) + end + end + context 'with checklist' do it 'preserves classes' do input = <<~ADOC -- cgit v1.2.1