259 lines
7.6 KiB
CoffeeScript
259 lines
7.6 KiB
CoffeeScript
|
# The MIT License (MIT)
|
||
|
#
|
||
|
# Copyright (c) 2014 GitHub, Inc.
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
# of this software and associated documentation files (the "Software"), to deal
|
||
|
# in the Software without restriction, including without limitation the rights
|
||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
# copies of the Software, and to permit persons to whom the Software is
|
||
|
# furnished to do so, subject to the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be included in all
|
||
|
# copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
# SOFTWARE.
|
||
|
|
||
|
# TaskList Behavior
|
||
|
#
|
||
|
#= provides tasklist:enabled
|
||
|
#= provides tasklist:disabled
|
||
|
#= provides tasklist:change
|
||
|
#= provides tasklist:changed
|
||
|
#
|
||
|
#
|
||
|
# Enables Task List update behavior.
|
||
|
#
|
||
|
# ### Example Markup
|
||
|
#
|
||
|
# <div class="js-task-list-container">
|
||
|
# <ul class="task-list">
|
||
|
# <li class="task-list-item">
|
||
|
# <input type="checkbox" class="js-task-list-item-checkbox" disabled />
|
||
|
# text
|
||
|
# </li>
|
||
|
# </ul>
|
||
|
# <form>
|
||
|
# <textarea class="js-task-list-field">- [ ] text</textarea>
|
||
|
# </form>
|
||
|
# </div>
|
||
|
#
|
||
|
# ### Specification
|
||
|
#
|
||
|
# TaskLists MUST be contained in a `(div).js-task-list-container`.
|
||
|
#
|
||
|
# TaskList Items SHOULD be an a list (`UL`/`OL`) element.
|
||
|
#
|
||
|
# Task list items MUST match `(input).task-list-item-checkbox` and MUST be
|
||
|
# `disabled` by default.
|
||
|
#
|
||
|
# TaskLists MUST have a `(textarea).js-task-list-field` form element whose
|
||
|
# `value` attribute is the source (Markdown) to be udpated. The source MUST
|
||
|
# follow the syntax guidelines.
|
||
|
#
|
||
|
# TaskList updates trigger `tasklist:change` events. If the change is
|
||
|
# successful, `tasklist:changed` is fired. The change can be canceled.
|
||
|
#
|
||
|
# jQuery is required.
|
||
|
#
|
||
|
# ### Methods
|
||
|
#
|
||
|
# `.taskList('enable')` or `.taskList()`
|
||
|
#
|
||
|
# Enables TaskList updates for the container.
|
||
|
#
|
||
|
# `.taskList('disable')`
|
||
|
#
|
||
|
# Disables TaskList updates for the container.
|
||
|
#
|
||
|
## ### Events
|
||
|
#
|
||
|
# `tasklist:enabled`
|
||
|
#
|
||
|
# Fired when the TaskList is enabled.
|
||
|
#
|
||
|
# * **Synchronicity** Sync
|
||
|
# * **Bubbles** Yes
|
||
|
# * **Cancelable** No
|
||
|
# * **Target** `.js-task-list-container`
|
||
|
#
|
||
|
# `tasklist:disabled`
|
||
|
#
|
||
|
# Fired when the TaskList is disabled.
|
||
|
#
|
||
|
# * **Synchronicity** Sync
|
||
|
# * **Bubbles** Yes
|
||
|
# * **Cancelable** No
|
||
|
# * **Target** `.js-task-list-container`
|
||
|
#
|
||
|
# `tasklist:change`
|
||
|
#
|
||
|
# Fired before the TaskList item change takes affect.
|
||
|
#
|
||
|
# * **Synchronicity** Sync
|
||
|
# * **Bubbles** Yes
|
||
|
# * **Cancelable** Yes
|
||
|
# * **Target** `.js-task-list-field`
|
||
|
#
|
||
|
# `tasklist:changed`
|
||
|
#
|
||
|
# Fired once the TaskList item change has taken affect.
|
||
|
#
|
||
|
# * **Synchronicity** Sync
|
||
|
# * **Bubbles** Yes
|
||
|
# * **Cancelable** No
|
||
|
# * **Target** `.js-task-list-field`
|
||
|
#
|
||
|
# ### NOTE
|
||
|
#
|
||
|
# Task list checkboxes are rendered as disabled by default because rendered
|
||
|
# user content is cached without regard for the viewer.
|
||
|
|
||
|
incomplete = "[ ]"
|
||
|
complete = "[x]"
|
||
|
|
||
|
# Escapes the String for regular expression matching.
|
||
|
escapePattern = (str) ->
|
||
|
str.
|
||
|
replace(/([\[\]])/g, "\\$1"). # escape square brackets
|
||
|
replace(/\s/, "\\s"). # match all white space
|
||
|
replace("x", "[xX]") # match all cases
|
||
|
|
||
|
incompletePattern = ///
|
||
|
#{escapePattern(incomplete)}
|
||
|
///
|
||
|
completePattern = ///
|
||
|
#{escapePattern(complete)}
|
||
|
///
|
||
|
|
||
|
# Pattern used to identify all task list items.
|
||
|
# Useful when you need iterate over all items.
|
||
|
itemPattern = ///
|
||
|
^
|
||
|
(?: # prefix, consisting of
|
||
|
\s* # optional leading whitespace
|
||
|
(?:>\s*)* # zero or more blockquotes
|
||
|
(?:[-+*]|(?:\d+\.)) # list item indicator
|
||
|
)
|
||
|
\s* # optional whitespace prefix
|
||
|
( # checkbox
|
||
|
#{escapePattern(complete)}|
|
||
|
#{escapePattern(incomplete)}
|
||
|
)
|
||
|
\s+ # is followed by whitespace
|
||
|
(?!
|
||
|
\(.*?\) # is not part of a [foo](url) link
|
||
|
)
|
||
|
(?= # and is followed by zero or more links
|
||
|
(?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
|
||
|
(?:[^\[]|$) # and either a non-link or the end of the string
|
||
|
)
|
||
|
///
|
||
|
|
||
|
# Used to filter out code fences from the source for comparison only.
|
||
|
# http://rubular.com/r/x5EwZVrloI
|
||
|
# Modified slightly due to issues with JS
|
||
|
codeFencesPattern = ///
|
||
|
^`{3} # ```
|
||
|
(?:\s*\w+)? # followed by optional language
|
||
|
[\S\s] # whitespace
|
||
|
.* # code
|
||
|
[\S\s] # whitespace
|
||
|
^`{3}$ # ```
|
||
|
///mg
|
||
|
|
||
|
# Used to filter out potential mismatches (items not in lists).
|
||
|
# http://rubular.com/r/OInl6CiePy
|
||
|
itemsInParasPattern = ///
|
||
|
^
|
||
|
(
|
||
|
#{escapePattern(complete)}|
|
||
|
#{escapePattern(incomplete)}
|
||
|
)
|
||
|
.+
|
||
|
$
|
||
|
///g
|
||
|
|
||
|
# Given the source text, updates the appropriate task list item to match the
|
||
|
# given checked value.
|
||
|
#
|
||
|
# Returns the updated String text.
|
||
|
updateTaskListItem = (source, itemIndex, checked) ->
|
||
|
clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').
|
||
|
replace(itemsInParasPattern, '').split("\n")
|
||
|
index = 0
|
||
|
result = for line in source.split("\n")
|
||
|
if line in clean && line.match(itemPattern)
|
||
|
index += 1
|
||
|
if index == itemIndex
|
||
|
line =
|
||
|
if checked
|
||
|
line.replace(incompletePattern, complete)
|
||
|
else
|
||
|
line.replace(completePattern, incomplete)
|
||
|
line
|
||
|
result.join("\n")
|
||
|
|
||
|
# Updates the $field value to reflect the state of $item.
|
||
|
# Triggers the `tasklist:change` event before the value has changed, and fires
|
||
|
# a `tasklist:changed` event once the value has changed.
|
||
|
updateTaskList = ($item) ->
|
||
|
$container = $item.closest '.js-task-list-container'
|
||
|
$field = $container.find '.js-task-list-field'
|
||
|
index = 1 + $container.find('.task-list-item-checkbox').index($item)
|
||
|
checked = $item.prop 'checked'
|
||
|
|
||
|
event = $.Event 'tasklist:change'
|
||
|
$field.trigger event, [index, checked]
|
||
|
|
||
|
unless event.isDefaultPrevented()
|
||
|
$field.val updateTaskListItem($field.val(), index, checked)
|
||
|
$field.trigger 'change'
|
||
|
$field.trigger 'tasklist:changed', [index, checked]
|
||
|
|
||
|
# When the task list item checkbox is updated, submit the change
|
||
|
$(document).on 'change', '.task-list-item-checkbox', ->
|
||
|
updateTaskList $(this)
|
||
|
|
||
|
# Enables TaskList item changes.
|
||
|
enableTaskList = ($container) ->
|
||
|
if $container.find('.js-task-list-field').length > 0
|
||
|
$container.
|
||
|
find('.task-list-item').addClass('enabled').
|
||
|
find('.task-list-item-checkbox').attr('disabled', null)
|
||
|
$container.addClass('is-task-list-enabled').
|
||
|
trigger 'tasklist:enabled'
|
||
|
|
||
|
# Enables a collection of TaskList containers.
|
||
|
enableTaskLists = ($containers) ->
|
||
|
for container in $containers
|
||
|
enableTaskList $(container)
|
||
|
|
||
|
# Disable TaskList item changes.
|
||
|
disableTaskList = ($container) ->
|
||
|
$container.
|
||
|
find('.task-list-item').removeClass('enabled').
|
||
|
find('.task-list-item-checkbox').attr('disabled', 'disabled')
|
||
|
$container.removeClass('is-task-list-enabled').
|
||
|
trigger 'tasklist:disabled'
|
||
|
|
||
|
# Disables a collection of TaskList containers.
|
||
|
disableTaskLists = ($containers) ->
|
||
|
for container in $containers
|
||
|
disableTaskList $(container)
|
||
|
|
||
|
$.fn.taskList = (method) ->
|
||
|
$container = $(this).closest('.js-task-list-container')
|
||
|
|
||
|
methods =
|
||
|
enable: enableTaskLists
|
||
|
disable: disableTaskLists
|
||
|
|
||
|
methods[method || 'enable']($container)
|