topic selector WIP
This commit is contained in:
parent
293e7a06e4
commit
73f9013dc0
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -33,6 +33,7 @@
|
||||||
"@solid-primitives/storage": "^1.3.7",
|
"@solid-primitives/storage": "^1.3.7",
|
||||||
"@solid-primitives/upload": "^0.0.109",
|
"@solid-primitives/upload": "^0.0.109",
|
||||||
"@solidjs/meta": "^0.28.2",
|
"@solidjs/meta": "^0.28.2",
|
||||||
|
"@thisbeyond/solid-select": "^0.13.0",
|
||||||
"@tiptap/core": "^2.0.0-beta.220",
|
"@tiptap/core": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
|
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-bold": "^2.0.0-beta.220",
|
"@tiptap/extension-bold": "^2.0.0-beta.220",
|
||||||
|
@ -5549,6 +5550,15 @@
|
||||||
"solid-js": ">=1.4.0"
|
"solid-js": ">=1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@thisbeyond/solid-select": {
|
||||||
|
"version": "0.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@thisbeyond/solid-select/-/solid-select-0.13.0.tgz",
|
||||||
|
"integrity": "sha512-eION+Xf8TGLs1NZrvRo1NRKOl4plYMbY7UswHhh5bEUY8oMltjrBhUWF0hzaFViEc1zZpkCQyafaD89iofG6Tg==",
|
||||||
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"solid-js": "^1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tiptap/core": {
|
"node_modules/@tiptap/core": {
|
||||||
"version": "2.0.0-beta.220",
|
"version": "2.0.0-beta.220",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz",
|
||||||
|
@ -27344,6 +27354,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"@thisbeyond/solid-select": {
|
||||||
|
"version": "0.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@thisbeyond/solid-select/-/solid-select-0.13.0.tgz",
|
||||||
|
"integrity": "sha512-eION+Xf8TGLs1NZrvRo1NRKOl4plYMbY7UswHhh5bEUY8oMltjrBhUWF0hzaFViEc1zZpkCQyafaD89iofG6Tg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@tiptap/core": {
|
"@tiptap/core": {
|
||||||
"version": "2.0.0-beta.220",
|
"version": "2.0.0-beta.220",
|
||||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz",
|
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.0-beta.220.tgz",
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
"@solid-primitives/storage": "^1.3.7",
|
"@solid-primitives/storage": "^1.3.7",
|
||||||
"@solid-primitives/upload": "^0.0.109",
|
"@solid-primitives/upload": "^0.0.109",
|
||||||
"@solidjs/meta": "^0.28.2",
|
"@solidjs/meta": "^0.28.2",
|
||||||
|
"@thisbeyond/solid-select": "^0.13.0",
|
||||||
"@tiptap/core": "^2.0.0-beta.220",
|
"@tiptap/core": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
|
"@tiptap/extension-blockquote": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-bold": "^2.0.0-beta.220",
|
"@tiptap/extension-bold": "^2.0.0-beta.220",
|
||||||
|
|
|
@ -34,7 +34,6 @@ import { SessionProvider } from '../context/session'
|
||||||
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
|
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
|
||||||
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
|
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
|
||||||
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
|
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
|
||||||
import { CreateSettingsPage } from '../pages/createSettings.page'
|
|
||||||
import { SnackbarProvider } from '../context/snackbar'
|
import { SnackbarProvider } from '../context/snackbar'
|
||||||
import { LocalizeProvider } from '../context/localize'
|
import { LocalizeProvider } from '../context/localize'
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
|
||||||
expo: LayoutShoutsPage,
|
expo: LayoutShoutsPage,
|
||||||
connect: ConnectPage,
|
connect: ConnectPage,
|
||||||
create: CreatePage,
|
create: CreatePage,
|
||||||
createSettings: CreateSettingsPage,
|
createSettings: CreatePage,
|
||||||
home: HomePage,
|
home: HomePage,
|
||||||
topics: AllTopicsPage,
|
topics: AllTopicsPage,
|
||||||
topic: TopicPage,
|
topic: TopicPage,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createTiptapEditor } from 'solid-tiptap'
|
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Blockquote } from '@tiptap/extension-blockquote'
|
import { Blockquote } from '@tiptap/extension-blockquote'
|
||||||
|
@ -35,9 +35,11 @@ import { TrailingNode } from './extensions/TrailingNode'
|
||||||
import './Prosemirror.scss'
|
import './Prosemirror.scss'
|
||||||
import { EditorBubbleMenu } from './EditorBubbleMenu'
|
import { EditorBubbleMenu } from './EditorBubbleMenu'
|
||||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||||
|
import { createEffect } from 'solid-js'
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
initialContent?: string
|
initialContent?: string
|
||||||
|
onChange: (text: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// const ydoc = new Y.Doc()
|
// const ydoc = new Y.Doc()
|
||||||
|
@ -118,6 +120,12 @@ export const Editor = (props: EditorProps) => {
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const html = useEditorHTML(() => editor())
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
props.onChange(html())
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={(el) => (editorElRef.current = el)} />
|
<div ref={(el) => (editorElRef.current = el)} />
|
||||||
|
|
21
src/components/Editor/TopicSelect/TopicSelect.tsx
Normal file
21
src/components/Editor/TopicSelect/TopicSelect.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { Topic } from '../../../graphql/types.gen'
|
||||||
|
import { createOptions, Select } from '@thisbeyond/solid-select'
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import '@thisbeyond/solid-select/style.css'
|
||||||
|
|
||||||
|
type TopicSelectProps = {
|
||||||
|
topics: Topic[]
|
||||||
|
onChange: (selectedTopics: Topic[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
const selectProps = createOptions(props.topics, { key: 'title' })
|
||||||
|
|
||||||
|
const handleChange = (selectedTopics: Topic[]) => {
|
||||||
|
props.onChange(selectedTopics)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Select multiple={true} {...selectProps} placeholder={t('Topics')} onChange={handleChange} />
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useRouter } from '../../stores/router'
|
import { router, useRouter } from '../../stores/router'
|
||||||
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { createSignal, Show } from 'solid-js'
|
||||||
|
@ -12,6 +12,7 @@ import { showModal, useWarningsStore } from '../../stores/ui'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { getPagePath } from '@nanostores/router'
|
||||||
|
|
||||||
type HeaderAuthProps = {
|
type HeaderAuthProps = {
|
||||||
setIsProfilePopupVisible: (value: boolean) => void
|
setIsProfilePopupVisible: (value: boolean) => void
|
||||||
|
@ -43,12 +44,14 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
<Show when={isSessionLoaded()} keyed={true}>
|
<Show when={isSessionLoaded()} keyed={true}>
|
||||||
<div class={styles.usernav}>
|
<div class={styles.usernav}>
|
||||||
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
||||||
|
<Show when={page().route !== 'create'}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
<a href="/create">
|
<a href={getPagePath(router, 'create')}>
|
||||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||||
<Icon name="pencil" class={styles.icon} />
|
<Icon name="pencil" class={styles.icon} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
<Show when={isAuthenticated()}>
|
<Show when={isAuthenticated()}>
|
||||||
<div class={styles.userControlItem}>
|
<div class={styles.userControlItem}>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
// aka TopicInput
|
|
||||||
|
|
||||||
export default () => <></>
|
|
|
@ -1,42 +1,96 @@
|
||||||
import { lazy, Suspense } from 'solid-js'
|
import { createSignal, lazy, onMount, Show, Suspense } from 'solid-js'
|
||||||
import { Loading } from '../_shared/Loading'
|
import { Loading } from '../_shared/Loading'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './Create.module.scss'
|
import styles from './Create.module.scss'
|
||||||
import { Title } from '@solidjs/meta'
|
import { Title } from '@solidjs/meta'
|
||||||
|
import { createStore } from 'solid-js/store'
|
||||||
|
import type { ShoutInput, Topic } from '../../graphql/types.gen'
|
||||||
|
import { apiClient } from '../../utils/apiClient'
|
||||||
|
import { TopicSelect } from '../Editor/TopicSelect/TopicSelect'
|
||||||
|
|
||||||
const Editor = lazy(() => import('../Editor/Editor'))
|
const Editor = lazy(() => import('../Editor/Editor'))
|
||||||
|
|
||||||
|
type ShoutForm = {
|
||||||
|
slug: string
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
topicSlugs: string[]
|
||||||
|
body: string
|
||||||
|
coverImageUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
export const CreateView = () => {
|
export const CreateView = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
const [topics, setTopics] = createSignal<Topic[]>(null)
|
||||||
|
|
||||||
|
const [form, setForm] = createStore<ShoutForm>({
|
||||||
|
slug: '',
|
||||||
|
title: '',
|
||||||
|
subtitle: '',
|
||||||
|
topicSlugs: [],
|
||||||
|
body: '',
|
||||||
|
coverImageUrl: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const allTopics = await apiClient.getAllTopics()
|
||||||
|
setTopics(allTopics)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleFormSubmit = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>{t('Write an article')}</Title>
|
<Title>{t('Write an article')}</Title>
|
||||||
<Suspense fallback={<Loading />}>
|
<Suspense fallback={<Loading />}>
|
||||||
<form>
|
<form onSubmit={handleFormSubmit}>
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<div class="shift-content">
|
<div class="shift-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-lg-9 col-xl-8">
|
<div class="col-md-10 col-lg-9 col-xl-8">
|
||||||
|
<h4>Slug</h4>
|
||||||
|
<div class="pretty-form__item">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="slug"
|
||||||
|
id="slug"
|
||||||
|
value={form.slug}
|
||||||
|
onChange={(e) => setForm('slug', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<label for="slug">Slug</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4>Заголовок</h4>
|
<h4>Заголовок</h4>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="header"
|
name="title"
|
||||||
id="header"
|
id="title"
|
||||||
placeholder="Придумайте заголовок вашей истории"
|
placeholder="Придумайте заголовок вашей истории"
|
||||||
|
value={form.title}
|
||||||
|
onChange={(e) => setForm('title', e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<label for="header">Придумайте заголовок вашей истории</label>
|
<label for="title">Придумайте заголовок вашей истории</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>Подзаголовок</h4>
|
<h4>Подзаголовок</h4>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<input type="text" name="subheader" id="subheader" placeholder="Подзаголовок" />
|
<input
|
||||||
<label for="subheader">Подзаголовок</label>
|
type="text"
|
||||||
|
name="subtitle"
|
||||||
|
id="subtitle"
|
||||||
|
placeholder="Подзаголовок"
|
||||||
|
value={form.subtitle}
|
||||||
|
onChange={(e) => setForm('subtitle', e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<label for="subtitle">Подзаголовок</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Editor />
|
<Editor onChange={(body) => setForm('body', body)} />
|
||||||
|
|
||||||
<h1>Настройки публикации</h1>
|
<h1>Настройки публикации</h1>
|
||||||
{/*<h4>Лид</h4>*/}
|
{/*<h4>Лид</h4>*/}
|
||||||
|
@ -58,31 +112,38 @@ export const CreateView = () => {
|
||||||
{/*</div>*/}
|
{/*</div>*/}
|
||||||
|
|
||||||
<h4>Темы</h4>
|
<h4>Темы</h4>
|
||||||
<p class="description">
|
{/*<p class="description">*/}
|
||||||
Добавьте несколько тем, чтобы читатель знал, о чем ваш материал, и мог найти
|
{/* Добавьте несколько тем, чтобы читатель знал, о чем ваш материал, и мог найти*/}
|
||||||
его на страницах интересных ему тем. Темы можно менять местами, первая тема
|
{/* его на страницах интересных ему тем. Темы можно менять местами, первая тема*/}
|
||||||
становится заглавной
|
{/* становится заглавной*/}
|
||||||
</p>
|
{/*</p>*/}
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<input type="text" name="topics" id="topics" placeholder="Темы" class="nolabel" />
|
<Show when={topics()}>
|
||||||
|
<TopicSelect
|
||||||
|
topics={topics()}
|
||||||
|
onChange={(selectedTopicSlugs) => setForm('topicSlugs', selectedTopics)}
|
||||||
|
selectedTopicSlugs={form.topicSlugs}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
{/*<input type="text" name="topics" id="topics" placeholder="Темы" class="nolabel" />*/}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>Соавторы</h4>
|
{/*<h4>Соавторы</h4>*/}
|
||||||
<p class="description">У каждого соавтора можно добавить роль</p>
|
{/*<p class="description">У каждого соавтора можно добавить роль</p>*/}
|
||||||
<div class="pretty-form__item--with-button">
|
{/*<div class="pretty-form__item--with-button">*/}
|
||||||
<div class="pretty-form__item">
|
{/* <div class="pretty-form__item">*/}
|
||||||
<input type="text" name="authors" id="authors" placeholder="Введите имя или e-mail" />
|
{/* <input type="text" name="authors" id="authors" placeholder="Введите имя или e-mail" />*/}
|
||||||
<label for="authors">Введите имя или e-mail</label>
|
{/* <label for="authors">Введите имя или e-mail</label>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
<button class="button button--submit">Добавить</button>
|
{/* <button class="button button--submit">Добавить</button>*/}
|
||||||
</div>
|
{/*</div>*/}
|
||||||
|
|
||||||
<div class="row">
|
{/*<div class="row">*/}
|
||||||
<div class="col-md-6">Михаил Драбкин</div>
|
{/* <div class="col-md-6">Михаил Драбкин</div>*/}
|
||||||
<div class="col-md-6">
|
{/* <div class="col-md-6">*/}
|
||||||
<input type="text" name="coauthor" id="coauthor1" class="nolabel" />
|
{/* <input type="text" name="coauthor" id="coauthor1" class="nolabel" />*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
</div>
|
{/*</div>*/}
|
||||||
|
|
||||||
<h4>Карточка материала на главной</h4>
|
<h4>Карточка материала на главной</h4>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
|
@ -96,8 +157,10 @@ export const CreateView = () => {
|
||||||
Проверьте ещё раз введённые данные, если всё верно, вы можете сохранить или
|
Проверьте ещё раз введённые данные, если всё верно, вы можете сохранить или
|
||||||
опубликовать ваш текст
|
опубликовать ваш текст
|
||||||
</p>
|
</p>
|
||||||
<button class={clsx('button button--outline', styles.button)}>Сохранить</button>
|
{/*<button class={clsx('button button--outline', styles.button)}>Сохранить</button>*/}
|
||||||
<button class={clsx('button button--submit', styles.button)}>Опубликовать</button>
|
<button type="submit" class={clsx('button button--submit', styles.button)}>
|
||||||
|
Опубликовать
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
|
||||||
|
|
||||||
export const CreateSettingsPage = () => {
|
|
||||||
return <PageLayout>Настройки публикации</PageLayout>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Page = CreateSettingsPage
|
|
Loading…
Reference in New Issue
Block a user