Compare commits

..
41 Commits
Author SHA1 Message Date
Lauri Ojansivu db9c895da8 v3.89 2020-04-05 01:20:09 +03:00
Lauri Ojansivu a938c386cf Update translations. 2020-04-05 01:06:49 +03:00
Lauri Ojansivu 06c2ff0904 Update ChangeLog. 2020-04-04 01:43:09 +03:00
Lauri Ojansivu 76e832c3a6 Merge branch 'daniel-eder-feature/subtask-use-parent-swimlane' 2020-04-04 01:36:43 +03:00
Lauri Ojansivu b7fd79aef0 Merge branch 'feature/subtask-use-parent-swimlane' of https://github.com/daniel-eder/wekan into daniel-eder-feature/subtask-use-parent-swimlane 2020-04-04 01:26:49 +03:00
Lauri Ojansivu 28a9ab7131 Update translations. 2020-04-04 01:19:47 +03:00
Lauri Ojansivu 8e0b1cc056 Merge branch 'slvrpdr-master' 2020-04-04 01:04:17 +03:00
Lauri Ojansivu b424b34cab Merge branch 'master' of https://github.com/slvrpdr/wekan into slvrpdr-master 2020-04-04 00:42:14 +03:00
Lauri Ojansivu 45a83da7be Update translations. 2020-04-04 00:37:23 +03:00
Pedro Sousa 6e86292b99 Search also a Card's Custom Fields 2020-04-03 16:23:48 +01:00
Daniel Eder 89fe030fa3 subtasks now use swimlane with title matching parent lane title
As discussed in #1953 this is required to handle two-board scenarios correctly.
2020-04-02 07:49:26 +02:00
Lauri Ojansivu 1e0b53eb9b v3.88 2020-04-02 02:34:02 +03:00
Lauri Ojansivu a5f2ae497a Update translations. 2020-04-02 02:05:25 +03:00
Lauri Ojansivu 6441ebec83 Update ChangeLog. 2020-04-02 01:34:27 +03:00
Lauri Ojansivu 7c609d402a Update translations. 2020-04-02 01:29:22 +03:00
Lauri Ojansivu e34f240c35 Merge branch 'jtbairdsr-master' 2020-04-02 01:09:35 +03:00
Jonathan Baird 8ea86ae0b9 fix error from notifications subscriptions 2020-04-01 15:04:12 -06:00
Jonathan Baird f4d0791c6c add correct i18n for notifications menu 2020-04-01 14:50:31 -06:00
Jonathan Baird fcb1def355 Merge upstream/master
* commit '56667112d7fd146a9847794207e9a2224b1115d7':
  Update ChangeLog.
  Minicard labels on the top and title on bottom.
  Update translations.
  Update translations.
  Add translations for Notification drawer like Trello.
  Add translations for Board Settings / Allow Rename Board/Swimlane/Card.
  Update ChangeLog.
  Fix start-wekan.sh MongoDB port to 27017.
  v3.87
  Update translations.
  Update ChangeLog.
  Improvements on card details visualization.
  Update ChangeLog.
  Move "Rules" from "Board View" to "Board Settings".
  Update ChangeLog.
  Fix richer editor submit did not clear edit area.
  Update ChangeLog.
  Fix Browser always reload the whole page when I change one of the card color. Fixed by making label colors and text again editable. Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).
2020-04-01 14:47:53 -06:00
Lauri Ojansivu 56667112d7 Update ChangeLog. 2020-04-01 19:42:26 +03:00
Lauri Ojansivu d0003a334f Minicard labels on the top and title on bottom.
Thanks to helioguardabaxo and xet7 !

Closes #2980
2020-04-01 19:39:02 +03:00
Lauri Ojansivu 35427063ff Update translations. 2020-04-01 17:11:02 +03:00
Lauri Ojansivu f5182e6328 Update translations. 2020-04-01 17:04:01 +03:00
Lauri Ojansivu 0c911694c8 Add translations for Notification drawer like Trello. 2020-04-01 16:51:54 +03:00
Lauri Ojansivu 2eed1ca0eb Add translations for Board Settings / Allow Rename Board/Swimlane/Card.
Thanks to xet7 !
2020-04-01 16:48:14 +03:00
Lauri Ojansivu 99e5293ef5 Update ChangeLog. 2020-04-01 00:37:26 +03:00
Lauri Ojansivu c60a092fc0 Fix start-wekan.sh MongoDB port to 27017.
Thanks to Keelan and xet7 !

Related #2979
2020-04-01 00:34:21 +03:00
Lauri Ojansivu b33f44e23f v3.87 2020-04-01 00:12:35 +03:00
Lauri Ojansivu a65e39563b Update translations. 2020-04-01 00:04:18 +03:00
Lauri Ojansivu 703efa5216 Update ChangeLog. 2020-04-01 00:01:15 +03:00
Lauri Ojansivu 6b902bdb36 Improvements on card details visualization.
Thanks to helioguardabaxo and xet7 !

Closes #2974
2020-03-31 23:56:37 +03:00
Lauri Ojansivu d599fc12ff Update ChangeLog. 2020-03-31 23:36:47 +03:00
Lauri Ojansivu e7d2343593 Move "Rules" from "Board View" to "Board Settings".
Thanks to helioguardabaxo and xet7 !

Closes #2973
2020-03-31 23:32:32 +03:00
Lauri Ojansivu c8ec5d5f62 Update ChangeLog. 2020-03-31 23:20:07 +03:00
Lauri Ojansivu 033d671047 Fix richer editor submit did not clear edit area.
Thanks to xet7 !
2020-03-31 23:17:58 +03:00
Lauri Ojansivu 6ef3f8a1c8 Update ChangeLog. 2020-03-31 17:05:02 +03:00
Lauri Ojansivu 3546d7aa02 Fix Browser always reload the whole page when I change one of the card color.
Fixed by making label colors and text again editable.
Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).

Thanks to javen9881 and xet7 !

Closes #2971
2020-03-31 16:56:32 +03:00
Jonathan Baird 9819c9f801 add a notification drawer like trello 2020-03-27 11:35:03 -06:00
Daniel Eder c41c1e5293 subtasks now use parent task swimlane by default 2020-03-26 10:57:05 +01:00
Lauri Ojansivu 29d62440a5 Update ChangeLog. 2020-03-25 13:41:33 +02:00
Lauri Ojansivu 5bd0459cc2 Hide duplicate "Hide system messages" at Change Settings/Member Settings, because it's also on card slider.
Thanks to notohiro and xet7 !

Closes #2837
2020-03-25 13:35:54 +02:00
95 changed files with 40035 additions and 39443 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v3.86.0",
"version": "v3.89.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v3.86.0",
"version": "v3.89.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
+54
View File
@@ -1,3 +1,57 @@
# v3.89 2020-04-05 Wekan release
This release adds the following new features:
- [Create subtasks in parenttask swimlane](https://github.com/wekan/wekan/issues/1953).
Thanks to TOSCom-DanielEder.
- [When searching cards in a board, also search from Custom Fields](https://github.com/wekan/wekan/pull/2985).
Thanks to slvrpdr.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.88 2020-04-02 Wekan release
This release adds the following new features:
- [Notification drawer](https://github.com/wekan/wekan/pull/2975) [like Trello](https://github.com/wekan/wekan/issues/2471).
Thanks to jtbairdsr and xet7.
and makes the following UI changes:
- [Minicard labels on the top and title on bottom](https://github.com/wekan/wekan/issues/2980).
Thanks to helioguardabaxo and xet7.
and fixes the following bugs:
- [Fix start-wekan.sh MongoDB port to 27017](https://github.com/wekan/wekan/commit/c60a092fc0ed9fe15c417bcb443b1e3e3aaedf7e).
Thanks to Keelan and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.87 2020-04-01 Wekan release
This release makes the following UI changes:
- [Move "Rules" from "Board View" to "Board Settings"](https://github.com/wekan/wekan/issues/2973).
Thanks to helioguardabaxo and xet7.
- [Improvements on card details visualization](https://github.com/wekan/wekan/issues/2974).
Thanks to helioguardabaxo and xet7.
- [Hide duplicate "Hide system messages" at Change Settings/Member Settings, because it's also on card
slider](https://github.com/wekan/wekan/issues/2837).
Thanks to notohiro and xet7.
and fixes the following bugs:
- [Fix Browser always reload the whole page when I change one of the card
color](https://github.com/wekan/wekan/commit/3546d7aa02bc65cf1183cb493adeb543ba51945d).
Fixed by making label colors and text again editable.
Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).
Thanks to javen9881 and xet7.
- [Fix richer editor submit did not clear edit area](https://github.com/wekan/wekan/commit/033d6710470b2ecd7a0ec0b2f0741ff459e68b32).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.86 2020-03-24 Wekan release
This release fixes the following bugs:
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v3.86.0"
appVersion: "v3.89.0"
files:
userUploads:
- README.md
+167 -200
View File
@@ -8,234 +8,201 @@ template(name="activities")
+cardActivities
template(name="boardActivities")
each currentBoard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($eq activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
if($eq activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ card.absoluteUrl }}")
+viewer
= comment.text
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklist.title
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklistItem.title
if($eq activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
if($eq activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activityType 'archivedList')
| {{_ 'activity-archived' list.title}}.
if($eq activityType 'archivedSwimlane')
| {{_ 'activity-archived' swimlane.title}}.
if($eq activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
if($eq activityType 'createCard')
| {{{_ 'activity-added' cardLink boardLabel}}}.
if($eq activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activityType 'createList')
| {{_ 'activity-added' list.title boardLabel}}.
if($eq activityType 'createSwimlane')
| {{_ 'activity-added' swimlane.title boardLabel}}.
if($eq activityType 'removeList')
| {{_ 'activity-removed' title boardLabel}}.
if($eq activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
if($eq activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activityType 'moveCard')
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
if($eq activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
if($eq activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
if($eq activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
span(title=createdAt).activity-meta {{ moment createdAt }}
each activityData in currentBoard.activities
+activity(activity=activityData card=card mode=mode)
template(name="cardActivities")
each currentCard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'createCard')
| {{_ 'activity-added' cardLabel listName}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{_ 'activity-joined' cardLabel}}.
else
| {{{_ 'activity-added' memberLink cardLabel}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{_ 'activity-unjoined' cardLabel}}.
else
| {{{_ 'activity-removed' cardLabel memberLink}}}.
if($eq activityType 'archivedCard')
| {{_ 'activity-archived' cardLabel}}.
each activityData in currentCard.activities
+activity(activity=activityData card=card mode=mode)
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label-card' lastLabel }}}.
template(name="activity")
.activity
+userAvatar(userId=activity.user._id)
p.activity-desc
+memberName(user=activity.user)
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label-card' lastLabel }}}.
//- attachment activity -------------------------------------------------
if($eq activity.activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activity.activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($neq mode 'board')
if activity.attachment.isImage
img.attachment-image-preview(src=activity.attachment.url)
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
//- board activity ------------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
if($eq activity.activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed-card' checklist.title }}}.
if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
if($eq activity.activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
if($eq activityType 'restoredCard')
| {{_ 'activity-sent' cardLabel boardLabel}}.
if($eq activityType 'moveCard')
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
//- card activity -------------------------------------------------------
if($eq activity.activityType 'createCard')
if($eq mode 'card')
| {{{_ 'activity-added' cardLabel activity.listName}}}.
else
| {{{_ 'activity-added' cardLabel boardLabel}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activity.activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
if attachment.isImage
img.attachment-image-preview(src=attachment.url)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLabel}}}.
if($eq activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLabel}}}.
if($eq activity.activityType 'moveCard')
| {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}.
if($eq activity.activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}.
if($eq activity.activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activity.activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
//- checklist activity --------------------------------------------------
if($eq activity.activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
if($eq mode 'card')
.activity-checklist
+viewer
= checklist.title
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
= activity.checklist.title
else
a.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+viewer
= checklistItem.title
= activity.checklist.title
if(currentData.timeKey)
| {{{_ activityType }}}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activityType currentData.timeValue}}}
if($eq activity.activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activity.activityType 'completeChecklist')
| {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}.
if($eq activityType 'deleteComment')
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
if($eq activityType 'editComment')
| {{{_ 'activity-editComment' currentData.commentId}}}.
if($eq activityType 'addComment')
if($eq activity.activityType 'uncompleteChecklist')
| {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}.
if($eq activity.activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}.
if($eq activity.activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}.
if($eq activity.activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}.
.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+viewer
= activity.checklistItem.title
if($eq activity.activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}.
//- comment activity ----------------------------------------------------
if($eq mode 'card')
//- if we are in card mode we display the comment in a way that it
//- can be edited by the owner
if($eq activity.activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
= comment.text
= activity.comment.text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
else
.activity-comment
+viewer
= comment.text
span(title=createdAt).activity-meta {{ moment createdAt }}
if ($eq currentUser._id comment.userId)
= activity.comment.text
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if ($eq currentUser._id activity.comment.userId)
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
a.js-delete-comment {{_ "delete"}}
if($eq activity.activityType 'deleteComment')
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
if($eq activity.activityType 'editComment')
| {{{_ 'activity-editComment' currentData.commentId}}}.
else
//- if we are not in card mode we only display a summary of the comment
if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ activity.card.absoluteUrl }}")
+viewer
= activity.comment.text
//- customField activity ------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activity.activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activity.activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
//- label activity ------------------------------------------------------
if($eq activity.activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
if($eq activity.activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
//- list activity -------------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createList')
| {{{_ 'activity-added' listLabel boardLabel}}}.
if($eq activity.activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
if($eq activity.activityType 'removeList')
| {{{_ 'activity-removed' activity.title boardLabel}}}.
if($eq activity.activityType 'archivedList')
| {{_ 'activity-archived' listLabel}}.
//- member activity ----------------------------------------------------
if($eq activity.activityType 'joinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
span(title=createdAt).activity-meta {{ moment createdAt }}
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activity.activityType 'unjoinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
//- swimlane activity --------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createSwimlane')
| {{{_ 'activity-added' activity.swimlane.title boardLabel}}}.
if($eq activity.activityType 'archivedSwimlane')
| {{_ 'activity-archived' activity.swimlane.title}}.
//- I don't understand this part ----------------------------------------
if(currentData.timeKey)
| {{{_ activity.activityType }}}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activity.activityType currentData.timeValue}}}
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
+72 -39
View File
@@ -41,7 +41,9 @@ BlazeComponent.extendComponent({
});
});
},
}).register('activities');
BlazeComponent.extendComponent({
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
@@ -50,41 +52,37 @@ BlazeComponent.extendComponent({
},
checkItem() {
const checkItemId = this.currentData().checklistItemId;
const checkItemId = this.currentData().activity.checklistItemId;
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
return checkItem.title;
return checkItem && checkItem.title;
},
boardLabel() {
const data = this.currentData();
if (data.mode !== 'board') {
return createBoardLink(data.activity.board(), data.activity.listName);
}
return TAPi18n.__('this-board');
},
cardLabel() {
const data = this.currentData();
if (data.mode !== 'card') {
return createCardLink(this.currentData().activity.card());
}
return TAPi18n.__('this-card');
},
cardLink() {
const card = this.currentData().card();
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.absoluteUrl(),
class: 'action-card',
},
card.title,
),
)
);
return createCardLink(this.currentData().activity.card());
},
lastLabel() {
const lastLabelId = this.currentData().labelId;
const lastLabelId = this.currentData().activity.labelId;
if (!lastLabelId) return null;
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
lastLabelId,
);
const lastLabel = Boards.findOne(
this.currentData().activity.boardId,
).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
@@ -94,7 +92,7 @@ BlazeComponent.extendComponent({
lastCustomField() {
const lastCustomField = CustomFields.findOne(
this.currentData().customFieldId,
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
return lastCustomField.name;
@@ -102,10 +100,10 @@ BlazeComponent.extendComponent({
lastCustomFieldValue() {
const lastCustomField = CustomFields.findOne(
this.currentData().customFieldId,
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
const value = this.currentData().value;
const value = this.currentData().activity.value;
if (
lastCustomField.settings.dropdownItems &&
lastCustomField.settings.dropdownItems.length > 0
@@ -122,11 +120,13 @@ BlazeComponent.extendComponent({
},
listLabel() {
return this.currentData().list().title;
const activity = this.currentData().activity;
const list = activity.list();
return (list && list.title) || activity.title;
},
sourceLink() {
const source = this.currentData().source;
const source = this.currentData().activity.source;
if (source) {
if (source.url) {
return Blaze.toHTML(
@@ -146,30 +146,31 @@ BlazeComponent.extendComponent({
memberLink() {
return Blaze.toHTMLWithData(Template.memberName, {
user: this.currentData().member(),
user: this.currentData().activity.member(),
});
},
attachmentLink() {
const attachment = this.currentData().attachment();
const attachment = this.currentData().activity.attachment();
// trying to display url before file is stored generates js errors
return (
attachment &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: attachment.url({ download: true }),
target: '_blank',
},
attachment.name(),
),
)
(attachment &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: attachment.url({ download: true }),
target: '_blank',
},
attachment.name(),
),
)) ||
this.currentData().activity.attachmentName
);
},
customField() {
const customField = this.currentData().customField();
const customField = this.currentData().activity.customField();
if (!customField) return null;
return customField.name;
},
@@ -199,4 +200,36 @@ BlazeComponent.extendComponent({
},
];
},
}).register('activities');
}).register('activity');
function createCardLink(card) {
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.absoluteUrl(),
class: 'action-card',
},
card.title,
),
)
);
}
function createBoardLink(board, list) {
let text = board.title;
if (list) text += `: ${list}`;
return (
board &&
Blaze.toHTML(
HTML.A(
{
href: board.absoluteUrl(),
class: 'action-board',
},
text,
),
)
);
}
-7
View File
@@ -33,13 +33,6 @@ BlazeComponent.extendComponent({
cardId,
});
resetCommentInput(input);
// With Richer editor is in use, and comment is submitted,
// clear comment form with JQuery. Id #summernote is defined
// at client/components/main/editor.jade where it previously was
// id=id, now it is id="summernote".
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
$('#summernote').summernote('code', '');
}
Tracker.flush();
autosize.update(input);
input.trigger('submitted');
-14
View File
@@ -193,20 +193,6 @@ template(name="boardChangeViewPopup")
| {{_ 'board-view-cal'}}
if $eq Utils.boardView "board-view-cal"
i.fa.fa-check
if currentUser.isAdmin
hr
li
with "board-view-rules"
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
else if currentUser.isBoardAdmin
hr
li
with "board-view-rules"
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
template(name="createBoard")
form
-4
View File
@@ -191,10 +191,6 @@ Template.boardChangeViewPopup.events({
Utils.setBoardView('board-view-cal');
Popup.close();
},
'click .js-open-rules-view'() {
Modal.openWide('rulesMain');
Popup.close();
},
});
const CreateBoard = BlazeComponent.extendComponent({
+1 -1
View File
@@ -55,5 +55,5 @@ template(name="attachmentsGalery")
unless currentUser.isWorker
//li.attachment-item.add-attachment
a.js-add-attachment
i.fa.fa-paperclip
i.fa.fa-plus
| {{_ 'add-attachment' }}
+7 -4
View File
@@ -203,6 +203,7 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
if currentBoard.allowsDescriptionTitle
hr
h3
i.fa.fa-align-left
card-details-item-title {{_ 'description'}}
@@ -229,6 +230,7 @@ template(name="cardDetails")
a.js-close-inlined-form {{_ 'discard'}}
else if getDescription
if currentBoard.allowsDescriptionTitle
hr
h3.card-details-item-title {{_ 'description'}}
if currentBoard.allowsDescriptionText
+viewer
@@ -237,15 +239,16 @@ template(name="cardDetails")
.card-checklist-attachmentGalerys
.card-checklist-attachmentGalery.card-checklists
if currentBoard.allowsChecklists
hr
+checklists(cardId = _id)
if currentBoard.allowsSubtasks
hr
+subtasks(cardId = _id)
if currentBoard.allowsAttachments
//- hr
//- h3
//- i.fa.fa-paperclip
//- | {{_ 'attachments'}}
hr
h3
i.fa.fa-paperclip
| {{_ 'attachments'}}
.card-checklist-attachmentGalery.card-attachmentGalery
+attachmentsGalery
+1 -1
View File
@@ -79,7 +79,7 @@
border-radius: top 2px
.minicard-labels
float: right
float: none
display: flex
flex-wrap: wrap
+7 -1
View File
@@ -20,7 +20,13 @@ BlazeComponent.extendComponent({
const crtBoard = Boards.findOne(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
const swimlaneId = targetBoard.getDefaultSwimline()._id;
//Get the full swimlane data for the parent task.
const parentSwimlane = Swimlanes.findOne({boardId: crtBoard._id, _id: card.swimlaneId});
//find the swimlane of the same name in the target board.
const targetSwimlane = Swimlanes.findOne({boardId: targetBoard._id, title: parentSwimlane.title});
//If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
const swimlaneId = targetSwimlane === undefined ? targetBoard.getDefaultSwimline()._id : targetSwimlane._id;
if (title) {
const _id = Cards.insert({
+1 -6
View File
@@ -1,13 +1,8 @@
template(name="editor")
// With Richer editor is in use, and comment is submitted,
// clear comment form with JQuery Comment at
// client/components/activities/comments.js . Id #summernote is defined
// here at client/components/main/editor.jade where it previously was
// id=id, now it is id="summernote".
textarea.editor(
dir="auto"
class="{{class}}"
id="summernote"
id=id
autofocus=autofocus
placeholder="{{_ 'comment-placeholder'}}")
+Template.contentBlock
+19 -47
View File
@@ -30,7 +30,7 @@ Template.editor.onRendered(() => {
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
@@ -108,45 +108,17 @@ Template.editor.onRendered(() => {
}
return undefined;
};
// Prevent @member mentions on Add Comment input field
// from closing card, part 1.
let popupShown = false;
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
// Prevent @member mentions on Add Comment input field
// from closing card, part 2.
onKeydown(e) {
if (popupShown) {
e.preventDefault();
}
},
onKeyup(e) {
if (popupShown) {
e.preventDefault();
}
},
callbacks: {
// Prevent @member mentions on Add Comment input field
// from closing card, part 3.
onKeydown(e) {
if (popupShown) {
e.preventDefault();
}
},
onKeyup(e) {
if (popupShown) {
e.preventDefault();
}
},
onInit(object) {
const originalInput = this;
$(originalInput).on('input', function() {
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('reset');
object && object.editingArea.find('.note-placeholder').show();
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
@@ -163,6 +135,7 @@ Template.editor.onRendered(() => {
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
@@ -249,7 +222,7 @@ Template.editor.onRendered(() => {
// == Fix End ==
const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
someNote.summernote('reset'); //clear original
someNote.summernote('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
@@ -316,15 +289,15 @@ Blaze.Template.registerHelper(
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
const [fullMention, username] = currentMention;
const [fullMention, quoteduser, simple] = currentMention;
const username = quoteduser || simple;
const knowedUser = _.findWhere(knowedUsers, { username });
if (!knowedUser) {
continue;
}
const linkValue = [' ', at, knowedUser.username];
//let linkClass = 'atMention js-open-member';
let linkClass = 'atMention';
let linkClass = 'atMention js-open-member';
if (knowedUser.userId === Meteor.userId()) {
linkClass += ' me';
}
@@ -357,25 +330,24 @@ Template.viewer.events({
// the corresponding text). Clicking a link shouldn't fire these actions, stop
// we stop these event at the viewer component level.
'click a'(event, templateInstance) {
event.stopPropagation();
// XXX We hijack the build-in browser action because we currently don't have
// `_blank` attributes in viewer links, and the transformer function is
// handled by a third party package that we can't configure easily. Fix that
// by using directly `_blank` attribute in the rendered HTML.
event.preventDefault();
let prevent = true;
const userId = event.currentTarget.dataset.userid;
if (userId) {
// Prevent @member mentions on Add Comment input field
// from closing card, part 4.
PopupNoClose.open('member').call({ userId }, event, templateInstance);
event.preventDefault();
Popup.open('member').call({ userId }, event, templateInstance);
} else {
const href = event.currentTarget.href;
if (href) {
window.open(href, '_blank');
}
}
if (prevent) {
event.stopPropagation();
// XXX We hijack the build-in browser action because we currently don't have
// `_blank` attributes in viewer links, and the transformer function is
// handled by a third party package that we can't configure easily. Fix that
// by using directly `_blank` attribute in the rendered HTML.
event.preventDefault();
}
},
});
+2
View File
@@ -35,6 +35,8 @@ template(name="header")
a#header-new-board-icon.js-create-board
i.fa.fa-plus(title="Create a new board")
+notifications
+headerUserBar
#header(class=currentBoard.colorClass)
+1 -1
View File
@@ -99,7 +99,7 @@
height: 28px
font-size: 12px
display: flex
z-index: 17
z-index: 21
#header-user-bar,
#header-new-board-icon,
@@ -0,0 +1,10 @@
template(name='notification')
li.notification(class="{{#if read}}read{{/if}}")
.read-status
.materialCheckBox(class="{{#if read}}is-checked{{/if}}")
+notificationIcon(activityData)
.details
+activity(activity=activityData mode='none')
if read
.remove
a.fa.fa-trash
@@ -0,0 +1,28 @@
Template.notification.events({
'click .read-status .materialCheckBox'() {
const update = {};
update[`profile.notifications.${this.index}.read`] = this.read
? null
: Date.now();
Users.update(Meteor.userId(), { $set: update });
},
'click .remove a'() {
Meteor.user().removeNotification(this.activityData._id);
},
});
Template.notification.helpers({
mode: 'board',
isOfActivityType(activityId, type) {
const activity = Activities.findOne(activityId);
return activity && activity.activityType === type;
},
activityType(activityId) {
const activity = Activities.findOne(activityId);
return activity ? activity.activityType : '';
},
activityUser(activityId) {
const activity = Activities.findOne(activityId);
return activity && activity.userId;
},
});
@@ -0,0 +1,57 @@
#notifications-drawer
&.show-read .notification.read
display: flex
.notification
display: flex
float: none
padding: 12px 8px 8px
color: black
border-bottom: 1px solid #dbdbdb
&.read
display: none
.read-status
width: 30px
input
width: 24px
height: 24px
.activity-type
margin: 16px 0 0
width: 17px
height: 17px
font-size: 17px
display: block
color: #bbb
.details
width: calc(100% - 30px)
.activity
display: flex
.activity-desc
width: 100%;
.activity-comment
display: block
width: 100%
border-radius: 3px
background: #fff
text-decoration: none
box-shadow: 0 1px 2px rgba(0,0,0,0.2)
margin-top: 5px
padding: 5px
.activity-meta
display: block
font-size: 0.8em
color: #999
font-style: italic
.remove
a:hover
color #eb4646 !important
@@ -0,0 +1,53 @@
template(name='notificationIcon')
if($in activityType 'deleteAttachment' 'addAttachment')
i.fa.fa-paperclip.activity-type(title="attachment")
else if($in activityType 'createBoard' 'importBoard')
i.fa.fa-chalkboard.activity-type(title="board")
else if($in activityType 'createCard' 'importCard' 'moveCard')
+cardNotificationIcon
else if($in activityType 'moveCardBoard' 'archivedCard' 'restoredCard')
+cardNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
else if($in activityType 'addChecklist' 'removedChecklist' 'completeChecklist')
+checklistNotificationIcon
else if($in activityType 'uncompleteChecklist')
+checklistNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
i.fa.fa-check-square.activity-type(title="checklist item")
else if($in activityType 'addComment')
i.fa.fa-comment-o.activity-type(title="comment")
else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
i.fa.fa-code.activity-type(title="custom field")
else if($in activityType 'addedLabel' 'removedLabel')
i.fa.fa-tag.activity-type(title="label")
else if($in activityType 'createList' 'removeList' 'archivedList')
+listNotificationIcon
else if($in activityType 'importList')
+listNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
//- elswhere in the app we use fa-trello to indicate lists...
//- i personally like fa-columns a bit better
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
i.fa.fa-user.activity-type(title="member")
else if($in activityType 'createSwimlane' 'archivedSwimlane')
i.fa.fa-th-large.activity-type(title="swimlane")
else
i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
template(name='cardNotificationIcon')
i.fa.fa-clone.activity-type(title="card")
template(name='checklistNotificationIcon')
i.fa.fa-list.activity-type(title="checklist")
template(name='listNotificationIcon')
i.fa.fa-columns.activity-type(title="list")
@@ -0,0 +1,5 @@
template(name='notifications')
#notifications.board-header-btns.right
a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}")
if $.Session.get 'showNotificationsDrawer'
+notificationsDrawer(unreadNotifications=unreadNotifications)
@@ -0,0 +1,32 @@
// this hides the notifications drawer if anyone clicks off of the panel
Template.body.events({
click(event) {
if (
!$(event.target).is('#notifications *') &&
Session.get('showNotificationsDrawer')
) {
toggleNotificationsDrawer();
}
},
});
Template.notifications.helpers({
unreadNotifications() {
const notifications = Users.findOne(Meteor.userId()).notifications();
const unreadNotifications = _.filter(notifications, v => !v.read);
return unreadNotifications.length;
},
});
Template.notifications.events({
'click .notifications-drawer-toggle'() {
toggleNotificationsDrawer();
},
});
export function toggleNotificationsDrawer() {
Session.set(
'showNotificationsDrawer',
!Session.get('showNotificationsDrawer'),
);
}
@@ -0,0 +1,17 @@
#notifications
position: relative
.notifications-drawer-toggle
display: block
line-height: 28px
color: #f2f2f2
margin: 0 10px
width: 28px
height: 28px
text-align: center
border: 0
padding: 0
&.alert
background-color: #eb4646;
@@ -0,0 +1,16 @@
template(name='notificationsDrawer')
section#notifications-drawer(class="{{#if $.Session.get 'showReadNotifications'}}show-read{{/if}}")
.header
if $.Session.get 'showReadNotifications'
a.toggle-read {{_ 'filter-by-unread'}}
else
a.toggle-read {{_ 'view-all'}}
h5 {{_ 'notifications'}}
if($gt unreadNotifications 0)
|(#{unreadNotifications})
a.fa.fa-times-thin.close
ul.notifications
each transformedProfile.notifications
+notification(activityData=activity index=dbIndex read=read)
if($gt unreadNotifications 0)
a.all-read {{_ 'mark-all-as-read'}}
@@ -0,0 +1,38 @@
import { toggleNotificationsDrawer } from './notifications.js';
Template.notificationsDrawer.onCreated(function() {
Meteor.subscribe('notificationActivities');
Meteor.subscribe('notificationCards');
Meteor.subscribe('notificationUsers');
Meteor.subscribe('notificationsAttachments');
Meteor.subscribe('notificationChecklistItems');
Meteor.subscribe('notificationChecklists');
Meteor.subscribe('notificationComments');
Meteor.subscribe('notificationLists');
Meteor.subscribe('notificationSwimlanes');
});
Template.notificationsDrawer.helpers({
transformedProfile() {
return Users.findOne(Meteor.userId());
},
});
Template.notificationsDrawer.events({
'click .all-read'() {
const notifications = Meteor.user().profile.notifications;
for (const index in notifications) {
if (notifications.hasOwnProperty(index) && !notifications[index].read) {
const update = {};
update[`profile.notifications.${index}.read`] = Date.now();
Users.update(Meteor.userId(), { $set: update });
}
}
},
'click .close'() {
toggleNotificationsDrawer();
},
'click .toggle-read'() {
Session.set('showReadNotifications', !Session.get('showReadNotifications'));
},
});
@@ -0,0 +1,57 @@
belize = #2980b9
section#notifications-drawer
position: fixed
top: 28px
right: 0
width: 400px
background-color: #fafafa
box-shadow: 0 1px 2px rgba(0,0,0,0.15)
border-radius: 2px
max-height: calc(100vh - 28px - 36px)
color: black
padding-top 36px;
overflow: scroll
a:hover
color: belize !important
.header
position: fixed
top 28px
right 0
width calc(400px - 32px)
padding: 8px 16px
background: #ededed
border-bottom: 1px solid #dbdbdb
z-index 2
.toggle-read
position absolute
left 16px
top calc(50% - 8px)
color belize
h5
text-align: center
margin: 0
.close
position: absolute
top: calc(50% - 12px)
right: 12px
font-size: 24px
height: 24px
line-height: 24px
opacity 1
.all-read
color belize
background-color: #fafafa
margin 8px 16px 12px
display inline-block
ul.notifications
display: block
padding: 0px 16px
margin: 0
+4
View File
@@ -267,6 +267,10 @@ template(name="outgoingWebhooksPopup")
template(name="boardMenuPopup")
ul.pop-over-list
li
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
li
a.js-custom-fields
i.fa.fa-list-alt
+4
View File
@@ -182,6 +182,10 @@ Template.memberPopup.helpers({
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-open-rules-view'() {
Modal.openWide('rulesMain');
Popup.close();
},
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
+6 -6
View File
@@ -98,12 +98,12 @@ template(name="changeLanguagePopup")
template(name="changeSettingsPopup")
ul.pop-over-list
li
a.js-toggle-system-messages
i.fa.fa-comments-o
| {{_ 'hide-system-messages'}}
if hiddenSystemMessages
i.fa.fa-check
//li
// a.js-toggle-system-messages
// i.fa.fa-comments-o
// | {{_ 'hide-system-messages'}}
// if hiddenSystemMessages
// i.fa.fa-check
li
a.js-toggle-desktop-drag-handles
i.fa.fa-arrows
-204
View File
@@ -206,207 +206,3 @@ escapeActions.forEach(actionName => {
},
);
});
// Prevent @member mentions on Add Comment input field
// from closing card, part 5.
// This duplicate below of above popup function is needed, because at
// wekan/components/main/editor.js at bottom is popping up visible
// @member mention, and it seems to trigger closing also card popup,
// so in below closing popup is disabled.
window.PopupNoClose = new (class {
constructor() {
// The template we use to render popups
this.template = Template.popup;
// We only want to display one popup at a time and we keep the view object
// in this `Popup.current` variable. If there is no popup currently opened
// the value is `null`.
this.current = null;
// It's possible to open a sub-popup B from a popup A. In that case we keep
// the data of popup A so we can return back to it. Every time we open a new
// popup the stack grows, every time we go back the stack decrease, and if
// we close the popup the stack is reseted to the empty stack [].
this._stack = [];
// We invalidate this internal dependency every time the top of the stack
// has changed and we want to re-render a popup with the new top-stack data.
this._dep = new Tracker.Dependency();
}
/// This function returns a callback that can be used in an event map:
/// Template.tplName.events({
/// 'click .elementClass': Popup.open("popupName"),
/// });
/// The popup inherit the data context of its parent.
open(name) {
const self = this;
const popupName = `${name}Popup`;
function clickFromPopup(evt) {
return $(evt.target).closest('.js-pop-over').length !== 0;
}
return function(evt) {
// If a popup is already opened, clicking again on the opener element
// should close it -- and interrupt the current `open` function.
/*
if (self.isOpen()) {
const previousOpenerElement = self._getTopStack().openerElement;
if (previousOpenerElement === evt.currentTarget) {
self.close();
return;
} else {
$(previousOpenerElement).removeClass('is-active');
}
}
*/
// We determine the `openerElement` (the DOM element that is being clicked
// and the one we take in reference to position the popup) from the event
// if the popup has no parent, or from the parent `openerElement` if it
// has one. This allows us to position a sub-popup exactly at the same
// position than its parent.
let openerElement;
if (clickFromPopup(evt)) {
openerElement = self._getTopStack().openerElement;
} else {
self._stack = [];
openerElement = evt.currentTarget;
}
$(openerElement).addClass('is-active');
evt.preventDefault();
// We push our popup data to the stack. The top of the stack is always
// used as the data source for our current popup.
self._stack.push({
popupName,
openerElement,
hasPopupParent: clickFromPopup(evt),
title: self._getTitle(popupName),
depth: self._stack.length,
offset: self._getOffset(openerElement),
dataContext: (this && this.currentData && this.currentData()) || this,
});
// If there are no popup currently opened we use the Blaze API to render
// one into the DOM. We use a reactive function as the data parameter that
// return the complete along with its top element and depends on our
// internal dependency that is being invalidated every time the top
// element of the stack has changed and we want to update the popup.
//
// Otherwise if there is already a popup open we just need to invalidate
// our internal dependency, and since we just changed the top element of
// our internal stack, the popup will be updated with the new data.
if (!self.isOpen()) {
self.current = Blaze.renderWithData(
self.template,
() => {
self._dep.depend();
return { ...self._getTopStack(), stack: self._stack };
},
document.body,
);
} else {
self._dep.changed();
}
};
}
/// This function returns a callback that can be used in an event map:
/// Template.tplName.events({
/// 'click .elementClass': Popup.afterConfirm("popupName", function() {
/// // What to do after the user has confirmed the action
/// }),
/// });
afterConfirm(name, action) {
const self = this;
return function(evt, tpl) {
const context = (this.currentData && this.currentData()) || this;
context.__afterConfirmAction = action;
self.open(name).call(context, evt, tpl);
};
}
/// The public reactive state of the popup.
isOpen() {
this._dep.changed();
return Boolean(this.current);
}
/// In case the popup was opened from a parent popup we can get back to it
/// with this `Popup.back()` function. You can go back several steps at once
/// by providing a number to this function, e.g. `Popup.back(2)`. In this case
/// intermediate popup won't even be rendered on the DOM. If the number of
/// steps back is greater than the popup stack size, the popup will be closed.
back(n = 1) {
if (this._stack.length > n) {
_.times(n, () => this._stack.pop());
this._dep.changed();
}
// else {
// this.close();
//}
}
/// Close the current opened popup.
/*
close() {
if (this.isOpen()) {
Blaze.remove(this.current);
this.current = null;
const openerElement = this._getTopStack().openerElement;
$(openerElement).removeClass('is-active');
this._stack = [];
}
}
*/
getOpenerComponent() {
const { openerElement } = Template.parentData(4);
return BlazeComponent.getComponentForElement(openerElement);
}
// An utility fonction that returns the top element of the internal stack
_getTopStack() {
return this._stack[this._stack.length - 1];
}
// We automatically calculate the popup offset from the reference element
// position and dimensions. We also reactively use the window dimensions to
// ensure that the popup is always visible on the screen.
_getOffset(element) {
const $element = $(element);
return () => {
Utils.windowResizeDep.depend();
if (Utils.isMiniScreen()) return { left: 0, top: 0 };
const offset = $element.offset();
const popupWidth = 300 + 15;
return {
left: Math.min(offset.left, $(window).width() - popupWidth),
top: offset.top + $element.outerHeight(),
};
};
}
// We get the title from the translation files. Instead of returning the
// result, we return a function that compute the result and since `TAPi18n.__`
// is a reactive data source, the title will be changed reactively.
_getTitle(popupName) {
return () => {
const translationKey = `${popupName}-title`;
// XXX There is no public API to check if there is an available
// translation for a given key. So we try to translate the key and if the
// translation output equals the key input we deduce that no translation
// was available and returns `false`. There is a (small) risk a false
// positives.
const title = TAPi18n.__(translationKey);
// when popup showed as full of small screen, we need a default header to clearly see [X] button
const defaultTitle = Utils.isMiniScreen() ? '' : false;
return title !== translationKey ? title : defaultTitle;
};
}
})();
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+9 -3
View File
@@ -64,7 +64,7 @@
"activity-unchecked-item": "unchecked %s in checklist %s of %s",
"activity-checklist-added": "added checklist to %s",
"activity-checklist-removed": "removed a checklist from %s",
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"activity-checklist-completed": "completed checklist %s of %s",
"activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
"activity-checklist-item-added": "added checklist item to '%s' in %s",
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
@@ -440,7 +440,7 @@
"save": "Save",
"search": "Search",
"rules": "Rules",
"search-cards": "Search from card/list titles and descriptions on this board",
"search-cards": "Search from card/list titles, descriptions and custom fields on this board",
"search-example": "Text to search for?",
"select-color": "Select Color",
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
@@ -762,5 +762,11 @@
"show-on-card": "Show on Card",
"new": "New",
"editUserPopup-title": "Edit User",
"newUserPopup-title": "New User"
"newUserPopup-title": "New User",
"notifications": "Notifications",
"view-all": "View All",
"filter-by-unread": "Filter by Unread",
"mark-all-as-read": "Mark all as read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
}
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+6 -6
View File
@@ -219,6 +219,9 @@ if (Meteor.isServer) {
type: 'card',
activityType: 'addAttachment',
attachmentId: doc._id,
// this preserves the name so that notifications can be meaningful after
// this file is removed
attachmentName: doc.original.name,
boardId: doc.boardId,
cardId: doc.cardId,
listId: doc.listId,
@@ -246,18 +249,15 @@ if (Meteor.isServer) {
type: 'card',
activityType: 'deleteAttachment',
attachmentId: doc._id,
// this preserves the name so that notifications can be meaningful after
// this file is removed
attachmentName: doc.original.name,
boardId: doc.boardId,
cardId: doc.cardId,
listId: doc.listId,
swimlaneId: doc.swimlaneId,
});
});
Attachments.files.after.remove((userId, doc) => {
Activities.remove({
attachmentId: doc._id,
});
});
}
export default Attachments;
+5 -1
View File
@@ -806,7 +806,11 @@ Boards.helpers({
if (term) {
const regex = new RegExp(term, 'i');
query.$or = [{ title: regex }, { description: regex }];
query.$or = [
{ title: regex },
{ description: regex },
{ customFields: { $elemMatch: { value: regex } } },
];
}
return Cards.find(query, projection);
+6
View File
@@ -369,6 +369,9 @@ if (Meteor.isServer) {
activityType: 'createList',
boardId: doc.boardId,
listId: doc._id,
// this preserves the name so that the activity can be useful after the
// list is deleted
title: doc.title,
});
});
@@ -397,6 +400,9 @@ if (Meteor.isServer) {
activityType: 'archivedList',
listId: doc._id,
boardId: doc.boardId,
// this preserves the name so that the activity can be useful after the
// list is deleted
title: doc.title,
});
}
});
+30 -3
View File
@@ -165,7 +165,20 @@ Users.attachSchema(
/**
* enabled notifications for the user
*/
type: [String],
type: [Object],
optional: true,
},
'profile.notifications.$.activity': {
/**
* The id of the activity this notification references
*/
type: String,
},
'profile.notifications.$.read': {
/**
* the date on which this notification was read
*/
type: Date,
optional: true,
},
'profile.showCardsCountAt': {
@@ -429,6 +442,20 @@ Users.helpers({
return _.contains(notifications, activityId);
},
notifications() {
const { notifications = [] } = this.profile || {};
for (const index in notifications) {
if (!notifications.hasOwnProperty(index)) continue;
const notification = notifications[index];
// this preserves their db sort order for editing
notification.dbIndex = index;
notification.activity = Activities.findOne(notification.activity);
}
// this sorts them newest to oldest to match Trello's behavior
notifications.reverse();
return notifications;
},
hasShowDesktopDragHandles() {
const profile = this.profile || {};
return profile.showDesktopDragHandles || false;
@@ -573,7 +600,7 @@ Users.mutations({
addNotification(activityId) {
return {
$addToSet: {
'profile.notifications': activityId,
'profile.notifications': { activity: activityId },
},
};
},
@@ -581,7 +608,7 @@ Users.mutations({
removeNotification(activityId) {
return {
$pull: {
'profile.notifications': activityId,
'profile.notifications': { activity: activityId },
},
};
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v3.86.0",
"version": "v3.89.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v3.86.0",
"version": "v3.89.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
+29 -15
View File
@@ -1524,7 +1524,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
<ul class="toc-list-h1">
<li>
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v3.86">Wekan REST API v3.86</a>
<a href="#wekan-rest-api" class="toc-h1 toc-link" data-title="Wekan REST API v3.89">Wekan REST API v3.89</a>
</li>
@@ -2017,7 +2017,7 @@ var n=this.pipeline.run(e.tokenizer(t)),r=new e.Vector,i=[],o=this._fields.reduc
<div class="page-wrapper">
<div class="dark-box"></div>
<div class="content">
<h1 id="wekan-rest-api">Wekan REST API v3.86</h1>
<h1 id="wekan-rest-api">Wekan REST API v3.89</h1>
<blockquote>
<p>Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.</p>
</blockquote>
@@ -11665,9 +11665,9 @@ System.out.println(response.toString());
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"language"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"notifications"</span>: [
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"notifications"</span>: [],
<span class="hljs-attr">"activity"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"read"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"showCardsCountAt"</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">"starredBoards"</span>: [
<span class="hljs-string">"string"</span>
@@ -12320,9 +12320,9 @@ System.out.println(response.toString());
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"language"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"notifications"</span>: [
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"notifications"</span>: [],
<span class="hljs-attr">"activity"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"read"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"showCardsCountAt"</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">"starredBoards"</span>: [
<span class="hljs-string">"string"</span>
@@ -15625,9 +15625,9 @@ UserSecurity
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"language"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"notifications"</span>: [
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"notifications"</span>: [],
<span class="hljs-attr">"activity"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"read"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"showCardsCountAt"</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">"starredBoards"</span>: [
<span class="hljs-string">"string"</span>
@@ -15793,9 +15793,9 @@ UserSecurity
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"language"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"notifications"</span>: [
<span class="hljs-string">"string"</span>
],
<span class="hljs-attr">"notifications"</span>: [],
<span class="hljs-attr">"activity"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"read"</span>: <span class="hljs-string">"string"</span>,
<span class="hljs-attr">"showCardsCountAt"</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">"starredBoards"</span>: [
<span class="hljs-string">"string"</span>
@@ -15887,12 +15887,26 @@ UserSecurity
</tr>
<tr>
<td>notifications</td>
<td>[string]</td>
<td>[<a href="#schemausersprofilenotifications">UsersProfileNotifications</a>]</td>
<td>false</td>
<td>none</td>
<td>enabled notifications for the user</td>
</tr>
<tr>
<td>activity</td>
<td>string</td>
<td>true</td>
<td>none</td>
<td>The id of the activity this notification references</td>
</tr>
<tr>
<td>read</td>
<td>string</td>
<td>false</td>
<td>none</td>
<td>the date on which this notification was read</td>
</tr>
<tr>
<td>showCardsCountAt</td>
<td>number</td>
<td>false</td>
+11 -3
View File
@@ -1,7 +1,7 @@
swagger: '2.0'
info:
title: Wekan REST API
version: v3.86
version: v3.89
description: |
The REST API allows you to control and extend Wekan with ease.
@@ -3025,8 +3025,15 @@ definitions:
enabled notifications for the user
type: array
items:
type: string
x-nullable: true
$ref: "#/definitions/UsersProfileNotifications"
activity:
description: |
The id of the activity this notification references
type: string
read:
description: |
the date on which this notification was read
type: string
showCardsCountAt:
description: |
showCardCountAt field of the user
@@ -3078,6 +3085,7 @@ definitions:
Reference to the board templates swimlane Id
type: string
required:
- activity
- templatesBoardId
- cardTemplatesSwimlaneId
- listTemplatesSwimlaneId
+2 -2
View File
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
appTitle = (defaultText = "Wekan"),
# The name of the app as it is displayed to the user.
appVersion = 386,
appVersion = 389,
# Increment this for every release.
appMarketingVersion = (defaultText = "3.86.0~2020-03-24"),
appMarketingVersion = (defaultText = "3.89.0~2020-04-05"),
# Human-readable presentation of the app version.
minUpgradableAppVersion = 0,
+3 -7
View File
@@ -1,9 +1,5 @@
Meteor.startup(() => {
// XXX: add activity id to profile.notifications,
// it can be displayed and rendered on web or mobile UI
// will uncomment the following code once UI implemented
//
// Notifications.subscribe('profile', (user, title, description, params) => {
// user.addNotification(params.activityId);
// });
Notifications.subscribe('profile', (user, title, description, params) => {
user.addNotification(params.activityId);
});
});
+101
View File
@@ -0,0 +1,101 @@
// We use these when displaying notifications in the notificationsDrawer
// gets all activities associated with the current user
Meteor.publish('notificationActivities', () => {
return activities();
});
// gets all attachments associated with activities associated with the current user
Meteor.publish('notificationAttachments', function() {
return Attachments.find({
_id: {
$in: activities()
.map(v => v.attachmentId)
.filter(v => !!v),
},
});
});
// gets all cards associated with activities associated with the current user
Meteor.publish('notificationCards', function() {
return Cards.find({
_id: {
$in: activities()
.map(v => v.cardId)
.filter(v => !!v),
},
});
});
// gets all checklistItems associated with activities associated with the current user
Meteor.publish('notificationChecklistItems', function() {
return ChecklistItems.find({
_id: {
$in: activities()
.map(v => v.checklistItemId)
.filter(v => !!v),
},
});
});
// gets all checklists associated with activities associated with the current user
Meteor.publish('notificationChecklists', function() {
return Checklists.find({
_id: {
$in: activities()
.map(v => v.checklistId)
.filter(v => !!v),
},
});
});
// gets all comments associated with activities associated with the current user
Meteor.publish('notificationComments', function() {
return CardComments.find({
_id: {
$in: activities()
.map(v => v.commentId)
.filter(v => !!v),
},
});
});
// gets all lists associated with activities associated with the current user
Meteor.publish('notificationLists', function() {
return Lists.find({
_id: {
$in: activities()
.map(v => v.listId)
.filter(v => !!v),
},
});
});
// gets all swimlanes associated with activities associated with the current user
Meteor.publish('notificationSwimlanes', function() {
return Swimlanes.find({
_id: {
$in: activities()
.map(v => v.swimlaneId)
.filter(v => !!v),
},
});
});
// gets all users associated with activities associated with the current user
Meteor.publish('notificationUsers', function() {
return Users.find({
_id: {
$in: activities()
.map(v => v.userId)
.filter(v => !!v),
},
});
});
function activities() {
const notifications = Meteor.user().profile.notifications || [];
return Activities.find({
_id: { $in: notifications.map(v => v.activity) },
});
}
+1 -1
View File
@@ -7,7 +7,7 @@
# Debug OIDC OAuth2 etc.
#export DEBUG=true
#---------------------------------------------
export MONGO_URL='mongodb://127.0.0.1:27018/wekan'
export MONGO_URL='mongodb://127.0.0.1:27017/wekan'
#---------------------------------------------
# Production: https://example.com/wekan
# Local: http://localhost:2000