From 74ed98ae0202dc4afd11a701ca2a499c580d4523 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 23 Sep 2020 02:33:47 -0500 Subject: [PATCH] Add indeterminate checkboxes to GFM --- .../markdown/nodes/task_list_item.js | 31 +++++++++++--- app/assets/javascripts/task_list.js | 17 +++++++- .../unreleased/indeterminate-checkbox-gfm.yml | 5 +++ doc/user/img/completed_tasks_v13_3.png | Bin 10844 -> 0 bytes doc/user/img/completed_tasks_v13_5.png | Bin 0 -> 19150 bytes doc/user/markdown.md | 27 ++++++++----- .../filter/indeterminate_checkbox_filter.rb | 36 +++++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 3 +- spec/features/markdown/copy_as_gfm_spec.rb | 5 +++ .../indeterminate_checkbox_filter_spec.rb | 38 ++++++++++++++++++ 10 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 changelogs/unreleased/indeterminate-checkbox-gfm.yml delete mode 100644 doc/user/img/completed_tasks_v13_3.png create mode 100644 doc/user/img/completed_tasks_v13_5.png create mode 100644 lib/banzai/filter/indeterminate_checkbox_filter.rb create mode 100644 spec/lib/banzai/filter/indeterminate_checkbox_filter_spec.rb diff --git a/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js index 56c2b17286da74..8932385565ecf3 100644 --- a/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js +++ b/app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js @@ -12,8 +12,8 @@ export default class TaskListItem extends Node { get schema() { return { attrs: { - done: { - default: false, + state: { + default: null, }, }, defining: true, @@ -25,7 +25,13 @@ export default class TaskListItem extends Node { tag: 'li.task-list-item', getAttrs: (el) => { const checkbox = el.querySelector('input[type=checkbox].task-list-item-checkbox'); - return { done: checkbox && checkbox.checked }; + if (checkbox?.matches('.indeterminate')) { + return { state: 'indeterminate' }; + } else if (checkbox?.checked) { + return { state: 'done' }; + } + + return {}; }, }, ], @@ -35,7 +41,12 @@ export default class TaskListItem extends Node { { class: 'task-list-item' }, [ 'input', - { type: 'checkbox', class: 'task-list-item-checkbox', checked: node.attrs.done }, + { + type: 'checkbox', + class: 'task-list-item-checkbox', + checked: node.attrs.state === 'done', + indeterminate: node.attrs.state === 'indeterminate', + }, ], ['div', { class: 'todo-content' }, 0], ]; @@ -44,7 +55,17 @@ export default class TaskListItem extends Node { } toMarkdown(state, node) { - state.write(`[${node.attrs.done ? 'x' : ' '}] `); + switch (node.attrs.state) { + case 'done': + state.write('[x] '); + break; + case 'indeterminate': + state.write('[-] '); + break; + default: + state.write('[ ] '); + break; + } state.renderContent(node); } } diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js index 81d9d9d37a707e..6384969f987ff8 100644 --- a/app/assets/javascripts/task_list.js +++ b/app/assets/javascripts/task_list.js @@ -40,20 +40,33 @@ export default class TaskList { taskListField.value = taskListField.dataset.value; }); - $(this.taskListContainerSelector).taskList('enable'); - $(document).on('tasklist:changed', this.taskListContainerSelector, this.updateHandler); + this.enable(); } getTaskListTarget(e) { return e && e.currentTarget ? $(e.currentTarget) : $(this.taskListContainerSelector); } + updateIndeterminateTaskListItems(e) { + this.getTaskListTarget(e) + .find('.task-list-item-checkbox.indeterminate') + .prop('indeterminate', true) + .prop('disabled', true); + } + disableTaskListItems(e) { this.getTaskListTarget(e).taskList('disable'); + this.updateIndeterminateTaskListItems(); } enableTaskListItems(e) { this.getTaskListTarget(e).taskList('enable'); + this.updateIndeterminateTaskListItems(); + } + + enable() { + this.enableTaskListItems(); + $(document).on('tasklist:changed', this.taskListContainerSelector, this.updateHandler); } disable() { diff --git a/changelogs/unreleased/indeterminate-checkbox-gfm.yml b/changelogs/unreleased/indeterminate-checkbox-gfm.yml new file mode 100644 index 00000000000000..d1b086959e4978 --- /dev/null +++ b/changelogs/unreleased/indeterminate-checkbox-gfm.yml @@ -0,0 +1,5 @@ +--- +title: Render `[-]` as an indeterminate checkbox in GitLab Flavored Markdown +merge_request: 43208 +author: Ethan Reesor (@firelizzard) +type: added diff --git a/doc/user/img/completed_tasks_v13_3.png b/doc/user/img/completed_tasks_v13_3.png deleted file mode 100644 index 31e051852cbc408d18199ce657ad5d64b33c3b95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10844 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 M>FVdQ&MBb@04B9Gs{jB1 diff --git a/doc/user/img/completed_tasks_v13_5.png b/doc/user/img/completed_tasks_v13_5.png new file mode 100644 index 0000000000000000000000000000000000000000..7316069177efcd24ccf2107d7f9e2f23176e7b5b GIT binary patch literal 19150 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 qwwkfBa~ZG9|XxORs}A&wIN1xvX Indeterminate checkboxes [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43208) in GitLab 13.5. + If this section is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#task-lists). -You can add task lists anywhere Markdown is supported, but you can only "click" -to toggle the boxes if they are in issues, merge requests, or comments. In other -places you must edit the Markdown manually to change the status by adding or -removing an `x` within the square brackets. +You can add task lists anywhere Markdown is supported, but you can only click to +toggle the boxes if they are in issues, merge requests, epics, or comments. In +other places you must edit the Markdown manually to change the status by adding +or removing an `x` within the square brackets. + +Besides complete and incomplete, tasks can also be indeterminate. An +indeterminate checkbox cannot be modified; clicking on an indeterminate checkbox +in an issue, merge request, or comment will have no effect. To create a task list, add a specially-formatted Markdown list. You can use either unordered or ordered lists: ```markdown - [x] Completed task +- [-] Indeterminate task - [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 + - [x] Sub-task 1 + - [-] Sub-task 2 - [ ] Sub-task 3 1. [x] Completed task +1. [-] Indeterminate task 1. [ ] Incomplete task - 1. [ ] Sub-task 1 - 1. [x] Sub-task 2 + 1. [x] Sub-task 1 + 1. [-] Sub-task 2 + 1. [ ] Sub-task 3 ``` -![A task list as rendered by the GitLab interface](img/completed_tasks_v13_3.png) +![A task list as rendered by the GitLab interface](img/completed_tasks_v13_5.png) ### Table of contents diff --git a/lib/banzai/filter/indeterminate_checkbox_filter.rb b/lib/banzai/filter/indeterminate_checkbox_filter.rb new file mode 100644 index 00000000000000..81a38a2ca364a9 --- /dev/null +++ b/lib/banzai/filter/indeterminate_checkbox_filter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "nokogiri" + +module Banzai + module Filter + class IndeterminateCheckboxFilter < HTML::Pipeline::Filter + SELECTOR = "li:contains('[-]')" + REGEX = /^(\s*)\[-\]/.freeze + + BOX = Nokogiri::HTML::DocumentFragment.parse( + '' + ).children.first.freeze + + def call + return doc unless doc.at(SELECTOR) + + doc.css(SELECTOR).each do |li| + next unless li.content.match(REGEX) + + text = li.children.first + next unless text.is_a?(Nokogiri::XML::Text) + next unless text.content.match(REGEX) + + text.add_previous_sibling(BOX.dup) + text.content = text.content.sub(REGEX, '\1') + + li.add_class('task-list-item') + li.parent.add_class('task-list') + end + + doc + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 344afc9b33c63e..4da59eb7694770 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -35,8 +35,9 @@ def self.filters *reference_filters, Filter::EmojiFilter, Filter::TaskListFilter, + Filter::IndeterminateCheckboxFilter, Filter::InlineDiffFilter, - Filter::SetDirectionFilter + Filter::SetDirectionFilter, ] end diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index c9dc764f93bb8a..1373431b1c3f1a 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -614,6 +614,11 @@ def foo | b | 0 | 1 | GFM ) + + verify( + 'IndeterminateCheckboxFilter', + '* [-] foo' + ) end alias_method :gfm_to_html, :markdown diff --git a/spec/lib/banzai/filter/indeterminate_checkbox_filter_spec.rb b/spec/lib/banzai/filter/indeterminate_checkbox_filter_spec.rb new file mode 100644 index 00000000000000..aa486191db4348 --- /dev/null +++ b/spec/lib/banzai/filter/indeterminate_checkbox_filter_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'html/pipeline' +require 'support/helpers/filter_spec_helper' + +RSpec.describe Banzai::Filter::IndeterminateCheckboxFilter, lib: true do + include FilterSpecHelper + shared_examples 'a valid indeterminate task list item' do |html| + it "behaves correctly for `#{html}`" do + filtered = filter("
  • #{html}
") + + expected = <<~HTML.strip_heredoc +
  • + #{described_class::BOX}#{html.sub('[-]', '')} +
+ HTML + + expect(filtered.to_html.delete("\n")).to eq(expected.delete("\n")) + end + end + + shared_examples 'an invalid indeterminate task list item' do |html| + it "does nothing for `#{html}`" do + original = "
  • #{html}
" + filtered = filter(original.dup) + + expect(filtered.to_html.delete("\n")).to eq(original.delete("\n")) + end + end + + it_behaves_like 'a valid indeterminate task list item', '[-] foobar' + it_behaves_like 'a valid indeterminate task list item', '[-] foobar' + + it_behaves_like 'an invalid indeterminate task list item', '[-] foobar' + it_behaves_like 'an invalid indeterminate task list item', 'foo [-] bar' + it_behaves_like 'an invalid indeterminate task list item', 'foo [-] bar' +end -- GitLab