mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Fixed announcement bar preview (#16715)
refs TryGhost/Team#3122 - Fixed that preview takes data from user input before saving on backend. --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖 Generated by Copilot at 54d5b2d</samp> This pull request adds the ability to preview the announcement bar in the Ghost admin panel and the theme settings. It also adds a confirmation dialog to discard or save unsaved changes before leaving the announcement bar settings. It refactors some components and methods to remove unnecessary or redundant calls to save the settings. It modifies the `ghost_head` helper, the `theme-management` service, and the `announcement-bar/src` files to support the preview feature.
This commit is contained in:
parent
15eca6020d
commit
f69674ff9a
12 changed files with 132 additions and 38 deletions
|
@ -20,7 +20,6 @@ export default class AnnouncementSettingsBackgroundComponent extends Component {
|
|||
@action
|
||||
setBackground(value) {
|
||||
this.settings.announcementBackground = value;
|
||||
this.settings.save();
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,5 @@ export default class AnnouncementSettingsContentComponent extends Component {
|
|||
@action
|
||||
setContent(html) {
|
||||
this.settings.announcementContent = html;
|
||||
this.settings.save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,9 @@ export default class AnnouncementSettingsVisibilityComponent extends Component {
|
|||
}
|
||||
|
||||
this.settings.announcementVisibility = updatedVisibilityOptions;
|
||||
this.settings.save();
|
||||
this.args.onChange?.();
|
||||
// update preview if there are no visibility options or just one to avoid update flickering on every check
|
||||
if (!updatedVisibilityOptions.length || updatedVisibilityOptions.length === 1) {
|
||||
this.args.onChange?.();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,7 @@ export default class SettingsAnnouncementBarIndexController extends Controller {
|
|||
}
|
||||
|
||||
yield Promise.all([
|
||||
this.settings.save(),
|
||||
this.customThemeSettings.save()
|
||||
this.settings.save()
|
||||
]);
|
||||
|
||||
// ensure task button switches to success state
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import AdminRoute from 'ghost-admin/routes/authenticated';
|
||||
import ConfirmUnsavedChangesModal from '../../components/modals/confirm-unsaved-changes';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
export default class SettingsDesignRoute extends AdminRoute {
|
||||
export default class AnnouncementBarRoute extends AdminRoute {
|
||||
@service customThemeSettings;
|
||||
@service feature;
|
||||
@service modals;
|
||||
|
@ -41,6 +43,8 @@ export default class SettingsDesignRoute extends AdminRoute {
|
|||
|
||||
deactivate() {
|
||||
this.ui.contextualNavMenu = null;
|
||||
this.confirmModal = null;
|
||||
this.hasConfirmed = false;
|
||||
}
|
||||
|
||||
buildRouteInfoMetadata() {
|
||||
|
@ -49,4 +53,41 @@ export default class SettingsDesignRoute extends AdminRoute {
|
|||
mainClasses: ['gh-main-fullwidth']
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
willTransition(transition) {
|
||||
if (this.hasConfirmed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// always abort when not confirmed because Ember's router doesn't automatically wait on promises
|
||||
transition.abort();
|
||||
|
||||
this.confirmUnsavedChanges().then((shouldLeave) => {
|
||||
if (shouldLeave === true) {
|
||||
this.hasConfirmed = true;
|
||||
return transition.retry();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
confirmUnsavedChanges() {
|
||||
if (!this.settings.hasDirtyAttributes) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
if (!this.confirmModal) {
|
||||
this.confirmModal = this.modals.open(ConfirmUnsavedChangesModal)
|
||||
.then((discardChanges) => {
|
||||
if (discardChanges === true) {
|
||||
this.settings.rollbackAttributes();
|
||||
}
|
||||
return discardChanges;
|
||||
}).finally(() => {
|
||||
this.confirmModal = null;
|
||||
});
|
||||
}
|
||||
|
||||
return this.confirmModal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,6 +222,14 @@ export default class ThemeManagementService extends Service {
|
|||
params.append('logo', this.settings.logo);
|
||||
params.append('cover', this.settings.coverImage);
|
||||
|
||||
if (this.settings.announcementContent) {
|
||||
params.append('announcement', this.settings.announcementContent);
|
||||
}
|
||||
params.append('announcement_bg', this.settings.announcementBackground);
|
||||
if (this.settings.announcementVisibility.length) {
|
||||
params.append('announcement_vis', this.settings.announcementVisibility);
|
||||
}
|
||||
|
||||
params.append('custom', JSON.stringify(this.customThemeSettings.keyValueObject));
|
||||
|
||||
return params.toString();
|
||||
|
|
|
@ -3708,7 +3708,8 @@ p.theme-validation-details {
|
|||
/* Announcement bar */
|
||||
|
||||
.gh-announcement-editor {
|
||||
height: 120px;
|
||||
min-height: 120px;
|
||||
overflow: auto;
|
||||
padding: 6px 12px 6px 12px;
|
||||
border: 1px solid var(--whitegrey-d1);
|
||||
background: var(--white);
|
||||
|
|
|
@ -1,30 +1,12 @@
|
|||
import React from 'react';
|
||||
import {AnnouncementBar} from './components/AnnouncementBar';
|
||||
import setupGhostApi from './utils/api';
|
||||
|
||||
export function App({apiUrl}) {
|
||||
const api = React.useRef(setupGhostApi({apiUrl}));
|
||||
const [siteSettings, setSiteSettings] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (siteSettings) {
|
||||
return;
|
||||
}
|
||||
const getSiteSettings = async () => {
|
||||
const announcement = await api.current.init();
|
||||
|
||||
setSiteSettings(announcement);
|
||||
};
|
||||
|
||||
getSiteSettings();
|
||||
// We only do this for init
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
import {Preview} from './components/Preview';
|
||||
import {Main} from './components/Main';
|
||||
|
||||
export function App({apiUrl, previewData}) {
|
||||
if (previewData) {
|
||||
return <Preview previewData={previewData}/>;
|
||||
}
|
||||
return (
|
||||
<AnnouncementBar
|
||||
api={api}
|
||||
settings={siteSettings}
|
||||
/>
|
||||
<Main apiUrl={apiUrl} />
|
||||
);
|
||||
}
|
||||
|
|
27
ghost/announcement-bar/src/components/Main.js
Normal file
27
ghost/announcement-bar/src/components/Main.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import {AnnouncementBar} from './AnnouncementBar';
|
||||
import setupGhostApi from '../utils/api';
|
||||
|
||||
export function Main({apiUrl}) {
|
||||
const api = React.useRef(setupGhostApi({apiUrl}));
|
||||
const [siteSettings, setSiteSettings] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (siteSettings) {
|
||||
return;
|
||||
}
|
||||
const getSiteSettings = async () => {
|
||||
const announcement = await api.current.init();
|
||||
|
||||
setSiteSettings(announcement);
|
||||
};
|
||||
|
||||
getSiteSettings();
|
||||
// We only do this for init
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AnnouncementBar settings={siteSettings}/>
|
||||
);
|
||||
}
|
8
ghost/announcement-bar/src/components/Preview.js
Normal file
8
ghost/announcement-bar/src/components/Preview.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import {AnnouncementBar} from './AnnouncementBar';
|
||||
|
||||
export function Preview({previewData}) {
|
||||
return (
|
||||
<AnnouncementBar settings={previewData} />
|
||||
);
|
||||
}
|
|
@ -22,22 +22,34 @@ function getSiteData() {
|
|||
const scriptTag = document.querySelector('script[data-announcement-bar]');
|
||||
if (scriptTag) {
|
||||
const apiUrl = scriptTag.dataset.apiUrl;
|
||||
return {apiUrl};
|
||||
return {apiUrl, previewData: getPreviewData(scriptTag)};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function getPreviewData(scriptTag) {
|
||||
if (scriptTag.dataset.preview) {
|
||||
const announcement = scriptTag.dataset.announcement;
|
||||
const announcementBackground = scriptTag.dataset.announcementBackground;
|
||||
|
||||
return {announcement, announcement_background: announcementBackground};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function setup() {
|
||||
addRootDiv();
|
||||
}
|
||||
|
||||
function init() {
|
||||
const {apiUrl} = getSiteData();
|
||||
const {apiUrl, previewData} = getSiteData();
|
||||
setup();
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App
|
||||
apiUrl={apiUrl}
|
||||
previewData={previewData}
|
||||
/>
|
||||
</React.StrictMode>,
|
||||
document.getElementById(ROOT_DIV_ID)
|
||||
|
|
|
@ -87,8 +87,10 @@ function getSearchHelper(frontendKey) {
|
|||
return helper;
|
||||
}
|
||||
|
||||
function getAnnouncementBarHelper() {
|
||||
if (!settingsCache.get('announcement_content')) {
|
||||
function getAnnouncementBarHelper(data) {
|
||||
const preview = data?.site?._preview;
|
||||
|
||||
if (!settingsCache.get('announcement_content') && !preview) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -100,6 +102,20 @@ function getAnnouncementBarHelper() {
|
|||
'api-url': announcementUrl
|
||||
};
|
||||
|
||||
if (preview) {
|
||||
const searchParam = new URLSearchParams(preview);
|
||||
const announcement = searchParam.get('announcement');
|
||||
const announcementBackground = searchParam.has('announcement_bg') ? searchParam.get('announcement_bg') : '';
|
||||
const announcementVisibility = searchParam.has('announcement_vis');
|
||||
|
||||
if (!announcement || !announcementVisibility) {
|
||||
return;
|
||||
}
|
||||
attrs.announcement = escapeExpression(announcement);
|
||||
attrs['announcement-background'] = escapeExpression(announcementBackground);
|
||||
attrs.preview = true;
|
||||
}
|
||||
|
||||
const dataAttrs = getDataAttributes(attrs);
|
||||
let helper = `<script defer src="${scriptUrl}" ${dataAttrs} crossorigin="anonymous"></script>`;
|
||||
|
||||
|
@ -249,7 +265,7 @@ module.exports = async function ghost_head(options) { // eslint-disable-line cam
|
|||
if (!_.includes(context, 'amp')) {
|
||||
head.push(getMembersHelper(options.data, frontendKey));
|
||||
head.push(getSearchHelper(frontendKey));
|
||||
head.push(getAnnouncementBarHelper());
|
||||
head.push(getAnnouncementBarHelper(options.data));
|
||||
try {
|
||||
head.push(getWebmentionDiscoveryLink());
|
||||
} catch (err) {
|
||||
|
|
Loading…
Add table
Reference in a new issue