Merge branch 'hotfix/editor-permission' into feature/rating

This commit is contained in:
Untone 2024-05-04 14:52:24 +03:00
commit df90953138
8 changed files with 83 additions and 70 deletions

View File

@ -75,7 +75,7 @@ export const FullArticle = (props: Props) => {
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false) const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false) const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const { t, formatDate, lang } = useLocalize() const { t, formatDate, lang } = useLocalize()
const { author, session, isAuthenticated, requireAuthentication } = useSession() const { author, session, requireAuthentication } = useSession()
const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000))) const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
@ -576,7 +576,7 @@ export const FullArticle = (props: Props) => {
/> />
</div> </div>
<Show when={isAuthenticated() && !canEdit()}> <Show when={author()?.id && !canEdit()}>
<div class={styles.help}> <div class={styles.help}>
<button class="button">{t('Cooperate')}</button> <button class="button">{t('Cooperate')}</button>
</div> </div>

View File

@ -12,7 +12,7 @@ type Props = {
} }
export const AuthGuard = (props: Props) => { export const AuthGuard = (props: Props) => {
const { isAuthenticated, isSessionLoaded } = useSession() const { author, isSessionLoaded } = useSession()
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>() const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
createEffect(() => { createEffect(() => {
@ -20,7 +20,7 @@ export const AuthGuard = (props: Props) => {
return return
} }
if (isSessionLoaded()) { if (isSessionLoaded()) {
if (isAuthenticated()) { if (author()?.id) {
hideModal() hideModal()
} else { } else {
changeSearchParams( changeSearchParams(
@ -37,5 +37,5 @@ export const AuthGuard = (props: Props) => {
} }
}) })
return <Show when={(isSessionLoaded() && isAuthenticated()) || props.disabled}>{props.children}</Show> return <Show when={(isSessionLoaded() && author()?.id) || props.disabled}>{props.children}</Show>
} }

View File

@ -23,8 +23,16 @@ type Props = {
export const Panel = (props: Props) => { export const Panel = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const { isEditorPanelVisible, wordCounter, editorRef, form, toggleEditorPanel, saveShout, publishShout } = const {
useEditorContext() isEditorPanelVisible,
wordCounter,
editorRef,
form,
toggleEditorPanel,
saveShout,
saveDraft,
publishShout,
} = useEditorContext()
const containerRef: { current: HTMLElement } = { current: null } const containerRef: { current: HTMLElement } = { current: null }
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false) const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
@ -43,7 +51,12 @@ export const Panel = (props: Props) => {
}) })
const handleSaveClick = () => { const handleSaveClick = () => {
saveShout(form) const hasTopics = form.selectedTopics?.length > 0
if (hasTopics) {
saveShout(form)
} else {
saveDraft(form)
}
} }
const html = useEditorHTML(() => editorRef.current()) const html = useEditorHTML(() => editorRef.current())

View File

@ -32,14 +32,14 @@ const MD_WIDTH_BREAKPOINT = 992
export const HeaderAuth = (props: Props) => { export const HeaderAuth = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const { page } = useRouter() const { page } = useRouter()
const { session, author, isAuthenticated, isSessionLoaded } = useSession() const { session, author, isSessionLoaded } = useSession()
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications() const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext() const { form, toggleEditorPanel, saveShout, saveDraft, publishShout } = useEditorContext()
const handleBellIconClick = (event: Event) => { const handleBellIconClick = (event: Event) => {
event.preventDefault() event.preventDefault()
if (!isAuthenticated()) { if (!author()?.id) {
showModal('auth') showModal('auth')
return return
} }
@ -48,20 +48,22 @@ export const HeaderAuth = (props: Props) => {
} }
const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings') const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings')
const isNotificationsVisible = createMemo(() => isAuthenticated() && !isEditorPage()) const isNotificationsVisible = createMemo(() => author()?.id && !isEditorPage())
const isSaveButtonVisible = createMemo(() => isAuthenticated() && isEditorPage()) const isSaveButtonVisible = createMemo(() => author()?.id && isEditorPage())
const isCreatePostButtonVisible = createMemo(() => !isEditorPage()) const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
const isAuthenticatedControlsVisible = createMemo( const isAuthenticatedControlsVisible = createMemo(() => author()?.id && session()?.user?.email_verified)
() => isAuthenticated() && session()?.user?.email_verified,
)
const handleBurgerButtonClick = () => { const handleBurgerButtonClick = () => {
toggleEditorPanel() toggleEditorPanel()
} }
// FIXME: remove the code if not needed here const handleSaveClick = () => {
const _handleSaveButtonClick = () => { const hasTopics = form.selectedTopics?.length > 0
saveShout(form) if (hasTopics) {
saveShout(form)
} else {
saveDraft(form)
}
} }
const [width, setWidth] = createSignal(0) const [width, setWidth] = createSignal(0)
@ -107,7 +109,7 @@ export const HeaderAuth = (props: Props) => {
<Show when={isSessionLoaded()} keyed={true}> <Show when={isSessionLoaded()} keyed={true}>
<div class={clsx('col-auto col-lg-7', styles.usernav)}> <div class={clsx('col-auto col-lg-7', styles.usernav)}>
<div class={styles.userControl}> <div class={styles.userControl}>
<Show when={isCreatePostButtonVisible() && isAuthenticated()}> <Show when={isCreatePostButtonVisible() && author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
@ -215,7 +217,7 @@ export const HeaderAuth = (props: Props) => {
</div> </div>
</Show> </Show>
<Show when={isCreatePostButtonVisible() && !isAuthenticated()}> <Show when={isCreatePostButtonVisible() && !author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
@ -228,7 +230,7 @@ export const HeaderAuth = (props: Props) => {
<Show <Show
when={isAuthenticatedControlsVisible()} when={isAuthenticatedControlsVisible()}
fallback={ fallback={
<Show when={!isAuthenticated()}> <Show when={!author()?.id}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}>
<a href="?m=auth&mode=login"> <a href="?m=auth&mode=login">
<span class={styles.textLabel}>{t('Enter')}</span> <span class={styles.textLabel}>{t('Enter')}</span>
@ -239,20 +241,31 @@ export const HeaderAuth = (props: Props) => {
</Show> </Show>
} }
> >
<Show when={!isSaveButtonVisible()}> <Show
<div class={clsx(styles.userControlItem, styles.userControlItemInbox)}> when={isSaveButtonVisible()}
<a href={getPagePath(router, 'inbox')}> fallback={
<div classList={{ entered: page().path === '/inbox' }}> <div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
<Icon name="inbox-white" class={styles.icon} /> <a href={getPagePath(router, 'inbox')}>
<Icon name="inbox-white-hover" class={clsx(styles.icon, styles.iconHover)} /> <div classList={{ entered: page().path === '/inbox' }}>
</div> <Icon name="inbox-white" class={styles.icon} />
</a> <Icon name="inbox-white-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</a>
</div>
}
>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
<button onClick={handleSaveClick}>
<span class={styles.textLabel}>{t('Save')}</span>
<Icon name="save" class={styles.icon} />
<Icon name="save" class={clsx(styles.icon, styles.iconHover)} />
</button>
</div> </div>
</Show> </Show>
</Show> </Show>
</div> </div>
<Show when={isAuthenticated()}> <Show when={author()?.id}>
<ProfilePopup <ProfilePopup
onVisibilityChange={(isVisible) => { onVisibilityChange={(isVisible) => {
props.setIsProfilePopupVisible(isVisible) props.setIsProfilePopupVisible(isVisible)

View File

@ -46,7 +46,7 @@ const isEarlier = (date: Date) => {
export const NotificationsPanel = (props: Props) => { export const NotificationsPanel = (props: Props) => {
const [isLoading, setIsLoading] = createSignal(false) const [isLoading, setIsLoading] = createSignal(false)
const { isAuthenticated } = useSession() const { author } = useSession()
const { t } = useLocalize() const { t } = useLocalize()
const { const {
after, after,
@ -150,16 +150,13 @@ export const NotificationsPanel = (props: Props) => {
}) })
createEffect( createEffect(
on( on(author, async (a) => {
() => isAuthenticated(), if (a?.id) {
async () => { setIsLoading(true)
if (isAuthenticated()) { await loadNextPage()
setIsLoading(true) setIsLoading(false)
await loadNextPage() }
setIsLoading(false) }),
}
},
),
) )
return ( return (

View File

@ -68,13 +68,14 @@ export const EditView = (props: Props) => {
} = useEditorContext() } = useEditorContext()
const shoutTopics = props.shout.topics || [] const shoutTopics = props.shout.topics || []
// TODO: проверить сохранение черновика в local storage (не работает) const draft = getDraftFromLocalStorage(props.shout.id)
const draft = props.shout || getDraftFromLocalStorage(props.shout.id)
if (draft) { if (draft) {
// console.debug('draft: ', draft) const draftForm = Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }
setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }) setForm(draftForm)
console.debug('draft from localstorage: ', draftForm)
} else { } else {
setForm({ const draftForm = {
slug: props.shout.slug, slug: props.shout.slug,
shoutId: props.shout.id, shoutId: props.shout.id,
title: props.shout.title, title: props.shout.title,
@ -87,7 +88,9 @@ export const EditView = (props: Props) => {
coverImageUrl: props.shout.cover, coverImageUrl: props.shout.cover,
media: props.shout.media, media: props.shout.media,
layout: props.shout.layout, layout: props.shout.layout,
}) }
setForm(draftForm)
console.debug('draft from props data: ', draftForm)
} }
const subtitleInput: { current: HTMLTextAreaElement } = { current: null } const subtitleInput: { current: HTMLTextAreaElement } = { current: null }
@ -110,9 +113,6 @@ export const EditView = (props: Props) => {
onCleanup(() => { onCleanup(() => {
window.removeEventListener('scroll', handleScroll) window.removeEventListener('scroll', handleScroll)
}) })
})
onMount(() => {
// eslint-disable-next-line unicorn/consistent-function-scoping // eslint-disable-next-line unicorn/consistent-function-scoping
const handleBeforeUnload = (event) => { const handleBeforeUnload = (event) => {
if (!deepEqual(prevForm, form)) { if (!deepEqual(prevForm, form)) {
@ -188,17 +188,12 @@ export const EditView = (props: Props) => {
const hasChanges = !deepEqual(form, prevForm) const hasChanges = !deepEqual(form, prevForm)
const hasTopic = Boolean(form.mainTopic) const hasTopic = Boolean(form.mainTopic)
if (hasChanges || hasTopic) { if (hasChanges || hasTopic) {
console.debug('[EditView.autoSave] shout has topic') console.debug('saving draft', form)
setSaving(true) setSaving(true)
if (props.shout?.published_at) { saveDraftToLocalStorage(form)
saveDraftToLocalStorage(form) await saveDraft(form)
} else {
await saveDraft(form)
}
setPrevForm(clone(form)) setPrevForm(clone(form))
setTimeout(() => { setTimeout(() => setSaving(false), AUTO_SAVE_DELAY)
setSaving(false)
}, AUTO_SAVE_DELAY)
} }
} }

View File

@ -40,11 +40,11 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0) const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore<Record<string, NotificationGroup>>({}) const [notificationEntities, setNotificationEntities] = createStore<Record<string, NotificationGroup>>({})
const { isAuthenticated } = useSession() const { author } = useSession()
const { addHandler } = useConnect() const { addHandler } = useConnect()
const loadNotificationsGrouped = async (options: { after: number; limit?: number; offset?: number }) => { const loadNotificationsGrouped = async (options: { after: number; limit?: number; offset?: number }) => {
if (isAuthenticated() && notifierClient?.private) { if (author()?.id && notifierClient?.private) {
const notificationsResult = await notifierClient.getNotifications(options) const notificationsResult = await notifierClient.getNotifications(options)
const groups = notificationsResult?.notifications || [] const groups = notificationsResult?.notifications || []
const total = notificationsResult?.total || 0 const total = notificationsResult?.total || 0
@ -74,7 +74,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
onMount(() => { onMount(() => {
addHandler((data: SSEMessage) => { addHandler((data: SSEMessage) => {
if (data.entity === 'reaction' && isAuthenticated()) { if (data.entity === 'reaction' && author()?.id) {
console.info('[context.notifications] event', data) console.info('[context.notifications] event', data)
loadNotificationsGrouped({ after: after(), limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) }) loadNotificationsGrouped({ after: after(), limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
} }
@ -91,14 +91,14 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
} }
const markSeenAll = async () => { const markSeenAll = async () => {
if (isAuthenticated() && notifierClient.private) { if (author()?.id && notifierClient.private) {
await notifierClient.markSeenAfter({ after: after() }) await notifierClient.markSeenAfter({ after: after() })
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() }) await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
} }
} }
const markSeen = async (notification_id: number) => { const markSeen = async (notification_id: number) => {
if (isAuthenticated() && notifierClient.private) { if (author()?.id && notifierClient.private) {
await notifierClient.markSeen(notification_id) await notifierClient.markSeen(notification_id)
await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() }) await loadNotificationsGrouped({ after: after(), limit: loadedNotificationsCount() })
} }

View File

@ -48,7 +48,6 @@ export type SessionContextType = {
author: Resource<Author | null> author: Resource<Author | null>
authError: Accessor<string> authError: Accessor<string>
isSessionLoaded: Accessor<boolean> isSessionLoaded: Accessor<boolean>
isAuthenticated: Accessor<boolean>
loadSession: () => AuthToken | Promise<AuthToken> loadSession: () => AuthToken | Promise<AuthToken>
setSession: (token: AuthToken | null) => void // setSession setSession: (token: AuthToken | null) => void // setSession
loadAuthor: (info?: unknown) => Author | Promise<Author> loadAuthor: (info?: unknown) => Author | Promise<Author>
@ -375,9 +374,6 @@ export const SessionProvider = (props: {
console.warn(error) console.warn(error)
} }
} }
const isAuthenticated = createMemo(() => Boolean(author()))
const actions = { const actions = {
loadSession, loadSession,
requireAuthentication, requireAuthentication,
@ -402,7 +398,6 @@ export const SessionProvider = (props: {
isSessionLoaded, isSessionLoaded,
author, author,
...actions, ...actions,
isAuthenticated,
resendVerifyEmail, resendVerifyEmail,
} }