diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index fc907cea..ec796cd4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -3,6 +3,8 @@ "About myself": "About myself", "About the project": "About the project", "Add comment": "Comment", + "Add image": "Add image", + "Add another image": "Add another image", "Address on Discourse": "Address on Discourse", "All": "All", "All authors": "All authors", @@ -32,6 +34,7 @@ "By views": "By views", "Characters": "Знаков", "Chat Title": "Chat Title", + "Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.", "Choose who you want to write to": "Choose who you want to write to", "Collaborate": "Help Edit", "Comments": "Comments", @@ -119,6 +122,7 @@ "Logout": "Logout", "Manifest": "Manifest", "Many files, choose only one": "Many files, choose only one", + "Material card": "Material card", "More": "More", "Most commented": "Commented", "Most read": "Readable", @@ -162,6 +166,7 @@ "Recent": "Fresh", "Reply": "Reply", "Report": "Complain", + "Required": "Required", "Resend code": "Send confirmation", "Restore password": "Restore password", "Save draft": "Save draft", @@ -272,6 +277,6 @@ "user already exist": "user already exists", "view": "view", "zine": "zine", - "Required": "Required", - "Unnamed draft": "Unnamed draft" + "Unnamed draft": "Unnamed draft", + "Publish Settings": "Publish Settings" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index c2d09cdf..850bd9c9 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -4,6 +4,8 @@ "About myself": "О себе", "About the project": "О проекте", "Add comment": "Комментировать", + "Add image": "Добавить изображение", + "Add another image": "Добавить другое изображение", "Add to bookmarks": "Добавить в закладки", "Address on Discourse": "Адрес на Дискурсе", "All": "Все", @@ -34,6 +36,7 @@ "By views": "По просмотрам", "Characters": "Знаков", "Chat Title": "Тема дискурса", + "Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.", "Choose who you want to write to": "Выберите кому хотите написать", "Collaborate": "Помочь редактировать", "Comments": "Комментарии", @@ -126,6 +129,7 @@ "Logout": "Выход", "Manifest": "Манифест", "Many files, choose only one": "Много файлов, выберете один", + "Material card": "Карточка материала", "More": "Ещё", "Most commented": "Комментируемое", "Most read": "Читаемое", @@ -173,11 +177,13 @@ "Recent": "Свежее", "Reply": "Ответить", "Report": "Пожаловаться", + "Required": "Поле обязательно для заполнения", "Resend code": "Выслать подтверждение", "Restore password": "Восстановить пароль", "Save": "Сохранить", "Save draft": "Сохранить черновик", "Save settings": "Сохранить настройки", + "Scroll up": "Наверх", "Search": "Поиск", "Search author": "Поиск автора", "Search topic": "Поиск темы", @@ -187,7 +193,6 @@ "Send": "Отправить", "Send link again": "Прислать ссылку ещё раз", "Settings": "Настройки", - "Scroll up": "Наверх", "Share": "Поделиться", "Short opening": "Небольшое вступление, чтобы заинтересовать читателя", "Show": "Показать", @@ -293,6 +298,6 @@ "user already exist": "пользователь уже существует", "view": "просмотр", "zine": "журнал", - "Required": "Поле обязательно для заполнения", + "Publish Settings": "Настройки публикации", "Unnamed draft": "Unnamed draft" } diff --git a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx index d74c637d..bb90fc93 100644 --- a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx +++ b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx @@ -8,9 +8,10 @@ import { useLocalize } from '../../../context/localize' import { Modal } from '../../Nav/Modal' import { Menu } from './Menu' import type { MenuItem } from './Menu/Menu' -import { showModal } from '../../../stores/ui' -import { UploadModalContent } from '../UploadModal' +import { hideModal, showModal } from '../../../stores/ui' +import { UploadModalContent } from '../UploadModalContent' import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' +import { imageProxy } from '../../../utils/imageProxy' type FloatingMenuProps = { editor: Editor @@ -35,6 +36,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => { const { t } = useLocalize() const [selectedMenuItem, setSelectedMenuItem] = createSignal() const [menuOpen, setMenuOpen] = createSignal(false) + const menuRef: { current: HTMLDivElement } = { current: null } const handleEmbedFormSubmit = async (value: string) => { // TODO: add support instagram embed (blockquote) const emb = await embedData(value) @@ -58,8 +60,6 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => { setMenuOpen(false) } - const menuRef: { current: HTMLDivElement } = { current: null } - useOutsideClickHandler({ containerRef: menuRef, 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 ( <>
@@ -100,7 +109,12 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
- setSelectedMenuItem()} editor={props.editor} /> + { + renderImage(value) + setSelectedMenuItem() + }} + /> ) diff --git a/src/components/Editor/InlineForm/InlineForm.tsx b/src/components/Editor/InlineForm/InlineForm.tsx index 3a5ea88d..486999f9 100644 --- a/src/components/Editor/InlineForm/InlineForm.tsx +++ b/src/components/Editor/InlineForm/InlineForm.tsx @@ -33,6 +33,9 @@ export const InlineForm = (props: Props) => { } else { setFormValueError(props.errorMessage) } + } else { + props.onSubmit(formValue()) + props.onClose() } } diff --git a/src/components/Editor/TopicSelect/index.ts b/src/components/Editor/TopicSelect/index.ts new file mode 100644 index 00000000..e2250eef --- /dev/null +++ b/src/components/Editor/TopicSelect/index.ts @@ -0,0 +1 @@ +export { TopicSelect } from './TopicSelect' diff --git a/src/components/Editor/UploadModal/UploadModalContent.module.scss b/src/components/Editor/UploadModalContent/UploadModalContent.module.scss similarity index 100% rename from src/components/Editor/UploadModal/UploadModalContent.module.scss rename to src/components/Editor/UploadModalContent/UploadModalContent.module.scss diff --git a/src/components/Editor/UploadModal/UploadModalContent.tsx b/src/components/Editor/UploadModalContent/UploadModalContent.tsx similarity index 87% rename from src/components/Editor/UploadModal/UploadModalContent.tsx rename to src/components/Editor/UploadModalContent/UploadModalContent.tsx index 47009e03..4f6bbc34 100644 --- a/src/components/Editor/UploadModal/UploadModalContent.tsx +++ b/src/components/Editor/UploadModalContent/UploadModalContent.tsx @@ -8,14 +8,11 @@ import { hideModal } from '../../../stores/ui' import { createDropzone, createFileUploader, UploadFile } from '@solid-primitives/upload' import { handleFileUpload } from '../../../utils/handleFileUpload' import { useLocalize } from '../../../context/localize' -import { Editor } from '@tiptap/core' import { Loading } from '../../_shared/Loading' import { verifyImg } from '../../../utils/verifyImg' -import { imageProxy } from '../../../utils/imageProxy' type Props = { - editor: Editor - closeCallback: () => void + onClose: (imgUrl?: string) => void } export const UploadModalContent = (props: Props) => { @@ -25,27 +22,17 @@ export const UploadModalContent = (props: Props) => { const [dragActive, setDragActive] = createSignal(false) const [dragError, setDragError] = createSignal() - const renderImage = (src: string) => { - props.editor - .chain() - .focus() - .setImage({ src: imageProxy(src) }) - .run() - hideModal() - } - const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' }) const runUpload = async (file) => { try { setIsUploading(true) const fileUrl = await handleFileUpload(file) setIsUploading(false) - props.closeCallback() - renderImage(fileUrl) + props.onClose(fileUrl) } catch (error) { - console.error('[upload image] error', error) setIsUploading(false) setUploadError(t('Error')) + console.error('[runUpload]', error) } } @@ -53,7 +40,7 @@ export const UploadModalContent = (props: Props) => { try { const data = await fetch(value) 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 = { source: blob.toString(), name: file.name, @@ -124,7 +111,7 @@ export const UploadModalContent = (props: Props) => { showInput={true} onClose={() => { hideModal() - props.closeCallback() + props.onClose() }} validate={(value) => verifyImg(value)} onSubmit={handleImageFormSubmit} diff --git a/src/components/Editor/UploadModal/index.ts b/src/components/Editor/UploadModalContent/index.ts similarity index 100% rename from src/components/Editor/UploadModal/index.ts rename to src/components/Editor/UploadModalContent/index.ts diff --git a/src/components/Editor/index.ts b/src/components/Editor/index.ts new file mode 100644 index 00000000..a9541268 --- /dev/null +++ b/src/components/Editor/index.ts @@ -0,0 +1,4 @@ +export { Editor } from './Editor' +export { Panel } from './Panel' +export { TopicSelect } from './TopicSelect' +export { UploadModalContent } from './UploadModalContent' diff --git a/src/components/Views/Edit.module.scss b/src/components/Views/Edit.module.scss index 7b796bfb..91e165a1 100644 --- a/src/components/Views/Edit.module.scss +++ b/src/components/Views/Edit.module.scss @@ -4,8 +4,62 @@ .articlePreview { border: 2px solid #e8e8e8; - min-height: 10em; padding: 1rem 1.2rem; + display: flex; + flex-direction: column; + min-height: 300px; + align-items: flex-start; + box-sizing: border-box; + + .shoutCardCoverContainer { + position: relative; + width: 100%; + .shoutCardCover { + height: 0; + overflow: hidden; + position: relative; + margin: 1.6rem 0; + padding-bottom: 56.2%; + + 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 { + @include font-size(2.2rem); + + font-weight: 700; + line-height: 1.25; + margin: auto 0 0.8rem; + } + + .shoutCardSubtitle { + @include font-size(1.7rem); + + color: #696969; + font-weight: 400; + line-height: 1.3; + margin-bottom: 0.8rem; + transition: color 0.2s, background-color 0.2s, box-shadow 0.2s; + } + + .shoutAuthor { + @include font-size(1.2rem); + + margin-right: 1.6rem; + color: rgb(0 0 0 / 70%); + } } .formHolder { @@ -60,7 +114,7 @@ .close { filter: invert(1); - margin: -1.6rem 0 0 -1.6rem; + margin: -1.6rem 0 0 -2.8rem; } section { diff --git a/src/components/Views/Edit.tsx b/src/components/Views/Edit.tsx index 3630f50f..66b9dc13 100644 --- a/src/components/Views/Edit.tsx +++ b/src/components/Views/Edit.tsx @@ -1,34 +1,43 @@ import { createSignal, onCleanup, onMount, Show } from 'solid-js' import { useLocalize } from '../../context/localize' import { clsx } from 'clsx' -import styles from './Edit.module.scss' import { Title } from '@solidjs/meta' import type { Shout, Topic } from '../../graphql/types.gen' import { apiClient } from '../../utils/apiClient' -import { TopicSelect } from '../Editor/TopicSelect/TopicSelect' import { useRouter } from '../../stores/router' -import { Editor } from '../Editor/Editor' -import { Panel } from '../Editor/Panel' import { useEditorContext } from '../../context/editor' +import { Editor, Panel, TopicSelect, UploadModalContent } from '../Editor' import { Icon } from '../_shared/Icon' +import { Button } from '../_shared/Button' +import styles from './Edit.module.scss' +import { useSession } from '../../context/session' +import { Modal } from '../Nav/Modal' +import { hideModal, showModal } from '../../stores/ui' type EditViewProps = { shout: Shout } +const scrollTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }) +} + export const EditView = (props: EditViewProps) => { const { t } = useLocalize() + const { user } = useSession() const [isScrolled, setIsScrolled] = createSignal(false) const [topics, setTopics] = createSignal(null) + const [coverImage, setCoverImage] = createSignal(null) const { page } = useRouter() - const { form, formErrors, actions: { setForm, setFormErrors } } = useEditorContext() - const [isSlugChanged, setIsSlugChanged] = createSignal(false) setForm({ @@ -76,11 +85,10 @@ export const EditView = (props: EditViewProps) => { setForm('slug', slug) } - const scrollTop = () => { - window.scrollTo({ - top: 0, - behavior: 'smooth' - }) + const handleUploadModalContentCloseSetCover = (imgUrl: string) => { + hideModal() + setCoverImage(imgUrl) + setForm('coverImageUrl', imgUrl) } return ( @@ -112,7 +120,7 @@ export const EditView = (props: EditViewProps) => { type="text" name="title" id="title" - placeholder="Заголовок" + placeholder={t('Header')} autocomplete="off" value={form.title} onInput={handleTitleInputChange} @@ -128,7 +136,7 @@ export const EditView = (props: EditViewProps) => { name="subtitle" id="subtitle" autocomplete="off" - placeholder="Подзаголовок" + placeholder={t('Subheader')} value={form.subtitle} onChange={(e) => setForm('subtitle', e.currentTarget.value)} /> @@ -143,7 +151,7 @@ export const EditView = (props: EditViewProps) => { [styles.visible]: page().route === 'editSettings' })} > -

Настройки публикации

+

{t('Publish Settings')}

Slug

@@ -209,18 +217,38 @@ export const EditView = (props: EditViewProps) => { {/*
*/} {/**/} -

Карточка материала на главной

+

{t('Material card')}

- Выберите заглавное изображение для статьи, тут сразу можно увидеть как карточка будет - выглядеть на главной странице + {t( + 'Choose a title image for the article. You can immediately see how the publication card will look like.' + )}

-
+
+
+ + handleUploadModalContentCloseSetCover(value)} /> + )