fix(markdown): render ordered lists with custom values and handle multiline list items

This commit is contained in:
Paul Chavard 2023-07-26 18:55:02 +02:00
parent cf4048312e
commit 2daee794bc
3 changed files with 81 additions and 13 deletions

View file

@ -1,14 +1,14 @@
class SimpleFormatComponent < ApplicationComponent class SimpleFormatComponent < ApplicationComponent
# see: https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use # see: https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
REDCARPET_EXTENSIONS = { REDCARPET_EXTENSIONS = {
no_intra_emphasis: false, no_intra_emphasis: true,
disable_indented_code_blocks: true,
space_after_headers: true,
tables: false, tables: false,
fenced_code_blocks: false, fenced_code_blocks: false,
autolink: false, autolink: false,
disable_indented_code_blocks: false,
strikethrough: false, strikethrough: false,
lax_spacing: false, lax_spacing: false,
space_after_headers: false,
superscript: false, superscript: false,
underline: false, underline: false,
highlight: false, highlight: false,
@ -27,15 +27,28 @@ class SimpleFormatComponent < ApplicationComponent
def initialize(text, allow_a: true, class_names_map: {}) def initialize(text, allow_a: true, class_names_map: {})
@allow_a = allow_a @allow_a = allow_a
@text = (text || "").gsub(/\R/, "\n\n") # force double \n otherwise a single one won't split paragraph list_item = false
.split("\n\n") lines = (text || "")
.lines
.map(&:lstrip) # this block prevent redcarpet to consider " text" as block code by lstriping .map(&:lstrip) # this block prevent redcarpet to consider " text" as block code by lstriping
.join("\n\n")
.gsub(EMAIL_IN_TEXT_REGEX) { _1.gsub('_', '\\_') } # Workaround for redcarpet bug on autolink email having _. Cf tests
if !@allow_a @text = lines.map do |line|
@text = @text.gsub(SIMPLE_URL_REGEX) { _1.gsub('_', '\\_') } # Escape underscores in URLs item_number = line.match(/\A(\d+)\./)
if item_number.present?
list_item = true
"\n" + line + "[value:#{item_number[1]}]"
elsif line.match?(/\A[-*+]\s/)
list_item = true
"\n" + line
elsif line == ''
list_item = false
"\n" + line
elsif list_item
line
else
"\n" + line
end end
end.join.lstrip
@renderer = Redcarpet::Markdown.new( @renderer = Redcarpet::Markdown.new(
Redcarpet::BareRenderer.new(class_names_map:), Redcarpet::BareRenderer.new(class_names_map:),
@ -52,6 +65,6 @@ class SimpleFormatComponent < ApplicationComponent
end end
def attributes def attributes
['target', 'rel', 'href', 'class', 'title'] ['target', 'rel', 'href', 'class', 'title', 'value']
end end
end end

View file

@ -10,7 +10,14 @@ module Redcarpet
end end
def list_item(content, list_type) def list_item(content, list_type)
content_tag(:li, content.strip.gsub(/<\/?p>/, ''), {}, false) item_number = content.match(/\[value:(\d+)\]/)
text = content.strip
.gsub(/<\/?p>/, '')
.gsub(/\[value:\d+\]/, '')
.gsub(/\n/, '<br>')
attributes = item_number.present? ? { value: item_number[1] } : {}
content_tag(:li, text, attributes, false)
end end
def paragraph(text) def paragraph(text)

View file

@ -47,11 +47,59 @@ TEXT
<<~TEXT <<~TEXT
1. 1er paragraphe 1. 1er paragraphe
2. paragraphe 2. paragraphe
4. 4eme paragraphe
TEXT TEXT
end end
it { expect(page).to have_selector("ol", count: 1) } it { expect(page).to have_selector("ol", count: 1) }
it { expect(page).to have_selector("li", count: 2) } it { expect(page).to have_selector("li", count: 3) }
it { expect(page.native.inner_html).to match('value="1"') }
it { expect(page.native.inner_html).to match('value="4"') }
end
context 'multi line lists' do
let(:text) do
<<~TEXT
Lorsque nous souhaitons envoyer ce message :
1. Premier point de la recette
Commentaire 1
2. Deuxième point de la recette
Commentaire 2
4. Troisième point de la recette
Commentaire 3
trois nouveaux paragraphes
sur plusieures
lignes
- 1er point de la recette
* 2eme point de la recette
avec des détailles
+ 3eme point de la recette
beaucoup
de détails
conclusion
TEXT
end
it { expect(page).to have_selector("ol", count: 1) }
it { expect(page).to have_selector("ul", count: 1) }
it { expect(page).to have_selector("li", count: 6) }
it { expect(page).to have_selector("p", count: 5) }
end
context 'strong' do
let(:text) do
<<~TEXT
1er paragraphe **fort** un_mot_pas_italic
TEXT
end
it { expect(page).to have_selector("strong", count: 1) }
it { expect(page).not_to have_selector("em") }
end end
context 'auto-link' do context 'auto-link' do