bad update 2
This commit is contained in:
parent
344f716d1d
commit
2fad5b8db9
64
src/components/Editor/MiniEditor/MiniEditor.stories.tsx
Normal file
64
src/components/Editor/MiniEditor/MiniEditor.stories.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { Meta, StoryObj } from 'storybook-solidjs'
|
||||||
|
import MiniEditor from './MiniEditor'
|
||||||
|
|
||||||
|
const meta: Meta<typeof MiniEditor> = {
|
||||||
|
title: 'Components/MiniEditor',
|
||||||
|
component: MiniEditor,
|
||||||
|
argTypes: {
|
||||||
|
content: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Initial content for the editor',
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
control: 'number',
|
||||||
|
description: 'Character limit for the editor',
|
||||||
|
defaultValue: 500
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
control: 'text',
|
||||||
|
description: 'Placeholder text when the editor is empty',
|
||||||
|
defaultValue: 'Start typing here...'
|
||||||
|
},
|
||||||
|
onChange: {
|
||||||
|
action: 'changed',
|
||||||
|
description: 'Callback when the content changes'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof MiniEditor>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
content: '',
|
||||||
|
limit: 500,
|
||||||
|
placeholder: 'Start typing here...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithInitialContent: Story = {
|
||||||
|
args: {
|
||||||
|
content: 'This is some initial content',
|
||||||
|
limit: 500,
|
||||||
|
placeholder: 'Start typing here...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithCharacterLimit: Story = {
|
||||||
|
args: {
|
||||||
|
content: '',
|
||||||
|
limit: 50,
|
||||||
|
placeholder: 'You have a 50 character limit...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithCustomPlaceholder: Story = {
|
||||||
|
args: {
|
||||||
|
content: '',
|
||||||
|
limit: 500,
|
||||||
|
placeholder: 'Custom placeholder here...'
|
||||||
|
}
|
||||||
|
}
|
191
src/components/Editor/MiniEditor/MiniEditor.tsx
Normal file
191
src/components/Editor/MiniEditor/MiniEditor.tsx
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import type { Editor } from '@tiptap/core'
|
||||||
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { type JSX, Show, createEffect, createSignal, onCleanup } from 'solid-js'
|
||||||
|
import {
|
||||||
|
createEditorTransaction,
|
||||||
|
createTiptapEditor,
|
||||||
|
useEditorHTML,
|
||||||
|
useEditorIsEmpty,
|
||||||
|
useEditorIsFocused
|
||||||
|
} from 'solid-tiptap'
|
||||||
|
import { Toolbar } from 'terracotta'
|
||||||
|
|
||||||
|
import { useLocalize } from '~/context/localize'
|
||||||
|
import { useUI } from '~/context/ui'
|
||||||
|
import { base, custom } from '~/lib/editorOptions'
|
||||||
|
import { Icon } from '../_shared/Icon/Icon'
|
||||||
|
import { Popover } from '../_shared/Popover/Popover'
|
||||||
|
import { InsertLinkForm } from './InsertLinkForm/InsertLinkForm'
|
||||||
|
|
||||||
|
import styles from './SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
|
interface ControlProps {
|
||||||
|
editor: Editor
|
||||||
|
title: string
|
||||||
|
key: string
|
||||||
|
onChange: () => void
|
||||||
|
isActive?: (editor: Editor) => boolean
|
||||||
|
children: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
function Control(props: ControlProps): JSX.Element {
|
||||||
|
const handleClick = (ev?: MouseEvent) => {
|
||||||
|
ev?.preventDefault()
|
||||||
|
ev?.stopPropagation()
|
||||||
|
props.onChange?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={props.title}>
|
||||||
|
{(triggerRef: (el: HTMLElement) => void) => (
|
||||||
|
<button
|
||||||
|
ref={triggerRef}
|
||||||
|
type="button"
|
||||||
|
class={clsx(styles.actionButton, { [styles.active]: props.editor.isActive(props.key) })}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MiniEditorProps {
|
||||||
|
content?: string
|
||||||
|
onChange?: (content: string) => void
|
||||||
|
limit?: number
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MiniEditor(props: MiniEditorProps): JSX.Element {
|
||||||
|
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
||||||
|
const [counter, setCounter] = createSignal(0)
|
||||||
|
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
||||||
|
const [showSimpleMenu, setShowSimpleMenu] = createSignal(false)
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const { showModal } = useUI()
|
||||||
|
|
||||||
|
const editor = createTiptapEditor(() => ({
|
||||||
|
element: editorElement()!,
|
||||||
|
extensions: [
|
||||||
|
...base,
|
||||||
|
...custom,
|
||||||
|
Placeholder.configure({ emptyNodeClass: styles.emptyNode, placeholder: props.placeholder }),
|
||||||
|
CharacterCount.configure({ limit: props.limit })
|
||||||
|
],
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: styles.simplifiedEditorField
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: props.content || ''
|
||||||
|
}))
|
||||||
|
|
||||||
|
const isEmpty = useEditorIsEmpty(editor)
|
||||||
|
const isFocused = useEditorIsFocused(editor)
|
||||||
|
const isTextSelection = createEditorTransaction(editor, (instance) => !instance?.state.selection.empty)
|
||||||
|
const html = useEditorHTML(editor)
|
||||||
|
|
||||||
|
createEffect(() => setShowSimpleMenu(isTextSelection()))
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const textLength = editor()?.getText().length || 0
|
||||||
|
setCounter(textLength)
|
||||||
|
const content = html()
|
||||||
|
content && props.onChange?.(content)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLinkClick = () => {
|
||||||
|
setShowLinkInput(!showLinkInput())
|
||||||
|
editor()?.chain().focus().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent focus loss when clicking inside the toolbar
|
||||||
|
const handleMouseDownOnToolbar = (event: MouseEvent) => {
|
||||||
|
event.preventDefault() // Prevent the default focus shift
|
||||||
|
}
|
||||||
|
const [toolbarElement, setToolbarElement] = createSignal<HTMLElement>()
|
||||||
|
// Attach the event handler to the toolbar
|
||||||
|
onCleanup(() => {
|
||||||
|
toolbarElement()?.removeEventListener('mousedown', handleMouseDownOnToolbar)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx(styles.SimplifiedEditor, styles.bordered, {
|
||||||
|
[styles.isFocused]: isEmpty() || isFocused()
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Show when={showSimpleMenu() || showLinkInput()}>
|
||||||
|
<Toolbar style={{ 'background-color': 'white' }} ref={setToolbarElement} horizontal>
|
||||||
|
<Show when={editor()} keyed>
|
||||||
|
{(instance) => (
|
||||||
|
<div class={styles.controls}>
|
||||||
|
<Show
|
||||||
|
when={!showLinkInput()}
|
||||||
|
fallback={<InsertLinkForm editor={instance} onClose={() => setShowLinkInput(false)} />}
|
||||||
|
>
|
||||||
|
<div class={styles.actions}>
|
||||||
|
<Control
|
||||||
|
key="bold"
|
||||||
|
editor={instance}
|
||||||
|
onChange={() => instance.chain().focus().toggleBold().run()}
|
||||||
|
title={t('Bold')}
|
||||||
|
>
|
||||||
|
<Icon name="editor-bold" />
|
||||||
|
</Control>
|
||||||
|
<Control
|
||||||
|
key="italic"
|
||||||
|
editor={instance}
|
||||||
|
onChange={() => instance.chain().focus().toggleItalic().run()}
|
||||||
|
title={t('Italic')}
|
||||||
|
>
|
||||||
|
<Icon name="editor-italic" />
|
||||||
|
</Control>
|
||||||
|
<Control
|
||||||
|
key="link"
|
||||||
|
editor={instance}
|
||||||
|
onChange={handleLinkClick}
|
||||||
|
title={t('Add url')}
|
||||||
|
isActive={showLinkInput}
|
||||||
|
>
|
||||||
|
<Icon name="editor-link" />
|
||||||
|
</Control>
|
||||||
|
<Control
|
||||||
|
key="blockquote"
|
||||||
|
editor={instance}
|
||||||
|
onChange={() => instance.chain().focus().toggleBlockquote().run()}
|
||||||
|
title={t('Add blockquote')}
|
||||||
|
>
|
||||||
|
<Icon name="editor-quote" />
|
||||||
|
</Control>
|
||||||
|
<Control
|
||||||
|
key="image"
|
||||||
|
editor={instance}
|
||||||
|
onChange={() => showModal('simplifiedEditorUploadImage')}
|
||||||
|
title={t('Add image')}
|
||||||
|
>
|
||||||
|
<Icon name="editor-image-dd-full" />
|
||||||
|
</Control>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</Toolbar>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div id="mini-editor" ref={setEditorElement} />
|
||||||
|
|
||||||
|
<Show when={counter() > 0}>
|
||||||
|
<small class={styles.limit}>
|
||||||
|
{counter()} / {props.limit || '∞'}
|
||||||
|
</small>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ const desktopCoverImageWidths: Record<string, number> = {
|
||||||
M: 600,
|
M: 600,
|
||||||
L: 800
|
L: 800
|
||||||
}
|
}
|
||||||
|
const titleSeparator = /{!|\?|:|;}\s/
|
||||||
const getTitleAndSubtitle = (
|
const getTitleAndSubtitle = (
|
||||||
article: Shout
|
article: Shout
|
||||||
): {
|
): {
|
||||||
|
@ -69,7 +69,7 @@ const getTitleAndSubtitle = (
|
||||||
let titleParts = article.title?.split('. ') || []
|
let titleParts = article.title?.split('. ') || []
|
||||||
|
|
||||||
if (titleParts?.length === 1) {
|
if (titleParts?.length === 1) {
|
||||||
titleParts = article.title?.split(/{!|\?|:|;}\s/) || []
|
titleParts = article.title?.split(titleSeparator) || []
|
||||||
}
|
}
|
||||||
|
|
||||||
if (titleParts && titleParts.length > 1) {
|
if (titleParts && titleParts.length > 1) {
|
||||||
|
@ -88,7 +88,7 @@ const getMainTopicTitle = (article: Shout, lng: string) => {
|
||||||
const mainTopicSlug = article.main_topic || ''
|
const mainTopicSlug = article.main_topic || ''
|
||||||
const mainTopic = (article.topics || []).find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
|
const mainTopic = (article.topics || []).find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
|
||||||
const mainTopicTitle =
|
const mainTopicTitle =
|
||||||
mainTopicSlug && lng === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
mainTopicSlug && lng === 'en' ? mainTopicSlug.replaceAll('-', ' ') : mainTopic?.title || ''
|
||||||
|
|
||||||
return [mainTopicTitle, mainTopicSlug]
|
return [mainTopicTitle, mainTopicSlug]
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,6 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class={styles.action}
|
class={styles.action}
|
||||||
role="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onShareClick()
|
props.onShareClick()
|
||||||
setHidePopup(true)
|
setHidePopup(true)
|
||||||
|
@ -47,7 +46,6 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class={styles.action}
|
class={styles.action}
|
||||||
role="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert('Help to edit')
|
alert('Help to edit')
|
||||||
setHidePopup(true)
|
setHidePopup(true)
|
||||||
|
@ -61,7 +59,6 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class={styles.action}
|
class={styles.action}
|
||||||
role="button"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onInviteClick()
|
props.onInviteClick()
|
||||||
setHidePopup(false)
|
setHidePopup(false)
|
||||||
|
@ -73,7 +70,7 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
<Show when={!props.canEdit}>
|
<Show when={!props.canEdit}>
|
||||||
<li>
|
<li>
|
||||||
<button class={clsx(styles.action, styles.soon)} role="button">
|
<button class={clsx(styles.action, styles.soon)}>
|
||||||
<Icon name="bell-white" class={styles.icon} />
|
<Icon name="bell-white" class={styles.icon} />
|
||||||
<div class={styles.title}>{t('Subscribe to comments')}</div>
|
<div class={styles.title}>{t('Subscribe to comments')}</div>
|
||||||
<SoonChip />
|
<SoonChip />
|
||||||
|
@ -81,7 +78,7 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</Show>
|
</Show>
|
||||||
<li>
|
<li>
|
||||||
<button class={clsx(styles.action, styles.soon)} role="button">
|
<button class={clsx(styles.action, styles.soon)}>
|
||||||
<Icon name="bookmark" class={styles.icon} />
|
<Icon name="bookmark" class={styles.icon} />
|
||||||
<div class={styles.title}>{t('Add to bookmarks')}</div>
|
<div class={styles.title}>{t('Add to bookmarks')}</div>
|
||||||
<SoonChip />
|
<SoonChip />
|
||||||
|
@ -91,7 +88,7 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
{/* <li>*/}
|
{/* <li>*/}
|
||||||
{/* <button*/}
|
{/* <button*/}
|
||||||
{/* class={styles.action}*/}
|
{/* class={styles.action}*/}
|
||||||
{/* role="button"*/}
|
{/* */}
|
||||||
{/* onClick={() => {*/}
|
{/* onClick={() => {*/}
|
||||||
{/* alert('Complain')*/}
|
{/* alert('Complain')*/}
|
||||||
{/* }}*/}
|
{/* }}*/}
|
||||||
|
@ -103,7 +100,7 @@ export const FeedArticlePopup = (props: Props) => {
|
||||||
{/*<li>*/}
|
{/*<li>*/}
|
||||||
{/* <button*/}
|
{/* <button*/}
|
||||||
{/* class={styles.action}*/}
|
{/* class={styles.action}*/}
|
||||||
{/* role="button"*/}
|
{/* */}
|
||||||
{/* onClick={() => {*/}
|
{/* onClick={() => {*/}
|
||||||
{/* alert('Get notifications')*/}
|
{/* alert('Get notifications')*/}
|
||||||
{/* }}*/}
|
{/* }}*/}
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const FullTopic = (props: Props) => {
|
||||||
/* FIXME: use title translation*/
|
/* FIXME: use title translation*/
|
||||||
setTitle((_) => tpc?.title || '')
|
setTitle((_) => tpc?.title || '')
|
||||||
return `#${capitalize(
|
return `#${capitalize(
|
||||||
lang() === 'en' ? tpc.slug.replace(/-/, ' ') : tpc.title || tpc.slug.replace(/-/, ' '),
|
lang() === 'en' ? tpc.slug.replaceAll('-', ' ') : tpc.title || tpc.slug.replaceAll('-', ' '),
|
||||||
true
|
true
|
||||||
)}`
|
)}`
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,7 @@ export const FullTopic = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (follows?.topics?.length !== 0) {
|
if (follows?.topics?.length ?? true) {
|
||||||
const items = follows.topics || []
|
const items = follows.topics || []
|
||||||
setFollowed(items.some((x: Topic) => x?.slug === props.topic?.slug))
|
setFollowed(items.some((x: Topic) => x?.slug === props.topic?.slug))
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ export const AllAuthors = (props: Props) => {
|
||||||
|
|
||||||
// store by first char
|
// store by first char
|
||||||
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
|
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||||
if (!(filteredAuthors()?.length > 0)) return {}
|
if (!filteredAuthors()) return {}
|
||||||
console.debug('[components.AllAuthors] update byLetterFiltered', filteredAuthors()?.length)
|
console.debug('[components.AllAuthors] update byLetterFiltered', filteredAuthors()?.length)
|
||||||
return (
|
return (
|
||||||
filteredAuthors()?.reduce(
|
filteredAuthors()?.reduce(
|
||||||
|
|
|
@ -250,7 +250,7 @@ export const ProfileSecurityView = (_props: any) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.socialButton,
|
styles.socialButton,
|
||||||
styles.socialButtonApple,
|
styles.socialButtonApple,
|
||||||
'button' + ' button--light'
|
'button button--light'
|
||||||
)}
|
)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
onMount
|
onMount
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
import MiniEditor from '~/components/Editor/MiniEditor/MiniEditor'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { useProfile } from '~/context/profile'
|
import { useProfile } from '~/context/profile'
|
||||||
import { useSession } from '~/context/session'
|
import { useSession } from '~/context/session'
|
||||||
|
@ -34,7 +35,7 @@ import { SocialNetworkInput } from '../../_shared/SocialNetworkInput'
|
||||||
import styles from './Settings.module.scss'
|
import styles from './Settings.module.scss'
|
||||||
import { profileSocialLinks } from './profileSocialLinks'
|
import { profileSocialLinks } from './profileSocialLinks'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor'))
|
// const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
|
|
||||||
function filterNulls(arr: InputMaybe<string>[]): string[] {
|
function filterNulls(arr: InputMaybe<string>[]): string[] {
|
||||||
|
@ -56,11 +57,11 @@ export const ProfileSettings = () => {
|
||||||
const [slugError, setSlugError] = createSignal<string>()
|
const [slugError, setSlugError] = createSignal<string>()
|
||||||
const [nameError, setNameError] = createSignal<string>()
|
const [nameError, setNameError] = createSignal<string>()
|
||||||
const { form, submit, updateFormField, setForm } = useProfile()
|
const { form, submit, updateFormField, setForm } = useProfile()
|
||||||
|
const [about, setAbout] = createSignal(form.about)
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const { loadSession, session } = useSession()
|
const { loadSession, session } = useSession()
|
||||||
const [prevForm, setPrevForm] = createStore<ProfileInput>()
|
const [prevForm, setPrevForm] = createStore<ProfileInput>()
|
||||||
const { showConfirm } = useUI()
|
const { showConfirm } = useUI()
|
||||||
const [clearAbout, setClearAbout] = createSignal(false)
|
|
||||||
const { showModal, hideModal } = useUI()
|
const { showModal, hideModal } = useUI()
|
||||||
const [loading, setLoading] = createSignal(true)
|
const [loading, setLoading] = createSignal(true)
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ export const ProfileSettings = () => {
|
||||||
try {
|
try {
|
||||||
await submit(form)
|
await submit(form)
|
||||||
setPrevForm(clone(form))
|
setPrevForm(clone(form))
|
||||||
|
setAbout(form.about)
|
||||||
showSnackbar({ body: t('Profile successfully saved') })
|
showSnackbar({ body: t('Profile successfully saved') })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.toString().search('duplicate_slug')) {
|
if (error?.toString().search('duplicate_slug')) {
|
||||||
|
@ -132,11 +134,7 @@ export const ProfileSettings = () => {
|
||||||
confirmButtonVariant: 'primary',
|
confirmButtonVariant: 'primary',
|
||||||
declineButtonVariant: 'secondary'
|
declineButtonVariant: 'secondary'
|
||||||
})
|
})
|
||||||
if (isConfirmed) {
|
isConfirmed && setForm(clone(prevForm))
|
||||||
setClearAbout(true)
|
|
||||||
setForm(clone(prevForm))
|
|
||||||
setClearAbout(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCropAvatar = () => {
|
const handleCropAvatar = () => {
|
||||||
|
@ -254,7 +252,7 @@ export const ProfileSettings = () => {
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
{/* @@TODO inspect popover below. onClick causes page refreshing */}
|
{/* @@TODO inspect popover below. onClick causes page refreshing */}
|
||||||
{/* <Popover content={t('Upload userpic')}>
|
<Popover content={t('Upload userpic')}>
|
||||||
{(triggerRef: (el: HTMLElement) => void) => (
|
{(triggerRef: (el: HTMLElement) => void) => (
|
||||||
<button
|
<button
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
|
@ -264,7 +262,7 @@ export const ProfileSettings = () => {
|
||||||
<Icon name="user-image-black" />
|
<Icon name="user-image-black" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Popover> */}
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!form.pic}>
|
<Match when={!form.pic}>
|
||||||
|
@ -284,18 +282,20 @@ export const ProfileSettings = () => {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<input
|
<label for="nameOfUser">
|
||||||
type="text"
|
<input
|
||||||
name="nameOfUser"
|
type="text"
|
||||||
id="nameOfUser"
|
name="nameOfUser"
|
||||||
data-lpignore="true"
|
id="nameOfUser"
|
||||||
autocomplete="one-time-code"
|
data-lpignore="true"
|
||||||
placeholder={t('Name')}
|
autocomplete="one-time-code"
|
||||||
onInput={(event) => updateFormField('name', event.currentTarget.value)}
|
placeholder={t('Name')}
|
||||||
value={form.name || ''}
|
onInput={(event) => updateFormField('name', event.currentTarget.value)}
|
||||||
ref={(el) => (nameInputRef = el)}
|
value={form.name || ''}
|
||||||
/>
|
ref={(el) => (nameInputRef = el)}
|
||||||
<label for="nameOfUser">{t('Name')}</label>
|
/>
|
||||||
|
{t('Name')}
|
||||||
|
</label>
|
||||||
<Show when={nameError()}>
|
<Show when={nameError()}>
|
||||||
<div
|
<div
|
||||||
style={{ position: 'absolute', 'margin-top': '-4px' }}
|
style={{ position: 'absolute', 'margin-top': '-4px' }}
|
||||||
|
@ -340,17 +340,10 @@ export const ProfileSettings = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h4>{t('About')}</h4>
|
<h4>{t('About')}</h4>
|
||||||
<SimplifiedEditor
|
<MiniEditor
|
||||||
resetToInitial={clearAbout()}
|
content={about() || ''}
|
||||||
noLimits={true}
|
|
||||||
variant="bordered"
|
|
||||||
onlyBubbleControls={true}
|
|
||||||
smallHeight={true}
|
|
||||||
placeholder={t('About')}
|
|
||||||
label={t('About')}
|
|
||||||
initialContent={form.about || ''}
|
|
||||||
autoFocus={false}
|
|
||||||
onChange={(value) => updateFormField('about', value)}
|
onChange={(value) => updateFormField('about', value)}
|
||||||
|
placeholder={t('About')}
|
||||||
/>
|
/>
|
||||||
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
|
<div class={clsx(styles.multipleControls, 'pretty-form__item')}>
|
||||||
<div class={styles.multipleControlsHeader}>
|
<div class={styles.multipleControlsHeader}>
|
||||||
|
|
|
@ -151,16 +151,6 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
||||||
setFollows((prevFollows: AuthorFollowsResult) => {
|
setFollows((prevFollows: AuthorFollowsResult) => {
|
||||||
const updatedFollows = { ...prevFollows }
|
const updatedFollows = { ...prevFollows }
|
||||||
switch (what) {
|
switch (what) {
|
||||||
case 'AUTHOR': {
|
|
||||||
if (value) {
|
|
||||||
if (!updatedFollows.authors?.some((author) => author.slug === slug)) {
|
|
||||||
updatedFollows.authors = [...(updatedFollows.authors || []), { slug } as Author]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updatedFollows.authors = updatedFollows.authors?.filter((author) => author.slug !== slug) || []
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'TOPIC': {
|
case 'TOPIC': {
|
||||||
if (value) {
|
if (value) {
|
||||||
if (!updatedFollows.topics?.some((topic) => topic.slug === slug)) {
|
if (!updatedFollows.topics?.some((topic) => topic.slug === slug)) {
|
||||||
|
@ -182,6 +172,17 @@ export const FollowingProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
// case 'AUTHOR':
|
||||||
|
default: {
|
||||||
|
if (value) {
|
||||||
|
if (!updatedFollows.authors?.some((author) => author.slug === slug)) {
|
||||||
|
updatedFollows.authors = [...(updatedFollows.authors || []), { slug } as Author]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updatedFollows.authors = updatedFollows.authors?.filter((author) => author.slug !== slug) || []
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return updatedFollows
|
return updatedFollows
|
||||||
})
|
})
|
||||||
|
|
103
src/lib/editorOptions.ts
Normal file
103
src/lib/editorOptions.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { EditorOptions } from '@tiptap/core'
|
||||||
|
import Highlight from '@tiptap/extension-highlight'
|
||||||
|
import Image from '@tiptap/extension-image'
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
import Underline from '@tiptap/extension-underline'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { CustomBlockquote } from '~/components/Editor/extensions/CustomBlockquote'
|
||||||
|
import { Figcaption } from '~/components/Editor/extensions/Figcaption'
|
||||||
|
import { Figure } from '~/components/Editor/extensions/Figure'
|
||||||
|
import { Footnote } from '~/components/Editor/extensions/Footnote'
|
||||||
|
import { Iframe } from '~/components/Editor/extensions/Iframe'
|
||||||
|
import { Span } from '~/components/Editor/extensions/Span'
|
||||||
|
import { ToggleTextWrap } from '~/components/Editor/extensions/ToggleTextWrap'
|
||||||
|
import { TrailingNode } from '~/components/Editor/extensions/TrailingNode'
|
||||||
|
|
||||||
|
// Extend the Figure extension to include Figcaption
|
||||||
|
const ImageFigure = Figure.extend({
|
||||||
|
name: 'capturedImage',
|
||||||
|
content: 'figcaption image'
|
||||||
|
})
|
||||||
|
|
||||||
|
export const base: EditorOptions['extensions'] = [
|
||||||
|
StarterKit.configure({
|
||||||
|
heading: {
|
||||||
|
levels: [2, 3, 4]
|
||||||
|
},
|
||||||
|
horizontalRule: {
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'horizontalRule'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockquote: undefined
|
||||||
|
}),
|
||||||
|
Underline, // не входит в StarterKit
|
||||||
|
Link.configure({
|
||||||
|
autolink: true,
|
||||||
|
openOnClick: false
|
||||||
|
}),
|
||||||
|
Image,
|
||||||
|
Highlight.configure({
|
||||||
|
multicolor: true,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'highlight'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
export const custom: EditorOptions['extensions'] = [
|
||||||
|
ImageFigure,
|
||||||
|
Figure,
|
||||||
|
Figcaption,
|
||||||
|
Footnote,
|
||||||
|
CustomBlockquote,
|
||||||
|
Iframe,
|
||||||
|
Span,
|
||||||
|
ToggleTextWrap,
|
||||||
|
TrailingNode
|
||||||
|
// Добавьте другие кастомные расширения здесь
|
||||||
|
]
|
||||||
|
|
||||||
|
export const collab: EditorOptions['extensions'] = []
|
||||||
|
/*
|
||||||
|
content: '',
|
||||||
|
autofocus: false,
|
||||||
|
editable: false,
|
||||||
|
element: undefined,
|
||||||
|
injectCSS: false,
|
||||||
|
injectNonce: undefined,
|
||||||
|
editorProps: {} as EditorProps,
|
||||||
|
parseOptions: {} as EditorOptions['parseOptions'],
|
||||||
|
enableInputRules: false,
|
||||||
|
enablePasteRules: false,
|
||||||
|
enableCoreExtensions: false,
|
||||||
|
enableContentCheck: false,
|
||||||
|
onBeforeCreate: (_props: EditorEvents['beforeCreate']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onCreate: (_props: EditorEvents['create']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onContentError: (_props: EditorEvents['contentError']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onUpdate: (_props: EditorEvents['update']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onSelectionUpdate: (_props: EditorEvents['selectionUpdate']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onTransaction: (_props: EditorEvents['transaction']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onFocus: (_props: EditorEvents['focus']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onBlur: (_props: EditorEvents['blur']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
},
|
||||||
|
onDestroy: (_props: EditorEvents['destroy']): void => {
|
||||||
|
throw new Error('Function not implemented.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
Loading…
Reference in New Issue
Block a user