mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +03:00
New content screen prototype (#503)
refs https://github.com/TryGhost/Ghost/issues/7860 - remove preview pane from content screen - add basic post status filters - replace custom infinite scroll with ember-infinity and increase trigger threshold for improved scroll behaviour Commits: * basic content list + filter using existing infinite scroll and pagination * swap our custom pagination + infinite loader for `ember-infinity` * minor cleanups * reset scroll position when changing filter * fix tests * remove client-side sorting step as we no longer have a live collection * remove unused `mobile-index-route` * add acceptance tests for content screen filters
This commit is contained in:
parent
74b020e0e3
commit
c16d633d4b
@ -1,11 +0,0 @@
|
||||
import Component from 'ember-component';
|
||||
import {reads} from 'ember-computed';
|
||||
import injectService from 'ember-service/inject';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: 'section',
|
||||
classNames: ['gh-view', 'content-view-container'],
|
||||
|
||||
mediaQueries: injectService(),
|
||||
previewIsHidden: reads('mediaQueries.maxWidth900')
|
||||
});
|
@ -1,21 +1,16 @@
|
||||
import $ from 'jquery';
|
||||
import Ember from 'ember';
|
||||
import Component from 'ember-component';
|
||||
import {htmlSafe} from 'ember-string';
|
||||
import computed, {alias, equal} from 'ember-computed';
|
||||
import injectService from 'ember-service/inject';
|
||||
|
||||
import ActiveLinkMixin from 'ember-cli-active-link-wrapper/mixins/active-link';
|
||||
import {invokeAction} from 'ember-invoke-action';
|
||||
|
||||
// ember-cli-shims doesn't export these
|
||||
const {Handlebars, ObjectProxy, PromiseProxyMixin} = Ember;
|
||||
|
||||
const ObjectPromiseProxy = ObjectProxy.extend(PromiseProxyMixin);
|
||||
|
||||
export default Component.extend(ActiveLinkMixin, {
|
||||
export default Component.extend({
|
||||
tagName: 'li',
|
||||
classNameBindings: ['isFeatured:featured', 'isPage:page'],
|
||||
|
||||
post: null,
|
||||
previewIsHidden: false,
|
||||
@ -54,45 +49,5 @@ export default Component.extend(ActiveLinkMixin, {
|
||||
|
||||
doubleClick() {
|
||||
this.sendAction('onDoubleClick', this.get('post'));
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
this.addObserver('active', this, this.scrollIntoView);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
this.removeObserver('active', this, this.scrollIntoView);
|
||||
if (this.get('post.isDeleted') && this.get('onDelete')) {
|
||||
invokeAction(this, 'onDelete');
|
||||
}
|
||||
},
|
||||
|
||||
scrollIntoView() {
|
||||
if (!this.get('active')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let element = this.$();
|
||||
let offset = element.offset().top;
|
||||
let elementHeight = element.height();
|
||||
let container = $('.js-content-scrollbox');
|
||||
let containerHeight = container.height();
|
||||
let currentScroll = container.scrollTop();
|
||||
let isBelowTop, isAboveBottom, isOnScreen;
|
||||
|
||||
isAboveBottom = offset < containerHeight;
|
||||
isBelowTop = offset > elementHeight;
|
||||
|
||||
isOnScreen = isBelowTop && isAboveBottom;
|
||||
|
||||
if (!isOnScreen) {
|
||||
// Scroll so that element is centered in container
|
||||
// 40 is the amount of padding on the container
|
||||
container.clearQueue().animate({
|
||||
scrollTop: currentScroll + offset - 40 - containerHeight / 2
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,26 +1,5 @@
|
||||
import Ember from 'ember';
|
||||
import Controller from 'ember-controller';
|
||||
import computed, {equal} from 'ember-computed';
|
||||
import injectService from 'ember-service/inject';
|
||||
|
||||
const {compare} = Ember;
|
||||
|
||||
export default Controller.extend({
|
||||
feature: injectService(),
|
||||
|
||||
showDeletePostModal: false,
|
||||
|
||||
// See PostsRoute's shortcuts
|
||||
postListFocused: equal('keyboardFocus', 'postList'),
|
||||
postContentFocused: equal('keyboardFocus', 'postContent'),
|
||||
|
||||
sortedPosts: computed('model.@each.{status,publishedAtUTC,isNew,updatedAtUTC}', function () {
|
||||
return this.get('model').toArray().sort(compare);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
toggleDeletePostModal() {
|
||||
this.toggleProperty('showDeletePostModal');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
23
ghost/admin/app/controllers/posts/index.js
Normal file
23
ghost/admin/app/controllers/posts/index.js
Normal file
@ -0,0 +1,23 @@
|
||||
import Controller from 'ember-controller';
|
||||
import computed from 'ember-computed';
|
||||
import injectService from 'ember-service/inject';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
queryParams: ['type'],
|
||||
type: null,
|
||||
|
||||
session: injectService(),
|
||||
|
||||
showDeletePostModal: false,
|
||||
|
||||
showingAll: computed('type', function () {
|
||||
return this.get('type') === null;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
toggleDeletePostModal() {
|
||||
this.toggleProperty('showDeletePostModal');
|
||||
}
|
||||
}
|
||||
});
|
@ -30,12 +30,17 @@ export default Mixin.create({
|
||||
}),
|
||||
|
||||
init() {
|
||||
let paginationSettings = this.get('paginationSettings');
|
||||
let settings = assign({}, defaultPaginationSettings, paginationSettings);
|
||||
// don't merge defaults if paginationSettings is a CP
|
||||
if (!this.paginationSettings.isDescriptor) {
|
||||
let paginationSettings = this.get('paginationSettings');
|
||||
let settings = assign({}, defaultPaginationSettings, paginationSettings);
|
||||
|
||||
this.set('paginationSettings', settings);
|
||||
}
|
||||
|
||||
this.set('paginationMeta', {});
|
||||
|
||||
this._super(...arguments);
|
||||
this.set('paginationSettings', settings);
|
||||
this.set('paginationMeta', {});
|
||||
},
|
||||
|
||||
reportLoadError(error) {
|
||||
|
@ -31,9 +31,7 @@ GhostRouter.map(function () {
|
||||
this.route('reset', {path: '/reset/:token'});
|
||||
this.route('about', {path: '/about'});
|
||||
|
||||
this.route('posts', {path: '/'}, function () {
|
||||
this.route('post', {path: ':post_id'});
|
||||
});
|
||||
this.route('posts', {path: '/'}, function() {});
|
||||
|
||||
this.route('editor', function () {
|
||||
this.route('new', {path: ''});
|
||||
|
@ -1,35 +0,0 @@
|
||||
import Route from 'ember-route';
|
||||
import {addObserver, removeObserver} from 'ember-metal/observer';
|
||||
import injectService from 'ember-service/inject';
|
||||
|
||||
function K() {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Routes that extend MobileIndexRoute need to implement
|
||||
// desktopTransition, a function which is called when
|
||||
// the user resizes to desktop levels.
|
||||
export default Route.extend({
|
||||
desktopTransition: K,
|
||||
_callDesktopTransition: null,
|
||||
|
||||
mediaQueries: injectService(),
|
||||
|
||||
activate() {
|
||||
this._super(...arguments);
|
||||
this._callDesktopTransition = () => {
|
||||
if (!this.get('mediaQueries.isMobile')) {
|
||||
this.desktopTransition();
|
||||
}
|
||||
};
|
||||
addObserver(this, 'mediaQueries.isMobile', this._callDesktopTransition);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super(...arguments);
|
||||
if (this._callDesktopTransition) {
|
||||
removeObserver(this, 'mediaQueries.isMobile', this._callDesktopTransition);
|
||||
this._callDesktopTransition = null;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,100 +1,5 @@
|
||||
import $ from 'jquery';
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
|
||||
import PaginationMixin from 'ghost-admin/mixins/pagination';
|
||||
|
||||
export default AuthenticatedRoute.extend(ShortcutsRoute, PaginationMixin, {
|
||||
titleToken: 'Content',
|
||||
|
||||
paginationModel: 'post',
|
||||
paginationSettings: {
|
||||
status: 'all',
|
||||
staticPages: 'all'
|
||||
},
|
||||
|
||||
model() {
|
||||
let paginationSettings = this.get('paginationSettings');
|
||||
|
||||
return this.get('session.user').then((user) => {
|
||||
if (user.get('isAuthor')) {
|
||||
paginationSettings.filter = paginationSettings.filter
|
||||
? `${paginationSettings.filter}+author:${user.get('slug')}` : `author:${user.get('slug')}`;
|
||||
}
|
||||
|
||||
return this.loadFirstPage().then(() => {
|
||||
// using `.filter` allows the template to auto-update when new models are pulled in from the server.
|
||||
// we just need to 'return true' to allow all models by default.
|
||||
return this.store.filter('post', (post) => {
|
||||
if (user.get('isAuthor')) {
|
||||
return post.isAuthoredByUser(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
stepThroughPosts(step) {
|
||||
let currentPost = this.get('controller.currentPost');
|
||||
let posts = this.get('controller.sortedPosts');
|
||||
let length = posts.get('length');
|
||||
let newPosition = posts.indexOf(currentPost) + step;
|
||||
|
||||
// if we are on the first or last item
|
||||
// just do nothing (desired behavior is to not
|
||||
// loop around)
|
||||
if (newPosition >= length) {
|
||||
return;
|
||||
} else if (newPosition < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transitionTo('posts.post', posts.objectAt(newPosition));
|
||||
},
|
||||
|
||||
scrollContent(amount) {
|
||||
let content = $('.js-content-preview');
|
||||
let scrolled = content.scrollTop();
|
||||
|
||||
content.scrollTop(scrolled + 50 * amount);
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
'up, k': 'moveUp',
|
||||
'down, j': 'moveDown',
|
||||
left: 'focusList',
|
||||
right: 'focusContent',
|
||||
c: 'newPost'
|
||||
},
|
||||
|
||||
actions: {
|
||||
focusList() {
|
||||
this.controller.set('keyboardFocus', 'postList');
|
||||
},
|
||||
|
||||
focusContent() {
|
||||
this.controller.set('keyboardFocus', 'postContent');
|
||||
},
|
||||
|
||||
newPost() {
|
||||
this.transitionTo('editor.new');
|
||||
},
|
||||
|
||||
moveUp() {
|
||||
if (this.controller.get('postContentFocused')) {
|
||||
this.scrollContent(-1);
|
||||
} else {
|
||||
this.stepThroughPosts(-1);
|
||||
}
|
||||
},
|
||||
|
||||
moveDown() {
|
||||
if (this.controller.get('postContentFocused')) {
|
||||
this.scrollContent(1);
|
||||
} else {
|
||||
this.stepThroughPosts(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
export default AuthenticatedRoute.extend({
|
||||
titleToken: 'Content'
|
||||
});
|
||||
|
@ -1,53 +1,104 @@
|
||||
import {reads} from 'ember-computed';
|
||||
import injectService from 'ember-service/inject';
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
|
||||
import InfinityRoute from 'ember-infinity/mixins/route';
|
||||
import computed from 'ember-computed';
|
||||
import {assign} from 'ember-platform';
|
||||
import $ from 'jquery';
|
||||
|
||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||
import MobileIndexRoute from 'ghost-admin/routes/mobile-index-route';
|
||||
export default AuthenticatedRoute.extend(InfinityRoute, ShortcutsRoute, {
|
||||
|
||||
export default MobileIndexRoute.extend(AuthenticatedRouteMixin, {
|
||||
noPosts: false,
|
||||
perPageParam: 'limit',
|
||||
totalPagesParam: 'meta.pagination.pages',
|
||||
|
||||
mediaQueries: injectService(),
|
||||
isMobile: reads('mediaQueries.isMobile'),
|
||||
_type: null,
|
||||
|
||||
// Transition to a specific post if we're not on mobile
|
||||
beforeModel() {
|
||||
this._super(...arguments);
|
||||
if (!this.get('isMobile')) {
|
||||
return this.goToPost();
|
||||
}
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
controller.set('noPosts', this.get('noPosts'));
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
goToPost() {
|
||||
// the store has been populated by PostsRoute
|
||||
let posts = this.store.peekAll('post');
|
||||
let post;
|
||||
model(params) {
|
||||
this.set('_type', params.type);
|
||||
let filterSettings = this.get('filterSettings');
|
||||
|
||||
return this.get('session.user').then((user) => {
|
||||
post = posts.find(function (post) {
|
||||
// Authors can only see posts they've written
|
||||
if (user.get('isAuthor')) {
|
||||
return post.isAuthoredByUser(user);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (post) {
|
||||
return this.transitionTo('posts.post', post);
|
||||
if (user.get('isAuthor')) {
|
||||
filterSettings.filter = filterSettings.filter
|
||||
? `${filterSettings.filter}+author:${user.get('slug')}` : `author:${user.get('slug')}`;
|
||||
}
|
||||
|
||||
this.set('noPosts', true);
|
||||
let paginationSettings = assign({perPage: 15, startingPage: 1}, filterSettings);
|
||||
|
||||
return this.infinityModel('post', paginationSettings);
|
||||
});
|
||||
},
|
||||
|
||||
// Mobile posts route callback
|
||||
desktopTransition() {
|
||||
this.goToPost();
|
||||
filterSettings: computed('_type', function () {
|
||||
let type = this.get('_type');
|
||||
let status = 'all';
|
||||
let staticPages = 'all';
|
||||
|
||||
switch (type) {
|
||||
case 'draft':
|
||||
status = 'draft';
|
||||
staticPages = false;
|
||||
break;
|
||||
case 'published':
|
||||
status = 'published';
|
||||
staticPages = false;
|
||||
break;
|
||||
case 'scheduled':
|
||||
status = 'scheduled';
|
||||
staticPages = false;
|
||||
break;
|
||||
case 'page':
|
||||
staticPages = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
staticPages
|
||||
};
|
||||
}),
|
||||
|
||||
stepThroughPosts(step) {
|
||||
let currentPost = this.get('controller.currentPost');
|
||||
let posts = this.get('controller.sortedPosts');
|
||||
let length = posts.get('length');
|
||||
let newPosition = posts.indexOf(currentPost) + step;
|
||||
|
||||
// if we are on the first or last item
|
||||
// just do nothing (desired behavior is to not
|
||||
// loop around)
|
||||
if (newPosition >= length) {
|
||||
return;
|
||||
} else if (newPosition < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: highlight post
|
||||
// this.transitionTo('posts.post', posts.objectAt(newPosition));
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
'up, k': 'moveUp',
|
||||
'down, j': 'moveDown',
|
||||
c: 'newPost'
|
||||
},
|
||||
|
||||
actions: {
|
||||
queryParamsDidChange() {
|
||||
this.refresh();
|
||||
// reset the scroll position
|
||||
$('.content-list-content').scrollTop(0);
|
||||
},
|
||||
|
||||
newPost() {
|
||||
this.transitionTo('editor.new');
|
||||
},
|
||||
|
||||
moveUp() {
|
||||
this.stepThroughPosts(-1);
|
||||
},
|
||||
|
||||
moveDown() {
|
||||
this.stepThroughPosts(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,64 +0,0 @@
|
||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
|
||||
|
||||
export default AuthenticatedRoute.extend(ShortcutsRoute, {
|
||||
model(params) {
|
||||
/* eslint-disable camelcase */
|
||||
let post = this.store.peekRecord('post', params.post_id);
|
||||
let query = {
|
||||
id: params.post_id,
|
||||
status: 'all',
|
||||
staticPages: 'all'
|
||||
};
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
if (post) {
|
||||
return post;
|
||||
}
|
||||
|
||||
return this.store.query('post', query).then((records) => {
|
||||
let post = records.get('firstObject');
|
||||
|
||||
if (post) {
|
||||
return post;
|
||||
}
|
||||
|
||||
return this.replaceWith('posts.index');
|
||||
});
|
||||
},
|
||||
|
||||
afterModel(post) {
|
||||
return this.get('session.user').then((user) => {
|
||||
if (user.get('isAuthor') && !post.isAuthoredByUser(user)) {
|
||||
return this.replaceWith('posts.index');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
this.controllerFor('posts').set('currentPost', model);
|
||||
},
|
||||
|
||||
shortcuts: {
|
||||
'enter, o': 'openEditor',
|
||||
'command+backspace, ctrl+backspace': 'deletePost'
|
||||
},
|
||||
|
||||
actions: {
|
||||
openEditor(post) {
|
||||
post = post || this.get('controller.model');
|
||||
|
||||
if (!post) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.transitionTo('editor.edit', post.get('id'));
|
||||
},
|
||||
|
||||
deletePost() {
|
||||
this.controllerFor('posts').send('toggleDeletePostModal');
|
||||
}
|
||||
}
|
||||
});
|
@ -48,3 +48,12 @@
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Infinite scroll */
|
||||
.infinity-loader {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2em 0;
|
||||
}
|
||||
|
@ -1,26 +1,35 @@
|
||||
/* Content /ghost/
|
||||
|
||||
/* Show/Hide on Mobile // TODO: What the fuck does that mean?
|
||||
/* Header
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.content-list.show-menu {
|
||||
display: block;
|
||||
.gh-content-view-container .view-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.content-list.show-content {
|
||||
display: none;
|
||||
.basic-filter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: #dfe1e3 1px solid;
|
||||
}
|
||||
|
||||
.content-preview.show-menu {
|
||||
display: none;
|
||||
.basic-filter > ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.content-preview.show-content {
|
||||
display: block;
|
||||
.basic-filter > ul li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.basic-filter > ul a.active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Content List (Left pane)
|
||||
/* Content List
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.content-list {
|
||||
@ -28,7 +37,7 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 30%;
|
||||
width: 100%;
|
||||
border-right: #dfe1e3 1px solid;
|
||||
background: #fff;
|
||||
}
|
||||
@ -163,80 +172,6 @@
|
||||
}
|
||||
|
||||
|
||||
/* Preview (Right pane)
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.content-preview-title a {
|
||||
position: relative;
|
||||
color: var(--darkgrey);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content-preview {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
width: 70%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.content-preview {
|
||||
display: none;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content-preview-content {
|
||||
padding: 5vw 6vw;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.content-preview-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
.content-preview-content .wrapper {
|
||||
margin: 0 auto;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.content-preview .post-controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.content-preview .post-controls .post-edit {
|
||||
padding-top: 0;
|
||||
padding-right: 0;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
}
|
||||
.content-preview .post-controls .post-edit:hover {
|
||||
color: var(--darkgrey);
|
||||
}
|
||||
|
||||
.content-preview img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
/* Empty State
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
@ -270,7 +205,6 @@
|
||||
|
||||
|
||||
/* This has to be a pseudo element to sit over the top of everything else in the content list */
|
||||
.content-list.keyboard-focused:before,
|
||||
.tag-list-content.keyboard-focused:before,
|
||||
.tag-settings.keyboard-focused:before {
|
||||
content: "";
|
||||
@ -283,7 +217,3 @@
|
||||
animation: keyboard-focus-style-fade-out 1.5s 1 forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.content-preview.keyboard-focused {
|
||||
animation: keyboard-focus-style-fade-out 1.5s 1 forwards;
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
{{yield previewIsHidden}}
|
@ -1,4 +1,4 @@
|
||||
{{#link-to (if previewIsHidden 'editor.edit' 'posts.post') post.id class="permalink" title="Edit this post"}}
|
||||
{{#link-to "editor.edit" post.id class="permalink" title="Edit this post"}}
|
||||
<h3 class="entry-title">{{post.title}}</h3>
|
||||
<section class="entry-meta">
|
||||
<span class="avatar" style={{authorAvatarBackground}}>
|
||||
@ -28,4 +28,4 @@
|
||||
{{/if}}
|
||||
</span>
|
||||
</section>
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
|
8
ghost/admin/app/templates/components/infinity-loader.hbs
Normal file
8
ghost/admin/app/templates/components/infinity-loader.hbs
Normal file
@ -0,0 +1,8 @@
|
||||
{{#if hasBlock}}
|
||||
{{yield}}
|
||||
{{else}}
|
||||
{{#if infinityModel.reachedInfinity}}
|
||||
{{else}}
|
||||
<div class="gh-loading-spinner"></div>
|
||||
{{/if}}
|
||||
{{/if}}
|
@ -1,30 +1,42 @@
|
||||
{{#gh-content-view-container as |previewIsHidden|}}
|
||||
<header class="view-header">
|
||||
{{#gh-view-title openMobileMenu="openMobileMenu"}}<span>Content</span>{{/gh-view-title}}
|
||||
<section class="view-actions">
|
||||
{{#link-to "editor.new" class="btn btn-green" title="New Post"}}New Post{{/link-to}}
|
||||
</section>
|
||||
</header>
|
||||
<section class="gh-view gh-content-view-container">
|
||||
<header class="view-header">
|
||||
{{#gh-view-title openMobileMenu="openMobileMenu"}}<span>Content</span>{{/gh-view-title}}
|
||||
<section class="view-actions">
|
||||
{{#link-to "editor.new" class="btn btn-green" title="New Post" data-test-new-post-button=true}}
|
||||
New Post
|
||||
{{/link-to}}
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<div class="view-container">
|
||||
<section class="content-list js-content-list {{if postListFocused 'keyboard-focused'}}">
|
||||
{{#gh-infinite-scroll tagName="section" classNames="content-list-content js-content-scrollbox" fetch="loadNextPage" as |checkScroll|}}
|
||||
<ol class="posts-list">
|
||||
{{#each sortedPosts key="id" as |post|}}
|
||||
{{gh-posts-list-item post=post previewIsHidden=previewIsHidden onDoubleClick="openEditor" onDelete=(action checkScroll)}}
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{/gh-infinite-scroll}}
|
||||
<section class="basic-filter">
|
||||
<ul>
|
||||
<li>
|
||||
{{#link-to "posts.index" (query-params type=null) data-test-all-filter-link=true}}
|
||||
All
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "posts.index" (query-params type="draft") data-test-drafts-filter-link=true}}
|
||||
Drafts
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "posts.index" (query-params type="published") data-test-published-filter-link=true}}
|
||||
Published
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "posts.index" (query-params type="scheduled") data-test-scheduled-filter-link=true}}
|
||||
Scheduled
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li>
|
||||
{{#link-to "posts.index" (query-params type="page") data-test-pages-filter-link=true}}
|
||||
Pages
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="content-preview js-content-preview {{if postContentFocused 'keyboard-focused'}}">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</div>
|
||||
{{/gh-content-view-container}}
|
||||
|
||||
{{#if showDeletePostModal}}
|
||||
{{gh-fullscreen-modal "delete-post"
|
||||
model=currentPost
|
||||
close=(action "toggleDeletePostModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
{{outlet}}
|
||||
</section>
|
||||
|
@ -1,8 +1,40 @@
|
||||
<div class="no-posts-box">
|
||||
{{#if noPosts}}
|
||||
<div class="no-posts">
|
||||
<h3>You Haven't Written Any Posts Yet!</h3>
|
||||
{{#link-to "editor.new"}}<button type="button" class="btn btn-green btn-lg" title="New Post">Write a new Post</button>{{/link-to}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="view-container">
|
||||
<section class="content-list js-content-list {{if postListFocused 'keyboard-focused'}}">
|
||||
<section class="content-list-content">
|
||||
<ol class="posts-list">
|
||||
{{#each model as |post|}}
|
||||
{{gh-posts-list-item
|
||||
post=post
|
||||
onDoubleClick="openEditor"
|
||||
data-test-posts-list-item-id=post.id}}
|
||||
{{else}}
|
||||
<li class="no-posts-box">
|
||||
<div class="no-posts">
|
||||
{{#if showingAll}}
|
||||
<h3>You Haven't Written Any Posts Yet!</h3>
|
||||
{{#link-to "editor.new"}}<button type="button" class="btn btn-green btn-lg" title="New Post">Write a new Post</button>{{/link-to}}
|
||||
{{else}}
|
||||
<h3>No posts that match the current filter</h3>
|
||||
{{#link-to "posts.index" (query-params type=null)}}<button type="button" class="btn btn-lg">Show all posts</button>{{/link-to}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
{{infinity-loader
|
||||
infinityModel=model
|
||||
scrollable=".content-list-content"
|
||||
triggerOffset=1000}}
|
||||
</ol>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{#if showDeletePostModal}}
|
||||
{{gh-fullscreen-modal "delete-post"
|
||||
model=currentPost
|
||||
close=(action "toggleDeletePostModal")
|
||||
modifier="action wide"}}
|
||||
{{/if}}
|
||||
|
||||
{{outlet}}
|
||||
|
@ -1,14 +0,0 @@
|
||||
<section class="post-controls">
|
||||
{{#link-to "editor.edit" model.id class="btn btn-minor post-edit" title="Edit this post"}}<i class="icon-edit"></i>{{/link-to}}
|
||||
</section>
|
||||
|
||||
{{#gh-content-preview-content tagName="section" content=model}}
|
||||
<div class="wrapper">
|
||||
<h1 class="content-preview-title">
|
||||
{{#link-to "editor.edit" model.id}}
|
||||
{{model.title}}
|
||||
{{/link-to}}
|
||||
</h1>
|
||||
{{gh-format-html model.html}}
|
||||
</div>
|
||||
{{/gh-content-preview-content}}
|
@ -64,7 +64,7 @@ codemirrorAssets = function () {
|
||||
|
||||
module.exports = function (defaults) {
|
||||
var app = new EmberApp(defaults, {
|
||||
babel: {
|
||||
"ember-cli-babel": {
|
||||
optional: ['es6.spec.symbols'],
|
||||
includePolyfill: true
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {Response} from 'ember-cli-mirage';
|
||||
import {isBlank} from 'ember-utils';
|
||||
import {paginatedResponse} from '../utils';
|
||||
import {paginateModelArray} from '../utils';
|
||||
import {dasherize} from 'ember-string';
|
||||
|
||||
export default function mockPosts(server) {
|
||||
@ -11,12 +11,33 @@ export default function mockPosts(server) {
|
||||
attrs.slug = dasherize(attrs.title);
|
||||
}
|
||||
|
||||
// NOTE: this does not use the post factory to fill in blank fields
|
||||
return posts.create(attrs);
|
||||
});
|
||||
|
||||
// TODO: handle status/staticPages/author params
|
||||
server.get('/posts/', paginatedResponse('posts'));
|
||||
// TODO: handle author filter
|
||||
server.get('/posts/', function ({posts}, {queryParams}) {
|
||||
let page = +queryParams.page || 1;
|
||||
let limit = +queryParams.limit || 15;
|
||||
let {status, staticPages} = queryParams;
|
||||
let query = {};
|
||||
let models;
|
||||
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
if (staticPages === 'false') {
|
||||
query.page = false;
|
||||
}
|
||||
|
||||
if (staticPages === 'true') {
|
||||
query.page = true;
|
||||
}
|
||||
|
||||
models = posts.where(query).models;
|
||||
|
||||
return paginateModelArray('posts', models, page, limit);
|
||||
});
|
||||
|
||||
server.get('/posts/:id/', function ({posts}, {params}) {
|
||||
let {id} = params;
|
||||
|
@ -4,44 +4,48 @@ import {Response} from 'ember-cli-mirage';
|
||||
export function paginatedResponse(modelName) {
|
||||
return function (schema, request) {
|
||||
let page = +request.queryParams.page || 1;
|
||||
let limit = request.queryParams.limit || 15;
|
||||
let pages, next, prev, models;
|
||||
|
||||
let limit = +request.queryParams.limit || 15;
|
||||
let allModels = this.serialize(schema[modelName].all())[modelName];
|
||||
|
||||
if (limit === 'all') {
|
||||
pages = 1;
|
||||
} else {
|
||||
limit = +limit;
|
||||
return paginateModelArray(modelName, allModels, page, limit);
|
||||
};
|
||||
}
|
||||
|
||||
let start = (page - 1) * limit;
|
||||
let end = start + limit;
|
||||
export function paginateModelArray(modelName, allModels, page, limit) {
|
||||
let pages, next, prev, models;
|
||||
|
||||
pages = Math.ceil(allModels.length / limit);
|
||||
models = allModels.slice(start, end);
|
||||
if (limit === 'all') {
|
||||
pages = 1;
|
||||
} else {
|
||||
limit = +limit;
|
||||
|
||||
if (start > 0) {
|
||||
prev = page - 1;
|
||||
}
|
||||
let start = (page - 1) * limit;
|
||||
let end = start + limit;
|
||||
|
||||
if (end < allModels.length) {
|
||||
next = page + 1;
|
||||
}
|
||||
pages = Math.ceil(allModels.length / limit);
|
||||
models = allModels.slice(start, end);
|
||||
|
||||
if (start > 0) {
|
||||
prev = page - 1;
|
||||
}
|
||||
|
||||
return {
|
||||
meta: {
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
pages,
|
||||
total: allModels.length,
|
||||
next: next || null,
|
||||
prev: prev || null
|
||||
}
|
||||
},
|
||||
[modelName]: models || allModels
|
||||
};
|
||||
if (end < allModels.length) {
|
||||
next = page + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
meta: {
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
pages,
|
||||
total: allModels.length,
|
||||
next: next || null,
|
||||
prev: prev || null
|
||||
}
|
||||
},
|
||||
[modelName]: models || allModels
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@
|
||||
"ember-data": "2.11.0",
|
||||
"ember-data-filter": "1.13.0",
|
||||
"ember-export-application-global": "1.1.1",
|
||||
"ember-infinity": "0.2.8",
|
||||
"ember-invoke-action": "1.4.0",
|
||||
"ember-light-table": "1.8.1",
|
||||
"ember-load": "0.0.11",
|
||||
@ -75,6 +76,7 @@
|
||||
"ember-simple-auth": "1.1.0",
|
||||
"ember-sinon": "0.6.0",
|
||||
"ember-sortable": "1.9.1",
|
||||
"ember-test-selectors": "0.2.0",
|
||||
"ember-wormhole": "0.5.1",
|
||||
"emberx-file-input": "1.1.1",
|
||||
"eslint-plugin-ember-suave": "1.0.0",
|
||||
|
@ -33,6 +33,7 @@ describe('Acceptance: Authentication', function () {
|
||||
beforeEach(function () {
|
||||
originalReplaceLocation = windowProxy.replaceLocation;
|
||||
windowProxy.replaceLocation = function (url) {
|
||||
url = url.replace(/^\/ghost\//, '/');
|
||||
visit(url);
|
||||
};
|
||||
|
||||
|
160
ghost/admin/tests/acceptance/content-test.js
Normal file
160
ghost/admin/tests/acceptance/content-test.js
Normal file
@ -0,0 +1,160 @@
|
||||
import {describe, it, beforeEach, afterEach} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import startApp from '../helpers/start-app';
|
||||
import destroyApp from '../helpers/destroy-app';
|
||||
import {
|
||||
invalidateSession,
|
||||
authenticateSession
|
||||
} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import testSelector from 'ember-test-selectors';
|
||||
|
||||
describe('Acceptance: Content', function() {
|
||||
let application;
|
||||
|
||||
beforeEach(function() {
|
||||
application = startApp();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
destroyApp(application);
|
||||
});
|
||||
|
||||
it('redirects to signin when not authenticated', function () {
|
||||
invalidateSession(application);
|
||||
visit('/');
|
||||
|
||||
andThen(function () {
|
||||
expect(currentURL()).to.equal('/signin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as admin', function () {
|
||||
let publishedPost, scheduledPost, draftPost, publishedPage, authorPost;
|
||||
|
||||
beforeEach(function () {
|
||||
let adminRole = server.create('role', {name: 'Administrator'});
|
||||
let admin = server.create('user', {roles: [adminRole]});
|
||||
let editorRole = server.create('role', {name: 'Editor'});
|
||||
let editor = server.create('user', {roles: [editorRole]});
|
||||
|
||||
publishedPost = server.create('post', {authorId: admin.id, status: 'published', title: 'Published Post'});
|
||||
scheduledPost = server.create('post', {authorId: admin.id, status: 'scheduled', title: 'Scheduled Post'});
|
||||
draftPost = server.create('post', {authorId: admin.id, status: 'draft', title: 'Draft Post'});
|
||||
publishedPage = server.create('post', {authorId: admin.id, status: 'published', page: true, title: 'Published Page'});
|
||||
authorPost = server.create('post', {authorId: editor.id, status: 'published', title: 'Editor Published Post'});
|
||||
|
||||
return authenticateSession(application);
|
||||
});
|
||||
|
||||
it('displays and filters posts', function () {
|
||||
visit('/');
|
||||
|
||||
andThen(() => {
|
||||
// All filter is active by default
|
||||
expect(find(testSelector('all-filter-link'))).to.have.class('active');
|
||||
// Not checking request here as it won't be the last request made
|
||||
// Displays all posts + pages
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'all posts count').to.equal(5);
|
||||
});
|
||||
|
||||
click(testSelector('drafts-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// Filter link is highlighted
|
||||
expect(find(testSelector('drafts-filter-link'))).to.have.class('active');
|
||||
// API request is correct
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.status, '"drafts" request status param').to.equal('draft');
|
||||
expect(lastRequest.queryParams.staticPages, '"drafts" request staticPages param').to.equal('false');
|
||||
// Displays draft post
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'drafts count').to.equal(1);
|
||||
expect(find(testSelector('posts-list-item-id', draftPost.id)), 'draft post').to.exist;
|
||||
});
|
||||
|
||||
click(testSelector('published-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// Filter link is highlighted
|
||||
expect(find(testSelector('published-filter-link'))).to.have.class('active');
|
||||
// API request is correct
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.status, '"published" request status param').to.equal('published');
|
||||
expect(lastRequest.queryParams.staticPages, '"published" request staticPages param').to.equal('false');
|
||||
// Displays three published posts + pages
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'published count').to.equal(2);
|
||||
expect(find(testSelector('posts-list-item-id', publishedPost.id)), 'admin published post').to.exist;
|
||||
expect(find(testSelector('posts-list-item-id', authorPost.id)), 'author published post').to.exist;
|
||||
});
|
||||
|
||||
click(testSelector('scheduled-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// Filter link is highlighted
|
||||
expect(find(testSelector('scheduled-filter-link'))).to.have.class('active');
|
||||
// API request is correct
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.status, '"scheduled" request status param').to.equal('scheduled');
|
||||
expect(lastRequest.queryParams.staticPages, '"scheduled" request staticPages param').to.equal('false');
|
||||
// Displays scheduled post
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'scheduled count').to.equal(1);
|
||||
expect(find(testSelector('posts-list-item-id', scheduledPost.id)), 'scheduled post').to.exist;
|
||||
});
|
||||
|
||||
click(testSelector('pages-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// Filter link is highlighted
|
||||
expect(find(testSelector('pages-filter-link'))).to.have.class('active');
|
||||
// API request is correct
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.status, '"pages" request status param').to.equal('all');
|
||||
expect(lastRequest.queryParams.staticPages, '"pages" request staticPages param').to.equal('true');
|
||||
// Displays page
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'pages count').to.equal(1);
|
||||
expect(find(testSelector('posts-list-item-id', publishedPage.id)), 'page post').to.exist;
|
||||
});
|
||||
|
||||
click(testSelector('all-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// API request is correct
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.status, '"all" request status param').to.equal('all');
|
||||
expect(lastRequest.queryParams.staticPages, '"all" request staticPages param').to.equal('all');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as author', function () {
|
||||
let author, authorPost;
|
||||
|
||||
beforeEach(function () {
|
||||
let authorRole = server.create('role', {name: 'Author'});
|
||||
author = server.create('user', {roles: [authorRole]});
|
||||
let adminRole = server.create('role', {name: 'Administrator'});
|
||||
let admin = server.create('user', {roles: [adminRole]});
|
||||
|
||||
// create posts
|
||||
authorPost = server.create('post', {authorId: author.id, status: 'published', title: 'Author Post'});
|
||||
server.create('post', {authorId: admin.id, status: 'scheduled', title: 'Admin Post'});
|
||||
|
||||
return authenticateSession(application);
|
||||
});
|
||||
|
||||
it('only fetches the author\'s posts', function () {
|
||||
visit('/');
|
||||
// trigger a filter request so we can grab the posts API request easily
|
||||
click(testSelector('published-filter-link'));
|
||||
|
||||
andThen(() => {
|
||||
// API request includes author filter
|
||||
let [lastRequest] = server.pretender.handledRequests.slice(-1);
|
||||
expect(lastRequest.queryParams.filter).to.equal(`author:${author.slug}`);
|
||||
|
||||
// only author's post is shown
|
||||
expect(find(testSelector('posts-list-item-id')).length, 'post count').to.equal(1);
|
||||
expect(find(testSelector('posts-list-item-id', authorPost.id)), 'author post').to.exist;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,84 +0,0 @@
|
||||
/* jshint expr:true */
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
beforeEach,
|
||||
afterEach
|
||||
} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import startApp from '../../helpers/start-app';
|
||||
import destroyApp from '../../helpers/destroy-app';
|
||||
import {authenticateSession} from 'ghost-admin/tests/helpers/ember-simple-auth';
|
||||
import {errorOverride, errorReset} from 'ghost-admin/tests/helpers/adapter-error';
|
||||
import {Response} from 'ember-cli-mirage';
|
||||
|
||||
describe('Acceptance: Posts - Post', function() {
|
||||
let application;
|
||||
|
||||
beforeEach(function() {
|
||||
application = startApp();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
destroyApp(application);
|
||||
});
|
||||
|
||||
describe('when logged in', function () {
|
||||
beforeEach(function () {
|
||||
let role = server.create('role', {name: 'Administrator'});
|
||||
server.create('user', {roles: [role]});
|
||||
|
||||
return authenticateSession(application);
|
||||
});
|
||||
|
||||
it('can visit post route', function () {
|
||||
let posts = server.createList('post', 6);
|
||||
|
||||
visit('/');
|
||||
|
||||
andThen(() => {
|
||||
expect(find('.posts-list li').length, 'post list count').to.equal(6);
|
||||
|
||||
// if we're in "desktop" size, we should redirect and highlight
|
||||
if (find('.content-preview:visible').length) {
|
||||
expect(currentURL(), 'currentURL').to.equal(`/${posts[0].id}`);
|
||||
// expect(find('.posts-list li').first().hasClass('active'), 'highlights latest post').to.be.true;
|
||||
expect(find('.posts-list li:nth-child(1) .status span').first().hasClass('scheduled'), 'first post in list is a scheduled one')
|
||||
.to.be.true;
|
||||
expect(find('.posts-list li:nth-child(3) .status span').first().hasClass('draft'), 'third post in list is a draft')
|
||||
.to.be.true;
|
||||
expect(find('.posts-list li:nth-child(5) .status time').first().hasClass('published'), 'fifth post in list is a published one')
|
||||
.to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
// check if we can edit the post
|
||||
click('.post-edit');
|
||||
|
||||
andThen(() => {
|
||||
expect(currentURL(), 'currentURL to editor')
|
||||
.to.equal('/editor/1');
|
||||
});
|
||||
|
||||
// TODO: test the right order of the listes posts
|
||||
// and fix the faker import to ensure correct ordering
|
||||
});
|
||||
|
||||
it('redirects to 404 when post does not exist', function () {
|
||||
server.get('/posts/200/', function () {
|
||||
return new Response(404, {'Content-Type': 'application/json'}, {errors: [{message: 'Post not found.', errorType: 'NotFoundError'}]});
|
||||
});
|
||||
|
||||
errorOverride();
|
||||
|
||||
visit('/200');
|
||||
|
||||
andThen(() => {
|
||||
errorReset();
|
||||
expect(currentPath()).to.equal('error404');
|
||||
expect(currentURL()).to.equal('/200');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -38,7 +38,6 @@ describe('Acceptance: Version Mismatch', function() {
|
||||
|
||||
visit('/');
|
||||
click('.posts-list li:nth-of-type(2) a'); // select second post
|
||||
click('.post-edit'); // preview edit button
|
||||
click('.js-publish-button'); // "Save post"
|
||||
|
||||
andThen(() => {
|
||||
|
@ -1315,7 +1315,7 @@ commander@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928"
|
||||
|
||||
commander@2.3.0, commander@^2.1.0:
|
||||
commander@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
|
||||
|
||||
@ -1325,7 +1325,7 @@ commander@2.8.x:
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commander@^2.5.0, commander@^2.6.0, commander@^2.9.0:
|
||||
commander@^2.1.0, commander@^2.5.0, commander@^2.6.0, commander@^2.9.0:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||
dependencies:
|
||||
@ -1897,7 +1897,7 @@ ember-cli-htmlbars-inline-precompile@0.3.6:
|
||||
ember-cli-htmlbars "^1.0.0"
|
||||
hash-for-dep "^1.0.2"
|
||||
|
||||
ember-cli-htmlbars@1.1.1, ember-cli-htmlbars@^1.0.0, ember-cli-htmlbars@^1.0.10, ember-cli-htmlbars@^1.0.11, ember-cli-htmlbars@^1.0.3, ember-cli-htmlbars@^1.1.0, ember-cli-htmlbars@^1.1.1:
|
||||
ember-cli-htmlbars@1.1.1, ember-cli-htmlbars@^1.0.0, ember-cli-htmlbars@^1.0.1, ember-cli-htmlbars@^1.0.10, ember-cli-htmlbars@^1.0.11, ember-cli-htmlbars@^1.0.3, ember-cli-htmlbars@^1.1.0, ember-cli-htmlbars@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-1.1.1.tgz#8776cf59796dac8f32e8625fc6d1ea45ffa55de1"
|
||||
dependencies:
|
||||
@ -2289,6 +2289,15 @@ ember-in-viewport@2.1.1:
|
||||
ember-cli-babel "^5.1.6"
|
||||
ember-getowner-polyfill "^1.1.1"
|
||||
|
||||
ember-infinity@0.2.8:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/ember-infinity/-/ember-infinity-0.2.8.tgz#813a24d0828446a44d09c21fee5adf371897d8dd"
|
||||
dependencies:
|
||||
ember-cli-babel "^5.1.5"
|
||||
ember-cli-htmlbars "^1.0.1"
|
||||
ember-cli-version-checker "^1.0.2"
|
||||
ember-version-is "0.0.3"
|
||||
|
||||
ember-inflector@^1.9.2, ember-inflector@^1.9.4:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-1.11.0.tgz#99baae18e2bee53cfa97d8db1d739280289a174f"
|
||||
@ -2459,6 +2468,12 @@ ember-test-helpers@^0.6.0-beta.1:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-test-helpers/-/ember-test-helpers-0.6.0.tgz#1f644fd303437ef4d19430a3e18447d57a97cf36"
|
||||
|
||||
ember-test-selectors@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-test-selectors/-/ember-test-selectors-0.2.0.tgz#d0305d01a96e3a0ad6a53f08f2d7b74975d3296e"
|
||||
dependencies:
|
||||
ember-cli-babel "^5.1.7"
|
||||
|
||||
ember-text-measurer@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-text-measurer/-/ember-text-measurer-0.3.3.tgz#0762809a71c2e1f2e60ab00c53c6eb1b63c9f963"
|
||||
@ -2506,6 +2521,12 @@ ember-try@^0.2.6:
|
||||
semver "^5.1.0"
|
||||
sync-exec "^0.6.2"
|
||||
|
||||
ember-version-is@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-version-is/-/ember-version-is-0.0.3.tgz#7d54ec39ed5e03f0df11cf8a5e22dc20b0810b1a"
|
||||
dependencies:
|
||||
ember-cli-babel "^5.0.0"
|
||||
|
||||
ember-weakmap@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-weakmap/-/ember-weakmap-2.0.0.tgz#71c6819a8bfd0b077ae17ca1d9053fc5db06e4ac"
|
||||
@ -5505,7 +5526,19 @@ read@1, read@~1.0.1, read@~1.0.7:
|
||||
dependencies:
|
||||
mute-stream "~0.0.4"
|
||||
|
||||
"readable-stream@1 || 2", readable-stream@^2, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.2.2:
|
||||
"readable-stream@1 || 2", readable-stream@^2, readable-stream@^2.0.2, readable-stream@~2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
|
||||
dependencies:
|
||||
buffer-shims "^1.0.0"
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~1.0.6"
|
||||
string_decoder "~0.10.x"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
|
||||
dependencies:
|
||||
@ -5537,18 +5570,6 @@ readable-stream@~2.0.5:
|
||||
string_decoder "~0.10.x"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@~2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
|
||||
dependencies:
|
||||
buffer-shims "^1.0.0"
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~1.0.6"
|
||||
string_decoder "~0.10.x"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readdir-scoped-modules@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
|
||||
|
Loading…
Reference in New Issue
Block a user