mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-04 02:01:58 -05:00
Improved Lexical behaviour in AdminX portal settings (#17232)
refs https://github.com/TryGhost/Product/issues/3545 - Fixed TODOs - Fixed koenig editor not resizing as content expands - Fixed placeholder in html field - Updated signup options to validate terms length
This commit is contained in:
parent
fc7e150cc2
commit
e397368393
4 changed files with 58 additions and 15 deletions
|
@ -91,7 +91,7 @@ const KoenigWrapper: React.FC<HtmlEditorProps & { editor: EditorResource }> = ({
|
|||
// ensure we're still showing errors in development
|
||||
console.error(error); // eslint-disable-line
|
||||
|
||||
// TODO: Sentry integration?
|
||||
// Pass down Sentry from Ember?
|
||||
// if (this.config.sentry_dsn) {
|
||||
// Sentry.captureException(error, {
|
||||
// tags: {lexical: true},
|
||||
|
@ -111,6 +111,12 @@ const KoenigWrapper: React.FC<HtmlEditorProps & { editor: EditorResource }> = ({
|
|||
}
|
||||
}), [editor]);
|
||||
|
||||
const transformers = {
|
||||
DEFAULT_NODES: koenig.DEFAULT_TRANSFORMERS,
|
||||
BASIC_NODES: koenig.BASIC_TRANSFORMERS,
|
||||
MINIMAL_NODES: koenig.MINIMAL_TRANSFORMERS
|
||||
};
|
||||
|
||||
return (
|
||||
<koenig.KoenigComposer
|
||||
nodes={koenig[nodes || 'DEFAULT_NODES']}
|
||||
|
@ -120,8 +126,7 @@ const KoenigWrapper: React.FC<HtmlEditorProps & { editor: EditorResource }> = ({
|
|||
className='koenig-lexical koenig-lexical-editor-input'
|
||||
darkMode={false}
|
||||
isSnippetsEnabled={false}
|
||||
// TODO: set based on nodes or remove if there's another way to get the minimal editor
|
||||
markdownTransformers={[]}
|
||||
markdownTransformers={transformers[nodes || 'DEFAULT_NODES']}
|
||||
placeholderClassName='koenig-lexical-editor-input-placeholder'
|
||||
placeholderText={placeholder}
|
||||
singleParagraph={true}
|
||||
|
@ -146,7 +151,7 @@ const HtmlEditor: React.FC<HtmlEditorProps & {
|
|||
editorVersion: config.editor.version
|
||||
}), [config.editor.url, config.editor.version]);
|
||||
|
||||
return <div className={className || 'h-[200px] w-full'}>
|
||||
return <div className={className || 'w-full'}>
|
||||
<div className="koenig-react-editor w-full [&_*]:!font-inherit [&_*]:!text-inherit">
|
||||
<ErrorHandler>
|
||||
<Suspense fallback={<p className="koenig-react-editor-loading">Loading editor...</p>}>
|
||||
|
|
|
@ -28,7 +28,6 @@ const HtmlField: React.FC<HtmlFieldProps> = ({
|
|||
title,
|
||||
hideTitle,
|
||||
error,
|
||||
placeholder,
|
||||
hint,
|
||||
value,
|
||||
clearBg = true,
|
||||
|
@ -39,7 +38,7 @@ const HtmlField: React.FC<HtmlFieldProps> = ({
|
|||
...props
|
||||
}) => {
|
||||
const textFieldClasses = unstyled ? '' : clsx(
|
||||
'h-10 border-b py-2',
|
||||
'min-h-10 border-b py-2',
|
||||
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
|
||||
error ? `border-red` : `border-grey-500 hover:border-grey-700 focus:border-black`,
|
||||
(title && !hideTitle && !clearBg) && `mt-2`,
|
||||
|
|
|
@ -16,14 +16,23 @@ const Sidebar: React.FC<{
|
|||
updateSetting: (key: string, setting: SettingValue) => void
|
||||
localTiers: Tier[]
|
||||
updateTier: (tier: Tier) => void
|
||||
}> = ({localSettings, updateSetting, localTiers, updateTier}) => {
|
||||
errors: Record<string, string | undefined>
|
||||
setError: (key: string, error: string | undefined) => void
|
||||
}> = ({localSettings, updateSetting, localTiers, updateTier, errors, setError}) => {
|
||||
const [selectedTab, setSelectedTab] = useState('signupOptions');
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
id: 'signupOptions',
|
||||
title: 'Signup options',
|
||||
contents: <SignupOptions localSettings={localSettings} localTiers={localTiers} updateSetting={updateSetting} updateTier={updateTier} />
|
||||
contents: <SignupOptions
|
||||
errors={errors}
|
||||
localSettings={localSettings}
|
||||
localTiers={localTiers}
|
||||
setError={setError}
|
||||
updateSetting={updateSetting}
|
||||
updateTier={updateTier}
|
||||
/>
|
||||
},
|
||||
{
|
||||
id: 'lookAndFeel',
|
||||
|
@ -67,6 +76,8 @@ const PortalModal: React.FC = () => {
|
|||
}
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<Record<string, string | undefined>>({});
|
||||
|
||||
const updateSetting = (key: string, value: SettingValue) => {
|
||||
updateForm(state => ({
|
||||
...state,
|
||||
|
@ -76,6 +87,13 @@ const PortalModal: React.FC = () => {
|
|||
}));
|
||||
};
|
||||
|
||||
const setError = (key: string, error: string | undefined) => {
|
||||
setErrors(state => ({
|
||||
...state,
|
||||
[key]: error
|
||||
}));
|
||||
};
|
||||
|
||||
const updateTier = (newTier: Tier) => {
|
||||
updateForm(state => ({
|
||||
...state,
|
||||
|
@ -89,7 +107,14 @@ const PortalModal: React.FC = () => {
|
|||
setSelectedPreviewTab(id);
|
||||
};
|
||||
|
||||
const sidebar = <Sidebar localSettings={formState.settings} localTiers={formState.tiers} updateSetting={updateSetting} updateTier={updateTier} />;
|
||||
const sidebar = <Sidebar
|
||||
errors={errors}
|
||||
localSettings={formState.settings}
|
||||
localTiers={formState.tiers}
|
||||
setError={setError}
|
||||
updateSetting={updateSetting}
|
||||
updateTier={updateTier}
|
||||
/>;
|
||||
const preview = <PortalPreview
|
||||
localSettings={formState.settings} localTiers={formState.tiers}
|
||||
selectedTab={selectedPreviewTab}
|
||||
|
@ -112,8 +137,10 @@ const PortalModal: React.FC = () => {
|
|||
testId='portal-modal'
|
||||
title='Portal'
|
||||
onOk={async () => {
|
||||
await handleSave();
|
||||
modal.remove();
|
||||
if (!Object.values(errors).filter(Boolean).length) {
|
||||
await handleSave();
|
||||
modal.remove();
|
||||
}
|
||||
}}
|
||||
onSelectURL={onSelectURL}
|
||||
/>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import CheckboxGroup from '../../../../admin-x-ds/global/form/CheckboxGroup';
|
||||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import HtmlField from '../../../../admin-x-ds/global/form/HtmlField';
|
||||
import React, {useContext, useMemo} from 'react';
|
||||
import React, {useContext, useEffect, useMemo} from 'react';
|
||||
import Toggle from '../../../../admin-x-ds/global/form/Toggle';
|
||||
import {CheckboxProps} from '../../../../admin-x-ds/global/form/Checkbox';
|
||||
import {Setting, SettingValue, Tier} from '../../../../types/api';
|
||||
|
@ -13,7 +13,9 @@ const SignupOptions: React.FC<{
|
|||
updateSetting: (key: string, setting: SettingValue) => void
|
||||
localTiers: Tier[]
|
||||
updateTier: (tier: Tier) => void
|
||||
}> = ({localSettings, updateSetting, localTiers, updateTier}) => {
|
||||
errors: Record<string, string | undefined>
|
||||
setError: (key: string, error: string | undefined) => void
|
||||
}> = ({localSettings, updateSetting, localTiers, updateTier, errors, setError}) => {
|
||||
const {config} = useContext(SettingsContext);
|
||||
|
||||
const [membersSignupAccess, portalName, portalSignupTermsHtml, portalSignupCheckboxRequired, portalPlansJson] = getSettingValues(
|
||||
|
@ -21,12 +23,21 @@ const SignupOptions: React.FC<{
|
|||
);
|
||||
const portalPlans = JSON.parse(portalPlansJson?.toString() || '[]') as string[];
|
||||
|
||||
const signupTermsMaxLength = 115;
|
||||
const signupTermsLength = useMemo(() => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = portalSignupTermsHtml?.toString() || '';
|
||||
return div.innerText.length;
|
||||
}, [portalSignupTermsHtml]);
|
||||
|
||||
useEffect(() => {
|
||||
if (signupTermsLength > signupTermsMaxLength) {
|
||||
setError('portal_signup_terms_html', 'Signup notice is too long');
|
||||
} else {
|
||||
setError('portal_signup_terms_html', undefined);
|
||||
}
|
||||
}, [signupTermsLength, setError]);
|
||||
|
||||
const togglePlan = (plan: string) => {
|
||||
const index = portalPlans.indexOf(plan);
|
||||
|
||||
|
@ -107,11 +118,12 @@ const SignupOptions: React.FC<{
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* TODO: validate length according to hint */}
|
||||
<HtmlField
|
||||
config={config as { editor: any }}
|
||||
hint={<>Recommended: <strong>115</strong> characters. You've used <strong className="text-green">{signupTermsLength}</strong></>}
|
||||
error={Boolean(errors.portal_signup_terms_html)}
|
||||
hint={errors.portal_signup_terms_html || <>Recommended: <strong>115</strong> characters. You've used <strong className="text-green">{signupTermsLength}</strong></>}
|
||||
nodes='MINIMAL_NODES'
|
||||
placeholder={`By signing up, I agree to receive emails from ...`}
|
||||
title='Display notice at signup'
|
||||
value={portalSignupTermsHtml?.toString()}
|
||||
onChange={html => updateSetting('portal_signup_terms_html', html)}
|
||||
|
|
Loading…
Add table
Reference in a new issue