Added Subheads behind a flag (#20265)

refs MOM-152 MOM-148 MOM-151

- Added Subheads behind a flag + toggle in settings.
- Removes Excerpt fields from post settings if flag is enabled.
- Added subhead toggle in newsletter settings.
- Loads of styling

---------

Co-authored-by: Sanne de Vries <sannedv@protonmail.com>
This commit is contained in:
Ronald Langeveld 2024-05-29 16:53:40 +07:00 committed by GitHub
parent 9099ab47c4
commit fddcf3ffee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 187 additions and 88 deletions

View File

@ -21,6 +21,7 @@ export type Newsletter = {
show_header_title: boolean;
title_font_category: string;
title_alignment: string;
show_subhead: boolean;
show_feature_image: boolean;
body_font_category: string;
footer_content: string | null;

View File

@ -19,6 +19,7 @@
"show_header_title": true,
"title_font_category": "serif",
"title_alignment": "center",
"show_subhead": true,
"show_feature_image": true,
"body_font_category": "serif",
"footer_content": "",

View File

@ -67,6 +67,10 @@ const features = [{
title: 'ActivityPub',
description: '(Highly) Experimental support for ActivityPub.',
flag: 'ActivityPub'
},{
title: 'Subhead',
description: 'Using custom excerpts as subheads in editor and newsletter',
flag: 'subhead'
}];
const AlphaFeatures: React.FC = () => {

View File

@ -103,6 +103,7 @@ const Sidebar: React.FC<{
const {mutateAsync: uploadImage} = useUploadImage();
const [selectedTab, setSelectedTab] = useState('generalSettings');
const hasEmailCustomization = useFeatureFlag('emailCustomization');
const hasSubhead = useFeatureFlag('subhead');
const {localSettings} = useSettingGroup();
const [siteTitle] = getSettingValues(localSettings, ['title']) as string[];
const handleError = useHandleError();
@ -423,6 +424,14 @@ const Sidebar: React.FC<{
title='Body style'
onSelect={option => updateNewsletter({body_font_category: option?.value})}
/>
{hasSubhead &&
<Toggle
checked={newsletter.show_subhead}
direction="rtl"
label='Subhead'
onChange={e => updateNewsletter({show_subhead: e.target.checked})}
/>
}
<Toggle
checked={newsletter.show_feature_image}
direction="rtl"

View File

@ -110,6 +110,7 @@ const NewsletterPreview: React.FC<{newsletter: Newsletter}> = ({newsletter}) =>
showFeedback={showFeedback}
showLatestPosts={newsletter.show_latest_posts}
showPostTitleSection={newsletter.show_post_title_section}
showSubhead={newsletter.show_subhead}
showSubscriptionDetails={newsletter.show_subscription_details}
siteTitle={title}
titleAlignment={newsletter.title_alignment}

View File

@ -18,6 +18,7 @@ const NewsletterPreviewContent: React.FC<{
headerTitle?: string | null;
headerSubtitle?: string | null;
showPostTitleSection: boolean;
showSubhead: boolean;
titleAlignment?: string;
titleFontCategory?: string;
bodyFontCategory?: string;
@ -49,6 +50,7 @@ const NewsletterPreviewContent: React.FC<{
headerTitle,
headerSubtitle,
showPostTitleSection,
showSubhead,
titleAlignment,
titleFontCategory,
bodyFontCategory,
@ -75,6 +77,7 @@ const NewsletterPreviewContent: React.FC<{
const showHeader = headerIcon || headerTitle;
const {config} = useGlobalData();
const hasNewEmailAddresses = useFeatureFlag('newEmailAddresses');
const hasSubhead = useFeatureFlag('subhead');
const currentDate = new Date().toLocaleDateString('default', {
year: 'numeric',
@ -131,6 +134,9 @@ const NewsletterPreviewContent: React.FC<{
)} style={{color: titleColor}}>
Your email newsletter
</h2>
{(hasSubhead && showSubhead) && (
<p className="mb-4 text-[1.6rem] leading-[1.7] text-black">A subhead that highlights the key points of your newsletter.</p>
)}
<div className={clsx(
'flex w-full justify-between text-center text-md leading-none text-grey-700',
titleAlignment === 'center' ? 'flex-col' : 'flex-row'

View File

@ -56,6 +56,22 @@
{{on "paste" this.cleanPastedTitle}}
data-test-editor-title-input={{true}}
/>
{{#if (feature 'subhead')}}
<GhTextarea
@class="gh-editor-subhead"
@placeholder="Add a subhead..."
@shouldFocus={{false}}
@tabindex="1"
@autoExpand=".gh-koenig-editor"
@value={{readonly this.excerpt}}
@input={{this.updateExcerpt}}
@focus-out={{optional @blurExcerpt}}
@keyDown={{this.onExcerptKeydown}}
data-test-editor-subhead-input={{true}}
/>
<hr class="gh-editor-title-divider">
{{/if}}
</div>
<KoenigLexicalEditor

View File

@ -6,6 +6,7 @@ import {tracked} from '@glimmer/tracking';
export default class GhKoenigEditorReactComponent extends Component {
@service settings;
@service feature;
containerElement = null;
titleElement = null;
@ -30,6 +31,10 @@ export default class GhKoenigEditorReactComponent extends Component {
return color;
}
get excerpt() {
return this.args.excerpt || '';
}
@action
registerElement(element) {
this.containerElement = element;
@ -112,29 +117,66 @@ export default class GhKoenigEditorReactComponent extends Component {
// - Enter, creating an empty paragraph when editor is not empty
@action
onTitleKeydown(event) {
const {editorAPI} = this;
if (this.feature.get('subhead')) {
if (event.key === 'Enter') {
event.preventDefault();
const subheadElement = document.querySelector('.gh-editor-subhead');
if (subheadElement) {
subheadElement.focus();
}
}
} else {
const {editorAPI} = this;
if (!editorAPI || event.originalEvent.isComposing) {
return;
}
if (!editorAPI || event.originalEvent.isComposing) {
return;
}
const {key} = event;
const {value, selectionStart} = event.target;
const {key} = event;
const {value, selectionStart} = event.target;
const couldLeaveTitle = !value || selectionStart === value.length;
const arrowLeavingTitle = ['ArrowDown', 'ArrowRight'].includes(key) && couldLeaveTitle;
const couldLeaveTitle = !value || selectionStart === value.length;
const arrowLeavingTitle = ['ArrowDown', 'ArrowRight'].includes(key) && couldLeaveTitle;
if (key === 'Enter' || key === 'Tab' || arrowLeavingTitle) {
event.preventDefault();
if (key === 'Enter' || key === 'Tab' || arrowLeavingTitle) {
event.preventDefault();
if (key === 'Enter' && !editorAPI.editorIsEmpty()) {
editorAPI.insertParagraphAtTop({focus: true});
} else {
editorAPI.focusEditor({position: 'top'});
if (key === 'Enter' && !editorAPI.editorIsEmpty()) {
editorAPI.insertParagraphAtTop({focus: true});
} else {
editorAPI.focusEditor({position: 'top'});
}
}
}
}
// Subheader ("excerpt") Actions -------------------------------------------
@action
updateExcerpt(event) {
this.args.onExcerptChange?.(event.target.value);
}
@action
focusExcerpt() {
this.args.onExcerptFocus?.();
}
@action
blurExcerpt() {
this.args.onExcerptBlur?.();
}
@action
onExcerptKeydown(event) {
if (event.key === 'Enter') {
event.preventDefault();
this.editorAPI.focusEditor({position: 'top'});
}
}
// move cursor to the editor on
// Body actions ------------------------------------------------------------
@action

View File

@ -93,7 +93,7 @@
{{/if}}
{{/if}}
{{#unless (feature 'subhead')}}
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="customExcerpt">
<label for="custom-excerpt">Excerpt</label>
<GhTextarea
@ -108,6 +108,7 @@
/>
<GhErrorMessage @errors={{this.post.errors}} @property="customExcerpt" data-test-error="custom-excerpt" />
</GhFormGroup>
{{/unless}}
{{#unless this.session.user.isAuthorOrContributor}}
<GhFormGroup class="for-select mb8" @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="authors" data-test-input="authors">

View File

@ -284,6 +284,11 @@ export default class LexicalEditorController extends Controller {
this.set('post.titleScratch', title);
}
@action
updateExcerptScratch(excerpt) {
this.set('post.customExcerptScratch', excerpt);
}
// updates local willPublish/Schedule values, does not get applied to
// the post's `status` value until a save is triggered
@action

View File

@ -24,6 +24,7 @@ export default class Newsletter extends Model.extend(ValidationEngine) {
@attr({defaultValue: true}) showHeaderTitle;
@attr({defaultValue: true}) showHeaderName;
@attr({defaultValue: true}) showPostTitleSection;
@attr({defaultValue: false}) showSubhead;
@attr({defaultValue: true}) showCommentCta;
@attr({defaultValue: false}) showSubscriptionDetails;
@attr({defaultValue: false}) showLatestPosts;

View File

@ -83,6 +83,7 @@ export default class FeatureService extends Service {
@feature('onboardingChecklist') onboardingChecklist;
@feature('ActivityPub') ActivityPub;
@feature('internalLinking') internalLinking;
@feature('subhead') subhead;
_user = null;

View File

@ -681,7 +681,7 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
.gh-editor-feature-image-caption {
width: 100%;
min-height: 24px;
margin: 0 0 1.7em 0;
margin: 0 0 1.2rem 0;
padding: 0;
outline: none;
border-width: 0;
@ -717,12 +717,25 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
opacity: .5;
}
.gh-editor-title-container {
position: relative;
max-width: 740px;
width: 100%;
margin-right: auto;
margin-left: auto;
border: none;
background: transparent;
}
.gh-editor-title {
display: block;
width: 100%;
max-width: unset;
min-height: auto;
margin-bottom: 1.2rem;
margin: 0 0 1.6rem;
padding: 0 0 4px;
border: none;
background: transparent;
color: var(--black);
font-size: 4.8rem;
letter-spacing: -0.017em;
@ -732,6 +745,18 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
box-shadow: none;
}
@media (min-width: 500px) and (max-width: 768px) {
.gh-editor-title {
font-size: 3.6rem;
}
}
@media (max-width: 500px) {
.gh-editor-title {
font-size: 2.8rem;
}
}
.gh-editor-title:focus {
box-shadow: none !important;
border: none !important;
@ -741,6 +766,55 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
opacity: .5;
}
.gh-editor-title::placeholder {
color: var(--lightgrey-d1);
font-weight: 700;
opacity: 1;
}
.gh-editor-hidden-indicator {
position: absolute;
top: -1px;
height: 2.4rem;
margin-left: -6rem;
color: var(--midgrey-l2);
}
.gh-editor-title-container .gh-editor-hidden-indicator {
top: 1.8rem;
}
.gh-editor-hidden-indicator svg {
height: 2.4rem;
}
.gh-editor-subhead {
display: block;
width: 100%;
max-width: unset;
min-width: auto;
margin: 0;
padding: 0;
border: none;
background: transparent;
color: var(--darkgrey);
font-size: 1.9rem;
font-weight: 440;
line-height: 1.4em;
letter-spacing: -.018em;
overflow: hidden;
box-shadow: none;
}
.gh-editor-subhead:focus {
box-shadow: none !important;
border: none !important;
}
.gh-editor-title-divider {
margin: 1.6rem 0 4.8rem;
}
.gh-editor .tk-indicator {
position: absolute;
top: 15px;
@ -927,21 +1001,6 @@ body[data-user-is-dragging] .gh-editor-feature-image-dropzone {
}
}
@media (max-width: 500px) {
.gh-editor-title {
font-size: 3.4rem;
}
}
.gh-editor-title {
padding: 0 0 4px;
}
.gh-editor-title::placeholder {
color: var(--lightgrey-d1);
font-weight: 700;
opacity: 1;
}
.gh-editor .editor-preview {
height: auto;
margin-top: 4px;
@ -1168,61 +1227,6 @@ figure {
/* Labs
/* ---------------------------------------------------------- */
.gh-editor-title-container {
position: relative;
max-width: 740px;
width: 100%;
margin-right: auto;
margin-left: auto;
border: none;
background: transparent;
}
.gh-editor .gh-editor-title {
display: block;
width: 100%;
max-width: unset;
min-height: auto;
margin: 0 0 1.2rem;
border: none;
background: transparent;
color: var(--black);
font-size: 4.8rem;
letter-spacing: -0.017em;
line-height: 1.1em;
font-weight: 700;
overflow: hidden;
box-shadow: none;
}
@media (min-width: 500px) and (max-width: 768px) {
.gh-editor .gh-editor-title {
font-size: 3.6rem;
}
}
@media (max-width: 500px) {
.gh-editor .gh-editor-title {
font-size: 2.8rem;
}
}
.gh-editor-hidden-indicator {
position: absolute;
top: -1px;
height: 2.4rem;
margin-left: -6rem;
color: var(--midgrey-l2);
}
.gh-editor-title-container .gh-editor-hidden-indicator {
top: 1.8rem;
}
.gh-editor-hidden-indicator svg {
height: 2.4rem;
}
.gh-setting-error {
margin-top: 1em;
line-height: 1.3em;

View File

@ -199,3 +199,7 @@
.gh-post-history-hidden-lexical {
display: none;
}
.gh-post-history .gh-editor-feature-image p {
margin: 0 0 1.2rem;
}

View File

@ -61,10 +61,12 @@
--}}
<GhKoenigEditorLexical
@title={{readonly this.post.titleScratch}}
@excerpt={{readonly this.post.customExcerpt}}
@titleAutofocus={{this.shouldFocusTitle}}
@titlePlaceholder={{concat (capitalize this.post.displayName) " title"}}
@titleHasTk={{this.titleHasTk}}
@onTitleChange={{this.updateTitleScratch}}
@onExcerptChange={{this.updateExcerptScratch}}
@onTitleBlur={{perform this.saveTitleTask}}
@body={{readonly this.post.lexicalScratch}}
@bodyPlaceholder={{concat "Begin writing your " this.post.displayName "..."}}

View File

@ -51,7 +51,8 @@ const ALPHA_FEATURES = [
'tipsAndDonations',
'importMemberTier',
'lexicalIndicators',
'adminXDemo'
'adminXDemo',
'subhead'
];
module.exports.GA_KEYS = [...GA_FEATURES];