diff --git a/package-lock.json b/package-lock.json
index e8a84240..5c76496f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"form-data": "4.0.0",
+ "idb": "8.0.0",
"mailgun.js": "10.1.0"
},
"devDependencies": {
@@ -7231,6 +7232,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/idb": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz",
+ "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
diff --git a/package.json b/package.json
index 9a277dbc..5e79d11f 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
},
"dependencies": {
"form-data": "4.0.0",
+ "idb": "8.0.0",
"mailgun.js": "10.1.0"
},
"devDependencies": {
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 7ad43757..726dd596 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -14,6 +14,7 @@ import { MediaQueryProvider } from '../context/mediaQuery'
import { NotificationsProvider } from '../context/notifications'
import { SessionProvider } from '../context/session'
import { SnackbarProvider } from '../context/snackbar'
+import { TopicsProvider } from '../context/topics'
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
import { DogmaPage } from '../pages/about/dogma.page'
import { GuidePage } from '../pages/about/guide.page'
@@ -117,21 +118,23 @@ export const App = (props: Props) => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx
index ff0661c9..81e69d58 100644
--- a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx
+++ b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx
@@ -39,6 +39,7 @@ export const PlayerPlaylist = (props: Props) => {
}
const play = (index: number) => {
+ props.onPlayMedia(index)
const mi = props.media[index]
gtag('event', 'select_item', {
item_list_id: props.articleSlug,
diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/Nav/AuthModal/LoginForm.tsx
index 1c4c774d..98c9ec05 100644
--- a/src/components/Nav/AuthModal/LoginForm.tsx
+++ b/src/components/Nav/AuthModal/LoginForm.tsx
@@ -160,7 +160,7 @@ export const LoginForm = () => {
/>
- {submitError()}
+ {submitError()}
diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx
index 5c11b8ad..a478225d 100644
--- a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx
+++ b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx
@@ -41,8 +41,9 @@ export const PasswordField = (props: Props) => {
}
const handleInputBlur = (value: string) => {
- if (props.variant === 'login') {
- return props.onBlur(value)
+ if (props.variant === 'login' && props.onBlur) {
+ props.onBlur(value)
+ return
}
if (value.length < 1) {
return
diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx
index dfbb2e71..7c0258e1 100644
--- a/src/components/Nav/Header/Header.tsx
+++ b/src/components/Nav/Header/Header.tsx
@@ -6,12 +6,10 @@ import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
-import { apiClient } from '../../../graphql/client/core'
import { ROUTES, router, useRouter } from '../../../stores/router'
import { useModalStore } from '../../../stores/ui'
import { getDescription } from '../../../utils/meta'
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
-import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
import { Icon } from '../../_shared/Icon'
import { Subscribe } from '../../_shared/Subscribe'
import { AuthModal } from '../AuthModal'
@@ -23,6 +21,8 @@ import { Snackbar } from '../Snackbar'
import { Link } from './Link'
+import { useTopics } from '../../../context/topics'
+import { getRandomTopicsFromArray } from '../../../utils/getRandomTopicsFromArray'
import styles from './Header.module.scss'
type Props = {
@@ -48,6 +48,7 @@ export const Header = (props: Props) => {
const { page } = useRouter()
const { requireAuthentication } = useSession()
const { searchParams } = useRouter
()
+ const { topics } = useTopics()
const [randomTopics, setRandomTopics] = createSignal([])
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
const [getIsScrolled, setIsScrolled] = createSignal(false)
@@ -58,6 +59,7 @@ export const Header = (props: Props) => {
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
+
const toggleFixed = () => setFixed(!fixed())
const tag = (topic: Topic) =>
@@ -65,6 +67,10 @@ export const Header = (props: Props) => {
let windowScrollTop = 0
+ createEffect(() => {
+ setRandomTopics(getRandomTopicsFromArray(topics()))
+ })
+
createEffect(() => {
const mainContent = document.querySelector('.main-content')
@@ -141,11 +147,6 @@ export const Header = (props: Props) => {
}, time)
}
- onMount(async () => {
- const topics = await apiClient.getRandomTopics({ amount: RANDOM_TOPICS_COUNT })
- setRandomTopics(topics)
- })
-
const handleToggleMenuByLink = (event: MouseEvent, route: keyof typeof ROUTES) => {
if (!fixed()) {
return
diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx
index 1c2afd98..70389ccb 100644
--- a/src/components/ProfileSettings/ProfileSettings.tsx
+++ b/src/components/ProfileSettings/ProfileSettings.tsx
@@ -123,7 +123,7 @@ export const ProfileSettings = () => {
setIsUserpicUpdating(true)
const result = await handleImageUpload(uploadFile)
- updateFormField('userpic', result.url)
+ updateFormField('pic', result.url)
setUserpicFile(null)
setIsUserpicUpdating(false)
diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx
index 6ca91c66..a592f433 100644
--- a/src/components/Views/Author/Author.tsx
+++ b/src/components/Views/Author/Author.tsx
@@ -26,6 +26,7 @@ import { Loading } from '../../_shared/Loading'
import { byCreated } from '../../../utils/sortby'
import stylesArticle from '../../Article/Article.module.scss'
import styles from './Author.module.scss'
+import { hideModal, MODALS } from "../../../stores/ui";
type Props = {
shouts: Shout[]
@@ -39,13 +40,14 @@ export const AuthorView = (props: Props) => {
const { t } = useLocalize()
const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
- const { page: getPage } = useRouter()
+ const { page: getPage, searchParams } = useRouter()
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
const [followers, setFollowers] = createSignal([])
const [following, setFollowing] = createSignal>([])
const [showExpandBioControl, setShowExpandBioControl] = createSignal(false)
const [commented, setCommented] = createSignal()
+ const modal = MODALS[searchParams().m]
// current author
const [author, setAuthor] = createSignal()
@@ -102,6 +104,9 @@ export const AuthorView = (props: Props) => {
}
onMount(() => {
+ if (!modal) {
+ hideModal()
+ }
checkBioHeight()
// pagination
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx
index 6dbe995b..eddd12a4 100644
--- a/src/components/Views/Expo/Expo.tsx
+++ b/src/components/Views/Expo/Expo.tsx
@@ -24,8 +24,8 @@ type Props = {
layout: LayoutType
}
-export const PRERENDERED_ARTICLES_COUNT = 24
-const LOAD_MORE_PAGE_SIZE = 16
+export const PRERENDERED_ARTICLES_COUNT = 37
+const LOAD_MORE_PAGE_SIZE = 11
export const Expo = (props: Props) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts))
diff --git a/src/components/Views/Home.tsx b/src/components/Views/Home.tsx
index 4e4dbce3..25e15a31 100644
--- a/src/components/Views/Home.tsx
+++ b/src/components/Views/Home.tsx
@@ -64,18 +64,8 @@ export const HomeView = (props: Props) => {
limit: CLIENT_LOAD_ARTICLES_COUNT,
offset: sortedArticles().length,
})
-
setIsLoadMoreButtonVisible(hasMore)
}
-
- const result = await apiClient.getRandomTopicShouts(RANDOM_TOPIC_SHOUTS_COUNT)
- if (!result || result.error) console.warn('[apiClient.getRandomTopicShouts] failed')
- batch(() => {
- if (!result?.error) {
- if (result?.topic) setRandomTopic(result.topic)
- if (result?.shouts) setRandomTopicArticles(result.shouts)
- }
- })
})
const loadMore = async () => {
diff --git a/src/context/profile.tsx b/src/context/profile.tsx
index 1b88f149..775b55c0 100644
--- a/src/context/profile.tsx
+++ b/src/context/profile.tsx
@@ -54,18 +54,12 @@ export const ProfileFormProvider = (props: { children: JSX.Element }) => {
const updateFormField = (fieldName: string, value: string, remove?: boolean) => {
if (fieldName === 'links') {
- if (remove) {
- setForm(
- 'links',
- form.links.filter((item) => item !== value),
- )
- } else {
- setForm((prev) => ({ ...prev, links: [...prev.links, value] }))
- }
- } else {
- setForm({
- [fieldName]: value,
+ setForm((prev) => {
+ const updatedLinks = remove ? prev.links.filter((item) => item !== value) : [...prev.links, value]
+ return { ...prev, links: updatedLinks }
})
+ } else {
+ setForm((prev) => ({ ...prev, [fieldName]: value }))
}
}
diff --git a/src/context/topics.tsx b/src/context/topics.tsx
new file mode 100644
index 00000000..4fe8d5f6
--- /dev/null
+++ b/src/context/topics.tsx
@@ -0,0 +1,59 @@
+import { openDB } from 'idb'
+import { Accessor, JSX, createContext, createSignal, onMount, useContext } from 'solid-js'
+import { apiClient } from '../graphql/client/core'
+import { Topic } from '../graphql/schema/core.gen'
+
+type TopicsContextType = {
+ topics: Accessor
+}
+
+const TopicsContext = createContext()
+export function useTopics() {
+ return useContext(TopicsContext)
+}
+
+const DB_NAME = 'discourseAppDB'
+const DB_VERSION = 1
+const STORE_NAME = 'topics'
+const setupIndexedDB = async () => {
+ return await openDB(DB_NAME, DB_VERSION, {
+ upgrade(db) {
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
+ db.createObjectStore(STORE_NAME, { keyPath: 'id' })
+ }
+ },
+ })
+}
+
+const getTopicsFromIndexedDB = async (db) => {
+ const tx = db.transaction(STORE_NAME, 'readonly')
+ const store = tx.objectStore(STORE_NAME)
+ return store.getAll()
+}
+const saveTopicsToIndexedDB = async (db, topics) => {
+ const tx = db.transaction(STORE_NAME, 'readwrite')
+ const store = tx.objectStore(STORE_NAME)
+ for (const topic of topics) {
+ await store.put(topic)
+ }
+ await tx.done
+}
+
+export const TopicsProvider = (props: { children: JSX.Element }) => {
+ const [randomTopics, setRandomTopics] = createSignal([])
+
+ onMount(async () => {
+ const db = await setupIndexedDB()
+ let topics = await getTopicsFromIndexedDB(db)
+
+ if (topics.length === 0) {
+ topics = await apiClient.getAllTopics()
+ await saveTopicsToIndexedDB(db, topics)
+ }
+ setRandomTopics(topics)
+ })
+
+ const value: TopicsContextType = { topics: randomTopics }
+
+ return {props.children}
+}
diff --git a/src/graphql/client/chat.ts b/src/graphql/client/chat.ts
index 42c5be97..c00fd705 100644
--- a/src/graphql/client/chat.ts
+++ b/src/graphql/client/chat.ts
@@ -1,3 +1,4 @@
+import { chatApiUrl } from '../../utils/config'
// inbox
import { createGraphQLClient } from '../createGraphQLClient'
import createChat from '../mutation/chat/chat-create'
@@ -24,7 +25,7 @@ import {
export const inboxClient = {
private: null,
- connect: (token: string) => (inboxClient.private = createGraphQLClient('chat', token)),
+ connect: (token: string) => (inboxClient.private = createGraphQLClient(chatApiUrl, token)),
loadChats: async (options: QueryLoad_ChatsArgs): Promise => {
const resp = await inboxClient.private.query(myChats, options).toPromise()
diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts
index 4cbe81c2..e7d8708e 100644
--- a/src/graphql/client/core.ts
+++ b/src/graphql/client/core.ts
@@ -16,6 +16,7 @@ import type {
Topic,
} from '../schema/core.gen'
+import { coreApiUrl } from '../../utils/config'
import { createGraphQLClient } from '../createGraphQLClient'
import createArticle from '../mutation/core/article-create'
import deleteShout from '../mutation/core/article-delete'
@@ -47,13 +48,13 @@ import topicBySlug from '../query/core/topic-by-slug'
import topicsAll from '../query/core/topics-all'
import topicsRandomQuery from '../query/core/topics-random'
-const publicGraphQLClient = createGraphQLClient('core')
+const publicGraphQLClient = createGraphQLClient(coreApiUrl)
export const apiClient = {
private: null,
connect: (token: string) => {
// NOTE: use it after token appears
- apiClient.private = createGraphQLClient('core', token)
+ apiClient.private = createGraphQLClient(coreApiUrl, token)
},
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
@@ -182,7 +183,7 @@ export const apiClient = {
createReaction: async (input: ReactionInput) => {
const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise()
console.debug('[graphql.client.core] createReaction:', response)
- return response.data.create_reaction.reaction
+ return response.data.create_reaction
},
destroyReaction: async (reaction_id: number) => {
const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise()
@@ -192,7 +193,7 @@ export const apiClient = {
updateReaction: async (reaction: ReactionInput) => {
const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise()
console.debug('[graphql.client.core] updateReaction:', response)
- return response.data.update_reaction.reaction
+ return response.data.update_reaction
},
loadAuthorsBy: async (args: QueryLoad_Authors_ByArgs) => {
const resp = await publicGraphQLClient.query(authorsLoadBy, args).toPromise()
diff --git a/src/graphql/createGraphQLClient.ts b/src/graphql/createGraphQLClient.ts
index 62755e9a..89b0d620 100644
--- a/src/graphql/createGraphQLClient.ts
+++ b/src/graphql/createGraphQLClient.ts
@@ -1,18 +1,17 @@
-import { ClientOptions, Exchange, createClient, dedupExchange, fetchExchange } from '@urql/core'
+import { ClientOptions, Exchange, createClient, fetchExchange } from '@urql/core'
import { devtoolsExchange } from '@urql/devtools'
import { isDev } from '../utils/config'
-const exchanges: Exchange[] = [dedupExchange, fetchExchange]
+const exchanges: Exchange[] = [fetchExchange]
if (isDev) {
exchanges.unshift(devtoolsExchange)
}
-export const createGraphQLClient = (serviceName: string, token = '') => {
+export const createGraphQLClient = (url: string, token = '') => {
const options: ClientOptions = {
- url: `https://${serviceName}.discours.io`,
- maskTypename: true,
+ url,
requestPolicy: 'cache-and-network',
fetchOptions: () => (token ? { headers: { Authorization: token } } : {}),
exchanges,
diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts
index e517378b..a52abf65 100644
--- a/src/stores/zine/articles.ts
+++ b/src/stores/zine/articles.ts
@@ -131,9 +131,8 @@ export const loadShout = async (slug: string): Promise => {
export const loadShouts = async (
options: LoadShoutsOptions,
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
- options.limit += 1
const newShouts = await apiClient.getShouts(options)
- const hasMore = newShouts?.length === options.limit + 1
+ const hasMore = newShouts?.length !== options.limit + 1 && newShouts?.length !== 0
if (hasMore) {
newShouts.splice(-1)
diff --git a/src/utils/config.ts b/src/utils/config.ts
index 1cfd89a5..cfc2f7fa 100644
--- a/src/utils/config.ts
+++ b/src/utils/config.ts
@@ -8,3 +8,12 @@ export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
const defaultSearchUrl = 'https://search.discours.io'
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl
+
+const defaultCoreUrl = 'https://core.discours.io'
+export const coreApiUrl = import.meta.env.PUBLIC_CORE_API || defaultCoreUrl
+
+const defaultChatUrl = 'https://chat.discours.io'
+export const chatApiUrl = import.meta.env.PUBLIC_CHAT_API || defaultChatUrl
+
+const defaultAuthUrl = 'https://auth.discours.io'
+export const authApiUrl = import.meta.env.PUBLIC_AUTH_API || defaultAuthUrl
diff --git a/src/utils/getRandomTopicsFromArray.ts b/src/utils/getRandomTopicsFromArray.ts
new file mode 100644
index 00000000..ced616c0
--- /dev/null
+++ b/src/utils/getRandomTopicsFromArray.ts
@@ -0,0 +1,7 @@
+import { RANDOM_TOPICS_COUNT } from '../components/Views/Home'
+import { Topic } from '../graphql/schema/core.gen'
+
+export const getRandomTopicsFromArray = (topics: Topic[], count: number = RANDOM_TOPICS_COUNT): Topic[] => {
+ const shuffledTopics = [...topics].sort(() => 0.5 - Math.random())
+ return shuffledTopics.slice(0, count)
+}