diff --git a/package.json b/package.json index b3a96ab9..855840f8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@hocuspocus/provider": "2.0.6", + "fast-deep-equal": "3.1.3", "form-data": "4.0.0", "i18next": "22.4.15", "mailgun.js": "8.2.1", @@ -100,7 +101,6 @@ "cookie-signature": "1.2.1", "cosmiconfig-toml-loader": "1.0.0", "cross-env": "7.0.3", - "debounce": "1.2.1", "eslint": "8.40.0", "eslint-config-stylelint": "18.0.0", "eslint-import-resolver-typescript": "3.5.5", @@ -111,7 +111,6 @@ "eslint-plugin-solid": "0.12.1", "eslint-plugin-sonarjs": "0.19.0", "eslint-plugin-unicorn": "47.0.0", - "fast-deep-equal": "3.1.3", "graphql": "16.6.0", "graphql-tag": "2.12.6", "graphql-ws": "5.12.1", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index c3e06da6..a5a3ed40 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -302,8 +302,6 @@ "Topic is supported by": "Topic is supported by", "Topics": "Topics", "Topics which supported by author": "Topics which supported by author", - "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?", - "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?", "Try to find another way": "Try to find another way", "Unfollow": "Unfollow", "Unfollow the topic": "Unfollow the topic", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index b3e49787..5612625c 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -319,8 +319,6 @@ "Topic is supported by": "Тему поддерживают", "Topics": "Темы", "Topics which supported by author": "Автор поддерживает темы", - "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 profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", "Try to find another way": "Попробуйте найти по-другому", "Unfollow": "Отписаться", "Unfollow the topic": "Отписаться от темы", diff --git a/src/components/Article/Article.module.scss b/src/components/Article/Article.module.scss index 42d0b498..342db76f 100644 --- a/src/components/Article/Article.module.scss +++ b/src/components/Article/Article.module.scss @@ -1,13 +1,11 @@ h1 { @include font-size(4rem); - line-height: 1.1; margin-top: 0.5em; } h2 { @include font-size(4rem); - line-height: 1.1; } @@ -46,7 +44,7 @@ img { margin: 3.2rem 0; position: relative; - &::before { + &:before { background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNiAyMSIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0wIDEwLjUwMDFWMjFIMTAuNzA2MVYxMC41MDAxSDQuNTg4MzNDNC41ODgzMyA3LjE4NjU4IDcuMzI3NTggNC41MDAwOCAxMC43MDYxIDQuNTAwMDhWMEM0Ljc5Mjk3IDAgMCA0LjcwMDczIDAgMTAuNDk5OVYxMC41MDAxWk0yNiA0LjUwMDA4VjBDMjAuMDg2OSAwIDE1LjI5MzkgNC43MDA3MyAxNS4yOTM5IDEwLjQ5OTlWMjAuOTk5OUgyNlYxMC41MDA1SDE5Ljg4MjJDMTkuODgyNCA3LjE4NyAyMi42MjE3IDQuNTAwNSAyNiA0LjUwMDVWNC41MDAwOFoiIGZpbGw9ImJsYWNrIi8+PC9zdmc+') no-repeat; content: ''; @@ -62,19 +60,17 @@ img { blockquote[data-type='quote'], ta-quotation { @include font-size(1.4rem); - border: solid #000; border-width: 0 0 0 2px; display: block; font-weight: 500; line-height: 1.6; - margin: 1.6rem 0 0 calc(-8.3333% - 2px); - padding: 0 0 0 8.3333%; + margin: 1.6rem 0 0 calc(-8.33333% - 2px); + padding: 0 0 0 8.33333%; &[data-float='left'], &[data-float='right'] { @include font-size(2.2rem); - line-height: 1.4; } @@ -88,7 +84,7 @@ img { } } - &::before { + &:before { display: none; } } @@ -99,15 +95,13 @@ img { ta-border-sub { background: #f1f2f3; display: block; - @include font-size(1.4rem); - margin: 3.2rem 0; padding: 3.2rem; @include media-breakpoint-up(md) { - margin: 3.2rem -8.3333%; - padding: 3.2rem 8.3333%; + margin: 3.2rem -8.33333%; + padding: 3.2rem 8.33333%; } p:last-child { @@ -150,7 +144,7 @@ img { } @include media-breakpoint-up(md) { - margin: 0 8.3333% 3.2rem -16.6666%; + margin: 0 8.33333% 3.2rem -16.66666%; } } @@ -160,7 +154,7 @@ img { } @include media-breakpoint-up(md) { - margin: 0 -16.6666% 3.2rem 8.3333%; + margin: 0 -16.66666% 3.2rem 8.33333%; } } @@ -174,13 +168,13 @@ img { h2 { @include media-breakpoint-up(xl) { - margin-left: -16.6666%; + margin-left: -16.6666666666%; } } :global(.img-align-left) { float: left; - margin: 1em 8.3333% 0.5em 0; + margin: 1em 8.333333333% 0.5em 0; } :global(.width-30) { @@ -193,18 +187,18 @@ img { :global(.img-align-left.width-50) { @include media-breakpoint-up(xl) { - margin-left: -16.6666%; + margin-left: -16.6666666666%; } } :global(.img-align-right) { float: right; - margin: 1em 0 0.5em 8.3333%; + margin: 1em 0 0.5em 8.333333333%; } :global(.img-align-right.width-50) { @include media-breakpoint-up(xl) { - margin-right: -16.6666%; + margin-right: -16.6666666666%; } } @@ -504,7 +498,6 @@ img { button { @include font-size(1.5rem); - border-radius: 0.8rem; margin-right: 1.2rem; padding: 0.9rem 1.2rem; diff --git a/src/components/Article/Comment.tsx b/src/components/Article/Comment.tsx index 6cfaf59b..c8cc6028 100644 --- a/src/components/Article/Comment.tsx +++ b/src/components/Article/Comment.tsx @@ -17,6 +17,7 @@ import { useSnackbar } from '../../context/snackbar' import { useConfirm } from '../../context/confirm' import { Author, Reaction, ReactionKind } from '../../graphql/types.gen' + import { router } from '../../stores/router' import styles from './Comment.module.scss' @@ -47,7 +48,6 @@ export const Comment = (props: Props) => { const { actions: { showConfirm } } = useConfirm() - const { actions: { showSnackbar } } = useSnackbar() diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 886119e0..61940471 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -132,7 +132,7 @@ export const FullArticle = (props: Props) => { <> {props.article.title}
-
+
{/*TODO: Check styles.shoutTopic*/} @@ -212,7 +212,7 @@ export const FullArticle = (props: Props) => {
- +
diff --git a/src/components/Author/Userpic/Userpic.tsx b/src/components/Author/Userpic/Userpic.tsx index 077109a5..57c205bd 100644 --- a/src/components/Author/Userpic/Userpic.tsx +++ b/src/components/Author/Userpic/Userpic.tsx @@ -1,4 +1,5 @@ import { Show } from 'solid-js' +import type { Author, User } from '../../../graphql/types.gen' import styles from './Userpic.module.scss' import { clsx } from 'clsx' import { imageProxy } from '../../../utils/imageProxy' diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 7d8daf88..56bb2514 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -62,7 +62,6 @@ const providers: Record = {} export const Editor = (props: Props) => { const { t } = useLocalize() const { user } = useSession() - const [isCommonMarkup, setIsCommonMarkup] = createSignal(false) const docName = `shout-${props.shoutId}` @@ -248,7 +247,7 @@ export const Editor = (props: Props) => { <>
(editorElRef.current = el)} id="editorBody" /> - + = 768px) { + @media (min-width: 768px) { padding-left: calc(21.9% + 3px); max-width: 72.7%; } - - @media (width >= 1200px) { + @media (min-width: 1200px) { padding-left: calc(21.5% + 3px); max-width: 64.9%; } @@ -39,35 +38,32 @@ .articleEditor figure, .articleEditor .uploadedImage, .articleEditor article[data-type='incut'] { - @media (width >= 768px) { + @media (min-width: 768px) { margin-left: calc(21.9% + 3px) !important; max-width: 73.6%; } - - @media (width >= 1200px) { + @media (min-width: 1200px) { margin-left: calc(21.4% + 3px) !important; max-width: 65.3%; } } .articleEditor h2 { - @media (width >= 768px) { + @media (min-width: 768px) { padding-left: calc(21.9% + 2px); max-width: 72.7%; } - - @media (width >= 1200px) { + @media (min-width: 1200px) { padding-left: 21.5%; max-width: 87.1%; } } .articleEditor h3 { - @media (width >= 768px) { + @media (min-width: 768px) { padding-left: calc(21.9% + 2px); } - - @media (width >= 1200px) { + @media (min-width: 1200px) { padding-left: 21.5%; max-width: 87.1%; } @@ -77,7 +73,7 @@ .articleEditor * h2, .articleEditor * h3, .articleEditor * h4 { - @media (width >= 768px) { + @media (min-width: 768px) { padding-left: unset; max-width: unset; } @@ -187,7 +183,6 @@ mark.highlight { &[data-type='quote'] { @include font-size(1.4rem); - border: solid #000; border-width: 0 0 0 2px; margin: 1.6rem 0; @@ -209,9 +204,7 @@ mark.highlight { &[data-type='punchline'] { border: solid #000; border-width: 2px 0; - @include font-size(3.2rem); - font-weight: 700; line-height: 1.2; margin: 1em 0; @@ -220,7 +213,6 @@ mark.highlight { &[data-float='left'], &[data-float='right'] { @include font-size(2.2rem); - line-height: 1.4; } @@ -238,9 +230,7 @@ mark.highlight { .ProseMirror article[data-type='incut'] { background: #f1f2f3; - @include font-size(1.4rem); - margin: 1em -1rem; padding: 2em 2rem; transition: background 0.3s ease-in-out; diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss index deb8f946..266b4adb 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss +++ b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss @@ -19,6 +19,7 @@ .confirmModalActions { display: flex; justify-content: space-between; + margin-top: 16px; } @@ -26,23 +27,26 @@ display: block; width: 100%; margin-right: 12px; + font-weight: 700; + margin-top: 32px; padding: 1.6rem !important; border: 1px solid black; &:hover { - background-color: rgb(0 0 0 / 8%); + background-color: rgba(0, 0, 0, 0.08); } } .confirmModalButtonPrimary { margin-right: 0; + background-color: black; color: white; border: none; &:hover { - background-color: rgb(0 0 0 / 60%); + background-color: rgba(0, 0, 0, 0.6); } } diff --git a/src/components/Nav/Header.tsx b/src/components/Nav/Header.tsx index 614597da..58135664 100644 --- a/src/components/Nav/Header.tsx +++ b/src/components/Nav/Header.tsx @@ -1,6 +1,7 @@ import { Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js' -import { getPagePath, redirectPage } from '@nanostores/router' +import { getPagePath } from '@nanostores/router' import { clsx } from 'clsx' +import { redirectPage } from '@nanostores/router' import { Modal } from './Modal' import { AuthModal } from './AuthModal' diff --git a/src/components/TableOfContents/TableOfContents.module.scss b/src/components/TableOfContents/TableOfContents.module.scss index c6f310a9..56ab8ace 100644 --- a/src/components/TableOfContents/TableOfContents.module.scss +++ b/src/components/TableOfContents/TableOfContents.module.scss @@ -1,26 +1,28 @@ .TableOfContentsFixedWrapper { - position: absolute; - top: 0; - right: 0; + position: fixed; + top: 150px; + right: 20px; + width: 281px; - min-height: 100%; } .TableOfContentsFixedWrapperLefted { right: auto; - left: 70px; + left: 20px; } .TableOfContentsContainer { - position: sticky; - top: 150px; - right: 20px; + position: absolute; + right: 0; + top: 0; + display: flex; width: 100%; height: auto; padding: 20px; flex-direction: column; align-items: flex-start; + background-color: transparent; } @@ -32,6 +34,7 @@ .TableOfContentsHeading { margin: 0; + color: #000; font-size: 22px; font-style: normal; @@ -43,17 +46,20 @@ position: absolute; right: 20px; top: 10px; + display: flex; align-items: center; justify-content: center; + width: 40px; height: 40px; + background: transparent; border: none; cursor: pointer; &:hover { - box-shadow: 0 0 1px 1px rgb(0 0 0 / 30%); + box-shadow: 0px 0px 1px 1px rgba(0, 0, 0, 0.3); } } @@ -64,16 +70,18 @@ .TableOfContentsHeadingsList { position: relative; + display: flex; flex-direction: column; list-style-type: none; + margin: 0; padding: 0 38px 0 0; - width: 100%; } .TableOfContentsHeadingsItem { margin-top: 20px; + color: #000; font-size: 14px; font-style: normal; @@ -83,7 +91,7 @@ letter-spacing: -0.14px; &:hover { - color: rgb(0 0 0 / 50%); + transform: scale(1.05); } } diff --git a/src/components/TableOfContents/TableOfContents.tsx b/src/components/TableOfContents/TableOfContents.tsx index 7a3793cd..a0b36dd2 100644 --- a/src/components/TableOfContents/TableOfContents.tsx +++ b/src/components/TableOfContents/TableOfContents.tsx @@ -1,12 +1,10 @@ -import { For, Show, createSignal, createEffect, on } from 'solid-js' +import { onMount, For, Show, createSignal } from 'solid-js' import { clsx } from 'clsx' import { DEFAULT_HEADER_OFFSET } from '../../stores/router' import { useLocalize } from '../../context/localize' -import { debounce } from 'debounce' - import { Icon } from '../_shared/Icon' import styles from './TableOfContents.module.scss' @@ -14,7 +12,6 @@ import styles from './TableOfContents.module.scss' interface Props { variant: 'article' | 'editor' parentSelector: string - body: string } const scrollToHeader = (element) => { @@ -33,33 +30,21 @@ export const TableOfContents = (props: Props) => { const [headings, setHeadings] = createSignal([]) const [areHeadingsLoaded, setAreHeadingsLoaded] = createSignal(false) - const [isVisible, setIsVisible] = createSignal(props.variant === 'article') + const [isVisible, setIsVisible] = createSignal(true) const toggleIsVisible = () => { setIsVisible((visible) => !visible) } - const updateHeadings = () => { + onMount(() => { const { parentSelector } = props - // eslint-disable-next-line unicorn/prefer-spread setHeadings(Array.from(document.querySelector(parentSelector).querySelectorAll('h2, h3, h4'))) - setAreHeadingsLoaded(true) - } - const debouncedUpdateHeadings = debounce(updateHeadings, 500) - createEffect( - on( - () => props.body, - () => debouncedUpdateHeadings() - ) - ) + setAreHeadingsLoaded(true) + }) return ( - 2 : headings().length > 1) - } - > +
{ }) } - const [prevForm, setPrevForm] = createStore(clone(form)) + const [prevForm, setPrevForm] = createSignal(clone(form)) const [saving, setSaving] = createSignal(false) const mediaItems: Accessor = createMemo(() => { @@ -90,20 +94,6 @@ export const EditView = (props: Props) => { }) }) - onMount(() => { - // eslint-disable-next-line unicorn/consistent-function-scoping - const handleBeforeUnload = (event) => { - if (!deepEqual(prevForm, form)) { - event.returnValue = t( - `There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?` - ) - } - } - - window.addEventListener('beforeunload', handleBeforeUnload) - onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) - }) - const handleTitleInputChange = (value) => { setForm('title', value) setForm('slug', slugify(value)) @@ -184,7 +174,7 @@ export const EditView = (props: Props) => { const autoSaveRecursive = () => { autoSaveTimeOutId = setTimeout(async () => { - const hasChanges = !deepEqual(form, prevForm) + const hasChanges = !deepEqual(form, prevForm()) if (hasChanges) { setSaving(true) if (props.shout.visibility === 'owner') { diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 4dce0a32..a14e4875 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' import styles from './PublishSettings.module.scss' -import { createSignal, onMount, Show } from 'solid-js' +import { createEffect, createSignal, onMount, Show } from 'solid-js' import { TopicSelect, UploadModalContent } from '../../Editor' import { Button } from '../../_shared/Button' import { hideModal, showModal } from '../../../stores/ui' diff --git a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx index 452cb032..d5e70faf 100644 --- a/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx +++ b/src/components/_shared/GrowingTextarea/GrowingTextarea.tsx @@ -1,6 +1,7 @@ import { clsx } from 'clsx' import styles from './GrowingTextarea.module.scss' -import { createSignal, Show } from 'solid-js' +import { createSignal, Show, Switch } from 'solid-js' +import { style } from 'solid-js/web' type Props = { class?: string diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 28316a7d..3cd0efca 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -65,17 +65,6 @@ const topic2topicInput = (topic: Topic): TopicInput => { } } -const saveDraftToLocalStorage = (formToSave: ShoutForm) => { - localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) -} -const getDraftFromLocalStorage = (shoutId: number) => { - return JSON.parse(localStorage.getItem(`shout-${shoutId}`)) -} - -const removeDraftFromLocalStorage = (shoutId: number) => { - localStorage.removeItem(`shout-${shoutId}`) -} - export const EditorProvider = (props: { children: JSX.Element }) => { const { t } = useLocalize() @@ -175,6 +164,17 @@ export const EditorProvider = (props: { children: JSX.Element }) => { await updateShout(draftForm, { publish: false }) } + const saveDraftToLocalStorage = (formToSave: ShoutForm) => { + localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) + } + const getDraftFromLocalStorage = (shoutId: number) => { + return JSON.parse(localStorage.getItem(`shout-${shoutId}`)) + } + + const removeDraftFromLocalStorage = (shoutId: number) => { + localStorage.removeItem(`shout-${shoutId}`) + } + const publishShout = async (formToPublish: ShoutForm) => { if (isEditorPanelVisible()) { toggleEditorPanel() diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 6588c354..0e4a8cbf 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -34,16 +34,14 @@ const useProfileForm = () => { if (!currentSlug()) return try { await loadAuthor({ slug: currentSlug() }) - const updatedFormValues = { + setForm({ name: currentAuthor()?.name, slug: currentAuthor()?.slug, bio: currentAuthor()?.bio, about: currentAuthor()?.about, userpic: currentAuthor()?.userpic, links: currentAuthor()?.links - } - - setForm(updatedFormValues) + }) } catch (error) { console.error(error) } diff --git a/src/pages/profile/profileSettings.page.tsx b/src/pages/profile/profileSettings.page.tsx index 6b086435..2a8ebf68 100644 --- a/src/pages/profile/profileSettings.page.tsx +++ b/src/pages/profile/profileSettings.page.tsx @@ -1,8 +1,7 @@ import { PageLayout } from '../../components/_shared/PageLayout' import { Icon } from '../../components/_shared/Icon' import ProfileSettingsNavigation from '../../components/Discours/ProfileSettingsNavigation' -import { For, createSignal, Show, onMount, onCleanup } from 'solid-js' -import deepEqual from 'fast-deep-equal' +import { For, createSignal, Show, onMount } from 'solid-js' import { clsx } from 'clsx' import styles from './Settings.module.scss' import { useProfileForm } from '../../context/profile' @@ -14,8 +13,6 @@ import { useSnackbar } from '../../context/snackbar' import { useLocalize } from '../../context/localize' import { handleFileUpload } from '../../utils/handleFileUpload' import { Userpic } from '../../components/Author/Userpic' -import { createStore } from 'solid-js/store' -import { clone } from '../../utils/clone' export const ProfileSettingsPage = () => { const { t } = useLocalize() @@ -32,7 +29,6 @@ export const ProfileSettingsPage = () => { actions: { loadSession } } = useSession() const { form, updateFormField, submit, slugError } = useProfileForm() - const [prevForm, setPrevForm] = createStore(clone(form)) const handleChangeSocial = (value: string) => { if (validateUrl(value)) { @@ -49,7 +45,6 @@ export const ProfileSettingsPage = () => { try { await submit(form) - setPrevForm(clone(form)) showSnackbar({ body: t('Profile successfully saved') }) } catch { showSnackbar({ type: 'error', body: t('Error') }) @@ -75,23 +70,9 @@ export const ProfileSettingsPage = () => { } const [hostname, setHostname] = createSignal(null) + onMount(() => setHostname(window?.location.host)) - onMount(() => { - setHostname(window?.location.host) - - // eslint-disable-next-line unicorn/consistent-function-scoping - const handleBeforeUnload = (event) => { - if (!deepEqual(form, prevForm)) { - event.returnValue = t( - 'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?' - ) - } - } - - window.addEventListener('beforeunload', handleBeforeUnload) - onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) - }) - + console.log('!!! form:', form) return (