gitlab-org--gitlab-foss/app/services/task_list_toggle_service.rb

85 lines
3.2 KiB
Ruby
Raw Normal View History

2019-01-24 22:13:35 +00:00
# frozen_string_literal: true
# Finds the correct checkbox in the passed in markdown/html and toggles it's state,
2019-01-25 18:50:21 +00:00
# returning the updated markdown/html.
2019-01-24 22:13:35 +00:00
# We don't care if the text has changed above or below the specific checkbox, as long
2019-01-25 18:50:21 +00:00
# the checkbox still exists at exactly the same line number and the text is equal.
2019-01-24 22:13:35 +00:00
# If successful, new values are available in `updated_markdown` and `updated_markdown_html`
2019-01-25 18:50:21 +00:00
#
# Note: once we've removed RedCarpet support, we can remove the `index` and `sourcepos`
# parameters
2019-01-24 22:13:35 +00:00
class TaskListToggleService
attr_reader :updated_markdown, :updated_markdown_html
2019-01-29 19:11:04 +00:00
def initialize(markdown, markdown_html, line_source:, line_number:, toggle_as_checked:, index:, sourcepos: true)
2019-01-24 22:13:35 +00:00
@markdown, @markdown_html = markdown, markdown_html
@line_source, @line_number = line_source, line_number
2019-01-29 19:11:04 +00:00
@toggle_as_checked = toggle_as_checked
2019-01-25 18:50:21 +00:00
@index, @use_sourcepos = index, sourcepos
2019-01-24 22:13:35 +00:00
@updated_markdown, @updated_markdown_html = nil
end
def execute
return false unless markdown && markdown_html
2019-01-29 19:11:04 +00:00
!!(toggle_markdown && toggle_markdown_html)
2019-01-24 22:13:35 +00:00
end
private
2019-01-29 19:11:04 +00:00
attr_reader :markdown, :markdown_html, :index, :toggle_as_checked
2019-01-25 18:50:21 +00:00
attr_reader :line_source, :line_number, :use_sourcepos
2019-01-24 22:13:35 +00:00
def toggle_markdown
2019-01-29 19:11:04 +00:00
source_lines = markdown.split("\n")
source_line_index = line_number - 1
markdown_task = source_lines[source_line_index]
2019-01-24 22:13:35 +00:00
return unless markdown_task == line_source
return unless source_checkbox = Taskable::ITEM_PATTERN.match(markdown_task)
2019-01-29 19:11:04 +00:00
currently_checked = TaskList::Item.new(source_checkbox[1]).complete?
# Check `toggle_as_checked` to make sure we don't accidentally replace
# any `[ ]` or `[x]` in the middle of the text
if currently_checked
markdown_task.sub!(Taskable::COMPLETE_PATTERN, '[ ]') unless toggle_as_checked
2019-01-24 22:13:35 +00:00
else
2019-01-29 19:11:04 +00:00
markdown_task.sub!(Taskable::INCOMPLETE_PATTERN, '[x]') if toggle_as_checked
2019-01-24 22:13:35 +00:00
end
2019-01-29 19:11:04 +00:00
source_lines[source_line_index] = markdown_task
2019-01-24 22:13:35 +00:00
@updated_markdown = source_lines.join("\n")
end
2019-01-29 19:11:04 +00:00
def toggle_markdown_html
2019-01-24 22:13:35 +00:00
html = Nokogiri::HTML.fragment(markdown_html)
2019-01-25 18:50:21 +00:00
html_checkbox = get_html_checkbox(html)
2019-01-24 22:13:35 +00:00
return unless html_checkbox
2019-01-29 19:11:04 +00:00
if toggle_as_checked
2019-01-24 22:13:35 +00:00
html_checkbox[:checked] = 'checked'
2019-01-29 19:11:04 +00:00
else
html_checkbox.remove_attribute('checked')
2019-01-24 22:13:35 +00:00
end
@updated_markdown_html = html.to_html
end
2019-01-25 18:50:21 +00:00
# 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.
2019-01-25 18:50:21 +00:00
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
end
2019-01-24 22:13:35 +00:00
end