Merge pull request #1170 from taigaio/rich-text-custom-fields

Rich text custom fields
stable
David Barragán Merino 2016-11-25 12:34:23 +01:00 committed by GitHub
commit 102bdc1e80
10 changed files with 121 additions and 13 deletions

View File

@ -5,8 +5,9 @@
### Features ### Features
- Contact with the project: if the projects have this module enabled Taiga users can contact them. - Contact with the project: if the projects have this module enabled Taiga users can contact them.
- Velocity forecasting. Create sprints according to team velocity. - Velocity forecasting. Create sprints according to team velocity.
- Remove bower - Remove bower, now use only npm packages.
- Add new wysiwyg editor (like the Medunm editor) with emojis, local storage changes, mentions... - Add new wysiwyg editor (like the Medunm editor) with emojis, local storage changes, mentions...
- Add rich text custom fields (with a wysiwyg editor like descreption or comments).
### Misc ### Misc
- Lots of small and not so small bugfixes. - Lots of small and not so small bugfixes.

View File

@ -406,6 +406,7 @@ module.directive("tgColorSelection", ColorSelectionDirective)
# Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py) # Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py)
TEXT_TYPE = "text" TEXT_TYPE = "text"
MULTILINE_TYPE = "multiline" MULTILINE_TYPE = "multiline"
RICHTEXT_TYPE = "richtext"
DATE_TYPE = "date" DATE_TYPE = "date"
URL_TYPE = "url" URL_TYPE = "url"
@ -419,6 +420,10 @@ TYPE_CHOICES = [
key: MULTILINE_TYPE, key: MULTILINE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI" name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI"
}, },
{
key: RICHTEXT_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_RICHTEXT"
},
{ {
key: DATE_TYPE, key: DATE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_DATE" name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_DATE"

View File

@ -32,6 +32,7 @@ module = angular.module("taigaCommon")
# Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py) # Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py)
TEXT_TYPE = "text" TEXT_TYPE = "text"
RICHTEXT_TYPE = "url"
MULTILINE_TYPE = "multiline" MULTILINE_TYPE = "multiline"
DATE_TYPE = "date" DATE_TYPE = "date"
URL_TYPE = "url" URL_TYPE = "url"
@ -53,6 +54,10 @@ TYPE_CHOICES = [
{ {
key: URL_TYPE, key: URL_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_URL" name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_URL"
},
{
key: RICHTEXT_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_RICHTEXT"
} }
] ]
@ -193,6 +198,15 @@ CustomAttributeValueDirective = ($template, $selectedText, $compile, $translate,
requiredEditionPerm = $attrs.requiredEditionPerm requiredEditionPerm = $attrs.requiredEditionPerm
return permissions.indexOf(requiredEditionPerm) > -1 return permissions.indexOf(requiredEditionPerm) > -1
$scope.saveCustomRichText = (markdown, callback) =>
attributeValue.value = markdown
$ctrl.updateAttributeValue(attributeValue).then ->
callback()
render(attributeValue, false)
$scope.cancelCustomRichText= () =>
render(attributeValue, false)
submit = debounce 2000, (event) => submit = debounce 2000, (event) =>
event.preventDefault() event.preventDefault()
@ -214,6 +228,9 @@ CustomAttributeValueDirective = ($template, $selectedText, $compile, $translate,
# Bootstrap # Bootstrap
attributeValue = $scope.$eval($attrs.tgCustomAttributeValue) attributeValue = $scope.$eval($attrs.tgCustomAttributeValue)
if attributeValue.value == null or attributeValue.value == undefined
attributeValue.value = ""
$scope.customAttributeValue = attributeValue
render(attributeValue) render(attributeValue)
## Actions (on view mode) ## Actions (on view mode)

View File

@ -573,6 +573,7 @@
"ISSUE_DESCRIPTION": "Issues custom fields", "ISSUE_DESCRIPTION": "Issues custom fields",
"ISSUE_ADD": "Add a custom field in issues", "ISSUE_ADD": "Add a custom field in issues",
"FIELD_TYPE_TEXT": "Text", "FIELD_TYPE_TEXT": "Text",
"FIELD_TYPE_RICHTEXT": "Rich text",
"FIELD_TYPE_MULTI": "Multi-line", "FIELD_TYPE_MULTI": "Multi-line",
"FIELD_TYPE_DATE": "Date", "FIELD_TYPE_DATE": "Date",
"FIELD_TYPE_URL": "Url" "FIELD_TYPE_URL": "Url"

View File

@ -0,0 +1,58 @@
###
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
# Copyright (C) 2014-2016 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2016 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# Copyright (C) 2014-2016 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
# Copyright (C) 2014-2016 Xavi Julian <xavier.julian@kaleidos.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules/components/wysiwyg/comment-edit-wysiwyg.directive.coffee
###
CustomFieldEditWysiwyg = (attachmentsFullService) ->
link = ($scope, $el, $attrs) ->
types = {
userstories: "us",
issues: "issue",
tasks: "task"
}
uploadFile = (file, cb) ->
return attachmentsFullService.addAttachment($scope.vm.projectId, $scope.vm.comment.comment.id, types[$scope.vm.comment.comment._name], file).then (result) ->
cb(result.getIn(['file', 'name']), result.getIn(['file', 'url']))
$scope.uploadFiles = (files, cb) ->
for file in files
uploadFile(file, cb)
return {
scope: true,
link: link,
template: """
<div>
<tg-wysiwyg
editonly
content='customAttributeValue.value'
on-save="saveCustomRichText(text, cb)"
on-cancel="cancelCustomRichText()"
on-upload-file='uploadFiles(files, cb)'>
</tg-wysiwyg>
</div>
"""
}
angular.module("taigaComponents")
.directive("tgCustomFieldEditWysiwyg", ["tgAttachmentsFullService", CustomFieldEditWysiwyg])

View File

@ -1,19 +1,29 @@
.diff-custom-new( .diff-status-wrapper(
ng-if="vm.diff.new.length" ng-if="vm.diff.new.length"
ng-repeat="newCustom in vm.diff.new" ng-repeat="newCustom in vm.diff.new"
) )
span.key(translate="ACTIVITY.CREATED_CUSTOM_ATTRIBUTE") span.key(translate="ACTIVITY.CREATED_CUSTOM_ATTRIBUTE")
span.diff ({{newCustom.name}}) span.diff ({{newCustom.name}})
span.diff {{newCustom.value}}
.diff-custom-new( span(ng-if="newCustom.type == 'richtext'")
p.diff(tg-bo-html="newCustom.value_diff")
span(ng-if="newCustom.type != 'richtext'")
span.diff {{newCustom.value}}
.diff-status-wrapper(
ng-if="vm.diff.changed.length" ng-if="vm.diff.changed.length"
ng-repeat="changeCustom in vm.diff.changed" ng-repeat="changeCustom in vm.diff.changed"
) )
span.key(translate="ACTIVITY.UPDATED_CUSTOM_ATTRIBUTE") span.key(translate="ACTIVITY.UPDATED_CUSTOM_ATTRIBUTE")
span.diff ({{changeCustom.name}}) span.diff ({{changeCustom.name}})
span.diff {{changeCustom.changes.value[0]}}
tg-svg( span(ng-if="changeCustom.type == 'richtext'")
svg-icon="icon-arrow-right" p.diff(tg-bo-html="changeCustom.value_diff")
)
span.diff {{changeCustom.changes.value[1]}} span(ng-if="changeCustom.type != 'richtext'")
span.diff {{changeCustom.changes.value[0]}}
tg-svg(
svg-icon="icon-arrow-right"
)
span.diff {{changeCustom.changes.value[1]}}

View File

@ -13,6 +13,8 @@ form.custom-field-single.editable
input#custom-field-value(name="value", type="text", value!="<%- value %>") input#custom-field-value(name="value", type="text", value!="<%- value %>")
<% } else if (type=="multiline") { %> <% } else if (type=="multiline") { %>
textarea#custom-field-value(name="value") <%- value %> textarea#custom-field-value(name="value") <%- value %>
<% } else if (type=="richtext") { %>
tg-custom-field-edit-wysiwyg()
<% } else if (type=="date") { %> <% } else if (type=="date") { %>
input#custom-field-value(name="value", type="text", data-pikaday, value!="<%- value %>") input#custom-field-value(name="value", type="text", data-pikaday, value!="<%- value %>")
<% } else if (type=="url") { %> <% } else if (type=="url") { %>
@ -21,6 +23,8 @@ form.custom-field-single.editable
input#custom-field-value(name="value", type="text", value!="<%- value %>") input#custom-field-value(name="value", type="text", value!="<%- value %>")
<% } %> <% } %>
<% if (type != "richtext") { %>
div.custom-field-options div.custom-field-options
a.js-save-description(href="", title="{{'COMMON.CUSTOM_ATTRIBUTES.SAVE' | translate}}") a.js-save-description(href="", title="{{'COMMON.CUSTOM_ATTRIBUTES.SAVE' | translate}}")
tg-svg(svg-icon="icon-save") tg-svg(svg-icon="icon-save")
<% } %>

View File

@ -7,14 +7,19 @@
<%- description %> <%- description %>
<% } %> <% } %>
<% if (type=="url") { %>
.custom-field-value.js-value-view-mode .custom-field-value.js-value-view-mode
span span
<% if (type=="url") { %>
a(href!="<%- value %>") a(href!="<%- value %>")
<%- value %> <%- value %>
<% } else { %> <% } else if (type=="richtext") { %>
.custom-field-value.js-value-view-mode.wysiwyg
div(ng-bind-html!="\'<%- value %>\'|markdownToHTML")
<% } else { %>
.custom-field-value.js-value-view-mode
span
<%- value %> <%- value %>
<% } %> <% } %>
<% if (isEditable) { %> <% if (isEditable) { %>
.custom-field-options .custom-field-options

View File

@ -30,6 +30,10 @@ section.custom-fields-table.basic-table
ng-switch-default ng-switch-default
translate="ADMIN.CUSTOM_FIELDS.FIELD_TYPE_TEXT" translate="ADMIN.CUSTOM_FIELDS.FIELD_TYPE_TEXT"
) )
span(
ng-switch-when="richtext"
translate="ADMIN.CUSTOM_FIELDS.FIELD_TYPE_RICHTEXT"
)
span( span(
ng-switch-when="multiline" ng-switch-when="multiline"
translate="ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI" translate="ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI"

View File

@ -71,6 +71,9 @@
padding: 0 1rem 0 2rem; padding: 0 1rem 0 2rem;
&.js-value-view-mode { &.js-value-view-mode {
white-space: pre-line; white-space: pre-line;
&.wysiwyg {
white-space: normal;
}
} }
} }
form { form {