Merge branch 'dev' into hotfix/editor-permission
This commit is contained in:
commit
82e2abf8e3
640
package-lock.json
generated
640
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -47,7 +47,7 @@
|
||||||
"@nanostores/solid": "0.4.2",
|
"@nanostores/solid": "0.4.2",
|
||||||
"@playwright/test": "1.41.2",
|
"@playwright/test": "1.41.2",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@sentry/browser": "7.99.0",
|
"@sentry/browser": "^7.113.0",
|
||||||
"@solid-primitives/media": "2.2.3",
|
"@solid-primitives/media": "2.2.3",
|
||||||
"@solid-primitives/memo": "1.2.4",
|
"@solid-primitives/memo": "1.2.4",
|
||||||
"@solid-primitives/pagination": "0.2.10",
|
"@solid-primitives/pagination": "0.2.10",
|
||||||
|
|
|
@ -532,7 +532,7 @@
|
||||||
"resend confirmation link": "отправить ссылку ещё раз",
|
"resend confirmation link": "отправить ссылку ещё раз",
|
||||||
"shout": "пост",
|
"shout": "пост",
|
||||||
"shout not found": "публикация не найдена",
|
"shout not found": "публикация не найдена",
|
||||||
"shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}",
|
"shoutsWithCount": "{count} {count, plural, one {публикация} few {публикации} other {публикаций}}",
|
||||||
"sign in": "войти",
|
"sign in": "войти",
|
||||||
"sign up or sign in": "зарегистрироваться или войти",
|
"sign up or sign in": "зарегистрироваться или войти",
|
||||||
"sign up": "зарегистрироваться",
|
"sign up": "зарегистрироваться",
|
||||||
|
|
|
@ -60,11 +60,13 @@
|
||||||
.bio {
|
.bio {
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
color: var(--black-400);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1rem;
|
|
||||||
color: var(--black-400);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
gap: 1rem;
|
||||||
|
max-width: 100%;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
|
|
@ -27,6 +27,7 @@ type Props = {
|
||||||
inviteView?: boolean
|
inviteView?: boolean
|
||||||
onInvite?: (id: number) => void
|
onInvite?: (id: number) => void
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
|
subscriptionsMode?: boolean
|
||||||
}
|
}
|
||||||
export const AuthorBadge = (props: Props) => {
|
export const AuthorBadge = (props: Props) => {
|
||||||
const { mediaMatches } = useMediaQuery()
|
const { mediaMatches } = useMediaQuery()
|
||||||
|
@ -45,7 +46,6 @@ export const AuthorBadge = (props: Props) => {
|
||||||
setIsMobileView(!mediaMatches.sm)
|
setIsMobileView(!mediaMatches.sm)
|
||||||
})
|
})
|
||||||
|
|
||||||
// const { setFollowing } = useFollowing()
|
|
||||||
const { changeSearchParams } = useRouter()
|
const { changeSearchParams } = useRouter()
|
||||||
const { t, formatDate, lang } = useLocalize()
|
const { t, formatDate, lang } = useLocalize()
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
<div class={clsx('text-truncate', styles.bio)} innerHTML={props.author.bio} />
|
<div class={clsx('text-truncate', styles.bio)} innerHTML={props.author.bio} />
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Show when={props.author?.stat}>
|
<Show when={props.author?.stat && !props.subscriptionsMode}>
|
||||||
<div class={styles.bio}>
|
<div class={styles.bio}>
|
||||||
<Show when={props.author?.stat.shouts > 0}>
|
<Show when={props.author?.stat.shouts > 0}>
|
||||||
<div>{t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })}</div>
|
<div>{t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })}</div>
|
||||||
|
|
|
@ -312,9 +312,9 @@ export const AuthorCard = (props: Props) => {
|
||||||
<For each={authorSubs()}>
|
<For each={authorSubs()}>
|
||||||
{(subscription) =>
|
{(subscription) =>
|
||||||
isAuthor(subscription) ? (
|
isAuthor(subscription) ? (
|
||||||
<AuthorBadge author={subscription} />
|
<AuthorBadge author={subscription} subscriptionsMode={true} />
|
||||||
) : (
|
) : (
|
||||||
<TopicBadge topic={subscription} />
|
<TopicBadge topic={subscription} subscriptionsMode={true} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</For>
|
</For>
|
||||||
|
|
|
@ -151,7 +151,7 @@ export const Editor = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
showSnackbar({ body: t('Uploading image') })
|
showSnackbar({ body: t('Uploading image') })
|
||||||
const result = await handleImageUpload(uplFile)
|
const result = await handleImageUpload(uplFile, session()?.access_token)
|
||||||
|
|
||||||
editor()
|
editor()
|
||||||
.chain()
|
.chain()
|
||||||
|
|
|
@ -28,6 +28,7 @@ const embedData = (data) => {
|
||||||
|
|
||||||
const result: { src: string; width?: string; height?: string } = { src: '' }
|
const result: { src: string; width?: string; height?: string } = { src: '' }
|
||||||
|
|
||||||
|
// biome-ignore lint/style/useForOf: <explanation>
|
||||||
for (let i = 0; i < attributes.length; i++) {
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
const attribute = attributes.item(i)
|
const attribute = attributes.item(i)
|
||||||
if (attribute) {
|
if (attribute) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Icon } from '../../_shared/Icon'
|
||||||
import { Loading } from '../../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../InlineForm'
|
||||||
|
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import styles from './UploadModalContent.module.scss'
|
import styles from './UploadModalContent.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -24,12 +25,12 @@ export const UploadModalContent = (props: Props) => {
|
||||||
const [uploadError, setUploadError] = createSignal<string | undefined>()
|
const [uploadError, setUploadError] = createSignal<string | undefined>()
|
||||||
const [dragActive, setDragActive] = createSignal(false)
|
const [dragActive, setDragActive] = createSignal(false)
|
||||||
const [dragError, setDragError] = createSignal<string | undefined>()
|
const [dragError, setDragError] = createSignal<string | undefined>()
|
||||||
|
const { session } = useSession()
|
||||||
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
||||||
const runUpload = async (file: UploadFile) => {
|
const runUpload = async (file: UploadFile) => {
|
||||||
try {
|
try {
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const result = await handleImageUpload(file)
|
const result = await handleImageUpload(file, session()?.access_token)
|
||||||
props.onClose(result)
|
props.onClose(result)
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -298,10 +298,6 @@
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
padding-left: divide($container-padding-x, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -425,7 +421,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@include media-breakpoint-up(xl) {
|
@include media-breakpoint-up(xl) {
|
||||||
right: 9rem;
|
right: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
|
@ -457,6 +453,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.articleControlsAuthorized {
|
||||||
|
right: 3.6rem;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(xl) {
|
||||||
|
right: 9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.userControl {
|
.userControl {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -484,7 +488,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorControl {
|
.editorControl {
|
||||||
border-radius: 1.2em;
|
border-radius: 2em;
|
||||||
|
font-size: 1.4rem !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--background-color-invert);
|
background: var(--background-color-invert);
|
||||||
|
@ -518,6 +523,7 @@
|
||||||
height: 2.8rem;
|
height: 2.8rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: 0 0.4rem;
|
margin: 0 0.4rem;
|
||||||
|
order: 2;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: margin-left 0.3s;
|
transition: margin-left 0.3s;
|
||||||
width: 2.8rem;
|
width: 2.8rem;
|
||||||
|
@ -528,7 +534,7 @@
|
||||||
width: 3.2rem;
|
width: 3.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(xl) {
|
||||||
margin-left: 0.4rem !important;
|
margin-left: 0.4rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,6 +585,10 @@
|
||||||
|
|
||||||
.userControlItemSearch {
|
.userControlItemSearch {
|
||||||
margin: 0 1rem 0 2.2rem;
|
margin: 0 1rem 0 2.2rem;
|
||||||
|
|
||||||
|
@include media-breakpoint-down(xl) {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.userControlItemUserpic {
|
.userControlItemUserpic {
|
||||||
|
@ -591,13 +601,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.userControlItemInbox,
|
|
||||||
.userControlItemSearch {
|
|
||||||
@include media-breakpoint-down(sm) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.userControlItemVerbose {
|
.userControlItemVerbose {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -677,7 +680,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--link-hover-background);
|
@include media-breakpoint-up(xl) {
|
||||||
|
background-color: var(--link-hover-background);
|
||||||
|
}
|
||||||
|
|
||||||
&,
|
&,
|
||||||
.textLabel {
|
.textLabel {
|
||||||
|
@ -703,6 +708,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.userControlItemCreate {
|
||||||
|
.icon {
|
||||||
|
height: 2.8rem;
|
||||||
|
width: 2.8rem;
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
height: 3.2rem;
|
||||||
|
width: 3.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.subnavigation {
|
.subnavigation {
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
|
@ -59,6 +59,7 @@ export const Header = (props: Props) => {
|
||||||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||||
|
const { isAuthenticated } = useSession()
|
||||||
|
|
||||||
const toggleFixed = () => setFixed(!fixed())
|
const toggleFixed = () => setFixed(!fixed())
|
||||||
|
|
||||||
|
@ -68,7 +69,9 @@ export const Header = (props: Props) => {
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
setRandomTopics(getRandomTopicsFromArray(topics()))
|
if (topics()?.length) {
|
||||||
|
setRandomTopics(getRandomTopicsFromArray(topics()))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -330,7 +333,11 @@ export const Header = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
|
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
|
||||||
<Show when={props.title}>
|
<Show when={props.title}>
|
||||||
<div class={clsx(styles.articleControls, 'col-auto')}>
|
<div
|
||||||
|
class={clsx(styles.articleControls, 'col-auto', {
|
||||||
|
[styles.articleControlsAuthorized]: isAuthenticated(),
|
||||||
|
})}
|
||||||
|
>
|
||||||
<SharePopup
|
<SharePopup
|
||||||
title={props.title}
|
title={props.title}
|
||||||
imageUrl={props.cover}
|
imageUrl={props.cover}
|
||||||
|
|
|
@ -113,8 +113,8 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
<a href={getPagePath(router, '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-outline" class={styles.icon} />
|
||||||
<Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -221,8 +221,8 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
<a href={getPagePath(router, '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-outline" class={styles.icon} />
|
||||||
<Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -235,7 +235,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<a href="?m=auth&mode=login">
|
<a href="?m=auth&mode=login">
|
||||||
<span class={styles.textLabel}>{t('Enter')}</span>
|
<span class={styles.textLabel}>{t('Enter')}</span>
|
||||||
<Icon name="key" class={styles.icon} />
|
<Icon name="key" class={styles.icon} />
|
||||||
{/*<Icon name="user-default" class={clsx(styles.icon, styles.iconHover)} />*/}
|
<Icon name="key" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const ProfileSettings = () => {
|
||||||
const [nameError, setNameError] = createSignal<string>()
|
const [nameError, setNameError] = createSignal<string>()
|
||||||
const { form, submit, updateFormField, setForm } = useProfileForm()
|
const { form, submit, updateFormField, setForm } = useProfileForm()
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const { loadAuthor } = useSession()
|
const { loadAuthor, session } = useSession()
|
||||||
const { showConfirm } = useConfirm()
|
const { showConfirm } = useConfirm()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -140,7 +140,7 @@ export const ProfileSettings = () => {
|
||||||
setUploadError(false)
|
setUploadError(false)
|
||||||
setIsUserpicUpdating(true)
|
setIsUserpicUpdating(true)
|
||||||
|
|
||||||
const result = await handleImageUpload(uploadFile)
|
const result = await handleImageUpload(uploadFile, session()?.access_token)
|
||||||
updateFormField('pic', result.url)
|
updateFormField('pic', result.url)
|
||||||
|
|
||||||
setUserpicFile(null)
|
setUserpicFile(null)
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
.TopicBadge {
|
.TopicBadge {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 4.8rem;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-bottom: .8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.basicInfo {
|
.basicInfo {
|
||||||
|
@ -48,11 +47,62 @@
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
// display: flex;
|
||||||
|
// flex-direction: column;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: unset;
|
background: unset;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background: var(--background-color-invert);
|
||||||
|
color: var(--default-color-invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0.8rem 0;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribeButton {
|
||||||
|
border-radius: 0.8rem !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
width: 9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.TopicBadgeSubscriptionsMode {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
&:hover {
|
||||||
|
.title {
|
||||||
|
background-color: var(--blue-500);
|
||||||
|
color: var(--white-500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
@ -66,7 +116,11 @@
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: var(--black-400);
|
color: var(--black-400);
|
||||||
|
|
||||||
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +142,6 @@
|
||||||
|
|
||||||
color: var(--secondary-color);
|
color: var(--secondary-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 0 0 1em;
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -101,6 +154,7 @@
|
||||||
.statsItem {
|
.statsItem {
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
|
font-weight: 500;
|
||||||
margin-right: 1.6rem;
|
margin-right: 1.6rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ type Props = {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
minimizeSubscribeButton?: boolean
|
minimizeSubscribeButton?: boolean
|
||||||
showStat?: boolean
|
showStat?: boolean
|
||||||
|
subscriptionsMode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicBadge = (props: Props) => {
|
export const TopicBadge = (props: Props) => {
|
||||||
|
@ -47,23 +48,27 @@ export const TopicBadge = (props: Props) => {
|
||||||
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.TopicBadge}>
|
<div class={clsx(styles.TopicBadge, { [styles.TopicBadgeSubscriptionsMode]: props.subscriptionsMode })}>
|
||||||
<div class={styles.content}>
|
<div class={styles.content}>
|
||||||
<div class={styles.basicInfo}>
|
<div class={styles.basicInfo}>
|
||||||
<a
|
<Show when={props.subscriptionsMode}>
|
||||||
href={`/topic/${props.topic.slug}`}
|
<a
|
||||||
class={clsx(styles.picture, {
|
href={`/topic/${props.topic.slug}`}
|
||||||
[styles.withImage]: props.topic.pic,
|
class={clsx(styles.picture, {
|
||||||
[styles.smallSize]: isMobileView(),
|
[styles.withImage]: props.topic.pic,
|
||||||
})}
|
[styles.smallSize]: isMobileView(),
|
||||||
style={
|
})}
|
||||||
props.topic.pic && {
|
style={
|
||||||
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
|
props.topic.pic && {
|
||||||
|
'background-image': `url('${getImageUrl(props.topic.pic, { width: 40, height: 40 })}')`,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</Show>
|
||||||
|
|
||||||
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
||||||
<span class={styles.title}>{title()}</span>
|
<span class={styles.title}>{title()}</span>
|
||||||
|
|
||||||
<Show
|
<Show
|
||||||
when={props.topic.body}
|
when={props.topic.body}
|
||||||
fallback={
|
fallback={
|
||||||
|
@ -86,18 +91,23 @@ export const TopicBadge = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class={styles.stats}>
|
|
||||||
<span class={styles.statsItem}>{t('shoutsWithCount', { count: props.topic?.stat?.shouts })}</span>
|
<Show when={!props.subscriptionsMode}>
|
||||||
<span class={styles.statsItem}>{t('authorsWithCount', { count: props.topic?.stat?.authors })}</span>
|
<div class={styles.stats}>
|
||||||
<span class={styles.statsItem}>
|
<span class={styles.statsItem}>{t('shoutsWithCount', { count: props.topic?.stat?.shouts })}</span>
|
||||||
{t('FollowersWithCount', { count: props.topic?.stat?.followers })}
|
|
||||||
</span>
|
|
||||||
<Show when={props.topic?.stat?.comments}>
|
|
||||||
<span class={styles.statsItem}>
|
<span class={styles.statsItem}>
|
||||||
{t('CommentsWithCount', { count: props.topic?.stat?.comments ?? 0 })}
|
{t('authorsWithCount', { count: props.topic?.stat?.authors })}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
<span class={styles.statsItem}>
|
||||||
</div>
|
{t('FollowersWithCount', { count: props.topic?.stat?.followers })}
|
||||||
|
</span>
|
||||||
|
<Show when={props.topic?.stat?.comments}>
|
||||||
|
<span class={styles.statsItem}>
|
||||||
|
{t('CommentsWithCount', { count: props.topic?.stat?.comments ?? 0 })}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,8 @@ export const AllAuthors = (props: Props) => {
|
||||||
const filteredAuthors = createMemo(() => {
|
const filteredAuthors = createMemo(() => {
|
||||||
const query = searchQuery().toLowerCase()
|
const query = searchQuery().toLowerCase()
|
||||||
return sortedAuthors().filter((author) => {
|
return sortedAuthors().filter((author) => {
|
||||||
return author.name.toLowerCase().includes(query) // Предполагаем, что у автора есть свойство name
|
// Предполагаем, что у автора есть свойство name
|
||||||
|
return author.name.toLowerCase().includes(query)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ export const AllTopics = (props: Props) => {
|
||||||
|
|
||||||
const AllTopicsHead = () => (
|
const AllTopicsHead = () => (
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-20 col-xl-18">
|
<div class="col-lg-18 col-xl-15">
|
||||||
<h1>{t('Topics')}</h1>
|
<h1>{t('Topics')}</h1>
|
||||||
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ export const AllTopics = (props: Props) => {
|
||||||
|
|
||||||
<Show when={filteredResults().length > 0}>
|
<Show when={filteredResults().length > 0}>
|
||||||
<Show when={searchParams().by === 'title'}>
|
<Show when={searchParams().by === 'title'}>
|
||||||
<div class="col-lg-20 col-xl-18">
|
<div class="col-lg-18 col-xl-15">
|
||||||
<ul class={clsx('nodash', styles.alphabet)}>
|
<ul class={clsx('nodash', styles.alphabet)}>
|
||||||
<For each={ALPHABET}>
|
<For each={ALPHABET}>
|
||||||
{(letter, index) => (
|
{(letter, index) => (
|
||||||
|
@ -180,7 +180,7 @@ export const AllTopics = (props: Props) => {
|
||||||
|
|
||||||
<Show when={searchParams().by && searchParams().by !== 'title'}>
|
<Show when={searchParams().by && searchParams().by !== 'title'}>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-20 col-xl-18 py-4">
|
<div class="col-lg-18 col-xl-15 py-4">
|
||||||
<For each={filteredResults().slice(0, limit())}>
|
<For each={filteredResults().slice(0, limit())}>
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
float: none;
|
float: none;
|
||||||
padding-bottom: 0.6rem;
|
padding-bottom: 0.6rem;
|
||||||
padding-top: 0.6rem;
|
padding-top: 0.6rem;
|
||||||
width: 10em;
|
width: 9em;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
img {
|
img {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { clsx } from 'clsx'
|
||||||
import { JSX, Show, createSignal } from 'solid-js'
|
import { JSX, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { handleFileUpload } from '../../../utils/handleFileUpload'
|
import { handleFileUpload } from '../../../utils/handleFileUpload'
|
||||||
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
||||||
|
@ -27,6 +28,7 @@ export const DropArea = (props: Props) => {
|
||||||
const [dragActive, setDragActive] = createSignal(false)
|
const [dragActive, setDragActive] = createSignal(false)
|
||||||
const [dropAreaError, setDropAreaError] = createSignal<string>()
|
const [dropAreaError, setDropAreaError] = createSignal<string>()
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
const { session } = useSession()
|
||||||
|
|
||||||
const runUpload = async (files) => {
|
const runUpload = async (files) => {
|
||||||
try {
|
try {
|
||||||
|
@ -35,7 +37,7 @@ export const DropArea = (props: Props) => {
|
||||||
const results: UploadedFile[] = []
|
const results: UploadedFile[] = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload
|
const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload
|
||||||
const result = await handler(file)
|
const result = await handler(file, session()?.access_token)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
props.onUpload(results)
|
props.onUpload(results)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { Popover } from '../Popover'
|
||||||
|
|
||||||
import { SwiperRef } from './swiper'
|
import { SwiperRef } from './swiper'
|
||||||
|
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import styles from './Swiper.module.scss'
|
import styles from './Swiper.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||||
|
@ -36,7 +37,7 @@ export const EditorSwiper = (props: Props) => {
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
const [slideIndex, setSlideIndex] = createSignal(0)
|
const [slideIndex, setSlideIndex] = createSignal(0)
|
||||||
const [slideBody, setSlideBody] = createSignal<string>()
|
const [slideBody, setSlideBody] = createSignal<string>()
|
||||||
|
const { session } = useSession()
|
||||||
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ export const EditorSwiper = (props: Props) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const results: UploadedFile[] = []
|
const results: UploadedFile[] = []
|
||||||
for (const file of selectedFiles) {
|
for (const file of selectedFiles) {
|
||||||
const result = await handleImageUpload(file)
|
const result = await handleImageUpload(file, session()?.access_token)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
props.onImagesAdd(composeMediaItems(results))
|
props.onImagesAdd(composeMediaItems(results))
|
||||||
|
|
|
@ -5,12 +5,15 @@ import { UploadedFile } from '../pages/types'
|
||||||
const apiBaseUrl = 'https://core.discours.io'
|
const apiBaseUrl = 'https://core.discours.io'
|
||||||
const apiUrl = `${apiBaseUrl}/upload`
|
const apiUrl = `${apiBaseUrl}/upload`
|
||||||
|
|
||||||
export const handleFileUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => {
|
export const handleFileUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', uploadFile.file, uploadFile.name)
|
formData.append('file', uploadFile.file, uploadFile.name)
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
Authorization: token,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import { UploadedFile } from '../pages/types'
|
||||||
|
|
||||||
import { thumborUrl } from './config'
|
import { thumborUrl } from './config'
|
||||||
|
|
||||||
export const handleImageUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => {
|
export const handleImageUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('media', uploadFile.file, uploadFile.name)
|
formData.append('media', uploadFile.file, uploadFile.name)
|
||||||
|
const headers = token ? { Authorization: token } : {}
|
||||||
const response = await fetch(`${thumborUrl}/image`, {
|
const response = await fetch(`${thumborUrl}/image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
headers,
|
||||||
})
|
})
|
||||||
|
|
||||||
const location = response.headers.get('Location')
|
const location = response.headers.get('Location')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user