not-module-prosemirror-styles
This commit is contained in:
parent
194e40aa86
commit
1a755f4c69
|
@ -1,6 +1,6 @@
|
|||
import { HocuspocusProvider } from '@hocuspocus/provider'
|
||||
import { UploadFile } from '@solid-primitives/upload'
|
||||
import { Editor, EditorOptions, isTextSelection } from '@tiptap/core'
|
||||
import { Editor, EditorOptions } from '@tiptap/core'
|
||||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
||||
import { CharacterCount } from '@tiptap/extension-character-count'
|
||||
import { Collaboration } from '@tiptap/extension-collaboration'
|
||||
|
@ -8,6 +8,7 @@ import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
|
|||
import { FloatingMenu } from '@tiptap/extension-floating-menu'
|
||||
import { Placeholder } from '@tiptap/extension-placeholder'
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
import { isServer } from 'solid-js/web'
|
||||
import { createTiptapEditor } from 'solid-tiptap'
|
||||
import uniqolor from 'uniqolor'
|
||||
import { Doc } from 'yjs'
|
||||
|
@ -19,15 +20,14 @@ import { Author } from '~/graphql/schema/core.gen'
|
|||
import { base, custom, extended } from '~/lib/editorExtensions'
|
||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||
import { allowedImageTypes, renderUploadedImage } from '../Upload/renderUploadedImage'
|
||||
import { Panel } from './Panel/Panel'
|
||||
import { BlockquoteBubbleMenu } from './Toolbar/BlockquoteBubbleMenu'
|
||||
import { EditorFloatingMenu } from './Toolbar/EditorFloatingMenu'
|
||||
import { FigureBubbleMenu } from './Toolbar/FigureBubbleMenu'
|
||||
import { IncutBubbleMenu } from './Toolbar/IncutBubbleMenu'
|
||||
import { TextBubbleMenu } from './Toolbar/TextBubbleMenu'
|
||||
|
||||
import './Editor.module.scss'
|
||||
import { isServer } from 'solid-js/web'
|
||||
import { Panel } from './Panel/Panel'
|
||||
import './Prosemirror.scss'
|
||||
|
||||
export type EditorComponentProps = {
|
||||
shoutId: number
|
||||
|
@ -43,8 +43,13 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
const { t } = useLocalize()
|
||||
const { session, requireAuthentication } = useSession()
|
||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
||||
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
||||
const [isCommonMarkup, _setIsCommonMarkup] = createSignal(false)
|
||||
const createMenuSignal = () => createSignal(false)
|
||||
const [shouldShowTextBubbleMenu, _setShouldShowTextBubbleMenu] = createMenuSignal()
|
||||
const [shouldShowBlockquoteBubbleMenu, _setShouldShowBlockquoteBubbleMenu] = createMenuSignal()
|
||||
const [shouldShowFigureBubbleMenu, _setShouldShowFigureBubbleMenu] = createMenuSignal()
|
||||
const [shouldShowIncutBubbleMenu, _setShouldShowIncutBubbleMenu] = createMenuSignal()
|
||||
const [shouldShowFloatingMenu, _setShouldShowFloatingMenu] = createMenuSignal()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { countWords, setEditing } = useEditorContext()
|
||||
const [editorOptions, setEditorOptions] = createSignal<Partial<EditorOptions>>({})
|
||||
|
@ -71,11 +76,18 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
}
|
||||
console.log('stage 2: create editor instance without menus', opts)
|
||||
|
||||
const old = editor() || { options: {} }
|
||||
const old = editor() || { options: {} as EditorOptions }
|
||||
const uniqueExtensions = [
|
||||
...new Map(
|
||||
[...(old?.options?.extensions || []), ...(opts?.extensions || [])].map((ext) => [ext.name, ext])
|
||||
).values()
|
||||
]
|
||||
|
||||
const fresh = createTiptapEditor(() => ({
|
||||
...old?.options,
|
||||
...opts,
|
||||
element: opts.element as HTMLElement
|
||||
element: opts.element as HTMLElement,
|
||||
extensions: uniqueExtensions
|
||||
}))
|
||||
if (old instanceof Editor) old?.destroy()
|
||||
setEditor(fresh() || null)
|
||||
|
@ -147,7 +159,7 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
},
|
||||
content: props.initialContent ?? null
|
||||
}
|
||||
console.log('Editor options created:', options)
|
||||
console.log(options)
|
||||
setEditorOptions(() => options)
|
||||
}
|
||||
|
||||
|
@ -176,85 +188,39 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
if (editor()) {
|
||||
initializeMenus()
|
||||
}
|
||||
|
||||
// Инициализируем коллаборацию если необходимо
|
||||
if (!props.disableCollaboration) {
|
||||
initializeCollaboration()
|
||||
}
|
||||
}, 1200)
|
||||
}, 'edit')
|
||||
})
|
||||
|
||||
const initializeMenus = () => {
|
||||
if (menusInitialized() || !editor()) return
|
||||
|
||||
console.log('stage 3: initialize menus when editor instance is ready')
|
||||
|
||||
if (
|
||||
textBubbleMenuRef() &&
|
||||
blockquoteBubbleMenuRef() &&
|
||||
figureBubbleMenuRef() &&
|
||||
incutBubbleMenuRef() &&
|
||||
floatingMenuRef()
|
||||
) {
|
||||
const menus = [
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'textBubbleMenu',
|
||||
element: textBubbleMenuRef(),
|
||||
shouldShow: ({ editor: e, view, state: { doc, selection }, from, to }) => {
|
||||
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
|
||||
isEmptyTextBlock &&
|
||||
e?.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
|
||||
|
||||
setIsCommonMarkup(e?.isActive('figcaption'))
|
||||
const result =
|
||||
(view.hasFocus() &&
|
||||
!selection.empty &&
|
||||
!isEmptyTextBlock &&
|
||||
!e.isActive('image') &&
|
||||
!e.isActive('figure')) ||
|
||||
e.isActive('footnote') ||
|
||||
(e.isActive('figcaption') && !selection.empty)
|
||||
setShouldShowTextBubbleMenu(result)
|
||||
return result
|
||||
},
|
||||
tippyOptions: {
|
||||
onHide: () => editor()?.commands.focus() as false
|
||||
}
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'blockquoteBubbleMenu',
|
||||
element: blockquoteBubbleMenuRef(),
|
||||
shouldShow: ({ editor: e, view, state }) =>
|
||||
view.hasFocus() && !state.selection.empty && e?.isActive('blockquote')
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'figureBubbleMenu',
|
||||
element: figureBubbleMenuRef(),
|
||||
shouldShow: ({ editor: e, view, state }) =>
|
||||
view.hasFocus() && !state.selection.empty && e?.isActive('figure')
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
pluginKey: 'incutBubbleMenu',
|
||||
element: incutBubbleMenuRef(),
|
||||
shouldShow: ({ editor: e, view, state }) =>
|
||||
view.hasFocus() && !state.selection.empty && e?.isActive('figcaption')
|
||||
}),
|
||||
FloatingMenu.configure({
|
||||
element: floatingMenuRef(),
|
||||
pluginKey: 'floatingMenu',
|
||||
shouldShow: ({ editor: e, state: { selection } }) => {
|
||||
const isRootDepth = selection.$anchor.depth === 1
|
||||
const show =
|
||||
isRootDepth && selection.empty && !(e?.isActive('codeBlock') || e?.isActive('heading'))
|
||||
console.log('FloatingMenu shouldShow:', show)
|
||||
return show
|
||||
}
|
||||
})
|
||||
if (blockquoteBubbleMenuRef() && figureBubbleMenuRef() && incutBubbleMenuRef() && floatingMenuRef()) {
|
||||
console.log('stage 3: initialize menus when editor instance is ready')
|
||||
const menuConfigs = [
|
||||
{ key: 'textBubbleMenu', ref: textBubbleMenuRef, shouldShow: shouldShowTextBubbleMenu },
|
||||
{
|
||||
key: 'blockquoteBubbleMenu',
|
||||
ref: blockquoteBubbleMenuRef,
|
||||
shouldShow: shouldShowBlockquoteBubbleMenu
|
||||
},
|
||||
{ key: 'figureBubbleMenu', ref: figureBubbleMenuRef, shouldShow: shouldShowFigureBubbleMenu },
|
||||
{ key: 'incutBubbleMenu', ref: incutBubbleMenuRef, shouldShow: shouldShowIncutBubbleMenu },
|
||||
{ key: 'floatingMenu', ref: floatingMenuRef, shouldShow: shouldShowFloatingMenu, isFloating: true }
|
||||
]
|
||||
const extensions = [...(editorOptions().extensions || []), ...menus]
|
||||
setEditorOptions((prev) => ({ ...prev, extensions }))
|
||||
console.log('Editor menus initialized:', extensions)
|
||||
const menus = menuConfigs.map((config) =>
|
||||
config.isFloating
|
||||
? FloatingMenu.configure({
|
||||
pluginKey: config.key,
|
||||
element: config.ref(),
|
||||
shouldShow: config.shouldShow
|
||||
})
|
||||
: BubbleMenu.configure({
|
||||
pluginKey: config.key,
|
||||
element: config.ref(),
|
||||
shouldShow: config.shouldShow
|
||||
})
|
||||
)
|
||||
setEditorOptions((prev) => ({ ...prev, extensions: [...(prev.extensions || []), ...menus] }))
|
||||
setMenusInitialized(true)
|
||||
} else {
|
||||
console.error('Some menu references are missing')
|
||||
|
@ -292,6 +258,13 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
|
||||
setEditorOptions((prev: Partial<EditorOptions>) => {
|
||||
const extensions = [...(prev.extensions || [])]
|
||||
if (props.disableCollaboration) {
|
||||
// Remove collaboration extensions if they exist
|
||||
const filteredExtensions = extensions.filter(
|
||||
(ext) => ext.name !== 'collaboration' && ext.name !== 'collaborationCursor'
|
||||
)
|
||||
return { ...prev, extensions: filteredExtensions }
|
||||
}
|
||||
extensions.push(
|
||||
Collaboration.configure({ document: yDocs[docName] }),
|
||||
CollaborationCursor.configure({
|
||||
|
@ -316,6 +289,17 @@ export const EditorComponent = (props: EditorComponentProps) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Инициализируем коллаборацию если необходимо
|
||||
createEffect(
|
||||
on(
|
||||
() => props.disableCollaboration,
|
||||
() => {
|
||||
initializeCollaboration()
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
onCleanup(() => {
|
||||
editorElRef()?.removeEventListener('focus', handleFocus)
|
||||
editor()?.destroy()
|
||||
|
|
|
@ -86,25 +86,25 @@ mark.highlight {
|
|||
}
|
||||
|
||||
[data-float='half-left'] {
|
||||
@include media-breakpoint-up(md) {
|
||||
max-width: 50%;
|
||||
min-width: 30%;
|
||||
}
|
||||
|
||||
float: left;
|
||||
margin: 1rem 1rem 0;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
[data-float='half-right'] {
|
||||
@include media-breakpoint-up(md) {
|
||||
max-width: 50%;
|
||||
min-width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-float='half-right'] {
|
||||
float: right;
|
||||
margin: 1rem 0;
|
||||
clear: right;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
max-width: 50%;
|
||||
min-width: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +114,7 @@ mark.highlight {
|
|||
}
|
||||
|
||||
&[data-type='quote'] {
|
||||
font-size:1.4rem;
|
||||
font-size: 1.4rem;
|
||||
border: solid #000;
|
||||
border-width: 0 0 0 2px;
|
||||
margin: 1.6rem 0;
|
||||
|
@ -136,6 +136,20 @@ mark.highlight {
|
|||
}
|
||||
|
||||
&[data-type='punchline'] {
|
||||
border: solid #000;
|
||||
border-width: 2px 0;
|
||||
font-size: 3.2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin: 1em 0;
|
||||
padding: 2.4rem 0;
|
||||
|
||||
&[data-float='left'],
|
||||
&[data-float='right'] {
|
||||
font-size: 2.2rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
&[data-float='left'] {
|
||||
margin-right: 1.5em;
|
||||
|
@ -147,24 +161,16 @@ mark.highlight {
|
|||
clear: right;
|
||||
}
|
||||
}
|
||||
|
||||
font-size:3.2rem;
|
||||
border: solid #000;
|
||||
border-width: 2px 0;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin: 1em 0;
|
||||
padding: 2.4rem 0;
|
||||
|
||||
&[data-float='left'],
|
||||
&[data-float='right'] {
|
||||
font-size:2.2rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror article[data-type='incut'] {
|
||||
background: #f1f2f3;
|
||||
font-size: 1.4rem;
|
||||
margin: 1em -1rem;
|
||||
padding: 2em 2rem;
|
||||
transition: background 0.3s ease-in-out;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
|
@ -181,12 +187,6 @@ mark.highlight {
|
|||
margin-right: -3em;
|
||||
}
|
||||
|
||||
font-size:1.4rem;
|
||||
background: #f1f2f3;
|
||||
margin: 1em -1rem;
|
||||
padding: 2em 2rem;
|
||||
transition: background 0.3s ease-in-out;
|
||||
|
||||
&[data-float] img {
|
||||
float: none;
|
||||
max-width: unset;
|
||||
|
@ -196,6 +196,9 @@ mark.highlight {
|
|||
|
||||
&[data-float='left'],
|
||||
&[data-float='half-left'] {
|
||||
margin-left: -1rem;
|
||||
clear: left;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-left: -2rem;
|
||||
margin-right: 2rem;
|
||||
|
@ -208,13 +211,13 @@ mark.highlight {
|
|||
@include media-breakpoint-up(xl) {
|
||||
margin-left: -12.5%;
|
||||
}
|
||||
|
||||
margin-left: -1rem;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
&[data-float='right'],
|
||||
&[data-float='half-right'] {
|
||||
margin-right: -1rem;
|
||||
clear: right;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
margin-left: 2rem;
|
||||
margin-right: -2rem;
|
||||
|
@ -227,9 +230,6 @@ mark.highlight {
|
|||
@include media-breakpoint-up(xl) {
|
||||
margin-right: -12.5%;
|
||||
}
|
||||
|
||||
margin-right: -1rem;
|
||||
clear: right;
|
||||
}
|
||||
|
||||
*:last-child {
|
||||
|
@ -311,4 +311,4 @@ footnote {
|
|||
color: var(--selection-color);
|
||||
border: solid var(--selection-background);
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,16 @@
|
|||
import type { Editor } from '@tiptap/core'
|
||||
import { clsx } from 'clsx'
|
||||
import { Match, Show, Switch, createEffect, createSignal, lazy, onCleanup, onMount } from 'solid-js'
|
||||
import {
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
lazy,
|
||||
onCleanup,
|
||||
onMount
|
||||
} from 'solid-js'
|
||||
import { createEditorTransaction } from 'solid-tiptap'
|
||||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { Popover } from '~/components/_shared/Popover'
|
||||
|
@ -21,404 +31,445 @@ type BubbleMenuProps = {
|
|||
export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const isActive = (name: string, attributes?: Record<string, string | number>) =>
|
||||
createEditorTransaction(
|
||||
() => {
|
||||
console.log('isActive', name, attributes)
|
||||
return props.editor
|
||||
},
|
||||
(editor) => editor?.isActive(name, attributes)
|
||||
)
|
||||
const isActive = createMemo(
|
||||
() => (name: string, attributes?: Record<string, string | number>) =>
|
||||
props.editor?.isActive(name, attributes)
|
||||
)
|
||||
|
||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
||||
const [listBubbleOpen, setListBubbleOpen] = createSignal(false)
|
||||
const [linkEditorOpen, setLinkEditorOpen] = createSignal(false)
|
||||
const [footnoteEditorOpen, setFootnoteEditorOpen] = createSignal(false)
|
||||
const [footNote, setFootNote] = createSignal<string>()
|
||||
const [menuState, setMenuState] = createSignal({
|
||||
textSizeBubbleOpen: false,
|
||||
listBubbleOpen: false,
|
||||
linkEditorOpen: false,
|
||||
footnoteEditorOpen: false,
|
||||
footNote: undefined as string | undefined
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!props.shouldShow) {
|
||||
setFootNote()
|
||||
setFootnoteEditorOpen(false)
|
||||
setLinkEditorOpen(false)
|
||||
setTextSizeBubbleOpen(false)
|
||||
setListBubbleOpen(false)
|
||||
setMenuState((prev) => ({
|
||||
...prev,
|
||||
footNote: undefined,
|
||||
footnoteEditorOpen: false,
|
||||
linkEditorOpen: false,
|
||||
textSizeBubbleOpen: false,
|
||||
listBubbleOpen: false
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
const isBold = isActive('bold')
|
||||
const isItalic = isActive('italic')
|
||||
const isH1 = isActive('heading', { level: 2 })
|
||||
const isH2 = isActive('heading', { level: 3 })
|
||||
const isH3 = isActive('heading', { level: 4 })
|
||||
const isQuote = isActive('blockquote', { 'data-type': 'quote' })
|
||||
const isPunchLine = isActive('blockquote', { 'data-type': 'punchline' })
|
||||
const isOrderedList = isActive('isOrderedList')
|
||||
const isBulletList = isActive('isBulletList')
|
||||
const isLink = isActive('link')
|
||||
const isHighlight = isActive('highlight')
|
||||
const isFootnote = isActive('footnote')
|
||||
const isIncut = isActive('article')
|
||||
const activeStates = createMemo(() => ({
|
||||
bold: isActive()('bold'),
|
||||
italic: isActive()('italic'),
|
||||
h1: isActive()('heading', { level: 2 }),
|
||||
h2: isActive()('heading', { level: 3 }),
|
||||
h3: isActive()('heading', { level: 4 }),
|
||||
quote: isActive()('blockquote', { 'data-type': 'quote' }),
|
||||
punchLine: isActive()('blockquote', { 'data-type': 'punchline' }),
|
||||
orderedList: isActive()('orderedList'),
|
||||
bulletList: isActive()('bulletList'),
|
||||
link: isActive()('link'),
|
||||
highlight: isActive()('highlight'),
|
||||
footnote: isActive()('footnote'),
|
||||
incut: isActive()('article')
|
||||
// underline: isActive()('underline'),
|
||||
}))
|
||||
|
||||
const toggleTextSizePopup = () => {
|
||||
if (listBubbleOpen()) {
|
||||
setListBubbleOpen(false)
|
||||
}
|
||||
setTextSizeBubbleOpen((prev) => !prev)
|
||||
}
|
||||
const toggleListPopup = () => {
|
||||
if (textSizeBubbleOpen()) {
|
||||
setTextSizeBubbleOpen(false)
|
||||
}
|
||||
setListBubbleOpen((prev) => !prev)
|
||||
const togglePopup = (type: 'textSize' | 'list') => {
|
||||
setMenuState((prev) => ({
|
||||
...prev,
|
||||
textSizeBubbleOpen: type === 'textSize' ? !prev.textSizeBubbleOpen : false,
|
||||
listBubbleOpen: type === 'list' ? !prev.listBubbleOpen : false
|
||||
}))
|
||||
}
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.code === 'KeyK' && (event.metaKey || event.ctrlKey) && !props.editor.state.selection.empty) {
|
||||
event.preventDefault()
|
||||
setLinkEditorOpen(true)
|
||||
setMenuState((prev) => ({ ...prev, linkEditorOpen: true }))
|
||||
}
|
||||
}
|
||||
|
||||
const updateCurrentFootnoteValue = createEditorTransaction(
|
||||
() => props.editor,
|
||||
(ed) => {
|
||||
if (!isFootnote()) {
|
||||
if (!activeStates().footnote) {
|
||||
return
|
||||
}
|
||||
const value = ed.getAttributes('footnote').value
|
||||
setFootNote(value)
|
||||
setMenuState((prev) => ({ ...prev, footNote: value }))
|
||||
}
|
||||
)
|
||||
|
||||
const handleAddFootnote = (footnote: string) => {
|
||||
if (footNote()) {
|
||||
if (menuState().footNote) {
|
||||
props.editor?.chain().focus().updateFootnote({ value: footnote }).run()
|
||||
} else {
|
||||
props.editor?.chain().focus().setFootnote({ value: footnote }).run()
|
||||
}
|
||||
setFootNote()
|
||||
setLinkEditorOpen(false)
|
||||
setFootnoteEditorOpen(false)
|
||||
setMenuState((prev) => ({
|
||||
...prev,
|
||||
footNote: undefined,
|
||||
linkEditorOpen: false,
|
||||
footnoteEditorOpen: false
|
||||
}))
|
||||
}
|
||||
|
||||
const handleOpenFootnoteEditor = () => {
|
||||
updateCurrentFootnoteValue()
|
||||
setLinkEditorOpen(false)
|
||||
setFootnoteEditorOpen(true)
|
||||
setMenuState((prev) => ({ ...prev, linkEditorOpen: false, footnoteEditorOpen: true }))
|
||||
}
|
||||
|
||||
const handleSetPunchline = () => {
|
||||
if (isPunchLine()) {
|
||||
if (activeStates().punchLine) {
|
||||
props.editor?.chain().focus().toggleBlockquote('punchline').run()
|
||||
}
|
||||
props.editor?.chain().focus().toggleBlockquote('quote').run()
|
||||
toggleTextSizePopup()
|
||||
togglePopup('textSize')
|
||||
}
|
||||
const handleSetQuote = () => {
|
||||
if (isQuote()) {
|
||||
if (activeStates().quote) {
|
||||
props.editor?.chain().focus().toggleBlockquote('quote').run()
|
||||
}
|
||||
props.editor?.chain().focus().toggleBlockquote('punchline').run()
|
||||
toggleTextSizePopup()
|
||||
togglePopup('textSize')
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
setLinkEditorOpen(false)
|
||||
setMenuState((prev) => ({ ...prev, linkEditorOpen: false }))
|
||||
})
|
||||
})
|
||||
|
||||
const handleOpenLinkForm = () => {
|
||||
props.editor?.chain().focus().addTextWrap({ class: 'highlight-fake-selection' }).run()
|
||||
setLinkEditorOpen(true)
|
||||
setMenuState((prev) => ({ ...prev, linkEditorOpen: true }))
|
||||
}
|
||||
|
||||
const handleCloseLinkForm = () => {
|
||||
setLinkEditorOpen(false)
|
||||
setMenuState((prev) => ({ ...prev, linkEditorOpen: false }))
|
||||
props.editor?.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
|
||||
}
|
||||
const handleFormat = (type: 'Bold' | 'Italic' | 'Underline', _attributes?: Record<string, unknown>) => {
|
||||
props.editor?.chain().focus()[`toggle${type}`]().run()
|
||||
}
|
||||
|
||||
const ListBubbleMenu = (props: BubbleMenuProps) => {
|
||||
return (
|
||||
<div class={styles.dropDown}>
|
||||
<header>{t('Lists')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Bullet list')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().bulletList
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleBulletList().run()
|
||||
togglePopup('list')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-ul" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Ordered list')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().orderedList
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleOrderedList().run()
|
||||
togglePopup('list')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-ol" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const CommonMarkupBubbleMenu = (props: BubbleMenuProps) => {
|
||||
return (
|
||||
<>
|
||||
<Popover content={t('Insert footnote')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().footnote
|
||||
})}
|
||||
onClick={handleOpenFootnoteEditor}
|
||||
>
|
||||
<Icon name="editor-footnote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<div class={styles.delimiter} />
|
||||
<div class={styles.dropDownHolder}>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: menuState().listBubbleOpen
|
||||
})}
|
||||
onClick={() => togglePopup('list')}
|
||||
>
|
||||
<Icon name="editor-ul" />
|
||||
<Icon name="down-triangle" class={styles.triangle} />
|
||||
</button>
|
||||
<Show when={menuState().listBubbleOpen}>
|
||||
<ListBubbleMenu {...props} />
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const TextSizeBubbleMenu = (props: BubbleMenuProps) => {
|
||||
return (
|
||||
<div class={styles.dropDown}>
|
||||
<header>{t('Headers')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Header 1')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().h1
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
togglePopup('textSize')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h1" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Header 2')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().h2
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
togglePopup('textSize')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h2" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Header 3')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().h3
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 4 }).run()
|
||||
togglePopup('textSize')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h3" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
<header>{t('Quotes')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Quote')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().quote
|
||||
})}
|
||||
onClick={handleSetPunchline}
|
||||
>
|
||||
<Icon name="editor-blockquote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Punchline')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().punchLine
|
||||
})}
|
||||
onClick={handleSetQuote}
|
||||
>
|
||||
<Icon name="editor-quote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
<header>{t('squib')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Incut')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().incut
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleArticle().run()
|
||||
togglePopup('textSize')
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-squib" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const BaseTextBubbleMenu = (props: BubbleMenuProps) => {
|
||||
return (
|
||||
<>
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<>
|
||||
<div class={styles.dropDownHolder}>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: menuState().textSizeBubbleOpen
|
||||
})}
|
||||
onClick={() => togglePopup('textSize')}
|
||||
>
|
||||
<Icon name="editor-text-size" />
|
||||
<Icon name="down-triangle" class={styles.triangle} />
|
||||
</button>
|
||||
<Show when={menuState().textSizeBubbleOpen}>
|
||||
<TextSizeBubbleMenu {...props} />
|
||||
</Show>
|
||||
</div>
|
||||
<div class={styles.delimiter} />
|
||||
</>
|
||||
</Show>
|
||||
<Popover content={t('Bold')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().bold
|
||||
})}
|
||||
onClick={() => handleFormat('Bold')}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Italic')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().italic
|
||||
})}
|
||||
onClick={() => handleFormat('Italic')}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
{/*<Popover content={t('Underline')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().underline
|
||||
})}
|
||||
onClick={() => handleFormat('Underline')}
|
||||
>
|
||||
<Icon name="editor-underline" />
|
||||
</button>
|
||||
)}
|
||||
</Popover> */}
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<Popover content={t('Highlight')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().highlight
|
||||
})}
|
||||
onClick={() => props.editor?.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
|
||||
>
|
||||
<div class={styles.toggleHighlight} />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<div class={styles.delimiter} />
|
||||
</Show>
|
||||
<Popover content={<div class={styles.noWrap}>{t('Add url')}</div>}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={handleOpenLinkForm}
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: activeStates().link
|
||||
})}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<CommonMarkupBubbleMenu {...props} />
|
||||
</Show>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={props.ref} class={clsx(styles.TextBubbleMenu, { [styles.growWidth]: footnoteEditorOpen() })}>
|
||||
<div
|
||||
ref={props.ref}
|
||||
class={clsx(styles.TextBubbleMenu, { [styles.growWidth]: menuState().footnoteEditorOpen })}
|
||||
>
|
||||
<Switch>
|
||||
<Match when={linkEditorOpen()}>
|
||||
<Match when={menuState().linkEditorOpen}>
|
||||
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
|
||||
</Match>
|
||||
<Match when={footnoteEditorOpen()}>
|
||||
<Match when={menuState().footnoteEditorOpen}>
|
||||
<MiniEditor
|
||||
placeholder={t('Enter footnote text')}
|
||||
onSubmit={(value: string) => handleAddFootnote(value)}
|
||||
content={footNote()}
|
||||
onCancel={() => {
|
||||
setFootnoteEditorOpen(false)
|
||||
}}
|
||||
onSubmit={handleAddFootnote}
|
||||
content={menuState().footNote}
|
||||
onCancel={() => setMenuState((prev) => ({ ...prev, footnoteEditorOpen: false }))}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
||||
<>
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<>
|
||||
<div class={styles.dropDownHolder}>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
||||
})}
|
||||
onClick={toggleTextSizePopup}
|
||||
>
|
||||
<Icon name="editor-text-size" />
|
||||
<Icon name="down-triangle" class={styles.triangle} />
|
||||
</button>
|
||||
<Show when={textSizeBubbleOpen()}>
|
||||
<div class={styles.dropDown}>
|
||||
<header>{t('Headers')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Header 1')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isH1()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
toggleTextSizePopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h1" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Header 2')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isH2()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
toggleTextSizePopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h2" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Header 3')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isH3()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleHeading({ level: 4 }).run()
|
||||
toggleTextSizePopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-h3" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
<header>{t('Quotes')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Quote')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isQuote()
|
||||
})}
|
||||
onClick={handleSetPunchline}
|
||||
>
|
||||
<Icon name="editor-blockquote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Punchline')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isPunchLine()
|
||||
})}
|
||||
onClick={handleSetQuote}
|
||||
>
|
||||
<Icon name="editor-quote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
<header>{t('squib')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Incut')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isIncut()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleArticle().run()
|
||||
toggleTextSizePopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-squib" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class={styles.delimiter} />
|
||||
</>
|
||||
</Show>
|
||||
<Popover content={t('Bold')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isBold()
|
||||
})}
|
||||
onClick={() => props.editor?.chain().focus().toggleBold().run()}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Italic')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isItalic()
|
||||
})}
|
||||
onClick={() => props.editor?.chain().focus().toggleItalic().run()}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<Popover content={t('Highlight')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isHighlight()
|
||||
})}
|
||||
onClick={() =>
|
||||
props.editor?.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()
|
||||
}
|
||||
>
|
||||
<div class={styles.toggleHighlight} />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<div class={styles.delimiter} />
|
||||
</Show>
|
||||
<Popover content={<div class={styles.noWrap}>{t('Add url')}</div>}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
onClick={handleOpenLinkForm}
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isLink()
|
||||
})}
|
||||
>
|
||||
<Icon name="editor-link" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<>
|
||||
<Popover content={t('Insert footnote')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isFootnote()
|
||||
})}
|
||||
onClick={handleOpenFootnoteEditor}
|
||||
>
|
||||
<Icon name="editor-footnote" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<div class={styles.delimiter} />
|
||||
<div class={styles.dropDownHolder}>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: listBubbleOpen()
|
||||
})}
|
||||
onClick={toggleListPopup}
|
||||
>
|
||||
<Icon name="editor-ul" />
|
||||
<Icon name="down-triangle" class={styles.triangle} />
|
||||
</button>
|
||||
<Show when={listBubbleOpen()}>
|
||||
<div class={styles.dropDown}>
|
||||
<header>{t('Lists')}</header>
|
||||
<div class={styles.actions}>
|
||||
<Popover content={t('Bullet list')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isBulletList()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleBulletList().run()
|
||||
toggleListPopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-ul" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
<Popover content={t('Ordered list')}>
|
||||
{(triggerRef: (el: HTMLElement) => void) => (
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isOrderedList()
|
||||
})}
|
||||
onClick={() => {
|
||||
props.editor?.chain().focus().toggleOrderedList().run()
|
||||
toggleListPopup()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-ol" />
|
||||
</button>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
</Show>
|
||||
</>
|
||||
<Match when={!(menuState().linkEditorOpen || menuState().footnoteEditorOpen)}>
|
||||
<BaseTextBubbleMenu {...props} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
@ -95,7 +95,7 @@ export const EditSettingsView = (props: Props) => {
|
|||
if (d) {
|
||||
const draftForm = Object.keys(d).length !== 0 ? d : { shoutId: props.shout.id }
|
||||
setForm(draftForm)
|
||||
console.debug('draft from localstorage: ', draftForm)
|
||||
console.debug('got draft from localstorage')
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
|
|
|
@ -62,7 +62,8 @@ export const EditView = (props: Props) => {
|
|||
setFormErrors,
|
||||
saveDraft,
|
||||
saveDraftToLocalStorage,
|
||||
getDraftFromLocalStorage
|
||||
getDraftFromLocalStorage,
|
||||
isCollabMode
|
||||
} = useEditorContext()
|
||||
|
||||
const [subtitleInput, setSubtitleInput] = createSignal<HTMLTextAreaElement | undefined>()
|
||||
|
@ -453,6 +454,7 @@ export const EditView = (props: Props) => {
|
|||
shoutId={form.shoutId}
|
||||
initialContent={form.body}
|
||||
onChange={(body: string) => handleInputChange('body', body)}
|
||||
disableCollaboration={!isCollabMode()}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -50,6 +50,8 @@ export type EditorContextType = {
|
|||
setFormErrors: SetStoreFunction<Record<keyof ShoutForm, string>>
|
||||
editing: Accessor<Editor | undefined>
|
||||
setEditing: SetStoreFunction<Editor | undefined>
|
||||
isCollabMode: Accessor<boolean>
|
||||
setIsCollabMode: SetStoreFunction<boolean>
|
||||
}
|
||||
|
||||
export const EditorContext = createContext<EditorContextType>({} as EditorContextType)
|
||||
|
@ -99,6 +101,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
words: 0
|
||||
})
|
||||
const toggleEditorPanel = () => setIsEditorPanelVisible((value) => !value)
|
||||
const [isCollabMode, setIsCollabMode] = createSignal<boolean>(false)
|
||||
const countWords = (value: WordCounter) => setWordCounter(value)
|
||||
const validate = () => {
|
||||
if (!form.title) {
|
||||
|
@ -281,7 +284,9 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
countWords,
|
||||
setForm,
|
||||
setFormErrors,
|
||||
setEditing
|
||||
setEditing,
|
||||
isCollabMode,
|
||||
setIsCollabMode
|
||||
}
|
||||
|
||||
const value: EditorContextType = {
|
||||
|
|
Loading…
Reference in New Issue
Block a user