mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 09:03:12 +03:00
Added ability to install free themes directly from GitHub (#1837)
refs https://github.com/TryGhost/Ghost/issues/12608 requires https://github.com/TryGhost/Ghost/pull/12635 - adds `/settings/themes/install` route with `source` and `ref` query params that match the API. Shows a confirmation modal when accessed asking to confirm installation and activation - does not allow Casper to be installed - warns if installing the theme will overwrite an existing one - follows similar process to zip uploads for error handling - adds install/preview links for Massively in the free themes showcase Co-authored-by: Sanne de Vries <sannedv@protonmail.com>
This commit is contained in:
parent
4771d4fd88
commit
5a8753fb8f
@ -14,15 +14,15 @@
|
||||
<div class="apps-configured">
|
||||
{{!--Delete--}}
|
||||
{{#if theme.isDeletable}}
|
||||
<a href="#" {{action this.deleteTheme theme}} disabled={{theme.active}} class="apps-configured-action red-hover red-bg-hover" data-test-theme-delete-button>Delete</a>
|
||||
<a href="#" {{action this.deleteTheme theme}} disabled={{theme.active}} class="apps-configured-action darkgrey red-hover red-bg-hover" data-test-theme-delete-button>Delete</a>
|
||||
{{/if}}
|
||||
{{!--Download--}}
|
||||
<a href="#" {{action this.downloadTheme theme}} class="apps-configured-action darkgrey-hover blue-bg-hover" data-test-theme-download-button>Download</a>
|
||||
<a href="#" {{action this.downloadTheme theme}} class="apps-configured-action darkgrey darkgrey-hover lightgrey-bg-hover" data-test-theme-download-button>Download</a>
|
||||
{{!--Active Label / Activate Button--}}
|
||||
{{#if theme.active}}
|
||||
<span class="gh-badge gh-badge-black apps-configured-action" data-test-theme-badge>Active</span>
|
||||
{{else}}
|
||||
<a href="#" {{action this.activateTheme theme.model}} class="apps-configured-action apps-configured-action-activate green-hover green-bg-hover" data-test-theme-activate-button>
|
||||
<a href="#" {{action this.activateTheme theme.model}} class="apps-configured-action darkgrey apps-configured-action-activate green-hover green-bg-hover" data-test-theme-activate-button>
|
||||
Activate
|
||||
</a>
|
||||
{{/if}}
|
||||
|
142
ghost/admin/app/components/modal-install-theme.hbs
Normal file
142
ghost/admin/app/components/modal-install-theme.hbs
Normal file
@ -0,0 +1,142 @@
|
||||
<div class="theme-validation-container" {{did-update this.reset @model}}>
|
||||
<header class="modal-header" data-test-modal="install-theme">
|
||||
<h1>
|
||||
{{#if this.installSuccess}}
|
||||
{{#if this.hasWarningsOrErrors}}
|
||||
Install successful with {{#if this.validationErrors}}errors{{else}}warnings{{/if}}
|
||||
{{else}}
|
||||
Install successful!
|
||||
{{/if}}
|
||||
{{else if this.hasWarningsOrErrors}}
|
||||
Invalid theme
|
||||
{{else}}
|
||||
Install theme
|
||||
{{/if}}
|
||||
</h1>
|
||||
</header>
|
||||
<button type="button" class="close" title="Close" {{on "click" this.close}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
{{#if this.isReady}}
|
||||
<p>
|
||||
Are you sure you want to install <strong>{{this.themeName}}</strong> from
|
||||
<a href="https://github.com/{{this.model.ref}}" target="_blank" rel="noopener noreferrer">https://github.com/{{this.model.ref}}</a>?
|
||||
</p>
|
||||
{{#if this.willOverwriteExisting}}
|
||||
<p>
|
||||
This will overwrite your existing version of {{this.themeName}}{{if this.willOverwriteExisting.active " which is your active theme"}}.
|
||||
Any custom changes will be lost.
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.willOverwriteDefault}}
|
||||
<p>
|
||||
Sorry, the default Casper theme cannot be overwritten.<br>
|
||||
If you wish to make changes please download the theme and upload a renamed zip file.
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installSuccess}}
|
||||
{{#if this.hasWarningsOrErrors}}
|
||||
<p>
|
||||
The theme <strong>"{{this.themeName}}"</strong> was installed successfully but we detected some {{if this.validationErrors "errors" "warnings"}}.
|
||||
{{#unless this.theme.active}}
|
||||
You are still able to activate and use the theme but it is recommended to fix these {{if this.validationErrors "errors" "warnings"}} before you do so.
|
||||
{{/unless}}
|
||||
</p>
|
||||
{{else}}
|
||||
{{!-- Installed with no errors --}}
|
||||
<p>The theme <strong>"{{this.themeName}}"</strong> was installed successfully. {{unless this.theme.active "Do you want to activate it now?"}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installError}}
|
||||
{{!-- Outright failure - not found, not a theme, server error, etc --}}
|
||||
<p>{{this.themeName}} failed to install.</p>
|
||||
<p class="error"><strong class="response">{{this.installError}}</strong></p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.installFailure}}
|
||||
{{!-- Invalid theme --}}
|
||||
<p>This theme is invalid and cannot be activated. Contact the theme developer.</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.fatalValidationErrors}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Fatal Errors</h2>
|
||||
<p class="mb2">Must-fix to activate theme</p>
|
||||
</div>
|
||||
|
||||
<ul class="pa0">
|
||||
{{#each this.fatalValidationErrors as |error|}}
|
||||
<li class="theme-validation-item theme-fatal-error">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.validationErrors}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Errors</h2>
|
||||
<p class="mb2">Highly recommended to fix, functionality <strong>could</strong> be restricted</p>
|
||||
</div>
|
||||
<ul class="pa0">
|
||||
{{#each this.validationErrors as |error|}}
|
||||
<li class="theme-validation-item theme-error">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.validationWarnings}}
|
||||
<div>
|
||||
<h2 class="mb0 mt4 f5 fw6">Warnings</h2>
|
||||
</div>
|
||||
<ul class="pa0">
|
||||
{{#each this.validationWarnings as |error|}}
|
||||
<li class="theme-validation-item theme-warning">
|
||||
<GhThemeErrorLi @error={{error}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="flex items-center justify-between {{if this.hasActionButton "flex-auto"}}">
|
||||
<button type="button" class="gh-btn" {{on "click" this.close}} data-test-button="cancel">
|
||||
<span>{{#if (or this.installSuccess this.installFailure)}}Close{{else}}Cancel{{/if}}</span>
|
||||
</button>
|
||||
|
||||
{{#if this.hasActionButton}}
|
||||
<div class="flex items-center">
|
||||
{{#if this.shouldShowInstall}}
|
||||
<GhTaskButton
|
||||
@task={{this.installTask}}
|
||||
@type="button"
|
||||
@class="gh-btn gh-btn-icon gh-btn-green"
|
||||
@buttonText={{if this.willOverwriteExisting "Overwrite" "Install"}}
|
||||
@runningText="Installing"
|
||||
@successText="Installed"
|
||||
data-test-button="install"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.shouldShowActivate}}
|
||||
<GhTaskButton
|
||||
@task={{this.activateTask}}
|
||||
@type="button"
|
||||
@class="gh-btn gh-btn-icon gh-btn-green"
|
||||
@buttonText="Activate"
|
||||
@runningText="Activating"
|
||||
data-test-button="activate"
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
157
ghost/admin/app/components/modal-install-theme.js
Normal file
157
ghost/admin/app/components/modal-install-theme.js
Normal file
@ -0,0 +1,157 @@
|
||||
import ModalBase from 'ghost-admin/components/modal-base';
|
||||
import classic from 'ember-classic-decorator';
|
||||
import {action} from '@ember/object';
|
||||
import {isThemeValidationError} from 'ghost-admin/services/ajax';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency-decorators';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
// TODO: update modals to work fully with Glimmer components
|
||||
@classic
|
||||
export default class ModalInstallThemeComponent extends ModalBase {
|
||||
@service ajax;
|
||||
@service ghostPaths;
|
||||
@service store;
|
||||
|
||||
@tracked model;
|
||||
@tracked theme;
|
||||
@tracked installError = '';
|
||||
@tracked validationWarnings = [];
|
||||
@tracked validationErrors = [];
|
||||
@tracked fatalValidationErrors = [];
|
||||
|
||||
get themeName() {
|
||||
return this.model.ref.split('/')[1];
|
||||
}
|
||||
|
||||
get currentThemeNames() {
|
||||
return this.model.themes.mapBy('name');
|
||||
}
|
||||
|
||||
get willOverwriteDefault() {
|
||||
return this.themeName.toLowerCase() === 'casper';
|
||||
}
|
||||
|
||||
get willOverwriteExisting() {
|
||||
return this.model.themes.findBy('name', this.themeName.toLowerCase());
|
||||
}
|
||||
|
||||
get installSuccess() {
|
||||
return !!this.theme;
|
||||
}
|
||||
|
||||
get installFailure() {
|
||||
return !this.installSuccess && (this.validationErrors.length || this.fatalValidationErrors.length);
|
||||
}
|
||||
|
||||
get isReady() {
|
||||
return !this.installSuccess && !this.installError && !this.installFailure;
|
||||
}
|
||||
|
||||
get hasWarningsOrErrors() {
|
||||
return this.validationWarnings.length > 0 || this.validationErrors.length > 0;
|
||||
}
|
||||
|
||||
get shouldShowInstall() {
|
||||
return !this.installSuccess && !this.installFailure && !this.willOverwriteDefault;
|
||||
}
|
||||
|
||||
get shouldShowActivate() {
|
||||
return this.installSuccess && !this.theme.active;
|
||||
}
|
||||
|
||||
get hasActionButton() {
|
||||
return this.shouldShowInstall || this.shouldShowActivate;
|
||||
}
|
||||
|
||||
@action
|
||||
close() {
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
@action
|
||||
reset() {
|
||||
this.theme = null;
|
||||
this.resetErrors();
|
||||
}
|
||||
|
||||
actions = {
|
||||
confirm() {
|
||||
// noop - needed to override ModalBase.actions.confirm
|
||||
},
|
||||
|
||||
// needed because ModalBase uses .send() for keyboard events
|
||||
closeModal() {
|
||||
this.closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*installTask() {
|
||||
try {
|
||||
const url = this.ghostPaths.url.api('themes/install') + `?source=github&ref=${this.model.ref}`;
|
||||
const result = yield this.ajax.post(url);
|
||||
|
||||
this.installError = '';
|
||||
|
||||
if (result.themes) {
|
||||
// show theme in list immediately
|
||||
this.store.pushPayload(result);
|
||||
|
||||
this.theme = this.store.peekRecord('theme', result.themes[0].name);
|
||||
|
||||
this.validationWarnings = this.theme.warnings || [];
|
||||
this.validationErrors = this.theme.errors || [];
|
||||
this.fatalValidationErrors = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
if (isThemeValidationError(error)) {
|
||||
this.resetErrors();
|
||||
|
||||
let errors = error.payload.errors[0].details.errors;
|
||||
let fatalErrors = [];
|
||||
let normalErrors = [];
|
||||
|
||||
// to have a proper grouping of fatal errors and none fatal, we need to check
|
||||
// our errors for the fatal property
|
||||
if (errors && errors.length > 0) {
|
||||
for (let i = 0; i < errors.length; i += 1) {
|
||||
if (errors[i].fatal) {
|
||||
fatalErrors.push(errors[i]);
|
||||
} else {
|
||||
normalErrors.push(errors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.fatalValidationErrors = fatalErrors;
|
||||
this.validationErrors = normalErrors;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error.payload?.errors) {
|
||||
this.installError = error.payload.errors[0].message;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.installError = error.message;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*activateTask() {
|
||||
yield this.theme.activate();
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
resetErrors() {
|
||||
this.installError = '';
|
||||
this.validationWarnings = [];
|
||||
this.validationErrors = [];
|
||||
this.fatalValidationErrors = [];
|
||||
}
|
||||
}
|
@ -20,11 +20,9 @@
|
||||
{{#if this.theme}}
|
||||
{{#if this.hasWarningsOrErrors}}
|
||||
<p>
|
||||
The theme <strong>"{{this.themeName}}"</strong> was installed successfully but we detected some {{if this.validationErrors "errors" "warnings"}}.
|
||||
{{#if this.canActivateTheme}}
|
||||
The theme <strong>"{{this.themeName}}"</strong> was uploaded successfully but we detected some {{#if this.validationErrors}}errors{{else}}warnings{{/if}}. You are still able to activate and use the theme but it is recommended to fix these {{#if this.validationErrors}}errors{{else}}warnings{{/if}} before you do so.
|
||||
{{else}}
|
||||
The theme <strong>"{{this.themeName}}"</strong> was uploaded successfully but we detected some
|
||||
{{#if this.validationErrors}}errors{{else}}warnings{{/if}}.
|
||||
You are still able to activate and use the theme but it is recommended to fix these {{if this.validationErrors "errors" "warnings"}} before you do so.
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
|
18
ghost/admin/app/controllers/settings/theme/install.js
Normal file
18
ghost/admin/app/controllers/settings/theme/install.js
Normal file
@ -0,0 +1,18 @@
|
||||
import Controller from '@ember/controller';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class InstallThemeController extends Controller {
|
||||
@service router;
|
||||
|
||||
queryParams = ['source', 'ref'];
|
||||
|
||||
@tracked source = '';
|
||||
@tracked ref = '';
|
||||
|
||||
@action
|
||||
close() {
|
||||
this.router.transitionTo('settings.theme');
|
||||
}
|
||||
}
|
@ -53,6 +53,7 @@ Router.map(function () {
|
||||
this.route('settings.code-injection', {path: '/settings/code-injection'});
|
||||
this.route('settings.theme', {path: '/settings/theme'}, function () {
|
||||
this.route('uploadtheme');
|
||||
this.route('install');
|
||||
});
|
||||
this.route('settings.navigation', {path: '/settings/navigation'});
|
||||
this.route('settings.labs', {path: '/settings/labs'});
|
||||
|
15
ghost/admin/app/routes/settings/theme/install.js
Normal file
15
ghost/admin/app/routes/settings/theme/install.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class InstallThemeRoute extends Route {
|
||||
redirect(model, transition) {
|
||||
const {source, ref} = transition.to.queryParams || {};
|
||||
|
||||
if (!source || !ref) {
|
||||
this.transitionTo('settings.theme');
|
||||
}
|
||||
}
|
||||
|
||||
model() {
|
||||
return this.store.findAll('theme');
|
||||
}
|
||||
}
|
@ -217,8 +217,8 @@
|
||||
|
||||
.apps-configured a {
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.apps-configured-action {
|
||||
|
@ -139,7 +139,7 @@
|
||||
/* Setting headers */
|
||||
|
||||
.gh-setting-header {
|
||||
margin: 4vw 0 5px 1px;
|
||||
margin: 4vw 0 0 1px;
|
||||
color: var(--black);
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
@ -349,7 +349,6 @@
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.gh-theme-directory-container {
|
||||
border-top: var(--lightgrey) 1px solid;
|
||||
padding: 25px 0 0;
|
||||
}
|
||||
|
||||
@ -381,10 +380,15 @@
|
||||
.td-item img {
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.02), 0 9px 25px -12px rgba(0,0,0,0.5);
|
||||
transition: all .8s ease;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.td-item svg circle {
|
||||
stroke: var(--midlightgrey);
|
||||
}
|
||||
|
||||
.td-item:hover {
|
||||
transform: translateY(-1.5%);
|
||||
transform: translateY(-1%);
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
@ -396,7 +400,7 @@
|
||||
.td-item-desc {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
margin-top: 16px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
@ -410,94 +414,56 @@
|
||||
color: color-mod(var(--midgrey) l(-5%));
|
||||
}
|
||||
|
||||
.td-cta {
|
||||
display: grid;
|
||||
justify-content: space-between;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 24px;
|
||||
margin: 2vw 0 4vw;
|
||||
.td-item-screenshot {
|
||||
line-height: 0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.td-cta-box {
|
||||
.td-item-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.td-item-overlay:hover,
|
||||
.td-item-overlay:focus {
|
||||
background-color: var(--white-90);
|
||||
}
|
||||
|
||||
.td-item-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.td-item-overlay:hover .td-item-action {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.td-item-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
text-decoration: none;
|
||||
color: var(--darkgrey);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 3px 6px -2px rgba(0,0,0,.1);
|
||||
height: 100%;
|
||||
margin-bottom: 39px;
|
||||
padding: 2rem;
|
||||
background: var(--white);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.02), 0 9px 25px -10px rgba(0,0,0,0.2);
|
||||
transition: all .8s ease;
|
||||
}
|
||||
|
||||
.td-cta-box:hover {
|
||||
transform: translateY(-2%);
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.02), 0 10px 10px -10px rgba(0,0,0,.12);
|
||||
.td-item-empty:hover {
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.02), 0 19px 35px -14px rgba(0,0,0,.2);
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.td-cta-icon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
background: var(--midgrey);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.td-cta-icon svg {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.td-cta-marketplace .td-cta-icon {
|
||||
background: var(--purple);
|
||||
}
|
||||
|
||||
.td-cta-docs .td-cta-icon {
|
||||
background: var(--blue);
|
||||
}
|
||||
|
||||
.td-cta-content-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.td-cta-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 0 0 14px;
|
||||
}
|
||||
|
||||
.td-cta-content p {
|
||||
margin: 0;
|
||||
color: color-mod(var(--midgrey) l(-5%));
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.td-cta-arrow {
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.td-cta-arrow svg {
|
||||
margin-left: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.td-cta-arrow svg path {
|
||||
fill: var(--midgrey);
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.theme-directory {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
@ -822,10 +788,6 @@
|
||||
/* Themes
|
||||
/* ---------------------------------------------------------- */
|
||||
|
||||
.gh-themes-container {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.gh-themes-container .apps-configured {
|
||||
justify-content: flex-end;
|
||||
@ -835,10 +797,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gh-themes-uploadbtn {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
/*Errors */
|
||||
.theme-validation-container {
|
||||
max-height: calc(100vh - 12vw - 110px);
|
||||
|
@ -85,6 +85,7 @@ fieldset[disabled] .gh-btn {
|
||||
background: color-mod(var(--black) l(-20%)) !important;
|
||||
}
|
||||
|
||||
.gh-btn-primary,
|
||||
.gh-btn-black svg {
|
||||
fill: var(--white);
|
||||
}
|
||||
@ -216,6 +217,10 @@ fieldset[disabled] .gh-btn {
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
.gh-btn-hover-background:hover {
|
||||
background: var(--lightgrey);
|
||||
}
|
||||
|
||||
|
||||
/* Special Buttons
|
||||
/* ---------------------------------------------------------- */
|
||||
|
@ -15,12 +15,15 @@
|
||||
{{/if}}
|
||||
|
||||
<section class="view-container">
|
||||
<div class="gh-setting-header gh-first-header">Theme Directory</div>
|
||||
<div class="gh-theme-directory-container">
|
||||
<div class="theme-directory">
|
||||
<a class="td-item" href="https://github.com/TryGhost/Massively" target="_blank" rel="noopener noreferrer">
|
||||
<div class="td-item-screenshot">
|
||||
<div class="td-item-screenshot relative">
|
||||
<img style="object-fit:contain;" src="assets/img/themes/massively.jpg" alt="Massively Theme" />
|
||||
<div class="td-item-overlay">
|
||||
<LinkTo class="td-item-action gh-btn gh-btn-black mb4" @route="settings.theme.install" @query={{hash source="github" ref="TryGhost/Massively"}}><span>Install</span></LinkTo>
|
||||
<a href="https://massively.ghost.io" class="td-item-action gh-btn" target="_blank" rel="noopener"><span>Preview</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="td-item-desc">
|
||||
<div>Massively <span>— Free</span></div>
|
||||
@ -50,12 +53,11 @@
|
||||
<div>Farafra <span>— Premium</span></div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="td-item" href="https://themeforest.net/item/valkyrie-a-highly-visual-ghost-blog/22576630" target="_blank" rel="noopener noreferrer">
|
||||
<div class="td-item-screenshot">
|
||||
<img style="object-fit:contain;" src="assets/img/themes/valkyrie.jpg" alt="Valkyrie Theme" />
|
||||
</div>
|
||||
<div class="td-item-desc">
|
||||
<div>Valkyrie <span>— Premium</span></div>
|
||||
<a class="td-item" href="https://ghost.org/marketplace/" target="_blank" rel="noopener noreferrer">
|
||||
<div class="td-item-empty">
|
||||
{{svg-jar "circle-ellipsis" class="w5"}}
|
||||
<h4 class="fw6 f6 pt3 pb1">More themes</h4>
|
||||
<div class="midgrey f7">Explore more free and premium themes on our Marketplace</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="td-item" href="https://themeforest.net/item/sente-magazine-ghost-blog-theme/21019644" target="_blank" rel="noopener noreferrer">
|
||||
@ -67,33 +69,6 @@
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="td-cta">
|
||||
<a class="td-cta-box td-cta-marketplace" href="https://ghost.org/marketplace/" target="_blank" rel="noopener">
|
||||
<div class="td-cta-icon">{{svg-jar "store"}}</div>
|
||||
<div class="td-cta-content-wrapper">
|
||||
<div class="td-cta-content">
|
||||
<h4 class="fw6 f6">Theme Marketplace</h4>
|
||||
<p>Explore a huge range of free and premium themes for Ghost with a range of design and layout options</p>
|
||||
</div>
|
||||
<div class="td-cta-arrow">
|
||||
{{svg-jar "arrow-right"}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="td-cta-box td-cta-docs" href="https://ghost.org/docs/themes/" target="_blank" rel="noopener">
|
||||
<div class="td-cta-icon">{{svg-jar "book-open"}}</div>
|
||||
<div class="td-cta-content-wrapper">
|
||||
<div class="td-cta-content">
|
||||
<h4 class="fw6 f6">Developer Docs</h4>
|
||||
<p>Build your own custom Ghost theme from scratch using our comprehensive Handlebars.js SDK</p>
|
||||
</div>
|
||||
<div class="td-cta-arrow">
|
||||
{{svg-jar "arrow-right"}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gh-setting-header">Installed Themes</div>
|
||||
@ -105,9 +80,15 @@
|
||||
@downloadTheme={{action "downloadTheme"}}
|
||||
@deleteTheme={{action "deleteTheme"}} />
|
||||
|
||||
<LinkTo @route="settings.theme.uploadtheme" class="gh-btn gh-btn-green gh-themes-uploadbtn" data-test-upload-theme-button={{true}}>
|
||||
<span>Upload a theme</span>
|
||||
</LinkTo>
|
||||
<div class="flex justify-between mt6">
|
||||
<LinkTo id="upload-theme" @route="settings.theme.uploadtheme" class="gh-btn gh-btn-green" data-test-button="uploadtheme">
|
||||
<span>Upload a theme</span>
|
||||
</LinkTo>
|
||||
|
||||
<a href="https://ghost.org/docs/themes/" target=_"blank" rel="noopener noreferrer" class="gh-btn gh-btn-outline">
|
||||
<span>Theme developer docs</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{{#if this.showDeleteThemeModal}}
|
||||
@ -152,7 +133,7 @@
|
||||
{{outlet}}
|
||||
|
||||
<GhTourItem @throbberId="upload-a-theme"
|
||||
@target=".gh-themes-uploadbtn"
|
||||
@target="#upload-theme"
|
||||
@throbberAttachment="top middle"
|
||||
@popoverTriangleClass="bottom"
|
||||
/>
|
||||
|
10
ghost/admin/app/templates/settings/theme/install.hbs
Normal file
10
ghost/admin/app/templates/settings/theme/install.hbs
Normal file
@ -0,0 +1,10 @@
|
||||
<GhFullscreenModal
|
||||
@modal="install-theme"
|
||||
@model={{hash
|
||||
source=this.source
|
||||
ref=this.ref
|
||||
themes=@model
|
||||
}}
|
||||
@close={{this.close}}
|
||||
@modifier="action wide"
|
||||
/>
|
@ -1,6 +1,6 @@
|
||||
<GhFullscreenModal @modal="upload-theme"
|
||||
@model={{hash
|
||||
themes=themes
|
||||
themes=this.themes
|
||||
activate=(route-action 'activateTheme')
|
||||
}}
|
||||
@close={{route-action "cancel"}}
|
||||
|
@ -88,7 +88,7 @@ describe('Acceptance: Settings - Theme', function () {
|
||||
).to.equal('Blog (default)');
|
||||
|
||||
// theme upload displays modal
|
||||
await click('[data-test-upload-theme-button]');
|
||||
await click('[data-test-button="uploadtheme"]');
|
||||
expect(
|
||||
findAll('[data-test-modal="upload-theme"]').length,
|
||||
'theme upload modal displayed after button click'
|
||||
@ -102,7 +102,7 @@ describe('Acceptance: Settings - Theme', function () {
|
||||
).to.be.true;
|
||||
|
||||
// theme upload validates mime type
|
||||
await click('[data-test-upload-theme-button]');
|
||||
await click('[data-test-button="uploadtheme"]');
|
||||
await fileUpload('.fullscreen-modal input[type="file"]', ['test'], {type: 'text/csv'});
|
||||
|
||||
expect(
|
||||
@ -281,7 +281,7 @@ describe('Acceptance: Settings - Theme', function () {
|
||||
await click('.fullscreen-modal [data-test-close-button]');
|
||||
|
||||
// theme upload handles success then close
|
||||
await click('[data-test-upload-theme-button]');
|
||||
await click('[data-test-button="uploadtheme"]');
|
||||
await fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'theme-1.zip', type: 'application/zip'});
|
||||
|
||||
expect(
|
||||
@ -307,7 +307,7 @@ describe('Acceptance: Settings - Theme', function () {
|
||||
await click('.fullscreen-modal [data-test-close-button]');
|
||||
|
||||
// theme upload handles success then activate
|
||||
await click('[data-test-upload-theme-button]');
|
||||
await click('[data-test-button="uploadtheme"]');
|
||||
await fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'theme-2.zip', type: 'application/zip'});
|
||||
await click('.fullscreen-modal [data-test-activate-now-button]');
|
||||
|
||||
@ -547,7 +547,7 @@ describe('Acceptance: Settings - Theme', function () {
|
||||
await click('[data-test-theme-id="foo"] [data-test-theme-delete-button]');
|
||||
await click('.fullscreen-modal [data-test-delete-button]');
|
||||
|
||||
await click('[data-test-upload-theme-button]');
|
||||
await click('[data-test-button="uploadtheme"]');
|
||||
await fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'foo.zip', type: 'application/zip'});
|
||||
// this will fail if upload failed because there won't be an activate now button
|
||||
await click('.fullscreen-modal [data-test-activate-now-button]');
|
||||
|
Loading…
Reference in New Issue
Block a user