merged
This commit is contained in:
commit
12e5743d24
|
@ -28,6 +28,8 @@
|
||||||
"All posts": "All posts",
|
"All posts": "All posts",
|
||||||
"All topics": "All topics",
|
"All topics": "All topics",
|
||||||
"Almost done! Check your email.": "Almost done! Just checking your email.",
|
"Almost done! Check your email.": "Almost done! Just checking your email.",
|
||||||
|
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
|
||||||
|
"Are you sure you want to delete this draft?": "Are you sure you want to delete this draft?",
|
||||||
"Are you sure you want to to proceed the action?": "Are you sure you want to to proceed the action?",
|
"Are you sure you want to to proceed the action?": "Are you sure you want to to proceed the action?",
|
||||||
"Art": "Art",
|
"Art": "Art",
|
||||||
"Artist": "Artist",
|
"Artist": "Artist",
|
||||||
|
@ -100,6 +102,7 @@
|
||||||
"Discussion rules": "Discussion rules",
|
"Discussion rules": "Discussion rules",
|
||||||
"Discussions": "Discussions",
|
"Discussions": "Discussions",
|
||||||
"Dogma": "Dogma",
|
"Dogma": "Dogma",
|
||||||
|
"Draft successfully deleted": "Draft successfully deleted",
|
||||||
"Drafts": "Drafts",
|
"Drafts": "Drafts",
|
||||||
"Drag the image to this area": "Drag the image to this area",
|
"Drag the image to this area": "Drag the image to this area",
|
||||||
"Each image must be no larger than 5 MB.": "Each image must be no larger than 5 MB.",
|
"Each image must be no larger than 5 MB.": "Each image must be no larger than 5 MB.",
|
||||||
|
@ -193,6 +196,7 @@
|
||||||
"Manifesto": "Manifesto",
|
"Manifesto": "Manifesto",
|
||||||
"Many files, choose only one": "Many files, choose only one",
|
"Many files, choose only one": "Many files, choose only one",
|
||||||
"Material card": "Material card",
|
"Material card": "Material card",
|
||||||
|
"Message": "Message",
|
||||||
"More": "More",
|
"More": "More",
|
||||||
"Most commented": "Commented",
|
"Most commented": "Commented",
|
||||||
"Most read": "Readable",
|
"Most read": "Readable",
|
||||||
|
@ -206,12 +210,19 @@
|
||||||
"New only": "New only",
|
"New only": "New only",
|
||||||
"New password": "New password",
|
"New password": "New password",
|
||||||
"New stories every day and even more!": "New stories and more are waiting for you every day!",
|
"New stories every day and even more!": "New stories and more are waiting for you every day!",
|
||||||
"NewCommentNotificationText": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication {shoutTitle} from {lastCommenterName}{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}",
|
|
||||||
"NewReplyNotificationText": "{commentsCount, plural, one {New reply} other {{commentsCount} replays} other {{commentsCount} новых ответов}} to your publication {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
|
"NotificationNewCommentText1": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication",
|
||||||
|
"NotificationNewCommentText2": "from",
|
||||||
|
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}",
|
||||||
|
|
||||||
|
"NotificationNewReplyText1": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication",
|
||||||
|
"NotificationNewReplyText2": "from",
|
||||||
|
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
|
||||||
|
|
||||||
"Newsletter": "Newsletter",
|
"Newsletter": "Newsletter",
|
||||||
"Night mode": "Night mode",
|
"Night mode": "Night mode",
|
||||||
"No notifications, yet": "No notifications, yet",
|
"No notifications yet": "No notifications yet",
|
||||||
"No such account, please try to register": "No such account found, please try to register",
|
"Write good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here",
|
||||||
"Nothing here yet": "There's nothing here yet",
|
"Nothing here yet": "There's nothing here yet",
|
||||||
"Nothing is here": "There is nothing here",
|
"Nothing is here": "There is nothing here",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
|
@ -353,7 +364,6 @@
|
||||||
"Where": "From",
|
"Where": "From",
|
||||||
"Words": "Слов",
|
"Words": "Слов",
|
||||||
"Work with us": "Cooperate with Discourse",
|
"Work with us": "Cooperate with Discourse",
|
||||||
"Message": "Message",
|
|
||||||
"Write a comment...": "Write a comment...",
|
"Write a comment...": "Write a comment...",
|
||||||
"Write a short introduction": "Write a short introduction",
|
"Write a short introduction": "Write a short introduction",
|
||||||
"Write about the topic": "Write about the topic",
|
"Write about the topic": "Write about the topic",
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
"All posts": "Все публикации",
|
"All posts": "Все публикации",
|
||||||
"All topics": "Все темы",
|
"All topics": "Все темы",
|
||||||
"Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.",
|
"Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.",
|
||||||
|
"Are you sure you want to delete this comment?": "Уверены, что хотите удалить этот комментарий?",
|
||||||
|
"Are you sure you want to delete this draft?": "Уверены, что хотите удалить этот черновик?",
|
||||||
"Are you sure you want to to proceed the action?": "Вы уверены, что хотите продолжить?",
|
"Are you sure you want to to proceed the action?": "Вы уверены, что хотите продолжить?",
|
||||||
"Art": "Искусство",
|
"Art": "Искусство",
|
||||||
"Artist": "Исполнитель",
|
"Artist": "Исполнитель",
|
||||||
|
@ -104,6 +106,7 @@
|
||||||
"Discussion rules": "Правила сообществ самиздата в соцсетях",
|
"Discussion rules": "Правила сообществ самиздата в соцсетях",
|
||||||
"Discussions": "Дискуссии",
|
"Discussions": "Дискуссии",
|
||||||
"Dogma": "Догма",
|
"Dogma": "Догма",
|
||||||
|
"Draft successfully deleted": "Черновик успешно удален",
|
||||||
"Drafts": "Черновики",
|
"Drafts": "Черновики",
|
||||||
"Drag the image to this area": "Перетащите изображение в эту область",
|
"Drag the image to this area": "Перетащите изображение в эту область",
|
||||||
"Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.",
|
"Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.",
|
||||||
|
@ -217,11 +220,19 @@
|
||||||
"New only": "Только новые",
|
"New only": "Только новые",
|
||||||
"New password": "Новый пароль",
|
"New password": "Новый пароль",
|
||||||
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
|
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
|
||||||
"NewCommentNotificationText": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
|
||||||
"NewReplyNotificationText": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} к вашему комментарию к публикации {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
"NotificationNewCommentText1": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации",
|
||||||
|
"NotificationNewCommentText2": "от",
|
||||||
|
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
||||||
|
|
||||||
|
"NotificationNewReplyText1": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} к вашему комментарию к публикации",
|
||||||
|
"NotificationNewReplyText2": "от",
|
||||||
|
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
|
||||||
|
|
||||||
"Newsletter": "Рассылка",
|
"Newsletter": "Рассылка",
|
||||||
"Night mode": "Ночная тема",
|
"Night mode": "Ночная тема",
|
||||||
"No notifications, yet": "Тут пока пусто",
|
"No notifications yet": "Уведомлений пока нет",
|
||||||
|
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
|
||||||
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
|
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
|
||||||
"Nothing here yet": "Здесь пока ничего нет",
|
"Nothing here yet": "Здесь пока ничего нет",
|
||||||
"Nothing is here": "Здесь ничего нет",
|
"Nothing is here": "Здесь ничего нет",
|
||||||
|
|
|
@ -62,7 +62,12 @@ export const Comment = (props: Props) => {
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
if (comment()?.id) {
|
if (comment()?.id) {
|
||||||
try {
|
try {
|
||||||
const isConfirmed = await showConfirm()
|
const isConfirmed = await showConfirm({
|
||||||
|
confirmBody: t('Are you sure you want to delete this comment?'),
|
||||||
|
confirmButtonLabel: t('Delete'),
|
||||||
|
confirmButtonVariant: 'danger',
|
||||||
|
declineButtonVariant: 'primary'
|
||||||
|
})
|
||||||
|
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
await deleteReaction(comment().id)
|
await deleteReaction(comment().id)
|
||||||
|
@ -136,7 +141,7 @@ export const Comment = (props: Props) => {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<small>
|
<small>
|
||||||
<a href={`#comment-${comment()?.id}`}>{comment()?.shout.title || ''}</a>
|
<a href={`#comment_${comment()?.id}`}>{comment()?.shout.title || ''}</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -174,7 +179,7 @@ export const Comment = (props: Props) => {
|
||||||
<CommentRatingControl comment={comment()} />
|
<CommentRatingControl comment={comment()} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class={styles.commentBody} id={'comment-' + (comment().id || '')}>
|
<div class={styles.commentBody}>
|
||||||
<Show when={editMode()} fallback={<MD body={body()} />}>
|
<Show when={editMode()} fallback={<MD body={body()} />}>
|
||||||
<Suspense fallback={<p>{t('Loading')}</p>}>
|
<Suspense fallback={<p>{t('Loading')}</p>}>
|
||||||
<SimplifiedEditor
|
<SimplifiedEditor
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Show, createMemo, createSignal, onMount, For, createEffect } from 'solid-js'
|
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
|
||||||
import { Comment } from './Comment'
|
import { Comment } from './Comment'
|
||||||
import styles from './Article.module.scss'
|
import styles from './Article.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -181,7 +181,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
<SimplifiedEditor
|
<SimplifiedEditor
|
||||||
quoteEnabled={true}
|
quoteEnabled={true}
|
||||||
imageEnabled={true}
|
imageEnabled={true}
|
||||||
autoFocus={true}
|
autoFocus={false}
|
||||||
submitByCtrlEnter={true}
|
submitByCtrlEnter={true}
|
||||||
placeholder={t('Write a comment...')}
|
placeholder={t('Write a comment...')}
|
||||||
onSubmit={(value) => handleSubmitComment(value)}
|
onSubmit={(value) => handleSubmitComment(value)}
|
||||||
|
|
|
@ -28,11 +28,24 @@ import styles from './Article.module.scss'
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
import { CardTopic } from '../Feed/CardTopic'
|
||||||
import { createPopper } from '@popperjs/core'
|
import { createPopper } from '@popperjs/core'
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
article: Shout
|
article: Shout
|
||||||
scrollToComments?: boolean
|
scrollToComments?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ArticlePageSearchParams = {
|
||||||
|
scrollTo: 'comments'
|
||||||
|
commentId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollTo = (el: HTMLElement) => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: el.offsetTop - 96,
|
||||||
|
left: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const FullArticle = (props: Props) => {
|
export const FullArticle = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
|
@ -78,15 +91,12 @@ export const FullArticle = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const commentsRef: { current: HTMLDivElement } = { current: null }
|
const commentsRef: { current: HTMLDivElement } = { current: null }
|
||||||
|
|
||||||
const scrollToComments = () => {
|
const scrollToComments = () => {
|
||||||
window.scrollTo({
|
scrollTo(commentsRef.current)
|
||||||
top: commentsRef.current.offsetTop - 96,
|
|
||||||
left: 0,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams, changeSearchParam } = useRouter()
|
const { searchParams, changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.scrollToComments) {
|
if (props.scrollToComments) {
|
||||||
|
@ -105,9 +115,12 @@ export const FullArticle = (props: Props) => {
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (searchParams().commentId && isReactionsLoaded()) {
|
if (searchParams().commentId && isReactionsLoaded()) {
|
||||||
const commentElement = document.querySelector(`[id='comment_${searchParams().commentId}']`)
|
const commentElement = document.querySelector<HTMLElement>(
|
||||||
|
`[id='comment_${searchParams().commentId}']`
|
||||||
|
)
|
||||||
|
changeSearchParam({ commentId: null })
|
||||||
if (commentElement) {
|
if (commentElement) {
|
||||||
commentElement.scrollIntoView({ behavior: 'smooth' })
|
scrollTo(commentElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -35,11 +35,16 @@ export const Draft = (props: Props) => {
|
||||||
const handleDeleteLinkClick = async (e) => {
|
const handleDeleteLinkClick = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const isConfirmed = await showConfirm()
|
const isConfirmed = await showConfirm({
|
||||||
|
confirmBody: t('Are you sure you want to delete this draft?'),
|
||||||
|
confirmButtonLabel: t('Delete'),
|
||||||
|
confirmButtonVariant: 'danger',
|
||||||
|
declineButtonVariant: 'primary'
|
||||||
|
})
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
props.onDelete(props.shout)
|
props.onDelete(props.shout)
|
||||||
|
|
||||||
await showSnackbar({ type: 'success', body: t('Success') })
|
await showSnackbar({ body: t('Draft successfully deleted') })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import formattedTime from '../../utils/formatDateTime'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { MessageActionsPopup } from './MessageActionsPopup'
|
import { MessageActionsPopup } from './MessageActionsPopup'
|
||||||
import QuotedMessage from './QuotedMessage'
|
import QuotedMessage from './QuotedMessage'
|
||||||
import MD from '../Article/MD'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: MessageType
|
content: MessageType
|
||||||
|
|
|
@ -1,48 +1,22 @@
|
||||||
.confirmModal {
|
.confirmModal {
|
||||||
background: #fff;
|
|
||||||
min-height: 550px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@include media-breakpoint-up(md) {
|
.confirmModalTitle {
|
||||||
min-height: 710px;
|
@include font-size(2rem);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirmModalTitle {
|
|
||||||
font-size: 26px;
|
|
||||||
line-height: 32px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #141414;
|
color: var(--default-color);
|
||||||
text-align: left;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirmModalActions {
|
.confirmModalActions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 16px;
|
margin-top: 4rem;
|
||||||
}
|
gap: 2rem;
|
||||||
|
|
||||||
.confirmModalButton {
|
.confirmAction {
|
||||||
display: block;
|
flex: 1;
|
||||||
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%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirmModalButtonPrimary {
|
|
||||||
margin-right: 0;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgb(0 0 0 / 60%);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import { useConfirm } from '../../../context/confirm'
|
import { useConfirm } from '../../../context/confirm'
|
||||||
import styles from './ConfirmModal.module.scss'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Button } from '../../_shared/Button'
|
||||||
|
import styles from './ConfirmModal.module.scss'
|
||||||
|
|
||||||
export const ConfirmModal = () => {
|
export const ConfirmModal = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
@ -12,21 +12,26 @@ export const ConfirmModal = () => {
|
||||||
} = useConfirm()
|
} = useConfirm()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div class={styles.confirmModal}>
|
||||||
<h4 class={styles.confirmModalTitle}>
|
<h4 class={styles.confirmModalTitle}>
|
||||||
{confirmMessage().confirmBody ?? t('Are you sure you want to to proceed the action?')}
|
{confirmMessage().confirmBody ?? t('Are you sure you want to to proceed the action?')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div class={styles.confirmModalActions}>
|
<div class={styles.confirmModalActions}>
|
||||||
<button class={styles.confirmModalButton} onClick={() => resolveConfirm(false)}>
|
<Button
|
||||||
{confirmMessage().declineButtonLabel ?? t('Decline')}
|
onClick={() => resolveConfirm(false)}
|
||||||
</button>
|
value={confirmMessage().declineButtonLabel ?? t('Decline')}
|
||||||
<button
|
size="L"
|
||||||
class={clsx(styles.confirmModalButton, styles.confirmModalButtonPrimary)}
|
variant={confirmMessage().declineButtonVariant ?? 'secondary'}
|
||||||
|
class={styles.confirmAction}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
onClick={() => resolveConfirm(true)}
|
onClick={() => resolveConfirm(true)}
|
||||||
>
|
value={confirmMessage().confirmButtonLabel ?? t('Confirm')}
|
||||||
{confirmMessage().confirmButtonLabel ?? t('Confirm')}
|
size="L"
|
||||||
</button>
|
variant={confirmMessage().confirmButtonVariant ?? 'primary'}
|
||||||
|
class={styles.confirmAction}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,7 +67,7 @@ export const Header = (props: Props) => {
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const mainContent = document.querySelector('.main-content') as HTMLDivElement
|
const mainContent = document.querySelector<HTMLDivElement>('.main-content')
|
||||||
|
|
||||||
if (fixed() || modal() !== null) {
|
if (fixed() || modal() !== null) {
|
||||||
windowScrollTop = window.scrollY
|
windowScrollTop = window.scrollY
|
||||||
|
|
|
@ -71,8 +71,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
right: 3.6rem;
|
right: 1.6rem;
|
||||||
top: 12px;
|
top: 1.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, createMemo, createSignal, on, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { hideModal, useModalStore } from '../../../stores/ui'
|
import { hideModal, useModalStore } from '../../../stores/ui'
|
||||||
|
@ -8,7 +8,6 @@ import styles from './Modal.module.scss'
|
||||||
import { redirectPage } from '@nanostores/router'
|
import { redirectPage } from '@nanostores/router'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { resetSortedArticles } from '../../../stores/zine/articles'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
.EmptyMessage {
|
||||||
|
// TODO: check markup
|
||||||
|
color: var(--black-500);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 24px;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import styles from './EmptyMessage.module.scss'
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
|
||||||
|
export const EmptyMessage = () => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={clsx(styles.EmptyMessage)}>
|
||||||
|
<div class={styles.title}>{t('No notifications yet')}</div>
|
||||||
|
<div>{t("Write good articles, comment\nand it won't be so empty here")}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/NotificationsPanel/EmptyMessage/index.ts
Normal file
1
src/components/NotificationsPanel/EmptyMessage/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { EmptyMessage } from './EmptyMessage'
|
|
@ -20,6 +20,13 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--gray-100);
|
background-color: var(--gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
border-bottom: none !important;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.userpic {
|
.userpic {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { Icon } from '../_shared/Icon'
|
||||||
import { createEffect, For } from 'solid-js'
|
import { createEffect, For } from 'solid-js'
|
||||||
import { useNotifications } from '../../context/notifications'
|
import { useNotifications } from '../../context/notifications'
|
||||||
import { NotificationView } from './NotificationView'
|
import { NotificationView } from './NotificationView'
|
||||||
|
import { EmptyMessage } from './EmptyMessage'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
@ -30,8 +31,22 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
handler: () => handleHide()
|
handler: () => handleHide()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let windowScrollTop = 0
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
const mainContent = document.querySelector<HTMLDivElement>('.main-content')
|
||||||
|
|
||||||
|
if (props.isOpen) {
|
||||||
|
windowScrollTop = window.scrollY
|
||||||
|
mainContent.style.marginTop = `-${windowScrollTop}px`
|
||||||
|
}
|
||||||
|
|
||||||
document.body.classList.toggle('fixed', props.isOpen)
|
document.body.classList.toggle('fixed', props.isOpen)
|
||||||
|
|
||||||
|
if (!props.isOpen) {
|
||||||
|
mainContent.style.marginTop = ''
|
||||||
|
window.scrollTo(0, windowScrollTop)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useEscKeyDownHandler(handleHide)
|
useEscKeyDownHandler(handleHide)
|
||||||
|
@ -52,10 +67,7 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.title}>{t('Notifications')}</div>
|
<div class={styles.title}>{t('Notifications')}</div>
|
||||||
<For
|
<For each={sortedNotifications()} fallback={<EmptyMessage />}>
|
||||||
each={sortedNotifications()}
|
|
||||||
fallback={<div class={styles.emptyMessageContainer}>{t('No notifications, yet')}</div>}
|
|
||||||
>
|
|
||||||
{(notification) => (
|
{(notification) => (
|
||||||
<NotificationView
|
<NotificationView
|
||||||
notification={notification}
|
notification={notification}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { apiClient } from '../../../utils/apiClient'
|
||||||
import { Comment } from '../../Article/Comment'
|
import { Comment } from '../../Article/Comment'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { AuthorRatingControl } from '../../Author/AuthorRatingControl'
|
import { AuthorRatingControl } from '../../Author/AuthorRatingControl'
|
||||||
import { hideModal } from '../../../stores/ui'
|
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Loading } from '../../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
|
|
|
@ -31,6 +31,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
border: 3px solid var(--danger-color);
|
||||||
|
background: var(--background-color);
|
||||||
|
color: var(--danger-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--danger-color);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.inline {
|
&.inline {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
|
@ -2,10 +2,11 @@ import type { JSX } from 'solid-js'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './Button.module.scss'
|
import styles from './Button.module.scss'
|
||||||
|
|
||||||
|
export type ButtonVariant = 'primary' | 'secondary' | 'bordered' | 'inline' | 'light' | 'outline' | 'danger'
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string | JSX.Element
|
value: string | JSX.Element
|
||||||
size?: 'S' | 'M' | 'L'
|
size?: 'S' | 'M' | 'L'
|
||||||
variant?: 'primary' | 'secondary' | 'bordered' | 'inline' | 'light' | 'outline'
|
variant?: ButtonVariant
|
||||||
type?: 'submit' | 'button'
|
type?: 'submit' | 'button'
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|
|
@ -2,11 +2,14 @@ import { createContext, createSignal, useContext } from 'solid-js'
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
|
|
||||||
import { hideModal, showModal } from '../stores/ui'
|
import { hideModal, showModal } from '../stores/ui'
|
||||||
|
import { ButtonVariant } from '../components/_shared/Button/Button'
|
||||||
|
|
||||||
type ConfirmMessage = {
|
type ConfirmMessage = {
|
||||||
confirmBody?: string | JSX.Element
|
confirmBody?: string | JSX.Element
|
||||||
confirmButtonLabel?: string
|
confirmButtonLabel?: string
|
||||||
|
confirmButtonVariant?: ButtonVariant
|
||||||
declineButtonLabel?: string
|
declineButtonLabel?: string
|
||||||
|
declineButtonVariant?: ButtonVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfirmContextType = {
|
type ConfirmContextType = {
|
||||||
|
@ -15,7 +18,9 @@ type ConfirmContextType = {
|
||||||
showConfirm: (message?: {
|
showConfirm: (message?: {
|
||||||
confirmBody?: ConfirmMessage['confirmBody']
|
confirmBody?: ConfirmMessage['confirmBody']
|
||||||
confirmButtonLabel?: ConfirmMessage['confirmButtonLabel']
|
confirmButtonLabel?: ConfirmMessage['confirmButtonLabel']
|
||||||
|
confirmButtonVariant?: ConfirmMessage['confirmButtonVariant']
|
||||||
declineButtonLabel?: ConfirmMessage['declineButtonLabel']
|
declineButtonLabel?: ConfirmMessage['declineButtonLabel']
|
||||||
|
declineButtonVariant?: ConfirmMessage['declineButtonVariant']
|
||||||
}) => Promise<boolean>
|
}) => Promise<boolean>
|
||||||
resolveConfirm: (value: boolean) => void
|
resolveConfirm: (value: boolean) => void
|
||||||
}
|
}
|
||||||
|
@ -36,13 +41,17 @@ export const ConfirmProvider = (props: { children: JSX.Element }) => {
|
||||||
message: {
|
message: {
|
||||||
confirmBody?: ConfirmMessage['confirmBody']
|
confirmBody?: ConfirmMessage['confirmBody']
|
||||||
confirmButtonLabel?: ConfirmMessage['confirmButtonLabel']
|
confirmButtonLabel?: ConfirmMessage['confirmButtonLabel']
|
||||||
|
confirmButtonVariant?: ConfirmMessage['confirmButtonVariant']
|
||||||
declineButtonLabel?: ConfirmMessage['declineButtonLabel']
|
declineButtonLabel?: ConfirmMessage['declineButtonLabel']
|
||||||
|
declineButtonVariant?: ConfirmMessage['declineButtonVariant']
|
||||||
} = {}
|
} = {}
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const messageToShow = {
|
const messageToShow = {
|
||||||
confirmBody: message.confirmBody,
|
confirmBody: message.confirmBody,
|
||||||
confirmButtonLabel: message.confirmButtonLabel,
|
confirmButtonLabel: message.confirmButtonLabel,
|
||||||
declineButtonLabel: message.declineButtonLabel
|
confirmButtonVariant: message.confirmButtonVariant,
|
||||||
|
declineButtonLabel: message.declineButtonLabel,
|
||||||
|
declineButtonVariant: message.declineButtonVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfirmMessage(messageToShow)
|
setConfirmMessage(messageToShow)
|
||||||
|
|
|
@ -24,6 +24,7 @@ type NotificationsContextType = {
|
||||||
sortedNotifications: Accessor<ServerNotification[]>
|
sortedNotifications: Accessor<ServerNotification[]>
|
||||||
actions: {
|
actions: {
|
||||||
showNotificationsPanel: () => void
|
showNotificationsPanel: () => void
|
||||||
|
hideNotificationsPanel: () => void
|
||||||
markNotificationAsRead: (notification: ServerNotification) => Promise<void>
|
markNotificationAsRead: (notification: ServerNotification) => Promise<void>
|
||||||
setMessageHandler: (MessageHandler) => void
|
setMessageHandler: (MessageHandler) => void
|
||||||
}
|
}
|
||||||
|
@ -129,7 +130,16 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
setIsNotificationsPanelOpen(true)
|
setIsNotificationsPanelOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = { showNotificationsPanel, markNotificationAsRead, setMessageHandler }
|
const hideNotificationsPanel = () => {
|
||||||
|
setIsNotificationsPanelOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
setMessageHandler,
|
||||||
|
showNotificationsPanel,
|
||||||
|
hideNotificationsPanel,
|
||||||
|
markNotificationAsRead
|
||||||
|
}
|
||||||
|
|
||||||
const value: NotificationsContextType = {
|
const value: NotificationsContextType = {
|
||||||
notificationEntities,
|
notificationEntities,
|
||||||
|
|
|
@ -11,18 +11,9 @@ import { setPageLoadManagerPromise } from '../utils/pageLoadManager'
|
||||||
|
|
||||||
export const ArticlePage = (props: PageProps) => {
|
export const ArticlePage = (props: PageProps) => {
|
||||||
const shouts = props.article ? [props.article] : []
|
const shouts = props.article ? [props.article] : []
|
||||||
|
const { page } = useRouter()
|
||||||
|
|
||||||
const slug = createMemo(() => {
|
const slug = createMemo(() => page().params['slug'] as string)
|
||||||
const { page: getPage } = useRouter()
|
|
||||||
|
|
||||||
const page = getPage()
|
|
||||||
|
|
||||||
if (page.route !== 'article') {
|
|
||||||
throw new Error('ts guard')
|
|
||||||
}
|
|
||||||
|
|
||||||
return page.params.slug
|
|
||||||
})
|
|
||||||
|
|
||||||
const { articleEntities } = useArticlesStore({
|
const { articleEntities } = useArticlesStore({
|
||||||
shouts
|
shouts
|
||||||
|
|
Loading…
Reference in New Issue
Block a user