Added autocomplete for @mentions, #USs, #tasks, #issues, and [[wikipages]] in MD textareas.

stable
Brett Profitt 2015-08-21 19:11:38 -04:00
parent 57033c6e8b
commit 3b05871b0e
5 changed files with 110 additions and 5 deletions

View File

@ -9,6 +9,7 @@
- Make burndown chart collapsible at the backlog panel. - Make burndown chart collapsible at the backlog panel.
- Ability to choose a theme (thanks to [@astagi](https://github.com/astagi)) - Ability to choose a theme (thanks to [@astagi](https://github.com/astagi))
- Inline viewing of image attachments (thanks to [@brettp](https://github.com/brettp)). - Inline viewing of image attachments (thanks to [@brettp](https://github.com/brettp)).
- Autocomplete for usernames, user stories, tasks, issues, and wiki pages in text areas (thanks to [@brettp](https://github.com/brettp)).
- i18n. - i18n.
- Add polish (pl) translation. - Add polish (pl) translation.
- Add portuguese (Brazil) (pt_BR) translation. - Add portuguese (Brazil) (pt_BR) translation.

View File

@ -181,7 +181,11 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans
onShiftEnter: {keepDefault:false, openWith:"\n\n"} onShiftEnter: {keepDefault:false, openWith:"\n\n"}
onEnter: onEnter:
keepDefault: false, keepDefault: false,
replaceWith: () -> "\n" replaceWith: () ->
# Allow textcomplete to intercept the enter key if the options list is displayed
# @todo There doesn't seem to be a more graceful way to do this with the textcomplete API.
if not $('.textcomplete-dropdown').is(':visible')
"\n"
afterInsert: (data) -> afterInsert: (data) ->
lines = data.textarea.value.split("\n") lines = data.textarea.value.split("\n")
# Detect if we are in this situation +- aa at the beginning if the textarea # Detect if we are in this situation +- aa at the beginning if the textarea
@ -347,6 +351,104 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans
element element
.markItUpRemove() .markItUpRemove()
.markItUp(markdownSettings) .markItUp(markdownSettings)
.textcomplete([
# us, task, and issue autocomplete: #id or #<part of title>
{
cache: true
match: /(^|\s)#([a-z0-9]+)$/i,
search: (term, callback) ->
term = taiga.slugify(term)
searchTypes = ['issues', 'tasks', 'userstories']
searchProps = ['ref', 'subject']
filter = (item) =>
for prop in searchProps
if taiga.slugify(item[prop]).indexOf(term) >= 0
return true
return false
$rs.search.do($scope.projectId, term).then (res) =>
# ignore wikipages if they're the only results. can't exclude them in search
if res.count < 1 or res.count == res.wikipages.length
callback([])
else
for type in searchTypes
if res[type] and res[type].length > 0
callback(res[type].filter(filter), true)
# must signal end of lists
callback([])
replace: (res) ->
return "$1\##{res.ref} "
template: (res, term) ->
return "\##{res.ref} - #{res.subject}"
}
# username autocomplete: @username or @<part of name>
{
cache: true
match: /(^|\s)@([a-z0-9\-\._]{2,})$/i
search: (term, callback) ->
username = taiga.slugify(term)
searchProps = ['username', 'full_name', 'full_name_display']
if $scope.project.members.length < 1
callback([])
else
callback $scope.project.members.filter (user) =>
for prop in searchProps
if taiga.slugify(user[prop]).indexOf(username) >= 0
return true
return false
replace: (user) ->
return "$1@#{user.username} "
template: (user) ->
return "#{user.username} - #{user.full_name_display}"
}
# wiki pages autocomplete: [[slug or [[<part of slug>
# if the search function was called with the 3rd param the regex
# like the docs claim, we could combine this with the #123 search
{
cache: true
match: /(^|\s)\[\[([a-z0-9\-]+)$/i
search: (term, callback) ->
term = taiga.slugify(term)
$rs.search.do($scope.projectId, term).then (res) =>
if res.count < 1
callback([])
if res.count < 1 or not res.wikipages or res.wikipages.length <= 0
callback([])
else
callback res.wikipages.filter((page) =>
return taiga.slugify(page['slug']).indexOf(term) >= 0
), true
# must signal end of lists
callback([])
replace: (res) ->
return "$1[[#{res.slug}]]"
template: (res, term) ->
return res.slug
}
],
{
debounce: 200
}
)
renderMarkItUp() renderMarkItUp()

View File

@ -17,10 +17,12 @@
.dropdown-menu li:hover, .dropdown-menu li:hover,
.dropdown-menu .active { .dropdown-menu .active {
background-color: rgb(110, 183, 219); background-color: rgb(110, 183, 219);
color: white;
} }
.textcomplete-wrapper { .dropdown-menu .active > a,
width: 100%; .dropdown-menu .active > a:hover {
color: white;
} }
/* SHOULD not modify */ /* SHOULD not modify */

View File

@ -62,7 +62,7 @@
"google-diff-match-patch-js": "~1.0.0", "google-diff-match-patch-js": "~1.0.0",
"underscore.string": "~2.3.3", "underscore.string": "~2.3.3",
"markitup-1x": "~1.1.14", "markitup-1x": "~1.1.14",
"jquery-textcomplete": "yuku-t/jquery-textcomplete#~0.1.1", "jquery-textcomplete": "yuku-t/jquery-textcomplete#~0.7",
"flot-orderBars": "emmerich/flot-orderBars", "flot-orderBars": "emmerich/flot-orderBars",
"flot-axislabels": "markrcote/flot-axislabels", "flot-axislabels": "markrcote/flot-axislabels",
"flot.tooltip": "~0.8.4", "flot.tooltip": "~0.8.4",

View File

@ -154,7 +154,7 @@ paths.libs = [
paths.vendor + "jquery-flot/jquery.flot.time.js", paths.vendor + "jquery-flot/jquery.flot.time.js",
paths.vendor + "flot-axislabels/jquery.flot.axislabels.js", paths.vendor + "flot-axislabels/jquery.flot.axislabels.js",
paths.vendor + "flot.tooltip/js/jquery.flot.tooltip.js", paths.vendor + "flot.tooltip/js/jquery.flot.tooltip.js",
paths.vendor + "jquery-textcomplete/jquery.textcomplete.js", paths.vendor + "jquery-textcomplete/dist/jquery.textcomplete.js",
paths.vendor + "markitup-1x/markitup/jquery.markitup.js", paths.vendor + "markitup-1x/markitup/jquery.markitup.js",
paths.vendor + "malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js", paths.vendor + "malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js",
paths.vendor + "raven-js/dist/raven.js", paths.vendor + "raven-js/dist/raven.js",