mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 03:14:03 +03:00
Port custom theme setting visibility to admin-x (#18367)
refs https://github.com/TryGhost/Product/issues/3962 Port custom theme setting visibility to admin-x based on previous implementation in the Ghost admin: https://github.com/TryGhost/Ghost/pull/17920
This commit is contained in:
parent
b5fc527f8d
commit
043c9bb35d
4
apps/admin-x-settings/node-shim.cjs
Normal file
4
apps/admin-x-settings/node-shim.cjs
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This is used by vite to resolve node builtins. See resolve.alias in vite.config.js
|
||||
*/
|
||||
module.exports = {};
|
@ -47,6 +47,7 @@
|
||||
"@tanstack/react-query": "4.35.7",
|
||||
"@tryghost/color-utils": "0.1.24",
|
||||
"@tryghost/limit-service": "^1.2.10",
|
||||
"@tryghost/nql": "0.11.0",
|
||||
"@tryghost/timezone-data": "0.3.0",
|
||||
"@uiw/react-codemirror": "^4.21.9",
|
||||
"clsx": "2.0.0",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import nql from '@tryghost/nql';
|
||||
import {Setting} from './settings';
|
||||
import {createMutation, createQuery} from '../utils/api/hooks';
|
||||
|
||||
@ -19,8 +20,11 @@ export type CustomThemeSetting = CustomThemeSettingData & {
|
||||
description?: string
|
||||
// homepage and post are the only two groups we handle, but technically theme authors can put other things in package.json
|
||||
group?: 'homepage' | 'post' | string
|
||||
visibility?: string
|
||||
}
|
||||
|
||||
export const hiddenCustomThemeSettingValue = null;
|
||||
|
||||
export interface CustomThemeSettingsResponseType {
|
||||
custom_theme_settings: CustomThemeSetting[];
|
||||
}
|
||||
@ -45,3 +49,11 @@ export const useEditCustomThemeSettings = createMutation<CustomThemeSettingsResp
|
||||
update: newData => newData
|
||||
}
|
||||
});
|
||||
|
||||
export function isCustomThemeSettingVisible(setting: CustomThemeSetting, settingsKeyValueObj: Record<string, string>) {
|
||||
if (!setting.visibility) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nql(setting.visibility).queryJSON(settingsKeyValueObj);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import IframeBuffering from '../../../../utils/IframeBuffering';
|
||||
import React, {useCallback} from 'react';
|
||||
import {CustomThemeSetting} from '../../../../api/customThemeSettings';
|
||||
import {CustomThemeSetting, hiddenCustomThemeSettingValue, isCustomThemeSettingVisible} from '../../../../api/customThemeSettings';
|
||||
|
||||
type BrandSettings = {
|
||||
description: string;
|
||||
@ -35,6 +35,7 @@ function getPreviewData({
|
||||
if (!themeSettings) {
|
||||
return;
|
||||
}
|
||||
const themeSettingsKeyValueObj = themeSettings.reduce((obj, {key, value}) => ({...obj, [key]: value}), {});
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('c', accentColor);
|
||||
@ -42,13 +43,13 @@ function getPreviewData({
|
||||
params.append('icon', icon);
|
||||
params.append('logo', logo);
|
||||
params.append('cover', coverImage);
|
||||
const themeSettingsObj: {
|
||||
[key: string]: string;
|
||||
const custom: {
|
||||
[key: string]: string | typeof hiddenCustomThemeSettingValue;
|
||||
} = {};
|
||||
themeSettings.forEach((setting) => {
|
||||
themeSettingsObj[setting.key] = setting.value as string;
|
||||
custom[setting.key] = isCustomThemeSettingVisible(setting, themeSettingsKeyValueObj) ? setting.value as string : hiddenCustomThemeSettingValue;
|
||||
});
|
||||
params.append('custom', JSON.stringify(themeSettingsObj));
|
||||
params.append('custom', JSON.stringify(custom));
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import SettingGroupContent from '../../../../admin-x-ds/settings/SettingGroupCon
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import Toggle from '../../../../admin-x-ds/global/form/Toggle';
|
||||
import useHandleError from '../../../../utils/api/handleError';
|
||||
import {CustomThemeSetting} from '../../../../api/customThemeSettings';
|
||||
import {CustomThemeSetting, isCustomThemeSettingVisible} from '../../../../api/customThemeSettings';
|
||||
import {getImageUrl, useUploadImage} from '../../../../api/images';
|
||||
import {humanizeSettingKey} from '../../../../api/settings';
|
||||
|
||||
@ -85,9 +85,13 @@ const ThemeSetting: React.FC<{
|
||||
};
|
||||
|
||||
const ThemeSettings: React.FC<{ settings: CustomThemeSetting[], updateSetting: (setting: CustomThemeSetting) => void }> = ({settings, updateSetting}) => {
|
||||
// Filter out custom theme settings that should not be visible
|
||||
const settingsKeyValueObj = settings.reduce((obj, {key, value}) => ({...obj, [key]: value}), {});
|
||||
const filteredSettings = settings.filter(setting => isCustomThemeSettingVisible(setting, settingsKeyValueObj));
|
||||
|
||||
return (
|
||||
<SettingGroupContent className='mt-7'>
|
||||
{settings.map(setting => <ThemeSetting key={setting.key} setSetting={value => updateSetting({...setting, value} as CustomThemeSetting)} setting={setting} />)}
|
||||
{filteredSettings.map(setting => <ThemeSetting key={setting.key} setSetting={value => updateSetting({...setting, value} as CustomThemeSetting)} setting={setting} />)}
|
||||
</SettingGroupContent>
|
||||
);
|
||||
};
|
||||
|
1
apps/admin-x-settings/src/typings.d.ts
vendored
1
apps/admin-x-settings/src/typings.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
declare module '@tryghost/timezone-data'
|
||||
declare module '@tryghost/limit-service'
|
||||
declare module '@tryghost/color-utils'
|
||||
declare module '@tryghost/nql'
|
||||
|
||||
declare module '*.svg' {
|
||||
import React = require('react');
|
||||
|
@ -142,4 +142,69 @@ test.describe('Design settings', async () => {
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test('Custom theme setting visibility', async ({page}) => {
|
||||
const {lastApiRequests} = await mockApi({page, requests: {
|
||||
...globalDataRequests,
|
||||
editCustomThemeSettings: {method: 'PUT', path: '/custom_theme_settings/', response: responseFixtures.customThemeSettings},
|
||||
browseCustomThemeSettings: {method: 'GET', path: '/custom_theme_settings/', response: {
|
||||
custom_theme_settings: [{
|
||||
type: 'select',
|
||||
options: [
|
||||
'Logo on cover',
|
||||
'Logo in the middle',
|
||||
'Stacked'
|
||||
],
|
||||
default: 'Logo on cover',
|
||||
id: '648047658d265b0c8b33c591',
|
||||
value: 'Stacked',
|
||||
key: 'navigation_layout'
|
||||
}, {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
id: '648047658d265b0c8b33c592',
|
||||
value: 'false',
|
||||
key: 'show_featured_posts',
|
||||
visibility: 'navigation_layout:[Stacked]'
|
||||
}]
|
||||
}},
|
||||
browseLatestPost: {method: 'GET', path: /^\/posts\/.+limit=1/, response: responseFixtures.latestPost}
|
||||
}});
|
||||
const lastPreviewRequest = await mockSitePreview({
|
||||
page,
|
||||
url: responseFixtures.site.site.url,
|
||||
response: '<html><head><style></style></head><body><div>homepage preview</div></body></html>'
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
const section = page.getByTestId('design');
|
||||
|
||||
await section.getByRole('button', {name: 'Customize'}).click();
|
||||
|
||||
const modal = page.getByTestId('design-modal');
|
||||
|
||||
await modal.getByRole('tab', {name: 'Site wide'}).click();
|
||||
|
||||
const showFeaturedPostsCustomThemeSetting = modal.getByLabel('Show featured posts');
|
||||
|
||||
await expect(showFeaturedPostsCustomThemeSetting).toBeVisible();
|
||||
|
||||
await chooseOptionInSelect(modal.getByLabel('Navigation layout'), 'Logo in the middle');
|
||||
|
||||
await expect(showFeaturedPostsCustomThemeSetting).not.toBeVisible();
|
||||
|
||||
await modal.getByRole('button', {name: 'Save'}).click();
|
||||
|
||||
const expectedSettings = {navigation_layout: 'Logo in the middle', show_featured_posts: null};
|
||||
const expectedEncoded = new URLSearchParams([['custom', JSON.stringify(expectedSettings)]]).toString();
|
||||
expect(lastPreviewRequest.previewHeader).toMatch(new RegExp(`&${expectedEncoded.replace(/\+/g, '\\+')}`));
|
||||
|
||||
expect(lastApiRequests.editCustomThemeSettings?.body).toMatchObject({
|
||||
custom_theme_settings: [
|
||||
{key: 'navigation_layout', value: 'Logo in the middle'},
|
||||
{key: 'show_featured_posts', value: 'false'}
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
28
apps/admin-x-settings/test/unit/api/customThemeSettings.ts
Normal file
28
apps/admin-x-settings/test/unit/api/customThemeSettings.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import * as assert from 'assert/strict';
|
||||
import {CustomThemeSetting, isCustomThemeSettingVisible} from '../../../src/api/customThemeSettings';
|
||||
|
||||
describe('isCustomThemeSettingVisible', function () {
|
||||
it('returns whether or not a custom theme setting is visible', function () {
|
||||
const settings: CustomThemeSetting[] = [
|
||||
{
|
||||
id: 'abc123',
|
||||
key: 'foo',
|
||||
type: 'boolean',
|
||||
value: false,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
id: 'def456',
|
||||
key: 'bar',
|
||||
type: 'text',
|
||||
value: 'qux',
|
||||
default: 'qux',
|
||||
visibility: 'foo:true'
|
||||
}
|
||||
];
|
||||
const settingsKeyValueObj = settings.reduce((obj, {key, value}) => ({...obj, [key]: value}), {});
|
||||
|
||||
assert.equal(isCustomThemeSettingVisible(settings[0], settingsKeyValueObj), true);
|
||||
assert.equal(isCustomThemeSettingVisible(settings[1], settingsKeyValueObj), false);
|
||||
});
|
||||
});
|
@ -47,7 +47,8 @@ export default (function viteConfig() {
|
||||
],
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.VITEST_SEGFAULT_RETRY': 3
|
||||
'process.env.VITEST_SEGFAULT_RETRY': 3,
|
||||
'process.env.DEBUG': false // Shim env var utilized by the @tryghost/nql package
|
||||
},
|
||||
preview: {
|
||||
port: 4174
|
||||
@ -80,6 +81,16 @@ export default (function viteConfig() {
|
||||
minThreads: 1,
|
||||
maxThreads: 2
|
||||
})
|
||||
},
|
||||
resolve: {
|
||||
// Shim node modules utilized by the @tryghost/nql package
|
||||
alias: {
|
||||
fs: 'node-shim.cjs',
|
||||
path: 'node-shim.cjs',
|
||||
util: 'node-shim.cjs',
|
||||
// @TODO: Remove this when @tryghost/nql is updated
|
||||
mingo: resolve(__dirname, '../../node_modules/mingo/dist/mingo.js')
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user