Skip to content
Snippets Groups Projects
Commit 6c32abc5 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets
Browse files

Merge branch 'rs-task_list' into 'master'

Use task_list gem for task lists

Task Lists can now be used in comments, and they'll render in previews. :clap:

Closes internal https://dev.gitlab.org/gitlab/gitlabhq/issues/2271

See merge request !599
parents 757084f7 da8d6feb
No related branches found
No related tags found
No related merge requests found
Showing
with 229 additions and 175 deletions
Loading
Loading
@@ -35,7 +35,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
Loading
Loading
Loading
Loading
@@ -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'
Loading
Loading
@@ -251,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'
Loading
Loading
Loading
Loading
@@ -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)
Loading
Loading
@@ -597,6 +592,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)
Loading
Loading
@@ -724,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
Loading
Loading
@@ -789,6 +785,7 @@ DEPENDENCIES
spring-commands-spinach (= 1.0.0)
stamp
state_machine
task_list (~> 1.0.0)
test_after_commit
thin
tinder (~> 1.9.2)
Loading
Loading
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
#= require jquery
#= require jquery.waitforimages
#= require task_list
class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
Loading
Loading
@@ -6,11 +10,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:
Loading
Loading
@@ -22,3 +26,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
#= require jquery
#= require bootstrap
#= require task_list
class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
Loading
Loading
@@ -17,8 +21,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:
Loading
Loading
@@ -77,9 +84,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()
Loading
Loading
@@ -156,3 +160,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
#= require jquery
#= require autosave
#= require bootstrap
#= require dropzone
#= require dropzone_input
#= require gfm_auto_complete
#= require jquery.atwho
#= require task_list
class @Notes
@interval: null
 
Loading
Loading
@@ -11,6 +20,7 @@ class @Notes
@setupMainTargetNoteForm()
@cleanBinding()
@addBinding()
@initTaskList()
 
addBinding: ->
# add note to UI after creation
Loading
Loading
@@ -81,6 +91,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 =>
Loading
Loading
@@ -114,6 +127,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
Loading
Loading
@@ -268,6 +282,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
Loading
Loading
@@ -479,3 +495,13 @@ class @Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
initTaskList: ->
@enableTaskList()
$(document).on 'tasklist:changed', '.note .js-task-list-container', @updateTaskList
enableTaskList: ->
$('.note .js-task-list-container').taskList('enable')
updateTaskList: ->
$('form', this).submit()
Loading
Loading
@@ -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 {
Loading
Loading
Loading
Loading
@@ -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;
}
Loading
Loading
Loading
Loading
@@ -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(
Loading
Loading
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 = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze
TASK_PATTERN_HTML = /^<li>(?<p_tag>\s*<p>)?\[(?<checked>[ xX])\]/.freeze
# 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' : ' '
# Called by `TaskList::Summary`
def task_list_items
return [] if description.blank?
 
# 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)"
# list items, e.g. "20 tasks (12 completed, 8 remaining)"
def task_status
return nil unless description
num_tasks = 0
num_done = 0
description.scan(TASK_PATTERN_MD) do
num_tasks += 1
num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' '
end
return '' if description.blank?
 
"#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)"
sum = tasks.summary
"#{sum.item_count} tasks (#{sum.complete_count} completed, #{sum.incomplete_count} remaining)"
end
end
- 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'
 
Loading
Loading
@@ -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
Loading
Loading
= 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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -31,11 +31,13 @@
= gfm escape_once(@issue.title)
%div
- if @issue.description.present?
.description
.description{class: can?(current_user, :modify_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@issue.description, parse_tasks: true)
= markdown(@issue.description)
%textarea.hidden.js-task-list-field
= @issue.description
 
%hr
.issue-discussion
= render "projects/issues/discussion"
= render 'projects/issues/discussion'
= 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
Loading
Loading
@@ -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'
 
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -3,7 +3,9 @@
 
%div
- if @merge_request.description.present?
.description
.description{class: can?(current_user, :modify_merge_request, @merge_request) ? '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
.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 }}
.pull-right Attach files by dragging &amp; 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 &amp; 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'
Loading
Loading
@@ -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
&nbsp;
- 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
- if note_editable?(note)
= link_to '#', title: 'Edit comment', class: 'js-note-edit' do
= icon('pencil-square-o')
Edit
&nbsp;
= 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
Loading
Loading
@@ -33,24 +33,24 @@
- 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
 
 
.note-body
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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 |
Loading
Loading
@@ -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 |
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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:
###### ..Ab_c-d. e [anchor](URL) ![alt text](URL)..
will first be converted by step 1) into a string like:
Would generate the following link IDs:
 
```
..Ab_c-d. e &lt;a href="URL">anchor&lt;/a> &lt;img src="URL" alt="alt text"/>..
```
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`
 
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
 
Loading
Loading
@@ -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
Loading
Loading
@@ -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
Loading
Loading
@@ -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
<dl>
Loading
Loading
@@ -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).
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment