diff --git a/package-lock.json b/package-lock.json index 5253f9c8..06b8997b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "@solid-primitives/storage": "1.3.9", "@solid-primitives/upload": "0.0.110", "@solidjs/meta": "0.28.2", - "@thisbeyond/solid-select": "0.13.0", + "@thisbeyond/solid-select": "0.14.0", "@tiptap/core": "2.0.3", "@tiptap/extension-blockquote": "2.0.3", "@tiptap/extension-bold": "2.0.3", @@ -5745,9 +5745,9 @@ } }, "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==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@thisbeyond/solid-select/-/solid-select-0.14.0.tgz", + "integrity": "sha512-ecq4U3Vnc/nJbU84ARuPg2scNuYt994ljF5AmBlzuZW87x43mWiGJ5hEWufIJJMpDT6CcnCIx/xbrdDkaDEHQw==", "dev": true, "peerDependencies": { "solid-js": "^1.5" @@ -24828,9 +24828,9 @@ "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==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@thisbeyond/solid-select/-/solid-select-0.14.0.tgz", + "integrity": "sha512-ecq4U3Vnc/nJbU84ARuPg2scNuYt994ljF5AmBlzuZW87x43mWiGJ5hEWufIJJMpDT6CcnCIx/xbrdDkaDEHQw==", "dev": true, "requires": {} }, diff --git a/package.json b/package.json index 0bd48e79..a5cb701f 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@solid-primitives/storage": "1.3.9", "@solid-primitives/upload": "0.0.110", "@solidjs/meta": "0.28.2", - "@thisbeyond/solid-select": "0.13.0", + "@thisbeyond/solid-select": "0.14.0", "@tiptap/core": "2.0.3", "@tiptap/extension-blockquote": "2.0.3", "@tiptap/extension-bold": "2.0.3", diff --git a/src/components/Editor/TopicSelect/TopicSelect.module.scss b/src/components/Editor/TopicSelect/TopicSelect.module.scss new file mode 100644 index 00000000..a2a15c1b --- /dev/null +++ b/src/components/Editor/TopicSelect/TopicSelect.module.scss @@ -0,0 +1,23 @@ +.selectedItem { + cursor: pointer; + + &.mainTopic { + cursor: default; + + &, + + :global(.solid-select-multi-value-remove) { + color: #ccc; + } + + &:before { + background: #000; + content: ''; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: -1; + } + } +} diff --git a/src/components/Editor/TopicSelect/TopicSelect.tsx b/src/components/Editor/TopicSelect/TopicSelect.tsx index 924a4e40..ebab3d12 100644 --- a/src/components/Editor/TopicSelect/TopicSelect.tsx +++ b/src/components/Editor/TopicSelect/TopicSelect.tsx @@ -3,32 +3,77 @@ import { createOptions, Select } from '@thisbeyond/solid-select' import { useLocalize } from '../../../context/localize' import '@thisbeyond/solid-select/style.css' import './TopicSelect.scss' +import styles from './TopicSelect.module.scss' +import { clsx } from 'clsx' +import { createSignal } from 'solid-js' +import { slugify } from '../../../utils/slugify' +import { clone } from '../../../utils/clone' type TopicSelectProps = { topics: Topic[] selectedTopics: Topic[] onChange: (selectedTopics: Topic[]) => void + mainTopic?: Topic + onMainTopicChange: (mainTopic: Topic) => void } export const TopicSelect = (props: TopicSelectProps) => { const { t } = useLocalize() + const [isDisabled, setIsDisabled] = createSignal(false) + + const createValue = (title): Topic => { + const minId = Math.min(...props.selectedTopics.map((topic) => topic.id)) + const id = minId < 0 ? minId - 1 : -2 + return { id, title, slug: slugify(title) } + } + const selectProps = createOptions(props.topics, { key: 'title', disable: (topic) => { - // console.log({ selectedTopics: clone(props.selectedTopics) }) return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug) - } + }, + createable: createValue }) const handleChange = (selectedTopics: Topic[]) => { props.onChange(selectedTopics) } + const handleSelectedItemClick = (topic: Topic) => { + setIsDisabled(true) + props.onMainTopicChange(topic) + setIsDisabled(false) + } + + const format = (item, type) => { + if (type === 'option') { + return item.label + } + + const isMainTopic = item.id === props.mainTopic.id + + return ( +
handleSelectedItemClick(item)} + > + {item.title} +
+ ) + } + + const initialValue = clone(props.selectedTopics) + return ( */} {/*

Соавторы

*/} diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 221b912a..d47877ad 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -1,13 +1,13 @@ import type { JSX } from 'solid-js' import { Accessor, createContext, createSignal, useContext } from 'solid-js' import { createStore, SetStoreFunction } from 'solid-js/store' -import { Topic } from '../graphql/types.gen' +import { Topic, TopicInput } from '../graphql/types.gen' import { apiClient } from '../utils/apiClient' import { useLocalize } from './localize' import { useSnackbar } from './snackbar' -import { translit } from '../utils/ru2en' import { openPage } from '@nanostores/router' import { router, useRouter } from '../stores/router' +import { slugify } from '../utils/slugify' type WordCounter = { characters: number @@ -20,7 +20,7 @@ type ShoutForm = { title: string subtitle: string selectedTopics: Topic[] - mainTopic: string + mainTopic?: Topic body: string coverImageUrl: string } @@ -29,7 +29,7 @@ type EditorContextType = { isEditorPanelVisible: Accessor wordCounter: Accessor form: ShoutForm - formErrors: Partial + formErrors: Record actions: { saveShout: () => Promise publishShout: () => Promise @@ -38,7 +38,7 @@ type EditorContextType = { toggleEditorPanel: () => void countWords: (value: WordCounter) => void setForm: SetStoreFunction - setFormErrors: SetStoreFunction> + setFormErrors: SetStoreFunction> } } @@ -48,6 +48,14 @@ export function useEditorContext() { return useContext(EditorContext) } +const topic2topicInput = (topic: Topic): TopicInput => { + return { + id: topic.id, + slug: topic.slug, + title: topic.title + } +} + export const EditorProvider = (props: { children: JSX.Element }) => { const { t } = useLocalize() @@ -60,7 +68,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal(false) const [form, setForm] = createStore(null) - const [formErrors, setFormErrors] = createStore>(null) + const [formErrors, setFormErrors] = createStore>(null) const [wordCounter, setWordCounter] = createSignal({ characters: 0, @@ -79,31 +87,48 @@ export const EditorProvider = (props: { children: JSX.Element }) => { return true } + const validateSettings = () => { + if (form.selectedTopics.length === 0) { + setFormErrors('selectedTopics', t('Required')) + return false + } + + return true + } + + const updateShout = async ({ publish }: { publish: boolean }) => { + return apiClient.updateArticle({ + shoutId: form.shoutId, + shoutInput: { + body: form.body, + topics: form.selectedTopics.map((topic) => topic2topicInput(topic)), + // authors?: InputMaybe>> + // community?: InputMaybe + mainTopic: topic2topicInput(form.mainTopic), + slug: form.slug, + subtitle: form.subtitle, + title: form.title, + cover: form.coverImageUrl + }, + publish + }) + } + const saveShout = async () => { if (isEditorPanelVisible()) { toggleEditorPanel() } - if (!validate()) { + if (page().route === 'edit' && !validate()) { + return + } + + if (page().route === 'editSettings' && !validateSettings()) { return } try { - const shout = await apiClient.updateArticle({ - shoutId: form.shoutId, - shoutInput: { - body: form.body, - topics: form.selectedTopics.map((topic) => topic.slug), - // authors?: InputMaybe>> - // community?: InputMaybe - mainTopic: form.selectedTopics[0]?.slug || 'society', - slug: form.slug, - subtitle: form.subtitle, - title: form.title, - cover: form.coverImageUrl - }, - publish: false - }) + const shout = await updateShout({ publish: false }) if (shout.visibility === 'owner') { openPage(router, 'drafts') @@ -120,32 +145,26 @@ export const EditorProvider = (props: { children: JSX.Element }) => { if (isEditorPanelVisible()) { toggleEditorPanel() } - if (!validate()) { - return - } + if (page().route === 'edit') { - const slug = translit(form.title.toLowerCase()).replaceAll(' ', '-') + if (!validate()) { + return + } + + await updateShout({ publish: false }) + + const slug = slugify(form.title) setForm('slug', slug) openPage(router, 'editSettings', { shoutId: form.shoutId.toString() }) return } + if (!validateSettings()) { + return + } + try { - await apiClient.updateArticle({ - shoutId: form.shoutId, - shoutInput: { - body: form.body, - topics: form.selectedTopics.map((topic) => topic.slug), - // authors?: InputMaybe>> - // community?: InputMaybe - mainTopic: form.selectedTopics[0]?.slug || 'society', - slug: form.slug, - subtitle: form.subtitle, - title: form.title, - cover: form.coverImageUrl - }, - publish: true - }) + await updateShout({ publish: true }) openPage(router, 'feed') } catch (error) { console.error('[publishShout]', error) diff --git a/src/graphql/query/topics-all.ts b/src/graphql/query/topics-all.ts index d2ab1263..467ac999 100644 --- a/src/graphql/query/topics-all.ts +++ b/src/graphql/query/topics-all.ts @@ -3,6 +3,7 @@ import { gql } from '@urql/core' export default gql` query TopicsAllQuery { topicsAll { + id title body slug diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts index 37795c5b..9bab98ee 100644 --- a/src/graphql/types.gen.ts +++ b/src/graphql/types.gen.ts @@ -582,11 +582,11 @@ export type ShoutInput = { body?: InputMaybe community?: InputMaybe cover?: InputMaybe - mainTopic?: InputMaybe + mainTopic?: InputMaybe slug?: InputMaybe subtitle?: InputMaybe title?: InputMaybe - topics?: InputMaybe>> + topics?: InputMaybe>> } export type ShoutsFilterBy = { @@ -628,7 +628,6 @@ export type Token = { export type Topic = { body?: Maybe - community: Community id: Scalars['Int'] oid?: Maybe pic?: Maybe @@ -639,7 +638,7 @@ export type Topic = { export type TopicInput = { body?: InputMaybe - community: Scalars['String'] + id?: InputMaybe pic?: InputMaybe slug: Scalars['String'] title?: InputMaybe diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 1d60ac44..a0d66a58 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -25,7 +25,16 @@ export const EditPage = () => { return ( - + +
+
Давайте авторизуемся
+
+ + } + > }> diff --git a/src/pages/profile/Settings.module.scss b/src/pages/profile/Settings.module.scss index 24faffb6..3c3302bb 100644 --- a/src/pages/profile/Settings.module.scss +++ b/src/pages/profile/Settings.module.scss @@ -204,9 +204,23 @@ h5 { } :global(.solid-select-multi-value) { + background: none; margin: 0 0.5rem 0.5rem 0; + overflow: hidden; padding-left: 0.8rem; padding-bottom: 0.2rem; + position: relative; + + &:before { + background: rgb(243, 244, 246); + content: ''; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: -1; + } } :global(.solid-select-multi-value-remove) { diff --git a/src/utils/slugify.ts b/src/utils/slugify.ts new file mode 100644 index 00000000..5edb0c94 --- /dev/null +++ b/src/utils/slugify.ts @@ -0,0 +1,7 @@ +import { translit } from './ru2en' + +export const slugify = (text) => { + return translit(text.toLowerCase()) + .replaceAll(/[^\da-z]/g, '') + .replaceAll(' ', '-') +}