mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
merge with /devel
This commit is contained in:
commit
a7427b9ae4
27 changed files with 321 additions and 112 deletions
78
.eslintrc
78
.eslintrc
|
|
@ -1,7 +1,14 @@
|
||||||
ecmaFeatures:
|
ecmaFeatures:
|
||||||
experimentalObjectRestSpread: true
|
experimentalObjectRestSpread: true
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- meteor
|
||||||
|
|
||||||
|
parser: babel-eslint
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
strict: 0
|
||||||
|
no-undef: 2
|
||||||
accessor-pairs: 2
|
accessor-pairs: 2
|
||||||
comma-dangle: [2, 'always-multiline']
|
comma-dangle: [2, 'always-multiline']
|
||||||
consistent-return: 2
|
consistent-return: 2
|
||||||
|
|
@ -43,36 +50,39 @@ rules:
|
||||||
prefer-spread: 2
|
prefer-spread: 2
|
||||||
prefer-template: 2
|
prefer-template: 2
|
||||||
|
|
||||||
globals:
|
# eslint-plugin-meteor
|
||||||
# Meteor globals
|
## Meteor API
|
||||||
Meteor: false
|
meteor/globals: 2
|
||||||
DDP: false
|
meteor/core: 2
|
||||||
Mongo: false
|
meteor/pubsub: 2
|
||||||
Session: false
|
meteor/methods: 2
|
||||||
Accounts: false
|
meteor/check: 2
|
||||||
Template: false
|
meteor/connections: 2
|
||||||
Blaze: false
|
meteor/collections: 2
|
||||||
UI: false
|
meteor/session: [2, 'no-equal']
|
||||||
Match: false
|
|
||||||
check: false
|
|
||||||
Tracker: false
|
|
||||||
Deps: false
|
|
||||||
ReactiveVar: false
|
|
||||||
EJSON: false
|
|
||||||
HTTP: false
|
|
||||||
Email: false
|
|
||||||
Assets: false
|
|
||||||
Handlebars: false
|
|
||||||
Package: false
|
|
||||||
App: false
|
|
||||||
Npm: false
|
|
||||||
Tinytest: false
|
|
||||||
Random: false
|
|
||||||
HTML: false
|
|
||||||
|
|
||||||
|
## Best practices
|
||||||
|
meteor/no-session: 0
|
||||||
|
meteor/no-zero-timeout: 2
|
||||||
|
meteor/no-blaze-lifecycle-assignment: 2
|
||||||
|
|
||||||
|
settings:
|
||||||
|
meteor:
|
||||||
|
|
||||||
|
# Our collections
|
||||||
|
collections:
|
||||||
|
- AccountsTemplates
|
||||||
|
- Activities
|
||||||
|
- Attachments
|
||||||
|
- Boards
|
||||||
|
- CardComments
|
||||||
|
- Cards
|
||||||
|
- Lists
|
||||||
|
- UnsavedEditCollection
|
||||||
|
- Users
|
||||||
|
|
||||||
|
globals:
|
||||||
# Exported by packages we use
|
# Exported by packages we use
|
||||||
'$': false
|
|
||||||
_: false
|
|
||||||
autosize: false
|
autosize: false
|
||||||
Avatar: true
|
Avatar: true
|
||||||
Avatars: true
|
Avatars: true
|
||||||
|
|
@ -80,6 +90,7 @@ globals:
|
||||||
BlazeLayout: false
|
BlazeLayout: false
|
||||||
DocHead: false
|
DocHead: false
|
||||||
ESSearchResults: false
|
ESSearchResults: false
|
||||||
|
FastRender: false
|
||||||
FlowRouter: false
|
FlowRouter: false
|
||||||
FS: false
|
FS: false
|
||||||
getSlug: false
|
getSlug: false
|
||||||
|
|
@ -97,17 +108,6 @@ globals:
|
||||||
T9n: false
|
T9n: false
|
||||||
TAPi18n: false
|
TAPi18n: false
|
||||||
|
|
||||||
# Our collections
|
|
||||||
AccountsTemplates: true
|
|
||||||
Activities: true
|
|
||||||
Attachments: true
|
|
||||||
Boards: true
|
|
||||||
CardComments: true
|
|
||||||
Cards: true
|
|
||||||
Lists: true
|
|
||||||
UnsavedEditCollection: true
|
|
||||||
Users: true
|
|
||||||
|
|
||||||
# Our objects
|
# Our objects
|
||||||
CSSEvents: true
|
CSSEvents: true
|
||||||
EscapeActions: true
|
EscapeActions: true
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@
|
||||||
.tx/
|
.tx/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
tmp/
|
tmp/
|
||||||
|
node_modules/
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@
|
||||||
#
|
#
|
||||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||||
# but you can also edit it by hand.
|
# but you can also edit it by hand.
|
||||||
#
|
|
||||||
# XXX Should we replace tmeasday:presence by 3stack:presence? Or maybe the
|
|
||||||
# packages will merge in the future?
|
|
||||||
|
|
||||||
meteor-base
|
meteor-base
|
||||||
|
|
||||||
|
|
@ -52,6 +49,7 @@ audit-argument-checks
|
||||||
kadira:blaze-layout
|
kadira:blaze-layout
|
||||||
kadira:dochead
|
kadira:dochead
|
||||||
kadira:flow-router
|
kadira:flow-router
|
||||||
|
meteorhacks:fast-render
|
||||||
meteorhacks:picker
|
meteorhacks:picker
|
||||||
meteorhacks:subs-manager
|
meteorhacks:subs-manager
|
||||||
mquandalle:autofocus
|
mquandalle:autofocus
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ cfs:tempstore@0.1.5
|
||||||
cfs:upload-http@0.0.20
|
cfs:upload-http@0.0.20
|
||||||
cfs:worker@0.1.4
|
cfs:worker@0.1.4
|
||||||
check@1.1.0
|
check@1.1.0
|
||||||
|
chuangbo:cookie@1.1.0
|
||||||
coffeescript@1.0.11
|
coffeescript@1.0.11
|
||||||
cosmos:browserify@0.8.3
|
cosmos:browserify@0.8.3
|
||||||
dburles:collection-helpers@1.0.4
|
dburles:collection-helpers@1.0.4
|
||||||
|
|
@ -75,6 +76,8 @@ meteor-base@1.0.1
|
||||||
meteor-platform@1.2.3
|
meteor-platform@1.2.3
|
||||||
meteorhacks:aggregate@1.3.0
|
meteorhacks:aggregate@1.3.0
|
||||||
meteorhacks:collection-utils@1.2.0
|
meteorhacks:collection-utils@1.2.0
|
||||||
|
meteorhacks:fast-render@2.10.0
|
||||||
|
meteorhacks:inject-data@1.4.1
|
||||||
meteorhacks:picker@1.0.3
|
meteorhacks:picker@1.0.3
|
||||||
meteorhacks:subs-manager@1.6.2
|
meteorhacks:subs-manager@1.6.2
|
||||||
meteorspark:util@0.2.0
|
meteorspark:util@0.2.0
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10.40"
|
- "0.10.40"
|
||||||
install:
|
install:
|
||||||
- "npm install -g eslint"
|
- "npm install"
|
||||||
- "npm install -g eslint-plugin-meteor"
|
|
||||||
script:
|
script:
|
||||||
- "eslint ./"
|
- "npm test"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,13 @@
|
||||||
This release features:
|
This release features:
|
||||||
|
|
||||||
* Card import from Trello
|
* Card import from Trello
|
||||||
|
* Autocompletion in the minicard editor. Start with <kbd>@</kbd> to start the
|
||||||
|
a board member autocompletion, or <kbd>#</kbd> for a label.
|
||||||
|
* Accelerate the initial page rendering by sending the data on the intial HTTP
|
||||||
|
response instead of waiting for the DDP connection to open.
|
||||||
|
|
||||||
Thanks to GitHub users AlexanderS, fisle, ndarilek, and xavierpriour for their
|
Thanks to GitHub users AlexanderS, fisle, FuzzyWuzzie, ndarilek, and
|
||||||
contributions.
|
xavierpriour for their contributions.
|
||||||
|
|
||||||
# v0.9
|
# v0.9
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
'submit .js-edit-comment'(evt) {
|
'submit .js-edit-comment'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const commentText = this.currentComponent().getValue();
|
const commentText = this.currentComponent().getValue().trim();
|
||||||
const commentId = Template.parentData().commentId;
|
const commentId = Template.parentData().commentId;
|
||||||
if ($.trim(commentText)) {
|
if (commentText) {
|
||||||
CardComments.update(commentId, {
|
CardComments.update(commentId, {
|
||||||
$set: {
|
$set: {
|
||||||
text: commentText,
|
text: commentText,
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
'submit .js-new-comment-form'(evt) {
|
'submit .js-new-comment-form'(evt) {
|
||||||
const input = this.getInput();
|
const input = this.getInput();
|
||||||
if ($.trim(input.val())) {
|
const text = input.val().trim();
|
||||||
|
if (text) {
|
||||||
CardComments.insert({
|
CardComments.insert({
|
||||||
|
text,
|
||||||
boardId: this.currentData().boardId,
|
boardId: this.currentData().boardId,
|
||||||
cardId: this.currentData()._id,
|
cardId: this.currentData()._id,
|
||||||
text: input.val(),
|
|
||||||
});
|
});
|
||||||
resetCommentInput(input);
|
resetCommentInput(input);
|
||||||
Tracker.flush();
|
Tracker.flush();
|
||||||
|
|
@ -72,8 +73,9 @@ EscapeActions.register('inlinedForm',
|
||||||
docId: Session.get('currentCard'),
|
docId: Session.get('currentCard'),
|
||||||
};
|
};
|
||||||
const commentInput = $('.js-new-comment-input');
|
const commentInput = $('.js-new-comment-input');
|
||||||
if ($.trim(commentInput.val())) {
|
const draft = commentInput.val().trim();
|
||||||
UnsavedEdits.set(draftKey, commentInput.val());
|
if (draft) {
|
||||||
|
UnsavedEdits.set(draftKey, draft);
|
||||||
} else {
|
} else {
|
||||||
UnsavedEdits.reset(draftKey);
|
UnsavedEdits.reset(draftKey);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
openNewListForm() {
|
openNewListForm() {
|
||||||
this.childrenComponents('addListForm')[0].open();
|
this.childComponents('addListForm')[0].open();
|
||||||
},
|
},
|
||||||
|
|
||||||
// XXX Flow components allow us to avoid creating these two setter methods by
|
// XXX Flow components allow us to avoid creating these two setter methods by
|
||||||
|
|
@ -45,7 +45,8 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollLeft(position = 0) {
|
scrollLeft(position = 0) {
|
||||||
this.$('.js-lists').animate({
|
const lists = this.$('.js-lists');
|
||||||
|
lists && lists.animate({
|
||||||
scrollLeft: position,
|
scrollLeft: position,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -179,22 +180,24 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
// Proxy
|
// Proxy
|
||||||
open() {
|
open() {
|
||||||
this.childrenComponents('inlinedForm')[0].open();
|
this.childComponents('inlinedForm')[0].open();
|
||||||
},
|
},
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [{
|
return [{
|
||||||
submit(evt) {
|
submit(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const title = this.find('.list-name-input');
|
const titleInput = this.find('.list-name-input');
|
||||||
if ($.trim(title.value)) {
|
const title = titleInput.value.trim();
|
||||||
|
if (title) {
|
||||||
Lists.insert({
|
Lists.insert({
|
||||||
title: title.value,
|
title,
|
||||||
boardId: Session.get('currentBoard'),
|
boardId: Session.get('currentBoard'),
|
||||||
sort: $('.list').length,
|
sort: $('.list').length,
|
||||||
});
|
});
|
||||||
|
|
||||||
title.value = '';
|
titleInput.value = '';
|
||||||
|
titleInput.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
reachNextPeak() {
|
reachNextPeak() {
|
||||||
const activitiesComponent = this.childrenComponents('activities')[0];
|
const activitiesComponent = this.childComponents('activities')[0];
|
||||||
activitiesComponent.loadNextPage();
|
activitiesComponent.loadNextPage();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -75,8 +75,8 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
'submit .js-card-details-title'(evt) {
|
'submit .js-card-details-title'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const title = this.currentComponent().getValue();
|
const title = this.currentComponent().getValue().trim();
|
||||||
if ($.trim(title)) {
|
if (title) {
|
||||||
this.data().setTitle(title);
|
this.data().setTitle(title);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -106,7 +106,7 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
close(isReset = false) {
|
close(isReset = false) {
|
||||||
if (this.isOpen.get() && !isReset) {
|
if (this.isOpen.get() && !isReset) {
|
||||||
const draft = $.trim(this.getValue());
|
const draft = this.getValue().trim();
|
||||||
if (draft !== Cards.findOne(Session.get('currentCard')).description) {
|
if (draft !== Cards.findOne(Session.get('currentCard')).description) {
|
||||||
UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
|
UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -617,8 +617,15 @@ button
|
||||||
margin-right: 5px
|
margin-right: 5px
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
|
|
||||||
|
.minicard-label
|
||||||
|
width: 11px
|
||||||
|
height: @width
|
||||||
|
border-radius: 2px
|
||||||
|
margin: 2px 7px -2px -2px
|
||||||
|
display: inline-block
|
||||||
|
|
||||||
&.active
|
&.active
|
||||||
background: #005377
|
background: #005377
|
||||||
|
|
||||||
a
|
a, .quiet
|
||||||
color: white
|
color: white
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
// Proxy
|
// Proxy
|
||||||
openForm(options) {
|
openForm(options) {
|
||||||
this.childrenComponents('listBody')[0].openForm(options);
|
this.childComponents('listBody')[0].openForm(options);
|
||||||
},
|
},
|
||||||
|
|
||||||
onCreated() {
|
onCreated() {
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,20 @@ template(name="listBody")
|
||||||
|
|
||||||
template(name="addCardForm")
|
template(name="addCardForm")
|
||||||
.minicard.minicard-composer.js-composer
|
.minicard.minicard-composer.js-composer
|
||||||
.minicard-detailss.clearfix
|
if getLabels
|
||||||
textarea.minicard-composer-textarea.js-card-title(autofocus)
|
.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
|
.minicard-members.js-minicard-composer-members
|
||||||
|
each members.get
|
||||||
|
+userAvatar(userId=this)
|
||||||
|
|
||||||
.add-controls.clearfix
|
.add-controls.clearfix
|
||||||
button.primary.confirm(type="submit") {{_ 'add'}}
|
button.primary.confirm(type="submit") {{_ 'add'}}
|
||||||
a.fa.fa-times-thin.js-close-inlined-form
|
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 = options || {};
|
||||||
options.position = options.position || 'top';
|
options.position = options.position || 'top';
|
||||||
|
|
||||||
const forms = this.childrenComponents('inlinedForm');
|
const forms = this.childComponents('inlinedForm');
|
||||||
let form = _.find(forms, (component) => {
|
let form = forms.find((component) => {
|
||||||
return component.data().position === options.position;
|
return component.data().position === options.position;
|
||||||
});
|
});
|
||||||
if (!form && forms.length > 0) {
|
if (!form && forms.length > 0) {
|
||||||
|
|
@ -26,8 +26,10 @@ BlazeComponent.extendComponent({
|
||||||
const firstCardDom = this.find('.js-minicard:first');
|
const firstCardDom = this.find('.js-minicard:first');
|
||||||
const lastCardDom = this.find('.js-minicard:last');
|
const lastCardDom = this.find('.js-minicard:last');
|
||||||
const textarea = $(evt.currentTarget).find('textarea');
|
const textarea = $(evt.currentTarget).find('textarea');
|
||||||
const title = textarea.val();
|
|
||||||
const position = this.currentData().position;
|
const position = this.currentData().position;
|
||||||
|
const title = textarea.val().trim();
|
||||||
|
|
||||||
|
const formComponent = this.childComponents('addCardForm')[0];
|
||||||
let sortIndex;
|
let sortIndex;
|
||||||
if (position === 'top') {
|
if (position === 'top') {
|
||||||
sortIndex = Utils.calculateIndex(null, firstCardDom).base;
|
sortIndex = Utils.calculateIndex(null, firstCardDom).base;
|
||||||
|
|
@ -35,9 +37,14 @@ BlazeComponent.extendComponent({
|
||||||
sortIndex = Utils.calculateIndex(lastCardDom, null).base;
|
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({
|
const _id = Cards.insert({
|
||||||
title,
|
title,
|
||||||
|
members,
|
||||||
|
labelIds,
|
||||||
listId: this.data()._id,
|
listId: this.data()._id,
|
||||||
boardId: this.data().board()._id,
|
boardId: this.data().board()._id,
|
||||||
sort: sortIndex,
|
sort: sortIndex,
|
||||||
|
|
@ -53,6 +60,8 @@ BlazeComponent.extendComponent({
|
||||||
if (position === 'bottom') {
|
if (position === 'bottom') {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formComponent.reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -100,11 +109,39 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('listBody');
|
}).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({
|
BlazeComponent.extendComponent({
|
||||||
template() {
|
template() {
|
||||||
return 'addCardForm';
|
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) {
|
pressKey(evt) {
|
||||||
// Pressing Enter should submit the card
|
// Pressing Enter should submit the card
|
||||||
if (evt.keyCode === 13) {
|
if (evt.keyCode === 13) {
|
||||||
|
|
@ -140,4 +177,66 @@ BlazeComponent.extendComponent({
|
||||||
keydown: this.pressKey,
|
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');
|
}).register('addCardForm');
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
editTitle(evt) {
|
editTitle(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const newTitle = this.childrenComponents('inlinedForm')[0].getValue();
|
const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
|
||||||
const list = this.currentData();
|
const list = this.currentData();
|
||||||
if ($.trim(newTitle)) {
|
if (newTitle) {
|
||||||
list.rename(newTitle);
|
list.rename(newTitle.trim());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ Template.editor.onRendered(() => {
|
||||||
{
|
{
|
||||||
match: /\B:([\-+\w]*)$/,
|
match: /\B:([\-+\w]*)$/,
|
||||||
search(term, callback) {
|
search(term, callback) {
|
||||||
callback($.map(Emoji.values, (emoji) => {
|
callback(Emoji.values.map((emoji) => {
|
||||||
return emoji.indexOf(term) === 0 ? emoji : null;
|
return emoji.includes(term) ? emoji : null;
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
template(value) {
|
template(value) {
|
||||||
|
|
@ -28,9 +28,9 @@ Template.editor.onRendered(() => {
|
||||||
match: /\B@(\w*)$/,
|
match: /\B@(\w*)$/,
|
||||||
search(term, callback) {
|
search(term, callback) {
|
||||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
callback($.map(currentBoard.members, (member) => {
|
callback(currentBoard.members.map((member) => {
|
||||||
const username = Users.findOne(member.userId).username;
|
const username = Users.findOne(member.userId).username;
|
||||||
return username.indexOf(term) === 0 ? username : null;
|
return username.includes(term) ? username : null;
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
template(value) {
|
template(value) {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
reachNextPeak() {
|
reachNextPeak() {
|
||||||
const activitiesComponent = this.childrenComponents('activities')[0];
|
const activitiesComponent = this.childComponents('activities')[0];
|
||||||
activitiesComponent.loadNextPage();
|
activitiesComponent.loadNextPage();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ Template.memberMenuPopup.events({
|
||||||
Template.editProfilePopup.events({
|
Template.editProfilePopup.events({
|
||||||
submit(evt, tpl) {
|
submit(evt, tpl) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const fullname = $.trim(tpl.find('.js-profile-fullname').value);
|
const fullname = tpl.find('.js-profile-fullname').value.trim();
|
||||||
const username = $.trim(tpl.find('.js-profile-username').value);
|
const username = tpl.find('.js-profile-username').value.trim();
|
||||||
const initials = $.trim(tpl.find('.js-profile-initials').value);
|
const initials = tpl.find('.js-profile-initials').value.trim();
|
||||||
Users.update(Meteor.userId(), {$set: {
|
Users.update(Meteor.userId(), {$set: {
|
||||||
'profile.fullname': fullname,
|
'profile.fullname': fullname,
|
||||||
'profile.initials': initials,
|
'profile.initials': initials,
|
||||||
|
|
@ -41,7 +41,7 @@ Template.changePasswordPopup.onRendered(function() {
|
||||||
|
|
||||||
Template.changeLanguagePopup.helpers({
|
Template.changeLanguagePopup.helpers({
|
||||||
languages() {
|
languages() {
|
||||||
return TAPi18n.getLanguages().map((lang, tag) => {
|
return _.map(TAPi18n.getLanguages(), (lang, tag) => {
|
||||||
const name = lang.name;
|
const name = lang.name;
|
||||||
return { tag, name };
|
return { tag, name };
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ window.Modal = new class {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open(modalName, { onCloseGoTo = ''}) {
|
open(modalName, { onCloseGoTo = ''} = {}) {
|
||||||
this._currentModal.set(modalName);
|
this._currentModal.set(modalName);
|
||||||
this._onCloseGoTo = onCloseGoTo;
|
this._onCloseGoTo = onCloseGoTo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,23 @@
|
||||||
// of the vanilla `textcomplete`.
|
// of the vanilla `textcomplete`.
|
||||||
let dropdownMenuIsOpened = false;
|
let dropdownMenuIsOpened = false;
|
||||||
|
|
||||||
$.fn.escapeableTextComplete = function(...args) {
|
$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
|
||||||
this.textcomplete(...args);
|
// 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
|
// Since commit d474017 jquery-textComplete automatically closes a potential
|
||||||
// opened dropdown menu when the user press Escape. This behavior conflicts
|
// opened dropdown menu when the user press Escape. This behavior conflicts
|
||||||
|
|
@ -18,7 +33,14 @@ $.fn.escapeableTextComplete = function(...args) {
|
||||||
},
|
},
|
||||||
'textComplete:hide'() {
|
'textComplete:hide'() {
|
||||||
Tracker.afterFlush(() => {
|
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',
|
EscapeActions.register('textcomplete',
|
||||||
() => {},
|
() => {},
|
||||||
() => dropdownMenuIsOpened
|
() => dropdownMenuIsOpened, {
|
||||||
|
noClickEscapeOn: '.textcomplete-dropdown',
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Attachments = new FS.Collection('attachments', {
|
Attachments = new FS.Collection('attachments', { // eslint-disable-line meteor/collections
|
||||||
stores: [
|
stores: [
|
||||||
|
|
||||||
// XXX Add a new store for cover thumbnails so we don't load big images in
|
// XXX Add a new store for cover thumbnails so we don't load big images in
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,11 @@ Boards.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
labelIndex(labelId) {
|
labelIndex(labelId) {
|
||||||
return _.indexOf(_.pluck(this.labels, '_id'), labelId);
|
return _.pluck(this.labels, '_id').indexOf(labelId);
|
||||||
},
|
},
|
||||||
|
|
||||||
memberIndex(memberId) {
|
memberIndex(memberId) {
|
||||||
return _.indexOf(_.pluck(this.members, 'userId'), memberId);
|
return _.pluck(this.members, 'userId').indexOf(memberId);
|
||||||
},
|
},
|
||||||
|
|
||||||
absoluteUrl() {
|
absoluteUrl() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Users = Meteor.users;
|
Users = Meteor.users; // eslint-disable-line meteor/collections
|
||||||
|
|
||||||
// Search a user in the complete server database by its name or username. This
|
// Search a user in the complete server database by its name or username. This
|
||||||
// is used for instance to add a new user to a board.
|
// is used for instance to add a new user to a board.
|
||||||
|
|
@ -8,7 +8,23 @@ Users.initEasySearch(searchInFields, {
|
||||||
returnFields: [...searchInFields, 'profile.avatarUrl'],
|
returnFields: [...searchInFields, 'profile.avatarUrl'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Meteor.isClient) {
|
||||||
|
Users.helpers({
|
||||||
|
isBoardMember() {
|
||||||
|
const board = Boards.findOne(Session.get('currentBoard'));
|
||||||
|
return board &&
|
||||||
|
_.contains(_.pluck(board.members, 'userId'), this._id) &&
|
||||||
|
_.where(board.members, {userId: this._id})[0].isActive;
|
||||||
|
},
|
||||||
|
|
||||||
|
isBoardAdmin() {
|
||||||
|
const board = Boards.findOne(Session.get('currentBoard'));
|
||||||
|
return board &&
|
||||||
|
this.isBoardMember(board) &&
|
||||||
|
_.where(board.members, {userId: this._id})[0].isAdmin;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Users.helpers({
|
Users.helpers({
|
||||||
boards() {
|
boards() {
|
||||||
|
|
@ -25,18 +41,6 @@ Users.helpers({
|
||||||
return _.contains(starredBoards, boardId);
|
return _.contains(starredBoards, boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
isBoardMember() {
|
|
||||||
const board = Boards.findOne(Session.get('currentBoard'));
|
|
||||||
return board && _.contains(_.pluck(board.members, 'userId'), this._id) &&
|
|
||||||
_.where(board.members, {userId: this._id})[0].isActive;
|
|
||||||
},
|
|
||||||
|
|
||||||
isBoardAdmin() {
|
|
||||||
const board = Boards.findOne(Session.get('currentBoard'));
|
|
||||||
return board && this.isBoardMember(board) &&
|
|
||||||
_.where(board.members, {userId: this._id})[0].isAdmin;
|
|
||||||
},
|
|
||||||
|
|
||||||
getAvatarUrl() {
|
getAvatarUrl() {
|
||||||
// Although we put the avatar picture URL in the `profile` object, we need
|
// Although we put the avatar picture URL in the `profile` object, we need
|
||||||
// to support Sandstorm which put in the `picture` attribute by default.
|
// to support Sandstorm which put in the `picture` attribute by default.
|
||||||
|
|
|
||||||
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "wekan",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "The open-source Trello-like kanban",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "npm run --silent lint"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/wekan/wekan.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/wekan/wekan/issues"
|
||||||
|
},
|
||||||
|
"homepage": "http://wekan.io",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-eslint": "4.1.3",
|
||||||
|
"eslint": "1.7.3",
|
||||||
|
"eslint-plugin-meteor": "1.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
37
sandstorm.js
37
sandstorm.js
|
|
@ -22,12 +22,12 @@ if (isSandstorm && Meteor.isServer) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateUserPermissions(userId, permissions) {
|
function updateUserPermissions(userId, permissions) {
|
||||||
const isActive = permissions.includes('participate');
|
const isActive = permissions.indexOf('participate') > -1;
|
||||||
const isAdmin = permissions.includes('configure');
|
const isAdmin = permissions.indexOf('configure') > -1;
|
||||||
const permissionDoc = { userId, isActive, isAdmin };
|
const permissionDoc = { userId, isActive, isAdmin };
|
||||||
|
|
||||||
const boardMembers = Boards.findOne(sandstormBoard._id).members;
|
const boardMembers = Boards.findOne(sandstormBoard._id).members;
|
||||||
const memberIndex = _.indexOf(_.pluck(boardMembers, 'userId'), userId);
|
const memberIndex = _.pluck(boardMembers, 'userId').indexOf(userId);
|
||||||
|
|
||||||
let modifier;
|
let modifier;
|
||||||
if (memberIndex > -1)
|
if (memberIndex > -1)
|
||||||
|
|
@ -78,17 +78,40 @@ if (isSandstorm && Meteor.isServer) {
|
||||||
// unique board document. Note that when the `Users.after.insert` hook is
|
// unique board document. Note that when the `Users.after.insert` hook is
|
||||||
// called, the user is inserted into the database but not connected. So
|
// called, the user is inserted into the database but not connected. So
|
||||||
// despite the appearances `userId` is null in this block.
|
// despite the appearances `userId` is null in this block.
|
||||||
//
|
|
||||||
// XXX We should support the `preferredHandle` exposed by Sandstorm
|
|
||||||
Users.after.insert((userId, doc) => {
|
Users.after.insert((userId, doc) => {
|
||||||
if (!Boards.findOne(sandstormBoard._id)) {
|
if (!Boards.findOne(sandstormBoard._id)) {
|
||||||
Boards.insert(sandstormBoard, {validate: false});
|
Boards.insert(sandstormBoard, { validate: false });
|
||||||
Activities.update(
|
Activities.update(
|
||||||
{ activityTypeId: sandstormBoard._id },
|
{ activityTypeId: sandstormBoard._id },
|
||||||
{ $set: { userId: doc._id }}
|
{ $set: { userId: doc._id }}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We rely on username uniqueness for the user mention feature, but
|
||||||
|
// Sandstorm doesn't enforce this property -- see #352. Our strategy to
|
||||||
|
// generate unique usernames from the Sandstorm `preferredHandle` is to
|
||||||
|
// append a number that we increment until we generate a username that no
|
||||||
|
// one already uses (eg, 'max', 'max1', 'max2').
|
||||||
|
function generateUniqueUsername(username, appendNumber) {
|
||||||
|
return username + String(appendNumber === 0 ? '' : appendNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = doc.services.sandstorm.preferredHandle;
|
||||||
|
let appendNumber = 0;
|
||||||
|
while (Users.findOne({
|
||||||
|
_id: { $ne: doc._id },
|
||||||
|
username: generateUniqueUsername(username, appendNumber),
|
||||||
|
})) {
|
||||||
|
appendNumber += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Users.update(doc._id, {
|
||||||
|
$set: {
|
||||||
|
username: generateUniqueUsername(username, appendNumber),
|
||||||
|
'profile.fullname': doc.services.sandstorm.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
updateUserPermissions(doc._id, doc.services.sandstorm.permissions);
|
updateUserPermissions(doc._id, doc.services.sandstorm.permissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -109,7 +132,7 @@ if (isSandstorm && Meteor.isClient) {
|
||||||
// sandstorm client to return relative paths instead of absolutes.
|
// sandstorm client to return relative paths instead of absolutes.
|
||||||
const _absoluteUrl = Meteor.absoluteUrl;
|
const _absoluteUrl = Meteor.absoluteUrl;
|
||||||
const _defaultOptions = Meteor.absoluteUrl.defaultOptions;
|
const _defaultOptions = Meteor.absoluteUrl.defaultOptions;
|
||||||
Meteor.absoluteUrl = (path, options) => {
|
Meteor.absoluteUrl = (path, options) => { // eslint-disable-line meteor/core
|
||||||
const url = _absoluteUrl(path, options);
|
const url = _absoluteUrl(path, options);
|
||||||
return url.replace(/^https?:\/\/127\.0\.0\.1:[0-9]{2,5}/, '');
|
return url.replace(/^https?:\/\/127\.0\.0\.1:[0-9]{2,5}/, '');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
7
server/publications/fast-render.js
Normal file
7
server/publications/fast-render.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
FastRender.onAllRoutes(function() {
|
||||||
|
this.subscribe('boards');
|
||||||
|
});
|
||||||
|
|
||||||
|
FastRender.route('/b/:id/:slug', function({ id }) {
|
||||||
|
this.subscribe('board', id);
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue