diff --git a/package-lock.json b/package-lock.json
index d0ee57c0..eb3fc3ca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -126,7 +126,7 @@
"typograf": "7.1.0",
"uniqolor": "1.1.0",
"vike": "0.4.148",
- "vite": "4.5.0",
+ "vite": "4.5.1",
"vite-plugin-mkcert": "1.16.0",
"vite-plugin-sass-dts": "1.3.11",
"vite-plugin-solid": "2.7.2",
@@ -18707,9 +18707,9 @@
}
},
"node_modules/vite": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
- "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
+ "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
diff --git a/package.json b/package.json
index 617c8561..ba7d22ec 100644
--- a/package.json
+++ b/package.json
@@ -147,7 +147,7 @@
"typograf": "7.1.0",
"uniqolor": "1.1.0",
"vike": "0.4.148",
- "vite": "4.5.0",
+ "vite": "4.5.1",
"vite-plugin-mkcert": "1.16.0",
"vite-plugin-sass-dts": "1.3.11",
"vite-plugin-solid": "2.7.2",
diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx
index 4731b28d..9a4b7370 100644
--- a/src/components/Article/FullArticle.tsx
+++ b/src/components/Article/FullArticle.tsx
@@ -13,6 +13,7 @@ import { useSession } from '../../context/session'
import { MediaItem } from '../../pages/types'
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
import { capitalize } from '../../utils/capitalize'
+import { isCyrillic } from '../../utils/cyrillic'
import { getImageUrl } from '../../utils/getImageUrl'
import { getDescription, getKeywords } from '../../utils/meta'
import { Icon } from '../_shared/Icon'
@@ -295,7 +296,9 @@ export const FullArticle = (props: Props) => {
const description = getDescription(props.article.description || body())
const ogTitle = props.article.title
const keywords = getKeywords(props.article)
-
+ const getAuthorName = (a: Author) => {
+ return lang() == 'en' && isCyrillic(a.name) ? capitalize(a.slug.replace(/-/, ' ')) : a.name
+ }
return (
<>
@@ -333,7 +336,7 @@ export const FullArticle = (props: Props) => {
{(a: Author, index) => (
<>
0}>,
- {a.name}
+ {getAuthorName(a)}
>
)}
diff --git a/src/components/Author/AhtorLink/AuthorLink.tsx b/src/components/Author/AhtorLink/AuthorLink.tsx
index 16a6111d..262e36ab 100644
--- a/src/components/Author/AhtorLink/AuthorLink.tsx
+++ b/src/components/Author/AhtorLink/AuthorLink.tsx
@@ -1,6 +1,10 @@
import { clsx } from 'clsx'
+import { createMemo } from 'solid-js'
+import { useLocalize } from '../../../context/localize'
import { Author } from '../../../graphql/schema/core.gen'
+import { capitalize } from '../../../utils/capitalize'
+import { isCyrillic } from '../../../utils/cyrillic'
import { Userpic } from '../Userpic'
import styles from './AhtorLink.module.scss'
@@ -13,6 +17,12 @@ type Props = {
}
export const AuthorLink = (props: Props) => {
+ const { lang } = useLocalize()
+ const name = createMemo(() => {
+ return lang() === 'en' && isCyrillic(props.author.name)
+ ? capitalize(props.author.slug.replace(/-/, ' '))
+ : props.author.name
+ })
return (
)
diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx
index 9f648ff0..6c1400e5 100644
--- a/src/components/Author/AuthorBadge/AuthorBadge.tsx
+++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx
@@ -4,6 +4,7 @@ import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
+import { ChatMember } from '../../../graphql/schema/chat.gen'
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
import { router, useRouter } from '../../../stores/router'
import { follow, unfollow } from '../../../stores/zine/common'
diff --git a/src/components/Inbox/DialogCard.tsx b/src/components/Inbox/DialogCard.tsx
index afd7f24c..e4cb941e 100644
--- a/src/components/Inbox/DialogCard.tsx
+++ b/src/components/Inbox/DialogCard.tsx
@@ -1,9 +1,10 @@
-import type { ChatMember } from '../../graphql/schema/core.gen'
+import type { ChatMember } from '../../graphql/schema/chat.gen'
import { clsx } from 'clsx'
import { Show, Switch, Match, createMemo } from 'solid-js'
import { useLocalize } from '../../context/localize'
+import { Author } from '../../graphql/schema/core.gen'
import { AuthorBadge } from '../Author/AuthorBadge'
import DialogAvatar from './DialogAvatar'
@@ -26,7 +27,7 @@ type DialogProps = {
const DialogCard = (props: DialogProps) => {
const { t, formatTime } = useLocalize()
const companions = createMemo(
- () => props.members && props.members.filter((member) => member.id !== props.ownId),
+ () => props.members && props.members.filter((member: ChatMember) => member.id !== props.ownId),
)
const names = createMemo(
@@ -51,11 +52,11 @@ const DialogCard = (props: DialogProps) => {
when={props.isChatHeader}
fallback={
-
+
}
>
-
+
}
>
diff --git a/src/components/NotificationsPanel/NotificationsPanel.tsx b/src/components/NotificationsPanel/NotificationsPanel.tsx
index d35bf57e..10ab22fd 100644
--- a/src/components/NotificationsPanel/NotificationsPanel.tsx
+++ b/src/components/NotificationsPanel/NotificationsPanel.tsx
@@ -50,6 +50,7 @@ export const NotificationsPanel = (props: Props) => {
const { isAuthenticated } = useSession()
const { t } = useLocalize()
const {
+ after,
sortedNotifications,
unreadNotificationsCount,
loadedNotificationsCount,
@@ -112,7 +113,7 @@ export const NotificationsPanel = (props: Props) => {
const scrollContainerRef: { current: HTMLDivElement } = { current: null }
const loadNextPage = async () => {
- await loadNotifications({ limit: PAGE_SIZE, offset: loadedNotificationsCount() })
+ await loadNotifications({ after: after(), limit: PAGE_SIZE, offset: loadedNotificationsCount() })
if (loadedNotificationsCount() < totalNotificationsCount()) {
const hasMore = scrollContainerRef.current.scrollHeight <= scrollContainerRef.current.offsetHeight
diff --git a/src/context/editor.tsx b/src/context/editor.tsx
index 737026da..cdb4022d 100644
--- a/src/context/editor.tsx
+++ b/src/context/editor.tsx
@@ -2,16 +2,15 @@ import type { JSX } from 'solid-js'
import { openPage } from '@nanostores/router'
import { Editor } from '@tiptap/core'
-import { Accessor, createContext, createMemo, createSignal, useContext } from 'solid-js'
+import { Accessor, createContext, createSignal, useContext } from 'solid-js'
import { createStore, SetStoreFunction } from 'solid-js/store'
-import { apiClient as coreClient } from '../graphql/client/core'
+import { apiClient } from '../graphql/client/core'
import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router'
import { slugify } from '../utils/slugify'
import { useLocalize } from './localize'
-import { useSession } from './session'
import { useSnackbar } from './snackbar'
type WordCounter = {
@@ -83,23 +82,12 @@ const removeDraftFromLocalStorage = (shoutId: number) => {
export const EditorProvider = (props: { children: JSX.Element }) => {
const { t } = useLocalize()
- const {
- actions: { getToken },
- } = useSession()
const { page } = useRouter()
- const apiClient = createMemo(() => {
- const token = getToken()
- if (!coreClient.private) coreClient.connect(token)
- return coreClient
- })
const {
actions: { showSnackbar },
} = useSnackbar()
-
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal(false)
-
const editorRef: { current: () => Editor } = { current: null }
-
const [form, setForm] = createStore(null)
const [formErrors, setFormErrors] = createStore>(null)
const [wordCounter, setWordCounter] = createSignal({
@@ -133,7 +121,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
}
const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => {
- return apiClient().updateArticle({
+ return await apiClient.updateArticle({
shoutId: formToUpdate.shoutId,
shoutInput: {
body: formToUpdate.body,
@@ -218,7 +206,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const publishShoutById = async (shoutId: number) => {
try {
- await apiClient().updateArticle({
+ await apiClient.updateArticle({
shoutId,
publish: true,
})
@@ -232,7 +220,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const deleteShout = async (shoutId: number) => {
try {
- await apiClient().deleteShout({
+ await apiClient.deleteShout({
shoutId,
})
return true
diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx
index 5188e473..7bd2a087 100644
--- a/src/context/inbox.tsx
+++ b/src/context/inbox.tsx
@@ -5,6 +5,7 @@ import { createContext, createEffect, createSignal, useContext } from 'solid-js'
import { inboxClient } from '../graphql/client/chat'
import { Author } from '../graphql/schema/core.gen'
+import { useAuthorsStore } from '../stores/zine/authors'
import { SSEMessage, useConnect } from './connect'
import { useSession } from './session'
@@ -15,7 +16,7 @@ type InboxContextType = {
actions: {
createChat: (members: number[], title: string) => Promise<{ chat: Chat }>
loadChats: () => Promise>
- loadRecipients: () => Promise>
+ loadRecipients: () => Array
loadMessages: (by: MessagesBy, limit: number, offset: number) => Promise>
getMessages?: (chatId: string) => Promise>
sendMessage?: (args: MutationCreate_MessageArgs) => void
@@ -31,6 +32,7 @@ export function useInbox() {
export const InboxProvider = (props: { children: JSX.Element }) => {
const [chats, setChats] = createSignal([])
const [messages, setMessages] = createSignal([])
+ const { sortedAuthors } = useAuthorsStore()
const handleMessage = (sseMessage: SSEMessage) => {
console.log('[context.inbox]:', sseMessage)
@@ -48,25 +50,6 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
const { addHandler } = useConnect()
addHandler(handleMessage)
- const {
- actions: { getToken },
- } = useSession()
-
- createEffect(() => {
- const token = getToken()
- if (!inboxClient.private && token) {
- inboxClient.connect(token)
- }
- })
-
- const loadRecipients = async (limit = 50, offset = 0): Promise> => {
- if (inboxClient.private) {
- // TODO: perhaps setMembers(authors) ?
- return await inboxClient.loadRecipients({ limit, offset })
- }
- return []
- }
-
const loadMessages = async (
by: MessagesBy,
limit: number = 50,
@@ -136,7 +119,7 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
createChat,
loadChats,
loadMessages,
- loadRecipients,
+ loadRecipients: sortedAuthors,
getMessages,
sendMessage,
}
diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx
index 754006d9..5d9721dc 100644
--- a/src/context/notifications.tsx
+++ b/src/context/notifications.tsx
@@ -1,5 +1,6 @@
import type { Accessor, JSX } from 'solid-js'
+import { createStorageSignal } from '@solid-primitives/storage'
import { createContext, createEffect, createMemo, createSignal, onMount, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
import { Portal } from 'solid-js/web'
@@ -7,7 +8,7 @@ import { Portal } from 'solid-js/web'
import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated'
import { NotificationsPanel } from '../components/NotificationsPanel'
import { notifierClient } from '../graphql/client/notifier'
-import { Notification } from '../graphql/schema/notifier.gen'
+import { Notification, QueryLoad_NotificationsArgs } from '../graphql/schema/notifier.gen'
import { SSEMessage, useConnect } from './connect'
import { useSession } from './session'
@@ -15,6 +16,7 @@ import { useSession } from './session'
type NotificationsContextType = {
notificationEntities: Record
unreadNotificationsCount: Accessor
+ after: Accessor
sortedNotifications: Accessor
loadedNotificationsCount: Accessor
totalNotificationsCount: Accessor
@@ -23,7 +25,7 @@ type NotificationsContextType = {
hideNotificationsPanel: () => void
markNotificationAsRead: (notification: Notification) => Promise
markAllNotificationsAsRead: () => Promise
- loadNotifications: (options: { limit: number; offset: number }) => Promise
+ loadNotifications: (options: QueryLoad_NotificationsArgs) => Promise
}
}
@@ -39,21 +41,10 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore>({})
- const {
- isAuthenticated,
- actions: { getToken },
- } = useSession()
-
- createEffect(() => {
- const token = getToken()
- if (!notifierClient.private && token) {
- notifierClient.connect(token)
- }
- })
-
+ const { isAuthenticated } = useSession()
const { addHandler } = useConnect()
- const loadNotifications = async (options: { limit?: number; offset?: number }) => {
+ const loadNotifications = async (options: { after: number; limit?: number; offset?: number }) => {
if (isAuthenticated() && notifierClient?.private) {
const { notifications, unread, total } = await notifierClient.getNotifications(options)
const newNotificationEntities = notifications.reduce((acc, notification) => {
@@ -75,16 +66,18 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
return Object.values(notificationEntities).sort((a, b) => b.created_at - a.created_at)
})
+ const now = Math.floor(Date.now() / 1000)
const loadedNotificationsCount = createMemo(() => Object.keys(notificationEntities).length)
-
+ const [after, setAfter] = createStorageSignal('notifier_timestamp', now)
onMount(() => {
addHandler((data: SSEMessage) => {
if (data.entity === 'reaction' && isAuthenticated()) {
- loadNotifications({ limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
+ loadNotifications({ after: after(), limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) })
} else {
- console.error(`[NotificationsProvider] unhandled message type: ${JSON.stringify(data)}`)
+ console.info(`[NotificationsProvider] bypassed:`, data)
}
})
+ setAfter(now)
})
const markNotificationAsRead = async (notification: Notification) => {
@@ -96,7 +89,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const markAllNotificationsAsRead = async () => {
if (isAuthenticated() && notifierClient.private) {
await notifierClient.markAllNotificationsAsRead()
- await loadNotifications({ limit: loadedNotificationsCount() })
+ await loadNotifications({ after: after(), limit: loadedNotificationsCount() })
}
}
@@ -117,6 +110,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
}
const value: NotificationsContextType = {
+ after,
notificationEntities,
sortedNotifications,
unreadNotificationsCount,
diff --git a/src/context/profile.tsx b/src/context/profile.tsx
index 10b50a0f..3688f60b 100644
--- a/src/context/profile.tsx
+++ b/src/context/profile.tsx
@@ -3,7 +3,7 @@ import type { ProfileInput } from '../graphql/schema/core.gen'
import { createContext, createEffect, createMemo, JSX, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
-import { apiClient as coreClient } from '../graphql/client/core'
+import { apiClient } from '../graphql/client/core'
import { loadAuthor } from '../stores/zine/authors'
import { useSession } from './session'
@@ -38,14 +38,8 @@ export const ProfileFormProvider = (props: { children: JSX.Element }) => {
const currentSlug = createMemo(() => author()?.slug)
- const apiClient = createMemo(() => {
- const token = getToken()
- if (!coreClient.private) coreClient.connect(token)
- return coreClient
- })
-
const submit = async (profile: ProfileInput) => {
- const response = await apiClient().updateProfile(profile)
+ const response = await apiClient.updateProfile(profile)
if (response.error) {
console.error(response.error)
throw response.error
diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx
index d37ff466..c9893b6c 100644
--- a/src/context/reactions.tsx
+++ b/src/context/reactions.tsx
@@ -3,7 +3,7 @@ import type { JSX } from 'solid-js'
import { createContext, createMemo, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store'
-import { apiClient as coreClient } from '../graphql/client/core'
+import { apiClient } from '../graphql/client/core'
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
import { useSession } from './session'
@@ -38,12 +38,6 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
actions: { getToken },
} = useSession()
- const apiClient = createMemo(() => {
- const token = getToken()
- if (!coreClient.private) coreClient.connect(token)
- return coreClient
- })
-
const loadReactionsBy = async ({
by,
limit,
@@ -53,7 +47,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
limit?: number
offset?: number
}): Promise => {
- const reactions = await coreClient.getReactionsBy({ by, limit, offset })
+ const reactions = await apiClient.getReactionsBy({ by, limit, offset })
const newReactionEntities = reactions.reduce((acc, reaction) => {
acc[reaction.id] = reaction
return acc
@@ -63,7 +57,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
}
const createReaction = async (input: ReactionInput): Promise => {
- const reaction = await apiClient().createReaction(input)
+ const reaction = await apiClient.createReaction(input)
const changes = {
[reaction.id]: reaction,
@@ -90,14 +84,14 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
}
const deleteReaction = async (id: number): Promise => {
- const reaction = await apiClient().destroyReaction(id)
+ const reaction = await apiClient.destroyReaction(id)
setReactionEntities({
[reaction.id]: undefined,
})
}
const updateReaction = async (id: number, input: ReactionInput): Promise => {
- const reaction = await apiClient().updateReaction(id, input)
+ const reaction = await apiClient.updateReaction(id, input)
setReactionEntities(reaction.id, reaction)
}
diff --git a/src/context/session.tsx b/src/context/session.tsx
index e8ce4ad8..a8314bad 100644
--- a/src/context/session.tsx
+++ b/src/context/session.tsx
@@ -20,7 +20,9 @@ import {
useContext,
} from 'solid-js'
+import { inboxClient } from '../graphql/client/chat'
import { apiClient } from '../graphql/client/core'
+import { notifierClient } from '../graphql/client/notifier'
import { useRouter } from '../stores/router'
import { showModal } from '../stores/ui'
@@ -121,9 +123,12 @@ export const SessionProvider = (props: {
})
createEffect(() => {
- // authorized graphql client
- const tkn = getToken()
- if (tkn) apiClient.connect(tkn)
+ const token = getToken()
+ if (!inboxClient.private && token) {
+ apiClient.connect(token)
+ notifierClient.connect(token)
+ inboxClient.connect(token)
+ }
})
const loadSubscriptions = async (): Promise => {
diff --git a/src/graphql/query/core/articles-load-unrated.ts b/src/graphql/query/core/articles-load-unrated.ts
index a4d01b66..29b647de 100644
--- a/src/graphql/query/core/articles-load-unrated.ts
+++ b/src/graphql/query/core/articles-load-unrated.ts
@@ -1,8 +1,8 @@
import { gql } from '@urql/core'
export default gql`
- query LoadUnratedShoutsQuery($limit: Int!) {
- loadUnratedShouts(limit: $limit) {
+ query LoadUnratedShoutsQuery($limit: Int, $offset: Int) {
+ load_shouts_unrated(limit: $limit, offset: $offset) {
id
title
# lead
diff --git a/src/graphql/query/notifier/notifications-load.ts b/src/graphql/query/notifier/notifications-load.ts
index d119048d..666081ca 100644
--- a/src/graphql/query/notifier/notifications-load.ts
+++ b/src/graphql/query/notifier/notifications-load.ts
@@ -1,8 +1,8 @@
import { gql } from '@urql/core'
export default gql`
- query LoadNotificationsQuery($limit: Int, $offset: Int) {
- load_notifications(limit: $limit, offset: $offset) {
+ query LoadNotificationsQuery($after: Int!, $limit: Int, $offset: Int) {
+ load_notifications(after: $after, limit: $limit, offset: $offset) {
notifications {
id
entity
diff --git a/src/utils/cyrillic.ts b/src/utils/cyrillic.ts
new file mode 100644
index 00000000..09b6420e
--- /dev/null
+++ b/src/utils/cyrillic.ts
@@ -0,0 +1,5 @@
+export const isCyrillic = (s: string): boolean => {
+ const cyrillicRegex = /[\u0400-\u04FF]/ // Range for Cyrillic characters
+
+ return cyrillicRegex.test(s)
+}
diff --git a/src/utils/getImageUrl.ts b/src/utils/getImageUrl.ts
index 1d7123d7..d91e23db 100644
--- a/src/utils/getImageUrl.ts
+++ b/src/utils/getImageUrl.ts
@@ -13,6 +13,9 @@ const getSizeUrlPart = (options: { width?: number; height?: number } = {}) => {
export const getImageUrl = (src: string, options: { width?: number; height?: number } = {}) => {
const sizeUrlPart = getSizeUrlPart(options)
-
- return `${thumborUrl}/unsafe/${sizeUrlPart}${src.replace(thumborUrl, '').replace('/unsafe', '')}`
+ const sourceUrl = src.replace(thumborUrl, '').replace('/unsafe', '')
+ return (
+ 'https://' +
+ `${thumborUrl}/unsafe/${sizeUrlPart}${sourceUrl}`.replace('https://', '').replace('//', '/')
+ )
}