Card upload
This commit is contained in:
parent
39c99091f9
commit
2ea3216bff
|
@ -4,6 +4,7 @@
|
||||||
"About the project": "About the project",
|
"About the project": "About the project",
|
||||||
"Add comment": "Comment",
|
"Add comment": "Comment",
|
||||||
"Add image": "Add image",
|
"Add image": "Add image",
|
||||||
|
"Add another image": "Add another image",
|
||||||
"Address on Discourse": "Address on Discourse",
|
"Address on Discourse": "Address on Discourse",
|
||||||
"All": "All",
|
"All": "All",
|
||||||
"All authors": "All authors",
|
"All authors": "All authors",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"About the project": "О проекте",
|
"About the project": "О проекте",
|
||||||
"Add comment": "Комментировать",
|
"Add comment": "Комментировать",
|
||||||
"Add image": "Добавить изображение",
|
"Add image": "Добавить изображение",
|
||||||
|
"Add another image": "Добавить другое изображение",
|
||||||
"Add to bookmarks": "Добавить в закладки",
|
"Add to bookmarks": "Добавить в закладки",
|
||||||
"Address on Discourse": "Адрес на Дискурсе",
|
"Address on Discourse": "Адрес на Дискурсе",
|
||||||
"All": "Все",
|
"All": "Все",
|
||||||
|
|
|
@ -8,9 +8,10 @@ import { useLocalize } from '../../../context/localize'
|
||||||
import { Modal } from '../../Nav/Modal'
|
import { Modal } from '../../Nav/Modal'
|
||||||
import { Menu } from './Menu'
|
import { Menu } from './Menu'
|
||||||
import type { MenuItem } from './Menu/Menu'
|
import type { MenuItem } from './Menu/Menu'
|
||||||
import { showModal } from '../../../stores/ui'
|
import { hideModal, showModal } from '../../../stores/ui'
|
||||||
import { UploadModalContent } from '../UploadModal'
|
import { UploadModalContent } from '../UploadModalContent'
|
||||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
|
import { imageProxy } from '../../../utils/imageProxy'
|
||||||
|
|
||||||
type FloatingMenuProps = {
|
type FloatingMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
@ -35,6 +36,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [selectedMenuItem, setSelectedMenuItem] = createSignal<MenuItem | undefined>()
|
const [selectedMenuItem, setSelectedMenuItem] = createSignal<MenuItem | undefined>()
|
||||||
const [menuOpen, setMenuOpen] = createSignal<boolean>(false)
|
const [menuOpen, setMenuOpen] = createSignal<boolean>(false)
|
||||||
|
const menuRef: { current: HTMLDivElement } = { current: null }
|
||||||
const handleEmbedFormSubmit = async (value: string) => {
|
const handleEmbedFormSubmit = async (value: string) => {
|
||||||
// TODO: add support instagram embed (blockquote)
|
// TODO: add support instagram embed (blockquote)
|
||||||
const emb = await embedData(value)
|
const emb = await embedData(value)
|
||||||
|
@ -58,8 +60,6 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
setMenuOpen(false)
|
setMenuOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuRef: { current: HTMLDivElement } = { current: null }
|
|
||||||
|
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef: menuRef,
|
containerRef: menuRef,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
@ -69,6 +69,15 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const renderImage = (src: string) => {
|
||||||
|
props.editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.setImage({ src: imageProxy(src) })
|
||||||
|
.run()
|
||||||
|
hideModal()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={props.ref} class={styles.editorFloatingMenu}>
|
<div ref={props.ref} class={styles.editorFloatingMenu}>
|
||||||
|
@ -100,7 +109,12 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Modal variant="narrow" name="uploadImage" onClose={closeUploadModalHandler}>
|
<Modal variant="narrow" name="uploadImage" onClose={closeUploadModalHandler}>
|
||||||
<UploadModalContent closeCallback={() => setSelectedMenuItem()} editor={props.editor} />
|
<UploadModalContent
|
||||||
|
closeCallback={(value) => {
|
||||||
|
renderImage(value)
|
||||||
|
setSelectedMenuItem()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,9 @@ export const InlineForm = (props: Props) => {
|
||||||
} else {
|
} else {
|
||||||
setFormValueError(props.errorMessage)
|
setFormValueError(props.errorMessage)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
props.onSubmit(formValue())
|
||||||
|
props.onClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ import { verifyImg } from '../../../utils/verifyImg'
|
||||||
import { imageProxy } from '../../../utils/imageProxy'
|
import { imageProxy } from '../../../utils/imageProxy'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editor: Editor
|
closeCallback: (imgUrl?: string) => void
|
||||||
closeCallback: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadModalContent = (props: Props) => {
|
export const UploadModalContent = (props: Props) => {
|
||||||
|
@ -25,27 +24,17 @@ export const UploadModalContent = (props: Props) => {
|
||||||
const [dragActive, setDragActive] = createSignal(false)
|
const [dragActive, setDragActive] = createSignal(false)
|
||||||
const [dragError, setDragError] = createSignal<string | undefined>()
|
const [dragError, setDragError] = createSignal<string | undefined>()
|
||||||
|
|
||||||
const renderImage = (src: string) => {
|
|
||||||
props.editor
|
|
||||||
.chain()
|
|
||||||
.focus()
|
|
||||||
.setImage({ src: imageProxy(src) })
|
|
||||||
.run()
|
|
||||||
hideModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
||||||
const runUpload = async (file) => {
|
const runUpload = async (file) => {
|
||||||
try {
|
try {
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const fileUrl = await handleFileUpload(file)
|
const fileUrl = await handleFileUpload(file)
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
props.closeCallback()
|
props.closeCallback(fileUrl)
|
||||||
renderImage(fileUrl)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[upload image] error', error)
|
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
setUploadError(t('Error'))
|
setUploadError(t('Error'))
|
||||||
|
console.error('[runUpload]', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +42,7 @@ export const UploadModalContent = (props: Props) => {
|
||||||
try {
|
try {
|
||||||
const data = await fetch(value)
|
const data = await fetch(value)
|
||||||
const blob = await data.blob()
|
const blob = await data.blob()
|
||||||
const file = new File([blob], 'convertedFromUrl', { type: data.headers.get('Content-Type') })
|
const file = await new File([blob], 'convertedFromUrl', { type: data.headers.get('Content-Type') })
|
||||||
const fileToUpload: UploadFile = {
|
const fileToUpload: UploadFile = {
|
||||||
source: blob.toString(),
|
source: blob.toString(),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
@ -126,7 +115,7 @@ export const UploadModalContent = (props: Props) => {
|
||||||
hideModal()
|
hideModal()
|
||||||
props.closeCallback()
|
props.closeCallback()
|
||||||
}}
|
}}
|
||||||
validate={(value) => verifyImg(value)}
|
// validate={(value) => verifyImg(value)}
|
||||||
onSubmit={handleImageFormSubmit}
|
onSubmit={handleImageFormSubmit}
|
||||||
errorMessage={t('Invalid image link')}
|
errorMessage={t('Invalid image link')}
|
||||||
/>
|
/>
|
|
@ -1,3 +1,4 @@
|
||||||
export { Editor } from './Editor'
|
export { Editor } from './Editor'
|
||||||
export { Panel } from './Panel'
|
export { Panel } from './Panel'
|
||||||
export { TopicSelect } from './TopicSelect'
|
export { TopicSelect } from './TopicSelect'
|
||||||
|
export { UploadModalContent } from './UploadModalContent'
|
||||||
|
|
|
@ -11,6 +11,32 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.shoutCardCoverContainer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutCardCover {
|
||||||
|
height: 0;
|
||||||
|
margin-bottom: 1.6rem;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: 56.2%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
transition: transform 1s ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.shoutCardTitle {
|
.shoutCardTitle {
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
|
@ -89,7 +115,7 @@
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
margin: -1.6rem 0 0 -1.6rem;
|
margin: -1.6rem 0 0 -2.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -6,11 +6,13 @@ import type { Shout, Topic } from '../../graphql/types.gen'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { useEditorContext } from '../../context/editor'
|
import { useEditorContext } from '../../context/editor'
|
||||||
import { Editor, Panel, TopicSelect } from '../Editor'
|
import { Editor, Panel, TopicSelect, UploadModalContent } from '../Editor'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
import styles from './Edit.module.scss'
|
import styles from './Edit.module.scss'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
|
import { hideModal, showModal } from '../../stores/ui'
|
||||||
|
|
||||||
type EditViewProps = {
|
type EditViewProps = {
|
||||||
shout: Shout
|
shout: Shout
|
||||||
|
@ -22,14 +24,13 @@ export const EditView = (props: EditViewProps) => {
|
||||||
|
|
||||||
const [isScrolled, setIsScrolled] = createSignal(false)
|
const [isScrolled, setIsScrolled] = createSignal(false)
|
||||||
const [topics, setTopics] = createSignal<Topic[]>(null)
|
const [topics, setTopics] = createSignal<Topic[]>(null)
|
||||||
|
const [coverImage, setCoverImage] = createSignal<string>(null)
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
formErrors,
|
formErrors,
|
||||||
actions: { setForm, setFormErrors }
|
actions: { setForm, setFormErrors }
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
const [isSlugChanged, setIsSlugChanged] = createSignal(false)
|
const [isSlugChanged, setIsSlugChanged] = createSignal(false)
|
||||||
|
|
||||||
setForm({
|
setForm({
|
||||||
|
@ -88,7 +89,14 @@ export const EditView = (props: EditViewProps) => {
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
console.log('!!! :')
|
|
||||||
|
const handleSetCover = (imgUrl: string) => {
|
||||||
|
hideModal()
|
||||||
|
console.log('!!! imgUrl:', imgUrl)
|
||||||
|
setCoverImage(imgUrl)
|
||||||
|
setForm('coverImageUrl', imgUrl)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
|
@ -223,8 +231,18 @@ export const EditView = (props: EditViewProps) => {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class={styles.articlePreview}>
|
<div class={styles.articlePreview}>
|
||||||
<Button variant="primary" onClick={() => ''} value={t('Add image')} />
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => showModal('uploadImage')}
|
||||||
|
value={coverImage() ? t('Add another image') : t('Add image')}
|
||||||
|
/>
|
||||||
|
<Show when={coverImage() ?? form.coverImageUrl}>
|
||||||
|
<div class={styles.shoutCardCoverContainer}>
|
||||||
|
<div class={styles.shoutCardCover}>
|
||||||
|
<img src={coverImage() || form.coverImageUrl} alt={form.title} loading="lazy" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
<div class={styles.shoutCardTitle}>{form.title}</div>
|
<div class={styles.shoutCardTitle}>{form.title}</div>
|
||||||
<div class={styles.shoutCardSubtitle}>{form.subtitle}</div>
|
<div class={styles.shoutCardSubtitle}>{form.subtitle}</div>
|
||||||
<div class={styles.shoutAuthor}>{user().name}</div>
|
<div class={styles.shoutAuthor}>{user().name}</div>
|
||||||
|
@ -235,6 +253,9 @@ export const EditView = (props: EditViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<Modal variant="narrow" name="uploadImage">
|
||||||
|
<UploadModalContent closeCallback={(value) => handleSetCover(value)} />
|
||||||
|
</Modal>
|
||||||
<Panel shoutSlug={props.shout.slug} />
|
<Panel shoutSlug={props.shout.slug} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user