Commit b2c70230 authored by Jan Provaznik's avatar Jan Provaznik

Remove Redcarpet markdown engine

This engine was replaced with CommonMarker in 11.4, it was deprecated
since then.
parent b9f0eff9
......@@ -116,7 +116,6 @@ gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.2.0'
gem 'gitlab-markup', '~> 1.6.5'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.0'
......
......@@ -682,7 +682,6 @@ GEM
recaptcha (3.0.0)
json
recursive-open-struct (1.1.0)
redcarpet (3.4.0)
redis (3.3.5)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
......@@ -1118,7 +1117,6 @@ DEPENDENCIES
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
redcarpet (~> 3.4)
redis (~> 3.2)
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
......
......@@ -28,16 +28,13 @@ MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function($form) {
var mdText;
var markdownVersion;
var url;
var preview = $form.find('.js-md-preview');
var url = preview.data('url');
if (preview.hasClass('md-preview-loading')) {
return;
}
mdText = $form.find('textarea.markdown-area').val();
markdownVersion = $form.attr('data-markdown-version');
url = this.versionedPreviewPath(preview.data('url'), markdownVersion);
if (mdText.trim().length === 0) {
preview.text(this.emptyMessage);
......@@ -67,16 +64,6 @@ MarkdownPreview.prototype.showPreview = function($form) {
}
};
MarkdownPreview.prototype.versionedPreviewPath = function(markdownPreviewPath, markdownVersion) {
if (typeof markdownVersion === 'undefined') {
return markdownPreviewPath;
}
return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`;
};
MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) {
if (!url) {
return;
......
......@@ -108,11 +108,6 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
......@@ -313,7 +308,6 @@ export default {
:issuable-templates="issuableTemplates"
:markdown-docs-path="markdownDocsPath"
:markdown-preview-path="markdownPreviewPath"
:markdown-version="markdownVersion"
:project-path="projectPath"
:project-namespace="projectNamespace"
:show-delete-button="showDeleteButton"
......
......@@ -20,11 +20,6 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
canAttachFile: {
type: Boolean,
required: false,
......@@ -48,7 +43,6 @@ export default {
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
>
......
......@@ -39,11 +39,6 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
projectPath: {
type: String,
required: true,
......@@ -101,7 +96,6 @@ export default {
:form-state="formState"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:can-attach-file="canAttachFile"
:enable-autocomplete="enableAutocomplete"
/>
......
......@@ -1239,15 +1239,13 @@ export default class Notes {
var postUrl = $originalContentEl.data('postUrl');
var targetId = $originalContentEl.data('targetId');
var targetType = $originalContentEl.data('targetType');
var markdownVersion = $originalContentEl.data('markdownVersion');
this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
$editForm
.find('form')
.attr('action', `${postUrl}?html=true`)
.attr('data-remote', 'true')
.attr('data-markdown-version', markdownVersion);
.attr('data-remote', 'true');
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm
......
......@@ -39,11 +39,6 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
......@@ -342,7 +337,6 @@ Please check your network connection and try again.`;
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath"
:markdown-version="markdownVersion"
:add-spacing-classes="false"
>
<textarea
......
......@@ -111,7 +111,6 @@ export default {
:line="line"
:note="note"
:help-page-path="helpPagePath"
:markdown-version="note.cached_markdown_version"
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
/>
......
......@@ -26,11 +26,6 @@ export default {
required: false,
default: '',
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
saveButtonTitle: {
type: String,
required: false,
......@@ -202,7 +197,6 @@ export default {
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:quick-actions-docs-path="quickActionsDocsPath"
:line="line"
:note="discussionNote"
......
......@@ -44,11 +44,6 @@ export default {
required: false,
default: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
helpPagePath: {
type: String,
required: false,
......@@ -216,10 +211,6 @@ export default {
</template>
</ul>
<comment-form
v-if="!commentsDisabled"
:noteable-type="noteableType"
:markdown-version="markdownVersion"
/>
<comment-form v-if="!commentsDisabled" :noteable-type="noteableType" />
</div>
</template>
......@@ -18,7 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
const notesDataset = document.getElementById('js-vue-notes').dataset;
const parsedUserData = JSON.parse(notesDataset.currentUserData);
const noteableData = JSON.parse(notesDataset.noteableData);
const markdownVersion = parseInt(notesDataset.markdownVersion, 10);
let currentUserData = {};
noteableData.noteableType = notesDataset.noteableType;
......@@ -37,7 +36,6 @@ document.addEventListener('DOMContentLoaded', () => {
return {
noteableData,
currentUserData,
markdownVersion,
notesData: JSON.parse(notesDataset.notesData),
};
},
......@@ -47,7 +45,6 @@ document.addEventListener('DOMContentLoaded', () => {
noteableData: this.noteableData,
notesData: this.notesData,
userData: this.currentUserData,
markdownVersion: this.markdownVersion,
},
});
},
......
......@@ -27,11 +27,6 @@ export default {
type: String,
required: true,
},
markdownVersion: {
type: Number,
required: false,
default: 0,
},
addSpacingClasses: {
type: Boolean,
required: false,
......@@ -158,7 +153,7 @@ export default {
this.markdownPreviewLoading = true;
this.markdownPreview = __('Loading…');
this.$http
.post(this.versionedPreviewPath(), { text })
.post(this.markdownPreviewPath, { text })
.then(resp => resp.json())
.then(data => this.renderMarkdown(data))
.catch(() => new Flash(__('Error loading markdown preview')));
......@@ -186,13 +181,6 @@ export default {
.then(() => $(this.$refs['markdown-preview']).renderGFM())
.catch(() => new Flash(__('Error rendering markdown preview')));
},
versionedPreviewPath() {
const { markdownPreviewPath, markdownVersion } = this;
return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`;
},
},
};
</script>
......
......@@ -16,8 +16,6 @@ module PreviewMarkdown
else {}
end
markdown_params[:markdown_engine] = result[:markdown_engine]
render json: {
body: view_context.markdown(result[:text], markdown_params),
references: {
......
......@@ -268,7 +268,6 @@ module IssuablesHelper
issuableRef: issuable.to_reference,
markdownPreviewPath: preview_markdown_path(parent),
markdownDocsPath: help_page_path('user/markdown'),
markdownVersion: issuable.cached_markdown_version,
lockVersion: issuable.lock_version,
issuableTemplates: issuable_templates(issuable),
initialTitleHtml: markdown_field(issuable, :title),
......
......@@ -116,7 +116,6 @@ module MarkupHelper
def markup(file_name, text, context = {})
context[:project] ||= @project
context[:markdown_engine] ||= :redcarpet unless commonmark_for_repositories_enabled?
html = context.delete(:rendered) || markup_unsafe(file_name, text, context)
prepare_for_rendering(html, context)
end
......@@ -132,7 +131,6 @@ module MarkupHelper
page_slug: wiki_page.slug,
issuable_state_filter_enabled: true
)
context[:markdown_engine] ||= :redcarpet unless commonmark_for_repositories_enabled?
html =
case wiki_page.format
......@@ -187,10 +185,6 @@ module MarkupHelper
end
end
def commonmark_for_repositories_enabled?
Feature.enabled?(:commonmark_for_repositories, default_enabled: true)
end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
......
......@@ -171,7 +171,6 @@ module NotesHelper
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
markdownDocsPath: help_page_path('user/markdown'),
markdownVersion: issuable.cached_markdown_version,
quickActionsDocsPath: help_page_path('user/project/quick_actions'),
closePath: close_issuable_path(issuable),
reopenPath: reopen_issuable_path(issuable),
......
......@@ -265,10 +265,6 @@ module ProjectsHelper
link_to 'BFG', 'https://rtyley.github.io/bfg-repo-cleaner/', target: '_blank', rel: 'noopener noreferrer'
end
def legacy_render_context(params)
params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
end
def explore_projects_tab?
current_page?(explore_projects_path) ||
current_page?(trending_explore_projects_path) ||
......
......@@ -13,7 +13,6 @@ module CacheMarkdownField
extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 14
......@@ -42,18 +41,6 @@ module CacheMarkdownField
end
end
class MarkdownEngine
def self.from_version(version = nil)
return :common_mark if version.nil? || version == 0
if version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START
:redcarpet
else
:common_mark
end
end
end
def skip_project_check?
false
end
......@@ -71,7 +58,7 @@ module CacheMarkdownField
# Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author)
context[:markdown_engine] = MarkdownEngine.from_version(latest_cached_markdown_version)
context[:markdown_engine] = :common_mark
context
end
......@@ -128,17 +115,7 @@ module CacheMarkdownField
end
def latest_cached_markdown_version
return CacheMarkdownField::CACHE_COMMONMARK_VERSION unless cached_markdown_version
if legacy_markdown?
CacheMarkdownField::CACHE_REDCARPET_VERSION
else
CacheMarkdownField::CACHE_COMMONMARK_VERSION
end
end
def legacy_markdown?
cached_markdown_version && cached_markdown_version.between?(1, CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1)
CacheMarkdownField::CACHE_COMMONMARK_VERSION
end
included do
......
......@@ -614,7 +614,6 @@ class Repository
return unless readme
context = { project: project }
context[:markdown_engine] = :redcarpet unless MarkupHelper.commonmark_for_repositories_enabled?
MarkupHelper.markup_unsafe(readme.name, readme.data, context)
end
......
......@@ -270,9 +270,7 @@ class IssuableBaseService < BaseService
tasklist_toggler = TaskListToggleService.new(issuable.description, issuable.description_html,
line_source: update_task_params[:line_source],
line_number: update_task_params[:line_number].to_i,
toggle_as_checked: update_task_params[:checked],
index: update_task_params[:index].to_i,
sourcepos: !issuable.legacy_markdown?)
toggle_as_checked: update_task_params[:checked])
unless tasklist_toggler.execute
# if we make it here, the data is much newer than we thought it was - fail fast
......
......@@ -10,8 +10,7 @@ class PreviewMarkdownService < BaseService
text: text,
users: users,
suggestions: suggestions,
commands: commands.join(' '),
markdown_engine: markdown_engine
commands: commands.join(' ')
)
end
......@@ -49,12 +48,4 @@ class PreviewMarkdownService < BaseService
def commands_target_id
params[:quick_actions_target_id]
end
def markdown_engine
if params[:legacy_render]
:redcarpet
else
CacheMarkdownField::MarkdownEngine.from_version(params[:markdown_version].to_i)
end
end
end
......@@ -5,17 +5,13 @@
# We don't care if the text has changed above or below the specific checkbox, as long
# the checkbox still exists at exactly the same line number and the text is equal.
# If successful, new values are available in `updated_markdown` and `updated_markdown_html`
#
# Note: once we've removed RedCarpet support, we can remove the `index` and `sourcepos`
# parameters
class TaskListToggleService
attr_reader :updated_markdown, :updated_markdown_html
def initialize(markdown, markdown_html, line_source:, line_number:, toggle_as_checked:, index:, sourcepos: true)
def initialize(markdown, markdown_html, line_source:, line_number:, toggle_as_checked:)
@markdown, @markdown_html = markdown, markdown_html
@line_source, @line_number = line_source, line_number
@toggle_as_checked = toggle_as_checked
@index, @use_sourcepos = index, sourcepos
@updated_markdown, @updated_markdown_html = nil
end
......@@ -28,8 +24,8 @@ class TaskListToggleService
private
attr_reader :markdown, :markdown_html, :index, :toggle_as_checked
attr_reader :line_source, :line_number, :use_sourcepos
attr_reader :markdown, :markdown_html, :toggle_as_checked
attr_reader :line_source, :line_number
def toggle_markdown
source_lines = markdown.split("\n")
......@@ -68,17 +64,8 @@ class TaskListToggleService
end
# When using CommonMark, we should be able to use the embedded `sourcepos` attribute to
# target the exact line in the DOM. For RedCarpet, we need to use the index of the checkbox
# that was checked and match it with what we think is the same checkbox.
# The reason `sourcepos` is slightly more reliable is the case where a line of text is
# changed from a regular line into a checkbox (or vice versa). Then the checked index
# in the UI will be off from the list of checkboxes we've calculated locally.
# It's a rare circumstance, but since we can account for it, we do.
# target the exact line in the DOM.
def get_html_checkbox(html)
if use_sourcepos
html.css(".task-list-item[data-sourcepos^='#{line_number}:'] > input.task-list-item-checkbox").first
else
html.css('.task-list-item-checkbox')[index - 1]
end
html.css(".task-list-item[data-sourcepos^='#{line_number}:'] > input.task-list-item-checkbox").first
end
end
......@@ -2,7 +2,7 @@
%div{ class: container_class }
.prepend-top-default.append-bottom-default
.wiki
= render_wiki_content(@wiki_home, legacy_render_context(params))
= render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] }
......
......@@ -21,7 +21,7 @@
Write
%li
= link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id, legacy_render: params[:legacy_render]) do
= link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
= editing_preview_title(@blob.name)
= form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do
......
......@@ -2,7 +2,7 @@
.diff-content
- if markup?(@blob.name)
.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(@blob.name, @content, legacy_render_context(params))
= markup(@blob.name, @content)
- else
.file-content.code.js-syntax-highlight
- unless @diff_lines.empty?
......
- blob = viewer.blob
- context = legacy_render_context(params)
- unless context[:markdown_engine] == :redcarpet
- context[:rendered] = blob.rendered_markup if blob.respond_to?(:rendered_markup)
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(blob.name, blob.data, context)
= form_for [@project.namespace.becomes(Namespace), @project, @issue],
html: { class: 'issue-form common-note-form js-quick-submit js-requires-input' },
data: { markdown_version: @issue.cached_markdown_version } do |f|
html: { class: 'issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request],
html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' },
data: { markdown_version: @merge_request.cached_markdown_version } do |f|
html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request, presenter: @mr_presenter
= form_for [@project.namespace.becomes(Namespace), @project, @milestone],
html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' },
data: { markdown_version: @milestone.cached_markdown_version } do |f|
html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
= form_errors(@milestone)
.row
.col-md-6
......
......@@ -12,8 +12,7 @@
= form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name),
html: { class: 'common-note-form release-form js-quick-submit' },
data: { markdown_version: @release.cached_markdown_version }) do |f|
html: { class: 'common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…"
= render 'shared/notes/hints'
......
- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
- commit_message = commit_message % { page_title: @page.title }
- if params[:legacy_render] || !commonmark_for_repositories_enabled?
- markdown_version = CacheMarkdownField::CACHE_REDCARPET_VERSION
- else
- markdown_version = 0
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post,
html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' },
data: { markdown_version: markdown_version, uploads_path: uploads_path } do |f|
data: { uploads_path: uploads_path } do |f|
= form_errors(@page)
- if @page.persisted?
......
......@@ -12,7 +12,7 @@
.blocks-container
.block.block-first
- if @sidebar_page
= render_wiki_content(@sidebar_page, legacy_render_context(params))
= render_wiki_content(@sidebar_page)
- else
%ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar'
......
......@@ -27,6 +27,6 @@
.prepend-top-default.append-bottom-default
.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render_wiki_content(@page, legacy_render_context(params))
= render_wiki_content(@page)
= render 'sidebar'
......@@ -21,7 +21,7 @@
.file-content.wiki
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
= markup(snippet.file_name, chunk[:data], legacy_render_context(params))
= markup(snippet.file_name, chunk[:data])
- else
.file-content.code
.nothing-here-block= _("Empty file")
......
......@@ -54,7 +54,7 @@
.note-text.md
= markdown_field(note, :note)
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago')
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore, markdown_version: note.cached_markdown_version } }
.original-note-content.hidden{ data: { post_url: note_url(note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
#{note.note}
- if note_editable
= render 'shared/notes/edit', note: note
......
......@@ -3,8 +3,7 @@
.snippet-form-holder
= form_for @snippet, url: url,
html: { class: "snippet-form js-requires-input js-quick-submit common-note-form" },
data: { markdown_version: @snippet.cached_markdown_version } do |f|
html: { class: "snippet-form js-requires-input js-quick-submit common-note-form" } do |f|
= form_errors(@snippet)
.form-group.row
......
---
title: Removed deprecated Redcarpet markdown engine.
merge_request:
author:
type: removed
......@@ -31,8 +31,10 @@ dependency to do so. Please see the [`github-markup` gem readme](https://github.
> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown
processing of all new issues, merge requests, comments, and other Markdown content
in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the
repositories are also processed with CommonMark. Older content in issues/comments
are still processed using the [Redcarpet Ruby library][redcarpet].
repositories are also processed with CommonMark. As of 11.8, the [Redcarpet
Ruby library][redcarpet] has been removed and all issues/comments, including
those from pre-11.1, are now processed using [CommonMark Ruby
Library][commonmarker].
>
> The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108)
in October 2018.
......@@ -41,11 +43,11 @@ in October 2018.
### Transitioning to CommonMark
You may have Markdown documents in your repository that were written using some
of the nuances of RedCarpet's version of Markdown. Since CommonMark uses a
slightly stricter syntax, these documents may now display a little strangely
since we've transitioned to CommonMark. Numbered lists with nested lists in
particular can be displayed incorrectly.
You may have older issues/merge requests or Markdown documents in your
repository that were written using some of the nuances of RedCarpet's version
of Markdown. Since CommonMark uses a slightly stricter syntax, these documents
may now display a little strangely since we've transitioned to CommonMark.
Numbered lists with nested lists in particular can be displayed incorrectly.
It is usually quite easy to fix. In the case of a nested list such as this:
......@@ -65,11 +67,6 @@ simply add a space to each nested item:
In the documentation below, we try to highlight some of the differences.
If you have a need to view a document using RedCarpet, you can add the token
`legacy_render=1` to the end of the url, like this:
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md?legacy_render=1
If you have a large volume of Markdown files, it can be tedious to determine
if they will be displayed correctly or not. You can use the
[diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark)
......@@ -677,7 +674,7 @@ Becomes:
+ Or pluses
If a list item contains multiple paragraphs,
each subsequent paragraph should be indented to the same level as the start of the list item text (_Redcarpet: paragraph should be indented with four spaces._)
each subsequent paragraph should be indented to the same level as the start of the list item text
Example:
......@@ -841,7 +838,7 @@ These details <em>will</em> remain <strong>hidden</strong> until expanded.
</details>
</p>
**Note:** Markdown inside these tags is supported, as long as you have a blank line after the `</summary>` tag and before the `</details>` tag, as shown in the example. _Redcarpet does not support Markdown inside these tags. You can work around this by using HTML, for example you can use `<pre><code>` tags instead of [code fences](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting)._
**Note:** Markdown inside these tags is supported, as long as you have a blank line after the `</summary>` tag and before the `</details>` tag, as shown in the example.
```html
<details>
......
# frozen_string_literal: true
# `Redcarpet` markdown engine for GitLab's Banzai markdown filter.
# This module is used in Banzai::Filter::MarkdownFilter.
# Used gem is `redcarpet` which is a ruby library for markdown processing.
# Homepage: https://github.com/vmg/redcarpet
module Banzai
module Filter
module MarkdownEngines
class Redcarpet
OPTIONS = {
fenced_code_blocks: true,
footnotes: true,
lax_spacing: true,
no_intra_emphasis: true,
space_after_headers: true,
strikethrough: true,