Creates Ember Modal infastructure

resolves #2416

This is a pretty large commit but what it's adding are pretty fundamental to the admin app.

- Creates top level actions on the ApplicationRoute for opening and closing modals.  This allows sending the 'openModal' action from any template to open a modal.

- Every modal template lives in 'templates/modals/{{modalName}}'

- Each modal can have a backing controller of the same name that can provide additional control for that modal.  Those controllers reside in 'controllers/modals/{{modalName}}'

- Created the ModalDialog component which is where all the logic for the component resides.  It's not at 100% parity with the existing Ghost modal system but it has the foundation for further fleshing out.  It currently accepts parameters for styling how the modal should appear, which previously was defined in JS files in the Backbone admin.

- This creates the 'delete all posts', 'delete this post', 'markdown', and 'upload' modal.  Some are in more stages of completion than others, but I wanted to just get the foundation in place as fast as possible.

- This also creates the UploadModal component which is a subclass of the ModalDialog component.  The reason for this subclassing is that the UploadModal component directly accesses the DOM and when that occurs in Ember it should remain in a component definition.  It's ready for extending to reach parity.  Note: depending on needs the base ModalDialog class may need to be modified.
This commit is contained in:
Harry Wolff 2014-03-31 00:07:05 -04:00
parent 4dbaf58aba
commit 00da1a43b7
17 changed files with 351 additions and 15 deletions

View File

@ -0,0 +1,59 @@
var ModalDialog = Ember.Component.extend({
didInsertElement: function () {
this.$('#modal-container').fadeIn(50);
this.$('.modal-background').show().fadeIn(10, function () {
$(this).addClass('in');
});
this.$('.js-modal').addClass('in');
},
willDestroyElement: function () {
this.$('.js-modal').removeClass('in');
this.$('.modal-background').removeClass('in');
return this._super();
},
actions: {
closeModal: function () {
this.sendAction();
},
confirm: function (type) {
var func = this.get('confirm.' + type + '.func');
if (typeof func === 'function') {
func();
}
this.sendAction();
}
},
klass: function () {
var classNames = [];
classNames.push(this.get('type') ? 'modal-' + this.get('type') : 'modal');
if (this.get('style')) {
this.get('style').split(',').forEach(function (style) {
classNames.push('modal-style-' + style);
});
}
classNames.push(this.get('animation'));
return classNames.join(' ');
}.property('type', 'style', 'animation'),
acceptButtonClass: function () {
return this.get('confirm.accept.buttonClass') ? this.get('confirm.accept.buttonClass') : 'button-add';
}.property('confirm.accept.buttonClass'),
rejectButtonClass: function () {
return this.get('confirm.reject.buttonClass') ? this.get('confirm.reject.buttonClass') : 'button-delete';
}.property('confirm.reject.buttonClass')
});
export default ModalDialog;

View File

@ -0,0 +1,32 @@
/*global console */
import ModalDialog from 'ghost/components/modal-dialog';
var UploadModal = ModalDialog.extend({
layoutName: 'components/modal-dialog',
didInsertElement: function () {
this._super();
// @TODO: get this real
console.log('UploadController:afterRender');
// var filestorage = $('#' + this.options.model.id).data('filestorage');
// this.$('.js-drop-zone').upload({fileStorage: filestorage});
},
actions: {
closeModal: function () {
this.sendAction();
},
confirm: function (type) {
var func = this.get('confirm.' + type + '.func');
if (typeof func === 'function') {
func();
}
this.sendAction();
}
},
});
export default UploadModal;

View File

@ -0,0 +1,55 @@
/*global alert */
var DeleteAllController = Ember.Controller.extend({
confirm: {
accept: {
func: function () {
// @TODO make the below real :)
alert('Deleting everything!');
// $.ajax({
// url: Ghost.paths.apiRoot + '/db/',
// type: 'DELETE',
// headers: {
// 'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
// },
// success: function onSuccess(response) {
// if (!response) {
// throw new Error('No response received from server.');
// }
// if (!response.message) {
// throw new Error(response.detail || 'Unknown error');
// }
// Ghost.notifications.addItem({
// type: 'success',
// message: response.message,
// status: 'passive'
// });
// },
// error: function onError(response) {
// var responseText = JSON.parse(response.responseText),
// message = responseText && responseText.error ? responseText.error : 'unknown';
// Ghost.notifications.addItem({
// type: 'error',
// message: ['A problem was encountered while deleting content from your blog. Error: ', message].join(''),
// status: 'passive'
// });
// }
// });
},
text: "Delete",
buttonClass: "button-delete"
},
reject: {
func: function () {
return true;
},
text: "Cancel",
buttonClass: "button"
}
}
});
export default DeleteAllController;

View File

@ -0,0 +1,42 @@
/*global alert */
var DeletePostController = Ember.Controller.extend({
confirm: {
accept: {
func: function () {
// @TODO: make this real
alert('Deleting post');
// self.model.destroy({
// wait: true
// }).then(function () {
// // Redirect to content screen if deleting post from editor.
// if (window.location.pathname.indexOf('editor') > -1) {
// window.location = Ghost.paths.subdir + '/ghost/content/';
// }
// Ghost.notifications.addItem({
// type: 'success',
// message: 'Your post has been deleted.',
// status: 'passive'
// });
// }, function () {
// Ghost.notifications.addItem({
// type: 'error',
// message: 'Your post could not be deleted. Please try again.',
// status: 'passive'
// });
// });
},
text: "Delete",
buttonClass: "button-delete"
},
reject: {
func: function () {
return true;
},
text: "Cancel",
buttonClass: "button"
}
},
});
export default DeletePostController;

View File

@ -0,0 +1,14 @@
var UploadController = Ember.Controller.extend({
confirm: {
reject: {
func: function () { // The function called on rejection
return true;
},
buttonClass: true,
text: "Cancel" // The reject button text
}
}
});
export default UploadController;

View File

@ -16,14 +16,6 @@ var SettingsUserController = Ember.Controller.extend({
}.property('user.image'), }.property('user.image'),
actions: { actions: {
cover: function () {
alert('@TODO: Show Upload modal for cover');
},
image: function () {
alert('@TODO: Show Upload modal for image');
},
save: function () { save: function () {
alert('@TODO: Saving user...'); alert('@TODO: Saving user...');

View File

@ -0,0 +1,25 @@
var ApplicationRoute = Ember.Route.extend({
actions: {
openModal: function (modalName, model) {
modalName = 'modals/' + modalName;
// We don't always require a modal to have a controller
// so we're skipping asserting if one exists
if (this.controllerFor(modalName, true)) {
this.controllerFor(modalName).set('model', model);
}
return this.render(modalName, {
into: 'application',
outlet: 'modal'
});
},
closeModal: function () {
return this.disconnectOutlet({
outlet: 'modal',
parentView: 'application'
});
}
}
});
export default ApplicationRoute;

View File

@ -5,3 +5,5 @@
<main role="main" id="main"> <main role="main" id="main">
{{outlet}} {{outlet}}
</main> </main>
{{outlet modal}}

View File

@ -0,0 +1,22 @@
<div id="modal-container" {{action bubbles=false preventDefault=false}}>
<article {{bind-attr class="klass :js-modal"}}>
<section class="modal-content">
{{#if title}}<header class="modal-header"><h1>{{title}}</h1></header>{{/if}}
{{#if showClose}}<a class="close" href="" title="Close" {{action "closeModal"}}><span class="hidden">Close</span></a>{{/if}}
<section class="modal-body">
{{yield}}
</section>
{{#if confirm}}
<footer class="modal-footer">
<button {{bind-attr class="acceptButtonClass :js-button-accept"}} {{action "confirm" "accept"}}>
{{confirm.accept.text}}
</button>
<button {{bind-attr class="rejectButtonClass :js-button-reject"}} {{action "confirm" "reject"}}>
{{confirm.reject.text}}
</button>
</footer>
{{/if}}
</section>
</article>
</div>
<div class="modal-background fade" {{action "closeModal"}}></div>

View File

@ -8,7 +8,7 @@
<section class="entry-markdown active"> <section class="entry-markdown active">
<header class="floatingheader"> <header class="floatingheader">
<small>Markdown</small> <small>Markdown</small>
<a class="markdown-help" href="#"><span class="hidden">What is Markdown?</span></a> <a class="markdown-help" href="" {{action "openModal" "markdown"}}><span class="hidden">What is Markdown?</span></a>
</header> </header>
<section id="entry-markdown-content" class="entry-markdown-content"> <section id="entry-markdown-content" class="entry-markdown-content">
{{-codemirror value=markdown scrollPosition=view.scrollPosition}} {{-codemirror value=markdown scrollPosition=view.scrollPosition}}

View File

@ -0,0 +1,6 @@
{{#modal-dialog action="closeModal" type="action" style="wide,centered" animation="fade"
title="Would you really like to delete all content from your blog?" confirm=confirm}}
<p>This is permanent! No backups, no restores, no magic undo button. <br /> We warned you, ok?</p>
{{/modal-dialog}}

View File

@ -0,0 +1,6 @@
{{#modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
title="Are you sure you want to delete this post?" confirm=confirm}}
<p>This is permanent! No backups, no restores, no magic undo button. <br /> We warned you, ok?</p>
{{/modal-dialog}}

View File

@ -0,0 +1,72 @@
{{#modal-dialog action="closeModal" showClose=true style="wide" animation="fade"
title="Markdown Help"}}
<section class="markdown-help-container">
<table class="modal-markdown-help-table">
<thead>
<tr>
<th>Result</th>
<th>Markdown</th>
<th>Shortcut</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Bold</strong></td>
<td>**text**</td>
<td>Ctrl / Cmd + B</td>
</tr>
<tr>
<td><em>Emphasize</em></td>
<td>*text*</td>
<td>Ctrl / Cmd + I</td>
</tr>
<tr>
<td>Strike-through</td>
<td>~~text~~</td>
<td>Ctrl + Alt + U</td>
</tr>
<tr>
<td><a href="#">Link</a></td>
<td>[title](http://)</td>
<td>Ctrl + Shift + L</td>
</tr>
<tr>
<td>Image</td>
<td>![alt](http://)</td>
<td>Ctrl + Shift + I</td>
</tr>
<tr>
<td>List</td>
<td>* item</td>
<td>Ctrl + L</td>
</tr>
<tr>
<td>Blockquote</td>
<td>> quote</td>
<td>Ctrl + Q</td>
</tr>
<tr>
<td>H1</td>
<td># Heading</td>
<td>Ctrl + Alt + 1</td>
</tr>
<tr>
<td>H2</td>
<td>## Heading</td>
<td>Ctrl + Alt + 2</td>
</tr>
<tr>
<td>H3</td>
<td>### Heading</td>
<td>Ctrl + Alt + 3</td>
</tr>
<tr>
<td><code>Inline Code</code></td>
<td>`code`</td>
<td>Cmd + K / Ctrl + Shift + K</td>
</tr>
</tbody>
</table>
For further Markdown syntax reference: <a href="http://daringfireball.net/projects/markdown/syntax" target="_blank">Markdown Documentation</a>
</section>
{{/modal-dialog}}

View File

@ -0,0 +1,9 @@
{{#upload-modal action="closeModal" close=true type="action" style="wide"
animation="fade"}}
<section class="js-drop-zone">
<img class="js-upload-target" {{bind-attr src=src}} alt="logo">
<input data-url="upload" class="js-fileupload main" type="file" name="uploadimage" {{#if options.acceptEncoding}}accept="{{options.acceptEncoding}}"{{/if}}>
</section>
{{/upload-modal}}

View File

@ -26,7 +26,7 @@
<fieldset> <fieldset>
<div class="form-group"> <div class="form-group">
<label>Delete all Content</label> <label>Delete all Content</label>
<a href="javascript:void(0);" class="button-delete js-delete">Delete</a> <a href="javascript:void(0);" class="button-delete js-delete" {{action "openModal" "deleteAll"}}>Delete</a>
<p>Delete all posts and tags from the database.</p> <p>Delete all posts and tags from the database.</p>
</div> </div>
</fieldset> </fieldset>

View File

@ -31,7 +31,7 @@
{{#if logo}} {{#if logo}}
<a class="js-modal-logo" href="#"><img id="blog-logo" src="{{logo}}" alt="logo"></a> <a class="js-modal-logo" href="#"><img id="blog-logo" src="{{logo}}" alt="logo"></a>
{{else}} {{else}}
<a class="button-add js-modal-logo" >Upload Image</a> <a class="button-add js-modal-logo" {{action 'openModal' 'upload'}}>Upload Image</a>
{{/if}} {{/if}}
<p>Display a sexy logo for your publication</p> <p>Display a sexy logo for your publication</p>
</div> </div>
@ -41,7 +41,7 @@
{{#if cover}} {{#if cover}}
<a class="js-modal-cover" href="#"><img id="blog-cover" src="{{cover}}" alt="cover photo"></a> <a class="js-modal-cover" href="#"><img id="blog-cover" src="{{cover}}" alt="cover photo"></a>
{{else}} {{else}}
<a class="button-add js-modal-cover">Upload Image</a> <a class="button-add js-modal-cover" {{action 'openModal' 'upload'}}>Upload Image</a>
{{/if}} {{/if}}
<p>Display a cover image on your site</p> <p>Display a cover image on your site</p>
</div> </div>

View File

@ -11,7 +11,7 @@
<header class="user-profile-header"> <header class="user-profile-header">
<img id="user-cover" class="cover-image" {{bind-attr src=cover title=coverTitle}} /> <img id="user-cover" class="cover-image" {{bind-attr src=cover title=coverTitle}} />
<a class="edit-cover-image js-modal-cover button" {{action 'cover'}}>Change Cover</a> <a class="edit-cover-image js-modal-cover button" {{action 'openModal' 'upload'}}>Change Cover</a>
</header> </header>
<form class="user-profile" novalidate="novalidate"> <form class="user-profile" novalidate="novalidate">
@ -20,7 +20,7 @@
<figure class="user-image"> <figure class="user-image">
<div id="user-image" class="img" {{bind-attr style=image}} href="#"><span class="hidden">{{name}}'s Picture</span></div> <div id="user-image" class="img" {{bind-attr style=image}} href="#"><span class="hidden">{{name}}'s Picture</span></div>
<a {{action 'image'}} class="edit-user-image js-modal-image">Edit Picture</a> <a href="" {{action 'openModal' 'upload'}} class="edit-user-image js-modal-image">Edit Picture</a>
</figure> </figure>
<div class="form-group"> <div class="form-group">