mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
WIP importer modal
no issue
This commit is contained in:
parent
fc291240d5
commit
d5f8a2b59d
80
ghost/admin/app/components/modal-import-content.hbs
Normal file
80
ghost/admin/app/components/modal-import-content.hbs
Normal file
@ -0,0 +1,80 @@
|
||||
<div class="gh-content-import-wrapper">
|
||||
{{#if (eq this.state 'INIT')}}
|
||||
<header class="modal-header" data-test-modal="import-content">
|
||||
<h1>Import content</h1>
|
||||
</header>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'PROCESSING')}}
|
||||
<header class="modal-header icon-center" data-test-modal="import-content">
|
||||
{{svg-jar "import-in-progress" class="gh-import-content-icon"}}
|
||||
<h1>Import in progress</h1>
|
||||
</header>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'ERROR')}}
|
||||
<header class="modal-header" data-test-modal="import-content">
|
||||
<h1>{{this.errorHeader}}</h1>
|
||||
</header>
|
||||
{{/if}}
|
||||
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>
|
||||
{{svg-jar "close"}}
|
||||
<span class="hidden">Close</span>
|
||||
</a>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if (eq this.state 'INIT')}}
|
||||
<ModalImportContent::ContentFileSelect @setFile={{action "setFile"}} />
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'PROCESSING')}}
|
||||
<div class="gh-content-import-resultcontainer">
|
||||
<div class="gh-content-import-result-summary">
|
||||
<p>Your import is being processed, and you'll receive a confirmation email as soon as it's complete. Usually this only takes a few minutes, but larger imports may take longer.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'ERROR')}}
|
||||
<div class="failed flex items-start gh-content-upload-errorcontainer error">
|
||||
<div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
|
||||
<p class="ma0 pa0">{{this.errorMessage}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#if (eq this.state 'INIT')}}
|
||||
<button class="gh-btn" data-test-button="close-import-content" type="button" {{action "closeModal"}}>
|
||||
<span>Close</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'UPLOADING')}}
|
||||
<button class="gh-btn disabled" disabled="disabled" data-test-button="restart-import-content" type="button" {{action "reset"}}>
|
||||
<span>Start over</span>
|
||||
</button>
|
||||
<button class="gh-btn gh-btn-green gh-btn-icon disabled" disabled="disabled" type="button" {{action "upload"}}>
|
||||
<span>{{svg-jar "spinner" class="gh-icon-spinner"}} {{this.runningText}}Uploading</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'PROCESSING')}}
|
||||
<button class="gh-btn gh-btn-black" data-test-button="close-import-content" type="button" {{action "closeModal"}}>
|
||||
<span>Got it</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq this.state 'ERROR')}}
|
||||
{{#if this.showTryAgainButton}}
|
||||
<button class="gh-btn" data-test-button="restart-import-content" type="button" {{action "reset"}}>
|
||||
<span>Try again</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
<button class="gh-btn gh-btn-black" data-test-button="close-import-content" type="button" {{action "closeModal"}}>
|
||||
<span>OK</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
119
ghost/admin/app/components/modal-import-content.js
Normal file
119
ghost/admin/app/components/modal-import-content.js
Normal file
@ -0,0 +1,119 @@
|
||||
import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import {computed} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {
|
||||
isRequestEntityTooLargeError,
|
||||
isUnsupportedMediaTypeError,
|
||||
isVersionMismatchError
|
||||
} from 'ghost-admin/services/ajax';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
ajax: service(),
|
||||
notifications: service(),
|
||||
store: service(),
|
||||
|
||||
state: 'INIT',
|
||||
|
||||
file: null,
|
||||
paramName: 'importfile',
|
||||
importResponse: null,
|
||||
errorMessage: null,
|
||||
errorHeader: null,
|
||||
showTryAgainButton: true,
|
||||
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
config: inject(),
|
||||
|
||||
uploadUrl: computed(function () {
|
||||
return `${ghostPaths().apiRoot}/db/importFile`;
|
||||
}),
|
||||
|
||||
formData: computed('file', function () {
|
||||
let formData = new FormData();
|
||||
|
||||
formData.append(this.paramName, this.file);
|
||||
|
||||
if (this.mappingResult.labels) {
|
||||
this.mappingResult.labels.forEach((label) => {
|
||||
formData.append('labels', label.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.mappingResult.mapping) {
|
||||
let mapping = this.mappingResult.mapping.toJSON();
|
||||
for (let [key, val] of Object.entries(mapping)) {
|
||||
formData.append(`mapping[${key}]`, val);
|
||||
}
|
||||
}
|
||||
|
||||
return formData;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
setFile(file) {
|
||||
this.set('file', file);
|
||||
this.generateRequest();
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.set('errorMessage', null);
|
||||
this.set('errorHeader', null);
|
||||
this.set('file', null);
|
||||
this.set('state', 'INIT');
|
||||
this.set('showTryAgainButton', true);
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
if (this.state !== 'UPLOADING') {
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
|
||||
// noop - we don't want the enter key doing anything
|
||||
confirm() {}
|
||||
},
|
||||
|
||||
generateRequest() {
|
||||
let ajax = this.ajax;
|
||||
let formData = this.formData;
|
||||
let url = this.uploadUrl;
|
||||
|
||||
this.set('state', 'UPLOADING');
|
||||
ajax.post(url, {
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'text'
|
||||
}).then(() => {
|
||||
this.set('state', 'PROCESSING');
|
||||
}).catch((error) => {
|
||||
this._uploadError(error);
|
||||
this.set('state', 'ERROR');
|
||||
});
|
||||
},
|
||||
|
||||
_uploadError(error) {
|
||||
let message;
|
||||
let header = 'Import error';
|
||||
|
||||
if (isVersionMismatchError(error)) {
|
||||
this.notifications.showAPIError(error);
|
||||
}
|
||||
|
||||
if (isUnsupportedMediaTypeError(error)) {
|
||||
message = 'The file type you uploaded is not supported.';
|
||||
} else if (isRequestEntityTooLargeError(error)) {
|
||||
message = 'The file you uploaded was larger than the maximum file size your server allows.';
|
||||
} else {
|
||||
console.error(error); // eslint-disable-line
|
||||
message = 'Something went wrong :(';
|
||||
}
|
||||
|
||||
this.set('errorMessage', message);
|
||||
this.set('errorHeader', header);
|
||||
}
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
{{#if this.error}}
|
||||
<div class="failed flex items-start gh-content-upload-errorcontainer error">
|
||||
<div class="mr2">{{svg-jar "warning" class="nudge-top--2 w4 h4 fill-red"}}</div>
|
||||
<p class="ma0 pa0">{{this.error.message}}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="upload-form br3">
|
||||
<section class="gh-image-uploader gh-content-import-uploader {{this.dragClass}}"
|
||||
{{on "drop" this.drop}}
|
||||
{{on "dragover" this.dragOver}}
|
||||
{{on "dragleave" this.dragLeave}}
|
||||
>
|
||||
<GhFileInput @multiple={{false}} @alt={{this.labelText}} @action={{this.fileSelected}} @accept={{this.accept}} data-test-fileinput="content-import">
|
||||
<div class="flex flex-column items-center">
|
||||
{{svg-jar "upload"}}
|
||||
<div class="description">{{this.labelText}}</div>
|
||||
</div>
|
||||
</GhFileInput>
|
||||
</section>
|
||||
</div>
|
@ -0,0 +1,85 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {UnsupportedMediaTypeError} from 'ghost-admin/services/ajax';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class ContentFileSelect extends Component {
|
||||
labelText = 'Select or drop a JSON or zip file';
|
||||
|
||||
@tracked error = null;
|
||||
@tracked dragClass = null;
|
||||
|
||||
/*
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
assert(this.args.setFile);
|
||||
}
|
||||
*/
|
||||
|
||||
@action
|
||||
fileSelected(fileList) {
|
||||
console.log('File list: ' + JSON.stringify(fileList));
|
||||
|
||||
let [file] = Array.from(fileList);
|
||||
|
||||
console.log('Validating file: ' + JSON.stringify(file));
|
||||
|
||||
try {
|
||||
this._validateFileType(file);
|
||||
this.error = null;
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Setting file to: ' + JSON.stringify(file));
|
||||
|
||||
this.args.setFile(file);
|
||||
}
|
||||
|
||||
@action
|
||||
dragOver(event) {
|
||||
if (!event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is needed to work around inconsistencies with dropping files
|
||||
// from Chrome's downloads bar
|
||||
if (navigator.userAgent.indexOf('Chrome') > -1) {
|
||||
let eA = event.dataTransfer.effectAllowed;
|
||||
event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy';
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.dragClass = '-drag-over';
|
||||
}
|
||||
|
||||
@action
|
||||
dragLeave(event) {
|
||||
event.preventDefault();
|
||||
this.dragClass = null;
|
||||
}
|
||||
|
||||
@action
|
||||
drop(event) {
|
||||
event.preventDefault();
|
||||
this.dragClass = null;
|
||||
if (event.dataTransfer.files) {
|
||||
this.fileSelected(event.dataTransfer.files);
|
||||
}
|
||||
}
|
||||
|
||||
_validateFileType(file) {
|
||||
let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
|
||||
|
||||
if (extension.toLowerCase() !== 'json' || extension.toLowerCase() !== 'zip') {
|
||||
throw new UnsupportedMediaTypeError({
|
||||
message: 'The file type you uploaded is not supported'
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import {inject as service} from '@ember/service';
|
||||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
||||
import Controller from '@ember/controller';
|
||||
import DeleteAllModal from '../../components/settings/labs/delete-all-content-modal';
|
||||
import ImportContentModal from '../../components/modal-import-content';
|
||||
import RSVP from 'rsvp';
|
||||
import config from 'ghost-admin/config/environment';
|
||||
import {
|
||||
@ -139,6 +140,11 @@ export default class LabsController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
importContent() {
|
||||
return this.modals.open(ImportContentModal);
|
||||
}
|
||||
|
||||
@action
|
||||
downloadFile(endpoint) {
|
||||
this.utils.downloadFile(this.ghostPaths.url.api(endpoint));
|
||||
|
13
ghost/admin/app/controllers/settings/labs/import.js
Normal file
13
ghost/admin/app/controllers/settings/labs/import.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Controller, {inject as controller} from '@ember/controller';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class ImportController extends Controller {
|
||||
@service router;
|
||||
@controller labs;
|
||||
|
||||
@action
|
||||
close() {
|
||||
this.router.transitionTo('labs');
|
||||
}
|
||||
}
|
@ -103,6 +103,7 @@ Router.map(function () {
|
||||
|
||||
this.route('settings.navigation', {path: '/settings/navigation'});
|
||||
this.route('settings.labs', {path: '/settings/labs'});
|
||||
this.route('settings.labs.import', {path: '/settings/labs/import'});
|
||||
|
||||
this.route('members', function () {
|
||||
this.route('import');
|
||||
|
3
ghost/admin/app/routes/settings/labs/import.js
Normal file
3
ghost/admin/app/routes/settings/labs/import.js
Normal file
@ -0,0 +1,3 @@
|
||||
import AdminRoute from 'ghost-admin/routes/admin';
|
||||
|
||||
export default class LabsImportRoute extends AdminRoute {}
|
@ -79,4 +79,434 @@
|
||||
bottom: 1px;
|
||||
width: 18px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Import modal
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.fullscreen-modal-import-content {
|
||||
max-width: unset !important;
|
||||
}
|
||||
|
||||
.gh-content-import-wrapper {
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
.gh-content-import-wrapper .gh-btn.disabled,
|
||||
.gh-content-import-wrapper .gh-btn.disabled:hover {
|
||||
cursor: auto !important;
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
|
||||
.gh-content-import-wrapper .gh-btn.disabled span,
|
||||
.gh-content-import-wrapper .gh-btn.disabled span:hover {
|
||||
cursor: auto !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gh-content-import-wrapper .gh-token-input .ember-power-select-trigger[aria-disabled=true],
|
||||
.gh-content-import-wrapper .gh-token-input .ember-power-select-trigger-multiple-input:disabled {
|
||||
background: var(--whitegrey-l2);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.gh-content-import-wrapper,
|
||||
.gh-content-import-wrapper.wide {
|
||||
width: calc(100vw - 128px);
|
||||
}
|
||||
}
|
||||
|
||||
.gh-content-import-uploader {
|
||||
width: 100%;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.gh-content-import-uploader svg {
|
||||
width: 3.2rem;
|
||||
height: 3.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.gh-content-import-uploader svg path {
|
||||
stroke: var(--midlightgrey);
|
||||
}
|
||||
|
||||
.gh-content-import-uploader:hover svg path {
|
||||
stroke: var(--midgrey-l1);
|
||||
}
|
||||
|
||||
.gh-content-import-uploader .description {
|
||||
color: var(--midgrey);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gh-content-import-uploader:hover .description {
|
||||
color: var(--midgrey-d2);
|
||||
}
|
||||
|
||||
.gh-content-import-file {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.gh-content-import-spinner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 182px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
|
||||
.gh-content-import-spinner .gh-loading-content {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.gh-content-import-spinner .description {
|
||||
padding-top: 46px;
|
||||
}
|
||||
|
||||
.gh-content-upload-errorcontainer {
|
||||
border: 1px solid var(--whitegrey);
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
margin-bottom: 24px;
|
||||
color: var(--middarkgrey);
|
||||
}
|
||||
|
||||
.gh-content-upload-errorcontainer.warning {
|
||||
border-left: 4px solid var(--yellow);
|
||||
}
|
||||
|
||||
|
||||
.gh-content-upload-errorcontainer.warning p a {
|
||||
color: color-mod(var(--yellow) l(-12%));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.gh-content-upload-errorcontainer.error {
|
||||
border-left: 4px solid var(--red);
|
||||
}
|
||||
|
||||
.gh-content-upload-errorcontainer.error p a {
|
||||
color: var(--red);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.gh-content-import-errormessage {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
p.gh-content-import-errorcontext {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.3em;
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.gh-content-import-mapping .error {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.gh-content-import-mappingwrapper.error {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gh-content-import-mappingwrapper.error::before {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 1px solid red;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gh-content-import-scrollarea {
|
||||
position: relative;
|
||||
max-height: calc(100vh - 350px - 12vw);
|
||||
overflow-y: scroll;
|
||||
margin: 0 -32px;
|
||||
padding: 0 32px;
|
||||
background:
|
||||
/* Shadow covers */
|
||||
linear-gradient(var(--white) 30%, rgba(255,255,255,0)),
|
||||
linear-gradient(rgba(255,255,255,0), var(--white) 70%) 0 100%,
|
||||
|
||||
/* Shadows */
|
||||
/* radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.12), rgba(0,0,0,0)) -64px 0,
|
||||
radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.12), rgba(0,0,0,0)) -64px 100%; */
|
||||
linear-gradient(rgba(0,0,0,0.08), rgba(0,0,0,0)),
|
||||
linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.08)) 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-color: var(--white);
|
||||
background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
|
||||
|
||||
/* Opera doesn't support this in the shorthand */
|
||||
background-attachment: local, local, scroll, scroll;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.gh-content-import-errorheading {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.55em;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
p.gh-content-import-errordetailtext {
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.4em;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-content-import-errordetailtext:first-of-type {
|
||||
border-top: 1px solid var(--lightgrey);
|
||||
padding-top: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.gh-content-import-errordetailtext:not(:last-of-type) {
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.gh-content-import-table {
|
||||
position: relative;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.gh-content-import-table::before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
top: 0;
|
||||
left: -33px;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 32px;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.gh-content-import-table::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: "";
|
||||
top: 0;
|
||||
right: -32px;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 32px;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.gh-content-import-table th {
|
||||
padding: 3px 8px;
|
||||
background: color-mod(var(--darkgrey) a(5%) s(+50%));
|
||||
border-left: 1px solid var(--content-import-table-border);
|
||||
border-top: 1px solid var(--content-import-table-outline);
|
||||
border-bottom: 1px solid var(--content-import-table-border);
|
||||
}
|
||||
|
||||
.gh-content-import-table tr th:first-of-type {
|
||||
border-left: 1px solid var(--content-import-table-outline);
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.gh-content-import-table tr th:last-of-type {
|
||||
border-right: 1px solid var(--content-import-table-outline);
|
||||
}
|
||||
|
||||
.gh-content-import-table td.empty-cell {
|
||||
background: color-mod(var(--darkgrey) a(3%) s(+50%));
|
||||
}
|
||||
|
||||
.gh-content-import-table td {
|
||||
padding: 7px 8px 6px;
|
||||
border-left: 1px solid var(--content-import-table-border);
|
||||
border-bottom: 1px solid var(--content-import-table-border);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.gh-content-import-table tr td:first-of-type {
|
||||
border-left: 1px solid var(--content-import-table-outline);
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.gh-content-import-table tr td:last-of-type {
|
||||
padding: 0;
|
||||
border-right: 1px solid var(--content-import-table-outline);
|
||||
}
|
||||
|
||||
.gh-content-import-table tr:last-of-type td {
|
||||
border-bottom: 1px solid var(--content-import-table-outline);
|
||||
}
|
||||
|
||||
.gh-content-import-table td span,
|
||||
.gh-content-import-table th span {
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.gh-content-import-datanav {
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.01), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
p.gh-content-import-errordetail {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.4em;
|
||||
margin: 10px 0 0 24px;
|
||||
}
|
||||
|
||||
p.gh-content-import-errordetail:first-of-type {
|
||||
border-top: 1px solid var(--whitegrey);
|
||||
padding-top: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.gh-import-content-select {
|
||||
height: auto;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.gh-import-content-select select {
|
||||
height: 34px;
|
||||
border: none;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1em;
|
||||
padding: 4px 4px 4px 8px;
|
||||
background: none;
|
||||
color: var(--middarkgrey);
|
||||
font-weight: 600;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.gh-import-content-select select option {
|
||||
font-weight: 400;
|
||||
color: var(--darkgrey);
|
||||
}
|
||||
|
||||
.gh-import-content-select select:focus {
|
||||
background: none;
|
||||
color: var(--middarkgrey);
|
||||
}
|
||||
|
||||
.gh-import-content-select.unmapped select,
|
||||
.gh-import-content-select.unmapped select:focus {
|
||||
color: var(--midlightgrey);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.gh-import-content-select svg {
|
||||
right: 9px;
|
||||
}
|
||||
|
||||
.gh-content-import-table th.table-cell-field,
|
||||
.gh-content-import-table td.table-cell-field,
|
||||
.gh-content-import-table th.table-cell-data,
|
||||
.gh-content-import-table td.table-cell-data {
|
||||
max-width: 180px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.gh-content-import-resultcontainer {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.gh-content-import-result-summary {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.gh-content-import-result-summary h2 {
|
||||
font-size: 3.6rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.gh-content-import-result-summary p {
|
||||
color: var(--darkgrey);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.6em;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.gh-content-import-result-summary p strong {
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.gh-content-import-errorlist {
|
||||
width: 100%;
|
||||
margin: 8px 0 28px;
|
||||
}
|
||||
|
||||
.gh-content-import-errorlist h4 {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid var(--whitegrey);
|
||||
padding-bottom: 8px;
|
||||
margin-top: 0px;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-content-import-errorlist ul li {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: var(--midlightgrey-d2);
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.gh-content-import-resultcontainer hr {
|
||||
margin: 24px -32px;
|
||||
border-color: var(--whitegrey);
|
||||
}
|
||||
|
||||
.gh-content-import-nodata span {
|
||||
display: flex;
|
||||
min-height: 144px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--midgrey);
|
||||
}
|
||||
|
||||
.gh-content-import-icon-content path,
|
||||
.gh-content-import-icon-content circle {
|
||||
stroke-width: 0.85px;
|
||||
}
|
||||
|
||||
.gh-content-import-icon-confetti {
|
||||
color: var(--pink);
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.gh-content-import-icon-confetti path,
|
||||
.gh-content-import-icon-confetti circle,
|
||||
.gh-content-import-icon-confetti ellipse {
|
||||
stroke-width: 0.85px;
|
||||
}
|
||||
|
||||
.gh-import-content-icon {
|
||||
color: var(--darkgrey);
|
||||
width: 54px !important;
|
||||
height: 54px !important;
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.gh-import-content-icon * {
|
||||
stroke-width: 0.8px !important;
|
||||
}
|
||||
|
||||
/* Fixing Firefox's select padding */
|
||||
@-moz-document url-prefix() {
|
||||
.gh-import-content-select select {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
@ -23,44 +23,13 @@
|
||||
<div class="gh-expandable-header">
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Import content</h4>
|
||||
<p class="gh-expandable-description">Import posts from another Ghost installation</p>
|
||||
<p class="gh-expandable-description">Import posts from a JSON or zip file</p>
|
||||
</div>
|
||||
<form id="settings-import" enctype="multipart/form-data">
|
||||
<GhFileUpload
|
||||
@id="importfile"
|
||||
@classNames="flex"
|
||||
@uploadButtonText={{this.uploadButtonText}}
|
||||
@onUpload={{action "onUpload"}}
|
||||
@acceptEncoding={{this.importMimeType}}
|
||||
data-test-file-input="import"
|
||||
/>
|
||||
</form>
|
||||
<LinkTo @route="settings.labs.import" class="mr2" data-test-link="import-content">
|
||||
<span>Open Importer</span>
|
||||
</LinkTo>
|
||||
<button type="button" class="gh-btn" {{action "importContent"}}><span>Open Importer</span></button>
|
||||
</div>
|
||||
{{#if this.importErrors}}
|
||||
<div class="gh-import-errors {{if this.importSuccessful "gh-import-errors-alert"}}" data-test-import-errors>
|
||||
<div class="gh-import-errors-title">
|
||||
{{#if this.importSuccessful}}
|
||||
Import successful with warnings
|
||||
{{else}}
|
||||
Import failed
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#each this.importErrors as |error|}}
|
||||
<div class="gh-import-error" data-test-import-error>
|
||||
<p class="gh-import-error-message" data-test-import-error-message>
|
||||
{{#if error.help}}{{error.help}}: {{/if}}{{error.message}}
|
||||
</p>
|
||||
|
||||
{{#if error.context}}
|
||||
<div class="gh-import-error-entry" data-test-import-error-context>
|
||||
<pre>{{error.context}}</pre>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="gh-expandable-block">
|
||||
|
4
ghost/admin/app/templates/settings/labs/import.hbs
Normal file
4
ghost/admin/app/templates/settings/labs/import.hbs
Normal file
@ -0,0 +1,4 @@
|
||||
<GhFullscreenModal @modal="import-content"
|
||||
@confirm={{this.close}}
|
||||
@close={{this.close}}
|
||||
@modifier="action import-content" />
|
Loading…
Reference in New Issue
Block a user