From e397368393d79da6c7fe6d0e2d091aded9d61000 Mon Sep 17 00:00:00 2001 From: Jono M Date: Fri, 7 Jul 2023 14:03:46 +1200 Subject: [PATCH] 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 --- .../src/admin-x-ds/global/form/HtmlEditor.tsx | 13 +++++-- .../src/admin-x-ds/global/form/HtmlField.tsx | 3 +- .../settings/membership/PortalModal.tsx | 37 ++++++++++++++++--- .../membership/portal/SignupOptions.tsx | 20 ++++++++-- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlEditor.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlEditor.tsx index 844941227e..2a75d0011b 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlEditor.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlEditor.tsx @@ -91,7 +91,7 @@ const KoenigWrapper: React.FC = ({ // 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 = ({ } }), [editor]); + const transformers = { + DEFAULT_NODES: koenig.DEFAULT_TRANSFORMERS, + BASIC_NODES: koenig.BASIC_TRANSFORMERS, + MINIMAL_NODES: koenig.MINIMAL_TRANSFORMERS + }; + return ( = ({ 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 + return
Loading editor...

}> diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlField.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlField.tsx index ae507d676a..69610f87ea 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlField.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/HtmlField.tsx @@ -28,7 +28,6 @@ const HtmlField: React.FC = ({ title, hideTitle, error, - placeholder, hint, value, clearBg = true, @@ -39,7 +38,7 @@ const HtmlField: React.FC = ({ ...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`, diff --git a/apps/admin-x-settings/src/components/settings/membership/PortalModal.tsx b/apps/admin-x-settings/src/components/settings/membership/PortalModal.tsx index a2455e783c..02fab9b1e2 100644 --- a/apps/admin-x-settings/src/components/settings/membership/PortalModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/PortalModal.tsx @@ -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 + 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: + contents: }, { id: 'lookAndFeel', @@ -67,6 +76,8 @@ const PortalModal: React.FC = () => { } }); + const [errors, setErrors] = useState>({}); + 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 = ; + const sidebar = ; const preview = { testId='portal-modal' title='Portal' onOk={async () => { - await handleSave(); - modal.remove(); + if (!Object.values(errors).filter(Boolean).length) { + await handleSave(); + modal.remove(); + } }} onSelectURL={onSelectURL} />; diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx index ce901b6ca1..b084e19a29 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx @@ -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 + 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 */} Recommended: 115 characters. You've used {signupTermsLength}} + error={Boolean(errors.portal_signup_terms_html)} + hint={errors.portal_signup_terms_html || <>Recommended: 115 characters. You've used {signupTermsLength}} 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)}