routing-structure-fin

This commit is contained in:
Untone 2024-07-04 00:25:03 +03:00
parent 3fa235cd16
commit c9a8c1aa8e
109 changed files with 695 additions and 1916 deletions

View File

@ -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>

View File

@ -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')} &copy; 2015&ndash;{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}>

View File

@ -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>

View File

@ -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>

View File

@ -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'
}
]

View File

@ -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()
}}

View File

@ -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>

View File

@ -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)} />

View File

@ -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>

View File

@ -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>

View File

@ -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'))

View File

@ -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>

View File

@ -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()}`)

View File

@ -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()}`)

View File

@ -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" />

View File

@ -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">

View 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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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',

View File

@ -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)

View File

@ -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,

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.discussionRules)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.dogma)

View File

@ -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

View File

@ -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>
Дискурс&nbsp;&mdash; независимый журнал о&nbsp;культуре, науке, искусстве и&nbsp;обществе с&nbsp;
<a href="/about/manifest">открытой редакцией</a>. У&nbsp;нас нет главного редактора, инвестора
и&nbsp;вообще никого, кто&nbsp;бы принимал единоличные решения. Вместо традиционных иерархий
Дискурс основан на&nbsp;принципах прямой демократии: в&nbsp;нашем горизонтальном сообществе все
редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
</p>
<h3 id="how-it-works">Как устроен сайт Дискурса</h3>
<p>Дискурс состоит из&nbsp;четырех основных разделов:</p>
<ul>
<li>
<p>
<a href="/topics">Темы</a>
&nbsp;&mdash; у&nbsp;нас публикуются исследования, обзоры, эссе, интервью, репортажи,
аналитика и&nbsp;другие материалы о&nbsp;культуре, науке, искусстве и&nbsp;обществе.
</p>
</li>
<li>
<p>
<a href="/topic/art">Искусство</a>
&nbsp;&mdash; здесь, например, представлены художественные произведения: литература, живопись,
музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству, которое создают
российские художники, писатели, режиссёры и&nbsp;музыканты.
</p>
</li>
{/*
<li>
<p>
<a href="/topic/events">События</a>&nbsp; в&nbsp;этом разделе
публикуются самые важные, по&nbsp;мнению редакции, культурные
события России выставки, лекции, концерты, кинопоказы, фестивали,
художественные и политические&nbsp;акции. Напишите&nbsp;нам
на&nbsp;<a href="mailto:welcome@discours.io" target="_blank">почту</a>, если вы
хотите разместить объявление. Мы делаем&nbsp;это
на&nbsp;безвозмездной основе.
</p>
</li >
<li>
<p>
<a href="/create" class="ng-scope" target="_blank">Редакция</a>&nbsp;
это внутренний раздел, где появляются новые материалы, которые присылают
в&nbsp;редакцию. Здесь авторы обсуждают, редактируют и&nbsp;оценивают
публикации, определяя таким образом содержание журнала.
</p>
</li>
*/}
</ul>
<p>
Материалы в&nbsp;Дискурсе объединяются по&nbsp;<b>темам</b>
&mdash;&nbsp;ключевым словам, которые располагаются в&nbsp;конце материалов и&nbsp;связывают
материалы по&nbsp;жанрам (например, <a href="/topic/interview">интервью</a>,{' '}
<a href="/topic/reportage">репортажи</a>, <a href="/topic/essay">эссе</a>,{' '}
<a href="/topic/likbez">ликбезы</a>
), по&nbsp;тематике (<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> и&nbsp;т.д.) или в&nbsp;серии (как &laquo;
<a href="/topic/zakony-mira">Законы мира</a>&raquo; или &laquo;
<a href="/topic/za-liniey-mannergeyma">За&nbsp;линией Маннергейма</a>
&raquo;). Темы объединяют сотни публикаций, помогают ориентироваться в&nbsp;журнале и&nbsp;следить
за&nbsp;интересными материалами.
</p>
<section>
<h3 id="become-author">Как стать автором журнала</h3>
<p>
Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров,
философов, ученых и&nbsp;других замечательных людей. Каждый может <a href="/create">прислать</a>{' '}
свой материал в&nbsp;журнал. Формат и&nbsp;тематика не&nbsp;имеют значения, единственное, что
важно &mdash; <a href="/how-to-write-a-good-article">хороший</a> ли&nbsp;материал. Если
сообщество поддержит вашу публикацию, она выйдет в&nbsp;журнале и&nbsp;станет доступна тысячам
наших читателей.
</p>
</section>
<h3 id="voting">Как проходит голосование</h3>
<p>
Все присылаемые в&nbsp;Дискурс материалы попадают в&nbsp;
<strong>&laquo;Редакцию&raquo;</strong>. Это внутренний раздел сайта, где участники сообщества
решают, что будет опубликовано в&nbsp;Дискурсе. Как только работа получает одобрение как минимум
пятерых авторов открытой редакции, она немедленно публикуется в&nbsp;журнале. Если&nbsp;же
материал набирает более&nbsp;20% голосов &laquo;против&raquo;, он&nbsp;не&nbsp;выходит
и&nbsp;может быть отправлен на&nbsp;доработку. Жестких сроков рассмотрения материалов у&nbsp;нас
нет, иногда это занимает час, иногда месяц, обычно&nbsp;&mdash; несколько дней.
</p>
<section>
<p>
Как только сообщество поддержит публикацию, вы&nbsp;получите приглашение
в&nbsp;интернет-редакцию и&nbsp;сможете голосовать за&nbsp;новые материалы.
</p>
</section>
<h3 id="editing">Как мы&nbsp;делаем тексты друг друга лучше</h3>
<p>
Дискурс&nbsp;&mdash; журнал с&nbsp;совместным редактированием. Совершенствовать тексты нам
помогает <b>система ремарок</b>. Вы&nbsp;можете выделить часть текста в&nbsp;любой статье
и&nbsp;оставить к&nbsp;ней замечание, вопрос или предложение&nbsp;&mdash; автор текста получит
совет на&nbsp;почту и&nbsp;сможет его учесть. Так мы&nbsp;устраняем опечатки, неточности
и&nbsp;советуем друг другу, как сделать тексты качественнее и&nbsp;интереснее.
</p>
<p>
Среди участников сообщества есть профессиональные редакторы, которые помогают авторам делать
тексты лучше. Если вашему материалу потребуется доработка, они помогут отредактировать текст,
подобрать иллюстрации, придумать заголовок и&nbsp;красиво сверстать публикацию. Если
вы&nbsp;хотите обсудить текст, прежде чем загрузить материал в интернет-редакцию&nbsp;&mdash;
разместите его в&nbsp;google-документе, откройте доступ к&nbsp;редактированию по&nbsp;ссылке
и&nbsp;напишите нам на&nbsp;
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
welcome@discours.io
</a>
.
</p>
<p>
Если у&nbsp;вас возникают трудности с&nbsp;тем, чтобы подобрать к&nbsp;своему материалу
иллюстрации, тоже пишите на&nbsp;
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
почту
</a>
&mdash; наши коллеги-художники могут вам помочь{' '}
<a href="/create?collab" target="_blank" rel="noreferrer">
в&nbsp;режиме совместного редактирования
</a>
.
</p>
<h3 id="perks">Что сообщество дает авторам</h3>
<ul>
<li>
<p>
<strong>Право определять, каким будет журнал</strong>. Дискурс&nbsp;&mdash; это общественная
институция, созданная людьми и&nbsp;ради людей, функционирующая на&nbsp;условиях прямой
демократии. Авторы публикуют статьи и&nbsp;художественные проекты, участвуют
в&nbsp;обсуждениях, голосуют за&nbsp;работы коллег и&nbsp;таким образом вносят свой вклад
в&nbsp;развитие проекта, определяя содержание и&nbsp;направление журнала.
</p>
</li>
<li>
<p>
<strong>Возможность обратиться к&nbsp;широкой аудитории</strong>. Дискурс читают десятки тысяч
людей, и&nbsp;с&nbsp;каждым днем их&nbsp;становится больше.
</p>
</li>
<li>
<p>
<strong>Поддержка редакции</strong>. Дискурс предоставляет авторам аккредитацию
на&nbsp;мероприятия, базу контактов, юридическую поддержку, ознакомление с&nbsp;книжными,
кино- и&nbsp;музыкальными новинками до&nbsp;их&nbsp;выхода в&nbsp;свет. Если что-то
из&nbsp;этого вам понадобится, пишите на&nbsp;почту{' '}
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
welcome@discours.io
</a>
&nbsp;&mdash; поможем.
</p>
</li>
<li>
<p>
<strong>Пресс-карты для корреспондентов</strong>. Три опубликованные статьи позволяют авторам
Дискурса получить официальные удостоверения журналистов (пресс-карты) на&nbsp;следующий год.
Пресс-карты удостоверяют, что вы&nbsp;журналист и&nbsp;можете пользоваться всеми теми правами,
которые гарантирует Закон о&nbsp;СМИ. Кроме того, многие культурные институции (музеи, галереи
и&nbsp;др.) предоставляют журналистам право свободного входа.
</p>
</li>
<li>
<p>
<strong>Помощь сотен специалистов в&nbsp;разных областях</strong>. В&nbsp;основе Дискурса
лежит идея совместного редактирования. Участники редакционного сообщества&nbsp;&mdash;
несколько сотен журналистов, исследователей, художников, литераторов из&nbsp;разных стран
&mdash; изучают материалы друг друга до&nbsp;публикации и&nbsp;помогают сделать
их&nbsp;качественнее и&nbsp;интереснее. Так, в&nbsp;редакции нередко складываются творческие
союзы: например, авторов текстов и&nbsp;художников, создающих для них иллюстрации.
</p>
</li>
<li>
<p>
<strong>Пространство общения полное выдающихся людей</strong>. Дискурс&nbsp;&mdash; большое
живое сообщество интеллектуалов, разбросанных по&nbsp;всему земному шару. Вступив
в&nbsp;редакцию, вы&nbsp;сможете познакомиться со&nbsp;множеством интересных людей, которые
определяют повестку завтрашнего дня, вдохновляют окружающих, создают новое и&nbsp;изучают
старое, ищут знания и&nbsp;готовы ими делиться, чтобы менять мир в&nbsp;соответствии
со&nbsp;своими идеалами.
</p>
</li>
</ul>
<h3 id="contacts">Как быть в&nbsp;курсе</h3>
<p>
За&nbsp;свежими публикациями Дискурса можно следить не&nbsp;только на&nbsp;сайте,
но&nbsp;и&nbsp;на&nbsp;страницах в&nbsp;
<a href="https://facebook.com/discoursio/" target="_blank" rel="noreferrer">
Фейсбуке
</a>
,{' '}
<a href="https://vk.com/discoursio" target="_blank" rel="noreferrer">
ВКонтакте
</a>{' '}
и&nbsp;
<a href="https://t.me/discoursio" target="_blank" rel="noreferrer">
Телеграме
</a>
. А&nbsp;ещё раз в&nbsp;месяц мы&nbsp;отправляем <a href="#subscribe">почтовую рассылку</a>{' '}
с&nbsp;дайджестом лучших материалов.
</p>
<p>
Если вы&nbsp;хотите сотрудничать, что-то обсудить или предложить &mdash; пожалуйста, пишите
на&nbsp;
<a href="mailto:welcome@discours.io" target="_blank" rel="noreferrer">
welcome@discours.io
</a>
. Мы&nbsp;обязательно ответим.
</p>
</>
</StaticPage>
)
}
export const Page = GuidePage

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.guide)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.help)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.manifest)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.partners)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.principles)

View File

@ -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>. Мы&nbsp;все разные, и&nbsp;это классно. Вертикалей
в&nbsp;мире достаточно, мы&nbsp;&mdash; горизонтальное сообщество и&nbsp;ценим наши различия,
потому что знаем&nbsp;&mdash; в&nbsp;них наша сила. Благодаря разнообразию сотен голосов,
усиливающих друг друга, в&nbsp;сообществе складывается неповторимая синергия, которая помогает
вместе достигать большего.
</p>
</li>
<li>
<p>
<strong>Многоголосие</strong>. Мы&nbsp;ценим свободу слова и&nbsp;аргументированные мнения.
Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию позиций, знаний
и&nbsp;опыта, которые открывают более полную картину реальности.
</p>
</li>
<li>
<p>
<strong>Взаимопомощь</strong>. Мы&nbsp;помогаем друг другу, потому что хотим, чтобы в&nbsp;мире
было еще больше хорошего. Обсуждая что-то, мы&nbsp;всегда интересуемся, чем можем помочь.
В&nbsp;самиздате можно найти специалистов практически в&nbsp;любых сферах и&nbsp;получить
поддержку от&nbsp;сотен людей. Благодаря коллективной экспертизе глобального сообщества
в&nbsp;самиздате выходят крутейшие публикации, которыми можно вечно гордиться.
</p>
</li>
<li>
<p>
<strong>Взаимоуважение</strong>. Мы&nbsp;ценим, искренне уважаем друг друга и&nbsp;вместо
борщевиков враждебности культивируем цветы добра, мира, знания и&nbsp;юмора. Нам некогда
доказывать друг другу, кто круче. Гораздо приятнее сотрудничать, помогать и&nbsp;создавать
что-то важное, интересное и&nbsp;полезное.
</p>
</li>
<li>
<p>
<strong>Созидание</strong>. Мы&nbsp;создаем, потому что любим создавать. Мы&nbsp;открыто делимся
опытом, дарим идеи, обмениваемся мнениями и&nbsp;благодарим за&nbsp;критику, используя
ее&nbsp;для совершенствования мастерства и&nbsp;саморазвития. Мы&nbsp;знаем, что мир
не&nbsp;идеальное место, и&nbsp;делаем всё возможное, чтобы он&nbsp;стал лучше.
</p>
</li>
</ol>
<h2 class="h2" id="participation">
<span class="wrapped">Как у&nbsp;нас принято себя вести</span>
</h2>
<p>
Открытая редакция объединяет сотни потрясающих людей со&nbsp;всего мира, которые делают крутейшие
вещи. Это пространство, где доверяют, вдохновляют, исследуют и&nbsp;создают новое вместе. Поскольку
все в&nbsp;сообществе очень разные, как-то мы&nbsp;собрались и&nbsp;решили зафиксировать базовые
ценности открытой редакции, а&nbsp;заодно придумали универсальные правила взаимодействия, чтобы
общение было не&nbsp;только плодотворным, но&nbsp;и&nbsp;приятным для всех участников сообщества.
</p>
<ol>
<li>
<p>
<strong>Действуем, помогаем и&nbsp;делимся</strong>. В&nbsp;редакции мы&nbsp;создаем свои
проекты и&nbsp;помогаем другим создавать свои&nbsp;&mdash; советами, делом, участием,
вовлеченностью. Мы&nbsp;открыто делимся опытом, мнениями и&nbsp;идеями, потому что ценим силу
сотрудничества и&nbsp;знаем, что идеи реализуются скорее, лучше и&nbsp;веселее, если над ними
трудиться сообща.
</p>
</li>
<li>
<p>
<strong>Общаемся дружелюбно</strong>. Помните, по&nbsp;ту&nbsp;сторону монитора находятся
реальные люди. Неуважение ранит других так&nbsp;же, как ранило&nbsp;бы вас самих. Поэтому
не&nbsp;стоит кричать (даже капслоком), заполнять эфир желчью и&nbsp;бросаться
грубостями&nbsp;&mdash; так вы&nbsp;рискуете не&nbsp;только растерять доверие окружающих,
но&nbsp;и&nbsp;остаться непонятым.
</p>
</li>
<li>
<p>
<strong>Критикуем и&nbsp;реагируем конструктивно</strong>. Самиздат про&nbsp;то, чтобы
разбираться в&nbsp;сложных вещах всем сообществом, поэтому мы&nbsp;тактично и&nbsp;без агрессии
делимся мнениями, стараясь убедительно аргументировать позиции. И&nbsp;с&nbsp;благодарностью
принимаем критику, используя ее&nbsp;для улучшения наших проектов. Мы&nbsp;верим, что каждый
участник сообщества имеет добрые намерения, и&nbsp;придерживаемся принципов доброжелательной
критики, стараемся делиться советами&nbsp;&mdash; лучшим средством для самосовершенствования.
Обоснованная критика помогает и&nbsp;адресату, и&nbsp;всем участникам сообщества досконально
изучить тему и&nbsp;глубже разобраться в&nbsp;проблеме.
</p>
</li>
<li>
<p>
<strong>Решаем трудности не&nbsp;агрессией, а&nbsp;диалогом</strong>. Обесценивать мнения
и&nbsp;оскорблять других людей только потому, что вы&nbsp;с&nbsp;ними
не&nbsp;согласны,&nbsp;&mdash; не&nbsp;лучший способ донести свою точку зрения. Конечно, важно
высказаться, если вас что-то не&nbsp;устраивает и&nbsp;откровенно бесит. Но&nbsp;прежде чем
сжигать оппонента гневом, попробуйте понять, почему этот &laquo;нехороший человек&raquo; так
поступает. Возможно, аргументы собеседника окажутся убедительными или вам удастся изменить его
мнение. В&nbsp;любом случае конфликты решаются в&nbsp;диалогах и&nbsp;проходят,
а&nbsp;налаженное взаимопонимание останется надолго.
</p>
</li>
<li>
<p>
<strong>Не&nbsp;переходим на&nbsp;личности&nbsp;&mdash; это признак токсичности</strong>. Всегда
мудрее обсуждать точку зрения человека, а&nbsp;не&nbsp;его самого, даже если он&nbsp;вам
не&nbsp;импонирует. Предвзятое отношение ограничивает кругозор, добавляет преждевременные
морщины и&nbsp;не&nbsp;помогает окружающим стать лучше. Вежливость
и&nbsp;взаимоуважение&nbsp;&mdash; краеугольная основа вдумчивых и&nbsp;осмысленных дискуссий.
</p>
</li>
<li>
<p>
<strong>Благодарим за&nbsp;помощь</strong>. Благодарите коллег даже за&nbsp;самые,
казалось&nbsp;бы, простые вещи. &laquo;Спасибо&raquo; не&nbsp;зря называют волшебным
словом&nbsp;&mdash; на&nbsp;искренней благодарности держится любое подлинное сотрудничество.
Поддержка воодушевляет на&nbsp;новые подвиги и&nbsp;напоминает, что мир делают прекрасным
не&nbsp;машины, а&nbsp;живые люди.
</p>
</li>
<li>
<p>
<strong>Даем еще один шанс</strong>. Все совершают ошибки, и&nbsp;за&nbsp;один проступок
не&nbsp;стоит вычеркивать людей из&nbsp;жизни. Ошибки нужны, чтобы на&nbsp;них учиться
и&nbsp;делать выводы. Однако если многократно и&nbsp;систематически нарушать правила сообщества,
наверняка можно заслужить минусы в&nbsp;карму от&nbsp;других участников и&nbsp;потерять доступ
к&nbsp;сообществу.
</p>
</li>
<li>
<p>
<strong>Вместе создаем идеальную среду общения</strong>. Открытая редакция&nbsp;&mdash; это
утопическое пространство обогащающей и&nbsp;осмысленной коммуникации. Атмосфера горизонтального
сообщества складывается из&nbsp;действий каждого, поэтому мы&nbsp;действуем так, чтобы
способствовать сотворчеству, коллективному познанию и&nbsp;развитию самиздата и&nbsp;нашей
альтернативной интеллектуальной медиасреды.
</p>
</li>
<li>
<p>
<strong>Помним, что всё в&nbsp;сообществе зависит от&nbsp;нас</strong>. Если нам чего-то
не&nbsp;хватает, мы&nbsp;начинаем действовать&nbsp;&mdash; рассказываем об&nbsp;идее, находим
единомышленников, готовим и&nbsp;запускаем проект. Так в&nbsp;сообществе становится на&nbsp;одну
крутую активность больше. Так появилось наше сообщество. Так появился самиздат и&nbsp;все
проекты открытой редакции. Чтобы в&nbsp;сообществе случилось что-то прекрасное, достаточно
просто положить этому начало.
</p>
</li>
</ol>
</StaticPage>
)
}
export const Page = PrinciplesPage

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.projects)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.termsOfUse)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.thanks)

View File

@ -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>Неоценимый вклад в&nbsp;Дискурс внесли и&nbsp;вносят</h3>
<p>
Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян&nbsp;Выговский, Эльдар Гариффулин, Павел
Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик, Вячеслав Еременко,
Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр Коренков, Ирэна Лесневская,
Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений Медведев, Павел Никулин, Николай
Носачевский, Андрей Орловский, Михаил Панин, Антон Панов, Павел Пепперштейн, Любовь Покровская, Илья
Розовский, Денис Светличный, Павел Соколов, Сергей Стрельников, Глеб Струнников, Николай Тарковский,
Кирилл Филимонов, Алексей Хапов, Екатерина Харитонова
</p>
<h3>Авторы</h3>
<p>
Мы&nbsp;безмерно благодарны{' '}
<a href="/authors" target="_blank" rel="noopener noreferrer">
каждому автору
</a>{' '}
за&nbsp;участие и&nbsp;поддержку проекта. Сегодня, когда для большинства деньги стали целью
и&nbsp;основным источником мотивации, бескорыстная помощь и&nbsp;основанный на&nbsp;энтузиазме труд
бесценны. Именно вы&nbsp;своим трудом каждый день делаете Дискурс таким, какой он&nbsp;есть.
</p>
<h3>Иллюстраторы</h3>
<p>
Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова, Мария
Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья Diliago, Антон
Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита Поздняков, Матвей
Сапегин, Татьяна Сафонова, Виктория Шибаева
</p>
<h3>Меценаты</h3>
<p>
Дискурс существует исключительно на&nbsp;пожертвования читателей. Мы&nbsp;бесконечно признательны
всем, кто нас поддерживает. Ваши пожертвования&nbsp;&mdash; финансовый фундамент журнала. Благодаря
вам мы&nbsp;развиваем платформу качественной журналистики, которая помогает самым разным авторам
быть услышанными. Стать нашим меценатом и&nbsp;подписаться на&nbsp;ежемесячную поддержку проекта
можно <a href="/about/help">здесь</a>.
</p>
</StaticPage>
)
}
export const Page = ThanksPage

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.authors)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.topics)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.article)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.author)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.authorAbout)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.authorComments)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.connect)

View File

@ -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>
Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите
нам скорее! Если укажете свою почту, мы&nbsp;обязательно ответим.
</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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.create)

View File

@ -1,6 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute as gsr } from '../utils/getServerRoute'
const getServerRoute = () => gsr(ROUTES.drafts)
export { getServerRoute }

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.edit)

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.editSettings)

View File

@ -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?', '*'))

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.feed)

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.feedMy)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.fourOuFour)

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.inbox)

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.home)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.profileSecurity)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.profileSettings)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../../stores/router'
import { getServerRoute } from '../../utils/getServerRoute'
export default getServerRoute(ROUTES.profileSubscriptions)

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.search)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
import { ROUTES } from '../stores/router'
import { getServerRoute } from '../utils/getServerRoute'
export default getServerRoute(ROUTES.topic)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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'

View File

@ -75,4 +75,4 @@ export const ArticlePage = (props: RouteSectionProps<{ article: Shout }>) => {
)
}
export const Page = ArticlePage
export default ArticlePage

View File

@ -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 = {

View 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

View File

@ -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
View 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

View 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

View 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

View File

@ -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

View 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

View File

@ -140,4 +140,4 @@ export const FeedPage = (props: RouteSectionProps<Shout[]>) => {
)
}
export const Page = FeedPage
export default FeedPage

View File

@ -32,7 +32,7 @@ export default () => {
<p>
Дискурс&nbsp;&mdash; независимый журнал о&nbsp;культуре, науке, искусстве и&nbsp;обществе с&nbsp;
<a href="/about/manifest">открытой редакцией</a>. У&nbsp;нас нет главного редактора, инвестора
<a href="/guide/manifest">открытой редакцией</a>. У&nbsp;нас нет главного редактора, инвестора
и&nbsp;вообще никого, кто&nbsp;бы принимал единоличные решения. Вместо традиционных иерархий
Дискурс основан на&nbsp;принципах прямой демократии: в&nbsp;нашем горизонтальном сообществе все
редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.
@ -42,7 +42,7 @@ export default () => {
<ul>
<li>
<p>
<a href="/topics">Темы</a>
<a href="/topic">Темы</a>
&nbsp;&mdash; у&nbsp;нас публикуются исследования, обзоры, эссе, интервью, репортажи,
аналитика и&nbsp;другие материалы о&nbsp;культуре, науке, искусстве и&nbsp;обществе.
</p>
@ -69,7 +69,7 @@ export default () => {
</li >
<li>
<p>
<a href="/create" class="ng-scope" target="_blank">Редакция</a>&nbsp;
<a href="/edit/new" class="ng-scope" target="_blank">Редакция</a>&nbsp;
это внутренний раздел, где появляются новые материалы, которые присылают
в&nbsp;редакцию. Здесь авторы обсуждают, редактируют и&nbsp;оценивают
публикации, определяя таким образом содержание журнала.
@ -96,11 +96,11 @@ export default () => {
<h3 id="become-author">Как стать автором журнала</h3>
<p>
Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров,
философов, ученых и&nbsp;других замечательных людей. Каждый может <a href="/create">прислать</a>{' '}
свой материал в&nbsp;журнал. Формат и&nbsp;тематика не&nbsp;имеют значения, единственное, что
важно &mdash; <a href="/how-to-write-a-good-article">хороший</a> ли&nbsp;материал. Если
сообщество поддержит вашу публикацию, она выйдет в&nbsp;журнале и&nbsp;станет доступна тысячам
наших читателей.
философов, ученых и&nbsp;других замечательных людей. Каждый может{' '}
<a href="/edit/new">прислать</a> свой материал в&nbsp;журнал. Формат и&nbsp;тематика
не&nbsp;имеют значения, единственное, что важно &mdash;{' '}
<a href="/how-to-write-a-good-article">хороший</a> ли&nbsp;материал. Если сообщество поддержит
вашу публикацию, она выйдет в&nbsp;журнале и&nbsp;станет доступна тысячам наших читателей.
</p>
</section>
@ -148,7 +148,7 @@ export default () => {
почту
</a>
&mdash; наши коллеги-художники могут вам помочь{' '}
<a href="/create?collab" target="_blank" rel="noreferrer">
<a href="/edit/new?collab" target="_blank" rel="noreferrer">
в&nbsp;режиме совместного редактирования
</a>
.

View File

@ -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

View File

@ -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>
Редакция Дискурса открыта для всех: у&nbsp;нас нет цензуры, запретных тем и&nbsp;идеологических
рамок. Каждый может <a href="/create">прислать материал</a> в&nbsp;журнал и&nbsp;
<a href="/about/guide">присоединиться к&nbsp;редакции</a>. Предоставляя трибуну для независимой
<a href="/guide">присоединиться к&nbsp;редакции</a>. Предоставляя трибуну для независимой
журналистики и&nbsp;художественных проектов, мы&nbsp;помогаем людям рассказывать свои истории так,
чтобы они были услышаны. Мы&nbsp;убеждены: чем больше голосов будет звучать на&nbsp;Дискурсе, тем
громче в&nbsp;полифонии мнений будет слышна истина.
@ -72,7 +70,7 @@ export const ManifestPage = () => {
</h2>
<p>
Дискурс создается <a href="/about/guide">открытым сообществом</a> энтузиастов новой независимой
Дискурс создается <a href="/guide">открытым сообществом</a> энтузиастов новой независимой
журналистики. Участвовать в&nbsp;открытой редакции и&nbsp;помогать журналу можно следующими
способами:
</p>
@ -95,8 +93,8 @@ export const ManifestPage = () => {
</summary>
<p>
Дискурс существует на&nbsp;пожертвования читателей. Если вам нравится журнал, пожалуйста,{' '}
<a href="/about/help">поддержите</a> нашу работу. Ваши пожертвования пойдут на&nbsp;выпуск новых
материалов, оплату серверов, труда программистов, дизайнеров и&nbsp;редакторов.
<a href="/guide/support">поддержите</a> нашу работу. Ваши пожертвования пойдут на&nbsp;выпуск
новых материалов, оплату серверов, труда программистов, дизайнеров и&nbsp;редакторов.
</p>
</details>
@ -162,4 +160,4 @@ export const ManifestPage = () => {
)
}
export const Page = ManifestPage
export default ManifestPage

View File

@ -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'

View File

@ -192,4 +192,4 @@ export const PrinciplesPage = () => {
)
}
export const Page = PrinciplesPage
export default PrinciplesPage

View File

@ -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