0
Fork 0
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:
Elena Baidakova 2023-04-27 16:40:11 +04:00 committed by GitHub
parent 15eca6020d
commit f69674ff9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 132 additions and 38 deletions

View file

@ -20,7 +20,6 @@ export default class AnnouncementSettingsBackgroundComponent extends Component {
@action
setBackground(value) {
this.settings.announcementBackground = value;
this.settings.save();
this.args.onChange?.();
}
}

View file

@ -13,6 +13,5 @@ export default class AnnouncementSettingsContentComponent extends Component {
@action
setContent(html) {
this.settings.announcementContent = html;
this.settings.save();
}
}

View file

@ -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?.();
}
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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} />
);
}

View 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}/>
);
}

View file

@ -0,0 +1,8 @@
import React from 'react';
import {AnnouncementBar} from './AnnouncementBar';
export function Preview({previewData}) {
return (
<AnnouncementBar settings={previewData} />
);
}

View file

@ -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)

View file

@ -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) {