tippy-floating-fix
This commit is contained in:
parent
73f78823e0
commit
424af47b38
|
@ -140,7 +140,8 @@ export const EditorComponent = (props: Props) => {
|
||||||
}),
|
}),
|
||||||
FloatingMenu.configure({
|
FloatingMenu.configure({
|
||||||
tippyOptions: {
|
tippyOptions: {
|
||||||
placement: 'left'
|
placement: 'left',
|
||||||
|
appendTo: document.body
|
||||||
},
|
},
|
||||||
element: floatingMenuRef()!
|
element: floatingMenuRef()!
|
||||||
}),
|
}),
|
||||||
|
@ -151,8 +152,8 @@ export const EditorComponent = (props: Props) => {
|
||||||
content: props.initialContent || null,
|
content: props.initialContent || null,
|
||||||
onTransaction: ({ editor: e, transaction }) => {
|
onTransaction: ({ editor: e, transaction }) => {
|
||||||
if (transaction.docChanged) {
|
if (transaction.docChanged) {
|
||||||
const html = e.getHTML()
|
//const html = e.getHTML()
|
||||||
html && props.onChange(html)
|
//html && props.onChange(html)
|
||||||
const wordCount: number = e.storage.characterCount.words()
|
const wordCount: number = e.storage.characterCount.words()
|
||||||
const charsCount: number = e.storage.characterCount.characters()
|
const charsCount: number = e.storage.characterCount.characters()
|
||||||
charsCount && countWords({ words: wordCount, characters: charsCount })
|
charsCount && countWords({ words: wordCount, characters: charsCount })
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import deepEqual from 'fast-deep-equal'
|
import deepEqual from 'fast-deep-equal'
|
||||||
import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
|
||||||
import { debounce } from 'throttle-debounce'
|
|
||||||
import { EditorComponent } from '~/components/Editor/Editor'
|
import { EditorComponent } from '~/components/Editor/Editor'
|
||||||
import { DropArea } from '~/components/_shared/DropArea'
|
import { DropArea } from '~/components/_shared/DropArea'
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
|
@ -20,17 +18,16 @@ import { getImageUrl } from '~/lib/getThumbUrl'
|
||||||
import { isDesktop } from '~/lib/mediaQuery'
|
import { isDesktop } from '~/lib/mediaQuery'
|
||||||
import { LayoutType } from '~/types/common'
|
import { LayoutType } from '~/types/common'
|
||||||
import { MediaItem } from '~/types/mediaitem'
|
import { MediaItem } from '~/types/mediaitem'
|
||||||
import { clone } from '~/utils/clone'
|
|
||||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||||
|
import { MicroEditor } from '../Editor/MicroEditor'
|
||||||
import { Panel } from '../Editor/Panel/Panel'
|
import { Panel } from '../Editor/Panel/Panel'
|
||||||
import { AudioUploader } from '../Upload/AudioUploader'
|
import { AudioUploader } from '../Upload/AudioUploader'
|
||||||
import { VideoUploader } from '../Upload/VideoUploader'
|
import { VideoUploader } from '../Upload/VideoUploader'
|
||||||
|
import { GrowingTextarea } from '../_shared/GrowingTextarea/GrowingTextarea'
|
||||||
import { Modal } from '../_shared/Modal'
|
import { Modal } from '../_shared/Modal'
|
||||||
import { TableOfContents } from '../_shared/TableOfContents'
|
import { TableOfContents } from '../_shared/TableOfContents'
|
||||||
|
|
||||||
import styles from '~/styles/views/EditView.module.scss'
|
import styles from '~/styles/views/EditView.module.scss'
|
||||||
import MicroEditor from '../Editor/MicroEditor'
|
|
||||||
import GrowingTextarea from '../_shared/GrowingTextarea/GrowingTextarea'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
shout: Shout
|
shout: Shout
|
||||||
|
@ -42,8 +39,6 @@ export const EMPTY_TOPIC: Topic = {
|
||||||
slug: ''
|
slug: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTO_SAVE_DELAY = 3000
|
|
||||||
|
|
||||||
const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
window?.scrollTo({
|
window?.scrollTo({
|
||||||
|
@ -55,19 +50,10 @@ const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
||||||
export const EditView = (props: Props) => {
|
export const EditView = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { client } = useSession()
|
const { client } = useSession()
|
||||||
const {
|
const { form, formErrors, setForm, setFormErrors, handleInputChange, getDraftFromLocalStorage, saving } =
|
||||||
form,
|
useEditorContext()
|
||||||
formErrors,
|
|
||||||
setForm,
|
|
||||||
setFormErrors,
|
|
||||||
saveDraft,
|
|
||||||
saveDraftToLocalStorage,
|
|
||||||
getDraftFromLocalStorage
|
|
||||||
} = useEditorContext()
|
|
||||||
|
|
||||||
const [subtitleInput, setSubtitleInput] = createSignal<HTMLTextAreaElement | undefined>()
|
const [subtitleInput, setSubtitleInput] = createSignal<HTMLTextAreaElement | undefined>()
|
||||||
const [prevForm, setPrevForm] = createStore<ShoutForm>(clone(form))
|
|
||||||
const [saving, setSaving] = createSignal(false)
|
|
||||||
const [isSubtitleVisible, setIsSubtitleVisible] = createSignal(Boolean(form.subtitle))
|
const [isSubtitleVisible, setIsSubtitleVisible] = createSignal(Boolean(form.subtitle))
|
||||||
const [isLeadVisible, setIsLeadVisible] = createSignal(Boolean(form.lead))
|
const [isLeadVisible, setIsLeadVisible] = createSignal(Boolean(form.lead))
|
||||||
const [isScrolled, setIsScrolled] = createSignal(false)
|
const [isScrolled, setIsScrolled] = createSignal(false)
|
||||||
|
@ -80,68 +66,46 @@ export const EditView = (props: Props) => {
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.shout,
|
() => props.shout,
|
||||||
(shout) => {
|
async (shout) => {
|
||||||
if (shout) {
|
if (shout) {
|
||||||
// console.debug(`[EditView] shout is loaded: ${shout}`)
|
|
||||||
setShoutTopics((shout.topics as Topic[]) || [])
|
setShoutTopics((shout.topics as Topic[]) || [])
|
||||||
|
|
||||||
const stored = getDraftFromLocalStorage(shout.id)
|
const stored = getDraftFromLocalStorage(shout.id)
|
||||||
if (stored) {
|
if (stored) {
|
||||||
// console.info(`[EditView] got stored shout: ${stored}`)
|
|
||||||
setDraft((old) => ({ ...old, ...stored }) as Shout)
|
setDraft((old) => ({ ...old, ...stored }) as Shout)
|
||||||
|
setForm(stored as ShoutForm)
|
||||||
} else {
|
} else {
|
||||||
if (!shout.slug) {
|
if (!shout.slug) {
|
||||||
console.warn(`[EditView] shout has no slug! ${shout}`)
|
console.warn(`[EditView] shout has no slug! ${shout}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resp = await client()?.query(getMyShoutQuery, { shout_id: shout.id })
|
||||||
|
const result = resp?.data?.get_my_shout
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
const { shout: loadedShout, error } = result
|
||||||
|
if (error) {
|
||||||
|
console.log(error)
|
||||||
|
} else {
|
||||||
|
setDraft(loadedShout)
|
||||||
|
|
||||||
const draftForm = {
|
const draftForm = {
|
||||||
slug: shout.slug || '',
|
slug: loadedShout.slug || '',
|
||||||
shoutId: shout.id || 0,
|
shoutId: loadedShout.id || 0,
|
||||||
title: shout.title || '',
|
title: loadedShout.title || '',
|
||||||
lead: shout.lead || '',
|
lead: loadedShout.lead || '',
|
||||||
description: shout.description || '',
|
description: loadedShout.description || '',
|
||||||
subtitle: shout.subtitle || '',
|
subtitle: loadedShout.subtitle || '',
|
||||||
selectedTopics: (shoutTopics() || []) as Topic[],
|
selectedTopics: (shoutTopics() || []) as Topic[],
|
||||||
mainTopic: shoutTopics()[0] || '',
|
mainTopic: shoutTopics()[0] || '',
|
||||||
body: shout.body || '',
|
body: loadedShout.body || '',
|
||||||
coverImageUrl: shout.cover || '',
|
coverImageUrl: loadedShout.cover || '',
|
||||||
media: shout.media || '',
|
media: loadedShout.media || '',
|
||||||
layout: shout.layout
|
layout: loadedShout.layout
|
||||||
}
|
}
|
||||||
setForm((_) => draftForm)
|
setForm(draftForm)
|
||||||
console.debug('draft from props data: ', draftForm)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{ defer: true }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
draft,
|
|
||||||
(d) => {
|
|
||||||
if (d) {
|
|
||||||
const draftForm = Object.keys(d) ? d : { shoutId: props.shout.id }
|
|
||||||
setForm(draftForm as ShoutForm)
|
|
||||||
console.debug('draft from localstorage: ', draftForm)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ defer: true }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => props.shout?.id,
|
|
||||||
async (shoutId) => {
|
|
||||||
if (shoutId) {
|
|
||||||
const resp = await client()?.query(getMyShoutQuery, { shout_id: shoutId })
|
|
||||||
const result = resp?.data?.get_my_shout
|
|
||||||
if (result) {
|
|
||||||
// console.debug('[EditView] getMyShout result: ', result)
|
|
||||||
const { shout: loadedShout, error } = result
|
|
||||||
setDraft(loadedShout)
|
|
||||||
// console.debug('[EditView] loadedShout:', loadedShout)
|
|
||||||
error && console.log(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -160,6 +124,7 @@ export const EditView = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
|
const prevForm = getDraftFromLocalStorage(form.shoutId) || {}
|
||||||
if (!deepEqual(prevForm, form)) {
|
if (!deepEqual(prevForm, form)) {
|
||||||
event.returnValue = t(
|
event.returnValue = t(
|
||||||
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?'
|
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?'
|
||||||
|
@ -226,34 +191,6 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [hasChanges, setHasChanges] = createSignal(false)
|
|
||||||
const autoSave = async () => {
|
|
||||||
console.log('autoSave called')
|
|
||||||
if (hasChanges()) {
|
|
||||||
console.debug('saving draft', form)
|
|
||||||
setSaving(true)
|
|
||||||
saveDraftToLocalStorage(form)
|
|
||||||
await saveDraft(form)
|
|
||||||
setPrevForm(clone(form))
|
|
||||||
setSaving(false)
|
|
||||||
setHasChanges(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, autoSave)
|
|
||||||
|
|
||||||
const handleInputChange = (key: keyof ShoutForm, value: string) => {
|
|
||||||
console.log(`[handleInputChange] ${key}: ${value}`)
|
|
||||||
setForm(key, value)
|
|
||||||
setHasChanges(true)
|
|
||||||
debouncedAutoSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
onCleanup(() => {
|
|
||||||
debouncedAutoSave.cancel()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const showSubtitleInput = () => {
|
const showSubtitleInput = () => {
|
||||||
setIsSubtitleVisible(true)
|
setIsSubtitleVisible(true)
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Props = {
|
||||||
textAreaRef?: (el: HTMLTextAreaElement) => void
|
textAreaRef?: (el: HTMLTextAreaElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const GrowingTextarea = (props: Props) => {
|
export const GrowingTextarea = (props: Props) => {
|
||||||
const [value, setValue] = createSignal<string>('')
|
const [value, setValue] = createSignal<string>('')
|
||||||
const [isFocused, setIsFocused] = createSignal(false)
|
const [isFocused, setIsFocused] = createSignal(false)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { useMatch, useNavigate } from '@solidjs/router'
|
import { useMatch, useNavigate } from '@solidjs/router'
|
||||||
import { Editor } from '@tiptap/core'
|
import { Editor } from '@tiptap/core'
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
import { Accessor, createContext, createSignal, useContext } from 'solid-js'
|
import { Accessor, createContext, createSignal, onCleanup, useContext } from 'solid-js'
|
||||||
import { SetStoreFunction, createStore } from 'solid-js/store'
|
import { SetStoreFunction, createStore } from 'solid-js/store'
|
||||||
|
import { debounce } from 'throttle-debounce'
|
||||||
import { useSnackbar } from '~/context/ui'
|
import { useSnackbar } from '~/context/ui'
|
||||||
import deleteShoutQuery from '~/graphql/mutation/core/article-delete'
|
import deleteShoutQuery from '~/graphql/mutation/core/article-delete'
|
||||||
import updateShoutQuery from '~/graphql/mutation/core/article-update'
|
import updateShoutQuery from '~/graphql/mutation/core/article-update'
|
||||||
|
@ -12,6 +13,8 @@ import { useFeed } from '../context/feed'
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSession } from './session'
|
import { useSession } from './session'
|
||||||
|
|
||||||
|
export const AUTO_SAVE_DELAY = 3000
|
||||||
|
|
||||||
export type WordCounter = {
|
export type WordCounter = {
|
||||||
characters: number
|
characters: number
|
||||||
words: number
|
words: number
|
||||||
|
@ -52,6 +55,9 @@ export type EditorContextType = {
|
||||||
setEditing: SetStoreFunction<Editor | undefined>
|
setEditing: SetStoreFunction<Editor | undefined>
|
||||||
isCollabMode: Accessor<boolean>
|
isCollabMode: Accessor<boolean>
|
||||||
setIsCollabMode: SetStoreFunction<boolean>
|
setIsCollabMode: SetStoreFunction<boolean>
|
||||||
|
handleInputChange: (key: keyof ShoutForm, value: string) => void
|
||||||
|
saving: Accessor<boolean>
|
||||||
|
hasChanges: Accessor<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorContext = createContext<EditorContextType>({} as EditorContextType)
|
export const EditorContext = createContext<EditorContextType>({} as EditorContextType)
|
||||||
|
@ -79,6 +85,14 @@ const removeDraftFromLocalStorage = (shoutId: number) => {
|
||||||
localStorage?.removeItem(`shout-${shoutId}`)
|
localStorage?.removeItem(`shout-${shoutId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultForm: ShoutForm = {
|
||||||
|
body: '',
|
||||||
|
slug: '',
|
||||||
|
shoutId: 0,
|
||||||
|
title: '',
|
||||||
|
selectedTopics: []
|
||||||
|
}
|
||||||
|
|
||||||
export const EditorProvider = (props: { children: JSX.Element }) => {
|
export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const localize = useLocalize()
|
const localize = useLocalize()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
@ -88,20 +102,17 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const { addFeed } = useFeed()
|
const { addFeed } = useFeed()
|
||||||
const snackbar = useSnackbar()
|
const snackbar = useSnackbar()
|
||||||
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
|
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
|
||||||
const [form, setForm] = createStore<ShoutForm>({
|
const [form, setForm] = createStore<ShoutForm>(defaultForm)
|
||||||
body: '',
|
|
||||||
slug: '',
|
|
||||||
shoutId: 0,
|
|
||||||
title: '',
|
|
||||||
selectedTopics: []
|
|
||||||
})
|
|
||||||
const [formErrors, setFormErrors] = createStore({} as Record<keyof ShoutForm, string>)
|
const [formErrors, setFormErrors] = createStore({} as Record<keyof ShoutForm, string>)
|
||||||
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
|
const [wordCounter, setWordCounter] = createSignal<WordCounter>({ characters: 0, words: 0 })
|
||||||
characters: 0,
|
|
||||||
words: 0
|
|
||||||
})
|
|
||||||
const toggleEditorPanel = () => setIsEditorPanelVisible((value) => !value)
|
const toggleEditorPanel = () => setIsEditorPanelVisible((value) => !value)
|
||||||
const [isCollabMode, setIsCollabMode] = createSignal<boolean>(false)
|
const [isCollabMode, setIsCollabMode] = createSignal<boolean>(false)
|
||||||
|
|
||||||
|
// current publishing editor instance to connect settings, panel and editor
|
||||||
|
const [editing, setEditing] = createSignal<Editor | undefined>(undefined)
|
||||||
|
const [saving, setSaving] = createSignal(false)
|
||||||
|
const [hasChanges, setHasChanges] = createSignal(false)
|
||||||
|
|
||||||
const countWords = (value: WordCounter) => setWordCounter(value)
|
const countWords = (value: WordCounter) => setWordCounter(value)
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
if (!form.title) {
|
if (!form.title) {
|
||||||
|
@ -157,15 +168,9 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveShout = async (formToSave: ShoutForm) => {
|
const saveShout = async (formToSave: ShoutForm) => {
|
||||||
if (isEditorPanelVisible()) {
|
isEditorPanelVisible() && toggleEditorPanel()
|
||||||
toggleEditorPanel()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchEdit() && !validate()) {
|
if ((matchEdit() && !validate()) || (matchEditSettings() && !validateSettings())) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchEditSettings() && !validateSettings()) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,12 +181,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
removeDraftFromLocalStorage(formToSave.shoutId)
|
removeDraftFromLocalStorage(formToSave.shoutId)
|
||||||
|
navigate(shout?.published_at ? `/article/${shout.slug}` : '/edit')
|
||||||
if (shout?.published_at) {
|
|
||||||
navigate(`/article/${shout.slug}`)
|
|
||||||
} else {
|
|
||||||
navigate('/edit')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[saveShout]', error)
|
console.error('[saveShout]', error)
|
||||||
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' })
|
||||||
|
@ -197,25 +197,21 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishShout = async (formToPublish: ShoutForm) => {
|
const publishShout = async (formToPublish: ShoutForm) => {
|
||||||
if (isEditorPanelVisible()) {
|
isEditorPanelVisible() && toggleEditorPanel()
|
||||||
toggleEditorPanel()
|
|
||||||
|
if ((matchEdit() && !validate()) || (matchEditSettings() && !validateSettings())) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchEdit()) {
|
if (matchEdit()) {
|
||||||
if (!validate()) return
|
|
||||||
|
|
||||||
const slug = slugify(form.title)
|
const slug = slugify(form.title)
|
||||||
setForm('slug', slug)
|
setForm('slug', slug)
|
||||||
navigate(`/edit/${form.shoutId}/settings`)
|
navigate(`/edit/${form.shoutId}/settings`)
|
||||||
const { error } = await updateShout(formToPublish, { publish: false })
|
const { error } = await updateShout(formToPublish, { publish: false })
|
||||||
if (error) {
|
if (error) {
|
||||||
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' })
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateSettings()) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -269,8 +265,25 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// current publishing editor instance to connect settings, panel and editor
|
const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, async () => {
|
||||||
const [editing, setEditing] = createSignal<Editor | undefined>(undefined)
|
console.log('autoSave called')
|
||||||
|
if (hasChanges()) {
|
||||||
|
console.debug('saving draft', form)
|
||||||
|
setSaving(true)
|
||||||
|
saveDraftToLocalStorage(form)
|
||||||
|
await saveDraft(form)
|
||||||
|
setSaving(false)
|
||||||
|
setHasChanges(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onCleanup(debouncedAutoSave.cancel)
|
||||||
|
|
||||||
|
const handleInputChange = (key: keyof ShoutForm, value: string) => {
|
||||||
|
console.log(`[handleInputChange] ${key}: ${value}`)
|
||||||
|
setForm(key, value)
|
||||||
|
setHasChanges(true)
|
||||||
|
debouncedAutoSave()
|
||||||
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
saveShout,
|
saveShout,
|
||||||
|
@ -286,7 +299,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
setFormErrors,
|
setFormErrors,
|
||||||
setEditing,
|
setEditing,
|
||||||
isCollabMode,
|
isCollabMode,
|
||||||
setIsCollabMode
|
setIsCollabMode,
|
||||||
|
handleInputChange,
|
||||||
|
saving,
|
||||||
|
hasChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: EditorContextType = {
|
const value: EditorContextType = {
|
||||||
|
|
|
@ -198,6 +198,143 @@ button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button--subscribe {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--default-color);
|
||||||
|
border: 2px solid var(--black-100);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
transition: filter 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-invert);
|
||||||
|
color: var(--default-color-invert);
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--light {
|
||||||
|
font-size:1.5rem;
|
||||||
|
background-color: var(--black-100);
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
color: var(--default-color);
|
||||||
|
font-weight: 500;
|
||||||
|
height: auto;
|
||||||
|
padding: 0.6rem 1.2rem 0.6rem 1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--black-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--subscribe-topic {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--default-color);
|
||||||
|
border: 2px solid var(--default-color);
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 2.8rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-invert);
|
||||||
|
color: var(--default-color-invert);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled]:hover {
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--default-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
width: 1.4em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--content-index {
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 135px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
right: $container-padding-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--white-500);
|
||||||
|
height: 3.2rem;
|
||||||
|
float: right;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: $container-padding-x * 0.5;
|
||||||
|
top: -0.5rem;
|
||||||
|
width: 3.2rem;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
background: #fff;
|
||||||
|
transition: filter 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.icon {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
border-radius: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--submit,
|
||||||
|
.button--outline {
|
||||||
|
font-size:2rem;
|
||||||
|
padding: 1.6rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--outline {
|
||||||
|
background: none;
|
||||||
|
box-shadow: inset 0 0 0 2px #000;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: inset 0 0 0 2px var(--black-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
|
@ -819,11 +956,3 @@ iframe {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 1030;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user