diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index a21d1a60..09c1881b 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -1,9 +1,11 @@ name: 'deploy' + on: push: branches: - main - - feature/sse-connect + - dev + - feature/email-templates jobs: test: @@ -26,24 +28,40 @@ jobs: update_mailgun_template: runs-on: ubuntu-latest name: Update templates on Mailgun + if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates' continue-on-error: true steps: - name: Checkout uses: actions/checkout@v2 - - name: update authorizer_password_reset - uses: jlepocher/mailgun-create-template-version-action@v1.3 + + - name: "Email confirmation template" + uses: gyto/mailgun-template-action@v2 with: - mailgun-host: 'api.eu.mailgun.net' + html-file: "./templates/authorizer_email_confirmation.html" 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 - uses: jlepocher/mailgun-create-template-version-action@v1.3 + mailgun-domain: "discours.io" + mailgun-template: "authorizer_email_confirmation" + + - name: "Password reset template" + uses: gyto/mailgun-template-action@v2 with: - mailgun-host: 'api.eu.mailgun.net' + html-file: "./templates/authorizer_password_reset.html" mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} - mailgun-domain-name: 'discours.io' - mailgun-template-name: 'authorizer_email_confirm' - html-file-path: './templates/authorizer_email_confirm.html' + 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" diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx index a8456166..24e885e7 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.tsx +++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx @@ -8,7 +8,6 @@ import { useMediaQuery } from '../../../context/mediaQuery' import { useSession } from '../../../context/session' import { Author, FollowingEntity } from '../../../graphql/schema/core.gen' import { router, useRouter } from '../../../stores/router' -import { resetSortedArticles } from '../../../stores/zine/articles' import { isCyrillic } from '../../../utils/cyrillic' import { translit } from '../../../utils/ru2en' import { Button } from '../../_shared/Button' diff --git a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx index 493bd64a..babd8f8b 100644 --- a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx +++ b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx @@ -1,6 +1,5 @@ import type { AuthModalSearchParams } from './types' -import { ApiResponse, ForgotPasswordResponse } from '@authorizerdev/authorizer-js' import { clsx } from 'clsx' 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'))) { setIsUserNotFound(true) } + if (data.message) setMessage(data.message) } catch (error) { console.error(error) } finally { diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/Nav/AuthModal/LoginForm.tsx index b4b54cda..c8c7bd5f 100644 --- a/src/components/Nav/AuthModal/LoginForm.tsx +++ b/src/components/Nav/AuthModal/LoginForm.tsx @@ -1,12 +1,11 @@ import type { AuthModalSearchParams } from './types' import { clsx } from 'clsx' -import { createEffect, createSignal, Show } from 'solid-js' +import { createSignal, Show } from 'solid-js' import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' import { useSnackbar } from '../../../context/snackbar' -import { ApiError } from '../../../graphql/error' import { useRouter } from '../../../stores/router' import { hideModal } from '../../../stores/ui' import { validateEmail } from '../../../utils/validateEmail' diff --git a/src/components/Nav/SearchModal/SearchModal.tsx b/src/components/Nav/SearchModal/SearchModal.tsx index 9fdb03df..64faaee7 100644 --- a/src/components/Nav/SearchModal/SearchModal.tsx +++ b/src/components/Nav/SearchModal/SearchModal.tsx @@ -55,6 +55,7 @@ export const SearchModal = () => { >( async () => { setIsLoading(true) + saveScrollPosition() const { hasMore, newShouts } = await loadShoutsSearch({ limit: FEED_PAGE_SIZE, text: inputValue(), diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx index 44891092..17ad2a3e 100644 --- a/src/components/Views/Author/Author.tsx +++ b/src/components/Views/Author/Author.tsx @@ -128,7 +128,7 @@ export const AuthorView = (props: Props) => { const data = await apiClient.getReactionsBy({ by: { comment: false, created_by: commenter.id }, }) - console.debug(`[components.Author] fetched ${data.length} comments`) + console.debug(`[components.Author] fetched comments`, data) setCommented(data) } diff --git a/src/components/Views/DraftsView/DraftsView.tsx b/src/components/Views/DraftsView/DraftsView.tsx index 1aed1a8e..16d977ec 100644 --- a/src/components/Views/DraftsView/DraftsView.tsx +++ b/src/components/Views/DraftsView/DraftsView.tsx @@ -22,8 +22,8 @@ export const DraftsView = () => { } } - createEffect(async () => { - if (isSessionLoaded()) await loadDrafts() + createEffect(() => { + if (isSessionLoaded()) loadDrafts() }) const { @@ -32,7 +32,9 @@ export const DraftsView = () => { const handleDraftDelete = async (shout: Shout) => { const result = deleteShout(shout.id) - if (result) await loadDrafts() + if (result) { + setDrafts((ddd) => ddd.filter((d) => d.id !== shout.id)) + } } const handleDraftPublish = (shout: Shout) => { diff --git a/src/components/Views/Edit.tsx b/src/components/Views/Edit.tsx index e3779df8..f7ca7f4a 100644 --- a/src/components/Views/Edit.tsx +++ b/src/components/Views/Edit.tsx @@ -21,6 +21,7 @@ import { Editor, Panel } from '../Editor' import { AudioUploader } from '../Editor/AudioUploader' import { AutoSaveNotice } from '../Editor/AutoSaveNotice' import { VideoUploader } from '../Editor/VideoUploader' +import { Modal } from '../Nav/Modal' import { TableOfContents } from '../TableOfContents' import { PublishSettings } from './PublishSettings' @@ -413,7 +414,10 @@ export const EditView = (props: Props) => { - + + + + ) } diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index 07c46e50..bfd76067 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -30,6 +30,7 @@ import { AuthorBadge } from '../../Author/AuthorBadge' import { AuthorLink } from '../../Author/AuthorLink' import { ArticleCard } from '../../Feed/ArticleCard' import { Sidebar } from '../../Feed/Sidebar' +import { Modal } from '../../Nav/Modal' import styles from './Feed.module.scss' import stylesBeside from '../../Feed/Beside.module.scss' @@ -439,7 +440,10 @@ export const FeedView = (props: Props) => { shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })} /> - + + + + ) } diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx index 2f03cc0f..ac1d3b2b 100644 --- a/src/components/Views/Home.tsx +++ b/src/components/Views/Home.tsx @@ -1,7 +1,6 @@ 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 { apiClient } from '../../graphql/client/core' import { Shout, Topic } from '../../graphql/schema/core.gen' diff --git a/src/components/Views/Inbox/Inbox.tsx b/src/components/Views/Inbox/Inbox.tsx index 593df862..4d8cd014 100644 --- a/src/components/Views/Inbox/Inbox.tsx +++ b/src/components/Views/Inbox/Inbox.tsx @@ -19,6 +19,7 @@ import DialogHeader from '../../Inbox/DialogHeader' import { Message } from '../../Inbox/Message' import MessagesFallback from '../../Inbox/MessagesFallback' import Search from '../../Inbox/Search' +import { Modal } from '../../Nav/Modal' import styles from './Inbox.module.scss' @@ -181,7 +182,9 @@ export const InboxView = (props: Props) => { return (
- + + + {/**/}
diff --git a/src/components/_shared/InviteMembers/InviteMembers.tsx b/src/components/_shared/InviteMembers/InviteMembers.tsx index a1563d0e..75431db0 100644 --- a/src/components/_shared/InviteMembers/InviteMembers.tsx +++ b/src/components/_shared/InviteMembers/InviteMembers.tsx @@ -8,7 +8,6 @@ import { Author } from '../../../graphql/schema/core.gen' import { hideModal } from '../../../stores/ui' import { useAuthorsStore } from '../../../stores/zine/authors' import { AuthorBadge } from '../../Author/AuthorBadge' -import { Modal } from '../../Nav/Modal' import { Button } from '../Button' import { DropdownSelect } from '../DropdownSelect' import { Loading } from '../Loading' diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 83992474..049a7e84 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -8,6 +8,7 @@ import { createStore, SetStoreFunction } from 'solid-js/store' import { apiClient } from '../graphql/client/core' import { Topic, TopicInput } from '../graphql/schema/core.gen' import { router, useRouter } from '../stores/router' +import { addArticles } from '../stores/zine/articles' import { slugify } from '../utils/slugify' import { useLocalize } from './localize' @@ -122,20 +123,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { return await apiClient.updateArticle({ - shoutId: formToUpdate.shoutId, - shoutInput: { - body: formToUpdate.body, + shout_id: formToUpdate.shoutId, + shout_input: { + ...formToUpdate, topics: formToUpdate.selectedTopics.map((topic) => topic2topicInput(topic)), // NOTE: first is main - // authors?: InputMaybe>> - // community?: InputMaybe - // mainTopic: topic2topicInput(formToUpdate.mainTopic), - slug: formToUpdate.slug, - subtitle: formToUpdate.subtitle, - title: formToUpdate.title, - lead: formToUpdate.lead, - description: formToUpdate.description, cover: formToUpdate.coverImageUrl, - media: formToUpdate.media, }, publish, }) @@ -204,13 +196,13 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } } - const publishShoutById = async (shoutId: number) => { + const publishShoutById = async (shout_id: number) => { try { - await apiClient.updateArticle({ - shoutId, + const newShout = await apiClient.updateArticle({ + shout_id, publish: true, }) - + addArticles([newShout]) openPage(router, 'feed') } catch (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 { await apiClient.deleteShout({ - shoutId, + shout_id, }) return true } catch { diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 04e8acf6..90402a4f 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -13,6 +13,7 @@ import type { QueryLoad_Shouts_SearchArgs, QueryLoad_Shouts_Random_TopArgs, Community, + MutationDelete_ShoutArgs, } from '../schema/core.gen' import { createGraphQLClient } from '../createGraphQLClient' @@ -149,22 +150,22 @@ export const apiClient = { return response.data.create_shout.shout }, updateArticle: async ({ - shoutId, - shoutInput, + shout_id, + shout_input, publish, }: { - shoutId: number - shoutInput?: ShoutInput + shout_id: number + shout_input?: ShoutInput publish: boolean }): Promise => { const response = await apiClient.private - .mutation(updateArticle, { shoutId, shoutInput, publish }) + .mutation(updateArticle, { shout_id, shout_input, publish }) .toPromise() console.debug('[graphql.client.core] updateArticle:', response.data) return response.data.update_shout.shout }, - deleteShout: async ({ shoutId }: { shoutId: number }): Promise => { - const response = await apiClient.private.mutation(deleteShout, { shout_id: shoutId }).toPromise() + deleteShout: async (params: MutationDelete_ShoutArgs): Promise => { + const response = await apiClient.private.mutation(deleteShout, params).toPromise() console.debug('[graphql.client.core] deleteShout:', response) }, getDrafts: async (): Promise => { diff --git a/src/graphql/mutation/core/article-delete.ts b/src/graphql/mutation/core/article-delete.ts index 9aa22fc1..6ac5578e 100644 --- a/src/graphql/mutation/core/article-delete.ts +++ b/src/graphql/mutation/core/article-delete.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - mutation DeleteShoutMutation($shoutId: Int!) { - delete_shout(shout_id: $shoutId) { + mutation DeleteShoutMutation($shout_id: Int!) { + delete_shout(shout_id: $shout_id) { error } } diff --git a/src/graphql/mutation/core/article-update.ts b/src/graphql/mutation/core/article-update.ts index ca455bc5..307a4b90 100644 --- a/src/graphql/mutation/core/article-update.ts +++ b/src/graphql/mutation/core/article-update.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - mutation UpdateShoutMutation($shoutId: Int!, $shoutInput: ShoutInput, $publish: Boolean) { - update_shout(shout_id: $shoutId, shout_input: $shoutInput, publish: $publish) { + mutation UpdateShoutMutation($shout_id: Int!, $shout_input: ShoutInput, $publish: Boolean) { + update_shout(shout_id: $shout_id, shout_input: $shout_input, publish: $publish) { error shout { id @@ -12,7 +12,10 @@ export default gql` lead description body - visibility + created_at + updated_at + published_at + featured_at } } } diff --git a/src/graphql/query/core/reactions-load-by.ts b/src/graphql/query/core/reactions-load-by.ts index 02aff493..c0700f07 100644 --- a/src/graphql/query/core/reactions-load-by.ts +++ b/src/graphql/query/core/reactions-load-by.ts @@ -6,7 +6,6 @@ export default gql` id kind body - range reply_to shout { id diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index ed15fe97..9b41fc69 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -64,12 +64,14 @@ const topCommentedArticles = createLazyMemo(() => { }) // eslint-disable-next-line sonarjs/cognitive-complexity -const addArticles = (...args: Shout[][]) => { +export const addArticles = (...args: Shout[][]) => { const allArticles = args.flatMap((articles) => articles || []) const newArticleEntities = allArticles.reduce( (acc, article) => { - acc[article.slug] = article + if (!acc[article.slug]) { + acc[article.slug] = article + } return acc }, {} as { [articleSLug: string]: Shout },