commit
ab48f173b3
|
@ -1,9 +1,11 @@
|
||||||
name: 'deploy'
|
name: 'deploy'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- feature/sse-connect
|
- dev
|
||||||
|
- feature/email-templates
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@ -26,24 +28,40 @@ jobs:
|
||||||
update_mailgun_template:
|
update_mailgun_template:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Update templates on Mailgun
|
name: Update templates on Mailgun
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: update authorizer_password_reset
|
|
||||||
uses: jlepocher/mailgun-create-template-version-action@v1.3
|
|
||||||
with:
|
|
||||||
mailgun-host: 'api.eu.mailgun.net'
|
|
||||||
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
|
||||||
mailgun-domain-name: 'discours.io'
|
|
||||||
mailgun-template-name: 'authorizer_password_reset'
|
|
||||||
html-file-path: './templates/authorizer_password_reset.html'
|
|
||||||
|
|
||||||
- name: update authorizer_password_reset
|
- name: "Email confirmation template"
|
||||||
uses: jlepocher/mailgun-create-template-version-action@v1.3
|
uses: gyto/mailgun-template-action@v2
|
||||||
with:
|
with:
|
||||||
mailgun-host: 'api.eu.mailgun.net'
|
html-file: "./templates/authorizer_email_confirmation.html"
|
||||||
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
||||||
mailgun-domain-name: 'discours.io'
|
mailgun-domain: "discours.io"
|
||||||
mailgun-template-name: 'authorizer_email_confirm'
|
mailgun-template: "authorizer_email_confirmation"
|
||||||
html-file-path: './templates/authorizer_email_confirm.html'
|
|
||||||
|
- name: "Password reset template"
|
||||||
|
uses: gyto/mailgun-template-action@v2
|
||||||
|
with:
|
||||||
|
html-file: "./templates/authorizer_password_reset.html"
|
||||||
|
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
||||||
|
mailgun-domain: "discours.io"
|
||||||
|
mailgun-template: "authorizer_password_reset"
|
||||||
|
|
||||||
|
- name: "First publication notification"
|
||||||
|
uses: gyto/mailgun-template-action@v2
|
||||||
|
with:
|
||||||
|
html-file: "./templates/first_publication_notification.html"
|
||||||
|
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
||||||
|
mailgun-domain: "discours.io"
|
||||||
|
mailgun-template: "first_publication_notification"
|
||||||
|
|
||||||
|
- name: "New comment notification template"
|
||||||
|
uses: gyto/mailgun-template-action@v2
|
||||||
|
with:
|
||||||
|
html-file: "./templates/new_comment_notification.html"
|
||||||
|
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
|
||||||
|
mailgun-domain: "discours.io"
|
||||||
|
mailgun-template: "new_comment_notification"
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { useMediaQuery } from '../../../context/mediaQuery'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { resetSortedArticles } from '../../../stores/zine/articles'
|
|
||||||
import { isCyrillic } from '../../../utils/cyrillic'
|
import { isCyrillic } from '../../../utils/cyrillic'
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { ApiResponse, ForgotPasswordResponse } from '@authorizerdev/authorizer-js'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
import { createSignal, JSX, Show } from 'solid-js'
|
||||||
|
|
||||||
|
@ -67,6 +66,7 @@ export const ForgotPasswordForm = () => {
|
||||||
if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
|
if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
|
||||||
setIsUserNotFound(true)
|
setIsUserNotFound(true)
|
||||||
}
|
}
|
||||||
|
if (data.message) setMessage(data.message)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createSignal, Show } from 'solid-js'
|
import { createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { useSnackbar } from '../../../context/snackbar'
|
import { useSnackbar } from '../../../context/snackbar'
|
||||||
import { ApiError } from '../../../graphql/error'
|
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import { hideModal } from '../../../stores/ui'
|
||||||
import { validateEmail } from '../../../utils/validateEmail'
|
import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
|
@ -55,6 +55,7 @@ export const SearchModal = () => {
|
||||||
>(
|
>(
|
||||||
async () => {
|
async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
saveScrollPosition()
|
||||||
const { hasMore, newShouts } = await loadShoutsSearch({
|
const { hasMore, newShouts } = await loadShoutsSearch({
|
||||||
limit: FEED_PAGE_SIZE,
|
limit: FEED_PAGE_SIZE,
|
||||||
text: inputValue(),
|
text: inputValue(),
|
||||||
|
|
|
@ -128,7 +128,7 @@ export const AuthorView = (props: Props) => {
|
||||||
const data = await apiClient.getReactionsBy({
|
const data = await apiClient.getReactionsBy({
|
||||||
by: { comment: false, created_by: commenter.id },
|
by: { comment: false, created_by: commenter.id },
|
||||||
})
|
})
|
||||||
console.debug(`[components.Author] fetched ${data.length} comments`)
|
console.debug(`[components.Author] fetched comments`, data)
|
||||||
setCommented(data)
|
setCommented(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ export const DraftsView = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(async () => {
|
createEffect(() => {
|
||||||
if (isSessionLoaded()) await loadDrafts()
|
if (isSessionLoaded()) loadDrafts()
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -32,7 +32,9 @@ export const DraftsView = () => {
|
||||||
|
|
||||||
const handleDraftDelete = async (shout: Shout) => {
|
const handleDraftDelete = async (shout: Shout) => {
|
||||||
const result = deleteShout(shout.id)
|
const result = deleteShout(shout.id)
|
||||||
if (result) await loadDrafts()
|
if (result) {
|
||||||
|
setDrafts((ddd) => ddd.filter((d) => d.id !== shout.id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDraftPublish = (shout: Shout) => {
|
const handleDraftPublish = (shout: Shout) => {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { Editor, Panel } from '../Editor'
|
||||||
import { AudioUploader } from '../Editor/AudioUploader'
|
import { AudioUploader } from '../Editor/AudioUploader'
|
||||||
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
|
||||||
import { VideoUploader } from '../Editor/VideoUploader'
|
import { VideoUploader } from '../Editor/VideoUploader'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
import { TableOfContents } from '../TableOfContents'
|
import { TableOfContents } from '../TableOfContents'
|
||||||
|
|
||||||
import { PublishSettings } from './PublishSettings'
|
import { PublishSettings } from './PublishSettings'
|
||||||
|
@ -413,7 +414,10 @@ export const EditView = (props: Props) => {
|
||||||
<PublishSettings shoutId={props.shout.id} form={form} />
|
<PublishSettings shoutId={props.shout.id} form={form} />
|
||||||
</Show>
|
</Show>
|
||||||
<Panel shoutId={props.shout.id} />
|
<Panel shoutId={props.shout.id} />
|
||||||
|
|
||||||
|
<Modal variant="medium" name="inviteCoauthors">
|
||||||
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { AuthorBadge } from '../../Author/AuthorBadge'
|
||||||
import { AuthorLink } from '../../Author/AuthorLink'
|
import { AuthorLink } from '../../Author/AuthorLink'
|
||||||
import { ArticleCard } from '../../Feed/ArticleCard'
|
import { ArticleCard } from '../../Feed/ArticleCard'
|
||||||
import { Sidebar } from '../../Feed/Sidebar'
|
import { Sidebar } from '../../Feed/Sidebar'
|
||||||
|
import { Modal } from '../../Nav/Modal'
|
||||||
|
|
||||||
import styles from './Feed.module.scss'
|
import styles from './Feed.module.scss'
|
||||||
import stylesBeside from '../../Feed/Beside.module.scss'
|
import stylesBeside from '../../Feed/Beside.module.scss'
|
||||||
|
@ -439,7 +440,10 @@ export const FeedView = (props: Props) => {
|
||||||
shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })}
|
shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<InviteMembers title={t('Invite experts')} variant={'coauthors'} />
|
|
||||||
|
<Modal variant="medium" name="inviteCoauthors">
|
||||||
|
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { batch, createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
import { batch, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../context/following'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { apiClient } from '../../graphql/client/core'
|
import { apiClient } from '../../graphql/client/core'
|
||||||
import { Shout, Topic } from '../../graphql/schema/core.gen'
|
import { Shout, Topic } from '../../graphql/schema/core.gen'
|
||||||
|
|
|
@ -19,6 +19,7 @@ import DialogHeader from '../../Inbox/DialogHeader'
|
||||||
import { Message } from '../../Inbox/Message'
|
import { Message } from '../../Inbox/Message'
|
||||||
import MessagesFallback from '../../Inbox/MessagesFallback'
|
import MessagesFallback from '../../Inbox/MessagesFallback'
|
||||||
import Search from '../../Inbox/Search'
|
import Search from '../../Inbox/Search'
|
||||||
|
import { Modal } from '../../Nav/Modal'
|
||||||
|
|
||||||
import styles from './Inbox.module.scss'
|
import styles from './Inbox.module.scss'
|
||||||
|
|
||||||
|
@ -181,7 +182,9 @@ export const InboxView = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={clsx('container', styles.Inbox)}>
|
<div class={clsx('container', styles.Inbox)}>
|
||||||
|
<Modal variant="medium" name="inviteMembers">
|
||||||
<InviteMembers title={t('Create Chat')} variant={'recipients'} />
|
<InviteMembers title={t('Create Chat')} variant={'recipients'} />
|
||||||
|
</Modal>
|
||||||
{/*<CreateModalContent users={recipients()} />*/}
|
{/*<CreateModalContent users={recipients()} />*/}
|
||||||
<div class={clsx('row', styles.row)}>
|
<div class={clsx('row', styles.row)}>
|
||||||
<div class={clsx(styles.chatList, 'col-md-8')}>
|
<div class={clsx(styles.chatList, 'col-md-8')}>
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { Author } from '../../../graphql/schema/core.gen'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import { hideModal } from '../../../stores/ui'
|
||||||
import { useAuthorsStore } from '../../../stores/zine/authors'
|
import { useAuthorsStore } from '../../../stores/zine/authors'
|
||||||
import { AuthorBadge } from '../../Author/AuthorBadge'
|
import { AuthorBadge } from '../../Author/AuthorBadge'
|
||||||
import { Modal } from '../../Nav/Modal'
|
|
||||||
import { Button } from '../Button'
|
import { Button } from '../Button'
|
||||||
import { DropdownSelect } from '../DropdownSelect'
|
import { DropdownSelect } from '../DropdownSelect'
|
||||||
import { Loading } from '../Loading'
|
import { Loading } from '../Loading'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { createStore, SetStoreFunction } from 'solid-js/store'
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { Topic, TopicInput } from '../graphql/schema/core.gen'
|
import { Topic, TopicInput } from '../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../stores/router'
|
import { router, useRouter } from '../stores/router'
|
||||||
|
import { addArticles } from '../stores/zine/articles'
|
||||||
import { slugify } from '../utils/slugify'
|
import { slugify } from '../utils/slugify'
|
||||||
|
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
|
@ -122,20 +123,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
|
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
|
||||||
return await apiClient.updateArticle({
|
return await apiClient.updateArticle({
|
||||||
shoutId: formToUpdate.shoutId,
|
shout_id: formToUpdate.shoutId,
|
||||||
shoutInput: {
|
shout_input: {
|
||||||
body: formToUpdate.body,
|
...formToUpdate,
|
||||||
topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)), // NOTE: first is main
|
topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)), // NOTE: first is main
|
||||||
// authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
|
||||||
// community?: InputMaybe<Scalars['Int']>
|
|
||||||
// mainTopic: topic2topicInput(formToUpdate.mainTopic),
|
|
||||||
slug: formToUpdate.slug,
|
|
||||||
subtitle: formToUpdate.subtitle,
|
|
||||||
title: formToUpdate.title,
|
|
||||||
lead: formToUpdate.lead,
|
|
||||||
description: formToUpdate.description,
|
|
||||||
cover: formToUpdate.coverImageUrl,
|
cover: formToUpdate.coverImageUrl,
|
||||||
media: formToUpdate.media,
|
|
||||||
},
|
},
|
||||||
publish,
|
publish,
|
||||||
})
|
})
|
||||||
|
@ -204,13 +196,13 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishShoutById = async (shoutId: number) => {
|
const publishShoutById = async (shout_id: number) => {
|
||||||
try {
|
try {
|
||||||
await apiClient.updateArticle({
|
const newShout = await apiClient.updateArticle({
|
||||||
shoutId,
|
shout_id,
|
||||||
publish: true,
|
publish: true,
|
||||||
})
|
})
|
||||||
|
addArticles([newShout])
|
||||||
openPage(router, 'feed')
|
openPage(router, 'feed')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[publishShoutById]', error)
|
console.error('[publishShoutById]', error)
|
||||||
|
@ -218,10 +210,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteShout = async (shoutId: number) => {
|
const deleteShout = async (shout_id: number) => {
|
||||||
try {
|
try {
|
||||||
await apiClient.deleteShout({
|
await apiClient.deleteShout({
|
||||||
shoutId,
|
shout_id,
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type {
|
||||||
QueryLoad_Shouts_SearchArgs,
|
QueryLoad_Shouts_SearchArgs,
|
||||||
QueryLoad_Shouts_Random_TopArgs,
|
QueryLoad_Shouts_Random_TopArgs,
|
||||||
Community,
|
Community,
|
||||||
|
MutationDelete_ShoutArgs,
|
||||||
} from '../schema/core.gen'
|
} from '../schema/core.gen'
|
||||||
|
|
||||||
import { createGraphQLClient } from '../createGraphQLClient'
|
import { createGraphQLClient } from '../createGraphQLClient'
|
||||||
|
@ -149,22 +150,22 @@ export const apiClient = {
|
||||||
return response.data.create_shout.shout
|
return response.data.create_shout.shout
|
||||||
},
|
},
|
||||||
updateArticle: async ({
|
updateArticle: async ({
|
||||||
shoutId,
|
shout_id,
|
||||||
shoutInput,
|
shout_input,
|
||||||
publish,
|
publish,
|
||||||
}: {
|
}: {
|
||||||
shoutId: number
|
shout_id: number
|
||||||
shoutInput?: ShoutInput
|
shout_input?: ShoutInput
|
||||||
publish: boolean
|
publish: boolean
|
||||||
}): Promise<Shout> => {
|
}): Promise<Shout> => {
|
||||||
const response = await apiClient.private
|
const response = await apiClient.private
|
||||||
.mutation(updateArticle, { shoutId, shoutInput, publish })
|
.mutation(updateArticle, { shout_id, shout_input, publish })
|
||||||
.toPromise()
|
.toPromise()
|
||||||
console.debug('[graphql.client.core] updateArticle:', response.data)
|
console.debug('[graphql.client.core] updateArticle:', response.data)
|
||||||
return response.data.update_shout.shout
|
return response.data.update_shout.shout
|
||||||
},
|
},
|
||||||
deleteShout: async ({ shoutId }: { shoutId: number }): Promise<void> => {
|
deleteShout: async (params: MutationDelete_ShoutArgs): Promise<void> => {
|
||||||
const response = await apiClient.private.mutation(deleteShout, { shout_id: shoutId }).toPromise()
|
const response = await apiClient.private.mutation(deleteShout, params).toPromise()
|
||||||
console.debug('[graphql.client.core] deleteShout:', response)
|
console.debug('[graphql.client.core] deleteShout:', response)
|
||||||
},
|
},
|
||||||
getDrafts: async (): Promise<Shout[]> => {
|
getDrafts: async (): Promise<Shout[]> => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
mutation DeleteShoutMutation($shoutId: Int!) {
|
mutation DeleteShoutMutation($shout_id: Int!) {
|
||||||
delete_shout(shout_id: $shoutId) {
|
delete_shout(shout_id: $shout_id) {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
mutation UpdateShoutMutation($shoutId: Int!, $shoutInput: ShoutInput, $publish: Boolean) {
|
mutation UpdateShoutMutation($shout_id: Int!, $shout_input: ShoutInput, $publish: Boolean) {
|
||||||
update_shout(shout_id: $shoutId, shout_input: $shoutInput, publish: $publish) {
|
update_shout(shout_id: $shout_id, shout_input: $shout_input, publish: $publish) {
|
||||||
error
|
error
|
||||||
shout {
|
shout {
|
||||||
id
|
id
|
||||||
|
@ -12,7 +12,10 @@ export default gql`
|
||||||
lead
|
lead
|
||||||
description
|
description
|
||||||
body
|
body
|
||||||
visibility
|
created_at
|
||||||
|
updated_at
|
||||||
|
published_at
|
||||||
|
featured_at
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ export default gql`
|
||||||
id
|
id
|
||||||
kind
|
kind
|
||||||
body
|
body
|
||||||
range
|
|
||||||
reply_to
|
reply_to
|
||||||
shout {
|
shout {
|
||||||
id
|
id
|
||||||
|
|
|
@ -64,12 +64,14 @@ const topCommentedArticles = createLazyMemo(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const addArticles = (...args: Shout[][]) => {
|
export const addArticles = (...args: Shout[][]) => {
|
||||||
const allArticles = args.flatMap((articles) => articles || [])
|
const allArticles = args.flatMap((articles) => articles || [])
|
||||||
|
|
||||||
const newArticleEntities = allArticles.reduce(
|
const newArticleEntities = allArticles.reduce(
|
||||||
(acc, article) => {
|
(acc, article) => {
|
||||||
|
if (!acc[article.slug]) {
|
||||||
acc[article.slug] = article
|
acc[article.slug] = article
|
||||||
|
}
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
{} as { [articleSLug: string]: Shout },
|
{} as { [articleSLug: string]: Shout },
|
||||||
|
|
Loading…
Reference in New Issue
Block a user