mirror of
https://github.com/wekan/wekan.git
synced 2026-01-31 05:35:16 +01:00
merge with /devel
This commit is contained in:
commit
a7427b9ae4
27 changed files with 321 additions and 112 deletions
|
|
@ -101,9 +101,9 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'submit .js-edit-comment'(evt) {
|
||||
evt.preventDefault();
|
||||
const commentText = this.currentComponent().getValue();
|
||||
const commentText = this.currentComponent().getValue().trim();
|
||||
const commentId = Template.parentData().commentId;
|
||||
if ($.trim(commentText)) {
|
||||
if (commentText) {
|
||||
CardComments.update(commentId, {
|
||||
$set: {
|
||||
text: commentText,
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'submit .js-new-comment-form'(evt) {
|
||||
const input = this.getInput();
|
||||
if ($.trim(input.val())) {
|
||||
const text = input.val().trim();
|
||||
if (text) {
|
||||
CardComments.insert({
|
||||
text,
|
||||
boardId: this.currentData().boardId,
|
||||
cardId: this.currentData()._id,
|
||||
text: input.val(),
|
||||
});
|
||||
resetCommentInput(input);
|
||||
Tracker.flush();
|
||||
|
|
@ -72,8 +73,9 @@ EscapeActions.register('inlinedForm',
|
|||
docId: Session.get('currentCard'),
|
||||
};
|
||||
const commentInput = $('.js-new-comment-input');
|
||||
if ($.trim(commentInput.val())) {
|
||||
UnsavedEdits.set(draftKey, commentInput.val());
|
||||
const draft = commentInput.val().trim();
|
||||
if (draft) {
|
||||
UnsavedEdits.set(draftKey, draft);
|
||||
} else {
|
||||
UnsavedEdits.reset(draftKey);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
openNewListForm() {
|
||||
this.childrenComponents('addListForm')[0].open();
|
||||
this.childComponents('addListForm')[0].open();
|
||||
},
|
||||
|
||||
// XXX Flow components allow us to avoid creating these two setter methods by
|
||||
|
|
@ -45,7 +45,8 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
scrollLeft(position = 0) {
|
||||
this.$('.js-lists').animate({
|
||||
const lists = this.$('.js-lists');
|
||||
lists && lists.animate({
|
||||
scrollLeft: position,
|
||||
});
|
||||
},
|
||||
|
|
@ -179,22 +180,24 @@ BlazeComponent.extendComponent({
|
|||
|
||||
// Proxy
|
||||
open() {
|
||||
this.childrenComponents('inlinedForm')[0].open();
|
||||
this.childComponents('inlinedForm')[0].open();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [{
|
||||
submit(evt) {
|
||||
evt.preventDefault();
|
||||
const title = this.find('.list-name-input');
|
||||
if ($.trim(title.value)) {
|
||||
const titleInput = this.find('.list-name-input');
|
||||
const title = titleInput.value.trim();
|
||||
if (title) {
|
||||
Lists.insert({
|
||||
title: title.value,
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: $('.list').length,
|
||||
});
|
||||
|
||||
title.value = '';
|
||||
titleInput.value = '';
|
||||
titleInput.focus();
|
||||
}
|
||||
},
|
||||
}];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
reachNextPeak() {
|
||||
const activitiesComponent = this.childrenComponents('activities')[0];
|
||||
const activitiesComponent = this.childComponents('activities')[0];
|
||||
activitiesComponent.loadNextPage();
|
||||
},
|
||||
|
||||
|
|
@ -75,8 +75,8 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'submit .js-card-details-title'(evt) {
|
||||
evt.preventDefault();
|
||||
const title = this.currentComponent().getValue();
|
||||
if ($.trim(title)) {
|
||||
const title = this.currentComponent().getValue().trim();
|
||||
if (title) {
|
||||
this.data().setTitle(title);
|
||||
}
|
||||
},
|
||||
|
|
@ -106,7 +106,7 @@ BlazeComponent.extendComponent({
|
|||
|
||||
close(isReset = false) {
|
||||
if (this.isOpen.get() && !isReset) {
|
||||
const draft = $.trim(this.getValue());
|
||||
const draft = this.getValue().trim();
|
||||
if (draft !== Cards.findOne(Session.get('currentCard')).description) {
|
||||
UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,8 +617,15 @@ button
|
|||
margin-right: 5px
|
||||
vertical-align: middle
|
||||
|
||||
.minicard-label
|
||||
width: 11px
|
||||
height: @width
|
||||
border-radius: 2px
|
||||
margin: 2px 7px -2px -2px
|
||||
display: inline-block
|
||||
|
||||
&.active
|
||||
background: #005377
|
||||
|
||||
a
|
||||
a, .quiet
|
||||
color: white
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
|
|||
|
||||
// Proxy
|
||||
openForm(options) {
|
||||
this.childrenComponents('listBody')[0].openForm(options);
|
||||
this.childComponents('listBody')[0].openForm(options);
|
||||
},
|
||||
|
||||
onCreated() {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,20 @@ template(name="listBody")
|
|||
|
||||
template(name="addCardForm")
|
||||
.minicard.minicard-composer.js-composer
|
||||
.minicard-detailss.clearfix
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus)
|
||||
if getLabels
|
||||
.minicard-labels
|
||||
each getLabels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus)
|
||||
if members.get
|
||||
.minicard-members.js-minicard-composer-members
|
||||
each members.get
|
||||
+userAvatar(userId=this)
|
||||
|
||||
.add-controls.clearfix
|
||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
|
||||
template(name="autocompleteLabelLine")
|
||||
.minicard-label(class="card-label-{{colorName}}" title=labelName)
|
||||
span(class="{{#if hasNoName}}quiet{{/if}}")= labelName
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ BlazeComponent.extendComponent({
|
|||
options = options || {};
|
||||
options.position = options.position || 'top';
|
||||
|
||||
const forms = this.childrenComponents('inlinedForm');
|
||||
let form = _.find(forms, (component) => {
|
||||
const forms = this.childComponents('inlinedForm');
|
||||
let form = forms.find((component) => {
|
||||
return component.data().position === options.position;
|
||||
});
|
||||
if (!form && forms.length > 0) {
|
||||
|
|
@ -26,8 +26,10 @@ BlazeComponent.extendComponent({
|
|||
const firstCardDom = this.find('.js-minicard:first');
|
||||
const lastCardDom = this.find('.js-minicard:last');
|
||||
const textarea = $(evt.currentTarget).find('textarea');
|
||||
const title = textarea.val();
|
||||
const position = this.currentData().position;
|
||||
const title = textarea.val().trim();
|
||||
|
||||
const formComponent = this.childComponents('addCardForm')[0];
|
||||
let sortIndex;
|
||||
if (position === 'top') {
|
||||
sortIndex = Utils.calculateIndex(null, firstCardDom).base;
|
||||
|
|
@ -35,9 +37,14 @@ BlazeComponent.extendComponent({
|
|||
sortIndex = Utils.calculateIndex(lastCardDom, null).base;
|
||||
}
|
||||
|
||||
if ($.trim(title)) {
|
||||
const members = formComponent.members.get();
|
||||
const labelIds = formComponent.labels.get();
|
||||
|
||||
if (title) {
|
||||
const _id = Cards.insert({
|
||||
title,
|
||||
members,
|
||||
labelIds,
|
||||
listId: this.data()._id,
|
||||
boardId: this.data().board()._id,
|
||||
sort: sortIndex,
|
||||
|
|
@ -53,6 +60,8 @@ BlazeComponent.extendComponent({
|
|||
if (position === 'bottom') {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
formComponent.reset();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -100,11 +109,39 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('listBody');
|
||||
|
||||
function toggleValueInReactiveArray(reactiveValue, value) {
|
||||
const array = reactiveValue.get();
|
||||
const valueIndex = array.indexOf(value);
|
||||
if (valueIndex === -1) {
|
||||
array.push(value);
|
||||
} else {
|
||||
array.splice(valueIndex, 1);
|
||||
}
|
||||
reactiveValue.set(array);
|
||||
}
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
template() {
|
||||
return 'addCardForm';
|
||||
},
|
||||
|
||||
onCreated() {
|
||||
this.labels = new ReactiveVar([]);
|
||||
this.members = new ReactiveVar([]);
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.labels.set([]);
|
||||
this.members.set([]);
|
||||
},
|
||||
|
||||
getLabels() {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
return Boards.findOne(currentBoardId).labels.filter((label) => {
|
||||
return this.labels.get().indexOf(label._id) > -1;
|
||||
});
|
||||
},
|
||||
|
||||
pressKey(evt) {
|
||||
// Pressing Enter should submit the card
|
||||
if (evt.keyCode === 13) {
|
||||
|
|
@ -140,4 +177,66 @@ BlazeComponent.extendComponent({
|
|||
keydown: this.pressKey,
|
||||
}];
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
const editor = this;
|
||||
this.$('textarea').escapeableTextComplete([
|
||||
// User mentions
|
||||
{
|
||||
match: /\B@(\w*)$/,
|
||||
search(term, callback) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
callback($.map(currentBoard.members, (member) => {
|
||||
const user = Users.findOne(member.userId);
|
||||
return user.username.indexOf(term) === 0 ? user : null;
|
||||
}));
|
||||
},
|
||||
template(user) {
|
||||
return user.username;
|
||||
},
|
||||
replace(user) {
|
||||
toggleValueInReactiveArray(editor.members, user._id);
|
||||
return '';
|
||||
},
|
||||
index: 1,
|
||||
},
|
||||
|
||||
// Labels
|
||||
{
|
||||
match: /\B#(\w*)$/,
|
||||
search(term, callback) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
callback($.map(currentBoard.labels, (label) => {
|
||||
if (label.name.indexOf(term) > -1 ||
|
||||
label.color.indexOf(term) > -1) {
|
||||
return label;
|
||||
}
|
||||
}));
|
||||
},
|
||||
template(label) {
|
||||
return Blaze.toHTMLWithData(Template.autocompleteLabelLine, {
|
||||
hasNoName: !Boolean(label.name),
|
||||
colorName: label.color,
|
||||
labelName: label.name || label.color,
|
||||
});
|
||||
},
|
||||
replace(label) {
|
||||
toggleValueInReactiveArray(editor.labels, label._id);
|
||||
return '';
|
||||
},
|
||||
index: 1,
|
||||
},
|
||||
], {
|
||||
// When the autocomplete menu is shown we want both a press of both `Tab`
|
||||
// or `Enter` to validation the auto-completion. We also need to stop the
|
||||
// event propagation to prevent the card from submitting (on `Enter`) or
|
||||
// going on the next column (on `Tab`).
|
||||
onKeydown(evt, commands) {
|
||||
if (evt.keyCode === 9 || evt.keyCode === 13) {
|
||||
evt.stopPropagation();
|
||||
return commands.KEY_ENTER;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
}).register('addCardForm');
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ BlazeComponent.extendComponent({
|
|||
|
||||
editTitle(evt) {
|
||||
evt.preventDefault();
|
||||
const newTitle = this.childrenComponents('inlinedForm')[0].getValue();
|
||||
const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
|
||||
const list = this.currentData();
|
||||
if ($.trim(newTitle)) {
|
||||
list.rename(newTitle);
|
||||
if (newTitle) {
|
||||
list.rename(newTitle.trim());
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Template.editor.onRendered(() => {
|
|||
{
|
||||
match: /\B:([\-+\w]*)$/,
|
||||
search(term, callback) {
|
||||
callback($.map(Emoji.values, (emoji) => {
|
||||
return emoji.indexOf(term) === 0 ? emoji : null;
|
||||
callback(Emoji.values.map((emoji) => {
|
||||
return emoji.includes(term) ? emoji : null;
|
||||
}));
|
||||
},
|
||||
template(value) {
|
||||
|
|
@ -28,9 +28,9 @@ Template.editor.onRendered(() => {
|
|||
match: /\B@(\w*)$/,
|
||||
search(term, callback) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
callback($.map(currentBoard.members, (member) => {
|
||||
callback(currentBoard.members.map((member) => {
|
||||
const username = Users.findOne(member.userId).username;
|
||||
return username.indexOf(term) === 0 ? username : null;
|
||||
return username.includes(term) ? username : null;
|
||||
}));
|
||||
},
|
||||
template(value) {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
reachNextPeak() {
|
||||
const activitiesComponent = this.childrenComponents('activities')[0];
|
||||
const activitiesComponent = this.childComponents('activities')[0];
|
||||
activitiesComponent.loadNextPage();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ Template.memberMenuPopup.events({
|
|||
Template.editProfilePopup.events({
|
||||
submit(evt, tpl) {
|
||||
evt.preventDefault();
|
||||
const fullname = $.trim(tpl.find('.js-profile-fullname').value);
|
||||
const username = $.trim(tpl.find('.js-profile-username').value);
|
||||
const initials = $.trim(tpl.find('.js-profile-initials').value);
|
||||
const fullname = tpl.find('.js-profile-fullname').value.trim();
|
||||
const username = tpl.find('.js-profile-username').value.trim();
|
||||
const initials = tpl.find('.js-profile-initials').value.trim();
|
||||
Users.update(Meteor.userId(), {$set: {
|
||||
'profile.fullname': fullname,
|
||||
'profile.initials': initials,
|
||||
|
|
@ -41,7 +41,7 @@ Template.changePasswordPopup.onRendered(function() {
|
|||
|
||||
Template.changeLanguagePopup.helpers({
|
||||
languages() {
|
||||
return TAPi18n.getLanguages().map((lang, tag) => {
|
||||
return _.map(TAPi18n.getLanguages(), (lang, tag) => {
|
||||
const name = lang.name;
|
||||
return { tag, name };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
const passwordField = AccountsTemplates.removeField('password');
|
||||
const emailField = AccountsTemplates.removeField('email');
|
||||
AccountsTemplates.addFields([{
|
||||
_id: 'username',
|
||||
type: 'text',
|
||||
displayName: 'username',
|
||||
required: true,
|
||||
minLength: 2,
|
||||
}, emailField, passwordField]);
|
||||
|
||||
AccountsTemplates.configure({
|
||||
defaultLayout: 'userFormsLayout',
|
||||
defaultContentRegion: 'content',
|
||||
confirmPassword: false,
|
||||
enablePasswordChange: true,
|
||||
sendVerificationEmail: true,
|
||||
showForgotPasswordLink: true,
|
||||
onLogoutHook() {
|
||||
const homePage = 'home';
|
||||
if (FlowRouter.getRouteName() === homePage) {
|
||||
FlowRouter.reload();
|
||||
} else {
|
||||
FlowRouter.go(homePage);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
['signIn', 'signUp', 'resetPwd', 'forgotPwd', 'enrollAccount'].forEach(
|
||||
(routeName) => AccountsTemplates.configureRoute(routeName));
|
||||
|
||||
// We display the form to change the password in a popup window that already
|
||||
// have a title, so we unset the title automatically displayed by useraccounts.
|
||||
AccountsTemplates.configure({
|
||||
texts: {
|
||||
title: {
|
||||
changePwd: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
AccountsTemplates.configureRoute('changePwd', {
|
||||
redirect() {
|
||||
// XXX We should emit a notification once we have a notification system.
|
||||
// Currently the user has no indication that his modification has been
|
||||
// applied.
|
||||
Popup.back();
|
||||
},
|
||||
});
|
||||
|
|
@ -21,7 +21,7 @@ window.Modal = new class {
|
|||
}
|
||||
}
|
||||
|
||||
open(modalName, { onCloseGoTo = ''}) {
|
||||
open(modalName, { onCloseGoTo = ''} = {}) {
|
||||
this._currentModal.set(modalName);
|
||||
this._onCloseGoTo = onCloseGoTo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,23 @@
|
|||
// of the vanilla `textcomplete`.
|
||||
let dropdownMenuIsOpened = false;
|
||||
|
||||
$.fn.escapeableTextComplete = function(...args) {
|
||||
this.textcomplete(...args);
|
||||
$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
|
||||
// When the autocomplete menu is shown we want both a press of both `Tab`
|
||||
// or `Enter` to validation the auto-completion. We also need to stop the
|
||||
// event propagation to prevent EscapeActions side effect, for instance the
|
||||
// minicard submission (on `Enter`) or going on the next column (on `Tab`).
|
||||
options = {
|
||||
onKeydown(evt, commands) {
|
||||
if (evt.keyCode === 9 || evt.keyCode === 13) {
|
||||
evt.stopPropagation();
|
||||
return commands.KEY_ENTER;
|
||||
}
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
// Proxy to the vanilla jQuery component
|
||||
this.textcomplete(strategies, options, ...otherArgs);
|
||||
|
||||
// Since commit d474017 jquery-textComplete automatically closes a potential
|
||||
// opened dropdown menu when the user press Escape. This behavior conflicts
|
||||
|
|
@ -18,7 +33,14 @@ $.fn.escapeableTextComplete = function(...args) {
|
|||
},
|
||||
'textComplete:hide'() {
|
||||
Tracker.afterFlush(() => {
|
||||
dropdownMenuIsOpened = false;
|
||||
// XXX Hack. We unfortunately need to set a setTimeout here to make the
|
||||
// `noClickEscapeOn` work bellow, otherwise clicking on a autocomplete
|
||||
// item will close both the autocomplete menu (as expected) but also the
|
||||
// next item in the stack (for example the minicard editor) which we
|
||||
// don't want.
|
||||
setTimeout(() => {
|
||||
dropdownMenuIsOpened = false;
|
||||
}, 100);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -26,5 +48,7 @@ $.fn.escapeableTextComplete = function(...args) {
|
|||
|
||||
EscapeActions.register('textcomplete',
|
||||
() => {},
|
||||
() => dropdownMenuIsOpened
|
||||
() => dropdownMenuIsOpened, {
|
||||
noClickEscapeOn: '.textcomplete-dropdown',
|
||||
}
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue