author-page-wip
This commit is contained in:
parent
25d217389b
commit
b7e775eeea
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,3 +27,4 @@ target
|
|||
|
||||
.output
|
||||
.vinxi
|
||||
*.pem
|
||||
|
|
57
README.en.md
Normal file
57
README.en.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
## Development setup recommendations
|
||||
|
||||
### How to start
|
||||
|
||||
Use `bun i`, `npm i`, `pnpm i` or `yarn` to install packages. Then generate cert and key file for devserver with `mkcert localhost`.
|
||||
|
||||
### Config of variables
|
||||
|
||||
- Use `.env` file to setup your own development environment
|
||||
- Env vars with prefix `PUBLIC_` are widely used in `/src/utils/config.ts`
|
||||
|
||||
### Useful commands
|
||||
|
||||
run checks, fix styles, imports, formatting and autofixable linting errors:
|
||||
```
|
||||
bun run typecheck
|
||||
bun run fix
|
||||
```
|
||||
|
||||
## End-to-End (E2E) Tests
|
||||
|
||||
This directory contains end-to-end tests. These tests are written using [Playwright](https://playwright.dev/)
|
||||
|
||||
### Structure
|
||||
|
||||
- `/tests/*`: This directory contains the test files.
|
||||
- `/playwright.config.ts`: This is the configuration file for Playwright.
|
||||
|
||||
### Getting Started
|
||||
|
||||
Follow these steps:
|
||||
|
||||
1. **Install dependencies**: Run `npm run e2e:install` to install the necessary dependencies for running the tests.
|
||||
|
||||
2. **Run the tests**: After using `npm run e2e:tests`.
|
||||
|
||||
### Additional Information
|
||||
|
||||
If workers is no needed use:
|
||||
- `npx playwright test --project=webkit --workers 4`
|
||||
|
||||
For more information on how to write tests using Playwright - [Playwright documentation](https://playwright.dev/docs/intro).
|
||||
|
||||
### 🚀 Tests in CI Mode
|
||||
|
||||
Tests are executed within a GitHub workflow. We organize our tests into two main directories:
|
||||
|
||||
- `tests`: Contains tests that do not require authentication.
|
||||
- `tests-with-auth`: Houses tests that interact with authenticated parts of the application.
|
||||
|
||||
🔧 **Configuration:**
|
||||
|
||||
Playwright is configured to utilize the `BASE_URL` environment variable. Ensure this is properly set in your CI configuration to point to the correct environment.
|
||||
|
||||
📝 **Note:**
|
||||
|
||||
After pages have been adjusted to work with authentication, all tests should be moved to the `tests` directory to streamline the testing process.
|
68
README.md
68
README.md
|
@ -1,63 +1,59 @@
|
|||
## How to start
|
||||
## Рекомендации по настройке разработки
|
||||
|
||||
Use Bun to manage packages.
|
||||
### Как начать
|
||||
|
||||
```
|
||||
bun i
|
||||
```
|
||||
Используйте `bun i`, `npm i`, `pnpm i` или `yarn`, чтобы установить пакеты. Затем сгенерируйте сертификат и файл ключа для devserver с помощью `mkcert localhost`.
|
||||
|
||||
### Настройка переменных
|
||||
|
||||
- Используйте файл `.env` для настройки переменных собственной среды разработки.
|
||||
- Переменные окружения с префиксом `PUBLIC_` широко используются в `/src/utils/config.ts`.
|
||||
|
||||
### Полезные команды
|
||||
|
||||
Запуск проверки соответствия типов и автоматически исправить ошибки стилей, порядок импорта, форматирование:
|
||||
|
||||
## Useful commands
|
||||
run checks
|
||||
```
|
||||
bun run typecheck
|
||||
```
|
||||
fix styles, imports, formatting and autofixable linting errors:
|
||||
```
|
||||
bun run fix
|
||||
```
|
||||
|
||||
## Config of variables
|
||||
|
||||
- All vars are already in place and wroted in
|
||||
```
|
||||
/src/utils/config.ts
|
||||
```
|
||||
## End-to-End (E2E) тесты
|
||||
|
||||
# End-to-End (E2E) Tests
|
||||
End-to-end тесты написаны с использованием [Playwright](https://playwright.dev/).
|
||||
|
||||
This directory contains end-to-end tests. These tests are written using [Playwright](https://playwright.dev/)
|
||||
### Структура
|
||||
|
||||
## Structure
|
||||
- `/tests/*`: содержит файлы тестов
|
||||
- `/playwright.config.ts`: конфиг для Playwright
|
||||
|
||||
- `/tests/*`: This directory contains the test files.
|
||||
- `/playwright.config.ts`: This is the configuration file for Playwright.
|
||||
### Начало работы
|
||||
|
||||
## Getting Started
|
||||
Следуйте этим шагам:
|
||||
|
||||
Follow these steps:
|
||||
1. **Установите зависимости**: Запустите `npm run e2e:install`, чтобы установить необходимые зависимости для выполнения тестов.
|
||||
|
||||
1. **Install dependencies**: Run `pnpm e2e:install` to install the necessary dependencies for running the tests.
|
||||
2. **Запустите тесты**: После установки зависимостей используйте `npm run e2e:tests`.
|
||||
|
||||
2. **Run the tests**: After using `pnpm e2e:tests`.
|
||||
### Дополнительная информация
|
||||
|
||||
## Additional Information
|
||||
|
||||
If workers is no needed use:
|
||||
Для параллельного исполнения:
|
||||
- `npx playwright test --project=webkit --workers 4`
|
||||
|
||||
For more information on how to write tests using Playwright - [Playwright documentation](https://playwright.dev/docs/intro).
|
||||
Для получения дополнительной информации о написании тестов с использованием Playwright - [Документация Playwright](https://playwright.dev/docs/intro).
|
||||
|
||||
## 🚀 Tests in CI Mode
|
||||
### 🚀 Тесты в режиме CI
|
||||
|
||||
Tests are executed within a GitHub workflow. We organize our tests into two main directories:
|
||||
Тесты выполняются в рамках GitHub workflow. Мы организуем наши тесты в две основные директории:
|
||||
|
||||
- `tests`: Contains tests that do not require authentication.
|
||||
- `tests-with-auth`: Houses tests that interact with authenticated parts of the application.
|
||||
- `tests`: Содержит тесты, которые не требуют аутентификации.
|
||||
- `tests-with-auth`: Содержит тесты, которые взаимодействуют с аутентифицированными частями приложения.
|
||||
|
||||
🔧 **Configuration:**
|
||||
🔧 **Конфигурация:**
|
||||
|
||||
Playwright is configured to utilize the `BASE_URL` environment variable. Ensure this is properly set in your CI configuration to point to the correct environment.
|
||||
Playwright настроен на использование переменной окружения `BASE_URL`. Убедитесь, что она правильно установлена в вашей конфигурации CI для указания на правильную среду.
|
||||
|
||||
📝 **Note:**
|
||||
📝 **Примечание:**
|
||||
|
||||
After pages have been adjusted to work with authentication, all tests should be moved to the `tests` directory to streamline the testing process.
|
||||
После того как страницы были настроены для работы с аутентификацией, все тесты должны быть перемещены в директорию `tests` для упрощения процесса тестирования.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { SolidStartInlineConfig, defineConfig } from '@solidjs/start/config'
|
||||
import { visualizer } from "rollup-plugin-visualizer"
|
||||
import mkcert from 'vite-plugin-mkcert'
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||
import sassDts from 'vite-plugin-sass-dts'
|
||||
|
||||
|
@ -20,6 +22,7 @@ export default defineConfig({
|
|||
vite: {
|
||||
envPrefix: 'PUBLIC_',
|
||||
plugins: [
|
||||
mkcert(),
|
||||
nodePolyfills({
|
||||
include: ['path', 'stream', 'util'],
|
||||
exclude: ['http'],
|
||||
|
@ -43,7 +46,10 @@ export default defineConfig({
|
|||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 1024,
|
||||
target: 'esnext'
|
||||
target: 'esnext',
|
||||
rollupOptions: {
|
||||
plugins: [visualizer(), ]
|
||||
}
|
||||
},
|
||||
server: {
|
||||
https: true
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
"prosemirror-history": "^1.4.1",
|
||||
"prosemirror-trailing-node": "^2.0.8",
|
||||
"prosemirror-view": "^1.33.8",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "1.76.0",
|
||||
"solid-js": "^1.8.18",
|
||||
"solid-popper": "^0.3.0",
|
||||
|
@ -106,6 +107,7 @@
|
|||
"typograf": "^7.4.1",
|
||||
"uniqolor": "^1.1.1",
|
||||
"vinxi": "^0.3.14",
|
||||
"vite-plugin-mkcert": "^1.17.5",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-sass-dts": "^1.3.24",
|
||||
"y-prosemirror": "1.2.9",
|
||||
|
|
|
@ -28,35 +28,42 @@ import styles from './Author.module.scss'
|
|||
|
||||
type Props = {
|
||||
authorSlug: string
|
||||
selectedTab: string
|
||||
shouts?: Shout[]
|
||||
author?: Author
|
||||
topics?: Topic[]
|
||||
selectedTab: string
|
||||
}
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 12
|
||||
const LOAD_MORE_PAGE_SIZE = 9
|
||||
|
||||
export const AuthorView = (props: Props) => {
|
||||
console.debug('[components.AuthorView] reactive context init...')
|
||||
// contexts
|
||||
const { t } = useLocalize()
|
||||
const params = useParams()
|
||||
const { followers: myFollowers, follows: myFollows } = useFollowing()
|
||||
const { session } = useSession()
|
||||
const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||
const [authorSlug, setSlug] = createSignal(props.authorSlug)
|
||||
const { sortedFeed } = useFeed()
|
||||
const loc = useLocation()
|
||||
const params = useParams()
|
||||
const { session } = useSession()
|
||||
const { query } = useGraphQL()
|
||||
const { sortedFeed } = useFeed()
|
||||
const { loadAuthor, authorsEntities } = useAuthors()
|
||||
const { followers: myFollowers, follows: myFollows } = useFollowing()
|
||||
|
||||
// signals
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
|
||||
const { loadAuthor, authorsEntities } = useAuthors()
|
||||
const [author, setAuthor] = createSignal<Author>()
|
||||
const [followers, setFollowers] = createSignal<Author[]>([] as Author[])
|
||||
const [following, changeFollowing] = createSignal<Array<Author | Topic>>([] as Array<Author | Topic>) // flat AuthorFollowsResult
|
||||
const [showExpandBioControl, setShowExpandBioControl] = createSignal(false)
|
||||
const [commented, setCommented] = createSignal<Reaction[]>([])
|
||||
const { query } = useGraphQL()
|
||||
|
||||
// derivatives
|
||||
const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
|
||||
// fx
|
||||
// пагинация загрузки ленты постов
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
|
@ -74,6 +81,7 @@ export const AuthorView = (props: Props) => {
|
|||
const [isFetching, setIsFetching] = createSignal(false)
|
||||
createEffect(
|
||||
on([() => session()?.user?.app_data?.profile, () => props.authorSlug || ''], async ([me, slug]) => {
|
||||
console.debug('check if my profile')
|
||||
const my = slug && me?.slug === slug
|
||||
if (my) {
|
||||
console.debug('[Author] my profile precached')
|
||||
|
@ -84,29 +92,27 @@ export const AuthorView = (props: Props) => {
|
|||
}
|
||||
} else if (slug && !isFetching()) {
|
||||
setIsFetching(true)
|
||||
setSlug(slug)
|
||||
await loadAuthor({ slug })
|
||||
setIsFetching(false) // Сброс состояния загрузки после завершения
|
||||
}
|
||||
})
|
||||
}, {defer: true})
|
||||
)
|
||||
|
||||
// 2 // догружает подписки автора
|
||||
createEffect(
|
||||
on(
|
||||
[followers, () => props.author || authorsEntities()[authorSlug()]],
|
||||
async ([current, found]) => {
|
||||
if (current) return
|
||||
() => authorsEntities()[props.author?.slug || props.authorSlug || ''],
|
||||
async (found) => {
|
||||
if (!found) return
|
||||
setAuthor(found)
|
||||
console.info(`[Author] profile for @${authorSlug()} fetched`)
|
||||
const followsResp = await query(getAuthorFollowsQuery, { slug: authorSlug() }).toPromise()
|
||||
console.info(`[Author] profile for @${found.slug} fetched`)
|
||||
const followsResp = await query(getAuthorFollowsQuery, { slug: found.slug }).toPromise()
|
||||
const follows = followsResp?.data?.get_author_followers || {}
|
||||
changeFollowing([...(follows?.authors || []), ...(follows?.topics || [])])
|
||||
console.info(`[Author] follows for @${authorSlug()} fetched`)
|
||||
const followersResp = await query(getAuthorFollowersQuery, { slug: authorSlug() }).toPromise()
|
||||
console.info(`[Author] follows for @${found.slug} fetched`)
|
||||
const followersResp = await query(getAuthorFollowersQuery, { slug: found.slug }).toPromise()
|
||||
setFollowers(followersResp?.data?.get_author_followers || [])
|
||||
console.info(`[Author] followers for @${authorSlug()} fetched`)
|
||||
console.info(`[Author] followers for @${found.slug} fetched`)
|
||||
setIsFetching(false)
|
||||
},
|
||||
{ defer: true }
|
||||
|
@ -120,33 +126,32 @@ export const AuthorView = (props: Props) => {
|
|||
async (profile: Author) => {
|
||||
if (!commented() && profile) {
|
||||
await loadMore()
|
||||
|
||||
const commentsFetcher = loadReactions({
|
||||
by: { comment: true, created_by: profile.id }
|
||||
})
|
||||
const ccc = await commentsFetcher()
|
||||
if (ccc) setCommented((_) => ccc || [])
|
||||
}
|
||||
}
|
||||
},
|
||||
// { defer: true },
|
||||
)
|
||||
)
|
||||
|
||||
// event handlers
|
||||
let bioContainerRef: HTMLDivElement
|
||||
let bioWrapperRef: HTMLDivElement
|
||||
const checkBioHeight = () => {
|
||||
console.debug('[AuthorView] mounted, checking bio height...')
|
||||
if (bioContainerRef) {
|
||||
setShowExpandBioControl(bioContainerRef.offsetHeight > bioWrapperRef.offsetHeight)
|
||||
}
|
||||
}
|
||||
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
const handleDeleteComment = (id: number) => {
|
||||
setCommented((prev) => (prev || []).filter((comment) => comment.id !== id))
|
||||
}
|
||||
|
||||
// on load
|
||||
onMount(checkBioHeight)
|
||||
|
||||
return (
|
||||
|
|
|
@ -95,6 +95,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
|
|||
const fetcher = await getAuthor(opts)
|
||||
const author = await fetcher()
|
||||
if (author) addAuthor(author as Author)
|
||||
console.debug('Loaded author:', author)
|
||||
} catch (error) {
|
||||
console.error('Error loading author:', error)
|
||||
throw error
|
||||
|
|
|
@ -106,7 +106,7 @@ export const loadFollowersByTopic = (slug: string) => {
|
|||
// TODO: paginate topic followers
|
||||
return cache(async () => {
|
||||
const resp = await defaultClient.query(loadFollowersByTopicQuery, { slug }).toPromise()
|
||||
const result = resp?.data?.load_authors_by
|
||||
const result = resp?.data?.get_topic_followers
|
||||
if (result) return result as Author[]
|
||||
}, `topic-${slug}`)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ const fetchAllTopics = async () => {
|
|||
}
|
||||
|
||||
const fetchAuthor = async (slug: string) => {
|
||||
const authorFetcher = loadAuthors({ by: { slug }, limit: 1 } as QueryLoad_Authors_ByArgs)
|
||||
const authorFetcher = loadAuthors({ by: { slug }, limit: 1, offset: 0 } as QueryLoad_Authors_ByArgs)
|
||||
const aaa = await authorFetcher()
|
||||
return aaa?.[0]
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; to
|
|||
? getImageUrl(author()?.pic || '', { width: 1200 })
|
||||
: getImageUrl('production/image/logo_image.png')
|
||||
)
|
||||
|
||||
const selectedTab = createMemo(() => params.tab in ['followers', 'shouts'] ? params.tab : 'name')
|
||||
return (
|
||||
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
|
@ -91,9 +93,9 @@ export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; to
|
|||
<ReactionsProvider>
|
||||
<AuthorView
|
||||
author={author() as Author}
|
||||
selectedTab={selectedTab()}
|
||||
authorSlug={params.slug}
|
||||
shouts={articles() as Shout[]}
|
||||
selectedTab={'shouts'}
|
||||
topics={topics()}
|
||||
/>
|
||||
</ReactionsProvider>
|
|
@ -1,104 +0,0 @@
|
|||
import { RouteSectionProps, createAsync, useParams } from '@solidjs/router'
|
||||
import { ErrorBoundary, Suspense, createEffect, createMemo } from 'solid-js'
|
||||
import { AuthorView } from '~/components/Views/Author'
|
||||
import { FourOuFourView } from '~/components/Views/FourOuFour'
|
||||
import { Loading } from '~/components/_shared/Loading'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useAuthors } from '~/context/authors'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { ReactionsProvider } from '~/context/reactions'
|
||||
import { loadAuthors, loadShouts, loadTopics } from '~/graphql/api/public'
|
||||
import {
|
||||
Author,
|
||||
LoadShoutsOptions,
|
||||
QueryLoad_Authors_ByArgs,
|
||||
Shout,
|
||||
Topic
|
||||
} from '~/graphql/schema/core.gen'
|
||||
import { getImageUrl } from '~/lib/getImageUrl'
|
||||
import { SHOUTS_PER_PAGE } from '../../(home)'
|
||||
|
||||
const fetchAuthorShouts = async (slug: string, offset?: number) => {
|
||||
const opts: LoadShoutsOptions = { filters: { author: slug }, limit: SHOUTS_PER_PAGE, offset }
|
||||
const shoutsLoader = loadShouts(opts)
|
||||
return await shoutsLoader()
|
||||
}
|
||||
|
||||
const fetchAllTopics = async () => {
|
||||
const topicsFetcher = loadTopics()
|
||||
return await topicsFetcher()
|
||||
}
|
||||
|
||||
const fetchAuthor = async (slug: string) => {
|
||||
const authorFetcher = loadAuthors({ by: { slug }, limit: 1 } as QueryLoad_Authors_ByArgs)
|
||||
const aaa = await authorFetcher()
|
||||
return aaa?.[0]
|
||||
}
|
||||
|
||||
export const route = {
|
||||
load: async ({ params, location: { query } }: RouteSectionProps<{ articles: Shout[] }>) => {
|
||||
const offset: number = Number.parseInt(query.offset, 10)
|
||||
const result = await fetchAuthorShouts(params.slug, offset)
|
||||
return {
|
||||
author: await fetchAuthor(params.slug),
|
||||
shouts: result || [],
|
||||
topics: await fetchAllTopics()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default (props: RouteSectionProps<{ articles: Shout[]; author: Author; topics: Topic[] }>) => {
|
||||
const params = useParams()
|
||||
const { addAuthor } = useAuthors()
|
||||
const articles = createAsync(
|
||||
async () => props.data.articles || (await fetchAuthorShouts(params.slug)) || []
|
||||
)
|
||||
const author = createAsync(async () => {
|
||||
const a = props.data.author || (await fetchAuthor(params.slug))
|
||||
addAuthor(a)
|
||||
return a
|
||||
})
|
||||
const topics = createAsync(async () => props.data.topics || (await fetchAllTopics()))
|
||||
const { t } = useLocalize()
|
||||
const title = createMemo(() => `${author()?.name || ''}`)
|
||||
|
||||
createEffect(() => {
|
||||
if (author()) {
|
||||
console.debug('[routes] author/[slug] author loaded fx')
|
||||
window?.gtag?.('event', 'page_view', {
|
||||
page_title: author()?.name || '',
|
||||
page_location: window?.location.href || '',
|
||||
page_path: window?.location.pathname || ''
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const cover = createMemo(() =>
|
||||
author()?.pic
|
||||
? getImageUrl(author()?.pic || '', { width: 1200 })
|
||||
: getImageUrl('production/image/logo_image.png')
|
||||
)
|
||||
return (
|
||||
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<PageLayout
|
||||
title={`${t('Discours')} :: ${title()}`}
|
||||
headerTitle={author()?.name || ''}
|
||||
slug={author()?.slug}
|
||||
desc={author()?.about || author()?.bio || ''}
|
||||
cover={cover()}
|
||||
>
|
||||
<ReactionsProvider>
|
||||
<AuthorView
|
||||
author={author() as Author}
|
||||
authorSlug={params.slug}
|
||||
shouts={articles() as Shout[]}
|
||||
selectedTab={params.tab}
|
||||
topics={topics()}
|
||||
/>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user