Article Lead and Description with simple editor (#189)
* Article Lead and Description
This commit is contained in:
parent
f0bb04b33d
commit
328bd89d8d
|
@ -577,3 +577,14 @@ a[data-toggle='tooltip'] {
|
||||||
border-color: var(--black-500) transparent transparent transparent;
|
border-color: var(--black-500) transparent transparent transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
@include font-size(1.8rem);
|
||||||
|
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -233,6 +233,9 @@ export const FullArticle = (props: Props) => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={props.article.lead}>
|
||||||
|
<section class={styles.lead} innerHTML={props.article.lead} />
|
||||||
|
</Show>
|
||||||
<Show when={props.article.layout === 'audio'}>
|
<Show when={props.article.layout === 'audio'}>
|
||||||
<AudioHeader
|
<AudioHeader
|
||||||
title={props.article.title}
|
title={props.article.title}
|
||||||
|
|
|
@ -174,7 +174,7 @@ export const Editor = (props: Props) => {
|
||||||
Image,
|
Image,
|
||||||
Figcaption,
|
Figcaption,
|
||||||
Embed,
|
Embed,
|
||||||
CharacterCount,
|
CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'textBubbleMenu',
|
pluginKey: 'textBubbleMenu',
|
||||||
element: textBubbleMenuRef.current,
|
element: textBubbleMenuRef.current,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
background: var(--black-50);
|
background: var(--black-50);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 16px 16px 8px;
|
padding: 16px 16px 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.simplifiedEditorField {
|
.simplifiedEditorField {
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
@ -92,4 +93,48 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.minimal {
|
||||||
|
background: unset;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
& div[contenteditable] {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.bordered {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px 12px 6px 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 2px solid var(--black-100);
|
||||||
|
background: var(--white-500);
|
||||||
|
|
||||||
|
& div[contenteditable] {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.labelVisible {
|
||||||
|
padding-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
left: 12px;
|
||||||
|
color: var(--black-400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, onCleanup, onMount, Show } from 'solid-js'
|
import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import {
|
import {
|
||||||
createEditorTransaction,
|
createEditorTransaction,
|
||||||
createTiptapEditor,
|
createTiptapEditor,
|
||||||
|
@ -30,12 +30,19 @@ import { UploadedFile } from '../../pages/types'
|
||||||
import { Figure } from './extensions/Figure'
|
import { Figure } from './extensions/Figure'
|
||||||
import { Image } from '@tiptap/extension-image'
|
import { Image } from '@tiptap/extension-image'
|
||||||
import { Figcaption } from './extensions/Figcaption'
|
import { Figcaption } from './extensions/Figcaption'
|
||||||
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
|
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
||||||
|
import { CharacterCount } from '@tiptap/extension-character-count'
|
||||||
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialContent?: string
|
initialContent?: string
|
||||||
|
label?: string
|
||||||
onSubmit?: (text: string) => void
|
onSubmit?: (text: string) => void
|
||||||
onChange?: (text: string) => void
|
onChange?: (text: string) => void
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
variant?: 'minimal' | 'bordered'
|
||||||
|
maxLength?: number
|
||||||
submitButtonText?: string
|
submitButtonText?: string
|
||||||
quoteEnabled?: boolean
|
quoteEnabled?: boolean
|
||||||
imageEnabled?: boolean
|
imageEnabled?: boolean
|
||||||
|
@ -43,10 +50,13 @@ type Props = {
|
||||||
smallHeight?: boolean
|
smallHeight?: boolean
|
||||||
submitByEnter?: boolean
|
submitByEnter?: boolean
|
||||||
submitByShiftEnter?: boolean
|
submitByShiftEnter?: boolean
|
||||||
|
onlyBubbleControls?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_DESCRIPTION_LIMIT = 400
|
||||||
const SimplifiedEditor = (props: Props) => {
|
const SimplifiedEditor = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const [counter, setCounter] = createSignal<number>()
|
||||||
|
|
||||||
const wrapperEditorElRef: {
|
const wrapperEditorElRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
|
@ -60,6 +70,12 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
current: null
|
current: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const textBubbleMenuRef: {
|
||||||
|
current: HTMLDivElement
|
||||||
|
} = {
|
||||||
|
current: null
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { setEditor }
|
actions: { setEditor }
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
@ -69,6 +85,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
content: 'figcaption image'
|
content: 'figcaption image'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const content = props.initialContent
|
||||||
const editor = createTiptapEditor(() => ({
|
const editor = createTiptapEditor(() => ({
|
||||||
element: editorElRef.current,
|
element: editorElRef.current,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
|
@ -85,11 +102,25 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
Link.configure({
|
Link.configure({
|
||||||
openOnClick: false
|
openOnClick: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
CharacterCount.configure({
|
||||||
|
limit: MAX_DESCRIPTION_LIMIT
|
||||||
|
}),
|
||||||
Blockquote.configure({
|
Blockquote.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: styles.blockQuote
|
class: styles.blockQuote
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
BubbleMenu.configure({
|
||||||
|
pluginKey: 'textBubbleMenu',
|
||||||
|
element: textBubbleMenuRef.current,
|
||||||
|
shouldShow: ({ view, state }) => {
|
||||||
|
if (!props.onlyBubbleControls) return
|
||||||
|
const { selection } = state
|
||||||
|
const { empty } = selection
|
||||||
|
return view.hasFocus() && !empty
|
||||||
|
}
|
||||||
|
}),
|
||||||
ImageFigure,
|
ImageFigure,
|
||||||
Image,
|
Image,
|
||||||
Figcaption,
|
Figcaption,
|
||||||
|
@ -98,7 +129,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
placeholder: props.placeholder
|
placeholder: props.placeholder
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
content: props.initialContent ?? null
|
content: content ?? null
|
||||||
}))
|
}))
|
||||||
|
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
|
@ -193,94 +224,110 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
|
|
||||||
const handleInsertLink = () => !editor().state.selection.empty && showModal('editorInsertLink')
|
const handleInsertLink = () => !editor().state.selection.empty && showModal('editorInsertLink')
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (html()) {
|
||||||
|
setCounter(editor().storage.characterCount.characters())
|
||||||
|
}
|
||||||
|
})
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={(el) => (wrapperEditorElRef.current = el)}
|
ref={(el) => (wrapperEditorElRef.current = el)}
|
||||||
class={clsx(styles.SimplifiedEditor, {
|
class={clsx(styles.SimplifiedEditor, {
|
||||||
[styles.smallHeight]: props.smallHeight,
|
[styles.smallHeight]: props.smallHeight,
|
||||||
[styles.isFocused]: isFocused() || !isEmpty()
|
[styles.minimal]: props.variant === 'minimal',
|
||||||
|
[styles.bordered]: props.variant === 'bordered',
|
||||||
|
[styles.isFocused]: isFocused() || !isEmpty(),
|
||||||
|
[styles.labelVisible]: props.label && counter() > 0
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
<Show when={props.maxLength && editor()}>
|
||||||
|
<div class={styles.limit}>{MAX_DESCRIPTION_LIMIT - counter()}</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={props.label && counter() > 0}>
|
||||||
|
<div class={styles.label}>{props.label}</div>
|
||||||
|
</Show>
|
||||||
<div ref={(el) => (editorElRef.current = el)} />
|
<div ref={(el) => (editorElRef.current = el)} />
|
||||||
<div class={styles.controls}>
|
<Show when={!props.onlyBubbleControls}>
|
||||||
<div class={styles.actions}>
|
<div class={styles.controls}>
|
||||||
<Popover content={t('Bold')}>
|
<div class={styles.actions}>
|
||||||
{(triggerRef: (el) => void) => (
|
<Popover content={t('Bold')}>
|
||||||
<button
|
|
||||||
ref={triggerRef}
|
|
||||||
type="button"
|
|
||||||
class={clsx(styles.actionButton, { [styles.active]: isBold() })}
|
|
||||||
onClick={() => editor().chain().focus().toggleBold().run()}
|
|
||||||
>
|
|
||||||
<Icon name="editor-bold" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
<Popover content={t('Italic')}>
|
|
||||||
{(triggerRef: (el) => void) => (
|
|
||||||
<button
|
|
||||||
ref={triggerRef}
|
|
||||||
type="button"
|
|
||||||
class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
|
|
||||||
onClick={() => editor().chain().focus().toggleItalic().run()}
|
|
||||||
>
|
|
||||||
<Icon name="editor-italic" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
<Popover content={t('Add url')}>
|
|
||||||
{(triggerRef: (el) => void) => (
|
|
||||||
<button
|
|
||||||
ref={triggerRef}
|
|
||||||
type="button"
|
|
||||||
onClick={handleInsertLink}
|
|
||||||
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
|
|
||||||
>
|
|
||||||
<Icon name="editor-link" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
<Show when={props.quoteEnabled}>
|
|
||||||
<Popover content={t('Add blockquote')}>
|
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<button
|
<button
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => editor().chain().focus().toggleBlockquote().run()}
|
class={clsx(styles.actionButton, { [styles.active]: isBold() })}
|
||||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
onClick={() => editor().chain().focus().toggleBold().run()}
|
||||||
>
|
>
|
||||||
<Icon name="editor-quote" />
|
<Icon name="editor-bold" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
</Show>
|
<Popover content={t('Italic')}>
|
||||||
<Show when={props.imageEnabled}>
|
|
||||||
<Popover content={t('Add image')}>
|
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<button
|
<button
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => showModal('uploadImage')}
|
class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
|
||||||
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
onClick={() => editor().chain().focus().toggleItalic().run()}
|
||||||
>
|
>
|
||||||
<Icon name="editor-image-dd-full" />
|
<Icon name="editor-italic" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<Popover content={t('Add url')}>
|
||||||
|
{(triggerRef: (el) => void) => (
|
||||||
|
<button
|
||||||
|
ref={triggerRef}
|
||||||
|
type="button"
|
||||||
|
onClick={handleInsertLink}
|
||||||
|
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
|
||||||
|
>
|
||||||
|
<Icon name="editor-link" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
<Show when={props.quoteEnabled}>
|
||||||
|
<Popover content={t('Add blockquote')}>
|
||||||
|
{(triggerRef: (el) => void) => (
|
||||||
|
<button
|
||||||
|
ref={triggerRef}
|
||||||
|
type="button"
|
||||||
|
onClick={() => editor().chain().focus().toggleBlockquote().run()}
|
||||||
|
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||||
|
>
|
||||||
|
<Icon name="editor-quote" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
</Show>
|
||||||
|
<Show when={props.imageEnabled}>
|
||||||
|
<Popover content={t('Add image')}>
|
||||||
|
{(triggerRef: (el) => void) => (
|
||||||
|
<button
|
||||||
|
ref={triggerRef}
|
||||||
|
type="button"
|
||||||
|
onClick={() => showModal('uploadImage')}
|
||||||
|
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
|
||||||
|
>
|
||||||
|
<Icon name="editor-image-dd-full" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={!props.onChange}>
|
||||||
|
<div class={styles.buttons}>
|
||||||
|
<Button value={t('Cancel')} variant="secondary" disabled={isEmpty()} onClick={handleClear} />
|
||||||
|
<Button
|
||||||
|
value={props.submitButtonText ?? t('Send')}
|
||||||
|
variant="primary"
|
||||||
|
disabled={isEmpty()}
|
||||||
|
onClick={() => props.onSubmit(html())}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={!props.onChange}>
|
</Show>
|
||||||
<div class={styles.buttons}>
|
|
||||||
<Button value={t('Cancel')} variant="secondary" disabled={isEmpty()} onClick={handleClear} />
|
|
||||||
<Button
|
|
||||||
value={props.submitButtonText ?? t('Send')}
|
|
||||||
variant="primary"
|
|
||||||
disabled={isEmpty()}
|
|
||||||
onClick={() => props.onSubmit(html())}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<Modal variant="narrow" name="editorInsertLink">
|
<Modal variant="narrow" name="editorInsertLink">
|
||||||
<InsertLinkForm editor={editor()} onClose={() => hideModal()} />
|
<InsertLinkForm editor={editor()} onClose={() => hideModal()} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -293,6 +340,13 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={props.onlyBubbleControls}>
|
||||||
|
<TextBubbleMenu
|
||||||
|
isCommonMarkup={true}
|
||||||
|
editor={editor()}
|
||||||
|
ref={(el) => (textBubbleMenuRef.current = el)}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,12 +180,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutCardLead {
|
.shoutCardDescription {
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
color: var(--secondary-color);
|
color: var(--default-color);
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.3;
|
|
||||||
margin-bottom: 1.4rem;
|
margin-bottom: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,10 +164,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<Show when={props.article.lead}>
|
|
||||||
<div class={styles.shoutCardLead}>{props.article.lead}</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||||
|
@ -196,7 +192,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={props.article.description}>
|
||||||
|
<section class={styles.shoutCardDescription} innerHTML={props.article.description} />
|
||||||
|
</Show>
|
||||||
<Show when={props.settings?.isFeedMode}>
|
<Show when={props.settings?.isFeedMode}>
|
||||||
<Show when={!props.settings?.noimage && props.article.cover}>
|
<Show when={!props.settings?.noimage && props.article.cover}>
|
||||||
<div class={styles.shoutCardCoverContainer}>
|
<div class={styles.shoutCardCoverContainer}>
|
||||||
|
|
|
@ -21,13 +21,13 @@ import deepEqual from 'fast-deep-equal'
|
||||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||||
import { PublishSettings } from './PublishSettings'
|
import { PublishSettings } from './PublishSettings'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
import SimplifiedEditor from '../Editor/SimplifiedEditor'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
shout: Shout
|
shout: Shout
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_HEADER_LIMIT = 100
|
export const MAX_HEADER_LIMIT = 100
|
||||||
export const MAX_LEAD_LIMIT = 400
|
|
||||||
export const EMPTY_TOPIC: Topic = {
|
export const EMPTY_TOPIC: Topic = {
|
||||||
id: -1,
|
id: -1,
|
||||||
slug: ''
|
slug: ''
|
||||||
|
@ -64,6 +64,8 @@ export const EditView = (props: Props) => {
|
||||||
slug: props.shout.slug,
|
slug: props.shout.slug,
|
||||||
shoutId: props.shout.id,
|
shoutId: props.shout.id,
|
||||||
title: props.shout.title,
|
title: props.shout.title,
|
||||||
|
lead: props.shout.lead,
|
||||||
|
description: props.shout.description,
|
||||||
subtitle: props.shout.subtitle,
|
subtitle: props.shout.subtitle,
|
||||||
selectedTopics: shoutTopics,
|
selectedTopics: shoutTopics,
|
||||||
mainTopic: shoutTopics.find((topic) => topic.slug === props.shout.mainTopic) || EMPTY_TOPIC,
|
mainTopic: shoutTopics.find((topic) => topic.slug === props.shout.mainTopic) || EMPTY_TOPIC,
|
||||||
|
@ -75,7 +77,6 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const subtitleInput: { current: HTMLTextAreaElement } = { current: null }
|
const subtitleInput: { current: HTMLTextAreaElement } = { current: null }
|
||||||
const leadInput: { current: HTMLTextAreaElement } = { current: null }
|
|
||||||
|
|
||||||
const [prevForm, setPrevForm] = createStore<ShoutForm>(clone(form))
|
const [prevForm, setPrevForm] = createStore<ShoutForm>(clone(form))
|
||||||
const [saving, setSaving] = createSignal(false)
|
const [saving, setSaving] = createSignal(false)
|
||||||
|
@ -226,7 +227,6 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
const showLeadInput = () => {
|
const showLeadInput = () => {
|
||||||
setIsLeadVisible(true)
|
setIsLeadVisible(true)
|
||||||
leadInput.current.focus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -320,16 +320,13 @@ export const EditView = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={isLeadVisible()}>
|
<Show when={isLeadVisible()}>
|
||||||
<GrowingTextarea
|
<SimplifiedEditor
|
||||||
textAreaRef={(el) => {
|
variant="minimal"
|
||||||
leadInput.current = el
|
onlyBubbleControls={true}
|
||||||
}}
|
smallHeight={true}
|
||||||
allowEnterKey={true}
|
placeholder={t('A short introduction to keep the reader interested')}
|
||||||
value={(value) => setForm('lead', value)}
|
initialContent={form.lead}
|
||||||
class={styles.leadInput}
|
onChange={(value) => setForm('lead', value)}
|
||||||
placeholder={t('Description')}
|
|
||||||
initialValue={form.subtitle}
|
|
||||||
maxLength={MAX_LEAD_LIMIT}
|
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { useLocalize } from '../../../context/localize'
|
||||||
import { Modal } from '../../Nav/Modal'
|
import { Modal } from '../../Nav/Modal'
|
||||||
import { Topic } from '../../../graphql/types.gen'
|
import { Topic } from '../../../graphql/types.gen'
|
||||||
import { apiClient } from '../../../utils/apiClient'
|
import { apiClient } from '../../../utils/apiClient'
|
||||||
import { EMPTY_TOPIC, MAX_LEAD_LIMIT } from '../Edit'
|
import { EMPTY_TOPIC } from '../Edit'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import stylesBeside from '../../Feed/Beside.module.scss'
|
import stylesBeside from '../../Feed/Beside.module.scss'
|
||||||
|
@ -19,6 +19,7 @@ import { router } from '../../../stores/router'
|
||||||
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
|
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
|
import SimplifiedEditor, { MAX_DESCRIPTION_LIMIT } from '../../Editor/SimplifiedEditor'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
shoutId: number
|
shoutId: number
|
||||||
|
@ -35,12 +36,12 @@ export const PublishSettings = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { user } = useSession()
|
const { user } = useSession()
|
||||||
|
|
||||||
const composeLead = () => {
|
const composeDescription = () => {
|
||||||
if (!props.form.lead) {
|
if (!props.form.description) {
|
||||||
const leadText = props.form.body.replaceAll(/<\/?[^>]+(>|$)/gi, ' ')
|
const leadText = props.form.body.replaceAll(/<\/?[^>]+(>|$)/gi, ' ')
|
||||||
return shorten(leadText, MAX_LEAD_LIMIT).trim()
|
return shorten(leadText, MAX_DESCRIPTION_LIMIT).trim()
|
||||||
}
|
}
|
||||||
return props.form.lead
|
return props.form.description
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialData: Partial<ShoutForm> = {
|
const initialData: Partial<ShoutForm> = {
|
||||||
|
@ -49,7 +50,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
slug: props.form.slug,
|
slug: props.form.slug,
|
||||||
title: props.form.title,
|
title: props.form.title,
|
||||||
subtitle: props.form.subtitle,
|
subtitle: props.form.subtitle,
|
||||||
lead: composeLead()
|
description: composeDescription()
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -183,15 +184,15 @@ export const PublishSettings = (props: Props) => {
|
||||||
allowEnterKey={false}
|
allowEnterKey={false}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
/>
|
/>
|
||||||
<GrowingTextarea
|
<SimplifiedEditor
|
||||||
class={styles.settingInput}
|
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
fieldName={t('Description')}
|
onlyBubbleControls={true}
|
||||||
|
smallHeight={true}
|
||||||
placeholder={t('Write a short introduction')}
|
placeholder={t('Write a short introduction')}
|
||||||
initialValue={`${settingsForm.lead}`}
|
label={t('Description')}
|
||||||
value={(value) => setSettingsForm('lead', value)}
|
initialContent={composeDescription()}
|
||||||
allowEnterKey={false}
|
onChange={(value) => setForm('description', value)}
|
||||||
maxLength={MAX_LEAD_LIMIT}
|
maxLength={MAX_DESCRIPTION_LIMIT}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
padding: 16px 12px;
|
padding: 16px 12px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 2px solid var(--black-100);
|
border: 2px solid var(--black-100);
|
||||||
background: var(--white-500, #fff);
|
background: var(--white-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hasFieldName {
|
&.hasFieldName {
|
||||||
|
|
|
@ -21,12 +21,13 @@ export type ShoutForm = {
|
||||||
slug: string
|
slug: string
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
|
lead?: string
|
||||||
|
description?: string
|
||||||
selectedTopics: Topic[]
|
selectedTopics: Topic[]
|
||||||
mainTopic?: Topic
|
mainTopic?: Topic
|
||||||
body: string
|
body: string
|
||||||
coverImageUrl: string
|
coverImageUrl: string
|
||||||
media?: string
|
media?: string
|
||||||
lead?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditorContextType = {
|
type EditorContextType = {
|
||||||
|
@ -136,6 +137,8 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
slug: formToUpdate.slug,
|
slug: formToUpdate.slug,
|
||||||
subtitle: formToUpdate.subtitle,
|
subtitle: formToUpdate.subtitle,
|
||||||
title: formToUpdate.title,
|
title: formToUpdate.title,
|
||||||
|
lead: formToUpdate.lead,
|
||||||
|
description: formToUpdate.description,
|
||||||
cover: formToUpdate.coverImageUrl,
|
cover: formToUpdate.coverImageUrl,
|
||||||
media: formToUpdate.media
|
media: formToUpdate.media
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,8 @@ export default gql`
|
||||||
slug
|
slug
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
|
lead
|
||||||
|
description
|
||||||
body
|
body
|
||||||
visibility
|
visibility
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ export default gql`
|
||||||
loadShout(slug: $slug, shout_id: $shoutId) {
|
loadShout(slug: $slug, shout_id: $shoutId) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
lead
|
||||||
|
description
|
||||||
visibility
|
visibility
|
||||||
subtitle
|
subtitle
|
||||||
slug
|
slug
|
||||||
|
|
|
@ -5,6 +5,8 @@ export default gql`
|
||||||
loadShouts(options: $options) {
|
loadShouts(options: $options) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
|
lead
|
||||||
|
description
|
||||||
subtitle
|
subtitle
|
||||||
slug
|
slug
|
||||||
layout
|
layout
|
||||||
|
|
|
@ -554,6 +554,7 @@ export type Shout = {
|
||||||
createdAt: Scalars['DateTime']
|
createdAt: Scalars['DateTime']
|
||||||
deletedAt?: Maybe<Scalars['DateTime']>
|
deletedAt?: Maybe<Scalars['DateTime']>
|
||||||
deletedBy?: Maybe<User>
|
deletedBy?: Maybe<User>
|
||||||
|
description?: Maybe<Scalars['String']>
|
||||||
id: Scalars['Int']
|
id: Scalars['Int']
|
||||||
lang?: Maybe<Scalars['String']>
|
lang?: Maybe<Scalars['String']>
|
||||||
layout?: Maybe<Scalars['String']>
|
layout?: Maybe<Scalars['String']>
|
||||||
|
@ -577,7 +578,9 @@ export type ShoutInput = {
|
||||||
body?: InputMaybe<Scalars['String']>
|
body?: InputMaybe<Scalars['String']>
|
||||||
community?: InputMaybe<Scalars['Int']>
|
community?: InputMaybe<Scalars['Int']>
|
||||||
cover?: InputMaybe<Scalars['String']>
|
cover?: InputMaybe<Scalars['String']>
|
||||||
|
description?: InputMaybe<Scalars['String']>
|
||||||
layout?: InputMaybe<Scalars['String']>
|
layout?: InputMaybe<Scalars['String']>
|
||||||
|
lead?: InputMaybe<Scalars['String']>
|
||||||
mainTopic?: InputMaybe<TopicInput>
|
mainTopic?: InputMaybe<TopicInput>
|
||||||
media?: InputMaybe<Scalars['String']>
|
media?: InputMaybe<Scalars['String']>
|
||||||
slug?: InputMaybe<Scalars['String']>
|
slug?: InputMaybe<Scalars['String']>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user