editor-refactoring
This commit is contained in:
parent
b393810f7a
commit
595e2b8a4b
|
@ -9,7 +9,7 @@ import { SharePopup, getShareUrl } from '../SharePopup'
|
||||||
|
|
||||||
import styles from './AudioPlayer.module.scss'
|
import styles from './AudioPlayer.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -171,10 +171,9 @@ export const PlayerPlaylist = (props: Props) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class={styles.descriptionBlock}>
|
<div class={styles.descriptionBlock}>
|
||||||
<SimplifiedEditor
|
<MicroEditor
|
||||||
initialContent={mi.body}
|
content={mi.body}
|
||||||
placeholder={`${t('Description')}...`}
|
placeholder={`${t('Description')}...`}
|
||||||
smallHeight={true}
|
|
||||||
onChange={(value) => handleMediaItemFieldChange('body', value)}
|
onChange={(value) => handleMediaItemFieldChange('body', value)}
|
||||||
/>
|
/>
|
||||||
<GrowingTextarea
|
<GrowingTextarea
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { CommentDate } from '../CommentDate'
|
||||||
import { CommentRatingControl } from '../CommentRatingControl'
|
import { CommentRatingControl } from '../CommentRatingControl'
|
||||||
import styles from './Comment.module.scss'
|
import styles from './Comment.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor'))
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comment: Reaction
|
comment: Reaction
|
||||||
|
@ -41,7 +41,6 @@ export const Comment = (props: Props) => {
|
||||||
const [isReplyVisible, setIsReplyVisible] = createSignal(false)
|
const [isReplyVisible, setIsReplyVisible] = createSignal(false)
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
const [editMode, setEditMode] = createSignal(false)
|
const [editMode, setEditMode] = createSignal(false)
|
||||||
const [clearEditor, setClearEditor] = createSignal(false)
|
|
||||||
const [editedBody, setEditedBody] = createSignal<string>()
|
const [editedBody, setEditedBody] = createSignal<string>()
|
||||||
const { session, client } = useSession()
|
const { session, client } = useSession()
|
||||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||||
|
@ -104,13 +103,11 @@ export const Comment = (props: Props) => {
|
||||||
shout: props.comment.shout.id
|
shout: props.comment.shout.id
|
||||||
}
|
}
|
||||||
} as MutationCreate_ReactionArgs)
|
} as MutationCreate_ReactionArgs)
|
||||||
setClearEditor(true)
|
|
||||||
setIsReplyVisible(false)
|
setIsReplyVisible(false)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[handleCreate reaction]:', error)
|
console.error('[handleCreate reaction]:', error)
|
||||||
}
|
}
|
||||||
setClearEditor(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleEditMode = () => {
|
const toggleEditMode = () => {
|
||||||
|
@ -189,16 +186,11 @@ export const Comment = (props: Props) => {
|
||||||
<div class={styles.commentBody}>
|
<div class={styles.commentBody}>
|
||||||
<Show when={editMode()} fallback={<div innerHTML={body()} />}>
|
<Show when={editMode()} fallback={<div innerHTML={body()} />}>
|
||||||
<Suspense fallback={<p>{t('Loading')}</p>}>
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
||||||
<SimplifiedEditor
|
<MiniEditor
|
||||||
initialContent={editedBody() || props.comment.body || ''}
|
content={editedBody() || props.comment.body || ''}
|
||||||
submitButtonText={t('Save')}
|
|
||||||
quoteEnabled={true}
|
|
||||||
imageEnabled={true}
|
|
||||||
placeholder={t('Write a comment...')}
|
placeholder={t('Write a comment...')}
|
||||||
onSubmit={(value) => handleUpdate(value)}
|
onSubmit={(value) => handleUpdate(value)}
|
||||||
submitByCtrlEnter={true}
|
|
||||||
onCancel={() => setEditMode(false)}
|
onCancel={() => setEditMode(false)}
|
||||||
setClear={clearEditor()}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -258,12 +250,9 @@ export const Comment = (props: Props) => {
|
||||||
|
|
||||||
<Show when={isReplyVisible() && props.clickedReplyId === props.comment.id}>
|
<Show when={isReplyVisible() && props.clickedReplyId === props.comment.id}>
|
||||||
<Suspense fallback={<p>{t('Loading')}</p>}>
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
||||||
<SimplifiedEditor
|
<MiniEditor
|
||||||
quoteEnabled={true}
|
|
||||||
imageEnabled={true}
|
|
||||||
placeholder={t('Write a comment...')}
|
placeholder={t('Write a comment...')}
|
||||||
onSubmit={(value) => handleCreate(value)}
|
onSubmit={(value) => handleCreate(value)}
|
||||||
submitByCtrlEnter={true}
|
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -9,11 +9,12 @@ import { Author, Reaction, ReactionKind, ReactionSort } from '~/graphql/schema/c
|
||||||
import { SortFunction } from '~/types/common'
|
import { SortFunction } from '~/types/common'
|
||||||
import { byCreated, byStat } from '~/utils/sort'
|
import { byCreated, byStat } from '~/utils/sort'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
|
import { Loading } from '../_shared/Loading'
|
||||||
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
||||||
import styles from './Article.module.scss'
|
import styles from './Article.module.scss'
|
||||||
import { Comment } from './Comment'
|
import { Comment } from './Comment'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
|
const MiniEditor = lazy(() => import('../Editor/MiniEditor/MiniEditor'))
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
articleAuthors: Author[]
|
articleAuthors: Author[]
|
||||||
|
@ -27,7 +28,6 @@ export const CommentsTree = (props: Props) => {
|
||||||
const [commentsOrder, setCommentsOrder] = createSignal<ReactionSort>(ReactionSort.Newest)
|
const [commentsOrder, setCommentsOrder] = createSignal<ReactionSort>(ReactionSort.Newest)
|
||||||
const [onlyNew, setOnlyNew] = createSignal(false)
|
const [onlyNew, setOnlyNew] = createSignal(false)
|
||||||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||||
const [clearEditor, setClearEditor] = createSignal(false)
|
|
||||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||||
const { reactionEntities, createShoutReaction, loadReactionsBy } = useReactions()
|
const { reactionEntities, createShoutReaction, loadReactionsBy } = useReactions()
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
setCookie()
|
setCookie()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [posting, setPosting] = createSignal(false)
|
const [posting, setPosting] = createSignal(false)
|
||||||
const handleSubmitComment = async (value: string) => {
|
const handleSubmitComment = async (value: string) => {
|
||||||
setPosting(true)
|
setPosting(true)
|
||||||
|
@ -81,12 +82,10 @@ export const CommentsTree = (props: Props) => {
|
||||||
shout: props.shoutId
|
shout: props.shoutId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setClearEditor(true)
|
|
||||||
await loadReactionsBy({ by: { shout: props.shoutSlug } })
|
await loadReactionsBy({ by: { shout: props.shoutSlug } })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[handleCreate reaction]:', error)
|
console.error('[handleCreate reaction]:', error)
|
||||||
}
|
}
|
||||||
setClearEditor(false)
|
|
||||||
setPosting(false)
|
setPosting(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,16 +154,13 @@ export const CommentsTree = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SimplifiedEditor
|
<MiniEditor
|
||||||
quoteEnabled={true}
|
|
||||||
imageEnabled={true}
|
|
||||||
autoFocus={false}
|
|
||||||
submitByCtrlEnter={true}
|
|
||||||
placeholder={t('Write a comment...')}
|
placeholder={t('Write a comment...')}
|
||||||
onSubmit={(value) => handleSubmitComment(value)}
|
onSubmit={(value) => handleSubmitComment(value)}
|
||||||
setClear={clearEditor()}
|
|
||||||
isPosting={posting()}
|
|
||||||
/>
|
/>
|
||||||
|
<Show when={posting()}>
|
||||||
|
<Loading />
|
||||||
|
</Show>
|
||||||
</ShowIfAuthenticated>
|
</ShowIfAuthenticated>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
|
import { renderUploadedImage } from '~/components/Upload/renderUploadedImage'
|
||||||
import { renderUploadedImage } from '~/components/Editor/renderUploadedImage'
|
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
import { Popover } from '~/components/_shared/Popover'
|
import { Popover } from '~/components/_shared/Popover'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { UploadedFile } from '~/types/upload'
|
|
||||||
import { Modal } from '../../_shared/Modal'
|
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
|
||||||
|
|
||||||
import { useUI } from '~/context/ui'
|
import { useUI } from '~/context/ui'
|
||||||
|
import { UploadedFile } from '~/types/upload'
|
||||||
|
import { UploadModalContent } from '../../Upload/UploadModalContent'
|
||||||
|
import { Modal } from '../../_shared/Modal'
|
||||||
|
|
||||||
import styles from './BubbleMenu.module.scss'
|
import styles from './BubbleMenu.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -1,105 +1,28 @@
|
||||||
import { Editor, EditorOptions } from '@tiptap/core'
|
|
||||||
import { createSignal } from 'solid-js'
|
|
||||||
import { createStore } from 'solid-js/store'
|
|
||||||
import { Meta, StoryObj } from 'storybook-solidjs'
|
import { Meta, StoryObj } from 'storybook-solidjs'
|
||||||
import { EditorContext, EditorContextType, ShoutForm } from '~/context/editor'
|
import { EditorComponent } from './Editor'
|
||||||
import { LocalizeContext, LocalizeContextType } from '~/context/localize'
|
|
||||||
import { SessionContext, SessionContextType } from '~/context/session'
|
|
||||||
import { SnackbarContext, SnackbarContextType } from '~/context/ui'
|
|
||||||
import { EditorComponent, EditorComponentProps } from './Editor'
|
|
||||||
|
|
||||||
// Mock data
|
|
||||||
const mockSession = {
|
|
||||||
session: () => ({
|
|
||||||
user: {
|
|
||||||
app_data: {
|
|
||||||
profile: {
|
|
||||||
name: 'Test User',
|
|
||||||
slug: 'test-user'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
access_token: 'mock-access-token'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockLocalize = {
|
|
||||||
t: (key: string) => key,
|
|
||||||
lang: () => 'en'
|
|
||||||
}
|
|
||||||
|
|
||||||
const [_form, setForm] = createStore<ShoutForm>({
|
|
||||||
body: '',
|
|
||||||
slug: '',
|
|
||||||
shoutId: 0,
|
|
||||||
title: '',
|
|
||||||
selectedTopics: []
|
|
||||||
})
|
|
||||||
const [_formErrors, setFormErrors] = createStore({} as Record<keyof ShoutForm, string>)
|
|
||||||
const [editor, setEditor] = createSignal<Editor | undefined>()
|
|
||||||
|
|
||||||
const mockEditorContext: EditorContextType = {
|
|
||||||
countWords: () => 0,
|
|
||||||
isEditorPanelVisible: () => false,
|
|
||||||
wordCounter: () => ({ characters: 0, words: 0 }),
|
|
||||||
form: _form,
|
|
||||||
formErrors: _formErrors,
|
|
||||||
createEditor: (opts?: Partial<EditorOptions>) => {
|
|
||||||
const newEditor = new Editor(opts)
|
|
||||||
setEditor(newEditor)
|
|
||||||
return newEditor
|
|
||||||
},
|
|
||||||
editor,
|
|
||||||
saveShout: async (_form: ShoutForm) => {
|
|
||||||
// Simulate save
|
|
||||||
},
|
|
||||||
saveDraft: async (_form: ShoutForm) => {
|
|
||||||
// Simulate save draft
|
|
||||||
},
|
|
||||||
saveDraftToLocalStorage: (_form: ShoutForm) => {
|
|
||||||
// Simulate save to local storage
|
|
||||||
},
|
|
||||||
getDraftFromLocalStorage: (_shoutId: number): ShoutForm => _form,
|
|
||||||
publishShout: async (_form: ShoutForm) => {
|
|
||||||
// Simulate publish
|
|
||||||
},
|
|
||||||
publishShoutById: async (_shoutId: number) => {
|
|
||||||
// Simulate publish by ID
|
|
||||||
},
|
|
||||||
deleteShout: async (_shoutId: number): Promise<boolean> => true,
|
|
||||||
toggleEditorPanel: () => {
|
|
||||||
// Simulate toggle
|
|
||||||
},
|
|
||||||
setForm,
|
|
||||||
setFormErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockSnackbarContext = {
|
|
||||||
showSnackbar: console.log
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta: Meta<typeof EditorComponent> = {
|
const meta: Meta<typeof EditorComponent> = {
|
||||||
title: 'Components/Editor',
|
title: 'Components/Editor',
|
||||||
component: EditorComponent,
|
component: EditorComponent,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
shoutId: {
|
content: {
|
||||||
control: 'number',
|
|
||||||
description: 'Unique identifier for the shout (document)',
|
|
||||||
defaultValue: 1
|
|
||||||
},
|
|
||||||
initialContent: {
|
|
||||||
control: 'text',
|
control: 'text',
|
||||||
description: 'Initial content for the editor',
|
description: 'Initial content for the editor',
|
||||||
defaultValue: ''
|
defaultValue: ''
|
||||||
},
|
},
|
||||||
onChange: {
|
limit: {
|
||||||
action: 'contentChanged',
|
control: 'number',
|
||||||
description: 'Callback when the content changes'
|
description: 'Character limit for the editor',
|
||||||
|
defaultValue: 500
|
||||||
},
|
},
|
||||||
disableCollaboration: {
|
placeholder: {
|
||||||
control: 'boolean',
|
control: 'text',
|
||||||
description: 'Disable collaboration features for Storybook',
|
description: 'Placeholder text when the editor is empty',
|
||||||
defaultValue: true
|
defaultValue: 'Start typing here...'
|
||||||
|
},
|
||||||
|
onChange: {
|
||||||
|
action: 'changed',
|
||||||
|
description: 'Callback when the content changes'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,38 +32,33 @@ export default meta
|
||||||
type Story = StoryObj<typeof EditorComponent>
|
type Story = StoryObj<typeof EditorComponent>
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: (props: EditorComponentProps) => {
|
|
||||||
const [_content, setContent] = createSignal(props.initialContent || '')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SessionContext.Provider value={mockSession as SessionContextType}>
|
|
||||||
<LocalizeContext.Provider value={mockLocalize as LocalizeContextType}>
|
|
||||||
<SnackbarContext.Provider value={mockSnackbarContext as SnackbarContextType}>
|
|
||||||
<EditorContext.Provider value={mockEditorContext as EditorContextType}>
|
|
||||||
<EditorComponent
|
|
||||||
{...props}
|
|
||||||
onChange={(text: string) => {
|
|
||||||
props.onChange(text)
|
|
||||||
setContent(text)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EditorContext.Provider>
|
|
||||||
</SnackbarContext.Provider>
|
|
||||||
</LocalizeContext.Provider>
|
|
||||||
</SessionContext.Provider>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
args: {
|
args: {
|
||||||
shoutId: 1,
|
content: '',
|
||||||
initialContent: '',
|
limit: 500,
|
||||||
disableCollaboration: true
|
placeholder: 'Start typing here...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithInitialContent: Story = {
|
export const WithInitialContent: Story = {
|
||||||
...Default,
|
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
content: 'This is some initial content',
|
||||||
initialContent: '<p>This is some initial content in the editor.</p>'
|
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...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@ import { useSnackbar } from '~/context/ui'
|
||||||
import { Author } from '~/graphql/schema/core.gen'
|
import { Author } from '~/graphql/schema/core.gen'
|
||||||
import { base, custom, extended } from '~/lib/editorExtensions'
|
import { base, custom, extended } from '~/lib/editorExtensions'
|
||||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||||
|
import { renderUploadedImage } from '../Upload/renderUploadedImage'
|
||||||
import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
||||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
import { renderUploadedImage } from './renderUploadedImage'
|
|
||||||
|
|
||||||
import './Prosemirror.scss'
|
import './Prosemirror.scss'
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
import { Show, createEffect, createSignal } from 'solid-js'
|
import { Show, createEffect, createSignal } from 'solid-js'
|
||||||
|
import { renderUploadedImage } from '~/components/Upload/renderUploadedImage'
|
||||||
import { renderUploadedImage } from '~/components/Editor/renderUploadedImage'
|
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { useUI } from '~/context/ui'
|
import { useUI } from '~/context/ui'
|
||||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||||
import { UploadedFile } from '~/types/upload'
|
import { UploadedFile } from '~/types/upload'
|
||||||
|
import { UploadModalContent } from '../../Upload/UploadModalContent'
|
||||||
|
import { InlineForm } from '../../_shared/InlineForm'
|
||||||
import { Modal } from '../../_shared/Modal'
|
import { Modal } from '../../_shared/Modal'
|
||||||
import { InlineForm } from '../InlineForm'
|
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
|
||||||
import { Menu } from './Menu'
|
import { Menu } from './Menu'
|
||||||
import type { MenuItem } from './Menu/Menu'
|
import type { MenuItem } from './Menu/Menu'
|
||||||
|
|
||||||
|
|
113
src/components/Editor/EditorToolbar/MicroToolbar.tsx
Normal file
113
src/components/Editor/EditorToolbar/MicroToolbar.tsx
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { Editor } from '@tiptap/core'
|
||||||
|
import { Show, createEffect, createSignal, on } from 'solid-js'
|
||||||
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
import { Icon } from '~/components/_shared/Icon/Icon'
|
||||||
|
import { useLocalize } from '~/context/localize'
|
||||||
|
import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm'
|
||||||
|
import { ToolbarControl as Control } from './ToolbarControl'
|
||||||
|
|
||||||
|
import styles from '../SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
|
export interface MicroToolbarProps {
|
||||||
|
showing?: boolean
|
||||||
|
editor?: Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MicroToolbar = (props: MicroToolbarProps) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
// show / hide for menu
|
||||||
|
const [showSimpleMenu, setShowSimpleMenu] = createSignal(!props.showing)
|
||||||
|
const selection = createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(instance) => instance?.state.selection
|
||||||
|
)
|
||||||
|
|
||||||
|
// show / hide for link input
|
||||||
|
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
||||||
|
|
||||||
|
// change visibility on selection if not in link input mode
|
||||||
|
createEffect(on([selection, showLinkInput], ([s, l]) => !l && setShowSimpleMenu(!s?.empty)))
|
||||||
|
|
||||||
|
// focus on link input when it shows up
|
||||||
|
createEffect(on(showLinkInput, (x?: boolean) => x && props.editor?.chain().focus().run()))
|
||||||
|
|
||||||
|
const [storedSelection, setStoredSelection] = createSignal<Editor['state']['selection']>()
|
||||||
|
const recoverSelection = () => {
|
||||||
|
if (!storedSelection()?.empty) {
|
||||||
|
createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(instance?: Editor) => {
|
||||||
|
const r = selection()
|
||||||
|
if (instance && r) {
|
||||||
|
instance.state.selection.from === r.from
|
||||||
|
instance.state.selection.to === r.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const storeSelection = () => {
|
||||||
|
const selection = props.editor?.state.selection
|
||||||
|
if (!selection?.empty) {
|
||||||
|
setStoredSelection(selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const toggleShowLink = () => {
|
||||||
|
if (showLinkInput()) {
|
||||||
|
props.editor?.chain().focus().run()
|
||||||
|
recoverSelection()
|
||||||
|
} else {
|
||||||
|
storeSelection()
|
||||||
|
}
|
||||||
|
setShowLinkInput(!showLinkInput())
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Show when={props.editor} keyed>
|
||||||
|
{(instance) => (
|
||||||
|
<Show when={!showSimpleMenu()}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
background: 'var(--editor-bubble-menu-background)',
|
||||||
|
border: '1px solid black'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class={styles.controls}>
|
||||||
|
<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={toggleShowLink}
|
||||||
|
title={t('Add url')}
|
||||||
|
isActive={showLinkInput}
|
||||||
|
>
|
||||||
|
<Icon name="editor-link" />
|
||||||
|
</Control>
|
||||||
|
</div>
|
||||||
|
<Show when={showLinkInput()}>
|
||||||
|
<InsertLinkForm editor={instance} onClose={toggleShowLink} />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
}
|
117
src/components/Editor/EditorToolbar/MiniToolbar.tsx
Normal file
117
src/components/Editor/EditorToolbar/MiniToolbar.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { Editor } from '@tiptap/core'
|
||||||
|
import { Show, createEffect, createSignal, on } from 'solid-js'
|
||||||
|
import { Icon } from '~/components/_shared/Icon/Icon'
|
||||||
|
import { useLocalize } from '~/context/localize'
|
||||||
|
import { useUI } from '~/context/ui'
|
||||||
|
import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm'
|
||||||
|
import { ToolbarControl as Control } from './ToolbarControl'
|
||||||
|
|
||||||
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
import styles from '../SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
|
interface MiniToolbarProps {
|
||||||
|
editor?: Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MiniToolbar = (props: MiniToolbarProps) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const { showModal } = useUI()
|
||||||
|
|
||||||
|
// show / hide for link input
|
||||||
|
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
||||||
|
|
||||||
|
// focus on link input when it shows up
|
||||||
|
createEffect(on(showLinkInput, (x?: boolean) => x && props.editor?.chain().focus().run()))
|
||||||
|
|
||||||
|
const selection = createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(instance) => instance?.state.selection
|
||||||
|
)
|
||||||
|
const [storedSelection, setStoredSelection] = createSignal<Editor['state']['selection']>()
|
||||||
|
const recoverSelection = () => {
|
||||||
|
if (!storedSelection()?.empty) {
|
||||||
|
createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(instance?: Editor) => {
|
||||||
|
const r = selection()
|
||||||
|
if (instance && r) {
|
||||||
|
instance.state.selection.from === r.from
|
||||||
|
instance.state.selection.to === r.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const storeSelection = () => {
|
||||||
|
const selection = props.editor?.state.selection
|
||||||
|
if (!selection?.empty) {
|
||||||
|
setStoredSelection(selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const toggleShowLink = () => {
|
||||||
|
if (showLinkInput()) {
|
||||||
|
props.editor?.chain().focus().run()
|
||||||
|
recoverSelection()
|
||||||
|
} else {
|
||||||
|
storeSelection()
|
||||||
|
}
|
||||||
|
setShowLinkInput(!showLinkInput())
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ 'background-color': 'white', display: 'inline-flex' }}>
|
||||||
|
<Show when={props.editor} keyed>
|
||||||
|
{(instance) => (
|
||||||
|
<div class={styles.controls}>
|
||||||
|
<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={toggleShowLink}
|
||||||
|
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 when={showLinkInput()}>
|
||||||
|
<InsertLinkForm editor={instance} onClose={toggleShowLink} />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -4,13 +4,13 @@ import { createEditorTransaction, useEditorHTML, useEditorIsEmpty } from 'solid-
|
||||||
import { useEditorContext } from '~/context/editor'
|
import { useEditorContext } from '~/context/editor'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { useUI } from '~/context/ui'
|
import { useUI } from '~/context/ui'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Loading } from '../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
import { SimplifiedEditorProps } from './SimplifiedEditor'
|
import { SimplifiedEditorProps } from '../SimplifiedEditor'
|
||||||
|
|
||||||
import styles from './SimplifiedEditor.module.scss'
|
import styles from '../SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
export const ToolbarControls = (
|
export const ToolbarControls = (
|
||||||
props: SimplifiedEditorProps & { setShouldShowLinkBubbleMenu: (x: boolean) => void }
|
props: SimplifiedEditorProps & { setShouldShowLinkBubbleMenu: (x: boolean) => void }
|
40
src/components/Editor/EditorToolbar/ToolbarControl.tsx
Normal file
40
src/components/Editor/EditorToolbar/ToolbarControl.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Editor } from '@tiptap/core'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { JSX } from 'solid-js'
|
||||||
|
import { Popover } from '~/components/_shared/Popover'
|
||||||
|
|
||||||
|
import styles from '../SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
|
interface ControlProps {
|
||||||
|
editor: Editor
|
||||||
|
title: string
|
||||||
|
key: string
|
||||||
|
onChange: () => void
|
||||||
|
isActive?: (editor: Editor) => boolean
|
||||||
|
children: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ToolbarControl = (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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolbarControl
|
|
@ -3,7 +3,7 @@ import { createEffect, createSignal, onCleanup } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { validateUrl } from '~/utils/validate'
|
import { validateUrl } from '~/utils/validate'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../../_shared/InlineForm'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
|
|
@ -1,68 +1,21 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { type JSX, Show, createEffect, createReaction, createSignal, on, onCleanup } from 'solid-js'
|
import { type JSX, createEffect, createSignal, on } from 'solid-js'
|
||||||
import {
|
import { createTiptapEditor, useEditorHTML, useEditorIsEmpty, useEditorIsFocused } from 'solid-tiptap'
|
||||||
createEditorTransaction,
|
|
||||||
createTiptapEditor,
|
|
||||||
useEditorHTML,
|
|
||||||
useEditorIsEmpty,
|
|
||||||
useEditorIsFocused
|
|
||||||
} from 'solid-tiptap'
|
|
||||||
import { Icon } from '~/components/_shared/Icon/Icon'
|
|
||||||
import { Popover } from '~/components/_shared/Popover/Popover'
|
|
||||||
import { useLocalize } from '~/context/localize'
|
|
||||||
import { minimal } from '~/lib/editorExtensions'
|
import { minimal } from '~/lib/editorExtensions'
|
||||||
import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm'
|
import { MicroToolbar } from '../EditorToolbar/MicroToolbar'
|
||||||
|
|
||||||
import styles from '../SimplifiedEditor.module.scss'
|
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 MicroEditorProps {
|
interface MicroEditorProps {
|
||||||
content?: string
|
content?: string
|
||||||
onChange?: (content: string) => void
|
onChange?: (content: string) => void
|
||||||
|
onSubmit?: (content: string) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevent = (e: Event) => e.preventDefault()
|
|
||||||
|
|
||||||
export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
|
export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
|
||||||
const { t } = useLocalize()
|
|
||||||
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
||||||
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
|
||||||
const [showSimpleMenu, setShowSimpleMenu] = createSignal(false)
|
|
||||||
const [toolbarElement, setToolbarElement] = createSignal<HTMLElement>()
|
|
||||||
|
|
||||||
const editor = createTiptapEditor(() => ({
|
const editor = createTiptapEditor(() => ({
|
||||||
element: editorElement()!,
|
element: editorElement()!,
|
||||||
|
@ -78,36 +31,10 @@ export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
|
||||||
content: props.content || ''
|
content: props.content || ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const selection = createEditorTransaction(editor, (instance) => instance?.state.selection)
|
|
||||||
const [storedSelection, setStoredSelection] = createSignal<Editor['state']['selection']>()
|
|
||||||
const recoverSelection = () => {
|
|
||||||
if (!storedSelection()?.empty) {
|
|
||||||
// TODO set selection range from stored
|
|
||||||
createEditorTransaction(editor, (instance?: Editor) => {
|
|
||||||
const r = selection()
|
|
||||||
if (instance && r) {
|
|
||||||
instance.state.selection.from === r.from
|
|
||||||
instance.state.selection.to === r.to
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const storeSelection = (event: Event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
const selection = editor()?.state.selection
|
|
||||||
if (!selection?.empty) {
|
|
||||||
setStoredSelection(selection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEmpty = useEditorIsEmpty(editor)
|
const isEmpty = useEditorIsEmpty(editor)
|
||||||
const isFocused = useEditorIsFocused(editor)
|
const isFocused = useEditorIsFocused(editor)
|
||||||
const html = useEditorHTML(editor)
|
const html = useEditorHTML(editor)
|
||||||
createEffect(on([selection, showLinkInput], ([s, l]) => !l && setShowSimpleMenu(!s?.empty)))
|
|
||||||
createEffect(on(html, (c?: string) => c && props.onChange?.(c)))
|
createEffect(on(html, (c?: string) => c && props.onChange?.(c)))
|
||||||
createEffect(on(showLinkInput, (x?: boolean) => x && editor()?.chain().focus().run()))
|
|
||||||
createReaction(on(toolbarElement, (t?: HTMLElement) => t?.addEventListener('mousedown', prevent)))
|
|
||||||
onCleanup(() => toolbarElement()?.removeEventListener('mousedown', prevent))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -116,61 +43,9 @@ export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Show when={editor()} keyed>
|
<MicroToolbar />
|
||||||
{(instance) => (
|
|
||||||
<Show when={showSimpleMenu()}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'inline-flex',
|
|
||||||
background: 'var(--editor-bubble-menu-background)',
|
|
||||||
border: '1px solid black'
|
|
||||||
}}
|
|
||||||
ref={setToolbarElement}
|
|
||||||
>
|
|
||||||
<div class={styles.controls}>
|
|
||||||
<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={() => setShowLinkInput(!showLinkInput())}
|
|
||||||
title={t('Add url')}
|
|
||||||
isActive={showLinkInput}
|
|
||||||
>
|
|
||||||
<Icon name="editor-link" />
|
|
||||||
</Control>
|
|
||||||
</div>
|
|
||||||
<Show when={showLinkInput()}>
|
|
||||||
<InsertLinkForm
|
|
||||||
editor={instance}
|
|
||||||
onClose={() => {
|
|
||||||
setShowLinkInput(false)
|
|
||||||
recoverSelection()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div id="micro-editor" ref={setEditorElement} style={styles.minimal} onFocusOut={storeSelection} />
|
<div id="micro-editor" ref={setEditorElement} style={styles.minimal} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,54 +1,18 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
|
||||||
import CharacterCount from '@tiptap/extension-character-count'
|
import CharacterCount from '@tiptap/extension-character-count'
|
||||||
import Placeholder from '@tiptap/extension-placeholder'
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { type JSX, Show, createEffect, createSignal, on, onCleanup } from 'solid-js'
|
import { type JSX, Show, createEffect, createSignal, on } from 'solid-js'
|
||||||
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
import { createEditorTransaction, createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
||||||
import { Toolbar } from 'terracotta'
|
|
||||||
import { Icon } from '~/components/_shared/Icon/Icon'
|
|
||||||
import { Popover } from '~/components/_shared/Popover/Popover'
|
|
||||||
import { useLocalize } from '~/context/localize'
|
|
||||||
import { useUI } from '~/context/ui'
|
|
||||||
import { base } from '~/lib/editorExtensions'
|
import { base } from '~/lib/editorExtensions'
|
||||||
import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm'
|
|
||||||
|
|
||||||
|
import { MiniToolbar } from '../EditorToolbar/MiniToolbar'
|
||||||
import styles from '../SimplifiedEditor.module.scss'
|
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 {
|
interface MiniEditorProps {
|
||||||
content?: string
|
content?: string
|
||||||
onChange?: (content: string) => void
|
onChange?: (content: string) => void
|
||||||
|
onSubmit?: (content: string) => void
|
||||||
|
onCancel?: () => void
|
||||||
limit?: number
|
limit?: number
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
}
|
}
|
||||||
|
@ -56,9 +20,6 @@ interface MiniEditorProps {
|
||||||
export default function MiniEditor(props: MiniEditorProps): JSX.Element {
|
export default function MiniEditor(props: MiniEditorProps): JSX.Element {
|
||||||
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
|
||||||
const [counter, setCounter] = createSignal(0)
|
const [counter, setCounter] = createSignal(0)
|
||||||
const [showLinkInput, setShowLinkInput] = createSignal(false)
|
|
||||||
const { t } = useLocalize()
|
|
||||||
const { showModal } = useUI()
|
|
||||||
|
|
||||||
const editor = createTiptapEditor(() => ({
|
const editor = createTiptapEditor(() => ({
|
||||||
element: editorElement()!,
|
element: editorElement()!,
|
||||||
|
@ -77,7 +38,6 @@ export default function MiniEditor(props: MiniEditorProps): JSX.Element {
|
||||||
|
|
||||||
const html = useEditorHTML(editor)
|
const html = useEditorHTML(editor)
|
||||||
createEffect(on(html, (c?: string) => c && props.onChange?.(c)))
|
createEffect(on(html, (c?: string) => c && props.onChange?.(c)))
|
||||||
createEffect(on(showLinkInput, (x?: boolean) => x && editor()?.chain().focus().run()))
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const textLength = editor()?.getText().length || 0
|
const textLength = editor()?.getText().length || 0
|
||||||
|
@ -86,85 +46,14 @@ export default function MiniEditor(props: MiniEditorProps): JSX.Element {
|
||||||
content && props.onChange?.(content)
|
content && props.onChange?.(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleLinkClick = () => {
|
const isFocused = createEditorTransaction(editor, (instance) => instance?.isFocused)
|
||||||
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 (
|
return (
|
||||||
<div class={clsx(styles.SimplifiedEditor, styles.bordered, styles.isFocused)}>
|
<div class={clsx(styles.SimplifiedEditor, styles.bordered, { [styles.isFocused]: isFocused() })}>
|
||||||
<div>
|
<div>
|
||||||
<div id="mini-editor" ref={setEditorElement} />
|
<div id="mini-editor" ref={setEditorElement} />
|
||||||
|
|
||||||
<Toolbar
|
<MiniToolbar />
|
||||||
style={{ 'background-color': 'white', display: 'inline-flex' }}
|
|
||||||
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 when={counter() > 0}>
|
<Show when={counter() > 0}>
|
||||||
<small class={styles.limit}>
|
<small class={styles.limit}>
|
||||||
|
|
|
@ -11,15 +11,15 @@ import { useUI } from '~/context/ui'
|
||||||
import { base, custom } from '~/lib/editorExtensions'
|
import { base, custom } from '~/lib/editorExtensions'
|
||||||
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
||||||
import { UploadedFile } from '~/types/upload'
|
import { UploadedFile } from '~/types/upload'
|
||||||
|
import { UploadModalContent } from '../Upload/UploadModalContent'
|
||||||
|
import { renderUploadedImage } from '../Upload/renderUploadedImage'
|
||||||
import { Modal } from '../_shared/Modal/Modal'
|
import { Modal } from '../_shared/Modal/Modal'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
import { ToolbarControls } from './EditorToolbar'
|
import { ToolbarControls } from './EditorToolbar/SimplifiedToolbar'
|
||||||
import { LinkBubbleMenuModule } from './LinkBubbleMenu'
|
import { LinkBubbleMenuModule } from './LinkBubbleMenu'
|
||||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
import { UploadModalContent } from './UploadModalContent'
|
|
||||||
import { renderUploadedImage } from './renderUploadedImage'
|
|
||||||
|
|
||||||
import styles from './SimplifiedEditor.module.scss'
|
import styles from './Editor.module.scss'
|
||||||
|
|
||||||
export type SimplifiedEditorProps = {
|
export type SimplifiedEditorProps = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { InsertLinkForm } from '../InsertLinkForm'
|
||||||
|
|
||||||
import styles from './TextBubbleMenu.module.scss'
|
import styles from './TextBubbleMenu.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor'))
|
||||||
|
|
||||||
type BubbleMenuProps = {
|
type BubbleMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
@ -146,18 +146,13 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
|
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={footnoteEditorOpen()}>
|
<Match when={footnoteEditorOpen()}>
|
||||||
<SimplifiedEditor
|
<MiniEditor
|
||||||
maxHeight={180}
|
|
||||||
controlsAlwaysVisible={true}
|
|
||||||
imageEnabled={true}
|
|
||||||
placeholder={t('Enter footnote text')}
|
placeholder={t('Enter footnote text')}
|
||||||
onSubmit={(value) => handleAddFootnote(value)}
|
onSubmit={(value: string) => handleAddFootnote(value)}
|
||||||
variant={'bordered'}
|
content={footNote()}
|
||||||
initialContent={footNote()}
|
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setFootnoteEditorOpen(false)
|
setFootnoteEditorOpen(false)
|
||||||
}}
|
}}
|
||||||
submitButtonText={t('Send')}
|
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export { EditorComponent as Editor } from './Editor'
|
export { EditorComponent as Editor } from './Editor'
|
||||||
export { Panel } from './Panel'
|
export { Panel } from './Panel'
|
||||||
export { TopicSelect } from './TopicSelect'
|
export { TopicSelect } from '../TopicSelect'
|
||||||
export { UploadModalContent } from './UploadModalContent'
|
export { UploadModalContent } from '../Upload/UploadModalContent'
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { useSession } from '~/context/session'
|
||||||
import { useUI } from '~/context/ui'
|
import { useUI } from '~/context/ui'
|
||||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||||
import { UploadedFile } from '~/types/upload'
|
import { UploadedFile } from '~/types/upload'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../../_shared/InlineForm'
|
||||||
|
|
||||||
import styles from './UploadModalContent.module.scss'
|
import styles from './UploadModalContent.module.scss'
|
||||||
|
|
|
@ -21,14 +21,14 @@ import { LayoutType } from '~/types/common'
|
||||||
import { MediaItem } from '~/types/mediaitem'
|
import { MediaItem } from '~/types/mediaitem'
|
||||||
import { clone } from '~/utils/clone'
|
import { clone } from '~/utils/clone'
|
||||||
import { Editor as EditorComponent, Panel } from '../../Editor'
|
import { Editor as EditorComponent, Panel } from '../../Editor'
|
||||||
import { AudioUploader } from '../../Editor/AudioUploader'
|
|
||||||
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
||||||
import { VideoUploader } from '../../Editor/VideoUploader'
|
import { AudioUploader } from '../../Upload/AudioUploader'
|
||||||
|
import { VideoUploader } from '../../Upload/VideoUploader'
|
||||||
import { Modal } from '../../_shared/Modal'
|
import { Modal } from '../../_shared/Modal'
|
||||||
import { TableOfContents } from '../../_shared/TableOfContents'
|
import { TableOfContents } from '../../_shared/TableOfContents'
|
||||||
import styles from './EditView.module.scss'
|
import styles from './EditView.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -358,13 +358,10 @@ export const EditView = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={isLeadVisible()}>
|
<Show when={isLeadVisible()}>
|
||||||
<SimplifiedEditor
|
<MicroEditor
|
||||||
variant="minimal"
|
|
||||||
hideToolbar={true}
|
|
||||||
smallHeight={true}
|
|
||||||
placeholder={t('A short introduction to keep the reader interested')}
|
placeholder={t('A short introduction to keep the reader interested')}
|
||||||
initialContent={form.lead}
|
content={form.lead}
|
||||||
onChange={(value) => handleInputChange('lead', value)}
|
onChange={(value: string) => handleInputChange('lead', value)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useNavigate } from '@solidjs/router'
|
import { useNavigate } from '@solidjs/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
import { For, Show, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js'
|
||||||
import QuotedMessage from '~/components/Inbox/QuotedMessage'
|
import QuotedMessage from '~/components/Inbox/QuotedMessage'
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
import { InviteMembers } from '~/components/_shared/InviteMembers'
|
import { InviteMembers } from '~/components/_shared/InviteMembers'
|
||||||
|
@ -17,7 +17,6 @@ import type {
|
||||||
} from '~/graphql/schema/chat.gen'
|
} from '~/graphql/schema/chat.gen'
|
||||||
import type { Author } from '~/graphql/schema/core.gen'
|
import type { Author } from '~/graphql/schema/core.gen'
|
||||||
import { getShortDate } from '~/utils/date'
|
import { getShortDate } from '~/utils/date'
|
||||||
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
|
|
||||||
import DialogCard from '../../Inbox/DialogCard'
|
import DialogCard from '../../Inbox/DialogCard'
|
||||||
import DialogHeader from '../../Inbox/DialogHeader'
|
import DialogHeader from '../../Inbox/DialogHeader'
|
||||||
import { Message } from '../../Inbox/Message'
|
import { Message } from '../../Inbox/Message'
|
||||||
|
@ -26,6 +25,8 @@ import Search from '../../Inbox/Search'
|
||||||
import { Modal } from '../../_shared/Modal'
|
import { Modal } from '../../_shared/Modal'
|
||||||
import styles from './Inbox.module.scss'
|
import styles from './Inbox.module.scss'
|
||||||
|
|
||||||
|
const MiniEditor = lazy(() => import('../../Editor/MiniEditor/MiniEditor'))
|
||||||
|
|
||||||
const userSearch = (array: Author[], keyword: string) => {
|
const userSearch = (array: Author[], keyword: string) => {
|
||||||
return array.filter((value) => new RegExp(keyword.trim(), 'gi').test(value.name || ''))
|
return array.filter((value) => new RegExp(keyword.trim(), 'gi').test(value.name || ''))
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,6 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => {
|
||||||
const [sortByPerToPer, setSortByPerToPer] = createSignal(false)
|
const [sortByPerToPer, setSortByPerToPer] = createSignal(false)
|
||||||
const [currentDialog, setCurrentDialog] = createSignal<Chat>()
|
const [currentDialog, setCurrentDialog] = createSignal<Chat>()
|
||||||
const [messageToReply, setMessageToReply] = createSignal<MessageType | null>(null)
|
const [messageToReply, setMessageToReply] = createSignal<MessageType | null>(null)
|
||||||
const [isClear, setClear] = createSignal(false)
|
|
||||||
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
|
const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false)
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const authorId = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
|
const authorId = createMemo<number>(() => session()?.user?.app_data?.profile?.id || 0)
|
||||||
|
@ -77,11 +77,9 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => {
|
||||||
reply_to: messageToReply()?.id,
|
reply_to: messageToReply()?.id,
|
||||||
chat_id: currentDialog()?.id || ''
|
chat_id: currentDialog()?.id || ''
|
||||||
} as MutationCreate_MessageArgs)
|
} as MutationCreate_MessageArgs)
|
||||||
setClear(true)
|
|
||||||
setMessageToReply(null)
|
setMessageToReply(null)
|
||||||
if (messagesContainerRef)
|
if (messagesContainerRef)
|
||||||
(messagesContainerRef as HTMLDivElement).scrollTop = messagesContainerRef?.scrollHeight || 0
|
(messagesContainerRef as HTMLDivElement).scrollTop = messagesContainerRef?.scrollHeight || 0
|
||||||
setClear(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -291,15 +289,7 @@ export const InboxView = (props: { authors: Author[]; chat?: Chat }) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<div class={styles.wrapper}>
|
<div class={styles.wrapper}>
|
||||||
<SimplifiedEditor
|
<MiniEditor placeholder={t('New message')} onSubmit={handleSubmit} />
|
||||||
smallHeight={true}
|
|
||||||
imageEnabled={true}
|
|
||||||
isCancelButtonVisible={false}
|
|
||||||
placeholder={t('New message')}
|
|
||||||
setClear={isClear()}
|
|
||||||
onSubmit={(message) => handleSubmit(message)}
|
|
||||||
submitByCtrlEnter={true}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -14,7 +14,6 @@ import {
|
||||||
onMount
|
onMount
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import SimplifiedEditor from '~/components/Editor/SimplifiedEditor'
|
|
||||||
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'
|
||||||
|
@ -35,7 +34,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 MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor'))
|
||||||
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[] {
|
||||||
|
@ -340,18 +339,7 @@ export const ProfileSettings = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h4>{t('About')}</h4>
|
<h4>{t('About')}</h4>
|
||||||
<SimplifiedEditor
|
<MicroEditor content={about() || ''} onChange={setAbout} placeholder={t('About')} />
|
||||||
resetToInitial={true}
|
|
||||||
noLimits={true}
|
|
||||||
variant="bordered"
|
|
||||||
hideToolbar={true}
|
|
||||||
smallHeight={true}
|
|
||||||
label={t('About')}
|
|
||||||
initialContent={about() || ''}
|
|
||||||
autoFocus={false}
|
|
||||||
onChange={setAbout}
|
|
||||||
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}>
|
||||||
<h4>{t('Social networks')}</h4>
|
<h4>{t('Social networks')}</h4>
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Modal } from '../../_shared/Modal'
|
||||||
import stylesBeside from '../../Feed/Beside.module.scss'
|
import stylesBeside from '../../Feed/Beside.module.scss'
|
||||||
import styles from './PublishSettings.module.scss'
|
import styles from './PublishSettings.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
const DESCRIPTION_MAX_LENGTH = 400
|
const DESCRIPTION_MAX_LENGTH = 400
|
||||||
|
|
||||||
|
@ -224,16 +224,10 @@ export const PublishSettings = (props: Props) => {
|
||||||
allowEnterKey={false}
|
allowEnterKey={false}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
/>
|
/>
|
||||||
<SimplifiedEditor
|
<MicroEditor
|
||||||
variant="bordered"
|
|
||||||
hideToolbar={true}
|
|
||||||
smallHeight={true}
|
|
||||||
placeholder={t('Write a short introduction')}
|
placeholder={t('Write a short introduction')}
|
||||||
label={t('Description')}
|
content={composeDescription()}
|
||||||
initialContent={composeDescription()}
|
onChange={(value?: string) => value && setForm('description', value)}
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
|
||||||
onChange={(value: any) => setForm('description', value)}
|
|
||||||
maxLength={DESCRIPTION_MAX_LENGTH}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { clsx } from 'clsx'
|
||||||
import { For, Show, createEffect, createSignal, lazy, on, onMount } from 'solid-js'
|
import { For, Show, createEffect, createSignal, lazy, on, onMount } from 'solid-js'
|
||||||
import SwiperCore from 'swiper'
|
import SwiperCore from 'swiper'
|
||||||
import { Manipulation, Navigation, Pagination } from 'swiper/modules'
|
import { Manipulation, Navigation, Pagination } from 'swiper/modules'
|
||||||
|
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { useSnackbar } from '~/context/ui'
|
import { useSnackbar } from '~/context/ui'
|
||||||
import { composeMediaItems } from '~/lib/composeMediaItems'
|
import { composeMediaItems } from '~/lib/composeMediaItems'
|
||||||
|
@ -23,7 +22,7 @@ import { MediaItem } from '~/types/mediaitem'
|
||||||
import { UploadedFile } from '~/types/upload'
|
import { UploadedFile } from '~/types/upload'
|
||||||
import styles from './Swiper.module.scss'
|
import styles from './Swiper.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const MicroEditor = lazy(() => import('../../Editor/MicroEditor/MicroEditor'))
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
images: MediaItem[]
|
images: MediaItem[]
|
||||||
|
@ -316,9 +315,8 @@ export const EditorSwiper = (props: Props) => {
|
||||||
value={props.images[slideIndex()]?.source}
|
value={props.images[slideIndex()]?.source}
|
||||||
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)}
|
onChange={(event) => handleSlideDescriptionChange(slideIndex(), 'source', event.target.value)}
|
||||||
/>
|
/>
|
||||||
<SimplifiedEditor
|
<MicroEditor
|
||||||
initialContent={props.images[slideIndex()]?.body}
|
content={props.images[slideIndex()]?.body}
|
||||||
smallHeight={true}
|
|
||||||
placeholder={t('Enter image description')}
|
placeholder={t('Enter image description')}
|
||||||
onChange={(value) => setSlideBody(value)}
|
onChange={(value) => setSlideBody(value)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,3 +9,5 @@ mount(() => <StartClient />, document.getElementById('app') || document.body)
|
||||||
// navigator.serviceWorker.register(`/sw.js`);
|
// navigator.serviceWorker.register(`/sw.js`);
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
export default {}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user