diff --git a/app/components/gh-date-time-picker.js b/app/components/gh-date-time-picker.js index dc47f863c..dc7bdfa31 100644 --- a/app/components/gh-date-time-picker.js +++ b/app/components/gh-date-time-picker.js @@ -33,7 +33,7 @@ export default Component.extend({ let errors = this.get('errors'); let property = this.get('dateErrorProperty'); - if (!isEmpty(errors.errorsFor(property))) { + if (errors && !isEmpty(errors.errorsFor(property))) { return errors.errorsFor(property).get('firstObject').message; } }), @@ -42,7 +42,7 @@ export default Component.extend({ let errors = this.get('errors'); let property = this.get('timeErrorProperty'); - if (!isEmpty(errors.errorsFor(property))) { + if (errors && !isEmpty(errors.errorsFor(property))) { return errors.errorsFor(property).get('firstObject').message; } }), diff --git a/app/components/gh-post-settings-menu.js b/app/components/gh-post-settings-menu.js index ff0e6f20c..02282f9fe 100644 --- a/app/components/gh-post-settings-menu.js +++ b/app/components/gh-post-settings-menu.js @@ -137,21 +137,6 @@ export default Component.extend(SettingsMenuMixin, { return false; }, - togglePage() { - this.toggleProperty('post.page'); - - // If this is a new post. Don't save the post. Defer the save - // to the user pressing the save button - if (this.get('post.isNew')) { - return; - } - - this.get('savePost').perform().catch((error) => { - this.showError(error); - this.get('post').rollbackAttributes(); - }); - }, - toggleFeatured() { this.toggleProperty('post.featured'); diff --git a/app/components/gh-psm-template-select.js b/app/components/gh-psm-template-select.js index 952c5106f..a90101e33 100644 --- a/app/components/gh-psm-template-select.js +++ b/app/components/gh-psm-template-select.js @@ -31,7 +31,7 @@ export default Component.extend({ matchedSlugTemplate: computed('post.{page,slug}', 'activeTheme.slugTemplates.[]', function () { let slug = this.get('post.slug'); - let type = this.get('post.page') ? 'page' : 'post'; + let type = this.post.constructor.modelName; let [matchedTemplate] = this.get('activeTheme.slugTemplates').filter(function (template) { return template.for.includes(type) && template.slug === slug; diff --git a/app/components/gh-search-input.js b/app/components/gh-search-input.js index c31086f67..f430315ca 100644 --- a/app/components/gh-search-input.js +++ b/app/components/gh-search-input.js @@ -33,7 +33,7 @@ export default Component.extend({ currentSearch: '', selection: null, - posts: computedGroup('Stories'), + posts: computedGroup('Posts'), pages: computedGroup('Pages'), users: computedGroup('Users'), tags: computedGroup('Tags'), @@ -42,7 +42,7 @@ export default Component.extend({ let groups = []; if (!isEmpty(this.get('posts'))) { - groups.pushObject({groupName: 'Stories', options: this.get('posts')}); + groups.pushObject({groupName: 'Posts', options: this.get('posts')}); } if (!isEmpty(this.get('pages'))) { @@ -71,9 +71,14 @@ export default Component.extend({ return; } - if (selected.category === 'Stories' || selected.category === 'Pages') { + if (selected.category === 'Posts') { let id = selected.id.replace('post.', ''); - this.get('router').transitionTo('editor.edit', id); + this.get('router').transitionTo('editor.edit', 'post', id); + } + + if (selected.category === 'Pages') { + let id = selected.id.replace('page.', ''); + this.get('router').transitionTo('editor.edit', 'page', id); } if (selected.category === 'Users') { @@ -132,6 +137,7 @@ export default Component.extend({ this.set('content', []); promises.pushObject(this._loadPosts()); + promises.pushObject(this._loadPages()); promises.pushObject(this._loadUsers()); promises.pushObject(this._loadTags()); @@ -149,14 +155,31 @@ export default Component.extend({ _loadPosts() { let store = this.get('store'); let postsUrl = `${store.adapterFor('post').urlForQuery({}, 'post')}/`; - let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', filter: 'page:[true,false]'}; + let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all'}; let content = this.get('content'); return this.get('ajax').request(postsUrl, {data: postsQuery}).then((posts) => { content.pushObjects(posts.posts.map(post => ({ id: `post.${post.id}`, title: post.title, - category: post.page ? 'Pages' : 'Stories' + category: 'Posts' + }))); + }).catch((error) => { + this.get('notifications').showAPIError(error, {key: 'search.loadPosts.error'}); + }); + }, + + _loadPages() { + let store = this.get('store'); + let pagesUrl = `${store.adapterFor('page').urlForQuery({}, 'page')}/`; + let pagesQuery = {fields: 'id,title,page', limit: 'all', status: 'all'}; + let content = this.get('content'); + + return this.get('ajax').request(pagesUrl, {data: pagesQuery}).then((pages) => { + content.pushObjects(pages.pages.map(page => ({ + id: `page.${page.id}`, + title: page.title, + category: 'Pages' }))); }).catch((error) => { this.get('notifications').showAPIError(error, {key: 'search.loadPosts.error'}); diff --git a/app/controllers/pages-loading.js b/app/controllers/pages-loading.js new file mode 100644 index 000000000..c26eb82da --- /dev/null +++ b/app/controllers/pages-loading.js @@ -0,0 +1,7 @@ +import PostsLoadingController from './posts-loading'; +import {inject as controller} from '@ember/controller'; + +/* eslint-disable ghost/ember/alias-model-in-controller */ +export default PostsLoadingController.extend({ + postsController: controller('pages') +}); diff --git a/app/controllers/pages.js b/app/controllers/pages.js new file mode 100644 index 000000000..c4f5abc8f --- /dev/null +++ b/app/controllers/pages.js @@ -0,0 +1,44 @@ +import PostsController from './posts'; + +const TYPES = [{ + name: 'All pages', + value: null +}, { + name: 'Draft pages', + value: 'draft' +}, { + name: 'Published pages', + value: 'published' +}, { + name: 'Scheduled pages', + value: 'scheduled' +}, { + name: 'Featured pages', + value: 'featured' +}]; + +const ORDERS = [{ + name: 'Newest', + value: null +}, { + name: 'Oldest', + value: 'published_at asc' +}, { + name: 'Recently updated', + value: 'updated_at desc' +}]; + +/* eslint-disable ghost/ember/alias-model-in-controller */ +export default PostsController.extend({ + init() { + this._super(...arguments); + this.availableTypes = TYPES; + this.availableOrders = ORDERS; + }, + + actions: { + openEditor(page) { + this.transitionToRoute('editor.edit', 'page', page.get('id')); + } + } +}); diff --git a/app/controllers/posts-loading.js b/app/controllers/posts-loading.js index 7a1b6a51f..41c136ebe 100644 --- a/app/controllers/posts-loading.js +++ b/app/controllers/posts-loading.js @@ -1,8 +1,8 @@ -/* eslint-disable ghost/ember/alias-model-in-controller */ import Controller, {inject as controller} from '@ember/controller'; import {readOnly} from '@ember/object/computed'; import {inject as service} from '@ember/service'; +/* eslint-disable ghost/ember/alias-model-in-controller */ export default Controller.extend({ postsController: controller('posts'), diff --git a/app/controllers/posts.js b/app/controllers/posts.js index 6f3b21bcb..4fbbffb9e 100644 --- a/app/controllers/posts.js +++ b/app/controllers/posts.js @@ -19,9 +19,6 @@ const TYPES = [{ }, { name: 'Featured posts', value: 'featured' -}, { - name: 'Pages', - value: 'page' }]; const ORDERS = [{ @@ -137,7 +134,7 @@ export default Controller.extend({ }, openEditor(post) { - this.transitionToRoute('editor.edit', post.get('id')); + this.transitionToRoute('editor.edit', 'post', post.get('id')); } } }); diff --git a/app/models/page.js b/app/models/page.js new file mode 100644 index 000000000..5d3faca46 --- /dev/null +++ b/app/models/page.js @@ -0,0 +1,5 @@ +import PostModel from './post'; + +export default PostModel.extend({ + displayName: 'page' +}); diff --git a/app/models/post.js b/app/models/post.js index ff7f5ecc8..f117938fd 100644 --- a/app/models/post.js +++ b/app/models/post.js @@ -72,6 +72,7 @@ export default Model.extend(Comparable, ValidationEngine, { clock: service(), settings: service(), + displayName: 'post', validationType: 'post', createdAtUTC: attr('moment-utc'), @@ -92,7 +93,6 @@ export default Model.extend(Comparable, ValidationEngine, { metaDescription: attr('string'), metaTitle: attr('string'), mobiledoc: attr('json-string'), - page: attr('boolean', {defaultValue: false}), plaintext: attr('string'), publishedAtUTC: attr('moment-utc'), slug: attr('string'), diff --git a/app/router.js b/app/router.js index 1562131bf..cdf12b984 100644 --- a/app/router.js +++ b/app/router.js @@ -32,10 +32,11 @@ Router.map(function () { this.route('about', {path: '/about'}); this.route('posts', {path: '/'}, function () {}); + this.route('pages', {path: '/pages'}, function () {}); this.route('editor', function () { - this.route('new', {path: ''}); - this.route('edit', {path: ':post_id'}); + this.route('new', {path: ':type'}); + this.route('edit', {path: ':type/:post_id'}); }); this.route('team', {path: '/team'}, function () { diff --git a/app/routes/editor/edit.js b/app/routes/editor/edit.js index c3980e3ef..67548aecc 100644 --- a/app/routes/editor/edit.js +++ b/app/routes/editor/edit.js @@ -1,4 +1,5 @@ import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; +import {pluralize} from 'ember-inflector'; export default AuthenticatedRoute.extend({ beforeModel(transition) { @@ -17,11 +18,10 @@ export default AuthenticatedRoute.extend({ let query = { id: params.post_id, status: 'all', - filter: 'page:[true,false]', formats: 'mobiledoc,plaintext' }; - return this.store.query('post', query) + return this.store.query(params.type, query) .then(records => records.get('firstObject')); }, @@ -32,17 +32,26 @@ export default AuthenticatedRoute.extend({ this._super(...arguments); return this.get('session.user').then((user) => { + let returnRoute = `${pluralize(post.constructor.modelName)}.index`; + if (user.get('isAuthorOrContributor') && !post.isAuthoredByUser(user)) { - return this.replaceWith('posts.index'); + return this.replaceWith(returnRoute); } // If the post is not a draft and user is contributor, redirect to index if (user.get('isContributor') && !post.get('isDraft')) { - return this.replaceWith('posts.index'); + return this.replaceWith(returnRoute); } }); }, + serialize(model) { + return { + type: model.constructor.modelName, + post_id: model.id + }; + }, + // there's no specific controller for this route, instead all editor // handling is done on the editor route/controler setupController(controller, post) { diff --git a/app/routes/editor/index.js b/app/routes/editor/index.js new file mode 100644 index 000000000..1fa6ccfa9 --- /dev/null +++ b/app/routes/editor/index.js @@ -0,0 +1,8 @@ +import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; + +export default AuthenticatedRoute.extend({ + beforeModel() { + this._super(...arguments); + this.replaceWith('editor.new', 'post'); + } +}); diff --git a/app/routes/editor/new.js b/app/routes/editor/new.js index c88b5099a..63373860c 100644 --- a/app/routes/editor/new.js +++ b/app/routes/editor/new.js @@ -1,9 +1,9 @@ import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; export default AuthenticatedRoute.extend({ - model() { + model(params) { return this.get('session.user').then(user => ( - this.store.createRecord('post', {authors: [user]}) + this.store.createRecord(params.type, {authors: [user]}) )); }, diff --git a/app/routes/pages.js b/app/routes/pages.js new file mode 100644 index 000000000..ae543e0c1 --- /dev/null +++ b/app/routes/pages.js @@ -0,0 +1,6 @@ +import PostsRoute from './posts'; + +export default PostsRoute.extend({ + titleToken: 'Pages', + modelName: 'page' +}); diff --git a/app/routes/posts.js b/app/routes/posts.js index 012201bdc..024dbc448 100644 --- a/app/routes/posts.js +++ b/app/routes/posts.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import AuthenticatedRoute from 'ghost-admin/routes/authenticated'; import {assign} from '@ember/polyfills'; import {isBlank} from '@ember/utils'; @@ -26,14 +25,15 @@ export default AuthenticatedRoute.extend({ } }, - titleToken: 'Content', + titleToken: 'Posts', + modelName: 'post', perPage: 30, _type: null, model(params) { - return this.get('session.user').then((user) => { + return this.session.user.then((user) => { let queryParams = {}; let filterParams = {tag: params.tag}; let paginationParams = { @@ -47,12 +47,12 @@ export default AuthenticatedRoute.extend({ filterParams.featured = true; } - if (user.get('isAuthor')) { + if (user.isAuthor) { // authors can only view their own posts - filterParams.authors = user.get('slug'); - } else if (user.get('isContributor')) { + filterParams.authors = user.slug; + } else if (user.isContributor) { // Contributors can only view their own draft posts - filterParams.authors = user.get('slug'); + filterParams.authors = user.slug; filterParams.status = 'draft'; } else if (params.author) { filterParams.authors = params.author; @@ -69,10 +69,10 @@ export default AuthenticatedRoute.extend({ queryParams.formats = 'mobiledoc,plaintext'; - let perPage = this.get('perPage'); + let perPage = this.perPage; let paginationSettings = assign({perPage, startingPage: 1}, paginationParams, queryParams); - return this.infinity.model('post', paginationSettings); + return this.infinity.model(this.modelName, paginationSettings); }); }, @@ -81,14 +81,14 @@ export default AuthenticatedRoute.extend({ this._super(...arguments); if (!controller._hasLoadedTags) { - this.get('store').query('tag', {limit: 'all'}).then(() => { + this.store.query('tag', {limit: 'all'}).then(() => { controller._hasLoadedTags = true; }); } - this.get('session.user').then((user) => { - if (!user.get('isAuthorOrContributor') && !controller._hasLoadedAuthors) { - this.get('store').query('user', {limit: 'all'}).then(() => { + this.session.user.then((user) => { + if (!user.isAuthorOrContributor && !controller._hasLoadedAuthors) { + this.store.query('user', {limit: 'all'}).then(() => { controller._hasLoadedAuthors = true; }); } @@ -97,14 +97,17 @@ export default AuthenticatedRoute.extend({ actions: { willTransition() { - if (this.get('controller')) { + if (this.controller) { this.resetController(); } }, queryParamsDidChange() { // scroll back to the top - $('.content-list').scrollTop(0); + let contentList = document.querySelector('.content-list'); + if (contentList) { + contentList.scrollTop = 0; + } this._super(...arguments); } @@ -112,29 +115,21 @@ export default AuthenticatedRoute.extend({ _getTypeFilters(type) { let status = '[draft,scheduled,published]'; - let page = '[true,false]'; switch (type) { case 'draft': status = 'draft'; - page = false; break; case 'published': status = 'published'; - page = false; break; case 'scheduled': status = 'scheduled'; - page = false; - break; - case 'page': - page = true; break; } return { - status, - page + status }; }, diff --git a/app/serializers/page.js b/app/serializers/page.js new file mode 100644 index 000000000..5e82bbd95 --- /dev/null +++ b/app/serializers/page.js @@ -0,0 +1,3 @@ +import PostSerializer from './post'; + +export default PostSerializer.extend({}); diff --git a/app/services/tour.js b/app/services/tour.js index 2ad99c5e2..13ac6609a 100644 --- a/app/services/tour.js +++ b/app/services/tour.js @@ -53,10 +53,6 @@ export default Service.extend(Evented, { id: 'using-the-editor', title: 'Using the Ghost editor', message: 'Ghost uses Markdown to allow you to write and format content quickly and easily. This toolbar also helps! Hit the ? icon for more editor shortcuts.' - }, { - id: 'static-post', - title: 'Turning posts into pages', - message: 'Static pages are permanent pieces of content which live outside of your usual stream of posts, for example an \'about\' or \'contact\' page.' }, { id: 'featured-post', title: 'Setting a featured post', diff --git a/app/templates/components/gh-mobile-nav-bar.hbs b/app/templates/components/gh-mobile-nav-bar.hbs index 5b81a41b8..6a6184c6a 100644 --- a/app/templates/components/gh-mobile-nav-bar.hbs +++ b/app/templates/components/gh-mobile-nav-bar.hbs @@ -1,8 +1,8 @@ -{{#link-to "editor.new" data-test-mobile-nav="new-story"}}{{svg-jar "pen"}}New story{{/link-to}} +{{#link-to "editor.new" "post" data-test-mobile-nav="new-post"}}{{svg-jar "pen"}}New post{{/link-to}} {{#if (eq router.currentRouteName "posts.index")}} - {{#link-to "posts" (query-params type=null) classNames="active" data-test-mobile-nav="stories"}}{{svg-jar "content"}}Stories{{/link-to}} + {{#link-to "posts" (query-params type=null) classNames="active" data-test-mobile-nav="posts"}}{{svg-jar "content"}}Posts{{/link-to}} {{else}} - {{#link-to "posts"}}{{svg-jar "content" data-test-mobile-nav="stories"}}Content{{/link-to}} + {{#link-to "posts"}}{{svg-jar "content" data-test-mobile-nav="posts"}}Posts{{/link-to}} {{/if}} {{#link-to "team" classNames="gh-nav-main-users" data-test-mobile-nav="team"}}{{svg-jar "account-group"}}Team{{/link-to}}
diff --git a/app/templates/components/gh-nav-menu.hbs b/app/templates/components/gh-nav-menu.hbs index 46758c712..633c7b8f3 100644 --- a/app/templates/components/gh-nav-menu.hbs +++ b/app/templates/components/gh-nav-menu.hbs @@ -40,13 +40,21 @@ {{gh-search-input class="gh-nav-search-input"}}