mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added export button to posts page with placeholder endpoint (#16456)
fixes https://github.com/TryGhost/Team/issues/2780 refs https://github.com/TryGhost/Team/issues/2781 Adds an export button to the posts page in admin (behind feature flag). It downloads a placeholder CSV via a real endpoint (`/posts/export`).
This commit is contained in:
parent
2096773310
commit
0cc3164b25
8 changed files with 117 additions and 2 deletions
|
@ -1,4 +1,5 @@
|
|||
import Controller from '@ember/controller';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import {DEFAULT_QUERY_PARAMS} from 'ghost-admin/helpers/reset-query-params';
|
||||
import {action} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
|
@ -52,6 +53,7 @@ export default class PostsController extends Controller {
|
|||
@service router;
|
||||
@service session;
|
||||
@service store;
|
||||
@service utils;
|
||||
|
||||
@inject config;
|
||||
|
||||
|
@ -84,6 +86,10 @@ export default class PostsController extends Controller {
|
|||
return this.model;
|
||||
}
|
||||
|
||||
get totalPosts() {
|
||||
return this.model.meta?.pagination?.total ?? 0;
|
||||
}
|
||||
|
||||
get showingAll() {
|
||||
const {type, author, tag, visibility} = this;
|
||||
|
||||
|
@ -136,6 +142,17 @@ export default class PostsController extends Controller {
|
|||
return authors.findBy('slug', author) || {slug: '!unknown'};
|
||||
}
|
||||
|
||||
@action
|
||||
exportData() {
|
||||
let exportUrl = ghostPaths().url.api('posts/export');
|
||||
// the filter and order params are set from the route to the controller via the infinity model
|
||||
// we can retrieve these via the extraParams of the infinity model
|
||||
let downloadParams = new URLSearchParams(this.model.extraParams);
|
||||
downloadParams.set('limit', 'all');
|
||||
|
||||
this.utils.downloadFile(`${exportUrl}?${downloadParams.toString()}`);
|
||||
}
|
||||
|
||||
@action
|
||||
changeType(type) {
|
||||
this.type = type.value;
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
@import "layouts/whatsnew.css";
|
||||
@import "layouts/tags.css";
|
||||
@import "layouts/members.css";
|
||||
@import "layouts/posts.css";
|
||||
@import "layouts/member-activity.css";
|
||||
@import "layouts/error.css";
|
||||
@import "layouts/apps.css";
|
||||
|
|
10
ghost/admin/app/styles/layouts/posts.css
Normal file
10
ghost/admin/app/styles/layouts/posts.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
.gh-post-actions-menu {
|
||||
top: calc(100% + 6px);
|
||||
left: auto;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.gh-post-actions-menu.fade-out {
|
||||
animation-duration: .001s;
|
||||
pointer-events: none;
|
||||
}
|
|
@ -22,7 +22,46 @@
|
|||
@onOrderChange={{this.changeOrder}}
|
||||
/>
|
||||
|
||||
<LinkTo @route="editor.new" @model="post" class="gh-btn gh-btn-primary view-actions-top-row" data-test-new-post-button={{true}}><span>New post</span></LinkTo>
|
||||
<div class="view-actions-top-row">
|
||||
{{#if (feature 'makingItRain')}}
|
||||
<span class="dropdown posts-actions-dropdown">
|
||||
<GhDropdownButton
|
||||
@dropdownName="posts-actions-menu"
|
||||
@classNames="gh-btn gh-btn-icon icon-only gh-btn-action-icon"
|
||||
@title="Posts Actions"
|
||||
data-test-button="posts-actions"
|
||||
>
|
||||
<span>
|
||||
{{svg-jar "settings"}}
|
||||
<span class="hidden">Actions</span>
|
||||
</span>
|
||||
</GhDropdownButton>
|
||||
<GhDropdown
|
||||
@name="posts-actions-menu"
|
||||
@tagName="ul"
|
||||
@classNames="gh-post-actions-menu dropdown-menu dropdown-triangle-top-right"
|
||||
>
|
||||
<li class="{{if this.totalPosts "" "disabled"}}">
|
||||
{{#if this.totalPosts}}
|
||||
<button class="mr2" type="button" {{on "click" this.exportData}} data-test-button="export-posts">
|
||||
{{#if this.showingAll}}
|
||||
<span>Export all posts</span>
|
||||
{{else}}
|
||||
<span>Export selected posts ({{this.totalPosts}})</span>
|
||||
{{/if}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button class="mr2" disabled="true" type="button" data-test-button="export-posts">
|
||||
<span>Export selected posts (0)</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</li>
|
||||
</GhDropdown>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<LinkTo @route="editor.new" @model="post" class="gh-btn gh-btn-primary" data-test-new-post-button={{true}}><span>New post</span></LinkTo>
|
||||
</div>
|
||||
</section>
|
||||
</GhCanvasHeader>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ const allowedIncludes = [
|
|||
'email',
|
||||
'tiers',
|
||||
'newsletter',
|
||||
'count.conversions',
|
||||
'count.conversions',
|
||||
'count.signups',
|
||||
'count.paid_conversions',
|
||||
'count.clicks',
|
||||
|
@ -57,6 +57,35 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
|
||||
exportCSV: {
|
||||
options: [
|
||||
'limit',
|
||||
'filter',
|
||||
'order'
|
||||
],
|
||||
headers: {
|
||||
disposition: {
|
||||
type: 'csv',
|
||||
value() {
|
||||
const datetime = (new Date()).toJSON().substring(0, 10);
|
||||
return `posts.${datetime}.csv`;
|
||||
}
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: 'plain'
|
||||
},
|
||||
permissions: {
|
||||
method: 'browse'
|
||||
},
|
||||
validation: {},
|
||||
async query(frame) {
|
||||
return {
|
||||
data: await postsService.export(frame)
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
read: {
|
||||
options: [
|
||||
'include',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output:posts');
|
||||
const mappers = require('./mappers');
|
||||
const membersService = require('../../../../../services/members');
|
||||
const papaparse = require('papaparse');
|
||||
|
||||
module.exports = {
|
||||
async all(models, apiConfig, frame) {
|
||||
|
@ -32,5 +33,9 @@ module.exports = {
|
|||
frame.response = {
|
||||
posts: [post]
|
||||
};
|
||||
},
|
||||
|
||||
exportCSV(models, apiConfig, frame) {
|
||||
frame.response = papaparse.unparse(models.data);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -26,6 +26,8 @@ module.exports = function apiRoutes() {
|
|||
|
||||
// ## Posts
|
||||
router.get('/posts', mw.authAdminApi, http(api.posts.browse));
|
||||
router.get('/posts/export', mw.authAdminApi, http(api.posts.exportCSV));
|
||||
|
||||
router.post('/posts', mw.authAdminApi, http(api.posts.add));
|
||||
router.get('/posts/:id', mw.authAdminApi, http(api.posts.read));
|
||||
router.get('/posts/slug/:slug', mw.authAdminApi, http(api.posts.read));
|
||||
|
|
|
@ -57,6 +57,18 @@ class PostsService {
|
|||
return model;
|
||||
}
|
||||
|
||||
async export() {
|
||||
// Placeholder implementation
|
||||
return [
|
||||
{
|
||||
title: 'Example',
|
||||
url: 'https://example.com',
|
||||
author: 'Jamie Larson',
|
||||
status: 'published'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async getProductsFromVisibilityFilter(visibilityFilter) {
|
||||
try {
|
||||
const allProducts = await this.models.Product.findAll();
|
||||
|
|
Loading…
Add table
Reference in a new issue