Preserve footnote link ids
This commit is contained in:
parent
1e99c1b0a7
commit
34c6f2723c
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: "Preserve footnote link ids in Asciidoctor"
|
||||||
|
merge_request: 30790
|
||||||
|
author: Guillaume Grossetie
|
||||||
|
type: added
|
|
@ -8,12 +8,18 @@ module Banzai
|
||||||
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
|
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
|
||||||
# Section anchor link pattern
|
# Section anchor link pattern
|
||||||
SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
|
SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
|
||||||
|
SECTION_HEADINGS = %w(h2 h3 h4 h5 h6).freeze
|
||||||
|
|
||||||
|
# Footnote link patterns
|
||||||
|
FOOTNOTE_LINK_ID_PATTERNS = {
|
||||||
|
a: /\A_footnoteref_\d+\z/,
|
||||||
|
div: /\A_footnotedef_\d+\z/
|
||||||
|
}.freeze
|
||||||
|
|
||||||
# Classes used by Asciidoctor to style components
|
# Classes used by Asciidoctor to style components
|
||||||
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
|
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
|
||||||
CALLOUT_CLASSES = ['conum'].freeze
|
CALLOUT_CLASSES = ['conum'].freeze
|
||||||
CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
|
CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
|
||||||
|
|
||||||
LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
|
LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
|
||||||
|
|
||||||
ELEMENT_CLASSES_WHITELIST = {
|
ELEMENT_CLASSES_WHITELIST = {
|
||||||
|
@ -26,8 +32,6 @@ module Banzai
|
||||||
a: ['anchor'].freeze
|
a: ['anchor'].freeze
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
ALLOWED_HEADERS = %w(h2 h3 h4 h5 h6).freeze
|
|
||||||
|
|
||||||
def customize_whitelist(whitelist)
|
def customize_whitelist(whitelist)
|
||||||
# Allow marks
|
# Allow marks
|
||||||
whitelist[:elements].push('mark')
|
whitelist[:elements].push('mark')
|
||||||
|
@ -44,20 +48,39 @@ module Banzai
|
||||||
whitelist[:transformers].push(self.class.remove_element_classes)
|
whitelist[:transformers].push(self.class.remove_element_classes)
|
||||||
|
|
||||||
# Allow `id` in heading elements for section anchors
|
# Allow `id` in heading elements for section anchors
|
||||||
ALLOWED_HEADERS.each do |header|
|
SECTION_HEADINGS.each do |header|
|
||||||
whitelist[:attributes][header] = %w(id)
|
whitelist[:attributes][header] = %w(id)
|
||||||
end
|
end
|
||||||
whitelist[:transformers].push(self.class.remove_non_heading_ids)
|
whitelist[:transformers].push(self.class.remove_non_heading_ids)
|
||||||
|
|
||||||
|
# Allow `id` in footnote elements
|
||||||
|
FOOTNOTE_LINK_ID_PATTERNS.keys.each do |element|
|
||||||
|
whitelist[:attributes][element.to_s].push('id')
|
||||||
|
end
|
||||||
|
whitelist[:transformers].push(self.class.remove_non_footnote_ids)
|
||||||
|
|
||||||
whitelist
|
whitelist
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
def remove_non_footnote_ids
|
||||||
|
lambda do |env|
|
||||||
|
node = env[:node]
|
||||||
|
|
||||||
|
return unless (pattern = FOOTNOTE_LINK_ID_PATTERNS[node.name.to_sym])
|
||||||
|
return unless node.has_attribute?('id')
|
||||||
|
|
||||||
|
return if node['id'] =~ pattern
|
||||||
|
|
||||||
|
node.remove_attribute('id')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def remove_non_heading_ids
|
def remove_non_heading_ids
|
||||||
lambda do |env|
|
lambda do |env|
|
||||||
node = env[:node]
|
node = env[:node]
|
||||||
|
|
||||||
return unless ALLOWED_HEADERS.any?(node.name)
|
return unless SECTION_HEADINGS.any?(node.name)
|
||||||
return unless node.has_attribute?('id')
|
return unless node.has_attribute?('id')
|
||||||
|
|
||||||
return if node['id'] =~ SECTION_LINK_REF_PATTERN
|
return if node['id'] =~ SECTION_LINK_REF_PATTERN
|
||||||
|
|
|
@ -110,6 +110,56 @@ module Gitlab
|
||||||
|
|
||||||
expect(render(input, context)).to include(output.strip)
|
expect(render(input, context)).to include(output.strip)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'removes non footnote def ids' do
|
||||||
|
input = <<~ADOC
|
||||||
|
++++
|
||||||
|
<div id="def">Footnote definition</div>
|
||||||
|
++++
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<div>Footnote definition</div>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes non footnote ref ids' do
|
||||||
|
input = <<~ADOC
|
||||||
|
++++
|
||||||
|
<a id="ref">Footnote reference</a>
|
||||||
|
++++
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<a>Footnote reference</a>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with footnotes' do
|
||||||
|
it 'preserves ids and links' do
|
||||||
|
input = <<~ADOC
|
||||||
|
This paragraph has a footnote.footnote:[This is the text of the footnote.]
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<div>
|
||||||
|
<p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<hr>
|
||||||
|
<div id="_footnotedef_1">
|
||||||
|
<a href="#_footnoteref_1">1</a>. This is the text of the footnote.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with section anchors' do
|
context 'with section anchors' do
|
||||||
|
|
Loading…
Reference in New Issue