mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-01 15:29:19 +03:00
Merge pull request #2618 from novaugust/post-settings
[Ember.js] Create Post Settings Menu and its functionality on the Post controller.
This commit is contained in:
commit
843a8fd51f
@ -3,10 +3,6 @@
|
||||
The contents should be solved properly or moved into ghost-ui package.
|
||||
*/
|
||||
|
||||
.post-settings-menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#entry-markdown,
|
||||
.entry-preview,
|
||||
.CodeMirror.cm-s-default {
|
||||
@ -75,4 +71,4 @@
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
core/client/components/blur-text-field.js
Normal file
13
core/client/components/blur-text-field.js
Normal file
@ -0,0 +1,13 @@
|
||||
var BlurTextField = Ember.TextField.extend({
|
||||
selectOnClick: false,
|
||||
click: function (event) {
|
||||
if (this.get('selectOnClick')) {
|
||||
event.currentTarget.select();
|
||||
}
|
||||
},
|
||||
focusOut: function () {
|
||||
this.sendAction('action', this.get('value'));
|
||||
}
|
||||
});
|
||||
|
||||
export default BlurTextField;
|
@ -1,8 +1,176 @@
|
||||
import {parseDateString, formatDate} from 'ghost/utils/date-formatting';
|
||||
|
||||
var equal = Ember.computed.equal;
|
||||
|
||||
var PostController = Ember.ObjectController.extend({
|
||||
|
||||
isPublished: equal('status', 'published'),
|
||||
isDraft: equal('status', 'draft')
|
||||
isDraft: equal('status', 'draft'),
|
||||
isEditingSettings: false,
|
||||
isStaticPage: function (key, val) {
|
||||
if (arguments.length > 1) {
|
||||
this.set('model.page', val ? 1 : 0);
|
||||
this.get('model').save('page').then(function () {
|
||||
this.notifications.showSuccess('Succesfully converted ' + (val ? 'to static page' : 'to post'));
|
||||
}, this.notifications.showErrors);
|
||||
}
|
||||
return !!this.get('model.page');
|
||||
}.property('model.page'),
|
||||
|
||||
isOnServer: function () {
|
||||
return this.get('model.id') !== undefined;
|
||||
}.property('model.id'),
|
||||
|
||||
newSlugBinding: Ember.Binding.oneWay('model.slug'),
|
||||
slugPlaceholder: null,
|
||||
// Requests a new slug when the title was changed
|
||||
updateSlugPlaceholder: function () {
|
||||
var model,
|
||||
self = this,
|
||||
title = this.get('title');
|
||||
|
||||
// If there's a title present we want to
|
||||
// validate it against existing slugs in the db
|
||||
// and then update the placeholder value.
|
||||
if (title) {
|
||||
model = self.get('model');
|
||||
model.generateSlug().then(function (slug) {
|
||||
self.set('slugPlaceholder', slug);
|
||||
}, function () {
|
||||
self.notifications.showWarn('Unable to generate a slug for "' + title + '"');
|
||||
});
|
||||
} else {
|
||||
// If there's no title set placeholder to blank
|
||||
// and don't make an ajax request to server
|
||||
// for a proper slug (as there won't be any).
|
||||
self.set('slugPlaceholder', '');
|
||||
}
|
||||
}.observes('model.title'),
|
||||
|
||||
publishedAt: null,
|
||||
publishedAtChanged: function () {
|
||||
this.set('publishedAt', formatDate(this.get('model.published_at')));
|
||||
}.observes('model.published_at'),
|
||||
|
||||
actions: {
|
||||
editSettings: function () {
|
||||
this.toggleProperty('isEditingSettings');
|
||||
if (this.get('isEditingSettings')) {
|
||||
//Stop editing if the user clicks outside the settings view
|
||||
Ember.run.next(this, function () {
|
||||
var self = this;
|
||||
// @TODO has a race condition with click on the editSettings action
|
||||
$(document).one('click', function () {
|
||||
self.toggleProperty('isEditingSettings');
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
updateSlug: function () {
|
||||
var newSlug = this.get('newSlug'),
|
||||
slug = this.get('model.slug'),
|
||||
placeholder = this.get('slugPlaceholder'),
|
||||
self = this;
|
||||
|
||||
newSlug = (!newSlug && placeholder) ? placeholder : newSlug;
|
||||
|
||||
// Ignore unchanged slugs
|
||||
if (slug === newSlug) {
|
||||
return;
|
||||
}
|
||||
//reset to model's slug on empty string
|
||||
if (!newSlug) {
|
||||
this.set('newSlug', slug);
|
||||
return;
|
||||
}
|
||||
|
||||
//Validation complete
|
||||
this.set('model.slug', newSlug);
|
||||
|
||||
// If the model doesn't currently
|
||||
// exist on the server
|
||||
// then just update the model's value
|
||||
if (!this.get('isOnServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('model').save('slug').then(function () {
|
||||
self.notifications.showSuccess('Permalink successfully changed to <strong>' + this.get('model.slug') + '</strong>.');
|
||||
}, this.notifications.showErrors);
|
||||
},
|
||||
|
||||
updatePublishedAt: function (userInput) {
|
||||
var errMessage = '',
|
||||
newPubDate = formatDate(parseDateString(userInput)),
|
||||
pubDate = this.get('publishedAt'),
|
||||
newPubDateMoment,
|
||||
pubDateMoment;
|
||||
|
||||
// if there is no new pub date, mark that until the post is published,
|
||||
// when we'll fill in with the current time.
|
||||
if (!newPubDate) {
|
||||
this.set('publishedAt', '');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for missing time stamp on new data
|
||||
// If no time specified, add a 12:00
|
||||
if (newPubDate && !newPubDate.slice(-5).match(/\d+:\d\d/)) {
|
||||
newPubDate += " 12:00";
|
||||
}
|
||||
|
||||
newPubDateMoment = parseDateString(newPubDate);
|
||||
|
||||
// If there was a published date already set
|
||||
if (pubDate) {
|
||||
// Check for missing time stamp on current model
|
||||
// If no time specified, add a 12:00
|
||||
if (!pubDate.slice(-5).match(/\d+:\d\d/)) {
|
||||
pubDate += " 12:00";
|
||||
}
|
||||
|
||||
pubDateMoment = parseDateString(pubDate);
|
||||
|
||||
// Quit if the new date is the same
|
||||
if (pubDateMoment.isSame(newPubDateMoment)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate new Published date
|
||||
if (!newPubDateMoment.isValid() || newPubDate.substr(0, 12) === "Invalid date") {
|
||||
errMessage = 'Published Date must be a valid date with format: DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
|
||||
}
|
||||
|
||||
if (newPubDateMoment.diff(new Date(), 'h') > 0) {
|
||||
errMessage = 'Published Date cannot currently be in the future.';
|
||||
}
|
||||
|
||||
if (errMessage) {
|
||||
// Show error message
|
||||
this.notifications.showError(errMessage);
|
||||
//Hack to push a "change" when it's actually staying
|
||||
// the same.
|
||||
//This alerts the listener on post-settings-menu
|
||||
this.notifyPropertyChange('publishedAt');
|
||||
return;
|
||||
}
|
||||
|
||||
//Validation complete
|
||||
this.set('model.published_at', newPubDateMoment.toDate());
|
||||
|
||||
// If the model doesn't currently
|
||||
// exist on the server
|
||||
// then just update the model's value
|
||||
if (!this.get('isOnServer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('model').save('published_at').then(function () {
|
||||
this.notifications.showSuccess('Publish date successfully changed to <strong>' + this.get('publishedAt') + '</strong>.');
|
||||
}, this.notifications.showErrors);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default PostController;
|
||||
export default PostController;
|
||||
|
@ -39,6 +39,10 @@ var defineFixtures = function (status) {
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts', posts(status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/1', post(1, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/2', post(2, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/3', post(3, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/4', post(4, status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/posts/slug/test%20title/', response('generated-slug', status));
|
||||
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/signin', user(status));
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/users/me/', user(status));
|
||||
ic.ajax.defineFixture('/ghost/changepw/', response({
|
||||
@ -53,4 +57,4 @@ var defineFixtures = function (status) {
|
||||
ic.ajax.defineFixture('/ghost/api/v0.1/settings/?type=blog,theme,app', settings(status));
|
||||
};
|
||||
|
||||
export default defineFixtures;
|
||||
export default defineFixtures;
|
||||
|
@ -1,5 +1,56 @@
|
||||
var PostModel = Ember.Object.extend({
|
||||
import BaseModel from 'ghost/models/base';
|
||||
|
||||
var PostModel = BaseModel.extend({
|
||||
url: BaseModel.apiRoot + '/posts/',
|
||||
|
||||
generateSlug: function () {
|
||||
// @TODO Make this request use this.get('title') once we're an actual user
|
||||
var url = this.get('url') + 'slug/' + encodeURIComponent('test title') + '/';
|
||||
return ic.ajax.request(url, {
|
||||
type: 'GET'
|
||||
});
|
||||
},
|
||||
|
||||
save: function (properties) {
|
||||
var url = this.url,
|
||||
self = this,
|
||||
type,
|
||||
validationErrors = this.validate();
|
||||
|
||||
if (validationErrors.length) {
|
||||
return Ember.RSVP.Promise(function (resolve, reject) {
|
||||
return reject(validationErrors);
|
||||
});
|
||||
}
|
||||
|
||||
//If specific properties are being saved,
|
||||
//this is an edit. Otherwise, it's an add.
|
||||
if (properties && properties.length > 0) {
|
||||
type = 'PUT';
|
||||
url += this.get('id');
|
||||
} else {
|
||||
type = 'POST';
|
||||
properties = Ember.keys(this);
|
||||
}
|
||||
|
||||
return ic.ajax.request(url, {
|
||||
type: type,
|
||||
data: this.getProperties(properties)
|
||||
}).then(function (model) {
|
||||
return self.setProperties(model);
|
||||
});
|
||||
},
|
||||
validate: function () {
|
||||
var validationErrors = [];
|
||||
|
||||
if (!(this.get('title') && this.get('title').length)) {
|
||||
validationErrors.push({
|
||||
message: "You must specify a title for the post."
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
});
|
||||
|
||||
export default PostModel;
|
||||
export default PostModel;
|
||||
|
@ -1,13 +1,15 @@
|
||||
import ajax from 'ghost/utils/ajax';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
|
||||
import Post from 'ghost/models/post';
|
||||
var EditorRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['editor'],
|
||||
|
||||
controllerName: 'posts.post',
|
||||
model: function (params) {
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id);
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id).then(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default EditorRoute;
|
||||
export default EditorRoute;
|
||||
|
@ -1,13 +1,16 @@
|
||||
import ajax from 'ghost/utils/ajax';
|
||||
import styleBody from 'ghost/mixins/style-body';
|
||||
import AuthenticatedRoute from 'ghost/routes/authenticated';
|
||||
import Post from 'ghost/models/post';
|
||||
|
||||
var PostsRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
classNames: ['manage'],
|
||||
|
||||
model: function () {
|
||||
return ajax('/ghost/api/v0.1/posts').then(function (response) {
|
||||
return response.posts;
|
||||
return response.posts.map(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -18,4 +21,4 @@ var PostsRoute = AuthenticatedRoute.extend(styleBody, {
|
||||
}
|
||||
});
|
||||
|
||||
export default PostsRoute;
|
||||
export default PostsRoute;
|
||||
|
@ -1,7 +1,10 @@
|
||||
/*global ajax */
|
||||
import Post from 'ghost/models/post';
|
||||
var PostsPostRoute = Ember.Route.extend({
|
||||
model: function (params) {
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id);
|
||||
return ajax('/ghost/api/v0.1/posts/' + params.post_id).then(function (post) {
|
||||
return Post.create(post);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -14,40 +14,8 @@
|
||||
{{#link-to "editor" this class="post-edit" title="Edit Post"}}
|
||||
<span class="hidden">Edit Post</span>
|
||||
{{/link-to}}
|
||||
<a class="post-settings" href="#" data-toggle=".post-settings-menu" title="Post Settings"><span class="hidden">Post Settings</span></a>
|
||||
<div class="post-settings-menu menu-drop-right overlay" style="display: none;">
|
||||
<form>
|
||||
<table class="plain">
|
||||
<tbody>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="url">URL</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
<input id="url" class="post-setting-slug" type="text" value="">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="pub-date">Pub Date</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
<input id="pub-date" class="post-setting-date" type="text" value="" placeholder="17 Feb 14 @ 02:35"><!--<span class="post-setting-calendar"></span>-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<span class="label">Static Page</span>
|
||||
</td>
|
||||
<td class="post-setting-item">
|
||||
<input id="static-page" class="post-setting-static-page" type="checkbox" value="">
|
||||
<label class="checkbox" for="static-page"></label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<a class="delete" href="#">Delete This Post</a>
|
||||
</div>
|
||||
<a class="post-settings" title="Post Settings" {{action 'editSettings'}}><span class="hidden">Post Settings</span></a>
|
||||
<!-- @TODO use Ghost Popover (#2565) --->
|
||||
{{view "post-settings-menu-view"}}
|
||||
</section>
|
||||
</header>
|
||||
</header>
|
||||
|
@ -10,39 +10,9 @@
|
||||
<div class="right">
|
||||
|
||||
<section id="entry-controls">
|
||||
<a class="post-settings" href="#" data-toggle=".post-settings-menu" title="Post Settings"><span class="hidden">Post Settings</span></a>
|
||||
<div class="post-settings-menu menu-right overlay">
|
||||
<form>
|
||||
<table class="plain">
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="url">URL</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
<input id="url" class="post-setting-slug" type="text" placeholder="" value="" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="pub-date">Pub Date</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
<input id="pub-date" class="post-setting-date" type="text" placeholder="Now" value=""><!--<span class="post-setting-calendar"></span>-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<span class="label">Static Page</span>
|
||||
</td>
|
||||
<td class="post-setting-item">
|
||||
<input id="static-page" class="post-setting-static-page" type="checkbox" value="">
|
||||
<label class="checkbox" for="static-page"></label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<a class="delete" href="#">Delete This Post</a>
|
||||
</div>
|
||||
<a class="post-settings" title="Post Settings" {{action 'editSettings'}}><span class="hidden">Post Settings</span></a>
|
||||
<!-- @TODO Use Ghost Popover (#2565) and style arrow down -->
|
||||
{{view "post-settings-menu-view"}}
|
||||
</section>
|
||||
|
||||
<section id="entry-actions" class="js-publish-splitbutton splitbutton-save">
|
||||
@ -56,4 +26,4 @@
|
||||
</section>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
</footer>
|
||||
|
32
core/client/templates/post-settings-menu.hbs
Normal file
32
core/client/templates/post-settings-menu.hbs
Normal file
@ -0,0 +1,32 @@
|
||||
<form>
|
||||
<table class="plain">
|
||||
<tbody>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="url">URL</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
{{blur-text-field class="post-setting-slug" id="url" value=newSlug action="updateSlug" placeholder=slugPlaceholder selectOnClick="true"}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label for="pub-date">Pub Date</label>
|
||||
</td>
|
||||
<td class="post-setting-field">
|
||||
{{blur-text-field class="post-setting-date" value=view.publishedAt action="updatePublishedAt" placeholder=view.datePlaceholder}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="post-setting">
|
||||
<td class="post-setting-label">
|
||||
<label class="label" for="static-page">Static Page</label>
|
||||
</td>
|
||||
<td class="post-setting-item">
|
||||
{{input type="checkbox" name="static-page" id="static-page" class="post-setting-static-page" checked=isStaticPage}}
|
||||
<label class="checkbox" for="static-page"></label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<a class="delete" {{action 'openModal' 'delete-post' post}}>Delete This Post</a>
|
21
core/client/utils/date-formatting.js
Normal file
21
core/client/utils/date-formatting.js
Normal file
@ -0,0 +1,21 @@
|
||||
/* global moment */
|
||||
var parseDateFormats = ["DD MMM YY HH:mm",
|
||||
"DD MMM YYYY HH:mm",
|
||||
"DD/MM/YY HH:mm",
|
||||
"DD/MM/YYYY HH:mm",
|
||||
"DD-MM-YY HH:mm",
|
||||
"DD-MM-YYYY HH:mm",
|
||||
"YYYY-MM-DD HH:mm"],
|
||||
displayDateFormat = 'DD MMM YY @ HH:mm';
|
||||
|
||||
//Parses a string to a Moment
|
||||
var parseDateString = function (value) {
|
||||
return value ? moment(value, parseDateFormats) : '';
|
||||
};
|
||||
|
||||
//Formats a Date or Moment
|
||||
var formatDate = function (value) {
|
||||
return value ? moment(value).format(displayDateFormat) : '';
|
||||
};
|
||||
|
||||
export {parseDateString, formatDate};
|
@ -15,6 +15,11 @@ var Notifications = Ember.ArrayProxy.extend({
|
||||
message: message
|
||||
});
|
||||
},
|
||||
showErrors: function (errors) {
|
||||
for (var i = 0; i < errors.length; i += 1) {
|
||||
this.showError(errors[i].message || errors[i]);
|
||||
}
|
||||
},
|
||||
showInfo: function (message) {
|
||||
this.pushObject({
|
||||
type: 'info',
|
||||
@ -35,4 +40,4 @@ var Notifications = Ember.ArrayProxy.extend({
|
||||
}
|
||||
});
|
||||
|
||||
export default Notifications;
|
||||
export default Notifications;
|
||||
|
@ -2,7 +2,7 @@ import itemView from 'ghost/views/item-view';
|
||||
|
||||
var PostItemView = itemView.extend({
|
||||
openEditor: function () {
|
||||
this.get('controller').send('openEditor', this.get('context')); // send action to handle transition to editor route
|
||||
this.get('controller').send('openEditor', this.get('controller.model')); // send action to handle transition to editor route
|
||||
}.on("doubleClick")
|
||||
});
|
||||
|
||||
|
18
core/client/views/post-settings-menu-view.js
Normal file
18
core/client/views/post-settings-menu-view.js
Normal file
@ -0,0 +1,18 @@
|
||||
/* global moment */
|
||||
import {formatDate} from 'ghost/utils/date-formatting';
|
||||
|
||||
var PostSettingsMenuView = Ember.View.extend({
|
||||
templateName: 'post-settings-menu',
|
||||
classNames: ['post-settings-menu', 'menu-drop-right', 'overlay'],
|
||||
classNameBindings: ['controller.isEditingSettings::hidden'],
|
||||
publishedAtBinding: Ember.Binding.oneWay('controller.publishedAt'),
|
||||
click: function (event) {
|
||||
//Stop click propagation to prevent window closing
|
||||
event.stopPropagation();
|
||||
},
|
||||
datePlaceholder: function () {
|
||||
return formatDate(moment());
|
||||
}.property('controller.publishedAt')
|
||||
});
|
||||
|
||||
export default PostSettingsMenuView;
|
Loading…
Reference in New Issue
Block a user