From bc14d223a781322d672ca54d7afa946532b66d56 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 15:02:41 -0400 Subject: [PATCH 01/26] Add task_list gem --- Gemfile | 21 +++++++++------------ Gemfile.lock | 3 +++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index ea62396d8a..05e26f5b72 100644 --- a/Gemfile +++ b/Gemfile @@ -87,20 +87,17 @@ gem "six" # Seed data gem "seed-fu" -# Markup pipeline for GitLab +# Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' - -# Markdown to HTML -gem "github-markup" - -# Required markup gems by github-markdown -gem 'redcarpet', '~> 3.2.3' +gem 'task_list', '~> 1.0.0', require: 'task_list/railtie' +gem 'github-markup' +gem 'redcarpet', '~> 3.2.3' gem 'RedCloth' -gem 'rdoc', '~>3.6' -gem 'org-ruby', '= 0.9.12' -gem 'creole', '~>0.3.6' -gem 'wikicloth', '=0.8.1' -gem 'asciidoctor', '= 0.1.4' +gem 'rdoc', '~>3.6' +gem 'org-ruby', '= 0.9.12' +gem 'creole', '~>0.3.6' +gem 'wikicloth', '=0.8.1' +gem 'asciidoctor', '= 0.1.4' # Diffs gem 'diffy', '~> 3.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 676a519790..3334917797 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -597,6 +597,8 @@ GEM stamp (0.5.0) state_machine (1.2.0) stringex (2.5.2) + task_list (1.0.2) + html-pipeline temple (0.6.7) term-ansicolor (1.2.2) tins (~> 0.8) @@ -789,6 +791,7 @@ DEPENDENCIES spring-commands-spinach (= 1.0.0) stamp state_machine + task_list (~> 1.0.0) test_after_commit thin tinder (~> 1.9.2) From 5ef8bd33d03947528979540c6f7013c2b4c2a4ce Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 15:50:22 -0400 Subject: [PATCH 02/26] Remove taskable.js --- .../javascripts/behaviors/taskable.js.coffee | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 app/assets/javascripts/behaviors/taskable.js.coffee diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee deleted file mode 100644 index ddce71c188..0000000000 --- a/app/assets/javascripts/behaviors/taskable.js.coffee +++ /dev/null @@ -1,21 +0,0 @@ -window.updateTaskState = (taskableType) -> - objType = taskableType.data - isChecked = $(this).prop("checked") - if $(this).is(":checked") - stateEvent = "task_check" - else - stateEvent = "task_uncheck" - - taskableUrl = $("form.edit-" + objType).first().attr("action") - taskableNum = taskableUrl.match(/\d+$/) - taskNum = 0 - $("li.task-list-item input:checkbox").each( (index, e) => - if e == this - taskNum = index + 1 - ) - - $.ajax - type: "PATCH" - url: taskableUrl - data: objType + "[state_event]=" + stateEvent + - "&" + objType + "[task_num]=" + taskNum From 61c73575c348b3fd06047af130680f665f8ea724 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 15:51:32 -0400 Subject: [PATCH 03/26] Add TaskList::Filter to pipeline --- lib/gitlab/markdown.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 8348e28d0f..bbae6ae3e7 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -1,4 +1,5 @@ require 'html/pipeline' +require 'task_list/filter' module Gitlab # Custom parser for GitLab-flavored Markdown @@ -76,9 +77,9 @@ module Gitlab text = result[:output].to_html(save_with: save_options) - if options[:parse_tasks] - text = parse_tasks(text) - end + # if options[:parse_tasks] + # text = parse_tasks(text) + # end text.html_safe end @@ -106,7 +107,9 @@ module Gitlab Gitlab::Markdown::SnippetReferenceFilter, Gitlab::Markdown::CommitRangeReferenceFilter, Gitlab::Markdown::CommitReferenceFilter, - Gitlab::Markdown::LabelReferenceFilter + Gitlab::Markdown::LabelReferenceFilter, + + TaskList::Filter ] end From 18baf550e1ecfd0a8cd356490c3cae4e4566fb8f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 15:51:54 -0400 Subject: [PATCH 04/26] Add task_list JS --- app/assets/javascripts/application.js.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index bb9da14701..3b8eaa143e 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -41,6 +41,7 @@ #= require shortcuts_issuable #= require shortcuts_network #= require cal-heatmap +#= require task_list #= require_tree . window.slugify = (text) -> From a3d55db91053955a36c6a48c9ad596752a3aba40 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 16:47:31 -0400 Subject: [PATCH 05/26] Update task list behavior for Issues --- app/assets/javascripts/issue.js.coffee | 27 ++++++++++++++++--- .../projects/issues/_issue_context.html.haml | 2 +- app/views/projects/issues/show.html.haml | 4 ++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 4e2e6550eb..63d38594d2 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -6,11 +6,11 @@ class @Issue $(".context .inline-update").on "change", "#issue_assignee_id", -> $(this).submit() - if $("a.btn-close").length - $("li.task-list-item input:checkbox").prop("disabled", false) + # Prevent duplicate event bindings + @disableTaskList() - $('.task-list-item input:checkbox').off('change') - $('.task-list-item input:checkbox').change('issue', updateTaskState) + if $("a.btn-close").length + @initTaskList() $('.issue-details').waitForImages -> $('.issuable-affix').affix offset: @@ -22,3 +22,22 @@ class @Issue $(@).width($(@).outerWidth()) .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> $(@).width('') + + initTaskList: -> + $('.issue-details .js-task-list-container').taskList('enable') + $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList + + disableTaskList: -> + $('.issue-details .js-task-list-container').taskList('disable') + $(document).off 'tasklist:changed', '.issue-details .js-task-list-container' + + # TODO (rspeicher): Make the issue description inline-editable like a note so + # that we can re-use its form here + updateTaskList: -> + patchData = {} + patchData['issue'] = {'description': $('.js-task-list-field', this).val()} + + $.ajax + type: 'PATCH' + url: $('form.js-issue-update').attr('action') + data: patchData diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index 7cadb9983f..323f5c84a8 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update js-issue-update'} do |f| %div.prepend-top-20 .issuable-context-title %label diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 81478dfe56..2d248c15f4 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -31,10 +31,12 @@ = gfm escape_once(@issue.title) %div - if @issue.description.present? - .description + .description.js-task-list-container .wiki = preserve do = markdown(@issue.description, parse_tasks: true) + %textarea.hidden.js-task-list-field + = @issue.description %hr .issue-discussion From 922497560eb7d619a6064d7b1d44c831ccd64ea9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 16:56:50 -0400 Subject: [PATCH 06/26] Update task list behavior for Notes --- app/assets/javascripts/notes.js.coffee | 17 +++++++++++++++++ app/views/projects/notes/_edit_form.html.haml | 5 ++--- app/views/projects/notes/_note.html.haml | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 6dfe10f000..416218d2e0 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -11,6 +11,7 @@ class @Notes @setupMainTargetNoteForm() @cleanBinding() @addBinding() + @initTaskList() addBinding: -> # add note to UI after creation @@ -81,6 +82,9 @@ class @Notes $(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-close" + $('.note .js-task-list-container').taskList('disable') + $(document).off 'tasklist:changed', '.note .js-task-list-container' + initRefresh: -> clearInterval(Notes.interval) Notes.interval = setInterval => @@ -114,6 +118,7 @@ class @Notes if @isNewNote(note) @note_ids.push(note.id) $('ul.main-notes-list').append(note.html) + @initTaskList() ### Check if note does not exists on page @@ -268,6 +273,8 @@ class @Notes note_li.replaceWith(note.html) note_li.find('.note-edit-form').hide() note_li.find('.note-body > .note-text').show() + note_li.find('js-task-list-container').taskList('enable') + @enableTaskList() ### Called in response to clicking the edit note link @@ -479,3 +486,13 @@ class @Notes else form.find('.js-note-target-reopen').text('Reopen') form.find('.js-note-target-close').text('Close') + + enableTaskList: -> + $('.note .js-task-list-container').taskList('enable') + + initTaskList: -> + @enableTaskList() + $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList + + updateTaskList: -> + $('form', this).submit() diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index acb3991d29..d2c0933f8b 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -1,9 +1,8 @@ .note-edit-form = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| = note_target_fields(note) - = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do - = render 'projects/zen', f: f, attr: :note, - classes: 'note_text js-note-text' + = render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do + = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' .comment-hints.clearfix .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 0728f8fa42..203ab816e4 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -50,7 +50,7 @@ \-1 - .note-body + .note-body.js-task-list-container .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) From ce29e5cd8f09212afd3274326917c364272a6e39 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 17:00:30 -0400 Subject: [PATCH 07/26] Update task list behavior for Merge Requests --- .../javascripts/merge_request.js.coffee | 27 ++++++++++++++++--- .../merge_requests/show/_context.html.haml | 2 +- .../merge_requests/show/_mr_box.html.haml | 4 ++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index ae5d088d59..c8affba5b2 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -17,8 +17,11 @@ class @MergeRequest disableButtonIfEmptyField '#commit_message', '.accept_merge_request' + # Prevent duplicate event bindings + @disableTaskList() + if $("a.btn-close").length - $("li.task-list-item input:checkbox").prop("disabled", false) + @initTaskList() $('.merge-request-details').waitForImages -> $('.issuable-affix').affix offset: @@ -77,9 +80,6 @@ class @MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - $('.task-list-item input:checkbox').off('change') - $('.task-list-item input:checkbox').change('merge_request', updateTaskState) - activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' this.$('.tab-content').hide() @@ -156,3 +156,22 @@ class @MergeRequest else setTimeout(merge_request.mergeInProgress, 3000) dataType: 'json' + + initTaskList: -> + $('.merge-request-details .js-task-list-container').taskList('enable') + $(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList + + disableTaskList: -> + $('.merge-request-details .js-task-list-container').taskList('disable') + $(document).off 'tasklist:changed', '.merge-request-details .js-task-list-container' + + # TODO (rspeicher): Make the merge request description inline-editable like a + # note so that we can re-use its form here + updateTaskList: -> + patchData = {} + patchData['merge_request'] = {'description': $('.js-task-list-field', this).val()} + + $.ajax + type: 'PATCH' + url: $('form.js-merge-request-update').attr('action') + data: patchData diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml index eb80391ebc..7b9ad15a84 100644 --- a/app/views/projects/merge_requests/show/_context.html.haml +++ b/app/views/projects/merge_requests/show/_context.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update js-merge-request-update'} do |f| %div.prepend-top-20 .issuable-context-title %label diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index ada9ae58b8..80cfeb1c88 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -3,7 +3,9 @@ %div - if @merge_request.description.present? - .description + .description.js-task-list-container .wiki = preserve do = markdown(@merge_request.description, parse_tasks: true) + %textarea.hidden.js-task-list-field + = @merge_request.description From e167f28549b80141165f42b567073c5dd55f80c5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 17:47:55 -0400 Subject: [PATCH 08/26] Update Taskable to use TaskList --- app/models/concerns/taskable.rb | 45 ++++++++---------------- spec/support/taskable_shared_examples.rb | 30 ++++++---------- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index bbb3b301a9..6e04578a7d 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -1,51 +1,36 @@ +require 'task_list' + # Contains functionality for objects that can have task lists in their # descriptions. Task list items can be added with Markdown like "* [x] Fix # bugs". # # Used by MergeRequest and Issue module Taskable - TASK_PATTERN_MD = /^(? *[*-] *)\[(?[ xX])\]/.freeze - TASK_PATTERN_HTML = /^
  • (?\s*

    )?\[(?[ xX])\]/.freeze + # Called by `TaskList::Summary` + def task_list_items + return [] if description.blank? - # Change the state of a task list item for this Taskable. Edit the object's - # description by finding the nth task item and changing its checkbox - # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false. - # Note: task numbering starts with 1 - def update_nth_task(n, checked) - index = 0 - check_char = checked ? 'x' : ' ' - - # Do this instead of using #gsub! so that ActiveRecord detects that a field - # has changed. - self.description = self.description.gsub(TASK_PATTERN_MD) do |match| - index += 1 - case index - when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]" - else match - end + @task_list_items ||= description.scan(TaskList::Filter::ItemPattern).collect do |item| + # ItemPattern strips out the hyphen, but Item requires it. Rabble rabble. + TaskList::Item.new("- #{item}") end + end - save + def tasks + @tasks ||= TaskList.new(self) end # Return true if this object's description has any task list items. def tasks? - description && description.match(TASK_PATTERN_MD) + tasks.summary.items? end # Return a string that describes the current state of this Taskable's task # list items, e.g. "20 tasks (12 done, 8 unfinished)" def task_status - return nil unless description + return '' if description.blank? - num_tasks = 0 - num_done = 0 - - description.scan(TASK_PATTERN_MD) do - num_tasks += 1 - num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' ' - end - - "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)" + sum = tasks.summary + "#{sum.item_count} tasks (#{sum.complete_count} done, #{sum.incomplete_count} unfinished)" end end diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb index 8e5e3a8aaf..77e0ed5f57 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/taskable_shared_examples.rb @@ -4,27 +4,13 @@ # subject { Issue or MergeRequest } shared_examples 'a Taskable' do before do - subject.description = < Date: Wed, 29 Apr 2015 17:53:32 -0400 Subject: [PATCH 09/26] Remove all references to `parse_tasks` --- app/views/projects/issues/show.html.haml | 2 +- .../merge_requests/show/_mr_box.html.haml | 2 +- lib/gitlab/markdown.rb | 31 +---- spec/helpers/gitlab_markdown_helper_spec.rb | 109 ------------------ 4 files changed, 5 insertions(+), 139 deletions(-) diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 2d248c15f4..c1438dd718 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -34,7 +34,7 @@ .description.js-task-list-container .wiki = preserve do - = markdown(@issue.description, parse_tasks: true) + = markdown(@issue.description) %textarea.hidden.js-task-list-field = @issue.description diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index 80cfeb1c88..d5aab122b7 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -6,6 +6,6 @@ .description.js-task-list-container .wiki = preserve do - = markdown(@merge_request.description, parse_tasks: true) + = markdown(@merge_request.description) %textarea.hidden.js-task-list-field = @merge_request.description diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index bbae6ae3e7..63294aa54c 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -32,9 +32,9 @@ module Gitlab # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text - # options - parse_tasks - render tasks - # - xhtml - output XHTML instead of HTML - # - reference_only_path - Use relative path for reference links + # options - A Hash of options used to customize output (default: {}): + # :xhtml - output XHTML instead of HTML + # :reference_only_path - Use relative path for reference links # project - the project # html_options - extra options for the reference links as given to link_to def gfm_with_options(text, options = {}, project = @project, html_options = {}) @@ -46,7 +46,6 @@ module Gitlab text = text.dup.to_str options.reverse_merge!( - parse_tasks: false, xhtml: false, reference_only_path: true ) @@ -77,10 +76,6 @@ module Gitlab text = result[:output].to_html(save_with: save_options) - # if options[:parse_tasks] - # text = parse_tasks(text) - # end - text.html_safe end @@ -112,25 +107,5 @@ module Gitlab TaskList::Filter ] end - - # Turn list items that start with "[ ]" into HTML checkbox inputs. - def parse_tasks(text) - li_tag = '

  • ' - unchecked_box = '' - checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />') - - # Regexp captures don't seem to work when +text+ is an - # ActiveSupport::SafeBuffer, hence the `String.new` - String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do - checked = $LAST_MATCH_INFO[:checked].downcase == 'x' - p_tag = $LAST_MATCH_INFO[:p_tag] - - if checked - "#{li_tag}#{p_tag}#{checked_box}" - else - "#{li_tag}#{p_tag}#{unchecked_box}" - end - end - end end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index b6be82e410..2f67879efd 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -43,115 +43,6 @@ describe GitlabMarkdownHelper do expect(gfm(actual)).to match(expected) end end - - context 'parse_tasks: true' do - before(:all) do - @source_text_asterisk = <<-EOT.strip_heredoc - * [ ] valid unchecked task - * [x] valid lowercase checked task - * [X] valid uppercase checked task - * [ ] valid unchecked nested task - * [x] valid checked nested task - - [ ] not an unchecked task - no list item - [x] not a checked task - no list item - - * [ ] not an unchecked task - too many spaces - * [x ] not a checked task - too many spaces - * [] not an unchecked task - no spaces - * Not a task [ ] - not at beginning - EOT - - @source_text_dash = <<-EOT.strip_heredoc - - [ ] valid unchecked task - - [x] valid lowercase checked task - - [X] valid uppercase checked task - - [ ] valid unchecked nested task - - [x] valid checked nested task - EOT - end - - it 'should render checkboxes at beginning of asterisk list items' do - rendered_text = markdown(@source_text_asterisk, parse_tasks: true) - - expect(rendered_text).to match(/ Date: Wed, 29 Apr 2015 16:11:48 -0400 Subject: [PATCH 10/26] Single quotes/icon helper in views --- .../projects/issues/_discussion.html.haml | 8 ++--- app/views/projects/issues/show.html.haml | 14 ++++---- .../merge_requests/show/_context.html.haml | 16 +++++----- app/views/projects/notes/_edit_form.html.haml | 8 ++--- app/views/projects/notes/_note.html.haml | 32 +++++++++---------- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index ceca265301..2016f5c709 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,9 +1,9 @@ - content_for :note_actions do - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' + = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue' - else - = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" + = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue' = render 'shared/show_aside' @@ -15,11 +15,11 @@ %span= pluralize(@issue.participants(current_user).count, 'participant') - @issue.participants(current_user).each do |participant| = link_to_member(@project, participant, name: false, size: 24) - .voting_notes#notes= render "projects/notes/notes_with_form" + .voting_notes#notes= render 'projects/notes/notes_with_form' %aside.col-md-3 .issuable-affix .clearfix - %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} + %span.slead.has_tooltip{title: 'Cross-project reference'} = cross_project_reference(@project, @issue) %hr .context diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index c1438dd718..4c92823503 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -13,17 +13,17 @@ .pull-right - if can?(current_user, :write_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-grouped new-issue-link", title: "New Issue", id: "new_issue_link" do - %i.fa.fa-plus + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do + = icon('plus') New Issue - if can?(current_user, :modify_issue, @issue) - if @issue.closed? - = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen' - else - = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" + = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue' - = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do - %i.fa.fa-pencil-square-o + = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do + = icon('pencil-square-o') Edit %hr @@ -40,4 +40,4 @@ %hr .issue-discussion - = render "projects/issues/discussion" + = render 'projects/issues/discussion' diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml index 7b9ad15a84..a5a821c184 100644 --- a/app/views/projects/merge_requests/show/_context.html.haml +++ b/app/views/projects/merge_requests/show/_context.html.haml @@ -19,13 +19,13 @@ %span.back-to-milestone = link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do %strong - %i.fa.fa-clock-o + = icon('clock-o') = @merge_request.milestone.title - else none .issuable-context-selectbox - if can?(current_user, :modify_merge_request, @merge_request) - = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'}) + = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'}) = hidden_field_tag :merge_request_context = f.submit class: 'btn' @@ -35,13 +35,13 @@ %label Subscription: %button.btn.btn-block.subscribe-button{:type => 'button'} - %i.fa.fa-eye - %span= @merge_request.subscribed?(current_user) ? "Unsubscribe" : "Subscribe" - - subscribtion_status = @merge_request.subscribed?(current_user) ? "subscribed" : "unsubscribed" - .subscription-status{"data-status" => subscribtion_status} - .description-block.unsubscribed{class: ( "hidden" if @merge_request.subscribed?(current_user) )} + = icon('eye') + %span= @merge_request.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe' + - subscribtion_status = @merge_request.subscribed?(current_user) ? 'subscribed' : 'unsubscribed' + .subscription-status{data: {status: subscribtion_status}} + .description-block.unsubscribed{class: ( 'hidden' if @merge_request.subscribed?(current_user) )} You're not receiving notifications from this thread. - .description-block.subscribed{class: ( "hidden" unless @merge_request.subscribed?(current_user) )} + .description-block.subscribed{class: ( 'hidden' unless @merge_request.subscribed?(current_user) )} You're receiving notifications because you're subscribed to this thread. :coffeescript diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml index d2c0933f8b..a663950f03 100644 --- a/app/views/projects/notes/_edit_form.html.haml +++ b/app/views/projects/notes/_edit_form.html.haml @@ -5,10 +5,10 @@ = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' .comment-hints.clearfix - .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }} + .pull-right Attach files by dragging & dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }. .note-form-actions .buttons - = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button" - = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" \ No newline at end of file + = f.submit 'Save Comment', class: 'btn btn-primary btn-save btn-grouped js-comment-button' + = link_to 'Cancel', '#', class: 'btn btn-cancel note-edit-cancel' diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 203ab816e4..d582b247a4 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -2,28 +2,28 @@ .timeline-entry-inner .timeline-icon - if note.system - %span.fa.fa-circle + %span= icon('circle') - else = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: "avatar s40", alt: '' + = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' .timeline-content .note-header .note-actions = link_to "##{dom_id(note)}", name: dom_id(note) do - %i.fa.fa-link + = icon('link') Link here   - if can?(current_user, :admin_note, note) && note.editable? - = link_to "#", title: "Edit comment", class: "js-note-edit" do - %i.fa.fa-pencil-square-o + = link_to '#', title: 'Edit comment', class: 'js-note-edit' do + = icon('pencil-square-o') Edit   - = link_to namespace_project_note_path(@project.namespace, @project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do - %i.fa.fa-trash-o.cred + = link_to namespace_project_note_path(@project.namespace, @project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'danger js-note-delete' do + = icon('trash-o', class: 'cred') Remove - if note.system = link_to user_path(note.author) do - = image_tag avatar_icon(note.author_email), class: "avatar s16", alt: '' + = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: '' = link_to_member(@project, note.author, avatar: false) %span.author-username = '@' + note.author.username @@ -33,20 +33,20 @@ - if note.superceded?(@notes) - if note.upvote? %span.vote.upvote.label.label-gray.strikethrough - %i.fa.fa-thumbs-up + = icon('thumbs-up') \+1 - if note.downvote? %span.vote.downvote.label.label-gray.strikethrough - %i.fa.fa-thumbs-down + = icon('thumbs-down') \-1 - else - if note.upvote? %span.vote.upvote.label.label-success - %i.fa.fa-thumbs-up + = icon('thumbs-up') \+1 - if note.downvote? %span.vote.downvote.label.label-danger - %i.fa.fa-thumbs-down + = icon('thumbs-down') \-1 @@ -62,10 +62,10 @@ = link_to note.attachment.url, target: '_blank' do = image_tag note.attachment.url, class: 'note-image-attach' .attachment - = link_to note.attachment.url, target: "_blank" do - %i.fa.fa-paperclip + = link_to note.attachment.url, target: '_blank' do + = icon('paperclip') = note.attachment_identifier = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note), - title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do - %i.fa.fa-trash-o.cred + title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do + = icon('trash-o', class: 'cred') .clear From 725ff58bc891bfdad60f55db6b0dacdba11b1b20 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 20:34:43 -0400 Subject: [PATCH 11/26] Don't add js-task-list-container to things the user can't edit --- app/helpers/notes_helper.rb | 4 ++++ app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/show/_mr_box.html.haml | 2 +- app/views/projects/notes/_note.html.haml | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index ab44fa6ee4..271b53aa2b 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -9,6 +9,10 @@ module NotesHelper hidden_field_tag(:target_id, note.noteable.id) end + def note_editable?(note) + note.editable? && can?(current_user, :admin_note, note) + end + def link_to_commit_diff_line_note(note) if note.for_commit_diff_line? link_to( diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 4c92823503..ee1b2a08bc 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -31,7 +31,7 @@ = gfm escape_once(@issue.title) %div - if @issue.description.present? - .description.js-task-list-container + .description{class: can?(current_user, :modify_issue, @issue) ? 'js-task-list-container' : ''} .wiki = preserve do = markdown(@issue.description) diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index d5aab122b7..b3470ba37d 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -3,7 +3,7 @@ %div - if @merge_request.description.present? - .description.js-task-list-container + .description{class: can?(current_user, :modify_merge_request, @merge_request) ? 'js-task-list-container' : ''} .wiki = preserve do = markdown(@merge_request.description) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index d582b247a4..4d26b52df0 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -13,7 +13,7 @@ = icon('link') Link here   - - if can?(current_user, :admin_note, note) && note.editable? + - if note_editable?(note) = link_to '#', title: 'Edit comment', class: 'js-note-edit' do = icon('pencil-square-o') Edit @@ -50,7 +50,7 @@ \-1 - .note-body.js-task-list-container + .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) From 421f3f1551fee86cac163b06404c407b5ac34604 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 29 Apr 2015 20:36:12 -0400 Subject: [PATCH 12/26] Change padding for task lists in notes --- app/assets/stylesheets/pages/notes.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 97b19deb3e..589a43c426 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -62,6 +62,16 @@ ul.notes { word-wrap: break-word; @include md-typography; + // Reduce left padding of first ul element + ul.task-list:first-child { + padding-left: 10px; + + // sub-lists should be padded normally + ul { + padding-left: 20px; + } + } + hr { margin: 10px 0; } From c915c7b0010d5f9a76092622eff5f17afa072c61 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 May 2015 17:37:37 -0400 Subject: [PATCH 13/26] Add TaskList to Markdown feature spec --- spec/features/markdown_spec.rb | 15 ++++++++++++--- spec/fixtures/markdown.md.erb | 10 ++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 3528200e12..296055e89f 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -24,6 +24,7 @@ require 'erb' # -> Rinku (http, https, ftp) # -> Other schemes # -> References +# -> TaskList # -> `html_safe` # -> Template # @@ -266,6 +267,15 @@ describe 'GitLab Markdown' do expect(body).to have_selector('a.gfm.gfm-label', count: 3) end end + + describe 'Task Lists' do + it 'generates task lists' do + body = get_section('task-lists') + expect(body).to have_selector('ul.task-list', count: 2) + expect(body).to have_selector('li.task-list-item', count: 7) + expect(body).to have_selector('input[checked]', count: 3) + end + end end end @@ -276,9 +286,8 @@ end # once. Unfortunately RSpec will not let you access `let`s in a `before(:all)` # block, so we fake it by encapsulating all the shared setup in this class. # -# The class contains the raw Markup used in the test, dynamically substituting -# real objects, created from factories and setup on-demand, when referenced in -# the Markdown. +# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for +# reference to the factory-created objects. class MarkdownFeature include FactoryGirl::Syntax::Methods diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 0c14075855..09ee83ba77 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -176,3 +176,13 @@ References should be parseable even inside _!<%= merge_request.iid %>_ emphasis. - Label by name in quotes: ~"<%= label.name %>" - Ignored in code: `~<%= simple_label.name %>` - Ignored in links: [Link to ~<%= simple_label.id %>](#label-link) + +### Task Lists + +- [ ] Incomplete task 1 +- [x] Complete task 1 +- [ ] Incomplete task 2 + - [ ] Incomplete sub-task 1 + - [ ] Incomplete sub-task 2 + - [x] Complete sub-task 1 +- [X] Complete task 2 From f3d63eeb181db1c089bf9e4f5c0e296f6ca6230b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 May 2015 17:46:21 -0400 Subject: [PATCH 14/26] Remove feature tests for old task lists --- features/project/issues/issues.feature | 41 +++------------------ features/project/merge_requests.feature | 32 ----------------- features/steps/project/issues/issues.rb | 8 ----- features/steps/project/merge_requests.rb | 4 --- features/steps/shared/markdown.rb | 45 ------------------------ features/steps/shared/note.rb | 14 -------- features/steps/shared/paths.rb | 20 ----------- 7 files changed, 4 insertions(+), 160 deletions(-) diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index eb813884d1..bf84e2f8e8 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -134,48 +134,15 @@ Feature: Project Issues And I should see "Release 0.4" in issues And I should not see "Tweet control" in issues - Scenario: Issue description should render task checkboxes - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit issue page "Tasks-open" - Then I should see task checkboxes in the description - - @javascript - Scenario: Issue notes should not render task checkboxes - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit issue page "Tasks-open" - And I leave a comment with task markdown - Then I should not see task checkboxes in the comment - @javascript Scenario: Issue notes should be editable with +1 - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit issue page "Tasks-open" + Given project "Shop" have "Release 0.4" open issue + When I visit issue page "Release 0.4" And I leave a comment with a header containing "Comment with a header" Then The comment with the header should not have an ID And I edit the last comment with a +1 Then I should see +1 in the description - # Task status in issues list - - Scenario: Issues list should display task status - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit project "Shop" issues page - Then I should see the task status for the Taskable - - # Toggling task items - - @javascript - Scenario: Task checkboxes should be enabled for an open issue - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit issue page "Tasks-open" - Then Task checkboxes should be enabled - - @javascript - Scenario: Task checkboxes should be disabled for a closed issue - Given project "Shop" has "Tasks-closed" closed issue with task markdown - When I visit issue page "Tasks-closed" - Then Task checkboxes should be disabled - # Issue description preview @javascript @@ -212,8 +179,8 @@ Feature: Project Issues @javascript Scenario: I can unsubscribe from issue - Given project "Shop" has "Tasks-open" open issue with task markdown - When I visit issue page "Tasks-open" + Given project "Shop" have "Release 0.4" open issue + When I visit issue page "Release 0.4" Then I should see that I am subscribed When I click button "Unsubscribe" Then I should see that I am unsubscribed diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index cbb5c8eb39..60caf783fe 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -96,16 +96,6 @@ Feature: Project Merge Requests And I leave a comment with a header containing "Comment with a header" Then The comment with the header should not have an ID - Scenario: Merge request description should render task checkboxes - Given project "Shop" has "MR-task-open" open MR with task markdown - When I visit merge request page "MR-task-open" - Then I should see task checkboxes in the description - - Scenario: Merge request notes should not render task checkboxes - Given project "Shop" has "MR-task-open" open MR with task markdown - When I visit merge request page "MR-task-open" - Then I should not see task checkboxes in the comment - # Toggling inline comments @javascript @@ -173,28 +163,6 @@ Feature: Project Merge Requests And I click on the Changes tab via Javascript Then I should see the proper Inline and Side-by-side links - # Task status in issues list - - Scenario: Merge requests list should display task status - Given project "Shop" has "MR-task-open" open MR with task markdown - When I visit project "Shop" merge requests page - Then I should see the task status for the Taskable - - # Toggling task items - - @javascript - Scenario: Task checkboxes should be enabled for an open merge request - Given project "Shop" has "MR-task-open" open MR with task markdown - When I visit merge request page "MR-task-open" - Then Task checkboxes should be enabled - - @javascript - Scenario: Task checkboxes should be disabled for a closed merge request - Given project "Shop" has "MR-task-open" open MR with task markdown - And I visit merge request page "MR-task-open" - And I click link "Close" - Then Task checkboxes should be disabled - # Description preview @javascript diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index b8e282b202..504f0cff72 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -179,14 +179,6 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps author: project.users.first) end - step 'project "Shop" has "Tasks-open" open issue with task markdown' do - create_taskable(:issue, 'Tasks-open') - end - - step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do - create_taskable(:closed_issue, 'Tasks-closed') - end - step 'empty project "Empty Project"' do create :empty_project, name: 'Empty Project', namespace: @user.namespace end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 12d4d81159..f67e6e3d8c 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -108,10 +108,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end - step 'project "Shop" has "MR-task-open" open MR with task markdown' do - create_taskable(:merge_request, 'MR-task-open') - end - step 'I switch to the diff tab' do visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index a7231c47d1..943640007a 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -10,55 +10,10 @@ module SharedMarkdown find(:xpath, "#{node.path}/..").text.should == text end - def create_taskable(type, title) - desc_text = < Date: Mon, 4 May 2015 23:01:24 -0400 Subject: [PATCH 15/26] Update jasmine-fixture --- vendor/assets/javascripts/jasmine-fixture.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 vendor/assets/javascripts/jasmine-fixture.js diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js old mode 100644 new mode 100755 index 3815731fd2..9980aec6dd --- a/vendor/assets/javascripts/jasmine-fixture.js +++ b/vendor/assets/javascripts/jasmine-fixture.js @@ -1,4 +1,4 @@ -/* jasmine-fixture - 1.2.2 +/* jasmine-fixture - 1.3.1 * Makes injecting HTML snippets into the DOM easy & clean! * https://github.com/searls/jasmine-fixture */ @@ -8,7 +8,7 @@ (function($) { var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref; - root = this; + root = (1, eval)('this'); originalJasmineFixture = root.jasmineFixture; originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0; originalAffix = root.affix; @@ -33,7 +33,7 @@ create = function(selectorOptions, attach) { var $top; $top = null; - _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) { + _(selectorOptions.split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) { var $el; if (elementSelector === ">") { return $parent; From 968c281f403d59909ac6fcd54659ccc036c1775f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 May 2015 23:03:35 -0400 Subject: [PATCH 16/26] Make notes JS require its dependencies --- app/assets/javascripts/notes.js.coffee | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 416218d2e0..72e7492abc 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,3 +1,11 @@ +#= require autosave +#= require bootstrap +#= require dropzone +#= require dropzone_input +#= require gfm_auto_complete +#= require jquery.atwho +#= require task_list + class @Notes @interval: null @@ -487,12 +495,12 @@ class @Notes form.find('.js-note-target-reopen').text('Reopen') form.find('.js-note-target-close').text('Close') - enableTaskList: -> - $('.note .js-task-list-container').taskList('enable') - initTaskList: -> @enableTaskList() $(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList + enableTaskList: -> + $('.note .js-task-list-container').taskList('enable') + updateTaskList: -> $('form', this).submit() From c96c7cbfb15f3fae50b21862fdc5a863929ef0cd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 12:57:12 -0400 Subject: [PATCH 17/26] Make issue JS require its dependencies --- app/assets/javascripts/issue.js.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 63d38594d2..ab2435e28b 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,3 +1,5 @@ +#= require jquery.waitforimages + class @Issue constructor: -> $('.edit-issue.inline-update input[type="submit"]').hide() From 51c3393974c0702f56f22122006606d3e03b668d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 4 May 2015 23:06:57 -0400 Subject: [PATCH 18/26] Task List feature and JS specs --- spec/features/task_lists_spec.rb | 151 ++++++++++++++++++ spec/javascripts/issue_spec.js.coffee | 36 +++++ spec/javascripts/merge_request_spec.js.coffee | 36 +++++ spec/javascripts/notes_spec.js.coffee | 30 ++++ 4 files changed, 253 insertions(+) create mode 100644 spec/features/task_lists_spec.rb create mode 100644 spec/javascripts/issue_spec.js.coffee create mode 100644 spec/javascripts/merge_request_spec.js.coffee create mode 100644 spec/javascripts/notes_spec.js.coffee diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb new file mode 100644 index 0000000000..651792601e --- /dev/null +++ b/spec/features/task_lists_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' + +feature 'Task Lists' do + include Warden::Test::Helpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:user2) { create(:user) } + + let(:markdown) do + <<-MARKDOWN.strip_heredoc + This is a task list: + + - [ ] Incomplete entry 1 + - [x] Complete entry 1 + - [ ] Incomplete entry 2 + - [x] Complete entry 2 + - [ ] Incomplete entry 3 + - [ ] Incomplete entry 4 + MARKDOWN + end + + before do + Warden.test_mode! + + project.team << [user, :master] + project.team << [user2, :guest] + + login_as(user) + end + + def visit_issue(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) + end + + describe 'for Issues' do + let!(:issue) { create(:issue, description: markdown, author: user, project: project) } + + it 'renders' do + visit_issue(project, issue) + + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 6) + expect(page).to have_selector('ul input[checked]', count: 2) + end + + it 'contains the required selectors' do + visit_issue(project, issue) + + container = '.issue-details .description.js-task-list-container' + + expect(page).to have_selector(container) + expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") + expect(page).to have_selector('form.js-issue-update') + expect(page).to have_selector('a.btn-close') + end + + it 'is only editable by author' do + visit_issue(project, issue) + expect(page).to have_selector('.js-task-list-container') + + logout(:user) + + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end + + it 'provides a summary on Issues#index' do + visit namespace_project_issues_path(project.namespace, project) + expect(page).to have_content("6 tasks (2 done, 4 unfinished)") + end + end + + describe 'for Notes' do + let!(:issue) { create(:issue, author: user, project: project) } + let!(:note) { create(:note, note: markdown, noteable: issue, author: user) } + + it 'renders for note body' do + visit_issue(project, issue) + + expect(page).to have_selector('.note ul.task-list', count: 1) + expect(page).to have_selector('.note li.task-list-item', count: 6) + expect(page).to have_selector('.note ul input[checked]', count: 2) + end + + it 'contains the required selectors' do + visit_issue(project, issue) + + expect(page).to have_selector('.note .js-task-list-container') + expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') + expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') + end + + it 'is only editable by author' do + visit_issue(project, issue) + expect(page).to have_selector('.js-task-list-container') + + logout(:user) + + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end + end + + describe 'for Merge Requests' do + def visit_merge_request(project, merge) + visit namespace_project_merge_request_path(project.namespace, project, merge) + end + + let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) } + + it 'renders for description' do + visit_merge_request(project, merge) + + expect(page).to have_selector('ul.task-list', count: 1) + expect(page).to have_selector('li.task-list-item', count: 6) + expect(page).to have_selector('ul input[checked]', count: 2) + end + + it 'contains the required selectors' do + visit_merge_request(project, merge) + + container = '.merge-request-details .description.js-task-list-container' + + expect(page).to have_selector(container) + expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") + expect(page).to have_selector('form.js-merge-request-update') + expect(page).to have_selector('a.btn-close') + end + + it 'is only editable by author' do + visit_merge_request(project, merge) + expect(page).to have_selector('.js-task-list-container') + + logout(:user) + + login_as(user2) + visit current_path + expect(page).not_to have_selector('.js-task-list-container') + end + + it 'provides a summary on MergeRequests#index' do + visit namespace_project_merge_requests_path(project.namespace, project) + expect(page).to have_content("6 tasks (2 done, 4 unfinished)") + end + end +end diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee new file mode 100644 index 0000000000..13b25862f5 --- /dev/null +++ b/spec/javascripts/issue_spec.js.coffee @@ -0,0 +1,36 @@ +#= require jquery +#= require jasmine-fixture +#= require issue + +describe 'Issue', -> + describe 'task lists', -> + selectors = { + container: '.issue-details .description.js-task-list-container' + item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' + textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}' + form: 'form.js-issue-update[action="/foo"]' + close: 'a.btn-close' + } + + beforeEach -> + $container = affix(selectors.container) + + # # These two elements are siblings inside the container + $container.find('.js-task-list-container').append(affix(selectors.item)) + $container.find('.js-task-list-container').append(affix(selectors.textarea)) + + # Task lists don't get initialized unless this button exists. Not ideal. + $container.append(affix(selectors.close)) + + # This form is used to get the `update` URL. Not ideal. + $container.append(affix(selectors.form)) + + @issue = new Issue() + + it 'submits an ajax request on tasklist:changed', -> + spyOn($, 'ajax').and.callFake (req) -> + expect(req.type).toBe('PATCH') + expect(req.url).toBe('/foo') + expect(req.data.issue.description).not.toBe(null) + + $('.js-task-list-field').trigger('tasklist:changed') diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee new file mode 100644 index 0000000000..3ebc4a4eed --- /dev/null +++ b/spec/javascripts/merge_request_spec.js.coffee @@ -0,0 +1,36 @@ +#= require jquery +#= require jasmine-fixture +#= require merge_request + +describe 'MergeRequest', -> + describe 'task lists', -> + selectors = { + container: '.merge-request-details .description.js-task-list-container' + item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' + textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}' + form: 'form.js-merge-request-update[action="/foo"]' + close: 'a.btn-close' + } + + beforeEach -> + $container = affix(selectors.container) + + # # These two elements are siblings inside the container + $container.find('.js-task-list-container').append(affix(selectors.item)) + $container.find('.js-task-list-container').append(affix(selectors.textarea)) + + # Task lists don't get initialized unless this button exists. Not ideal. + $container.append(affix(selectors.close)) + + # This form is used to get the `update` URL. Not ideal. + $container.append(affix(selectors.form)) + + @merge = new MergeRequest({}) + + it 'submits an ajax request on tasklist:changed', -> + spyOn($, 'ajax').and.callFake (req) -> + expect(req.type).toBe('PATCH') + expect(req.url).toBe('/foo') + expect(req.data.merge_request.description).not.toBe(null) + + $('.js-task-list-field').trigger('tasklist:changed') diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee new file mode 100644 index 0000000000..de2e8e7f6c --- /dev/null +++ b/spec/javascripts/notes_spec.js.coffee @@ -0,0 +1,30 @@ +#= require jquery +#= require jasmine-fixture +#= require notes + +window.gon = {} +window.disableButtonIfEmptyField = -> null + +describe 'Notes', -> + describe 'task lists', -> + selectors = { + container: 'li.note .js-task-list-container' + item: '.note-text ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' + textarea: '.note-edit-form form textarea.js-task-list-field{- [ ] Task List Item}' + } + + beforeEach -> + $container = affix(selectors.container) + + # These two elements are siblings inside the container + $container.find('.js-task-list-container').append(affix(selectors.item)) + $container.find('.js-task-list-container').append(affix(selectors.textarea)) + + @notes = new Notes() + + it 'submits the form on tasklist:changed', -> + submitted = false + $('form').on 'submit', (e) -> submitted = true; e.preventDefault() + + $('.js-task-list-field').trigger('tasklist:changed') + expect(submitted).toBe(true) From b519e2312fc52c5065846c67d8e9ca1ce195ddc2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 15:12:57 -0400 Subject: [PATCH 19/26] Disable Rack::MiniProfiler for Jasmine's specs route --- config/initializers/6_rack_profiler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb index bdad6202b1..38a5fa98dc 100644 --- a/config/initializers/6_rack_profiler.rb +++ b/config/initializers/6_rack_profiler.rb @@ -5,4 +5,5 @@ if Rails.env.development? Rack::MiniProfilerRails.initialize!(Rails.application) Rack::MiniProfiler.config.position = 'right' Rack::MiniProfiler.config.start_hidden = true + Rack::MiniProfiler.config.skip_paths << '/specs' end From 33836ae02c160657c4320e41916f8162ebfab635 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 15:29:46 -0400 Subject: [PATCH 20/26] Remove unneeded jasmine setup --- spec/javascripts/helpers/.gitkeep | 0 spec/javascripts/support/jasmine.yml | 5 ----- 2 files changed, 5 deletions(-) delete mode 100644 spec/javascripts/helpers/.gitkeep diff --git a/spec/javascripts/helpers/.gitkeep b/spec/javascripts/helpers/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml index f4b01c9f27..168c961864 100644 --- a/spec/javascripts/support/jasmine.yml +++ b/spec/javascripts/support/jasmine.yml @@ -9,11 +9,6 @@ # defaults to spec/javascripts spec_dir: spec/javascripts -# list of file expressions to include as helpers into spec runner -# relative path from spec_dir -helpers: - - "helpers/**/*.{js.coffee,js,coffee}" - # list of file expressions to include as specs into spec runner # relative path from spec_dir spec_files: From d5ee54df163e0b2e613701ba107d6163be656930 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 15:31:08 -0400 Subject: [PATCH 21/26] More JS dependency requirements --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/issue.js.coffee | 2 ++ app/assets/javascripts/merge_request.js.coffee | 4 ++++ app/assets/javascripts/notes.js.coffee | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 3b8eaa143e..bb9da14701 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -41,7 +41,6 @@ #= require shortcuts_issuable #= require shortcuts_network #= require cal-heatmap -#= require task_list #= require_tree . window.slugify = (text) -> diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index ab2435e28b..86ad3d03ba 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,4 +1,6 @@ +#= require jquery #= require jquery.waitforimages +#= require task_list class @Issue constructor: -> diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index c8affba5b2..7c1e2b822d 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,3 +1,7 @@ +#= require jquery +#= require bootstrap +#= require task_list + class @MergeRequest constructor: (@opts) -> @initContextWidget() diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 72e7492abc..c25b1ddb06 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,3 +1,4 @@ +#= require jquery #= require autosave #= require bootstrap #= require dropzone From 0a99e6e2facee4126509c41b987aa88f1520626e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 15:31:24 -0400 Subject: [PATCH 22/26] Remove 'jasmine' gem jasmine-rails still requires it as a dependency, but for some reason running `rake jasmine:ci` produced test failures but `rake spec:javascript` didn't. --- Gemfile | 1 - Gemfile.lock | 6 ------ lib/tasks/jasmine.rake | 12 ++++++++++++ 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 lib/tasks/jasmine.rake diff --git a/Gemfile b/Gemfile index 05e26f5b72..f950c5be15 100644 --- a/Gemfile +++ b/Gemfile @@ -248,7 +248,6 @@ group :development, :test do # PhantomJS driver for Capybara gem 'poltergeist', '~> 1.5.1' - gem 'jasmine', '~> 2.2.0' gem 'jasmine-rails' gem "spring", '~> 1.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index 3334917797..6f58c4f4fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -290,11 +290,6 @@ GEM i18n (0.7.0) ice_cube (0.11.1) ice_nine (0.10.0) - jasmine (2.2.0) - jasmine-core (~> 2.2) - phantomjs - rack (>= 1.2.1) - rake jasmine-core (2.2.0) jasmine-rails (0.10.8) jasmine-core (>= 1.3, < 3.0) @@ -726,7 +721,6 @@ DEPENDENCIES hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty - jasmine (~> 2.2.0) jasmine-rails jquery-atwho-rails (~> 1.0.0) jquery-rails diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake new file mode 100644 index 0000000000..9e2cceffa1 --- /dev/null +++ b/lib/tasks/jasmine.rake @@ -0,0 +1,12 @@ +# Since we no longer explicitly require the 'jasmine' gem, we lost the +# `jasmine:ci` task used by GitLab CI jobs. +# +# This provides a simple alias to run the `spec:javascript` task from the +# 'jasmine-rails' gem. +task jasmine: ['jasmine:ci'] + +namespace :jasmine do + task :ci do + Rake::Task['spec:javascript'].invoke + end +end From 1108915f758db254187d47d6a2e626ed6abcbbf3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 17:27:10 -0400 Subject: [PATCH 23/26] Change wording for task list summaries --- app/models/concerns/taskable.rb | 4 ++-- spec/features/task_lists_spec.rb | 4 ++-- spec/support/taskable_shared_examples.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 6e04578a7d..33b4814d7e 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -26,11 +26,11 @@ module Taskable end # Return a string that describes the current state of this Taskable's task - # list items, e.g. "20 tasks (12 done, 8 unfinished)" + # list items, e.g. "20 tasks (12 completed, 8 remaining)" def task_status return '' if description.blank? sum = tasks.summary - "#{sum.item_count} tasks (#{sum.complete_count} done, #{sum.incomplete_count} unfinished)" + "#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)" end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 651792601e..2099fc40cc 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -69,7 +69,7 @@ feature 'Task Lists' do it 'provides a summary on Issues#index' do visit namespace_project_issues_path(project.namespace, project) - expect(page).to have_content("6 tasks (2 done, 4 unfinished)") + expect(page).to have_content("6 tasks (2 completed, 4 remaining)") end end @@ -145,7 +145,7 @@ feature 'Task Lists' do it 'provides a summary on MergeRequests#index' do visit namespace_project_merge_requests_path(project.namespace, project) - expect(page).to have_content("6 tasks (2 done, 4 unfinished)") + expect(page).to have_content("6 tasks (2 completed, 4 remaining)") end end end diff --git a/spec/support/taskable_shared_examples.rb b/spec/support/taskable_shared_examples.rb index 77e0ed5f57..927c72c740 100644 --- a/spec/support/taskable_shared_examples.rb +++ b/spec/support/taskable_shared_examples.rb @@ -15,8 +15,8 @@ shared_examples 'a Taskable' do it 'returns the correct task status' do expect(subject.task_status).to match('5 tasks') - expect(subject.task_status).to match('2 done') - expect(subject.task_status).to match('3 unfinished') + expect(subject.task_status).to match('2 completed') + expect(subject.task_status).to match('3 remaining') end describe '#tasks?' do From 0cc75363415a4f60100a230d78945465f9cefdcc Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 18:38:43 -0400 Subject: [PATCH 24/26] Fix header link anchors --- app/assets/stylesheets/generic/typography.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 36a9a54074..e559089794 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -37,7 +37,9 @@ pre { position: relative; a.anchor { - display: none; + // Setting `display: none` would prevent the anchor being scrolled to, so + // instead we set the height to 0 and it gets updated on hover. + height: 0; } &:hover > a.anchor { From cca28c79201aea3703c46289e22d5ff75faf10d6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 18:55:57 -0400 Subject: [PATCH 25/26] Update Markdown help docs for latest changes --- doc/markdown/markdown.md | 93 ++++++++++--------- .../markdown/table_of_contents_filter.rb | 2 +- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 8ec5a20035..e95ddbb757 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -172,7 +172,7 @@ GFM will turn that reference into a link so you can navigate between them easily GFM will recognize the following: | input | references | -|-----------------------:|:---------------------------| +|:-----------------------|:---------------------------| | `@user_name` | specific user | | `@group_name` | specific group | | `@all` | entire team | @@ -189,7 +189,7 @@ GFM will recognize the following: GFM also recognizes certain cross-project references: | input | references | -|----------------------------------------:|:------------------------| +|:----------------------------------------|:------------------------| | `namespace/project#123` | issue | | `namespace/project!123` | merge request | | `namespace/project$123` | snippet | @@ -198,15 +198,23 @@ GFM also recognizes certain cross-project references: ## Task Lists -You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so: +You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: ```no-highlight -* [x] Completed task -* [ ] Unfinished task - * [x] Nested task +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 ``` -Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes. +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 + +Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. # Standard Markdown @@ -246,51 +254,38 @@ Alt-H2 ### Header IDs and links -All markdown rendered headers automatically get IDs, except for comments. +All Markdown-rendered headers automatically get IDs, except in comments. On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else. The IDs are generated from the content of the header according to the following rules: -1. remove the heading hashes `#` and process the rest of the line as it would be processed if it were not a header -2. from the result, remove all HTML tags, but keep their inner content -3. convert all characters to lowercase -4. convert all characters except `[a-z0-9_-]` into hyphens `-` -5. transform multiple adjacent hyphens into a single hyphen -6. remove trailing and heading hyphens +1. All text is converted to lowercase +1. All non-word text (e.g., punctuation, HTML) is removed +1. All spaces are converted to hyphens +1. Two or more hyphens in a row are converted to one +1. If a header with the same ID has already been generated, a unique + incrementing number is appended. For example: ``` -###### ..Ab_c-d. e [anchor](URL) ![alt text](URL).. +# This header has spaces in it +## This header has a :thumbsup: in it +# This header has Unicode in it: 한글 +## This header has spaces in it +### This header has spaces in it ``` -which renders as: +Would generate the following link IDs: -###### ..Ab_c-d. e [anchor](URL) ![alt text](URL).. +1. `this-header-has-spaces-in-it` +1. `this-header-has-a-in-it` +1. `this-header-has-unicode-in-it-한글` +1. `this-header-has-spaces-in-it-1` +1. `this-header-has-spaces-in-it-2` -will first be converted by step 1) into a string like: - -``` -..Ab_c-d. e <a href="URL">anchor</a> <img src="URL" alt="alt text"/>.. -``` - -After removing the tags in step 2) we get: - -``` -..Ab_c-d. e anchor .. -``` - -And applying all the other steps gives the id: - -``` -ab_c-d-e-anchor -``` - -Note in particular how: - -- for markdown anchors `[text](URL)`, only the `text` is used -- markdown images `![alt](URL)` are completely ignored +Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. ## Emphasis @@ -322,8 +317,6 @@ Strikethrough uses two tildes. ~~Scratch this.~~ 1. Ordered sub-list 4. And another item. - Some text that should be aligned with the above item. - * Unordered list can use asterisks - Or minuses + Or pluses @@ -336,8 +329,6 @@ Strikethrough uses two tildes. ~~Scratch this.~~ 1. Ordered sub-list 4. And another item. - Some text that should be aligned with the above item. - * Unordered list can use asterisks - Or minuses + Or pluses @@ -432,7 +423,7 @@ Quote break. You can also use raw HTML in your Markdown, and it'll mostly work pretty well. -See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows the `class`, `id`, and `style` attributes. +See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements, as well as the `class`, and `id` attributes on all elements. ```no-highlight
    @@ -536,6 +527,20 @@ Code above produces next output: The row of dashes between the table header and body must have at least three dashes in each column. +By including colons in the header row, you can align the text within that column: + +``` +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +``` + +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | + ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/gitlab/markdown/table_of_contents_filter.rb index c97612dafb..38887c9778 100644 --- a/lib/gitlab/markdown/table_of_contents_filter.rb +++ b/lib/gitlab/markdown/table_of_contents_filter.rb @@ -31,7 +31,7 @@ module Gitlab id = text.downcase id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation id.gsub!(' ', '-') # replace spaces with dash - id.squeeze!(' -') # replace multiple spaces or dashes with one + id.squeeze!('-') # replace multiple dashes with one uniq = (headers[id] > 0) ? "-#{headers[id]}" : '' headers[id] += 1 From da8d6feb2c9a273a32b072fbed30f958435c2eeb Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 21:06:18 -0400 Subject: [PATCH 26/26] Add CHANGELOG entry for latest task lists [ci skip] --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index c7fb49dc18..f471d46de1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,7 +32,7 @@ v 7.11.0 (unreleased) - Show incompatible projects in Google Code import status (Stan Hu) - Fix bug where commit data would not appear in some subdirectories (Stan Hu) - Unescape branch names in compare commit (Stan Hu) - - + - Task lists are now usable in comments, and will show up in Markdown previews. - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content