Commit 9f5d4574 authored by Brett Walker's avatar Brett Walker Committed by Heinrich Lee Yu

Enable wiki table of contents tag for GFM

`[[_TOC_]]` now gets converted for normal GDM markdown
parent 4d6dc20d
---
title: Support for table of contents tag in GitLab Flavored Markdown
merge_request: 24196
author:
type: added
...@@ -25,12 +25,10 @@ module Banzai ...@@ -25,12 +25,10 @@ module Banzai
# * [[http://example.com/images/logo.png]] # * [[http://example.com/images/logo.png]]
# * [[http://example.com/images/logo.png|alt=Logo]] # * [[http://example.com/images/logo.png|alt=Logo]]
# #
# - Insert a Table of Contents list:
#
# * [[_TOC_]]
#
# Based on Gollum::Filter::Tags # Based on Gollum::Filter::Tags
# #
# Note: the table of contents tag is now handled by TableOfContentsTagFilter
#
# Context options: # Context options:
# :project_wiki (required) - Current project wiki. # :project_wiki (required) - Current project wiki.
# #
...@@ -64,23 +62,11 @@ module Banzai ...@@ -64,23 +62,11 @@ module Banzai
def call def call
doc.search(".//text()").each do |node| doc.search(".//text()").each do |node|
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
next unless node.content =~ TAGS_PATTERN
# A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running html = process_tag($1)
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it
# needs special-case handling
if toc_tag?(node)
process_toc_tag(node)
else
content = node.content
next unless content =~ TAGS_PATTERN
html = process_tag($1)
if html && html != node.content node.replace(html) if html && html != node.content
node.replace(html)
end
end
end end
doc doc
...@@ -88,12 +74,6 @@ module Banzai ...@@ -88,12 +74,6 @@ module Banzai
private private
# Replace an entire `[[<em>TOC</em>]]` node with the result generated by
# TableOfContentsFilter
def process_toc_tag(node)
node.parent.parent.replace(result[:toc].presence || '')
end
# Process a single tag into its final HTML form. # Process a single tag into its final HTML form.
# #
# tag - The String tag contents (the stuff inside the double brackets). # tag - The String tag contents (the stuff inside the double brackets).
...@@ -129,12 +109,6 @@ module Banzai ...@@ -129,12 +109,6 @@ module Banzai
end end
end end
def toc_tag?(node)
node.content == 'TOC' &&
node.parent.name == 'em' &&
node.parent.parent.text == '[[TOC]]'
end
def image?(path) def image?(path)
path =~ ALLOWED_IMAGE_EXTENSIONS path =~ ALLOWED_IMAGE_EXTENSIONS
end end
......
# frozen_string_literal: true
module Banzai
module Filter
# Using `[[_TOC_]]`, inserts a Table of Contents list.
# This syntax is based on the Gollum syntax. This way we have
# some consistency between with wiki and normal markdown.
# If there ever emerges a markdown standard, we can implement
# that here.
#
# The support for this has been removed from GollumTagsFilter
#
# Based on Banzai::Filter::GollumTagsFilter
class TableOfContentsTagFilter < HTML::Pipeline::Filter
TEXT_QUERY = %q(descendant-or-self::text()[ancestor::p and contains(., 'TOC')])
def call
return doc if context[:no_header_anchors]
doc.xpath(TEXT_QUERY).each do |node|
# A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it
# needs special-case handling
process_toc_tag(node) if toc_tag?(node)
end
doc
end
private
# Replace an entire `[[<em>TOC</em>]]` node with the result generated by
# TableOfContentsFilter
def process_toc_tag(node)
node.parent.parent.replace(result[:toc].presence || '')
end
def toc_tag?(node)
node.content == 'TOC' &&
node.parent.name == 'em' &&
node.parent.parent.text == '[[TOC]]'
end
end
end
end
...@@ -32,6 +32,7 @@ module Banzai ...@@ -32,6 +32,7 @@ module Banzai
Filter::InlineMetricsFilter, Filter::InlineMetricsFilter,
Filter::InlineGrafanaMetricsFilter, Filter::InlineGrafanaMetricsFilter,
Filter::TableOfContentsFilter, Filter::TableOfContentsFilter,
Filter::TableOfContentsTagFilter,
Filter::AutolinkFilter, Filter::AutolinkFilter,
Filter::ExternalLinkFilter, Filter::ExternalLinkFilter,
Filter::SuggestionFilter, Filter::SuggestionFilter,
......
...@@ -100,19 +100,4 @@ describe Banzai::Filter::GollumTagsFilter do ...@@ -100,19 +100,4 @@ describe Banzai::Filter::GollumTagsFilter do
expect(doc.at_css('code').text).to eq '[[link-in-backticks]]' expect(doc.at_css('code').text).to eq '[[link-in-backticks]]'
end end
end end
context 'table of contents' do
it 'replaces [[<em>TOC</em>]] with ToC result' do
doc = described_class.call("<p>[[<em>TOC</em>]]</p>", { project_wiki: project_wiki }, { toc: "FOO" })
expect(doc.to_html).to eq("FOO")
end
it 'handles an empty ToC result' do
input = "<p>[[<em>TOC</em>]]</p>"
doc = described_class.call(input, project_wiki: project_wiki)
expect(doc.to_html).to eq ''
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::Filter::TableOfContentsTagFilter do
include FilterSpecHelper
context 'table of contents' do
let(:html) { '<p>[[<em>TOC</em>]]</p>' }
it 'replaces [[<em>TOC</em>]] with ToC result' do
doc = filter(html, {}, { toc: "FOO" })
expect(doc.to_html).to eq("FOO")
end
it 'handles an empty ToC result' do
doc = filter(html)
expect(doc.to_html).to eq ''
end
end
end
...@@ -99,4 +99,35 @@ describe Banzai::Pipeline::FullPipeline do ...@@ -99,4 +99,35 @@ describe Banzai::Pipeline::FullPipeline do
end end
end end
end end
describe 'table of contents' do
let(:project) { create(:project, :public) }
let(:markdown) do
<<-MARKDOWN.strip_heredoc
[[_TOC_]]
# Header
MARKDOWN
end
let(:invalid_markdown) do
<<-MARKDOWN.strip_heredoc
test [[_TOC_]]
# Header
MARKDOWN
end
it 'inserts a table of contents' do
output = described_class.to_html(markdown, project: project)
expect(output).to include("<ul class=\"section-nav\">")
expect(output).to include("<li><a href=\"#header\">Header</a></li>")
end
it 'does not insert a table of contents' do
output = described_class.to_html(invalid_markdown, project: project)
expect(output).to include("test [[<em>TOC</em>]]")
end
end
end end
...@@ -15,7 +15,7 @@ module FilterSpecHelper ...@@ -15,7 +15,7 @@ module FilterSpecHelper
# context - Hash context for the filter. (default: {project: project}) # context - Hash context for the filter. (default: {project: project})
# #
# Returns a Nokogiri::XML::DocumentFragment # Returns a Nokogiri::XML::DocumentFragment
def filter(html, context = {}) def filter(html, context = {}, result = nil)
if defined?(project) if defined?(project)
context.reverse_merge!(project: project) context.reverse_merge!(project: project)
end end
...@@ -25,7 +25,7 @@ module FilterSpecHelper ...@@ -25,7 +25,7 @@ module FilterSpecHelper
context = context.merge(render_context: render_context) context = context.merge(render_context: render_context)
described_class.call(html, context) described_class.call(html, context, result)
end end
# Get an instance of the Filter class # Get an instance of the Filter class
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment