summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Priour <xavier.priour@bubblyware.com>2015-12-02 09:54:21 +0100
committerXavier Priour <xavier.priour@bubblyware.com>2015-12-02 09:54:21 +0100
commit569f8d50ba5dd65be2ab2c3ee9a52ea22d2e566b (patch)
tree75b1188515d70a814933e30d1b89d63116029710
parent82c59fb85f5f44928a506da59087809640d99050 (diff)
parent827663f2550ba691067535b317dd9f0b0bec12ba (diff)
downloadwekan-569f8d50ba5dd65be2ab2c3ee9a52ea22d2e566b.tar.gz
wekan-569f8d50ba5dd65be2ab2c3ee9a52ea22d2e566b.tar.bz2
wekan-569f8d50ba5dd65be2ab2c3ee9a52ea22d2e566b.zip
Merge remote-tracking branch 'upstream/devel' into devel
-rw-r--r--.meteor/versions4
-rw-r--r--History.md12
-rw-r--r--client/components/boards/boardBody.js6
-rw-r--r--client/components/cards/attachments.jade12
-rw-r--r--client/components/cards/attachments.js79
-rw-r--r--client/components/cards/attachments.styl11
-rw-r--r--client/components/main/keyboardShortcuts.styl5
-rw-r--r--client/components/main/layouts.styl9
-rw-r--r--client/lib/dropImage.js62
-rw-r--r--client/lib/pasteImage.js57
-rw-r--r--config/accounts.js6
-rw-r--r--i18n/en.i18n.json6
-rw-r--r--sandstorm.js20
13 files changed, 270 insertions, 19 deletions
diff --git a/.meteor/versions b/.meteor/versions
index a16f56bd..fb3f61f3 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -63,7 +63,7 @@ id-map@1.0.4
idmontie:migrations@1.0.1
jquery@1.11.4
kadira:blaze-layout@2.2.0
-kadira:dochead@1.3.2
+kadira:dochead@1.4.0
kadira:flow-router@2.9.0
kenton:accounts-sandstorm@0.1.8
launch-screen@1.0.4
@@ -95,7 +95,7 @@ mquandalle:jade@0.4.5
mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:jquery-ui-drag-drop-sort@0.1.0
-mquandalle:moment@1.0.0
+mquandalle:moment@1.0.1
mquandalle:mousetrap-bindglobal@0.0.1
mquandalle:perfect-scrollbar@0.6.5_2
mquandalle:stylus@1.1.1
diff --git a/History.md b/History.md
index 505be881..4391df3b 100644
--- a/History.md
+++ b/History.md
@@ -2,14 +2,16 @@
This release features:
-* Card import from Trello
+* Trello boards and cards importation, including card history, assigned members,
+ labels, comments, and attachments;
* Autocompletion in the minicard editor. Start with <kbd>@</kbd> to start the
- a board member autocompletion, or <kbd>#</kbd> for a label.
+ 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.
+ response instead of waiting for the DDP connection to open;
+* Support images attachments copy pasting.
-Thanks to GitHub users AlexanderS, fisle, FuzzyWuzzie, ndarilek, and
-xavierpriour for their contributions.
+Thanks to GitHub users AlexanderS, fisle, floatinghotpot, FuzzyWuzzie, mnutt,
+ndarilek, SirCmpwn, and xavierpriour for their contributions.
# v0.9
diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js
index 5a74e61b..a601bc2e 100644
--- a/client/components/boards/boardBody.js
+++ b/client/components/boards/boardBody.js
@@ -134,7 +134,7 @@ Template.boardBody.onRendered(function() {
if (!Meteor.user() || !Meteor.user().isBoardMember())
return;
- self.$(self.listsDom).sortable({
+ $(self.listsDom).sortable({
tolerance: 'pointer',
helper: 'clone',
handle: '.js-list-header',
@@ -146,7 +146,7 @@ Template.boardBody.onRendered(function() {
Popup.close();
},
stop() {
- self.$('.js-lists').find('.js-list:not(.js-list-composer)').each(
+ $(self.listsDom).find('.js-list:not(.js-list-composer)').each(
(i, list) => {
const data = Blaze.getData(list);
Lists.update(data._id, {
@@ -161,7 +161,7 @@ Template.boardBody.onRendered(function() {
// Disable drag-dropping while in multi-selection mode
self.autorun(() => {
- self.$(self.listsDom).sortable('option', 'disabled',
+ $(self.listsDom).sortable('option', 'disabled',
MultiSelection.isActive());
});
diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade
index 168fc2c8..2cb3bb85 100644
--- a/client/components/cards/attachments.jade
+++ b/client/components/cards/attachments.jade
@@ -3,6 +3,16 @@ template(name="cardAttachmentsPopup")
li
input.js-attach-file.hide(type="file" name="file" multiple)
a.js-computer-upload {{_ 'computer'}}
+ li
+ a.js-upload-clipboard-image {{_ 'clipboard'}}
+
+template(name="previewClipboardImagePopup")
+ p <kbd>Ctrl</kbd>+<kbd>V</kbd> {{_ "paste-or-dragdrop"}}
+ img.preview-clipboard-image()
+ button.primary.js-upload-pasted-image {{_ 'upload'}}
+
+template(name="previewAttachedImagePopup")
+ img.preview-large-image.js-large-image-clicked(src="{{pathFor url}}")
template(name="attachmentDeletePopup")
p {{_ "attachment-delete-pop"}}
@@ -15,7 +25,7 @@ template(name="attachmentsGalery")
.attachment-thumbnail
if isUploaded
if isImage
- img.attachment-thumbnail-img(src="{{pathFor url}}")
+ img.attachment-thumbnail-img.js-preview-image(src="{{pathFor url}}")
else
span.attachment-thumbnail-ext= extension
else
diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js
index ea621a74..1e5aa03b 100644
--- a/client/components/cards/attachments.js
+++ b/client/components/cards/attachments.js
@@ -20,6 +20,39 @@ Template.attachmentsGalery.events({
'click .js-remove-cover'() {
Cards.findOne(this.cardId).unsetCover();
},
+ 'click .js-preview-image'(evt) {
+ Popup.open('previewAttachedImage').call(this, evt);
+ // when multiple thumbnails, if click one then another very fast,
+ // we might get a wrong width from previous img.
+ // when popup reused, onRendered() won't be called, so we cannot get there.
+ // here make sure to get correct size when this img fully loaded.
+ const img = $('img.preview-large-image')[0];
+ if (!img) return;
+ const rePosPopup = () => {
+ const w = img.width;
+ const h = img.height;
+ // if the image is too large, we resize & center the popup.
+ if (w > 300) {
+ $('div.pop-over').css({
+ width: (w + 20),
+ position: 'absolute',
+ left: (window.innerWidth - w)/2,
+ top: (window.innerHeight - h)/2,
+ });
+ }
+ };
+ const url = $(evt.currentTarget).attr('src');
+ if (img.src === url && img.complete)
+ rePosPopup();
+ else
+ img.onload = rePosPopup;
+ },
+});
+
+Template.previewAttachedImagePopup.events({
+ 'click .js-large-image-clicked'(){
+ Popup.close();
+ },
});
Template.cardAttachmentsPopup.events({
@@ -28,7 +61,7 @@ Template.cardAttachmentsPopup.events({
FS.Utility.eachFile(evt, (f) => {
const file = new FS.File(f);
file.boardId = card.boardId;
- file.cardId = card._id;
+ file.cardId = card._id;
Attachments.insert(file);
Popup.close();
@@ -38,4 +71,48 @@ Template.cardAttachmentsPopup.events({
tpl.find('.js-attach-file').click();
evt.preventDefault();
},
+ 'click .js-upload-clipboard-image': Popup.open('previewClipboardImage'),
+});
+
+let pastedResults = null;
+
+Template.previewClipboardImagePopup.onRendered(() => {
+ // we can paste image from clipboard
+ $(document.body).pasteImageReader((results) => {
+ if (results.dataURL.startsWith('data:image/')) {
+ $('img.preview-clipboard-image').attr('src', results.dataURL);
+ pastedResults = results;
+ }
+ });
+
+ // we can also drag & drop image file to it
+ $(document.body).dropImageReader((results) => {
+ if (results.dataURL.startsWith('data:image/')) {
+ $('img.preview-clipboard-image').attr('src', results.dataURL);
+ pastedResults = results;
+ }
+ });
+});
+
+Template.previewClipboardImagePopup.events({
+ 'click .js-upload-pasted-image'() {
+ const results = pastedResults;
+ if (results && results.file) {
+ const card = this;
+ const file = new FS.File(results.file);
+ if (!results.name) {
+ // if no filename, it's from clipboard. then we give it a name, with ext name from MIME type
+ if (typeof results.file.type === 'string') {
+ file.name(results.file.type.replace('image/', 'clipboard.'));
+ }
+ }
+ file.updatedAt(new Date());
+ file.boardId = card.boardId;
+ file.cardId = card._id;
+ Attachments.insert(file);
+ pastedResults = null;
+ $(document.body).pasteImageReader(() => {});
+ Popup.close();
+ }
+ },
});
diff --git a/client/components/cards/attachments.styl b/client/components/cards/attachments.styl
index 5cdf7386..a582f3af 100644
--- a/client/components/cards/attachments.styl
+++ b/client/components/cards/attachments.styl
@@ -45,3 +45,14 @@
display: block
box-shadow: 0 1px 2px rgba(0,0,0,.2)
+.preview-large-image
+ max-width: 1000px
+ display: block
+ box-shadow: 0 1px 2px rgba(0,0,0,.2)
+
+.preview-clipboard-image
+ width: 280px
+ height: 200px
+ display: block
+ border: 1px solid black
+ box-shadow: 0 1px 2px rgba(0,0,0,.2)
diff --git a/client/components/main/keyboardShortcuts.styl b/client/components/main/keyboardShortcuts.styl
index 42e0637b..f77d387f 100644
--- a/client/components/main/keyboardShortcuts.styl
+++ b/client/components/main/keyboardShortcuts.styl
@@ -14,11 +14,6 @@
padding: 5px 8px
margin: 5px
font-size: 18px
- font-weight: bold
- background: white
- border-radius: 3px
- border: 1px solid darken(white, 10%)
- box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.15)
.shortcuts-list-item-action
font-size: 1.4em
diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl
index 1dbefc20..fcc94251 100644
--- a/client/components/main/layouts.styl
+++ b/client/components/main/layouts.styl
@@ -172,6 +172,15 @@ dl, dt
dd
margin: 0 0 16px 24px
+kbd
+ padding: 1px 3px
+ margin: 3px
+ font-weight: bold
+ background: darken(white, 2%)
+ border-radius: 3px
+ border: 1px solid darken(white, 10%)
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.15)
+
.clear
clear: both
diff --git a/client/lib/dropImage.js b/client/lib/dropImage.js
new file mode 100644
index 00000000..592d5c8f
--- /dev/null
+++ b/client/lib/dropImage.js
@@ -0,0 +1,62 @@
+/* eslint-disable */
+
+// ------------------------------------------------------------------------
+// Created by STRd6
+// MIT License
+// https://github.com/distri/jquery-image_reader/blob/master/drop.coffee.md
+//
+// Raymond re-write it to javascript
+
+(function($) {
+ $.event.fix = (function(originalFix) {
+ return function(event) {
+ event = originalFix.apply(this, arguments);
+ if (event.type.indexOf('drag') === 0 || event.type.indexOf('drop') === 0) {
+ event.dataTransfer = event.originalEvent.dataTransfer;
+ }
+ return event;
+ };
+ })($.event.fix);
+
+ const defaults = {
+ callback: $.noop,
+ matchType: /image.*/,
+ };
+
+ return $.fn.dropImageReader = function(options) {
+ if (typeof options === 'function') {
+ options = {
+ callback: options,
+ };
+ }
+ options = $.extend({}, defaults, options);
+ const stopFn = function(event) {
+ event.stopPropagation();
+ return event.preventDefault();
+ };
+ return this.each(function() {
+ const element = this;
+ $(element).bind('dragenter dragover dragleave', stopFn);
+ return $(element).bind('drop', function(event) {
+ stopFn(event);
+ const files = event.dataTransfer.files;
+ for(let i=0; i<files.length; i++) {
+ const f = files[i];
+ if(f.type.match(options.matchType)) {
+ const reader = new FileReader();
+ reader.onload = function(evt) {
+ return options.callback.call(element, {
+ dataURL: evt.target.result,
+ event: evt,
+ file: f,
+ name: f.name,
+ });
+ };
+ reader.readAsDataURL(f);
+ return;
+ }
+ }
+ });
+ });
+ };
+})(jQuery);
diff --git a/client/lib/pasteImage.js b/client/lib/pasteImage.js
new file mode 100644
index 00000000..264d77ac
--- /dev/null
+++ b/client/lib/pasteImage.js
@@ -0,0 +1,57 @@
+/* eslint-disable */
+
+// ------------------------------------------------------------------------
+// Created by STRd6
+// MIT License
+// https://github.com/distri/jquery-image_reader/blob/master/paste.coffee.md
+//
+// Raymond re-write it to javascript
+
+(function($) {
+ $.event.fix = (function(originalFix) {
+ return function(event) {
+ event = originalFix.apply(this, arguments);
+ if (event.type.indexOf('copy') === 0 || event.type.indexOf('paste') === 0) {
+ event.clipboardData = event.originalEvent.clipboardData;
+ }
+ return event;
+ };
+ })($.event.fix);
+
+ const defaults = {
+ callback: $.noop,
+ matchType: /image.*/,
+ };
+
+ return $.fn.pasteImageReader = function(options) {
+ if (typeof options === 'function') {
+ options = {
+ callback: options,
+ };
+ }
+ options = $.extend({}, defaults, options);
+ return this.each(function() {
+ const element = this;
+ return $(element).bind('paste', function(event) {
+ const types = event.clipboardData.types;
+ const items = event.clipboardData.items;
+ for(let i=0; i<types.length; i++) {
+ if(types[i].match(options.matchType) || items[i].type.match(options.matchType)) {
+ const f = items[i].getAsFile();
+ const reader = new FileReader();
+ reader.onload = function(evt) {
+ return options.callback.call(element, {
+ dataURL: evt.target.result,
+ event: evt,
+ file: f,
+ name: f.name,
+ });
+ };
+ reader.readAsDataURL(f);
+ return;
+ }
+ }
+ });
+ });
+ };
+})(jQuery);
diff --git a/config/accounts.js b/config/accounts.js
index d475e6b2..b1a4a177 100644
--- a/config/accounts.js
+++ b/config/accounts.js
@@ -46,3 +46,9 @@ AccountsTemplates.configureRoute('changePwd', {
Popup.back();
},
});
+
+if (Meteor.isServer) {
+ if (process.env.MAIL_FROM) {
+ Accounts.emailTemplates.from = process.env.MAIL_FROM;
+ }
+}
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index e6fd4ccc..4a6edfe9 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -87,6 +87,7 @@
"changePermissionsPopup-title": "Change Permissions",
"click-to-star": "Click to star this board.",
"click-to-unstar": "Click to unstar this board.",
+ "clipboard" : "Clipboard or drag & drop",
"close": "Close",
"close-board": "Close Board",
"close-board-pop": "You can re-open the board by clicking the “Boards” menu from the header, selecting “View Closed Boards”, finding the board and clicking “Re-open”.",
@@ -189,6 +190,10 @@
"page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.",
"page-not-found": "Page not found.",
"password": "Password",
+ "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)",
+ "preview": "Preview",
+ "previewClipboardImagePopup-title": "Preview",
+ "previewAttachedImagePopup-title": "Preview",
"private": "Private",
"private-desc": "This board is private. Only people added to the board can view and edit it.",
"profile": "Profile",
@@ -228,6 +233,7 @@
"title": "Title",
"unassign-member": "Unassign member",
"unsaved-description": "You have an unsaved description.",
+ "upload": "Upload",
"upload-avatar": "Upload an avatar",
"uploaded-avatar": "Uploaded an avatar",
"username": "Username",
diff --git a/sandstorm.js b/sandstorm.js
index 997aed46..a711a960 100644
--- a/sandstorm.js
+++ b/sandstorm.js
@@ -54,10 +54,10 @@ if (isSandstorm && Meteor.isServer) {
// XXX If this routing scheme changes, this will break. We should generate
// the location URL using the router, but at the time of writing, the
// it is only accessible on the client.
- const path = `/boards/${sandstormBoard._id}/${sandstormBoard.slug}`;
+ const boardPath = `/b/${sandstormBoard._id}/${sandstormBoard.slug}`;
res.writeHead(301, {
- Location: base + path,
+ Location: base + boardPath,
});
res.end();
@@ -126,6 +126,22 @@ if (isSandstorm && Meteor.isServer) {
}
if (isSandstorm && Meteor.isClient) {
+ // Since the Sandstorm grain is displayed in an iframe of the Sandstorm shell,
+ // we need to explicitly expose meta data like the page title or the URL path
+ // so that they could appear in the browser window.
+ // See https://docs.sandstorm.io/en/latest/developing/path/
+ function updateSandstormMetaData(msg) {
+ return window.parent.postMessage(msg, '*');
+ }
+
+ FlowRouter.triggers.enter([({ path }) => {
+ updateSandstormMetaData({ setPath: path });
+ }]);
+
+ Tracker.autorun(() => {
+ updateSandstormMetaData({ setTitle: DocHead.getTitle() });
+ });
+
// XXX Hack. `Meteor.absoluteUrl` doesn't work in Sandstorm, since every
// session has a different URL whereas Meteor computes absoluteUrl based on
// the ROOT_URL environment variable. So we overwrite this function on a