From 8030aca3fd763de4be758cfc08a1872289ce653c Mon Sep 17 00:00:00 2001 From: cobbspur Date: Mon, 5 Aug 2013 18:31:29 +0100 Subject: [PATCH] Added image upload reusable plugin issue #40 and issue #280 - Adds uploader jquery plugin - includes settings for enabling/disabling upload progress bar - adds routing for image uploads - adds directories by year and month based on upload date - Implements plugin on settings - general pane - Implements plugin on editor - adjusted general tab to save uploaded image src TODO: - Add error handling - Storing information on editor - Add events --- ghost/admin/assets/img/AddImage.png | Bin 0 -> 367 bytes ghost/admin/assets/img/returnImage.png | Bin 0 -> 1879 bytes ghost/admin/assets/lib/uploader.js | 177 ++++++++++++++++++ ghost/admin/assets/sass/layouts/editor.scss | 15 +- ghost/admin/assets/sass/modules/global.scss | 69 ++++++- .../vendor/showdown/extensions/ghostdown.js | 1 + ghost/admin/tpl/settings/general.hbs | 10 +- ghost/admin/views/editor.js | 1 + ghost/admin/views/settings.js | 11 +- 9 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 ghost/admin/assets/img/AddImage.png create mode 100644 ghost/admin/assets/img/returnImage.png create mode 100644 ghost/admin/assets/lib/uploader.js diff --git a/ghost/admin/assets/img/AddImage.png b/ghost/admin/assets/img/AddImage.png new file mode 100644 index 0000000000000000000000000000000000000000..8bcf8b33dc0a8effc2f020702f6ac453d90cef55 GIT binary patch literal 367 zcmV-#0g(QQP)Sz3M;KbI5^UXfnaM|A4I{biETswz-~poJ)kXjzsU&GVdQSq6nK#eo5qF_`C>(OgU4_r&u&=BH^Y#eoqs z7{`(6S^$_A;Y)FTR@Vt_+inuaMSKl?Ghfp*Vp$e2TLzeJ*LCFS`9%~M=Bs`1j^Zx+ zcqgW50wcG8ssQbaU<;KQK+;np0FOtX*{`#>!)_q=_Zb+5A;4@Nt0AIqE}ZRu{Du8J zP!xrHzP)nVyL-SkF$jVp3_}{n@lA?cV1VUs;eg$4n&wZB39-AM^A8L^L#9E&waEYg N002ovPDHLkV1ggUp1lA7 literal 0 HcmV?d00001 diff --git a/ghost/admin/assets/img/returnImage.png b/ghost/admin/assets/img/returnImage.png new file mode 100644 index 0000000000000000000000000000000000000000..a59e8198d5849b9aa77e6fd06ba32504bfe84fe3 GIT binary patch literal 1879 zcmbVNX;2eq7!HUOX{l&Ig|V<*5iOQvcN3EACQ&!pP-2OpfeK-OF(iwG92+-_gu{w) zO4Zg%QLNR{3fdX5YUuz&!PbMyC6$U$id7Kmfmcxki;l;1qhR~P>5uNr?svV<^Ss}C z%x;QX{R^M7$3nP2$*<4 zR*g1hU|J%2LW^C=z)Xg*V?16?P7XJR&!y;9JQzh$HwS`5vKEn!TpOczM%o*=#ASnX={b7GrgB2a9|cuu{Y2Xi5w)#|?2Q)>sKCI4~b zh1QOQTsz6rk`5}HHn91~m^TS#WB1=eZbQ}^OhcR4r06XKWyrRYHbzCrK=y=dG#N2S z1PjG{0WK60N+krtC=S6&L=-I`#6nz%$R{~oge8O`6j3VWFb)e~SPA2Du|g>j@KFTi z%cI4}Bvxf}FnXJToYXb3x)WIZKe3pCCiM(OCs34SvI62VDTZ=nQg%QQ9|a_uY(^@_ z;ZDx8yAmXAT0oaxcYj{A* zg0`#G<@*j@J>w%Q-C6TyO{aHhp0+AP8X%YTW);^=4fN8a5i|bm{AeX_S=037k1F3A zyQ0`;~SEoeG=DZkxv?%*5eyn^$u9XDj;WUN^?x^A?;W7*~=@WDmp zvcGV&P+aIgYg5U)f=6FhS{g3zn$h3eUQx1W{9DP+WN(dHT~-_u_Hyw+nkF{(g5`em zKF(Jb;dy;!Lf(&qohLlT?%liYxt`Ns5*6T@Qk8tDA#Nc-*QXdpy+T(y91aa9LhcjT zIyCBaSZDSj=B31yz#j}vTYS8yXN8Wl@m0^^X^j^yLOs*(NxMT^nw!D&l2QoYvGk5d z@W(WbAlPr@;p6=sk4HvEBo#pTf(2yN zfh$j*yl)T$lk=~JoKm`~Qb#1mp?!PzZWt&^)9LCxHcMxHeezoCHPyCr0t)$jOd^p~ ztk~lHWRD+~82 ziwTQ~D2-TcfaZi{RO_ak&aY4eEDU;B)t51RgDwZ}pULw*uvc5TCNMcQ^|Wh|-<+d? zrF&kluRm9>JPM{11qA=qln(dUXE?Wf>RP*hW>Mq;m#$|e)LOn4AItT`3)gfmjx8EE zJC(liF0IEa`!4ov=(S0@d%C*2nYqI8px$Ea(2Dj3@1gSGGavrc;gSWe-CybL`lheH zKm6OYA0EY0YdzxSU!F;+zwv$V*|YSK>gsC0Kzz7wbx=v>A~j{!HwSS6i@Y zvo@fbgDnR`JIX=}G6cbz@5=n|?rHtB;gg)wr4c`?8o&6eGp76f>vvl}Zc0bDI5;Ws z<3ZJPH$uC2JgAvHAeotb_pQ_o*Y5iQ*!K&DwqB}RZAg^qM)o$}yjRpdKU}XGLYl^J z&ng&kKH?6CZY&9{SaM~%|3%k2A|fj*>%j2v@R#e?uYaj{&xNk8uDaqGvlZ&-p;Olj zf*MW?+NGOY{&@Lt?r(En-Add image' + + '' + + '' + + '', + $progress = $('
', { + "class" : "js-upload-progress progress progress-success active", + "style": "opacity:0", + "role": "progressbar", + "aria-valuemin": "0", + "aria-valuemax": "100" + }).append($("
", { + "class": "js-upload-progress-bar bar", + "style": "width:0%" + })); + + UploadUi = function ($dropzone, settings) { + var source, + $link = $('' + + 'add, edit'), + $back = $('' + + 'add, edit'); + + $.extend(this, { + bindFileUpload: function () { + var self = this; + + $dropzone.find('.js-fileupload').fileupload().fileupload("option", { + url: '/ghost/upload', + add: function (e, data) { + $dropzone.find('a.js-return-image').remove(); + $dropzone.find('span.media, div.description, a.image-url, a.image-webcam') + .animate({opacity: 0}, 250, function () { + if (settings.progressbar) { + $dropzone.find('span.media').after($progress); + $progress.animate({opacity: 100}, 250); + } + data.submit(); + }); + }, + dropZone: $dropzone, + progressall: function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10); + if (!settings.editor) {$progress.find('div.js-progress').css({"position": "absolute", "top": "40px"}); } + if (settings.progressbar) { + $progress.find('.js-upload-progress-bar').css('width', progress + '%'); + if (data.loaded / data.total === 1) { + $progress.animate({opacity: 0}, 250, function () { + $dropzone.find('span.media').after(''); + if (!settings.editor) {$progress.find('.fileupload-loading').css({"top": "56px"}); } + }); + } + } + + }, + done: function (e, data) { + function showImage(width, height) { + $dropzone.find('img.js-upload-target').attr({"width": width, "height": height}).css({"display": "block"}); + $dropzone.find('.fileupload-loading').removeClass('fileupload-loading'); + $dropzone.css({"height": "auto"}); + if (!$dropzone.find('a.js-edit-image')[0]) { + $link.css({"opacity": 100}); + $dropzone.find('.js-upload-target').after($link); + } + $dropzone.delay(250).animate({opacity: 100}, 1000, function () { + self.init(); + }); + } + + function animateDropzone($img) { + $dropzone.animate({opacity: 0}, 250, function () { + $dropzone.removeClass('image-uploader').addClass('pre-image-uploader'); + $dropzone.css({minHeight: 0}); + self.removeExtras(); + $dropzone.animate({height: $img.height()}, 250, function () { + showImage($img.width(), $img.height()); + }); + }); + } + + function preloadImage() { + var $img = $dropzone.find('img.js-upload-target') + .attr({'src': '', "width": 'auto', "height": 'auto'}); + $img.one('load', function () { animateDropzone($img); }) + .attr('src', data.result); + } + preloadImage(); + } + }); + }, + + removeExtras: function () { + $dropzone.find('div.description, span.media, div.js-upload-progress, a.image-url, a.image-webcam') + .remove(); + }, + + initWithDropzone: function () { + var self = this; + //This is the start point if no image exists + $dropzone.find('img.js-upload-target').css({"display": "none"}); + $dropzone.removeClass('pre-image-uploader').addClass('image-uploader'); + if (!$dropzone.find('span.media')[0]) { + $dropzone.append($loader); + } + if ($dropzone.find('a.js-edit-image')[0]) { + $dropzone.find('a.js-edit-image').remove(); + } + + $back.on('click', function () { + $dropzone.find('a.js-return-image').remove(); + $dropzone.find('img.js-upload-target').attr({"src": source}).css({"display": "block"}); + self.removeExtras(); + $dropzone.removeClass('image-uploader').addClass('pre-image-uploader'); + self.init(); + }); + this.bindFileUpload(); + }, + + initWithImage: function () { + var self = this; + // This is the start point if an image already exists + source = $dropzone.find('img.js-upload-target').attr('src'); + $dropzone.removeClass('image-uploader').addClass('pre-image-uploader'); + + if (!$dropzone.find('a.js-edit-image')[0]) { + $link.css({"opacity": 100}); + $dropzone.find('.js-upload-target').after($link); + } + + $link.on('click', function () { + $dropzone.find('a.js-edit-image').remove(); + $dropzone.find('img.js-upload-target').attr({"src": ""}).css({"display": "none"}); + $back.css({"cursor": "pointer", "z-index": 9999, "opacity": 100}); + $dropzone.find('.js-upload-target').after($back); + self.init(); + }); + }, + + init: function () { + var img; + // First check if field image is defined by checking for js-upload-target class + if ($dropzone.find('img.js-upload-target')[0]) { + if ($dropzone.find('img.js-upload-target').attr('src') === '') { + this.initWithDropzone(); + } else { + this.initWithImage(); + } + } else { + // This ensures there is an image we can hook into to display uploaded image + $dropzone.prepend(''); + this.init(); + } + } + }); + }; + + + $.fn.upload = function (options) { + var settings = $.extend({ + progressbar: true, + editor: false + }, options); + + return this.each(function () { + var $dropzone = $(this), + ui; + + ui = new UploadUi($dropzone, settings); + ui.init(); + }); + }; +}(jQuery)); \ No newline at end of file diff --git a/ghost/admin/assets/sass/layouts/editor.scss b/ghost/admin/assets/sass/layouts/editor.scss index 6e5732e24c..042f4eb5b8 100644 --- a/ghost/admin/assets/sass/layouts/editor.scss +++ b/ghost/admin/assets/sass/layouts/editor.scss @@ -117,7 +117,7 @@ padding: 5px; &:hover { - @include icon($i-question, '', $brown) + @include icon($i-question, '', $brown); } } @@ -255,7 +255,6 @@ @include breakpoint($netbook) {padding-top: 20px;} @include breakpoint($mobile) {padding: 15px;} } - } // Special case, when scrolling, add shadows to content headers. @@ -331,11 +330,21 @@ text-align: center; } } - + a { + &.image-edit { + width: 16px; + height: 16px; + } + } img { width: 100%; height: auto; } + // prevent uploaded image from being streched in editor + .pre-image-uploader img { + width: auto; + } + } diff --git a/ghost/admin/assets/sass/modules/global.scss b/ghost/admin/assets/sass/modules/global.scss index b196eac15b..02be82aeee 100644 --- a/ghost/admin/assets/sass/modules/global.scss +++ b/ghost/admin/assets/sass/modules/global.scss @@ -1135,6 +1135,16 @@ main { color: $brown; text-decoration: none; } + .image-edit { + line-height: 12px; + padding: 10px; + display: block; + position: absolute; + top: 0; + left: 0; + opacity: 0; + text-decoration: none; + } .image-webcam { @include icon($i-camera, 12px); @@ -1146,9 +1156,10 @@ main { right: 0; color: $brown; text-decoration: none; + } - .fileupload { + input { position: absolute; right: 0; margin: 0; @@ -1162,7 +1173,7 @@ main { .progress { position: relative; - top: -39px; + top: -22px; margin: auto; margin-bottom: -12px; display: block; @@ -1186,7 +1197,61 @@ main { background: $blue; } } +.pre-image-uploader { + @include box-sizing(border-box); + @include baseline; + position: relative; + overflow:hidden; + height: auto; + color: $brown; + input { + position: absolute; + left: 9999px; + opacity: 0; + } + .image-edit { + + line-height: 12px; + padding: 10px; + display: block; + position: absolute; + top: 0; + left: 0; + opacity: 0; + text-decoration: none; + } +} + + + + + +//.progress { +// position: relative; +// top: -39px; +// margin: auto; +// margin-bottom: -12px; +// display: block; +// overflow: hidden; +// @include linear-gradient(to bottom, #f5f5f5, #f9f9f9); +// border-radius: 12px; +// box-shadow: (rgba(0,0,0,0.1) 0 1px 2px inset); +//} +// +//.fileupload-loading { +// display: block; +// top: 50%; +// width: 35px; +// height: 28px; +// margin: -28px auto 0; +// background-size: contain; +//} +// +//.bar { +// height: 12px; +// background: $blue; +//} /* ========================================================================== Misc diff --git a/ghost/admin/assets/vendor/showdown/extensions/ghostdown.js b/ghost/admin/assets/vendor/showdown/extensions/ghostdown.js index 6d8862b3ed..984c044974 100644 --- a/ghost/admin/assets/vendor/showdown/extensions/ghostdown.js +++ b/ghost/admin/assets/vendor/showdown/extensions/ghostdown.js @@ -9,6 +9,7 @@ return '
' + '' + '
Add image of ' + alt + '
' + + '' + '' + '' + '' + diff --git a/ghost/admin/tpl/settings/general.hbs b/ghost/admin/tpl/settings/general.hbs index 0a89daf1bb..e9166ccf71 100644 --- a/ghost/admin/tpl/settings/general.hbs +++ b/ghost/admin/tpl/settings/general.hbs @@ -15,13 +15,19 @@
- logo +
+ + +

Display a logo on your site in place of blog title

- logo +
+ + +

The icon for your blog, used in your browser tab and elsewhere

diff --git a/ghost/admin/views/editor.js b/ghost/admin/views/editor.js index 3aa6f64594..f93d2f1832 100644 --- a/ghost/admin/views/editor.js +++ b/ghost/admin/views/editor.js @@ -321,6 +321,7 @@ var view = this, preview = document.getElementsByClassName('rendered-markdown')[0]; preview.innerHTML = this.converter.makeHtml(this.editor.getValue()); + view.$('.js-drop-zone').upload({editor: true}); Countable.once(preview, function (counter) { view.$('.entry-word-count').text(counter.words + ' words'); view.$('.entry-character-count').text(counter.characters + ' characters'); diff --git a/ghost/admin/views/settings.js b/ghost/admin/views/settings.js index 33e68288bc..fe8281306b 100644 --- a/ghost/admin/views/settings.js +++ b/ghost/admin/views/settings.js @@ -128,7 +128,10 @@ saveSettings: function () { this.model.save({ title: this.$('#blog-title').val(), - email: this.$('#email-address').val() + email: this.$('#email-address').val(), + logo: this.$('#logo').attr("src"), + icon: this.$('#icon').attr("src") + }, { success: this.saveSuccess, error: this.saveError @@ -141,6 +144,12 @@ var settings = this.model.toJSON(); this.$('#blog-title').val(settings.title); this.$('#email-address').val(settings.email); + }, + + afterRender: function () { + this.$el.attr('id', this.id); + this.$el.addClass('active'); + this.$('.js-drop-zone').upload(); } });