Commit 4292a286 authored by Brett Walker's avatar Brett Walker

Create pipeline/filter converting ADF to markdown

using kramdown

- Initial attmpt at using kramdown
for ADF to kramdown conversion
- Additional specs for marks code and link
- Add support for strikethrough, superscipt
and subscript
- Add underline and textColor
- Added blockquote, bull_list, heading, and rule
- Add inlineCard and orderedList support
- Added support for panel
- Add codeBlock support
- Added hardBreak and initial table support
- Add emoji support
- Add icons to panels
- Add support for mediaSingle and mediaGroup
- Add support for mentions
- Fix mention and complex document md
- Add handling of tables
- Remove unnecessary guard
- Added specs for invalid situations

Add link to CommonMark spec for clarification

and other minor changes

Use `add_text` method

and other minor changes

Use a blockquote for panels

instead of <hr> separation

Correctly set the raw test for headings

Adjust how mentions are generated

and changes for other review comments

Return original source for invalid documents
parent 725a5395
......@@ -140,6 +140,7 @@ gem 'deckar01-task_list', '2.3.1'
gem 'gitlab-markup', '~> 1.7.1'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.20'
gem 'kramdown', '~> 2.2.1'
gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~> 6.1.2'
gem 'org-ruby', '~> 0.9.12'
......
......@@ -575,7 +575,8 @@ GEM
kgio (2.11.3)
knapsack (1.17.0)
rake
kramdown (2.1.0)
kramdown (2.2.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
kubeclient (4.6.0)
......@@ -1282,6 +1283,7 @@ DEPENDENCIES
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
kramdown (~> 2.2.1)
kubeclient (~> 4.6.0)
letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
......
# frozen_string_literal: true
module Banzai
module Filter
module JiraImport
# Uses Kramdown to convert from the Atlassian Document Format (json)
# into CommonMark
# @see https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/
class AdfToCommonmarkFilter < HTML::Pipeline::TextFilter
def initialize(text, context = nil, result = nil)
super(text, context, result)
end
def call
Kramdown::Document.new(@text, input: 'AtlassianDocumentFormat', html_tables: true).to_commonmark
rescue ::Kramdown::Error => e
# If we get an error, then just return the original text so at
# least the user knows something went wrong
"#{e.message}\n\n#{@text}"
end
end
end
end
end
# frozen_string_literal: true
module Banzai
module Pipeline
module JiraImport
class AdfCommonmarkPipeline < BasePipeline
def self.filters
FilterArray[
Filter::JiraImport::AdfToCommonmarkFilter
]
end
end
end
end
end
# frozen_string_literal: true
module Kramdown
module Converter
# Overrides the base Kramdown converter to add any special
# behaviour for CommonMark.
#
# Currently we support an option `html_tables` that outputs
# an HTML table instead of a Markdown table. This is to
# support possibly being given complex tables, such as from ADF.
#
# Note: this is only an initial implementation. Currently don't
# strip out IALs or other specific kramdown syntax.
class Commonmark < ::Kramdown::Converter::Kramdown
def convert_table(el, opts)
return super unless @options[:html_tables]
opts[:alignment] = el.options[:alignment]
result = inner(el, opts)
"<table>\n#{result}</table>\n\n"
end
def convert_thead(el, opts)
return super unless @options[:html_tables]
"<thead>\n#{inner(el, opts)}</thead>\n"
end
def convert_tbody(el, opts)
return super unless @options[:html_tables]
"<tbody>\n#{inner(el, opts)}</tbody>\n"
end
def convert_tfoot(el, opts)
return super unless @options[:html_tables]
"<tfoot>\n#{inner(el, opts)}</tfoot>\n"
end
def convert_tr(el, opts)
return super unless @options[:html_tables]
"<tr>\n#{el.children.map {|c| convert(c, opts) }.join}</tr>\n"
end
def convert_td(el, opts)
return super unless @options[:html_tables]
# We need to add two linefeeds in order for any inner text to
# be processed as markdown. The HTML block must be "closed",
# as referenced in the CommonMark spec
# @see https://spec.commonmark.org/0.29/#html-blocks
"<td>\n\n#{inner(el, opts)}</td>\n"
end
def convert_th(el, opts)
return super unless @options[:html_tables]
# We need to add two linefeeds in order for any inner text to
# be processed as markdown. The HTML block must be "closed",
# as referenced in the CommonMark spec
# @see https://spec.commonmark.org/0.29/#html-blocks
"<th>\n\n#{inner(el, opts)}</th>\n"
end
end
end
end
# frozen_string_literal: true
module Kramdown
module Parser
# Parses an Atlassian Document Format (ADF) json into a
# Kramdown AST tree, for conversion to another format.
# The primary goal is to convert in GitLab Markdown.
#
# This parser does NOT resolve external resources, such as media/attachments.
# A special url is generated for media based on the id, for example
# ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
# so that a later filter/process can resolve those.
#
# @see https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/ ADF Document Structure
# @see https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/ ADF Playground
# @see https://developer.atlassian.com/cloud/jira/platform/apis/document/viewer/ ADF Viewer
class AtlassianDocumentFormat < Kramdown::Parser::Base
unless defined?(TOP_LEVEL_BLOCK_NODES)
TOP_LEVEL_BLOCK_NODES = %w[blockquote
bulletList
codeBlock
heading
mediaGroup
mediaSingle
orderedList
panel
paragraph
rule
table].freeze
CHILD_BLOCK_NODES = %w[listItem
media
table_cell
table_header
table_row].freeze
INLINE_NODES = %w[emoji
hardBreak
inlineCard
mention
text].freeze
MARKS = %w[code
em
link
strike
strong
subsup
textColor
underline].freeze
TABLE_CELL_NODES = %w[blockquote
bulletList
codeBlock
heading
mediaGroup
orderedList
panel
paragraph
rule].freeze
LIST_ITEM_NODES = %w[bulletList
codeBlock
mediaSingle
orderedList
paragraph].freeze
PANEL_NODES = %w[bulletList
heading
orderedList
paragraph].freeze
PANEL_EMOJIS = { info: ':information_source:',
note: ':notepad_spiral:',
warning: ':warning:',
success: ':white_check_mark:',
error: ':octagonal_sign:' }.freeze
# The default language for code blocks is `java`, as indicated in
# You can't change the default in Jira. There was a comment that indicated
# Confluence can set the default language.
# @see https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=advanced&_ga=2.5135221.773220073.1591894917-438867908.1591894917
# @see https://jira.atlassian.com/browse/JRASERVER-29184?focusedCommentId=832255&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-832255
CODE_BLOCK_DEFAULT_LANGUAGE = 'java'
end
def parse
ast = Gitlab::Json.parse(@source)
validate_document(ast)
process_content(@root, ast, TOP_LEVEL_BLOCK_NODES)
rescue ::JSON::ParserError => e
msg = 'Invalid Atlassian Document Format JSON'
Gitlab::AppLogger.error msg
Gitlab::AppLogger.error e
raise ::Kramdown::Error, msg
end
def process_content(element, ast_node, allowed_types)
ast_node['content'].each do |node|
next unless allowed_types.include?(node['type'])
public_send("process_#{node['type'].underscore}", element, node) # rubocop:disable GitlabSecurity/PublicSend
end
end
def process_blockquote(element, ast_node)
new_element = Element.new(:blockquote)
element.children << new_element
process_content(new_element, ast_node, TOP_LEVEL_BLOCK_NODES)
end
def process_bullet_list(element, ast_node)
new_element = Element.new(:ul)
element.children << new_element
process_content(new_element, ast_node, %w[listItem])
end
def process_code_block(element, ast_node)
code_text = gather_text(ast_node)
lang = ast_node.dig('attrs', 'language') || CODE_BLOCK_DEFAULT_LANGUAGE
element.children << Element.new(:codeblock, code_text, {}, { lang: lang })
end
def process_emoji(element, ast_node)
emoji = ast_node.dig('attrs', 'text') || ast_node.dig('attrs', 'shortName')
return unless emoji
add_text(emoji, element, :text)
end
def process_hard_break(element, ast_node)
element.children << Element.new(:br)
end
def process_heading(element, ast_node)
level = ast_node.dig('attrs', 'level').to_i.clamp(1, 6)
options = { level: level }
new_element = Element.new(:header, nil, nil, options)
element.children << new_element
process_content(new_element, ast_node, INLINE_NODES)
extract_element_text(new_element, new_element.options[:raw_text] = +'')
end
def process_inline_card(element, ast_node)
url = ast_node.dig('attrs', 'url')
data = ast_node.dig('attrs', 'data')
if url
# we don't pull a description from the link and create a panel,
# just convert to a normal link
new_element = Element.new(:text, url)
element.children << wrap_element(new_element, :a, nil, { 'href' => url })
elsif data
# data is JSONLD (https://json-ld.org/), so for now output
# as a codespan of text, with `adf-inlineCard: ` at the start
text = "adf-inlineCard: #{data}"
element.children << Element.new(:codespan, text, nil, { lang: 'adf-inlinecard' })
end
end
def process_list_item(element, ast_node)
new_element = Element.new(:li)
element.children << new_element
process_content(new_element, ast_node, LIST_ITEM_NODES)
end
def process_media(element, ast_node)
media_url = "adf-media://#{ast_node['attrs']['id']}"
case ast_node['attrs']['type']
when 'file'
attrs = { 'src' => media_url, 'alt' => ast_node['attrs']['collection'] }
media_element = Element.new(:img, nil, attrs)
when 'link'
attrs = { 'href' => media_url }
media_element = wrap_element(Element.new(:text, media_url), :a, nil, attrs)
end
media_element = wrap_element(media_element, :p)
element.children << media_element
end
# wraps a single media element.
# Currently ignore attrs.layout and attrs.width
def process_media_single(element, ast_node)
new_element = Element.new(:p)
element.children << new_element
process_content(new_element, ast_node, %w[media])
end
# wraps a group media element.
# Currently ignore attrs.layout and attrs.width
def process_media_group(element, ast_node)
ul_element = Element.new(:ul)
element.children << ul_element
ast_node['content'].each do |node|
next unless node['type'] == 'media'
li_element = Element.new(:li)
ul_element.children << li_element
process_media(li_element, node)
end
end
def process_mention(element, ast_node)
# Make it `@adf-mention:` since there is no guarantee that it is
# a valid username in our system. This gives us an
# opportunity to replace it later. Mention name can have
# spaces, so double quote it
mention_text = ast_node.dig('attrs', 'text')&.gsub('@', '')
mention_text = %Q("#{mention_text}") if mention_text.match?(/ /)
mention_text = %Q(@adf-mention:#{mention_text})
add_text(mention_text, element, :text)
end
def process_ordered_list(element, ast_node)
# `attrs.order` is not supported in the Kramdown AST
new_element = Element.new(:ol)
element.children << new_element
process_content(new_element, ast_node, %w[listItem])
end
# since we don't have something similar, then put <hr> around it and
# add a bolded status text (eg: "Error:") to the front of it.
def process_panel(element, ast_node)
panel_type = ast_node.dig('attrs', 'panelType')
return unless %w[info note warning success error].include?(panel_type)
panel_header_text = "#{PANEL_EMOJIS[panel_type.to_sym]} "
panel_header_element = Element.new(:text, panel_header_text)
new_element = Element.new(:blockquote)
new_element.children << panel_header_element
element.children << new_element
process_content(new_element, ast_node, PANEL_NODES)
end
def process_paragraph(element, ast_node)
new_element = Element.new(:p)
element.children << new_element
process_content(new_element, ast_node, INLINE_NODES)
end
def process_rule(element, ast_node)
element.children << Element.new(:hr)
end
def process_table(element, ast_node)
table = Element.new(:table, nil, nil, { alignment: [:default, :default] })
element.children << table
tbody = Element.new(:tbody)
table.children << tbody
process_content(tbody, ast_node, %w[tableRow])
end
# we ignore the attributes, attrs.background, attrs.colspan,
# attrs.colwidth, and attrs.rowspan
def process_table_cell(element, ast_node)
new_element = Element.new(:td)
element.children << new_element
process_content(new_element, ast_node, TABLE_CELL_NODES)
end
# we ignore the attributes, attrs.background, attrs.colspan,
# attrs.colwidth, and attrs.rowspan
def process_table_header(element, ast_node)
new_element = Element.new(:th)
element.children << new_element
process_content(new_element, ast_node, TABLE_CELL_NODES)
end
def process_table_row(element, ast_node)
new_element = Element.new(:tr)
element.children << new_element
process_content(new_element, ast_node, %w[tableHeader tableCell])
end
def process_text(element, ast_node)
new_element = Element.new(:text, ast_node['text'])
new_element = apply_marks(new_element, ast_node, MARKS)
element.children << new_element
end
private
def validate_document(ast)
return if ast['type'] == 'doc'
raise ::JSON::ParserError, 'missing doc node'
end
# ADF marks are an attribute on the node. For kramdown,
# we have to wrap the node with an element for the mark.
def apply_marks(element, ast_node, allowed_types)
return element unless ast_node['marks']
new_element = element
ast_node['marks'].each do |mark|
next unless allowed_types.include?(mark['type'])
case mark['type']
when 'code'
new_element = Element.new(:codespan, ast_node['text'])
when 'em'
new_element = wrap_element(new_element, :em)
when 'link'
attrs = { 'href' => mark.dig('attrs', 'href') }
attrs['title'] = mark.dig('attrs', 'title')
new_element = wrap_element(new_element, :a, nil, attrs)
when 'strike'
new_element = wrap_element(new_element, :html_element, 'del', {}, category: :span)
when 'strong'
new_element = wrap_element(new_element, :strong)
when 'subsup'
type = mark.dig('attrs', 'type')
case type
when 'sub'
new_element = wrap_element(new_element, :html_element, 'sub', {}, category: :span)
when 'sup'
new_element = wrap_element(new_element, :html_element, 'sup', {}, category: :span)
else
next
end
when 'textColor'
color = mark.dig('attrs', 'color')
new_element = wrap_element(new_element, :html_element, 'span', { color: color }, category: :span)
when 'underline'
new_element = wrap_element(new_element, :html_element, 'u', {}, category: :span)
else
next
end
end
new_element
end
def wrap_element(element, type, *args)
wrapper = Element.new(type, *args)
wrapper.children << element
wrapper
end
def extract_element_text(element, raw)
raw << element.value.to_s if element.type == :text
element.children.each { |c| extract_element_text(c, raw) }
end
def gather_text(ast_node)
ast_node['content'].inject('') do |memo, node|
node['type'] == 'text' ? (memo + node['text']) : memo
end
end
def method_missing(method, *args)
raise NotImplementedError, "method `#{method}` not implemented yet"
end
end
end
end
{
"version": 1,
"type": "doc",
"content": [
{
"type": "blockquote",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a sample quote"
},
{
"type": "text",
"text": " with lorem ipsum dolor sit amet..."
}
]
}
]
}
]
}
> This is a sample quote with lorem ipsum dolor sit amet...
{
"version": 1,
"type": "doc",
"content": [
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Item 1"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Item 2",
"marks": [
{
"type": "strong"
}
]
}
]
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Sub-item 1"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Sub-item 1 paragraph"
}
]
}
]
}
]
}
]
}
]
}
]
}
* Item 1
* **Item 2**
* Sub-item 1
Sub-item 1 paragraph
{
"version": 1,
"type": "doc",
"content": [
{
"type": "codeBlock",
"attrs": {
"language": "javascript"
},
"content": [
{
"type": "text",
"text": "export function makeIssue({ parentIssue, project, users }) {\n\n const issueType = pickRandom(project.issueTypes)\n\n console.log(data)\n\n return data\n}"
}
]
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Item 1"
}
]
},
{
"type": "codeBlock",
"content": [
{
"type": "text",
"text": "public DemoClass()\n{\n // assign default value\n x = 0;\n}"
}
]
}
]
}
]
}
]
}
export function makeIssue({ parentIssue, project, users }) {
const issueType = pickRandom(project.issueTypes)
console.log(data)
return data
}
* Item 1
public DemoClass()
{
// assign default value
x = 0;
}
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is some "
},
{
"type": "text",
"text": "inline code",
"marks": [
{
"type": "code"
}
]
}
]
}
]
}
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a complex issue…and this is normal text"
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "rule"
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - Dark Gray"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Light Gray",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#97a0af"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Purple",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#6554c0"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Teal",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#00b8d9"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Green",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#36b37e"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Red",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#ff5630"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Color - "
},
{
"type": "text",
"text": "Orange",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#ff991f"
}
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "rule"
},
{
"type": "paragraph",
"content": [
{
"type": "inlineCard",
"attrs": {
"url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
}
},
{
"type": "text",
"text": " "
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "inlineCard",
"attrs": {
"data": {
"@context": "https://json-ld.org/contexts/person.jsonld",
"@id": "http://dbpedia.org/resource/John_Lennon",
"name": "John Lennon",
"born": "1940-10-09",
"spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
}
}
},
{
"type": "text",
"text": " "
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "External Link",
"marks": [
{
"type": "link",
"attrs": {
"href": "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718"
}
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "rule"
},
{
"type": "paragraph",
"content": []
},
{
"type": "blockquote",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a block quote"
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "panel",
"attrs": {
"panelType": "success"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Success info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "info"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Info info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "note"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Note info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "warning"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Warning info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "error"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Error info panel"
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "paragraph",
"content": []
},
{
"type": "rule"
},
{
"type": "paragraph",
"content": [
{
"type": "mention",
"attrs": {
"id": "5e32f803e127810e82875bc1",
"text": "jhope"
}
},
{
"type": "text",
"text": " what up"
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "paragraph",
"content": [
{
"type": "emoji",
"attrs": {
"shortName": ":grinning:",
"id": "1f600",
"text": "\uD83D\uDE00"
}
},
{
"type": "text",
"text": " "
},
{
"type": "emoji",
"attrs": {
"shortName": ":rofl:",
"id": "1f923",
"text": "\uD83E\uDD23"
}
},
{
"type": "text",
"text": " "
},
{
"type": "emoji",
"attrs": {
"shortName": ":partying_face:",
"id": "1f973",
"text": "\uD83E\uDD73"
}
},
{
"type": "text",
"text": " "
},
{
"type": "emoji",
"attrs": {
"shortName": ":heart_eyes:",
"id": "1f60d",
"text": "\uD83D\uDE0D"
}
},
{
"type": "text",
"text": " "
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "table",
"attrs": {
"isNumberColumnEnabled": false,
"layout": "default"
},
"content": [
{
"type": "tableRow",
"content": [
{
"type": "tableHeader",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 1 Row 1",
"marks": [
{
"type": "strong"
}
]
}
]
}
]
},
{
"type": "tableHeader",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 2 Row 1",
"marks": [
{
"type": "strong"
}
]
}
]
}
]
},
{
"type": "tableHeader",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 3 Row 1",
"marks": [
{
"type": "strong"
}
]
}
]
}
]
}
]
},
{
"type": "tableRow",
"content": [
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 1 Row 2"
}
]
}
]
},
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 2 Row 2"
}
]
}
]
},
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 3 Row 2"
}
]
}
]
}
]
},
{
"type": "tableRow",
"content": [
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 1 Row 3"
}
]
}
]
},
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 2 Row 3"
}
]
}
]
},
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Col 3 Row 3"
}
]
}
]
}
]
}
]
},
{
"type": "heading",
"attrs": {
"level": 1
},
"content": [
{
"type": "text",
"text": "Header 1"
}
]
},
{
"type": "heading",
"attrs": {
"level": 2
},
"content": [
{
"type": "text",
"text": "Header 2"
}
]
},
{
"type": "heading",
"attrs": {
"level": 3
},
"content": [
{
"type": "text",
"text": "Header 3"
}
]
},
{
"type": "heading",
"attrs": {
"level": 4
},
"content": [
{
"type": "text",
"text": "Header 4"
}
]
},
{
"type": "heading",
"attrs": {
"level": 5
},
"content": [
{
"type": "text",
"text": "Header 5"
}
]
},
{
"type": "heading",
"attrs": {
"level": 6
},
"content": [
{
"type": "text",
"text": "Header 6"
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Bullet point list item 1"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Bullet point list Item 2"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Bullet point list Item 3"
}
]
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "orderedList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list Item 1"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 2"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 3"
}
]
}
]
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Underline",
"marks": [
{
"type": "underline"
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Superscript",
"marks": [
{
"type": "subsup",
"attrs": {
"type": "sup"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Subscript",
"marks": [
{
"type": "subsup",
"attrs": {
"type": "sub"
}
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Bold",
"marks": [
{
"type": "strong"
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Italic",
"marks": [
{
"type": "em"
}
]
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Strikethrough",
"marks": [
{
"type": "strike"
}
]
}
]
},
{
"type": "codeBlock",
"attrs": {
"language": "javascript"
},
"content": [
{
"type": "text",
"text": "export function makeIssue({ parentIssue, project, users }) {\n \n const issueType = pickRandom(project.issueTypes)\n\n let data = {\n fields: {\n summary: faker.lorem.sentence(),\n issuetype: {\n id: issueType.id\n },\n project: {\n id: project.id\n },\n reporter: {\n id: pickRandom(users)\n }\n }\n }\n\n if (issueType.subtask) {\n data = {\n parent: {\n key: parentIssue\n }\n }\n }\n\n console.log(data)\n\n return data\n}"
}
]
},
{
"type": "mediaSingle",
"attrs": {
"layout": "center"
},
"content": [
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
"type": "file",
"collection": "jira-10050-field-description",
"width": 400,
"height": 400
}
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "mediaSingle",
"attrs": {
"layout": "center"
},
"content": [
{
"type": "media",
"attrs": {
"id": "6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4",
"type": "file",
"collection": "jira-10050-field-description",
"width": 1280,
"height": 789
}
}
]
},
{
"type": "mediaSingle",
"attrs": {
"layout": "center"
},
"content": [
{
"type": "media",
"attrs": {
"id": "e818a88d-9185-4a7f-8882-18339a0f0966",
"type": "file",
"collection": "jira-10050-field-description",
"width": 1280,
"height": 598
}
}
]
},
{
"type": "paragraph",
"content": []
},
{
"type": "paragraph",
"content": []
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "blob:"
},
{
"type": "text",
"text": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408",
"marks": [
{
"type": "link",
"attrs": {
"href": "https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408"
}
}
]
}
]
},
{
"type": "paragraph",
"content": []
}
]
}
This is a complex issue…and this is normal text
* * *
Color - Dark Gray
Color - <span color="#97a0af">Light Gray</span>
Color - <span color="#6554c0">Purple</span>
Color - <span color="#00b8d9">Teal</span>
Color - <span color="#36b37e">Green</span>
Color - <span color="#ff5630">Red</span>
Color - <span color="#ff991f">Orange</span>
* * *
[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
`adf-inlineCard:
{"@context"=>"https://json-ld.org/contexts/person.jsonld",
"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
"born"=>"1940-10-09",
"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}`
[External Link][2]
* * *
> This is a block quote
> \:white\_check\_mark: Success info panel
> \:information\_source: Info info panel
> \:notepad\_spiral: Note info panel
> \:warning: Warning info panel
> \:octagonal\_sign: Error info panel
* * *
@adf-mention:jhope what up
😀 🤣 🥳 😍
<table>
<tbody>
<tr>
<th>
**Col 1 Row 1**
</th>
<th>
**Col 2 Row 1**
</th>
<th>
**Col 3 Row 1**
</th>
</tr>
<tr>
<td>
Col 1 Row 2
</td>
<td>
Col 2 Row 2
</td>
<td>
Col 3 Row 2
</td>
</tr>
<tr>
<td>
Col 1 Row 3
</td>
<td>
Col 2 Row 3
</td>
<td>
Col 3 Row 3
</td>
</tr>
</tbody>
</table>
# Header 1
## Header 2
### Header 3
#### Header 4
##### Header 5
###### Header 6
* Bullet point list item 1
* Bullet point list Item 2
* Bullet point list Item 3
1. Number list Item 1
2. Number list item 2
3. Number list item 3
<u>Underline</u>
<sup>Superscript</sup>
<sub>Subscript</sub>
**Bold**
*Italic*
<del>Strikethrough</del>
export function makeIssue({ parentIssue, project, users }) {
const issueType = pickRandom(project.issueTypes)
let data = {
fields: {
summary: faker.lorem.sentence(),
issuetype: {
id: issueType.id
},
project: {
id: project.id
},
reporter: {
id: pickRandom(users)
}
}
}
if (issueType.subtask) {
data = {
parent: {
key: parentIssue
}
}
}
console.log(data)
return data
}
![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
![jira-10050-field-description](adf-media://6a5b48c6-70bd-4747-9ac8-a9abc9adb1f4)
![jira-10050-field-description](adf-media://e818a88d-9185-4a7f-8882-18339a0f0966)
blob:[https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408][3]
[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
[2]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25718
[3]: https://gitlab-jira.atlassian.net/5eb8e93b-7b15-446f-82d9-9d82ad7b8ea5#media-blob-url=true&id=572b2c1b-1b38-44ba-904a-649ee1861917&collection=upload-user-collection-426749591&contextId=10042&mimeType=image%2Fpng&name=import-jira-issues.png&size=294408
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Grinning with unicode "
},
{
"type": "emoji",
"attrs": {
"shortName": ":grinning:",
"text": "😀"
}
},
{
"type": "text",
"text": " and heart eyes with raw unicode "
},
{
"type": "emoji",
"attrs": {
"shortName": ":heart_eyes:",
"id": "1f60d",
"text": "\uD83D\uDE0D"
}
},
{
"type": "text",
"text": ", non-standard Atlassian "
},
{
"type": "emoji",
"attrs": {
"shortName": ":awthanks:",
"id": "atlassian-awthanks",
"text": ":awthanks:"
}
},
{
"type": "text",
"text": ", non-standard customer emoji "
},
{
"type": "emoji",
"attrs": {
"shortName": ":thumbsup::skin-tone-2:"
}
},
{
"type": "text",
"text": ", and invalid is ignored "
},
{
"type": "emoji",
"attrs": {
"id": "1f44d"
}
}
]
}
]
}
Grinning with unicode 😀 and heart eyes with raw unicode 😍, non-standard
Atlassian :awthanks:, non-standard customer emoji
:thumbsup::skin-tone-2:, and invalid is ignored
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a sample paragraph"
},
{
"type": "hardBreak"
},
{
"type": "text",
"text": "with lorem ipsum dolor sit amet..."
}
]
}
]
}
This is a sample paragraph
with lorem ipsum dolor sit amet...
{
"version": 1,
"type": "doc",
"content": [
{
"type": "heading",
"attrs": {
"level": 1
},
"content": [
{
"type": "text",
"text": "Header 1"
}
]
},
{
"type": "heading",
"attrs": {
"level": 2
},
"content": [
{
"type": "text",
"text": "Header 2",
"marks": [
{
"type": "strong"
}
]
}
]
},
{
"type": "heading",
"attrs": {
"level": 3
},
"content": [
{
"type": "text",
"text": "Header 3"
}
]
},
{
"type": "heading",
"attrs": {
"level": 4
},
"content": [
{
"type": "text",
"text": "Header 4",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#ff6347"
}
}
]
}
]
},
{
"type": "heading",
"attrs": {
"level": 5
},
"content": [
{
"type": "text",
"text": "Header 5"
}
]
},
{
"type": "heading",
"attrs": {
"level": 6
},
"content": [
{
"type": "text",
"text": "Header 6"
}
]
}
]
}
# Header 1
## **Header 2**
### Header 3
#### <span color="#ff6347">Header 4</span>
##### Header 5
###### Header 6
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "An inline card (url): "
},
{
"type": "inlineCard",
"attrs": {
"url": "https://gitlab-jira.atlassian.net/browse/DEMO-1"
}
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Some prelude text "
},
{
"type": "inlineCard",
"attrs": {
"data": {
"@context": "https://json-ld.org/contexts/person.jsonld",
"@id": "http://dbpedia.org/resource/John_Lennon",
"name": "John Lennon",
"born": "1940-10-09",
"spouse": "http://dbpedia.org/resource/Cynthia_Lennon"
}
}
},
{
"type": "text",
"text": " some following text"
}
]
}
]
}
An inline card (url):
[https://gitlab-jira.atlassian.net/browse/DEMO-1][1]
Some prelude text `adf-inlineCard:
{"@context"=>"https://json-ld.org/contexts/person.jsonld",
"@id"=>"http://dbpedia.org/resource/John_Lennon", "name"=>"John Lennon",
"born"=>"1940-10-09",
"spouse"=>"http://dbpedia.org/resource/Cynthia_Lennon"}` some following
text
[1]: https://gitlab-jira.atlassian.net/browse/DEMO-1
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a second paragraph"
}
]
}
]
}
}
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a sample paragraph"
},
{
"type": "text",
"text": " with lorem ipsum dolor sit amet..."
}
]
}
{
"version": 1,
"type": "doc",
"content": [
{
"type": "invalid",
"content": [
{
"type": "text",
"text": "This is a sample paragraph"
},
{
"type": "text",
"text": " with lorem ipsum dolor sit amet..."
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a second paragraph"
}
]
}
]
}
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a "
},
{
"type": "text",
"text": "link without title",
"marks": [
{
"type": "link",
"attrs": {
"href": "http://example.com"
}
}
]
},
{
"type": "text",
"text": " and a "
},
{
"type": "text",
"text": "link with title",
"marks": [
{
"type": "link",
"attrs": {
"href": "http://example.net",
"title": "Link Title"
}
}
]
}
]
}
]
}
This is a [link without title][1] and a [link with title][2]
[1]: http://example.com
[2]: http://example.net "Link Title"
{
"version": 1,
"type": "doc",
"content": [
{
"type": "mediaGroup",
"content": [
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
"type": "file",
"collection": "jira-10050-field-description",
"width": 400,
"height": 400
}
},
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-abcde",
"type": "file",
"collection": "jira-10050-field-description",
"width": 400,
"height": 400
}
}
]
},
{
"type": "rule"
},
{
"type": "mediaGroup",
"content": [
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link1",
"type": "link",
"collection": "jira-10050-field-description-links",
"width": 400,
"height": 400
}
},
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-ac3a5887750c-link2",
"type": "link",
"collection": "jira-10050-field-description-links",
"width": 400,
"height": 400
}
}
]
}
]
}
* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
* ![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcde)
* * *
* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link1)
* [adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c-link2)
{
"version": 1,
"type": "doc",
"content": [
{
"type": "mediaSingle",
"attrs": {
"layout": "center"
},
"content": [
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-ac3a5887750c",
"type": "file",
"collection": "jira-10050-field-description",
"width": 400,
"height": 400
}
}
]
},
{
"type": "mediaSingle",
"attrs": {
"layout": "center"
},
"content": [
{
"type": "media",
"attrs": {
"id": "79411c6b-50e0-477f-b4ed-abcd",
"type": "file",
"collection": "another-jira-10050-field-description",
"width": 400,
"height": 400
}
}
]
}
]
}
![jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-ac3a5887750c)
![another-jira-10050-field-description](adf-media://79411c6b-50e0-477f-b4ed-abcd)
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Mentioning "
},
{
"type": "mention",
"attrs": {
"id": "ABCDE-ABCDE-ABCDE-ABCDE",
"text": "@testuser",
"userType": "APP"
}
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Mentioning "
},
{
"type": "mention",
"attrs": {
"id": "ABCDE-ABCDE-ABCDE-ABCDE",
"text": "@test user",
"userType": "APP"
}
},
{
"type": "text",
"text": " with space in user name"
}
]
}
]
}
Mentioning @adf-mention:testuser
Mentioning @adf-mention:\"test user\" with space in user name
{
"version": 1,
"type": "doc",
"content": [
{
"type": "orderedList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list Item 1"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 2"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 3"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 4"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 5"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 6"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 7"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 8"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 9"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Number list item 10"
}
]
}
]
}
]
}
]
}
1. Number list Item 1
2. Number list item 2
3. Number list item 3
4. Number list item 4
5. Number list item 5
6. Number list item 6
7. Number list item 7
8. Number list item 8
9. Number list item 9
10. Number list item 10
{
"version": 1,
"type": "doc",
"content": [
{
"type": "panel",
"attrs": {
"panelType": "success"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Success info panel"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Second paragraph"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "info"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Info info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "note"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Note info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "warning"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Warning info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "error"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Error info panel"
}
]
}
]
},
{
"type": "panel",
"attrs": {
"panelType": "unknown"
},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Unknown panel"
}
]
}
]
}
]
}
> \:white\_check\_mark: Success info panel
>
> Second paragraph
> \:information\_source: Info info panel
> \:notepad\_spiral: Note info panel
> \:warning: Warning info panel
> \:octagonal\_sign: Error info panel
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a sample paragraph"
},
{
"type": "text",
"text": " with lorem ipsum dolor sit amet..."
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is a second paragraph"
}
]
}
]
}
This is a sample paragraph with lorem ipsum dolor sit amet...
This is a second paragraph
{
"version": 1,
"type": "doc",
"content": [
{
"type": "rule"
}
]
}
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is "
},
{
"type": "text",
"text": "stricken",
"marks": [
{
"type": "strike"
}
]
},
{
"type": "text",
"text": " and "
},
{
"type": "text",
"text": "superscripted",
"marks": [
{
"type": "subsup",
"attrs": {
"type": "sup"
}
}
]
},
{
"type": "text",
"text": " and "
},
{
"type": "text",
"text": "subscripted",
"marks": [
{
"type": "subsup",
"attrs": {
"type": "sub"
}
}
]
}
]
}
]
}
This is <del>stricken</del> and <sup>superscripted</sup> and
<sub>subscripted</sub>
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Hello "
},
{
"type": "text",
"text": "strong",
"marks": [
{
"type": "strong"
}
]
},
{
"type": "text",
"text": " and "
},
{
"type": "text",
"text": "emphasis",
"marks": [
{
"type": "em"
}
]
},
{
"type": "text",
"text": " and "
},
{
"type": "text",
"text": "strongly emphasised",
"marks": [
{
"type": "em"
},
{
"type": "strong"
}
]
}
]
}
]
}
Hello **strong** and *emphasis* and ***strongly emphasised***
{
"version": 1,
"type": "doc",
"content": [
{
"type": "table",
"attrs": {
"isNumberColumnEnabled": false,
"layout": "default"
},
"content": [
{
"type": "tableRow",
"content": [
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Row one, cell one",
"marks": [
{
"type": "strong"
}
]
}
]
}
]
},
{
"type": "tableCell",
"attrs": {},
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Row one, cell two"
}
]
}
]
}
]
}
]
}
]
}
<table>
<tbody>
<tr>
<td>
**Row one, cell one**
</td>
<td>
Row one, cell two
</td>
</tr>
</tbody>
</table>
{
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "This is "
},
{
"type": "text",
"text": "underlined",
"marks": [
{
"type": "underline"
}
]
},
{
"type": "text",
"text": " and "
},
{
"type": "text",
"text": "red",
"marks": [
{
"type": "textColor",
"attrs": {
"color": "#ff6347"
}
}
]
}
]
}
]
}
This is <u>underlined</u> and <span color="#ff6347">red</span>
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::Filter::JiraImport::AdfToCommonmarkFilter do
include FilterSpecHelper
let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
it 'renders a complex document' do
source = fixture_file(File.join(fixtures_path, 'complex_document.json'))
target = fixture_file(File.join(fixtures_path, 'complex_document.md'))
expect(filter(source)).to eq target
end
it 'renders original source when it is invalid JSON' do
source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
end
it 'renders original source when missing document node' do
source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
expect(filter(source)).to eq "Invalid Atlassian Document Format JSON\n\n#{source}"
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Banzai::Pipeline::JiraImport::AdfCommonmarkPipeline do
let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
it 'converts text in Atlassian Document Format ' do
source = fixture_file(File.join(fixtures_path, 'paragraph.json'))
target = fixture_file(File.join(fixtures_path, 'paragraph.md'))
output = described_class.call(source, {})[:output]
expect(output).to eq target
end
end
# frozen_string_literal: true
require 'spec_helper'
context Kramdown::Parser::AtlassianDocumentFormat do
let_it_be(:options) { { input: 'AtlassianDocumentFormat', html_tables: true } }
let_it_be(:fixtures_path) { 'lib/kramdown/atlassian_document_format' }
context 'markdown render' do
shared_examples 'render elements to markdown' do |base_name|
let(:json_file) { "#{base_name}.json" }
let(:markdown_file) { "#{base_name}.md" }
it "renders #{base_name}" do
source = fixture_file(File.join(fixtures_path, json_file))
target = fixture_file(File.join(fixtures_path, markdown_file))
parser = Kramdown::Document.new(source, options)
expect(parser.to_commonmark).to eq target
end
end
it_behaves_like 'render elements to markdown', 'blockquote'
it_behaves_like 'render elements to markdown', 'bullet_list'
it_behaves_like 'render elements to markdown', 'code_block'
it_behaves_like 'render elements to markdown', 'emoji'
it_behaves_like 'render elements to markdown', 'hard_break'
it_behaves_like 'render elements to markdown', 'heading'
it_behaves_like 'render elements to markdown', 'inline_card'
it_behaves_like 'render elements to markdown', 'media_group'
it_behaves_like 'render elements to markdown', 'media_single'
it_behaves_like 'render elements to markdown', 'mention'
it_behaves_like 'render elements to markdown', 'ordered_list'
it_behaves_like 'render elements to markdown', 'panel'
it_behaves_like 'render elements to markdown', 'paragraph'
it_behaves_like 'render elements to markdown', 'rule'
it_behaves_like 'render elements to markdown', 'table'
it_behaves_like 'render elements to markdown', 'strong_em_mark'
it_behaves_like 'render elements to markdown', 'code_mark'
it_behaves_like 'render elements to markdown', 'link_mark'
it_behaves_like 'render elements to markdown', 'strike_sup_sub_mark'
it_behaves_like 'render elements to markdown', 'underline_text_color_mark'
it_behaves_like 'render elements to markdown', 'complex_document'
it 'renders header id to html' do
source = fixture_file(File.join(fixtures_path, 'heading.json'))
parser = Kramdown::Document.new(source, options)
expect(parser.to_html).to include('id="header-2"')
end
it 'logs an error with invalid json' do
source = fixture_file(File.join(fixtures_path, 'invalid_json.json'))
expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
expect(Gitlab::AppLogger).to receive(:error).with(any_args)
expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
end
it 'logs an error if no valid document node' do
source = fixture_file(File.join(fixtures_path, 'invalid_no_doc.json'))
expect(Gitlab::AppLogger).to receive(:error).with(/Invalid Atlassian Document Format JSON/)
expect(Gitlab::AppLogger).to receive(:error).with(any_args)
expect { Kramdown::Document.new(source, options) }.to raise_error(::Kramdown::Error, /Invalid Atlassian Document Format JSON/)
end
it 'invalid node gets ignored' do
source = fixture_file(File.join(fixtures_path, 'invalid_node_type.json'))
parser = Kramdown::Document.new(source, options)
expect(parser.to_commonmark).to eq "This is a second paragraph\n\n"
end
end
end
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