routing-structure-fin
This commit is contained in:
parent
3fa235cd16
commit
c9a8c1aa8e
|
@ -16,8 +16,8 @@ export default () => {
|
|||
<div class={clsx(styles.discoursBannerContent, 'col-lg-10')}>
|
||||
<h3>{t('Discours exists because of our common effort')}</h3>
|
||||
<p>
|
||||
<a href="/about/help">{t('Support us')}</a>
|
||||
<a href="/create">{t('Become an author')}</a>
|
||||
<a href="/guide/support">{t('Support us')}</a>
|
||||
<a href="/edit/new">{t('Become an author')}</a>
|
||||
<a href={''} onClick={() => showModal('auth')}>
|
||||
{t('Join the community')}
|
||||
</a>
|
||||
|
|
|
@ -25,9 +25,9 @@ export const FooterView = () => {
|
|||
{
|
||||
header: t('About the project'),
|
||||
items: [
|
||||
{ title: t('Discours Manifest'), slug: '/about/manifest' },
|
||||
{ title: t('How it works'), slug: '/about/guide' },
|
||||
{ title: t('Dogma'), slug: '/about/dogma' },
|
||||
{ title: t('Discours Manifest'), slug: '/guide/manifest' },
|
||||
{ title: t('How it works'), slug: '/guide' },
|
||||
{ title: t('Dogma'), slug: '/guide/dogma' },
|
||||
{ title: t('Our principles'), slug: '/guide/principles' },
|
||||
{ title: t('How to write an article'), slug: '/how-to-write-a-good-article' }
|
||||
]
|
||||
|
@ -36,8 +36,8 @@ export const FooterView = () => {
|
|||
header: t('Participating'),
|
||||
items: [
|
||||
{ title: t('Suggest an idea'), slug: '/connect' },
|
||||
{ title: t('Become an author'), slug: '/create' },
|
||||
{ title: t('Support Discours'), slug: '/about/help' },
|
||||
{ title: t('Become an author'), slug: '/edit/new' },
|
||||
{ title: t('Support Discours'), slug: '/guide/support' },
|
||||
{
|
||||
title: t('Work with us'),
|
||||
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform'
|
||||
|
@ -47,10 +47,10 @@ export const FooterView = () => {
|
|||
{
|
||||
header: t('Sections'),
|
||||
items: [
|
||||
{ title: t('Authors'), slug: '/authors' },
|
||||
{ title: t('Authors'), slug: '/author' },
|
||||
{ title: t('Communities'), slug: '/community' },
|
||||
{ title: t('Partners'), slug: '/about/partners' },
|
||||
{ title: t('Special projects'), slug: '/about/projects' },
|
||||
{ title: t('Partners'), slug: '/guide/partners' },
|
||||
{ title: t('Special projects'), slug: '/guide/projects' },
|
||||
{
|
||||
title: lang() === 'ru' ? 'English' : 'Русский',
|
||||
slug: `?lng=${lang() === 'ru' ? 'en' : 'ru'}`,
|
||||
|
@ -97,7 +97,7 @@ export const FooterView = () => {
|
|||
'Independant magazine with an open horizontal cooperation about culture, science and society'
|
||||
)}
|
||||
. {t('Discours')} © 2015–{new Date().getFullYear()}{' '}
|
||||
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
||||
<a href="/guide/terms">{t('Terms of use')}</a>
|
||||
</div>
|
||||
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
||||
<For each={social}>
|
||||
|
|
|
@ -20,7 +20,7 @@ export default () => {
|
|||
)}
|
||||
/>
|
||||
<div class={styles.aboutDiscoursActions}>
|
||||
<a class="button" href="/create">
|
||||
<a class="button" href="/edit/new">
|
||||
{t('Create post')}
|
||||
</a>
|
||||
<a
|
||||
|
@ -34,7 +34,7 @@ export default () => {
|
|||
>
|
||||
{t('Join the community')}
|
||||
</a>
|
||||
<a class="button" href="/about/help">
|
||||
<a class="button" href="/guide/support">
|
||||
{t('Support us')}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -48,14 +48,14 @@ export const Beside = (props: Props) => {
|
|||
<h4>{props.title}</h4>
|
||||
|
||||
<Show when={props.wrapper === 'author'}>
|
||||
<a href="/authors">
|
||||
<a href="/author">
|
||||
{t('All authors')}
|
||||
<Icon name="arrow-right" class={styles.icon} />
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<Show when={props.wrapper === 'topic'}>
|
||||
<a href="/topics">
|
||||
<a href="/topic">
|
||||
{t('All topics')}
|
||||
<Icon name="arrow-right" class={styles.icon} />
|
||||
</a>
|
||||
|
|
|
@ -36,14 +36,14 @@ const data: PlaceholderData = {
|
|||
text: 'Placeholder feed',
|
||||
buttonLabelAuthor: 'Popular authors',
|
||||
buttonLabelFeed: 'Create own feed',
|
||||
href: '/authors?by=followers'
|
||||
href: '/author?by=followers'
|
||||
},
|
||||
feedCollaborations: {
|
||||
image: 'placeholder-experts.webp',
|
||||
header: 'Find collaborators',
|
||||
text: 'Placeholder feedCollaborations',
|
||||
buttonLabel: 'Find co-authors',
|
||||
href: '/authors?by=name'
|
||||
href: '/author?by=name'
|
||||
},
|
||||
feedDiscussions: {
|
||||
image: 'placeholder-discussions.webp',
|
||||
|
@ -58,7 +58,7 @@ const data: PlaceholderData = {
|
|||
header: 'Join our team of authors',
|
||||
text: 'Join our team of authors text',
|
||||
buttonLabel: 'Create post',
|
||||
href: '/create',
|
||||
href: '/edit/new',
|
||||
profileLinks: [
|
||||
{
|
||||
href: '/how-to-write-a-good-article',
|
||||
|
@ -74,11 +74,11 @@ const data: PlaceholderData = {
|
|||
href: '/feed?by=last_comment',
|
||||
profileLinks: [
|
||||
{
|
||||
href: '/about/discussion-rules',
|
||||
href: '/guide/debate',
|
||||
label: 'Discussion rules'
|
||||
},
|
||||
{
|
||||
href: '/about/discussion-rules#ban',
|
||||
href: '/guide/debate#ban',
|
||||
label: 'Block rules'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -82,7 +82,7 @@ export const AuthModal = () => {
|
|||
<p class={styles.disclaimer}>
|
||||
{t('By signing up you agree with our')}{' '}
|
||||
<a
|
||||
href="/about/terms-of-use"
|
||||
href="/guide/terms"
|
||||
onClick={() => {
|
||||
hideModal()
|
||||
}}
|
||||
|
|
|
@ -119,7 +119,7 @@ export const Header = (props: Props) => {
|
|||
requireAuthentication(() => {
|
||||
ev?.preventDefault()
|
||||
|
||||
navigate('/create')
|
||||
navigate('/edit/new')
|
||||
}, 'create')
|
||||
}
|
||||
|
||||
|
@ -216,13 +216,13 @@ export const Header = (props: Props) => {
|
|||
<h4>{t('Participating')}</h4>
|
||||
<ul class="view-switcher">
|
||||
<li>
|
||||
<A href="/create">{t('Create post')}</A>
|
||||
<A href="/edit/new">{t('Create post')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/connect">{t('Suggest an idea')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/about/help">{t('Support the project')}</A>
|
||||
<A href="/guide/support">{t('Support the project')}</A>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -280,8 +280,8 @@ export const Header = (props: Props) => {
|
|||
</select>
|
||||
|
||||
<div class={styles.mainNavigationAdditionalLinks}>
|
||||
<A href="/about/dogma">{t('Dogma')}</A>
|
||||
<A href="/about/discussion-rules">{t('Discussion rules')}</A>
|
||||
<A href="/guide/dogma">{t('Dogma')}</A>
|
||||
<A href="/guide/debate">{t('Discussion rules')}</A>
|
||||
<A href="/guide/principles">{t('Our principles')}</A>
|
||||
</div>
|
||||
|
||||
|
@ -342,22 +342,22 @@ export const Header = (props: Props) => {
|
|||
>
|
||||
<ul class="nodash">
|
||||
<li>
|
||||
<A href="/about/manifest">{t('Manifesto')}</A>
|
||||
<A href="/guide/manifest">{t('Manifesto')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/about/dogma">{t('Dogma')}</A>
|
||||
<A href="/guide/dogma">{t('Dogma')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/guide/principles">{t('Community Our principles')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/about/guide">{t('Platform Guide')}</A>
|
||||
<A href="/guide">{t('Platform Guide')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/about/manifest#participation">{t('Support us')}</A>
|
||||
<A href="/guide/manifest#participation">{t('Support us')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/about/help">{t('How to help')}</A>
|
||||
<A href="/guide/support">{t('How to help')}</A>
|
||||
</li>
|
||||
<li class={styles.rightItem}>
|
||||
<A href="/connect">
|
||||
|
@ -382,7 +382,7 @@ export const Header = (props: Props) => {
|
|||
<A href="/podcasts">{t('Podcasts')}</A>
|
||||
</li>
|
||||
<li class="item">
|
||||
<A href="/about/projects">{t('Special Projects')}</A>
|
||||
<A href="/guide/projects">{t('Special Projects')}</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/topic/interview">#{t('Interview')}</A>
|
||||
|
@ -406,7 +406,7 @@ export const Header = (props: Props) => {
|
|||
<A href="/topic/poetry">#{t('Poetry')}</A>
|
||||
</li>
|
||||
<li class={styles.rightItem}>
|
||||
<A href="/topics">
|
||||
<A href="/topic">
|
||||
{t('All topics')}
|
||||
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
|
||||
</A>
|
||||
|
@ -431,7 +431,7 @@ export const Header = (props: Props) => {
|
|||
)}
|
||||
</For>
|
||||
<li class={styles.rightItem}>
|
||||
<A href="/topics">
|
||||
<A href="/topic">
|
||||
{t('All topics')}
|
||||
<Icon name="arrow-right-black" class={clsx(styles.icon, styles.rightItemIcon)} />
|
||||
</A>
|
||||
|
|
|
@ -111,7 +111,7 @@ export const HeaderAuth = (props: Props) => {
|
|||
styles.userControlItemCreate
|
||||
)}
|
||||
>
|
||||
<A href={'/create'}>
|
||||
<A href={'/edit/new'}>
|
||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
|
@ -224,7 +224,7 @@ export const HeaderAuth = (props: Props) => {
|
|||
styles.userControlItemCreate
|
||||
)}
|
||||
>
|
||||
<A href={'/create'}>
|
||||
<A href={'/edit/new'}>
|
||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
|
|
|
@ -27,7 +27,7 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
|
|||
</A>
|
||||
</li>
|
||||
<li>
|
||||
<A class={styles.action} href={'/drafts'}>
|
||||
<A class={styles.action} href={'/edit'}>
|
||||
<Icon name="pencil-outline" class={styles.icon} />
|
||||
{t('Drafts')}
|
||||
</A>
|
||||
|
|
|
@ -20,7 +20,7 @@ export const Topics = () => {
|
|||
<A href="/podcasts">{t('Podcasts')}</A>
|
||||
</li>
|
||||
<li class={styles.item}>
|
||||
<A href="/about/projects">{t('Special Projects')}</A>
|
||||
<A href="/guide/projects">{t('Special Projects')}</A>
|
||||
</li>
|
||||
<li class={styles.item}>
|
||||
<A href="/topic/interview">#{t('Interview')}</A>
|
||||
|
|
|
@ -14,13 +14,11 @@ import {
|
|||
onMount
|
||||
} from 'solid-js'
|
||||
import { createStore } from 'solid-js/store'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useProfile } from '../../context/profile'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar, useUI } from '../../context/ui'
|
||||
import { InputMaybe, ProfileInput } from '../../graphql/schema/core.gen'
|
||||
import styles from '../../pages/profile/Settings.module.scss'
|
||||
import { clone } from '../../utils/clone'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
||||
|
@ -34,6 +32,7 @@ import { ImageCropper } from '../_shared/ImageCropper'
|
|||
import { Loading } from '../_shared/Loading'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
||||
import styles from './Settings.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor'))
|
||||
const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea'))
|
||||
|
|
|
@ -70,7 +70,7 @@ export const FullTopic = (props: Props) => {
|
|||
value={followed() ? t('Unfollow the topic') : t('Follow the topic')}
|
||||
class={styles.followControl}
|
||||
/>
|
||||
<a class={styles.writeControl} href={`/create/?topicId=${props.topic?.id}`}>
|
||||
<a class={styles.writeControl} href={`/edit/new/?topicId=${props.topic?.id}`}>
|
||||
{t('Write about the topic')}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -91,21 +91,21 @@ export const AllAuthors = (props: Props) => {
|
|||
['view-switcher__item--selected']: !searchParams?.by || searchParams?.by === 'shouts'
|
||||
})}
|
||||
>
|
||||
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
||||
<a href="/author?by=shouts">{t('By shouts')}</a>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
['view-switcher__item--selected']: searchParams?.by === 'followers'
|
||||
})}
|
||||
>
|
||||
<a href="/authors?by=followers">{t('By popularity')}</a>
|
||||
<a href="/author?by=followers">{t('By popularity')}</a>
|
||||
</li>
|
||||
<li
|
||||
class={clsx({
|
||||
['view-switcher__item--selected']: searchParams?.by === 'name'
|
||||
})}
|
||||
>
|
||||
<a href="/authors?by=name">{t('By name')}</a>
|
||||
<a href="/author?by=name">{t('By name')}</a>
|
||||
</li>
|
||||
<Show when={searchParams?.by === 'name'}>
|
||||
<li class="view-switcher__search">
|
||||
|
@ -125,7 +125,7 @@ export const AllAuthors = (props: Props) => {
|
|||
<li>
|
||||
<Show when={letter in byLetterFiltered()} fallback={letter}>
|
||||
<a
|
||||
href={`/authors?by=name#letter-${index()}`}
|
||||
href={`/author?by=name#letter-${index()}`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
scrollHandler(`letter-${index()}`)
|
||||
|
|
|
@ -80,13 +80,13 @@ export const AllTopics = (props: Props) => {
|
|||
|
||||
<ul class="view-switcher">
|
||||
<li classList={{ 'view-switcher__item--selected': searchParams?.by === 'shouts' }}>
|
||||
<A href="/topics?by=shouts">{t('By shouts')}</A>
|
||||
<A href="/topic?by=shouts">{t('By shouts')}</A>
|
||||
</li>
|
||||
<li classList={{ 'view-switcher__item--selected': searchParams?.by === 'authors' }}>
|
||||
<A href="/topics?by=authors">{t('By authors')}</A>
|
||||
<A href="/topic?by=authors">{t('By authors')}</A>
|
||||
</li>
|
||||
<li classList={{ 'view-switcher__item--selected': searchParams?.by === 'title' }}>
|
||||
<A href="/topics?by=title">{t('By title')}</A>
|
||||
<A href="/topic?by=title">{t('By title')}</A>
|
||||
</li>
|
||||
<Show when={searchParams?.by !== 'title'}>
|
||||
<li class="view-switcher__search">
|
||||
|
@ -131,7 +131,7 @@ export const AllTopics = (props: Props) => {
|
|||
<li>
|
||||
<Show when={letter in byLetter()} fallback={letter}>
|
||||
<A
|
||||
href={`/topics?by=title#letter-${index()}`}
|
||||
href={`/topic?by=title#letter-${index()}`}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
scrollHandler(`letter-${index()}`)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Meta, Title } from '@solidjs/meta'
|
||||
import { A, useLocation, useMatch } from '@solidjs/router'
|
||||
import { A, useLocation, useParams } from '@solidjs/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
||||
import { useAuthors } from '~/context/authors'
|
||||
|
@ -34,6 +34,7 @@ type Props = {
|
|||
authorSlug: string
|
||||
shouts?: Shout[]
|
||||
author?: Author
|
||||
selectedTab: string
|
||||
}
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 12
|
||||
|
@ -41,6 +42,7 @@ const LOAD_MORE_PAGE_SIZE = 9
|
|||
|
||||
export const AuthorView = (props: Props) => {
|
||||
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)
|
||||
|
@ -48,9 +50,6 @@ export const AuthorView = (props: Props) => {
|
|||
const { sortedFeed } = useFeed()
|
||||
const { modal, hideModal } = useUI()
|
||||
const loc = useLocation()
|
||||
const matchAuthor = useMatch(() => '/author')
|
||||
const matchComments = useMatch(() => '/author/:authorId/comments')
|
||||
const matchAbout = useMatch(() => '/author/:authorId/about')
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
const [isBioExpanded, setIsBioExpanded] = createSignal(false)
|
||||
const { loadAuthor, authorsEntities } = useAuthors()
|
||||
|
@ -189,20 +188,20 @@ export const AuthorView = (props: Props) => {
|
|||
<div class={clsx(styles.groupControls, 'row')}>
|
||||
<div class="col-md-16">
|
||||
<ul class="view-switcher">
|
||||
<li classList={{ 'view-switcher__item--selected': !!matchAuthor() }}>
|
||||
<li classList={{ 'view-switcher__item--selected': params.tab === '' }}>
|
||||
<A href={`/author/${props.authorSlug}`}>{t('Publications')}</A>
|
||||
<Show when={author()?.stat}>
|
||||
<span class="view-switcher__counter">{author()?.stat?.shouts || 0}</span>
|
||||
</Show>
|
||||
</li>
|
||||
<li classList={{ 'view-switcher__item--selected': !!matchComments() }}>
|
||||
<li classList={{ 'view-switcher__item--selected': params.tab === 'comment' }}>
|
||||
<A href={`/author/${props.authorSlug}/comments`}>{t('Comments')}</A>
|
||||
<Show when={author()?.stat}>
|
||||
<span class="view-switcher__counter">{author()?.stat?.comments || 0}</span>
|
||||
</Show>
|
||||
</li>
|
||||
<li classList={{ 'view-switcher__item--selected': !!matchAbout() }}>
|
||||
<A onClick={() => checkBioHeight()} href={`/author/${props.authorSlug}`}>
|
||||
<li classList={{ 'view-switcher__item--selected': params.tab === 'about' }}>
|
||||
<A onClick={() => checkBioHeight()} href={`/author/${props.authorSlug}/about`}>
|
||||
{t('About the author')}
|
||||
</A>
|
||||
</li>
|
||||
|
@ -222,7 +221,7 @@ export const AuthorView = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<Switch>
|
||||
<Match when={matchAbout()}>
|
||||
<Match when={params.tab === 'about'}>
|
||||
<div class="wide-container">
|
||||
<div class="row">
|
||||
<div class="col-md-20 col-lg-18">
|
||||
|
@ -246,7 +245,7 @@ export const AuthorView = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={matchComments()}>
|
||||
<Match when={params.tab === 'comments'}>
|
||||
<Show when={me()?.slug === props.authorSlug && !me().stat?.comments}>
|
||||
<div class="wide-container">
|
||||
<Placeholder type={loc?.pathname} mode="profile" />
|
||||
|
@ -272,7 +271,7 @@ export const AuthorView = (props: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={matchAuthor()}>
|
||||
<Match when={params.tab === ''}>
|
||||
<Show when={me()?.slug === props.authorSlug && !me().stat?.shouts}>
|
||||
<div class="wide-container">
|
||||
<Placeholder type={loc?.pathname} mode="profile" />
|
||||
|
|
|
@ -1,46 +1,19 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import getDraftsQuery from '~/graphql/query/core/articles-load-drafts'
|
||||
import { useEditorContext } from '../../../context/editor'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { Shout } from '../../../graphql/schema/core.gen'
|
||||
import { Draft } from '../../Draft'
|
||||
import { Loading } from '../../_shared/Loading'
|
||||
import { clsx } from 'clsx'
|
||||
import { For, Show, createMemo, createSignal } from 'solid-js'
|
||||
import { Draft } from '~/components/Draft'
|
||||
import { Loading } from '~/components/_shared/Loading'
|
||||
import { useEditorContext } from '~/context/editor'
|
||||
import { useSession } from '~/context/session'
|
||||
import { Shout } from '~/graphql/schema/core.gen'
|
||||
import styles from './DraftsView.module.scss'
|
||||
|
||||
export const DraftsView = () => {
|
||||
export const DraftsView = (props: { drafts: Shout[] }) => {
|
||||
const [drafts, setDrafts] = createSignal<Shout[]>(props.drafts || [])
|
||||
const { session } = useSession()
|
||||
const authorized = createMemo<boolean>(() => Boolean(session()?.access_token))
|
||||
const navigate = useNavigate()
|
||||
const [drafts, setDrafts] = createSignal<Shout[]>([])
|
||||
const [loading, setLoading] = createSignal(false)
|
||||
const { query } = useGraphQL()
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => Boolean(session()?.access_token),
|
||||
async (s) => {
|
||||
if (s) {
|
||||
setLoading(true)
|
||||
const resp = await query(getDraftsQuery, {}).toPromise()
|
||||
const result = resp?.data?.get_shouts_drafts
|
||||
if (result) {
|
||||
const { error, drafts: loadedDrafts } = result
|
||||
if (error) console.warn(error)
|
||||
if (loadedDrafts) setDrafts(loadedDrafts)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
const { publishShoutById, deleteShout } = useEditorContext()
|
||||
|
||||
const handleDraftDelete = async (shout: Shout) => {
|
||||
const success = await deleteShout(shout.id)
|
||||
if (success) {
|
||||
|
@ -55,7 +28,7 @@ export const DraftsView = () => {
|
|||
|
||||
return (
|
||||
<div class={clsx(styles.DraftsView)}>
|
||||
<Show when={!loading() && authorized()} fallback={<Loading />}>
|
||||
<Show when={authorized()} fallback={<Loading />}>
|
||||
<div class="wide-container">
|
||||
<div class="row">
|
||||
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
|
||||
|
|
204
src/components/Views/EditView/EditSettingsView.tsx
Normal file
204
src/components/Views/EditView/EditSettingsView.tsx
Normal file
|
@ -0,0 +1,204 @@
|
|||
import { clsx } from 'clsx'
|
||||
import deepEqual from 'fast-deep-equal'
|
||||
import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
import { createStore } from 'solid-js/store'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import getMyShoutQuery from '~/graphql/query/core/article-my'
|
||||
import { ShoutForm, useEditorContext } from '../../../context/editor'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import type { Shout, Topic } from '../../../graphql/schema/core.gen'
|
||||
import { clone } from '../../../utils/clone'
|
||||
import { isDesktop } from '../../../utils/media-query'
|
||||
import { Panel } from '../../Editor'
|
||||
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { TableOfContents } from '../../TableOfContents'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { InviteMembers } from '../../_shared/InviteMembers'
|
||||
import { PublishSettings } from '../PublishSettings'
|
||||
import styles from './EditView.module.scss'
|
||||
|
||||
type Props = {
|
||||
shout: Shout
|
||||
}
|
||||
|
||||
export const MAX_HEADER_LIMIT = 100
|
||||
export const EMPTY_TOPIC: Topic = {
|
||||
id: -1,
|
||||
slug: ''
|
||||
}
|
||||
|
||||
const AUTO_SAVE_DELAY = 3000
|
||||
|
||||
const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
||||
ev.preventDefault()
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
export const EditSettingsView = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const [isScrolled, setIsScrolled] = createSignal(false)
|
||||
const { query } = useGraphQL()
|
||||
const { form, setForm, saveDraft, saveDraftToLocalStorage, getDraftFromLocalStorage } = useEditorContext()
|
||||
const [shoutTopics, setShoutTopics] = createSignal<Topic[]>([])
|
||||
const [draft, setDraft] = createSignal()
|
||||
const [prevForm, setPrevForm] = createStore<ShoutForm>(clone(form))
|
||||
const [saving, setSaving] = createSignal(false)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.shout,
|
||||
(shout) => {
|
||||
if (shout) {
|
||||
console.debug(`[EditView] shout is loaded: ${shout}`)
|
||||
setShoutTopics((shout.topics as Topic[]) || [])
|
||||
const stored = getDraftFromLocalStorage(shout.id)
|
||||
if (stored) {
|
||||
console.info(`[EditView] got stored shout: ${stored}`)
|
||||
setDraft(stored)
|
||||
} else {
|
||||
if (!shout.slug) {
|
||||
console.warn(`[EditView] shout has no slug! ${shout}`)
|
||||
}
|
||||
const draftForm = {
|
||||
slug: shout.slug || '',
|
||||
shoutId: shout.id || 0,
|
||||
title: shout.title || '',
|
||||
lead: shout.lead || '',
|
||||
description: shout.description || '',
|
||||
subtitle: shout.subtitle || '',
|
||||
selectedTopics: (shoutTopics() || []) as Topic[],
|
||||
mainTopic: shoutTopics()[0] || '',
|
||||
body: shout.body || '',
|
||||
coverImageUrl: shout.cover || '',
|
||||
media: shout.media || '',
|
||||
layout: shout.layout
|
||||
}
|
||||
setForm((_) => draftForm)
|
||||
console.debug('draft from props data: ', draftForm)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
draft,
|
||||
(d) => {
|
||||
if (d) {
|
||||
const draftForm = Object.keys(d).length !== 0 ? d : { shoutId: props.shout.id }
|
||||
setForm(draftForm)
|
||||
console.debug('draft from localstorage: ', draftForm)
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => props.shout?.id,
|
||||
async (shoutId) => {
|
||||
if (shoutId) {
|
||||
const resp = await query(getMyShoutQuery, { shout_id: shoutId })
|
||||
const result = resp?.data?.get_my_shout
|
||||
if (result) {
|
||||
console.debug('[EditView] getMyShout result: ', result)
|
||||
const { shout: loadedShout, error } = result
|
||||
setDraft(loadedShout)
|
||||
console.debug('[EditView] loadedShout:', loadedShout)
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
onMount(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 0)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
onCleanup(() => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (!deepEqual(prevForm, form)) {
|
||||
event.returnValue = t(
|
||||
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
|
||||
})
|
||||
const [hasChanges, setHasChanges] = createSignal(false)
|
||||
const autoSave = async () => {
|
||||
console.log('autoSave called')
|
||||
if (hasChanges()) {
|
||||
console.debug('saving draft', form)
|
||||
setSaving(true)
|
||||
saveDraftToLocalStorage(form)
|
||||
await saveDraft(form)
|
||||
setPrevForm(clone(form))
|
||||
setSaving(false)
|
||||
setHasChanges(false)
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, autoSave)
|
||||
|
||||
onMount(() => {
|
||||
onCleanup(() => {
|
||||
debouncedAutoSave.cancel()
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class={styles.container}>
|
||||
<form>
|
||||
<div class="wide-container">
|
||||
<button
|
||||
class={clsx(styles.scrollTopButton, {
|
||||
[styles.visible]: isScrolled()
|
||||
})}
|
||||
onClick={handleScrollTopButtonClick}
|
||||
>
|
||||
<Icon name="up-button" class={styles.icon} />
|
||||
<span class={styles.scrollTopButtonLabel}>{t('Scroll up')}</span>
|
||||
</button>
|
||||
|
||||
<AutoSaveNotice active={saving()} />
|
||||
|
||||
<div class={styles.wrapperTableOfContents}>
|
||||
<Show when={isDesktop() && form.body}>
|
||||
<TableOfContents variant="editor" parentSelector="#editorBody" body={form.body} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<PublishSettings shoutId={props.shout.id} form={form} />
|
||||
<Show when={props.shout}>
|
||||
<Panel shoutId={props.shout.id} />
|
||||
</Show>
|
||||
|
||||
<Modal variant="medium" name="inviteCoauthors">
|
||||
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditSettingsView
|
|
@ -1,4 +1,3 @@
|
|||
import { useMatch } from '@solidjs/router'
|
||||
import { clsx } from 'clsx'
|
||||
import deepEqual from 'fast-deep-equal'
|
||||
import {
|
||||
|
@ -37,7 +36,6 @@ import { InviteMembers } from '../../_shared/InviteMembers'
|
|||
import { Loading } from '../../_shared/Loading'
|
||||
import { Popover } from '../../_shared/Popover'
|
||||
import { EditorSwiper } from '../../_shared/SolidSwiper'
|
||||
import { PublishSettings } from '../PublishSettings'
|
||||
import styles from './EditView.module.scss'
|
||||
|
||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||
|
@ -272,8 +270,6 @@ export const EditView = (props: Props) => {
|
|||
setIsLeadVisible(true)
|
||||
}
|
||||
|
||||
const matchEdit = useMatch(() => 'edit')
|
||||
const matchEditSettings = useMatch(() => 'edit/:shoutId/settings')
|
||||
return (
|
||||
<>
|
||||
<div class={styles.container}>
|
||||
|
@ -299,7 +295,7 @@ export const EditView = (props: Props) => {
|
|||
|
||||
<div class="row">
|
||||
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
|
||||
<Show when={matchEdit() && props.shout}>
|
||||
<Show when={props.shout}>
|
||||
<div class={styles.headingActions}>
|
||||
<Show when={!isSubtitleVisible() && props.shout.layout !== 'audio'}>
|
||||
<div class={styles.action} onClick={showSubtitleInput}>
|
||||
|
@ -455,7 +451,7 @@ export const EditView = (props: Props) => {
|
|||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={matchEdit() && form?.shoutId} fallback={<Loading />}>
|
||||
<Show when={form?.shoutId} fallback={<Loading />}>
|
||||
<Editor
|
||||
shoutId={form.shoutId}
|
||||
initialContent={form.body}
|
||||
|
@ -465,9 +461,6 @@ export const EditView = (props: Props) => {
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<Show when={matchEditSettings()}>
|
||||
<PublishSettings shoutId={props.shout.id} form={form} />
|
||||
</Show>
|
||||
<Show when={props.shout}>
|
||||
<Panel shoutId={props.shout.id} />
|
||||
</Show>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { A } from '@solidjs/router'
|
|||
import { useGraphQL } from '~/context/graphql'
|
||||
import getShoutsQuery from '~/graphql/query/core/articles-load-by'
|
||||
import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top'
|
||||
import { LayoutType } from '~/types/common'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '../../../graphql/schema/core.gen'
|
||||
import { getUnixtime } from '../../../utils/getServerDate'
|
||||
|
@ -16,8 +17,6 @@ import { Loading } from '../../_shared/Loading'
|
|||
import { ArticleCardSwiper } from '../../_shared/SolidSwiper/ArticleCardSwiper'
|
||||
import styles from './Expo.module.scss'
|
||||
|
||||
export type LayoutType = 'music' | 'literature' | 'video' | 'article' | 'image'
|
||||
|
||||
type Props = {
|
||||
shouts: Shout[]
|
||||
layout: LayoutType
|
||||
|
|
|
@ -182,7 +182,7 @@ export const FeedView = (props: FeedProps) => {
|
|||
<div class={styles.asideSection}>
|
||||
<div class={stylesBeside.besideColumnTitle}>
|
||||
<h4>{t('Popular authors')}</h4>
|
||||
<a href="/authors">
|
||||
<a href="/author">
|
||||
{t('All authors')}
|
||||
<Icon name="arrow-right" class={stylesBeside.icon} />
|
||||
</a>
|
||||
|
|
|
@ -2,13 +2,13 @@ export const ROUTES = {
|
|||
home: '/',
|
||||
inbox: '/inbox',
|
||||
connect: '/connect',
|
||||
create: '/create',
|
||||
create: '/edit/new',
|
||||
edit: '/edit/:shoutId',
|
||||
editSettings: '/edit/:shoutId/settings',
|
||||
drafts: '/drafts',
|
||||
drafts: '/edit',
|
||||
topics: '/topics',
|
||||
topic: '/topic/:slug',
|
||||
authors: '/authors',
|
||||
authors: '/author',
|
||||
author: '/author/:slug',
|
||||
authorComments: '/author/:slug/comments',
|
||||
authorAbout: '/author/:slug/about',
|
||||
|
@ -19,16 +19,16 @@ export const ROUTES = {
|
|||
feedBookmarks: '/feed/bookmarked',
|
||||
feedCollaborations: '/feed/coauthored',
|
||||
search: '/search/:q?',
|
||||
dogma: '/about/dogma',
|
||||
dogma: '/guide/dogma',
|
||||
discussionRules: '/about/discussion-rules',
|
||||
guide: '/about/guide',
|
||||
help: '/about/help',
|
||||
manifest: '/about/manifest',
|
||||
partners: '/about/partners',
|
||||
principles: '/about/principles',
|
||||
projects: '/about/projects',
|
||||
guide: '/guide',
|
||||
help: '/guide/support',
|
||||
manifest: '/guide/manifest',
|
||||
partners: '/guide/partners',
|
||||
principles: '/guide/principles',
|
||||
projects: '/guide/projects',
|
||||
termsOfUse: '/about/terms-of-use',
|
||||
thanks: '/about/thanks',
|
||||
thanks: '/guide/thanks',
|
||||
expo: '/expo/:layout?',
|
||||
profileSettings: '/profile/settings',
|
||||
profileSecurity: '/profile/security',
|
||||
|
|
|
@ -179,7 +179,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
|||
if (shout?.published_at) {
|
||||
navigate(`/article/${shout.slug}`)
|
||||
} else {
|
||||
navigate('/drafts')
|
||||
navigate('/edit')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[saveShout]', error)
|
||||
|
|
|
@ -5,6 +5,7 @@ import loadShoutsCoauthoredQuery from '~/graphql/query/core/articles-load-coauth
|
|||
import loadShoutsDiscussedQuery from '~/graphql/query/core/articles-load-discussed'
|
||||
import loadShoutsFollowedQuery from '~/graphql/query/core/articles-load-followed'
|
||||
import loadShoutsUnratedQuery from '~/graphql/query/core/articles-load-unrated'
|
||||
|
||||
import {
|
||||
QueryLoad_Shouts_FollowedArgs,
|
||||
QueryLoad_Shouts_UnratedArgs,
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.discussionRules)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.dogma)
|
|
@ -1,73 +0,0 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const DogmaPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Dogma')
|
||||
const description = t('Professional principles that the open editorial team follows in its work')
|
||||
|
||||
return (
|
||||
<StaticPage title={ogTitle}>
|
||||
<Meta name="descprition" content={description} />
|
||||
<Meta name="keywords" content={t('dogma keywords')} />
|
||||
<Meta name="og:type" content="article" />
|
||||
<Meta name="og:title" content={ogTitle} />
|
||||
<Meta name="og:image" content={ogImage} />
|
||||
<Meta name="twitter:image" content={ogImage} />
|
||||
<Meta name="og:description" content={description} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={ogTitle} />
|
||||
<Meta name="twitter:description" content={description} />
|
||||
|
||||
<h1>
|
||||
<span class="wrapped">Редакционные принципы</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Дискурс — журнал с открытой горизонтальной редакцией. Содержание журнала определяется прямым
|
||||
голосованием его авторов. Мы нередко занимаем различные позиции по разным проблемам, но
|
||||
придерживаемся общих профессиональных принципов:
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<b>На первое место ставим факты.</b> Наша задача — не судить, а наблюдать и непредвзято
|
||||
фиксировать происходящее. Все утверждения и выводы, которые мы делаем, подтверждаются фактами,
|
||||
цифрами, мнениями экспертов или ссылками на авторитетные источники.
|
||||
</li>
|
||||
<li>
|
||||
<b>Ответственно относимся к источникам.</b>
|
||||
Мы выбираем только надежные источники, проверяем информацию и рассказываем, как и откуда мы её
|
||||
получили, кроме случаев, когда это может нанести вред источникам. Тогда мы не раскроем их, даже в
|
||||
суде.
|
||||
</li>
|
||||
<li>
|
||||
<b>Выбираем компетентных и независимых экспертов</b>, понимая всю степень ответственности перед
|
||||
аудиторией.
|
||||
</li>
|
||||
<li>
|
||||
<b>
|
||||
Даем возможность высказаться всем заинтересованным сторонам, но не присоединяемся ни к чьему
|
||||
лагерю.
|
||||
</b>
|
||||
Ко всем событиям, компаниям и людям мы относимся с одинаковым скептицизмом.
|
||||
</li>
|
||||
<li>
|
||||
<b>Всегда исправляем ошибки, если мы их допустили.</b>
|
||||
Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку — отправьте{' '}
|
||||
<a href="/about/guide#editing">ремарку</a> автору или напишите нам на{' '}
|
||||
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
|
||||
welcome@discours.io
|
||||
</a>
|
||||
.
|
||||
</li>
|
||||
</ol>
|
||||
</StaticPage>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = DogmaPage
|
|
@ -1,247 +0,0 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const GuidePage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('How Discours works')
|
||||
const description = t('A guide to horizontal editorial: how an open journal works')
|
||||
|
||||
return (
|
||||
<StaticPage title={ogTitle}>
|
||||
<>
|
||||
<Meta name="descprition" content={description} />
|
||||
<Meta name="keywords" content={t('keywords')} />
|
||||
<Meta name="og:type" content="article" />
|
||||
<Meta name="og:title" content={ogTitle} />
|
||||
<Meta name="og:image" content={ogImage} />
|
||||
<Meta name="twitter:image" content={ogImage} />
|
||||
<Meta name="og:description" content={description} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={ogTitle} />
|
||||
<Meta name="twitter:description" content={description} />
|
||||
|
||||
<h1 id="about">
|
||||
<span class="wrapped">{ogTitle}</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Дискурс — независимый журнал о культуре, науке, искусстве и обществе с
|
||||
<a href="/about/manifest">открытой редакцией</a>. У нас нет главного редактора, инвестора
|
||||
и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий
|
||||
Дискурс основан на принципах прямой демократии: в нашем горизонтальном сообществе все
|
||||
редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
|
||||
</p>
|
||||
<h3 id="how-it-works">Как устроен сайт Дискурса</h3>
|
||||
<p>Дискурс состоит из четырех основных разделов:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topics">Темы</a>
|
||||
— у нас публикуются исследования, обзоры, эссе, интервью, репортажи,
|
||||
аналитика и другие материалы о культуре, науке, искусстве и обществе.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topic/art">Искусство</a>
|
||||
— здесь, например, представлены художественные произведения: литература, живопись,
|
||||
музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству, которое создают
|
||||
российские художники, писатели, режиссёры и музыканты.
|
||||
</p>
|
||||
</li>
|
||||
{/*
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topic/events">События</a> — в этом разделе
|
||||
публикуются самые важные, по мнению редакции, культурные
|
||||
события России — выставки, лекции, концерты, кинопоказы, фестивали,
|
||||
художественные и политические акции. Напишите нам
|
||||
на <a href="mailto:welcome@discours.io" target="_blank">почту</a>, если вы
|
||||
хотите разместить объявление. Мы делаем это
|
||||
на безвозмездной основе.
|
||||
</p>
|
||||
</li >
|
||||
<li>
|
||||
<p>
|
||||
<a href="/create" class="ng-scope" target="_blank">Редакция</a> —
|
||||
это внутренний раздел, где появляются новые материалы, которые присылают
|
||||
в редакцию. Здесь авторы обсуждают, редактируют и оценивают
|
||||
публикации, определяя таким образом содержание журнала.
|
||||
</p>
|
||||
</li>
|
||||
*/}
|
||||
</ul>
|
||||
<p>
|
||||
Материалы в Дискурсе объединяются по <b>темам</b>
|
||||
— ключевым словам, которые располагаются в конце материалов и связывают
|
||||
материалы по жанрам (например, <a href="/topic/interview">интервью</a>,{' '}
|
||||
<a href="/topic/reportage">репортажи</a>, <a href="/topic/essay">эссе</a>,{' '}
|
||||
<a href="/topic/likbez">ликбезы</a>
|
||||
), по тематике (<a href="/topic/cinema">кино</a>, <a href="/topic/philosophy">философия</a>,{' '}
|
||||
<a href="/topic/history">история</a>, <a href="/topic/absurdism">абсурдизм</a>,{' '}
|
||||
<a href="/topic/sex">секс</a> и т.д.) или в серии (как «
|
||||
<a href="/topic/zakony-mira">Законы мира</a>» или «
|
||||
<a href="/topic/za-liniey-mannergeyma">За линией Маннергейма</a>
|
||||
»). Темы объединяют сотни публикаций, помогают ориентироваться в журнале и следить
|
||||
за интересными материалами.
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h3 id="become-author">Как стать автором журнала</h3>
|
||||
<p>
|
||||
Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров,
|
||||
философов, ученых и других замечательных людей. Каждый может <a href="/create">прислать</a>{' '}
|
||||
свой материал в журнал. Формат и тематика не имеют значения, единственное, что
|
||||
важно — <a href="/how-to-write-a-good-article">хороший</a> ли материал. Если
|
||||
сообщество поддержит вашу публикацию, она выйдет в журнале и станет доступна тысячам
|
||||
наших читателей.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h3 id="voting">Как проходит голосование</h3>
|
||||
<p>
|
||||
Все присылаемые в Дискурс материалы попадают в
|
||||
<strong>«Редакцию»</strong>. Это внутренний раздел сайта, где участники сообщества
|
||||
решают, что будет опубликовано в Дискурсе. Как только работа получает одобрение как минимум
|
||||
пятерых авторов открытой редакции, она немедленно публикуется в журнале. Если же
|
||||
материал набирает более 20% голосов «против», он не выходит
|
||||
и может быть отправлен на доработку. Жестких сроков рассмотрения материалов у нас
|
||||
нет, иногда это занимает час, иногда месяц, обычно — несколько дней.
|
||||
</p>
|
||||
<section>
|
||||
<p>
|
||||
Как только сообщество поддержит публикацию, вы получите приглашение
|
||||
в интернет-редакцию и сможете голосовать за новые материалы.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<h3 id="editing">Как мы делаем тексты друг друга лучше</h3>
|
||||
<p>
|
||||
Дискурс — журнал с совместным редактированием. Совершенствовать тексты нам
|
||||
помогает <b>система ремарок</b>. Вы можете выделить часть текста в любой статье
|
||||
и оставить к ней замечание, вопрос или предложение — автор текста получит
|
||||
совет на почту и сможет его учесть. Так мы устраняем опечатки, неточности
|
||||
и советуем друг другу, как сделать тексты качественнее и интереснее.
|
||||
</p>
|
||||
<p>
|
||||
Среди участников сообщества есть профессиональные редакторы, которые помогают авторам делать
|
||||
тексты лучше. Если вашему материалу потребуется доработка, они помогут отредактировать текст,
|
||||
подобрать иллюстрации, придумать заголовок и красиво сверстать публикацию. Если
|
||||
вы хотите обсудить текст, прежде чем загрузить материал в интернет-редакцию —
|
||||
разместите его в google-документе, откройте доступ к редактированию по ссылке
|
||||
и напишите нам на
|
||||
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
|
||||
welcome@discours.io
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<p>
|
||||
Если у вас возникают трудности с тем, чтобы подобрать к своему материалу
|
||||
иллюстрации, тоже пишите на
|
||||
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
|
||||
почту
|
||||
</a>
|
||||
— наши коллеги-художники могут вам помочь{' '}
|
||||
<a href="/create?collab" target="_blank" rel="noreferrer">
|
||||
в режиме совместного редактирования
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<h3 id="perks">Что сообщество дает авторам</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Право определять, каким будет журнал</strong>. Дискурс — это общественная
|
||||
институция, созданная людьми и ради людей, функционирующая на условиях прямой
|
||||
демократии. Авторы публикуют статьи и художественные проекты, участвуют
|
||||
в обсуждениях, голосуют за работы коллег и таким образом вносят свой вклад
|
||||
в развитие проекта, определяя содержание и направление журнала.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Возможность обратиться к широкой аудитории</strong>. Дискурс читают десятки тысяч
|
||||
людей, и с каждым днем их становится больше.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Поддержка редакции</strong>. Дискурс предоставляет авторам аккредитацию
|
||||
на мероприятия, базу контактов, юридическую поддержку, ознакомление с книжными,
|
||||
кино- и музыкальными новинками до их выхода в свет. Если что-то
|
||||
из этого вам понадобится, пишите на почту{' '}
|
||||
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
|
||||
welcome@discours.io
|
||||
</a>
|
||||
— поможем.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Пресс-карты для корреспондентов</strong>. Три опубликованные статьи позволяют авторам
|
||||
Дискурса получить официальные удостоверения журналистов (пресс-карты) на следующий год.
|
||||
Пресс-карты удостоверяют, что вы журналист и можете пользоваться всеми теми правами,
|
||||
которые гарантирует Закон о СМИ. Кроме того, многие культурные институции (музеи, галереи
|
||||
и др.) предоставляют журналистам право свободного входа.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Помощь сотен специалистов в разных областях</strong>. В основе Дискурса
|
||||
лежит идея совместного редактирования. Участники редакционного сообщества —
|
||||
несколько сотен журналистов, исследователей, художников, литераторов из разных стран
|
||||
— изучают материалы друг друга до публикации и помогают сделать
|
||||
их качественнее и интереснее. Так, в редакции нередко складываются творческие
|
||||
союзы: например, авторов текстов и художников, создающих для них иллюстрации.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Пространство общения полное выдающихся людей</strong>. Дискурс — большое
|
||||
живое сообщество интеллектуалов, разбросанных по всему земному шару. Вступив
|
||||
в редакцию, вы сможете познакомиться со множеством интересных людей, которые
|
||||
определяют повестку завтрашнего дня, вдохновляют окружающих, создают новое и изучают
|
||||
старое, ищут знания и готовы ими делиться, чтобы менять мир в соответствии
|
||||
со своими идеалами.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="contacts">Как быть в курсе</h3>
|
||||
<p>
|
||||
За свежими публикациями Дискурса можно следить не только на сайте,
|
||||
но и на страницах в
|
||||
<a href="https://facebook.com/discoursio/" target="_blank" rel="noreferrer">
|
||||
Фейсбуке
|
||||
</a>
|
||||
,{' '}
|
||||
<a href="https://vk.com/discoursio" target="_blank" rel="noreferrer">
|
||||
ВКонтакте
|
||||
</a>{' '}
|
||||
и
|
||||
<a href="https://t.me/discoursio" target="_blank" rel="noreferrer">
|
||||
Телеграме
|
||||
</a>
|
||||
. А ещё раз в месяц мы отправляем <a href="#subscribe">почтовую рассылку</a>{' '}
|
||||
с дайджестом лучших материалов.
|
||||
</p>
|
||||
<p>
|
||||
Если вы хотите сотрудничать, что-то обсудить или предложить — пожалуйста, пишите
|
||||
на
|
||||
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
|
||||
welcome@discours.io
|
||||
</a>
|
||||
. Мы обязательно ответим.
|
||||
</p>
|
||||
</>
|
||||
</StaticPage>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = GuidePage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.guide)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.help)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.manifest)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.partners)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.principles)
|
|
@ -1,188 +0,0 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const PrinciplesPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Community Principles')
|
||||
const description = t('Community values and rules of engagement for the open editorial team')
|
||||
|
||||
return (
|
||||
<StaticPage title={ogTitle}>
|
||||
<Meta name="descprition" content={description} />
|
||||
<Meta name="keywords" content={t('principles keywords')} />
|
||||
<Meta name="og:type" content="article" />
|
||||
<Meta name="og:title" content={ogTitle} />
|
||||
<Meta name="og:image" content={ogImage} />
|
||||
<Meta name="twitter:image" content={ogImage} />
|
||||
<Meta name="og:description" content={description} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={ogTitle} />
|
||||
<Meta name="twitter:description" content={description} />
|
||||
|
||||
<h1>
|
||||
<span class="wrapped">{ogTitle}</span>
|
||||
</h1>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Горизонтальность</strong>. Мы все разные, и это классно. Вертикалей
|
||||
в мире достаточно, мы — горизонтальное сообщество и ценим наши различия,
|
||||
потому что знаем — в них наша сила. Благодаря разнообразию сотен голосов,
|
||||
усиливающих друг друга, в сообществе складывается неповторимая синергия, которая помогает
|
||||
вместе достигать большего.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Многоголосие</strong>. Мы ценим свободу слова и аргументированные мнения.
|
||||
Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию позиций, знаний
|
||||
и опыта, которые открывают более полную картину реальности.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Взаимопомощь</strong>. Мы помогаем друг другу, потому что хотим, чтобы в мире
|
||||
было еще больше хорошего. Обсуждая что-то, мы всегда интересуемся, чем можем помочь.
|
||||
В самиздате можно найти специалистов практически в любых сферах и получить
|
||||
поддержку от сотен людей. Благодаря коллективной экспертизе глобального сообщества
|
||||
в самиздате выходят крутейшие публикации, которыми можно вечно гордиться.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Взаимоуважение</strong>. Мы ценим, искренне уважаем друг друга и вместо
|
||||
борщевиков враждебности культивируем цветы добра, мира, знания и юмора. Нам некогда
|
||||
доказывать друг другу, кто круче. Гораздо приятнее сотрудничать, помогать и создавать
|
||||
что-то важное, интересное и полезное.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Созидание</strong>. Мы создаем, потому что любим создавать. Мы открыто делимся
|
||||
опытом, дарим идеи, обмениваемся мнениями и благодарим за критику, используя
|
||||
ее для совершенствования мастерства и саморазвития. Мы знаем, что мир
|
||||
не идеальное место, и делаем всё возможное, чтобы он стал лучше.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2 class="h2" id="participation">
|
||||
<span class="wrapped">Как у нас принято себя вести</span>
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
Открытая редакция объединяет сотни потрясающих людей со всего мира, которые делают крутейшие
|
||||
вещи. Это пространство, где доверяют, вдохновляют, исследуют и создают новое вместе. Поскольку
|
||||
все в сообществе очень разные, как-то мы собрались и решили зафиксировать базовые
|
||||
ценности открытой редакции, а заодно придумали универсальные правила взаимодействия, чтобы
|
||||
общение было не только плодотворным, но и приятным для всех участников сообщества.
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>
|
||||
<strong>Действуем, помогаем и делимся</strong>. В редакции мы создаем свои
|
||||
проекты и помогаем другим создавать свои — советами, делом, участием,
|
||||
вовлеченностью. Мы открыто делимся опытом, мнениями и идеями, потому что ценим силу
|
||||
сотрудничества и знаем, что идеи реализуются скорее, лучше и веселее, если над ними
|
||||
трудиться сообща.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Общаемся дружелюбно</strong>. Помните, по ту сторону монитора находятся
|
||||
реальные люди. Неуважение ранит других так же, как ранило бы вас самих. Поэтому
|
||||
не стоит кричать (даже капслоком), заполнять эфир желчью и бросаться
|
||||
грубостями — так вы рискуете не только растерять доверие окружающих,
|
||||
но и остаться непонятым.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Критикуем и реагируем конструктивно</strong>. Самиздат про то, чтобы
|
||||
разбираться в сложных вещах всем сообществом, поэтому мы тактично и без агрессии
|
||||
делимся мнениями, стараясь убедительно аргументировать позиции. И с благодарностью
|
||||
принимаем критику, используя ее для улучшения наших проектов. Мы верим, что каждый
|
||||
участник сообщества имеет добрые намерения, и придерживаемся принципов доброжелательной
|
||||
критики, стараемся делиться советами — лучшим средством для самосовершенствования.
|
||||
Обоснованная критика помогает и адресату, и всем участникам сообщества досконально
|
||||
изучить тему и глубже разобраться в проблеме.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Решаем трудности не агрессией, а диалогом</strong>. Обесценивать мнения
|
||||
и оскорблять других людей только потому, что вы с ними
|
||||
не согласны, — не лучший способ донести свою точку зрения. Конечно, важно
|
||||
высказаться, если вас что-то не устраивает и откровенно бесит. Но прежде чем
|
||||
сжигать оппонента гневом, попробуйте понять, почему этот «нехороший человек» так
|
||||
поступает. Возможно, аргументы собеседника окажутся убедительными или вам удастся изменить его
|
||||
мнение. В любом случае конфликты решаются в диалогах и проходят,
|
||||
а налаженное взаимопонимание останется надолго.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Не переходим на личности — это признак токсичности</strong>. Всегда
|
||||
мудрее обсуждать точку зрения человека, а не его самого, даже если он вам
|
||||
не импонирует. Предвзятое отношение ограничивает кругозор, добавляет преждевременные
|
||||
морщины и не помогает окружающим стать лучше. Вежливость
|
||||
и взаимоуважение — краеугольная основа вдумчивых и осмысленных дискуссий.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Благодарим за помощь</strong>. Благодарите коллег даже за самые,
|
||||
казалось бы, простые вещи. «Спасибо» не зря называют волшебным
|
||||
словом — на искренней благодарности держится любое подлинное сотрудничество.
|
||||
Поддержка воодушевляет на новые подвиги и напоминает, что мир делают прекрасным
|
||||
не машины, а живые люди.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Даем еще один шанс</strong>. Все совершают ошибки, и за один проступок
|
||||
не стоит вычеркивать людей из жизни. Ошибки нужны, чтобы на них учиться
|
||||
и делать выводы. Однако если многократно и систематически нарушать правила сообщества,
|
||||
наверняка можно заслужить минусы в карму от других участников и потерять доступ
|
||||
к сообществу.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Вместе создаем идеальную среду общения</strong>. Открытая редакция — это
|
||||
утопическое пространство обогащающей и осмысленной коммуникации. Атмосфера горизонтального
|
||||
сообщества складывается из действий каждого, поэтому мы действуем так, чтобы
|
||||
способствовать сотворчеству, коллективному познанию и развитию самиздата и нашей
|
||||
альтернативной интеллектуальной медиасреды.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<p>
|
||||
<strong>Помним, что всё в сообществе зависит от нас</strong>. Если нам чего-то
|
||||
не хватает, мы начинаем действовать — рассказываем об идее, находим
|
||||
единомышленников, готовим и запускаем проект. Так в сообществе становится на одну
|
||||
крутую активность больше. Так появилось наше сообщество. Так появился самиздат и все
|
||||
проекты открытой редакции. Чтобы в сообществе случилось что-то прекрасное, достаточно
|
||||
просто положить этому начало.
|
||||
</p>
|
||||
</li>
|
||||
</ol>
|
||||
</StaticPage>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = PrinciplesPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.projects)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.termsOfUse)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.thanks)
|
|
@ -1,95 +0,0 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const ThanksPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Thank you')
|
||||
const description = t(
|
||||
'Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!'
|
||||
)
|
||||
|
||||
return (
|
||||
<StaticPage title={ogTitle}>
|
||||
<Meta name="descprition" content={description} />
|
||||
<Meta name="keywords" content={t('keywords')} />
|
||||
<Meta name="og:type" content="article" />
|
||||
<Meta name="og:title" content={ogTitle} />
|
||||
<Meta name="og:image" content={ogImage} />
|
||||
<Meta name="twitter:image" content={ogImage} />
|
||||
<Meta name="og:description" content={description} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={ogTitle} />
|
||||
<Meta name="twitter:description" content={description} />
|
||||
|
||||
<h1>
|
||||
<span class="wrapped">{ogTitle}</span>
|
||||
</h1>
|
||||
{/*
|
||||
<h3><b>Команда</b></h3>
|
||||
<p>
|
||||
Константин Ворович — исполнительный директор,
|
||||
<a href="mailto:welcome@discours.io" target="_blank"
|
||||
>welcome@discours.io</a
|
||||
><br />
|
||||
Александр Гусев — технический архитектор,
|
||||
<a href="mailto:services@discours.io" target="_blank"
|
||||
>services@discours.io</a
|
||||
><br />
|
||||
Екатерина Ильина — шеф-редактор проекта,
|
||||
<a href="mailto:letter@discours.io" target="_blank"
|
||||
>letter@discours.io</a
|
||||
><br />
|
||||
Яна Климова — редактор сайта и соцсетей,
|
||||
<a href="mailto:letter@discours.io" target="_blank"
|
||||
>letter@discours.io</a
|
||||
><br />
|
||||
Николай Носачевский — голос и душа подкаста,
|
||||
<a href="mailto:podcast@discours.io" target="_blank"
|
||||
>podcast@discours.io</a
|
||||
>
|
||||
</p>
|
||||
*/}
|
||||
<h3>Неоценимый вклад в Дискурс внесли и вносят</h3>
|
||||
<p>
|
||||
Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин, Павел
|
||||
Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик, Вячеслав Еременко,
|
||||
Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр Коренков, Ирэна Лесневская,
|
||||
Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений Медведев, Павел Никулин, Николай
|
||||
Носачевский, Андрей Орловский, Михаил Панин, Антон Панов, Павел Пепперштейн, Любовь Покровская, Илья
|
||||
Розовский, Денис Светличный, Павел Соколов, Сергей Стрельников, Глеб Струнников, Николай Тарковский,
|
||||
Кирилл Филимонов, Алексей Хапов, Екатерина Харитонова
|
||||
</p>
|
||||
<h3>Авторы</h3>
|
||||
<p>
|
||||
Мы безмерно благодарны{' '}
|
||||
<a href="/authors" target="_blank" rel="noopener noreferrer">
|
||||
каждому автору
|
||||
</a>{' '}
|
||||
за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью
|
||||
и основным источником мотивации, бескорыстная помощь и основанный на энтузиазме труд
|
||||
бесценны. Именно вы своим трудом каждый день делаете Дискурс таким, какой он есть.
|
||||
</p>
|
||||
<h3>Иллюстраторы</h3>
|
||||
<p>
|
||||
Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова, Мария
|
||||
Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья Diliago, Антон
|
||||
Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита Поздняков, Матвей
|
||||
Сапегин, Татьяна Сафонова, Виктория Шибаева
|
||||
</p>
|
||||
<h3>Меценаты</h3>
|
||||
<p>
|
||||
Дискурс существует исключительно на пожертвования читателей. Мы бесконечно признательны
|
||||
всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент журнала. Благодаря
|
||||
вам мы развиваем платформу качественной журналистики, которая помогает самым разным авторам
|
||||
быть услышанными. Стать нашим меценатом и подписаться на ежемесячную поддержку проекта
|
||||
можно <a href="/about/help">здесь</a>.
|
||||
</p>
|
||||
</StaticPage>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = ThanksPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.authors)
|
|
@ -1,26 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { PAGE_SIZE } from '../components/Views/AllTopics/AllTopics'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (_pageContext: PageContext) => {
|
||||
const allAuthors = await apiClient.getAllAuthors()
|
||||
const topWritingAuthors = await apiClient.loadAuthorsBy({
|
||||
by: { order: 'shouts' },
|
||||
limit: PAGE_SIZE,
|
||||
offset: 0
|
||||
})
|
||||
const topFollowedAuthors = await apiClient.loadAuthorsBy({
|
||||
by: { order: 'followers' },
|
||||
limit: PAGE_SIZE,
|
||||
offset: 0
|
||||
})
|
||||
const pageProps: PageProps = { allAuthors, seo: { title: '' }, topWritingAuthors, topFollowedAuthors }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { AllAuthors } from '../components/Views/AllAuthors/'
|
||||
import { PAGE_SIZE } from '../components/Views/AllTopics/AllTopics'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { loadAllAuthors, loadAuthors } from '../stores/zine/authors'
|
||||
|
||||
export const AllAuthorsPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(
|
||||
Boolean(props.allAuthors && props.topFollowedAuthors && props.topWritingAuthors)
|
||||
)
|
||||
|
||||
const { t } = useLocalize()
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadAllAuthors()
|
||||
await loadAuthors({ by: { order: 'shouts' }, limit: PAGE_SIZE, offset: 0 })
|
||||
await loadAuthors({ by: { order: 'followers' }, limit: PAGE_SIZE, offset: 0 })
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Authors')}>
|
||||
<AllAuthors
|
||||
isLoaded={isLoaded()}
|
||||
authors={props.allAuthors}
|
||||
topWritingAuthors={props.topWritingAuthors}
|
||||
topFollowedAuthors={props.topFollowedAuthors}
|
||||
/>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = AllAuthorsPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.topics)
|
|
@ -1,16 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (_pageContext: PageContext) => {
|
||||
const allTopics = await apiClient.getAllTopics()
|
||||
|
||||
const pageProps: PageProps = { allTopics, seo: { title: '' } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import { AllTopics } from '../components/Views/AllTopics'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { useTopics } from '../context/topics'
|
||||
|
||||
export const AllTopicsPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const { sortedTopics } = useTopics()
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Themes and plots')}>
|
||||
<AllTopics isLoaded={!!sortedTopics()?.length} topics={sortedTopics()} />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = AllTopicsPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.article)
|
|
@ -1,23 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { render } from 'vike/abort'
|
||||
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||
const { slug } = pageContext.routeParams
|
||||
const article = await apiClient.getShoutBySlug(slug)
|
||||
|
||||
if (!article) {
|
||||
throw render(404)
|
||||
}
|
||||
|
||||
const pageProps: PageProps = { article, seo: { title: article.title } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import type { Shout } from '../graphql/schema/core.gen'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { redirectPage } from '@nanostores/router'
|
||||
import { Show, createMemo, createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { FullArticle } from '../components/Article/FullArticle'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { router, useRouter } from '../stores/router'
|
||||
import { loadShout, useArticlesStore } from '../stores/zine/articles'
|
||||
import { setPageLoadManagerPromise } from '../utils/pageLoadManager'
|
||||
|
||||
export const ArticlePage = (props: PageProps) => {
|
||||
const shouts = props.article ? [props.article] : []
|
||||
const { page } = useRouter()
|
||||
|
||||
const slug = createMemo(() => page().params['slug'] as string)
|
||||
|
||||
const { articleEntities } = useArticlesStore({
|
||||
shouts
|
||||
})
|
||||
|
||||
const article = createMemo<Shout>(() => articleEntities()[slug()])
|
||||
|
||||
onMount(async () => {
|
||||
if (!article()?.body) {
|
||||
const loadShoutPromise = loadShout(slug())
|
||||
setPageLoadManagerPromise(loadShoutPromise)
|
||||
await loadShoutPromise
|
||||
|
||||
if (!article()) {
|
||||
redirectPage(router, 'fourOuFour')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
try {
|
||||
// document.body.appendChild(script)
|
||||
console.debug('TODO: connect ga')
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
})
|
||||
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
title={props.seo?.title}
|
||||
headerTitle={article()?.title || ''}
|
||||
slug={article()?.slug}
|
||||
articleBody={article()?.body}
|
||||
cover={article()?.cover}
|
||||
scrollToComments={(value) => {
|
||||
setScrollToComments(value)
|
||||
}}
|
||||
>
|
||||
<ReactionsProvider>
|
||||
<Show when={Boolean(article())} fallback={<Loading />}>
|
||||
<FullArticle article={article()} scrollToComments={scrollToComments()} />
|
||||
</Show>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = ArticlePage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.author)
|
|
@ -1,29 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { render } from 'vike/abort'
|
||||
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||
const { slug } = pageContext.routeParams
|
||||
console.debug(`[author.page] detected author in route: @${slug}`)
|
||||
const author = await apiClient.getAuthor({ slug })
|
||||
|
||||
if (!author) {
|
||||
throw render(404)
|
||||
}
|
||||
|
||||
const authorShouts = await apiClient.getShouts({
|
||||
filters: { author: slug, featured: false },
|
||||
limit: PRERENDERED_ARTICLES_COUNT
|
||||
})
|
||||
const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from 'solid-js'
|
||||
|
||||
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
import { loadAuthor } from '../stores/zine/authors'
|
||||
|
||||
export const AuthorPage = (props: PageProps) => {
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const slug = createMemo(() => page().params['slug'] as string)
|
||||
|
||||
const [isLoaded, setIsLoaded] = createSignal(
|
||||
Boolean(props.authorShouts) && Boolean(props.author) && props.author.slug === slug()
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on(slug, async (s) => {
|
||||
if (s) {
|
||||
setIsLoaded(false)
|
||||
resetSortedArticles()
|
||||
await loadShouts({
|
||||
filters: { author: s, featured: false },
|
||||
limit: PRERENDERED_ARTICLES_COUNT
|
||||
})
|
||||
await loadAuthor({ slug: s })
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageLayout title={props.seo?.title || t('Discours')}>
|
||||
<ReactionsProvider>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<AuthorView authorSlug={slug()} />
|
||||
</Show>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = AuthorPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.authorAbout)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.authorComments)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.connect)
|
|
@ -1,107 +0,0 @@
|
|||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
|
||||
export const ConnectPage = () => {
|
||||
const [state, setState] = createSignal<'initial' | 'loading' | 'success' | 'error'>('initial')
|
||||
|
||||
const formRef: { current: HTMLFormElement } = { current: null }
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
setState('loading')
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-spread
|
||||
const postData = Array.from(formRef.current.elements).reduce(
|
||||
(acc, element) => {
|
||||
const formField = element as unknown as { name: string; value: string }
|
||||
if (formField.name) {
|
||||
acc[formField.name] = formField.value
|
||||
}
|
||||
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, string>
|
||||
)
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
}
|
||||
|
||||
const result = await fetch('/api/feedback', requestOptions)
|
||||
|
||||
if (!result.ok) {
|
||||
console.error('[handleFormSubmit]', result)
|
||||
setState('error')
|
||||
return
|
||||
}
|
||||
|
||||
setState('success')
|
||||
window.scrollTo({
|
||||
top: 0
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: l10n
|
||||
return (
|
||||
<PageLayout title="Предложить идею">
|
||||
<article class="wide-container container--static-page">
|
||||
<div class="row">
|
||||
<div class="col-sm-20 col-md-16 col-lg-14 col-xl-12 offset-md-5">
|
||||
<Show when={state() === 'loading' || state() === 'initial' || state() === 'error'}>
|
||||
<h1>
|
||||
<span class="wrapped">Предложить идею</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите
|
||||
нам скорее! Если укажете свою почту, мы обязательно ответим.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleFormSubmit} ref={(el) => (formRef.current = el)}>
|
||||
<div class="pretty-form__item">
|
||||
<select name="subject" disabled={state() === 'loading'}>
|
||||
<option value="Сотрудничество" selected>
|
||||
Сотрудничество
|
||||
</option>
|
||||
<option value="Посоветовать тему">Посоветовать тему</option>
|
||||
<option value="Сообщить об ошибке">Сообщить об ошибке</option>
|
||||
<option value="Предложить проект">Предложить проект</option>
|
||||
<option value="Волонтерство">Волонтерство</option>
|
||||
<option value="Другое">Другое</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="pretty-form__item">
|
||||
<input
|
||||
type="email"
|
||||
name="contact"
|
||||
placeholder="Email для обратной связи"
|
||||
disabled={state() === 'loading'}
|
||||
/>
|
||||
<label for="contact-email">Email для обратной связи</label>
|
||||
</div>
|
||||
<div class="pretty-form__item">
|
||||
<textarea name="message" placeholder="Текст сообщения" disabled={state() === 'loading'} />
|
||||
<label for="message">Текст сообщения</label>
|
||||
</div>
|
||||
<button class="button" disabled={state() === 'loading'} type="submit">
|
||||
Отправить письмо
|
||||
</button>
|
||||
</form>
|
||||
</Show>
|
||||
<Show when={state() === 'error'}>
|
||||
<br />
|
||||
Ошибка. Что-то пошло не так. Пожалуйста, попробуйте написать ещё раз
|
||||
</Show>
|
||||
<Show when={state() === 'success'}>Спасибо за письмо!</Show>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = ConnectPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.create)
|
|
@ -1,6 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute as gsr } from '../utils/getServerRoute'
|
||||
|
||||
const getServerRoute = () => gsr(ROUTES.drafts)
|
||||
|
||||
export { getServerRoute }
|
|
@ -1,15 +0,0 @@
|
|||
import { DraftsView } from '../components/Views/DraftsView'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
|
||||
export const DraftsPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Drafts')}>
|
||||
<DraftsView />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = DraftsPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.edit)
|
|
@ -1,102 +0,0 @@
|
|||
import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on } from 'solid-js'
|
||||
|
||||
import { AuthGuard } from '../components/AuthGuard'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { useSession } from '../context/session'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
import { Shout } from '../graphql/schema/core.gen'
|
||||
import { router } from '../stores/router'
|
||||
|
||||
import { redirectPage } from '@nanostores/router'
|
||||
import { useSnackbar } from '../context/snackbar'
|
||||
import { LayoutType } from './types'
|
||||
|
||||
const EditView = lazy(() => import('../components/Views/EditView/EditView'))
|
||||
|
||||
const getContentTypeTitle = (layout: LayoutType) => {
|
||||
switch (layout) {
|
||||
case 'audio':
|
||||
return 'Publish Album'
|
||||
case 'image':
|
||||
return 'Create gallery'
|
||||
case 'video':
|
||||
return 'Create video'
|
||||
case 'literature':
|
||||
return 'New literary work'
|
||||
default:
|
||||
return 'Write an article'
|
||||
}
|
||||
}
|
||||
|
||||
export const EditPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const { session } = useSession()
|
||||
const snackbar = useSnackbar()
|
||||
|
||||
const fail = async (error: string) => {
|
||||
console.error(error)
|
||||
const errorMessage = error === 'forbidden' ? "You can't edit this post" : error
|
||||
await snackbar?.showSnackbar({ type: 'error', body: t(errorMessage) })
|
||||
redirectPage(router, 'drafts')
|
||||
}
|
||||
|
||||
const [shoutId, setShoutId] = createSignal<number>(0)
|
||||
const [shout, setShout] = createSignal<Shout>()
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => window?.location.pathname,
|
||||
(p) => {
|
||||
if (p) {
|
||||
console.debug(p)
|
||||
const shoutId = p.split('/').pop()
|
||||
if (shoutId) {
|
||||
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
|
||||
console.debug(`editing shout ${shoutIdFromUrl}`)
|
||||
if (shoutIdFromUrl) {
|
||||
setShoutId(shoutIdFromUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ defer: true }
|
||||
)
|
||||
)
|
||||
|
||||
createEffect(
|
||||
on([session, shout, shoutId], async ([ses, sh, shid]) => {
|
||||
if (ses?.user && !sh && shid) {
|
||||
const { shout: loadedShout, error } = await apiClient.getMyShout(shid)
|
||||
if (error) {
|
||||
fail(error)
|
||||
} else {
|
||||
setShout(loadedShout)
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ defer: true }
|
||||
)
|
||||
|
||||
const title = createMemo(() => {
|
||||
if (!shout()) {
|
||||
return t('Create post')
|
||||
}
|
||||
return t(getContentTypeTitle(shout()?.layout as LayoutType))
|
||||
})
|
||||
|
||||
return (
|
||||
<PageLayout title={title()}>
|
||||
<AuthGuard>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Show when={shout()} fallback={<Loading />}>
|
||||
<EditView shout={shout() as Shout} />
|
||||
</Show>
|
||||
</Suspense>
|
||||
</AuthGuard>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = EditPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.editSettings)
|
|
@ -1,5 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
// yes, it's a hack
|
||||
export default getServerRoute(ROUTES.expo.replace(':layout?', '*'))
|
|
@ -1,22 +0,0 @@
|
|||
import type { PageContext } from '../../renderer/types'
|
||||
import type { PageProps } from '../types'
|
||||
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Expo/Expo'
|
||||
import { apiClient } from '../../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||
const { layout } = pageContext.routeParams
|
||||
|
||||
const expoShouts = await apiClient.getShouts({
|
||||
filters: { layouts: layout ? [layout] : ['audio', 'video', 'literature', 'image'] },
|
||||
limit: PRERENDERED_ARTICLES_COUNT
|
||||
})
|
||||
|
||||
const pageProps: PageProps = { expoShouts, seo: { title: '' } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import type { PageProps } from '../types'
|
||||
|
||||
import { createEffect, createMemo, on } from 'solid-js'
|
||||
|
||||
import { Topics } from '../../components/Nav/Topics'
|
||||
import { Expo } from '../../components/Views/Expo'
|
||||
import { PageLayout } from '../../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { LayoutType } from '../types'
|
||||
|
||||
export const ExpoPage = (props: PageProps) => {
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const layout = createMemo(() => page().params['layout'] as LayoutType)
|
||||
const title = createMemo(() => {
|
||||
switch (layout()) {
|
||||
case 'audio': {
|
||||
return t('Audio')
|
||||
}
|
||||
case 'video': {
|
||||
return t('Video')
|
||||
}
|
||||
case 'image': {
|
||||
return t('Artworks')
|
||||
}
|
||||
case 'literature': {
|
||||
return t('Literature')
|
||||
}
|
||||
default: {
|
||||
return t('Art')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(on(title, (t) => (document.title = t), { defer: true }))
|
||||
|
||||
return (
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={title()}>
|
||||
<Topics />
|
||||
<Expo shouts={props.expoShouts} layout={layout()} />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = ExpoPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.feed)
|
|
@ -1,40 +0,0 @@
|
|||
import { createEffect, on, onCleanup } from 'solid-js'
|
||||
|
||||
import { Feed } from '../components/Views/Feed'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { LoadShoutsOptions } from '../graphql/schema/core.gen'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadMyFeed, loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
|
||||
const handleFeedLoadShouts = (options: LoadShoutsOptions) => {
|
||||
return loadShouts({
|
||||
...options,
|
||||
filters: {
|
||||
featured: false,
|
||||
...options.filters
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleMyFeedLoadShouts = (options: LoadShoutsOptions) => {
|
||||
return loadMyFeed(options)
|
||||
}
|
||||
|
||||
export const FeedPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
createEffect(on(page, (_) => resetSortedArticles(), { defer: true }))
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Feed')}>
|
||||
<ReactionsProvider>
|
||||
<Feed loadShouts={page().route === 'feedMy' ? handleMyFeedLoadShouts : handleFeedLoadShouts} />
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = FeedPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.feedMy)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.fourOuFour)
|
|
@ -1,15 +0,0 @@
|
|||
import { FourOuFourView } from '../components/Views/FourOuFour'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
|
||||
export const FourOuFourPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
return (
|
||||
<PageLayout isHeaderFixed={false} hideFooter={true} title={t('Nothing is here')}>
|
||||
<FourOuFourView />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = FourOuFourPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.inbox)
|
|
@ -1,32 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { createSignal, onMount } from 'solid-js'
|
||||
|
||||
import { InboxView } from '../components/Views/Inbox/Inbox'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { ShowOnlyOnClient } from '../components/_shared/ShowOnlyOnClient'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { loadAllAuthors } from '../stores/zine/authors'
|
||||
|
||||
export const InboxPage = (props: PageProps) => {
|
||||
const { t } = useLocalize()
|
||||
const [isLoaded, setIsLoaded] = createSignal<boolean>(Boolean(props.allAuthors))
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadAllAuthors()
|
||||
setIsLoaded(true)
|
||||
})
|
||||
return (
|
||||
<PageLayout hideFooter={true} title={t('Inbox')}>
|
||||
<ShowOnlyOnClient>
|
||||
<InboxView isLoaded={isLoaded()} authors={props.allAuthors} />
|
||||
</ShowOnlyOnClient>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = InboxPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.home)
|
|
@ -1,20 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (_pageContext: PageContext) => {
|
||||
const homeShouts = await apiClient.getShouts({
|
||||
filters: { featured: true },
|
||||
limit: PRERENDERED_ARTICLES_COUNT
|
||||
})
|
||||
|
||||
const pageProps: PageProps = { homeShouts, seo: { title: '' } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { Show, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
|
||||
import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
|
||||
export const HomePage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeShouts))
|
||||
const { t } = useLocalize()
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadShouts({ filters: { featured: true }, limit: PRERENDERED_ARTICLES_COUNT })
|
||||
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageLayout withPadding={true} title={t('Discours')}>
|
||||
<ReactionsProvider>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<HomeView shouts={props.homeShouts || []} />
|
||||
</Show>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = HomePage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.profileSecurity)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.profileSettings)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../../stores/router'
|
||||
import { getServerRoute } from '../../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.profileSubscriptions)
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.search)
|
|
@ -1,17 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
import { SearchResult } from '../graphql/schema/core.gen'
|
||||
|
||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||
const { q: text } = pageContext.routeParams
|
||||
const searchResults: SearchResult[] = await apiClient.getShoutsSearch({ text, limit: 50 })
|
||||
const pageProps: PageProps = { searchResults, seo: { title: '' } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { Show, createEffect, createMemo, createSignal, onCleanup } from 'solid-js'
|
||||
|
||||
import { SearchView } from '../components/Views/Search'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShoutsSearch, resetSortedArticles } from '../stores/zine/articles'
|
||||
|
||||
export const SearchPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.searchResults))
|
||||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const q = createMemo(() => page().params['q'] as string)
|
||||
|
||||
createEffect(async () => {
|
||||
if (isLoaded()) return
|
||||
if (q() && window) {
|
||||
const text = q() || window.location.href.split('/').pop()
|
||||
// TODO: pagination, load more
|
||||
await loadShoutsSearch({ text, limit: 50, offset: 0 })
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Search')}>
|
||||
<ReactionsProvider>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<SearchView results={props.searchResults || []} query={props.searchQuery} />
|
||||
</Show>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = SearchPage
|
|
@ -1,4 +0,0 @@
|
|||
import { ROUTES } from '../stores/router'
|
||||
import { getServerRoute } from '../utils/getServerRoute'
|
||||
|
||||
export default getServerRoute(ROUTES.topic)
|
|
@ -1,30 +0,0 @@
|
|||
import type { PageContext } from '../renderer/types'
|
||||
import type { PageProps } from './types'
|
||||
|
||||
import { render } from 'vike/abort'
|
||||
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Topic'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
|
||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||
const { slug } = pageContext.routeParams
|
||||
|
||||
const topic = await apiClient.getTopic({ slug })
|
||||
|
||||
if (!topic) {
|
||||
throw render(404)
|
||||
}
|
||||
|
||||
const topicShouts = await apiClient.getShouts({
|
||||
filters: { topic: topic.slug, featured: true },
|
||||
limit: PRERENDERED_ARTICLES_COUNT
|
||||
})
|
||||
|
||||
const pageProps: PageProps = { topic, topicShouts, seo: { title: topic.title } }
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
pageProps
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
import type { PageProps } from './types'
|
||||
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||
|
||||
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
import { useRouter } from '../stores/router'
|
||||
import { loadShouts, resetSortedArticles } from '../stores/zine/articles'
|
||||
|
||||
export const TopicPage = (props: PageProps) => {
|
||||
const { page } = useRouter()
|
||||
const slug = createMemo(() => page().params['slug'] as string)
|
||||
|
||||
const [isLoaded, setIsLoaded] = createSignal(
|
||||
Boolean(props.topicShouts) && Boolean(props.topic) && props.topic.slug === slug()
|
||||
)
|
||||
|
||||
const preload = () =>
|
||||
Promise.all([
|
||||
loadShouts({
|
||||
filters: { topic: slug(), featured: true },
|
||||
limit: PRERENDERED_ARTICLES_COUNT,
|
||||
offset: 0
|
||||
})
|
||||
])
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await preload()
|
||||
|
||||
setIsLoaded(true)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(slug, async (s) => {
|
||||
if (s) {
|
||||
setIsLoaded(false)
|
||||
resetSortedArticles()
|
||||
await preload()
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
onCleanup(resetSortedArticles)
|
||||
|
||||
const usePrerenderedData = props.topic?.slug === slug()
|
||||
|
||||
return (
|
||||
<PageLayout title={props.seo?.title || props.topic?.title}>
|
||||
<ReactionsProvider>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<TopicView
|
||||
topic={usePrerenderedData ? props.topic : null}
|
||||
shouts={usePrerenderedData ? props.topicShouts : null}
|
||||
topicSlug={slug()}
|
||||
/>
|
||||
</Show>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = TopicPage
|
|
@ -1,56 +0,0 @@
|
|||
// in a separate file to avoid circular dependencies
|
||||
import type { Chat } from '../graphql/schema/chat.gen'
|
||||
import type { Author, SearchResult, Shout, Topic } from '../graphql/schema/core.gen'
|
||||
// all the things (she said) that could be passed from the server
|
||||
export type PageProps = {
|
||||
article?: Shout
|
||||
expoShouts?: Shout[]
|
||||
authorShouts?: Shout[]
|
||||
topicShouts?: Shout[]
|
||||
homeShouts?: Shout[]
|
||||
author?: Author
|
||||
allAuthors?: Author[]
|
||||
topWritingAuthors?: Author[]
|
||||
topFollowedAuthors?: Author[]
|
||||
topic?: Topic
|
||||
allTopics?: Topic[]
|
||||
searchQuery?: string
|
||||
layouts?: LayoutType[]
|
||||
// other types?
|
||||
searchResults?: SearchResult[]
|
||||
chats?: Chat[]
|
||||
seo: {
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type RootSearchParams = {
|
||||
m: string // modal
|
||||
lang: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'
|
||||
|
||||
export type FileTypeToUpload = 'image' | 'video' | 'doc' | 'audio'
|
||||
|
||||
export type MediaItem = {
|
||||
url: string
|
||||
title: string
|
||||
body: string
|
||||
source?: string // for image
|
||||
pic?: string
|
||||
|
||||
// audio specific properties
|
||||
date?: string
|
||||
genre?: string
|
||||
artist?: string
|
||||
lyrics?: string
|
||||
}
|
||||
|
||||
export type UploadedFile = {
|
||||
url: string
|
||||
originalFilename?: string
|
||||
}
|
||||
|
||||
export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities'
|
|
@ -75,4 +75,4 @@ export const ArticlePage = (props: RouteSectionProps<{ article: Shout }>) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = ArticlePage
|
||||
export default ArticlePage
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router'
|
||||
import { Suspense, createEffect } from 'solid-js'
|
||||
import { AllAuthors } from '~/components/Views/AllAuthors'
|
||||
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 { Author, QueryLoad_Authors_ByArgs } from '~/graphql/schema/core.gen'
|
||||
import { loadAuthors } from '~/lib/api/public'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { ReactionsProvider } from '../context/reactions'
|
||||
|
||||
const fetchData = async () => {
|
||||
const opts: QueryLoad_Authors_ByArgs = {
|
74
src/routes/author/[slug]/[...tab].tsx
Normal file
74
src/routes/author/[slug]/[...tab].tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { RouteSectionProps, createAsync, useParams } from '@solidjs/router'
|
||||
import { ErrorBoundary, Suspense, createMemo, createReaction } 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 { Author, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
||||
import { loadShouts } from '~/lib/api/public'
|
||||
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()
|
||||
}
|
||||
|
||||
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 result
|
||||
}
|
||||
}
|
||||
|
||||
export const TopicPage = (props: RouteSectionProps<{ articles: Shout[] }>) => {
|
||||
const params = useParams()
|
||||
const articles = createAsync(
|
||||
async () => props.data.articles || (await fetchAuthorShouts(params.slug)) || []
|
||||
)
|
||||
const { authorsEntities } = useAuthors()
|
||||
const { t } = useLocalize()
|
||||
const author = createMemo(() => authorsEntities?.()[params.slug])
|
||||
const title = createMemo(() => `${t('Discours')}: ${author()?.name || ''}`)
|
||||
|
||||
// docs: `a side effect that is run the first time the expression
|
||||
// wrapped by the returned tracking function is notified of a change`
|
||||
createReaction(() => {
|
||||
if (author()) {
|
||||
console.debug('[routes.slug] article signal changed once')
|
||||
window.gtag?.('event', 'page_view', {
|
||||
page_title: author()?.name || '',
|
||||
page_location: window.location.href,
|
||||
page_path: window.location.pathname
|
||||
})
|
||||
}
|
||||
})
|
||||
return (
|
||||
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<PageLayout
|
||||
title={title()}
|
||||
headerTitle={author()?.name || ''}
|
||||
slug={author()?.slug}
|
||||
articleBody={author()?.about || author()?.bio || ''}
|
||||
cover={author()?.pic || ''}
|
||||
>
|
||||
<ReactionsProvider>
|
||||
<AuthorView
|
||||
author={author() as Author}
|
||||
authorSlug={params.slug}
|
||||
shouts={articles() as Shout[]}
|
||||
selectedTab={params.tab || ''}
|
||||
/>
|
||||
</ReactionsProvider>
|
||||
</PageLayout>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = TopicPage
|
|
@ -1,6 +1,6 @@
|
|||
import { ConnectView } from '~/components/Views/ConnectView'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
|
||||
export const ConnectPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
@ -11,4 +11,4 @@ export const ConnectPage = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = ConnectPage
|
||||
export default ConnectPage
|
31
src/routes/edit/(all).tsx
Normal file
31
src/routes/edit/(all).tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { createAsync } from '@solidjs/router'
|
||||
import { Client } from '@urql/core'
|
||||
import { AuthGuard } from '~/components/AuthGuard'
|
||||
import { DraftsView } from '~/components/Views/DraftsView'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import getDraftsQuery from '~/graphql/query/core/articles-load-drafts'
|
||||
import { Shout } from '~/graphql/schema/core.gen'
|
||||
|
||||
const fetchDrafts = async (client: Client) => {
|
||||
const resp = await client?.query(getDraftsQuery, {}).toPromise()
|
||||
const result = resp?.data?.load_drafts || []
|
||||
return result as Shout[]
|
||||
}
|
||||
|
||||
export const DraftsPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const client = useGraphQL()
|
||||
const drafts = createAsync(async () => await fetchDrafts(client))
|
||||
|
||||
return (
|
||||
<PageLayout title={t('Drafts')}>
|
||||
<AuthGuard>
|
||||
<DraftsView drafts={drafts() || []} />
|
||||
</AuthGuard>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default DraftsPage
|
75
src/routes/edit/[id]/(draft).tsx
Normal file
75
src/routes/edit/[id]/(draft).tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { useNavigate, useParams } from '@solidjs/router'
|
||||
import { createEffect, createMemo, createSignal, lazy, on } from 'solid-js'
|
||||
import { AuthGuard } from '~/components/AuthGuard'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import { useSnackbar } from '~/context/ui'
|
||||
import getShoutDraft from '~/graphql/query/core/article-my'
|
||||
import { Shout } from '~/graphql/schema/core.gen'
|
||||
import { LayoutType } from '~/types/common'
|
||||
|
||||
const EditView = lazy(() => import('~/components/Views/EditView/EditView'))
|
||||
|
||||
export const getContentTypeTitle = (layout: LayoutType) => {
|
||||
switch (layout) {
|
||||
case 'audio':
|
||||
return 'Publish Album'
|
||||
case 'image':
|
||||
return 'Create gallery'
|
||||
case 'video':
|
||||
return 'Create video'
|
||||
case 'literature':
|
||||
return 'New literary work'
|
||||
default:
|
||||
return 'Write an article'
|
||||
}
|
||||
}
|
||||
|
||||
export const EditPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const { session } = useSession()
|
||||
const snackbar = useSnackbar()
|
||||
const navigate = useNavigate()
|
||||
const fail = async (error: string) => {
|
||||
console.error(error)
|
||||
const errorMessage = error === 'forbidden' ? "You can't edit this post" : error
|
||||
await snackbar?.showSnackbar({ type: 'error', body: t(errorMessage) })
|
||||
navigate('/edit')
|
||||
}
|
||||
const [shout, setShout] = createSignal<Shout>()
|
||||
const params = useParams()
|
||||
const client = useGraphQL()
|
||||
|
||||
createEffect(on(session, (s) => s?.access_token && loadDraft(), { defer: true }))
|
||||
|
||||
const loadDraft = async () => {
|
||||
const result = await client.query(getShoutDraft, { shout_id: params.id }).toPromise()
|
||||
if (result) {
|
||||
const { shout: loadedShout, error } = result.data.get_my_shout
|
||||
if (error) {
|
||||
fail(error)
|
||||
} else {
|
||||
setShout(loadedShout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const title = createMemo(() => {
|
||||
if (!shout()) {
|
||||
return t('Create post')
|
||||
}
|
||||
return t(getContentTypeTitle(shout()?.layout as LayoutType))
|
||||
})
|
||||
|
||||
return (
|
||||
<PageLayout title={title()}>
|
||||
<AuthGuard>
|
||||
<EditView shout={shout() as Shout} />
|
||||
</AuthGuard>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditPage
|
36
src/routes/edit/[id]/settings.tsx
Normal file
36
src/routes/edit/[id]/settings.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useParams } from '@solidjs/router'
|
||||
import { createEffect, createSignal, on } from 'solid-js'
|
||||
import { AuthGuard } from '~/components/AuthGuard'
|
||||
import EditSettingsView from '~/components/Views/EditView/EditSettingsView'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import getShoutDraft from '~/graphql/query/core/article-my'
|
||||
import { Shout } from '~/graphql/schema/core.gen'
|
||||
|
||||
export const EditSettingsPage = () => {
|
||||
const { t } = useLocalize()
|
||||
const params = useParams()
|
||||
const client = useGraphQL()
|
||||
const { session } = useSession()
|
||||
createEffect(on(session, (s) => s?.access_token && loadDraft(), { defer: true }))
|
||||
const [shout, setShout] = createSignal<Shout>()
|
||||
const loadDraft = async () => {
|
||||
const result = await client.query(getShoutDraft, { shout_id: params.id }).toPromise()
|
||||
if (result) {
|
||||
const { shout: loadedShout, error } = result.data.get_my_shout
|
||||
if (error) throw Error(error)
|
||||
setShout(loadedShout)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<PageLayout title={t('Publication settings')}>
|
||||
<AuthGuard>
|
||||
<EditSettingsView shout={shout() as Shout} />
|
||||
</AuthGuard>
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditSettingsPage
|
|
@ -1,46 +1,48 @@
|
|||
import { redirectPage } from '@nanostores/router'
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { useNavigate } from '@solidjs/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { Meta } from '../context/meta'
|
||||
|
||||
import { AuthGuard } from '../components/AuthGuard'
|
||||
import { Button } from '../components/_shared/Button'
|
||||
import { Icon } from '../components/_shared/Icon'
|
||||
import { PageLayout } from '../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../context/localize'
|
||||
import { apiClient } from '../graphql/client/core'
|
||||
import { router } from '../stores/router'
|
||||
import { getImageUrl } from '../utils/getImageUrl'
|
||||
|
||||
import { LayoutType } from './types'
|
||||
|
||||
import { createMemo } from 'solid-js'
|
||||
import { AuthGuard } from '~/components/AuthGuard'
|
||||
import { Button } from '~/components/_shared/Button'
|
||||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||
import { useGraphQL } from '~/context/graphql'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import createShoutMutation from '~/graphql/mutation/core/article-create'
|
||||
import enKeywords from '~/lib/locales/en/keywords.json'
|
||||
import ruKeywords from '~/lib/locales/ru/keywords.json'
|
||||
import { LayoutType } from '~/types/common'
|
||||
import { getImageUrl } from '~/utils/getImageUrl'
|
||||
import styles from '../styles/Create.module.scss'
|
||||
|
||||
const handleCreate = async (layout: LayoutType) => {
|
||||
const shout = await apiClient.createArticle({ article: { layout: layout } })
|
||||
shout?.id &&
|
||||
redirectPage(router, 'edit', {
|
||||
shoutId: shout?.id.toString()
|
||||
})
|
||||
}
|
||||
|
||||
export const CreatePage = () => {
|
||||
const { t } = useLocalize()
|
||||
export default () => {
|
||||
const { t, lang } = useLocalize()
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Choose a post type')
|
||||
const description = t('Participate in the Discours: share information, join the editorial team')
|
||||
|
||||
const ogTitle = createMemo(() => t('Choose a post type'))
|
||||
const description = createMemo(() =>
|
||||
t('Participate in the Discours: share information, join the editorial team')
|
||||
)
|
||||
const client = useGraphQL()
|
||||
const navigate = useNavigate()
|
||||
const handleCreate = async (layout: LayoutType) => {
|
||||
const result = await client.mutation(createShoutMutation, { article: { layout: layout } }).toPromise()
|
||||
if (result) {
|
||||
const shout = result.data.create_shout
|
||||
if (shout?.id) navigate(`/edit/${shout.id}`)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<PageLayout title={ogTitle}>
|
||||
<Meta name="descprition" content={description} />
|
||||
<Meta name="keywords" content={t('keywords')} />
|
||||
<PageLayout title={ogTitle()}>
|
||||
<Meta name="descprition" content={description()} />
|
||||
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
|
||||
<Meta name="og:type" content="article" />
|
||||
<Meta name="og:title" content={ogTitle} />
|
||||
<Meta name="og:title" content={ogTitle()} />
|
||||
<Meta name="og:image" content={ogImage} />
|
||||
<Meta name="twitter:image" content={ogImage} />
|
||||
<Meta name="og:description" content={description} />
|
||||
<Meta name="og:description" content={description()} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={ogTitle} />
|
||||
<Meta name="twitter:description" content={description} />
|
||||
<Meta name="twitter:title" content={ogTitle()} />
|
||||
<Meta name="twitter:description" content={description()} />
|
||||
<AuthGuard>
|
||||
<article class={clsx('wide-container', 'container--static-page', styles.Create)}>
|
||||
<h1>{t('Choose a post type')}</h1>
|
||||
|
@ -82,5 +84,3 @@ export const CreatePage = () => {
|
|||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export const Page = CreatePage
|
70
src/routes/expo/[...layout].tsx
Normal file
70
src/routes/expo/[...layout].tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Params, RouteSectionProps, createAsync, useParams } from '@solidjs/router'
|
||||
import { createEffect, createMemo, on } from 'solid-js'
|
||||
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
||||
import { loadShouts } from '~/lib/api/public'
|
||||
import { LayoutType } from '~/types/common'
|
||||
import { SHOUTS_PER_PAGE } from '../(home)'
|
||||
import { Topics } from '../../components/Nav/Topics'
|
||||
import { Expo } from '../../components/Views/Expo'
|
||||
import { PageLayout } from '../../components/_shared/PageLayout'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
|
||||
const fetchExpoShouts = async (layouts: string[]) => {
|
||||
const result = await loadShouts({
|
||||
filters: { layouts },
|
||||
limit: SHOUTS_PER_PAGE,
|
||||
offset: 0
|
||||
} as LoadShoutsOptions)
|
||||
return result || []
|
||||
}
|
||||
|
||||
export const route = {
|
||||
load: async ({ params }: { params: Params }) => {
|
||||
const layouts = params.layout ? [params.layout] : ['audio', 'literature', 'article', 'video', 'image']
|
||||
const shoutsLoader = await fetchExpoShouts(layouts)
|
||||
return (await shoutsLoader()) as Shout[]
|
||||
}
|
||||
}
|
||||
|
||||
export const ExpoPage = (props: RouteSectionProps<Shout[]>) => {
|
||||
const { t } = useLocalize()
|
||||
const params = useParams()
|
||||
const shouts = createAsync(
|
||||
async () =>
|
||||
props.data ||
|
||||
(await fetchExpoShouts(
|
||||
params.layout ? [params.layout] : ['audio', 'literature', 'article', 'video', 'image']
|
||||
))
|
||||
)
|
||||
const layout = createMemo(() => params.layout)
|
||||
const title = createMemo(() => {
|
||||
switch (layout()) {
|
||||
case 'audio': {
|
||||
return t('Audio')
|
||||
}
|
||||
case 'video': {
|
||||
return t('Video')
|
||||
}
|
||||
case 'image': {
|
||||
return t('Artworks')
|
||||
}
|
||||
case 'literature': {
|
||||
return t('Literature')
|
||||
}
|
||||
default: {
|
||||
return t('Art')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(on(title, (ttl) => (document.title = ttl), { defer: true }))
|
||||
|
||||
return (
|
||||
<PageLayout withPadding={true} zeroBottomPadding={true} title={title()}>
|
||||
<Topics />
|
||||
<Expo shouts={shouts() || []} layout={layout() as LayoutType} />
|
||||
</PageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExpoPage
|
|
@ -140,4 +140,4 @@ export const FeedPage = (props: RouteSectionProps<Shout[]>) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = FeedPage
|
||||
export default FeedPage
|
||||
|
|
|
@ -32,7 +32,7 @@ export default () => {
|
|||
|
||||
<p>
|
||||
Дискурс — независимый журнал о культуре, науке, искусстве и обществе с
|
||||
<a href="/about/manifest">открытой редакцией</a>. У нас нет главного редактора, инвестора
|
||||
<a href="/guide/manifest">открытой редакцией</a>. У нас нет главного редактора, инвестора
|
||||
и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий
|
||||
Дискурс основан на принципах прямой демократии: в нашем горизонтальном сообществе все
|
||||
редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
|
||||
|
@ -42,7 +42,7 @@ export default () => {
|
|||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<a href="/topics">Темы</a>
|
||||
<a href="/topic">Темы</a>
|
||||
— у нас публикуются исследования, обзоры, эссе, интервью, репортажи,
|
||||
аналитика и другие материалы о культуре, науке, искусстве и обществе.
|
||||
</p>
|
||||
|
@ -69,7 +69,7 @@ export default () => {
|
|||
</li >
|
||||
<li>
|
||||
<p>
|
||||
<a href="/create" class="ng-scope" target="_blank">Редакция</a> —
|
||||
<a href="/edit/new" class="ng-scope" target="_blank">Редакция</a> —
|
||||
это внутренний раздел, где появляются новые материалы, которые присылают
|
||||
в редакцию. Здесь авторы обсуждают, редактируют и оценивают
|
||||
публикации, определяя таким образом содержание журнала.
|
||||
|
@ -96,11 +96,11 @@ export default () => {
|
|||
<h3 id="become-author">Как стать автором журнала</h3>
|
||||
<p>
|
||||
Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров,
|
||||
философов, ученых и других замечательных людей. Каждый может <a href="/create">прислать</a>{' '}
|
||||
свой материал в журнал. Формат и тематика не имеют значения, единственное, что
|
||||
важно — <a href="/how-to-write-a-good-article">хороший</a> ли материал. Если
|
||||
сообщество поддержит вашу публикацию, она выйдет в журнале и станет доступна тысячам
|
||||
наших читателей.
|
||||
философов, ученых и других замечательных людей. Каждый может{' '}
|
||||
<a href="/edit/new">прислать</a> свой материал в журнал. Формат и тематика
|
||||
не имеют значения, единственное, что важно —{' '}
|
||||
<a href="/how-to-write-a-good-article">хороший</a> ли материал. Если сообщество поддержит
|
||||
вашу публикацию, она выйдет в журнале и станет доступна тысячам наших читателей.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
@ -148,7 +148,7 @@ export default () => {
|
|||
почту
|
||||
</a>
|
||||
— наши коллеги-художники могут вам помочь{' '}
|
||||
<a href="/create?collab" target="_blank" rel="noreferrer">
|
||||
<a href="/edit/new?collab" target="_blank" rel="noreferrer">
|
||||
в режиме совместного редактирования
|
||||
</a>
|
||||
.
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const DiscussionRulesPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Community Discussion Rules')
|
||||
const description = t(
|
||||
|
@ -126,4 +124,4 @@ export const DiscussionRulesPage = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = DiscussionRulesPage
|
||||
export default DiscussionRulesPage
|
|
@ -1,5 +1,4 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { Feedback } from '../../components/Discours/Feedback'
|
||||
import { Modal } from '../../components/Nav/Modal'
|
||||
import Opener from '../../components/Nav/Modal/Opener'
|
||||
|
@ -10,7 +9,6 @@ import { getImageUrl } from '../../utils/getImageUrl'
|
|||
|
||||
export const ManifestPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
const ogTitle = t('Discours Manifest')
|
||||
const description = t(
|
||||
|
@ -61,7 +59,7 @@ export const ManifestPage = () => {
|
|||
<p>
|
||||
Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем и идеологических
|
||||
рамок. Каждый может <a href="/create">прислать материал</a> в журнал и
|
||||
<a href="/about/guide">присоединиться к редакции</a>. Предоставляя трибуну для независимой
|
||||
<a href="/guide">присоединиться к редакции</a>. Предоставляя трибуну для независимой
|
||||
журналистики и художественных проектов, мы помогаем людям рассказывать свои истории так,
|
||||
чтобы они были услышаны. Мы убеждены: чем больше голосов будет звучать на Дискурсе, тем
|
||||
громче в полифонии мнений будет слышна истина.
|
||||
|
@ -72,7 +70,7 @@ export const ManifestPage = () => {
|
|||
</h2>
|
||||
|
||||
<p>
|
||||
Дискурс создается <a href="/about/guide">открытым сообществом</a> энтузиастов новой независимой
|
||||
Дискурс создается <a href="/guide">открытым сообществом</a> энтузиастов новой независимой
|
||||
журналистики. Участвовать в открытой редакции и помогать журналу можно следующими
|
||||
способами:
|
||||
</p>
|
||||
|
@ -95,8 +93,8 @@ export const ManifestPage = () => {
|
|||
</summary>
|
||||
<p>
|
||||
Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,{' '}
|
||||
<a href="/about/help">поддержите</a> нашу работу. Ваши пожертвования пойдут на выпуск новых
|
||||
материалов, оплату серверов, труда программистов, дизайнеров и редакторов.
|
||||
<a href="/guide/support">поддержите</a> нашу работу. Ваши пожертвования пойдут на выпуск
|
||||
новых материалов, оплату серверов, труда программистов, дизайнеров и редакторов.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
@ -162,4 +160,4 @@ export const ManifestPage = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = ManifestPage
|
||||
export default ManifestPage
|
|
@ -1,5 +1,4 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
|
@ -192,4 +192,4 @@ export const PrinciplesPage = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = PrinciplesPage
|
||||
export default PrinciplesPage
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Meta } from '../../context/meta'
|
||||
|
||||
import { Meta } from '@solidjs/meta'
|
||||
import { Donate } from '../../components/Discours/Donate'
|
||||
import { StaticPage } from '../../components/Views/StaticPage'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
|
||||
export const HelpPage = () => {
|
||||
export const SupportPage = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||
|
@ -137,4 +136,4 @@ export const HelpPage = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Page = HelpPage
|
||||
export default SupportPage
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user