Feature/lint (#317)

* prettier

---------

Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
Ilya Y 2023-11-14 18:10:00 +03:00 committed by GitHub
parent 4162d4318c
commit 784bb435c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
216 changed files with 2019 additions and 1626 deletions

View File

@ -1,41 +1,41 @@
module.exports = {
plugins: ['@typescript-eslint', 'import', 'sonarjs', 'unicorn', 'promise', 'solid', 'jest'],
plugins: ["@typescript-eslint", "import", "sonarjs", "unicorn", "promise", "solid", "jest"],
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier',
'plugin:sonarjs/recommended',
'plugin:unicorn/recommended',
'plugin:promise/recommended',
'plugin:solid/recommended',
'plugin:jest/recommended'
"eslint:recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier",
"plugin:sonarjs/recommended",
"plugin:unicorn/recommended",
"plugin:promise/recommended",
"plugin:solid/recommended",
"plugin:jest/recommended"
],
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
files: ["**/*.ts", "**/*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2021,
ecmaFeatures: { jsx: true },
sourceType: 'module',
project: './tsconfig.json'
sourceType: "module",
project: "./tsconfig.json"
},
extends: [
'plugin:@typescript-eslint/recommended'
"plugin:@typescript-eslint/recommended"
// Maybe one day...
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: '^_'
argsIgnorePattern: "^_"
}
],
'@typescript-eslint/no-non-null-assertion': 'error',
"@typescript-eslint/no-non-null-assertion": "error",
// TODO: Remove any usage and enable
'@typescript-eslint/no-explicit-any': 'off'
"@typescript-eslint/no-explicit-any": "off"
}
}
],
@ -47,42 +47,60 @@ module.exports = {
globals: {},
rules: {
// Solid
'solid/reactivity': 'off', // FIXME
'solid/no-innerhtml': 'off',
"solid/reactivity": "off", // FIXME
"solid/no-innerhtml": "off",
/** Unicorn **/
'unicorn/no-null': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/no-array-reduce': 'off',
'unicorn/prefer-string-replace-all': 'warn',
'unicorn/prevent-abbreviations': 'off',
'unicorn/prefer-module': 'off',
'unicorn/import-style': 'off',
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/prefer-dom-node-append': 'off', // FIXME
'unicorn/prefer-top-level-await': 'warn',
'unicorn/consistent-function-scoping': 'warn',
'unicorn/no-array-callback-reference': 'warn',
'unicorn/no-array-method-this-argument': 'warn',
'unicorn/no-for-loop': 'off',
"unicorn/no-null": "off",
"unicorn/filename-case": "off",
"unicorn/no-array-for-each": "off",
"unicorn/no-array-reduce": "off",
"unicorn/prefer-string-replace-all": "warn",
"unicorn/prevent-abbreviations": "off",
"unicorn/prefer-module": "off",
"unicorn/import-style": "off",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-dom-node-append": "off", // FIXME
"unicorn/prefer-top-level-await": "warn",
"unicorn/consistent-function-scoping": "warn",
"unicorn/no-array-callback-reference": "warn",
"unicorn/no-array-method-this-argument": "warn",
"unicorn/no-for-loop": "off",
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
"sonarjs/no-duplicate-string": ["warn", { threshold: 5 }],
// Promise
// 'promise/catch-or-return': 'off', // Should be enabled
'promise/always-return': 'off',
"promise/always-return": "off",
eqeqeq: 'error',
'no-param-reassign': 'error',
'no-nested-ternary': 'error',
'no-shadow': 'error'
eqeqeq: "error",
"no-param-reassign": "error",
"no-nested-ternary": "error",
"no-shadow": "error",
"import/order": ["warn", {
groups: ["type", "builtin", "external", "internal", "parent", "sibling", "index"],
distinctGroup: false,
pathGroups: [
{
pattern: "*.scss",
patternOptions: { matchBase: true },
group: "index",
position: "after"
}
],
"newlines-between": "always",
alphabetize: {
order: "asc",
caseInsensitive: true
}
}]
},
settings: {
'import/resolver': {
"import/resolver": {
typescript: true,
node: true
}
}
}
};

View File

@ -4,7 +4,6 @@
"singleQuote": true,
"proseWrap": "always",
"printWidth": 108,
"trailingComma": "none",
"plugins": [],
"overrides": [
{

View File

@ -1,7 +1,7 @@
import { renderPage } from 'vike/server'
export const config = {
runtime: 'edge'
runtime: 'edge',
}
export default async function handler(request) {
const { url, cookies } = request

View File

@ -15,7 +15,7 @@ export default async function handler(req, res) {
from: 'Discours Feedback Robot <robot@discours.io>',
to: 'welcome@discours.io',
subject,
text
text,
}
try {

View File

@ -13,18 +13,18 @@ export default async (req, res) => {
const response = await mg.lists.members.createMember('newsletter@discours.io', {
address: email,
subscribed: true,
upsert: 'yes'
upsert: 'yes',
})
return res.status(200).json({
success: true,
message: 'Email was added to newsletter list',
response: JSON.stringify(response)
response: JSON.stringify(response),
})
} catch (error) {
return res.status(400).json({
success: false,
message: error.message
message: error.message,
})
}
}

View File

@ -1,18 +1,15 @@
import { hideModal, MODALS, showModal } from '../stores/ui'
import type { PageProps, RootSearchParams } from '../pages/types'
import { Meta, MetaProvider } from '@solidjs/meta'
import { Component, createEffect, createMemo } from 'solid-js'
import { ROUTES, useRouter } from '../stores/router'
import { Dynamic } from 'solid-js/web'
import type { PageProps, RootSearchParams } from '../pages/types'
import { HomePage } from '../pages/index.page'
import { AllTopicsPage } from '../pages/allTopics.page'
import { TopicPage } from '../pages/topic.page'
import { AllAuthorsPage } from '../pages/allAuthors.page'
import { AuthorPage } from '../pages/author.page'
import { FeedPage } from '../pages/feed.page'
import { ArticlePage } from '../pages/article.page'
import { SearchPage } from '../pages/search.page'
import { FourOuFourPage } from '../pages/fourOuFour.page'
import { ConfirmProvider } from '../context/confirm'
import { EditorProvider } from '../context/editor'
import { LocalizeProvider } from '../context/localize'
import { NotificationsProvider } from '../context/notifications'
import { SessionProvider } from '../context/session'
import { SnackbarProvider } from '../context/snackbar'
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
import { DogmaPage } from '../pages/about/dogma.page'
import { GuidePage } from '../pages/about/guide.page'
@ -23,22 +20,26 @@ import { PrinciplesPage } from '../pages/about/principles.page'
import { ProjectsPage } from '../pages/about/projects.page'
import { TermsOfUsePage } from '../pages/about/termsOfUse.page'
import { ThanksPage } from '../pages/about/thanks.page'
import { CreatePage } from '../pages/create.page'
import { EditPage } from '../pages/edit.page'
import { AllAuthorsPage } from '../pages/allAuthors.page'
import { AllTopicsPage } from '../pages/allTopics.page'
import { ArticlePage } from '../pages/article.page'
import { AuthorPage } from '../pages/author.page'
import { ConnectPage } from '../pages/connect.page'
import { InboxPage } from '../pages/inbox.page'
import { ExpoPage } from '../pages/expo/expo.page'
import { SessionProvider } from '../context/session'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { CreatePage } from '../pages/create.page'
import { DraftsPage } from '../pages/drafts.page'
import { SnackbarProvider } from '../context/snackbar'
import { LocalizeProvider } from '../context/localize'
import { ConfirmProvider } from '../context/confirm'
import { EditorProvider } from '../context/editor'
import { NotificationsProvider } from '../context/notifications'
import { Meta, MetaProvider } from '@solidjs/meta'
import { EditPage } from '../pages/edit.page'
import { ExpoPage } from '../pages/expo/expo.page'
import { FeedPage } from '../pages/feed.page'
import { FourOuFourPage } from '../pages/fourOuFour.page'
import { InboxPage } from '../pages/inbox.page'
import { HomePage } from '../pages/index.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { SearchPage } from '../pages/search.page'
import { TopicPage } from '../pages/topic.page'
import { ROUTES, useRouter } from '../stores/router'
import { hideModal, MODALS, showModal } from '../stores/ui'
// TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage'))
@ -80,7 +81,7 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
profileSettings: ProfileSettingsPage,
profileSecurity: ProfileSecurityPage,
profileSubscriptions: ProfileSubscriptionsPage,
fourOuFour: FourOuFourPage
fourOuFour: FourOuFourPage,
}
export const App = (props: PageProps) => {

View File

@ -1,11 +1,13 @@
import { clsx } from 'clsx'
import styles from './AudioHeader.module.scss'
import { MediaItem } from '../../../pages/types'
import { createSignal, Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import { Topic } from '../../../graphql/types.gen'
import { CardTopic } from '../../Feed/CardTopic'
import { MediaItem } from '../../../pages/types'
import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image'
import { CardTopic } from '../../Feed/CardTopic'
import styles from './AudioHeader.module.scss'
type Props = {
title: string

View File

@ -1,8 +1,11 @@
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
import { MediaItem } from '../../../pages/types'
import { PlayerHeader } from './PlayerHeader'
import { PlayerPlaylist } from './PlayerPlaylist'
import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types'
type Props = {
media: MediaItem[]
@ -35,8 +38,8 @@ export const AudioPlayer = (props: Props) => {
() => {
setCurrentTrackDuration(0)
},
{ defer: true }
)
{ defer: true },
),
)
const handlePlayMedia = async (trackIndex: number) => {
@ -131,7 +134,7 @@ export const AudioPlayer = (props: Props) => {
<div
class={styles.progressFilled}
style={{
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`,
}}
/>
</div>

View File

@ -1,11 +1,11 @@
import { createSignal, Show } from 'solid-js'
import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
type Props = {
onPlayMedia: () => void
@ -18,7 +18,7 @@ type Props = {
export const PlayerHeader = (props: Props) => {
const volumeContainerRef: { current: HTMLDivElement } = {
current: null
current: null,
}
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
@ -30,7 +30,7 @@ export const PlayerHeader = (props: Props) => {
useOutsideClickHandler({
containerRef: volumeContainerRef,
predicate: () => isVolumeBarOpened(),
handler: () => toggleVolumeBar()
handler: () => toggleVolumeBar(),
})
return (
@ -42,7 +42,7 @@ export const PlayerHeader = (props: Props) => {
onClick={props.onPlayMedia}
class={clsx(
styles.playButton,
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay,
)}
aria-label="Play"
data-playing="false"

View File

@ -1,13 +1,15 @@
import { createSignal, For, Show } from 'solid-js'
import { SharePopup, getShareUrl } from '../SharePopup'
import { getDescription } from '../../../utils/meta'
import { useLocalize } from '../../../context/localize'
import { Popover } from '../../_shared/Popover'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
import { MediaItem } from '../../../pages/types'
import { getDescription } from '../../../utils/meta'
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
import { SharePopup, getShareUrl } from '../SharePopup'
import styles from './AudioPlayer.module.scss'
type Props = {
media: MediaItem[]

View File

@ -1,24 +1,22 @@
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
import { clsx } from 'clsx'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
import { Userpic } from '../../Author/Userpic'
import { CommentRatingControl } from '../CommentRatingControl'
import { CommentDate } from '../CommentDate'
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
import { Icon } from '../../_shared/Icon'
import { useSession } from '../../../context/session'
import { useConfirm } from '../../../context/confirm'
import { useLocalize } from '../../../context/localize'
import { useReactions } from '../../../context/reactions'
import { useSession } from '../../../context/session'
import { useSnackbar } from '../../../context/snackbar'
import { useConfirm } from '../../../context/confirm'
import { Author, Reaction, ReactionKind } from '../../../graphql/types.gen'
import { router } from '../../../stores/router'
import { Icon } from '../../_shared/Icon'
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
import { AuthorLink } from '../../Author/AhtorLink'
import { Userpic } from '../../Author/Userpic'
import { CommentDate } from '../CommentDate'
import { CommentRatingControl } from '../CommentRatingControl'
import styles from './Comment.module.scss'
import { AuthorLink } from '../../Author/AhtorLink'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
@ -43,15 +41,15 @@ export const Comment = (props: Props) => {
const { session } = useSession()
const {
actions: { createReaction, deleteReaction, updateReaction }
actions: { createReaction, deleteReaction, updateReaction },
} = useReactions()
const {
actions: { showConfirm }
actions: { showConfirm },
} = useConfirm()
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
@ -65,7 +63,7 @@ export const Comment = (props: Props) => {
confirmBody: t('Are you sure you want to delete this comment?'),
confirmButtonLabel: t('Delete'),
confirmButtonVariant: 'danger',
declineButtonVariant: 'primary'
declineButtonVariant: 'primary',
})
if (isConfirmed) {
@ -86,7 +84,7 @@ export const Comment = (props: Props) => {
kind: ReactionKind.Comment,
replyTo: props.comment.id,
body: value,
shout: props.comment.shout.id
shout: props.comment.shout.id,
})
setClearEditor(true)
setIsReplyVisible(false)
@ -107,7 +105,7 @@ export const Comment = (props: Props) => {
await updateReaction(props.comment.id, {
kind: ReactionKind.Comment,
body: value,
shout: props.comment.shout.id
shout: props.comment.shout.id,
})
setEditMode(false)
setLoading(false)
@ -122,7 +120,7 @@ export const Comment = (props: Props) => {
<li
id={`comment_${comment().id}`}
class={clsx(styles.comment, props.class, {
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen,
})}
>
<Show when={!!body()}>
@ -135,7 +133,7 @@ export const Comment = (props: Props) => {
name={comment().createdBy.name}
userpic={comment().createdBy.userpic}
class={clsx({
[styles.compactUserpic]: props.compact
[styles.compactUserpic]: props.compact,
})}
/>
<small>

View File

@ -1,8 +1,11 @@
import { Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import type { Reaction } from '../../../graphql/types.gen'
import { useLocalize } from '../../../context/localize'
import { clsx } from 'clsx'
import { Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import styles from './CommentDate.module.scss'
type Props = {
@ -27,7 +30,7 @@ export const CommentDate = (props: Props) => {
<div
class={clsx(styles.commentDates, {
[styles.commentDatesLastInRow]: props.isLastInRow,
[styles.showOnHover]: props.showOnHover
[styles.showOnHover]: props.showOnHover,
})}
>
<time class={styles.date}>{formattedDate(props.comment.createdAt)}</time>

View File

@ -1,16 +1,19 @@
import { clsx } from 'clsx'
import styles from './CommentRatingControl.module.scss'
import type { Reaction } from '../../graphql/types.gen'
import { ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { useReactions } from '../../context/reactions'
import { clsx } from 'clsx'
import { createMemo } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/snackbar'
import { ReactionKind } from '../../graphql/types.gen'
import { loadShout } from '../../stores/zine/articles'
import { Popup } from '../_shared/Popup'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { VotersList } from '../_shared/VotersList'
import styles from './CommentRatingControl.module.scss'
type Props = {
comment: Reaction
}
@ -19,11 +22,11 @@ export const CommentRatingControl = (props: Props) => {
const { t } = useLocalize()
const { user } = useSession()
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const {
reactionEntities,
actions: { createReaction, deleteReaction, loadReactionsBy }
actions: { createReaction, deleteReaction, loadReactionsBy },
} = useReactions()
const checkReaction = (reactionKind: ReactionKind) =>
@ -32,7 +35,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind &&
r.createdBy.slug === user()?.slug &&
r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id
r.replyTo === props.comment.id,
)
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
@ -43,8 +46,8 @@ export const CommentRatingControl = (props: Props) => {
(r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id
)
r.replyTo === props.comment.id,
),
)
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
@ -53,7 +56,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind &&
r.createdBy.slug === user()?.slug &&
r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id
r.replyTo === props.comment.id,
)
return deleteReaction(reactionToDelete.id)
}
@ -68,7 +71,7 @@ export const CommentRatingControl = (props: Props) => {
await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.comment.shout.id,
replyTo: props.comment.id
replyTo: props.comment.id,
})
}
} catch {
@ -77,7 +80,7 @@ export const CommentRatingControl = (props: Props) => {
await loadShout(props.comment.shout.slug)
await loadReactionsBy({
by: { shout: props.comment.shout.slug }
by: { shout: props.comment.shout.slug },
})
}
@ -88,7 +91,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!canVote() || !user()}
onClick={() => handleRatingChange(true)}
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
[styles.voted]: isUpvoted()
[styles.voted]: isUpvoted(),
})}
/>
<Popup
@ -96,7 +99,7 @@ export const CommentRatingControl = (props: Props) => {
<div
class={clsx(styles.commentRatingValue, {
[styles.commentRatingPositive]: props.comment.stat.rating > 0,
[styles.commentRatingNegative]: props.comment.stat.rating < 0
[styles.commentRatingNegative]: props.comment.stat.rating < 0,
})}
>
{props.comment.stat.rating || 0}
@ -114,7 +117,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!canVote() || !user()}
onClick={() => handleRatingChange(false)}
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
[styles.voted]: isDownvoted()
[styles.voted]: isDownvoted(),
})}
/>
</div>

View File

@ -1,16 +1,19 @@
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
import { Comment } from './Comment'
import styles from './Article.module.scss'
import { clsx } from 'clsx'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { Button } from '../_shared/Button'
import { useReactions } from '../../context/reactions'
import { byCreated } from '../../utils/sortby'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { byCreated } from '../../utils/sortby'
import { Button } from '../_shared/Button'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import SimplifiedEditor from '../Editor/SimplifiedEditor'
import { Comment } from './Comment'
import styles from './Article.module.scss'
type CommentsOrder = 'createdAt' | 'rating' | 'newOnly'
const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {
@ -48,11 +51,11 @@ export const CommentsTree = (props: Props) => {
const {
reactionEntities,
actions: { createReaction }
actions: { createReaction },
} = useReactions()
const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
)
const sortedComments = createMemo(() => {
@ -96,7 +99,7 @@ export const CommentsTree = (props: Props) => {
await createReaction({
kind: ReactionKind.Comment,
body: value,
shout: props.shoutId
shout: props.shoutId,
})
setClearEditor(true)
} catch (error) {
@ -154,7 +157,7 @@ export const CommentsTree = (props: Props) => {
<Comment
sortedComments={sortedComments()}
isArticleAuthor={Boolean(
props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug)
props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug),
)}
comment={reaction}
clickedReply={(id) => setClickedReplyId(id)}

View File

@ -1,34 +1,36 @@
import type { Author, Shout } from '../../graphql/types.gen'
import { getPagePath } from '@nanostores/router'
import { createPopper } from '@popperjs/core'
import { clsx } from 'clsx'
import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js'
import { clsx } from 'clsx'
import { getPagePath } from '@nanostores/router'
import type { Author, Shout } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { MediaItem } from '../../pages/types'
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
import { getImageUrl } from '../../utils/getImageUrl'
import { getDescription } from '../../utils/meta'
import { Icon } from '../_shared/Icon'
import { Image } from '../_shared/Image'
import { Lightbox } from '../_shared/Lightbox'
import { Popover } from '../_shared/Popover'
import { ImageSwiper } from '../_shared/SolidSwiper'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { AuthorBadge } from '../Author/AuthorBadge'
import { CardTopic } from '../Feed/CardTopic'
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
import { TableOfContents } from '../TableOfContents'
import { AudioHeader } from './AudioHeader'
import { AudioPlayer } from './AudioPlayer'
import { CommentsTree } from './CommentsTree'
import { getShareUrl, SharePopup } from './SharePopup'
import { ShoutRatingControl } from './ShoutRatingControl'
import { CommentsTree } from './CommentsTree'
import stylesHeader from '../Nav/Header/Header.module.scss'
import { AudioHeader } from './AudioHeader'
import { Popover } from '../_shared/Popover'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { Icon } from '../_shared/Icon'
import { ImageSwiper } from '../_shared/SolidSwiper'
import styles from './Article.module.scss'
import { CardTopic } from '../Feed/CardTopic'
import { createPopper } from '@popperjs/core'
import { AuthorBadge } from '../Author/AuthorBadge'
import { getImageUrl } from '../../utils/getImageUrl'
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
import { Lightbox } from '../_shared/Lightbox'
import { Image } from '../_shared/Image'
import article from '../Editor/extensions/Article'
import stylesHeader from '../Nav/Header/Header.module.scss'
type Props = {
article: Shout
@ -46,7 +48,7 @@ const scrollTo = (el: HTMLElement) => {
window.scrollTo({
top: top + window.scrollY - DEFAULT_HEADER_OFFSET,
left: 0,
behavior: 'smooth'
behavior: 'smooth',
})
}
@ -57,7 +59,7 @@ export const FullArticle = (props: Props) => {
const {
user,
isAuthenticated,
actions: { requireAuthentication }
actions: { requireAuthentication },
} = useSession()
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
@ -67,7 +69,7 @@ export const FullArticle = (props: Props) => {
const mainTopic = createMemo(
() =>
props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic) ||
props.article.topics[0]
props.article.topics[0],
)
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
@ -121,7 +123,7 @@ export const FullArticle = (props: Props) => {
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
scrollToComments()
changeSearchParam({
scrollTo: null
scrollTo: null,
})
}
})
@ -129,7 +131,7 @@ export const FullArticle = (props: Props) => {
createEffect(() => {
if (searchParams().commentId && isReactionsLoaded()) {
const commentElement = document.querySelector<HTMLElement>(
`[id='comment_${searchParams().commentId}']`
`[id='comment_${searchParams().commentId}']`,
)
changeSearchParam({ commentId: null })
@ -141,12 +143,12 @@ export const FullArticle = (props: Props) => {
})
const {
actions: { loadReactionsBy }
actions: { loadReactionsBy },
} = useReactions()
onMount(async () => {
await loadReactionsBy({
by: { shout: props.article.slug }
by: { shout: props.article.slug },
})
setIsReactionsLoaded(true)
@ -165,7 +167,7 @@ export const FullArticle = (props: Props) => {
}
const tooltipElements: NodeListOf<HTMLElement> = document.querySelectorAll(
'[data-toggle="tooltip"], footnote'
'[data-toggle="tooltip"], footnote',
)
if (!tooltipElements) {
return
@ -190,19 +192,19 @@ export const FullArticle = (props: Props) => {
modifiers: [
{
name: 'eventListeners',
options: { scroll: false }
options: { scroll: false },
},
{
name: 'offset',
options: {
offset: [0, 8]
}
offset: [0, 8],
},
},
{
name: 'flip',
options: { fallbackPlacements: ['top'] }
}
]
options: { fallbackPlacements: ['top'] },
},
],
})
tooltip.style.visibility = 'hidden'

View File

@ -1,12 +1,14 @@
import { Icon } from '../_shared/Icon'
import type { PopupProps } from '../_shared/Popup'
import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share'
import styles from '../_shared/Popup/Popup.module.scss'
import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
import { useLocalize } from '../../context/localize'
import { createEffect, createSignal } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { Icon } from '../_shared/Icon'
import { Popup } from '../_shared/Popup'
import styles from '../_shared/Popup/Popup.module.scss'
type SharePopupProps = {
title: string
@ -26,7 +28,7 @@ export const SharePopup = (props: SharePopupProps) => {
const { t } = useLocalize()
const [isVisible, setIsVisible] = createSignal(false)
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
createEffect(() => {
@ -38,7 +40,7 @@ export const SharePopup = (props: SharePopupProps) => {
const [share] = createSocialShare(() => ({
title: props.title,
url: props.shareUrl,
description: props.description
description: props.description,
}))
const copyLink = async () => {

View File

@ -1,13 +1,15 @@
import { clsx } from 'clsx'
import { createMemo, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { ReactionKind, Shout } from '../../graphql/types.gen'
import { loadShout } from '../../stores/zine/articles'
import { useSession } from '../../context/session'
import { useReactions } from '../../context/reactions'
import { Icon } from '../_shared/Icon'
import { Popup } from '../_shared/Popup'
import { VotersList } from '../_shared/VotersList'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import styles from './ShoutRatingControl.module.scss'
interface ShoutRatingControlProps {
@ -19,12 +21,12 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
const { t } = useLocalize()
const {
user,
actions: { requireAuthentication }
actions: { requireAuthentication },
} = useSession()
const {
reactionEntities,
actions: { createReaction, deleteReaction, loadReactionsBy }
actions: { createReaction, deleteReaction, loadReactionsBy },
} = useReactions()
const checkReaction = (reactionKind: ReactionKind) =>
@ -33,7 +35,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
r.kind === reactionKind &&
r.createdBy.slug === user()?.slug &&
r.shout.id === props.shout.id &&
!r.replyTo
!r.replyTo,
)
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
@ -45,8 +47,8 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
(r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.shout.id &&
!r.replyTo
)
!r.replyTo,
),
)
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
@ -55,7 +57,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
r.kind === reactionKind &&
r.createdBy.slug === user()?.slug &&
r.shout.id === props.shout.id &&
!r.replyTo
!r.replyTo,
)
return deleteReaction(reactionToDelete.id)
}
@ -69,13 +71,13 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
} else {
await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.shout.id
shout: props.shout.id,
})
}
loadShout(props.shout.slug)
loadReactionsBy({
by: { shout: props.shout.slug }
by: { shout: props.shout.slug },
})
}, 'vote')
}

View File

@ -1,8 +1,9 @@
import { createEffect, JSX, Show } from 'solid-js'
import { useSession } from '../../context/session'
import { hideModal } from '../../stores/ui'
import { useRouter } from '../../stores/router'
import { RootSearchParams } from '../../pages/types'
import { useRouter } from '../../stores/router'
import { hideModal } from '../../stores/ui'
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
type Props = {
@ -25,9 +26,9 @@ export const AuthGuard = (props: Props) => {
changeSearchParam(
{
source: 'authguard',
modal: 'auth'
modal: 'auth',
},
true
true,
)
}
}

View File

@ -1,8 +1,10 @@
import { clsx } from 'clsx'
import styles from './AhtorLink.module.scss'
import { Author } from '../../../graphql/types.gen'
import { Userpic } from '../Userpic'
import styles from './AhtorLink.module.scss'
type Props = {
author: Author
size?: 'XS' | 'M' | 'L'

View File

@ -1,17 +1,19 @@
import { openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { Author, FollowingEntity } from '../../../graphql/types.gen'
import { router, useRouter } from '../../../stores/router'
import { follow, unfollow } from '../../../stores/zine/common'
import { Button } from '../../_shared/Button'
import { CheckButton } from '../../_shared/CheckButton'
import { Icon } from '../../_shared/Icon'
import { Userpic } from '../Userpic'
import styles from './AuthorBadge.module.scss'
import stylesButton from '../../_shared/Button/Button.module.scss'
import { Userpic } from '../Userpic'
import { Author, FollowingEntity } from '../../../graphql/types.gen'
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Button } from '../../_shared/Button'
import { useSession } from '../../../context/session'
import { follow, unfollow } from '../../../stores/zine/common'
import { CheckButton } from '../../_shared/CheckButton'
import { openPage } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router'
import { Icon } from '../../_shared/Icon'
type Props = {
author: Author
@ -25,12 +27,12 @@ export const AuthorBadge = (props: Props) => {
const {
session,
subscriptions,
actions: { loadSubscriptions, requireAuthentication }
actions: { loadSubscriptions, requireAuthentication },
} = useSession()
const { changeSearchParam } = useRouter()
const { t, formatDate } = useLocalize()
const subscribed = createMemo(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug)
subscriptions().authors.some((author) => author.slug === props.author.slug),
)
const subscribe = async (really = true) => {
@ -53,7 +55,7 @@ export const AuthorBadge = (props: Props) => {
requireAuthentication(() => {
openPage(router, `inbox`)
changeSearchParam({
initChat: props.author.id.toString()
initChat: props.author.id.toString(),
})
}, 'discussions')
}
@ -126,7 +128,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: subscribed()
[stylesButton.subscribed]: subscribed(),
})}
/>
}
@ -151,7 +153,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: subscribed()
[stylesButton.subscribed]: subscribed(),
})}
/>
</Show>

View File

@ -1,22 +1,25 @@
import type { Author } from '../../../graphql/types.gen'
import { Userpic } from '../Userpic'
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import { translit } from '../../../utils/ru2en'
import { follow, unfollow } from '../../../stores/zine/common'
import { clsx } from 'clsx'
import { useSession } from '../../../context/session'
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
import { router, useRouter } from '../../../stores/router'
import { openPage, redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Modal } from '../../Nav/Modal'
import { useSession } from '../../../context/session'
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
import { SubscriptionFilter } from '../../../pages/types'
import { router, useRouter } from '../../../stores/router'
import { follow, unfollow } from '../../../stores/zine/common'
import { isAuthor } from '../../../utils/isAuthor'
import { AuthorBadge } from '../AuthorBadge'
import { TopicBadge } from '../../Topic/TopicBadge'
import { translit } from '../../../utils/ru2en'
import { Button } from '../../_shared/Button'
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { Modal } from '../../Nav/Modal'
import { TopicBadge } from '../../Topic/TopicBadge'
import { AuthorBadge } from '../AuthorBadge'
import { Userpic } from '../Userpic'
import styles from './AuthorCard.module.scss'
import stylesButton from '../../_shared/Button/Button.module.scss'
@ -32,7 +35,7 @@ export const AuthorCard = (props: Props) => {
session,
subscriptions,
isSessionLoaded,
actions: { loadSubscriptions, requireAuthentication }
actions: { loadSubscriptions, requireAuthentication },
} = useSession()
const [isSubscribing, setIsSubscribing] = createSignal(false)
@ -40,7 +43,7 @@ export const AuthorCard = (props: Props) => {
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const subscribed = createMemo<boolean>(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug)
subscriptions().authors.some((author) => author.slug === props.author.slug),
)
const subscribe = async (really = true) => {
@ -74,7 +77,7 @@ export const AuthorCard = (props: Props) => {
requireAuthentication(() => {
openPage(router, `inbox`)
changeSearchParam({
initChat: props.author.id.toString()
initChat: props.author.id.toString(),
})
}, 'discussions')
}
@ -218,7 +221,7 @@ export const AuthorCard = (props: Props) => {
value={followButtonText()}
isSubscribeButton={true}
class={clsx({
[stylesButton.subscribed]: subscribed()
[stylesButton.subscribed]: subscribed(),
})}
/>
<Button

View File

@ -1,7 +1,9 @@
import styles from './AuthorRatingControl.module.scss'
import { clsx } from 'clsx'
import type { Author } from '../../graphql/types.gen'
import { clsx } from 'clsx'
import styles from './AuthorRatingControl.module.scss'
interface AuthorRatingControlProps {
author: Author
class?: string
@ -20,7 +22,7 @@ export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
<div
class={clsx(styles.rating, props.class, {
[styles.isUpvoted]: isUpvoted,
[styles.isDownvoted]: isDownvoted
[styles.isDownvoted]: isDownvoted,
})}
>
<button

View File

@ -1,9 +1,11 @@
import { createMemo, Show } from 'solid-js'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx'
import { createMemo, Show } from 'solid-js'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Loading } from '../../_shared/Loading'
import { Image } from '../../_shared/Image'
import { Loading } from '../../_shared/Loading'
import styles from './Userpic.module.scss'
type Props = {
name: string
@ -46,7 +48,7 @@ export const Userpic = (props: Props) => {
return (
<div
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
['cursorPointer']: props.onClick
['cursorPointer']: props.onClick,
})}
onClick={props.onClick}
>

View File

@ -1,8 +1,9 @@
import styles from './Banner.module.scss'
import { showModal } from '../../stores/ui'
import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize'
import { showModal } from '../../stores/ui'
import styles from './Banner.module.scss'
export default () => {
const { t } = useLocalize()

View File

@ -1,8 +1,9 @@
import '../../styles/help.scss'
import { createSignal, onMount } from 'solid-js'
import { showModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { showModal } from '../../stores/ui'
export const Donate = () => {
const { t } = useLocalize()
@ -11,7 +12,7 @@ export const Donate = () => {
const cpOptions = {
publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed',
description: t('Help discours to grow'),
currency: 'RUB'
currency: 'RUB',
}
let amountSwitchElement: HTMLDivElement | undefined
@ -22,13 +23,13 @@ export const Donate = () => {
const [period, setPeriod] = createSignal(monthly)
const [amount, setAmount] = createSignal(0)
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const initiated = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const {
cp: { CloudPayments }
cp: { CloudPayments },
} = window as any // Checkout(cpOptions)
setWidget(new CloudPayments())
console.log('[donate] payments initiated')
@ -42,8 +43,8 @@ export const Donate = () => {
amount: amount() || 0, //сумма
vat: 20, //ставка НДС
method: 0, // тег-1214 признак способа расчета - признак способа расчета
object: 0 // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
}
object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
},
],
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
@ -53,8 +54,8 @@ export const Donate = () => {
electronic: amount(), // Сумма оплаты электронными деньгами
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
}
provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
},
})
}
@ -93,10 +94,10 @@ export const Donate = () => {
recurrent: {
interval: period(), // local solid's signal
period: 1, // internal widget's
CustomerReciept: customerReciept() // чек для регулярных платежей
}
}
}
CustomerReciept: customerReciept(), // чек для регулярных платежей
},
},
},
},
(opts) => {
// success
@ -111,9 +112,9 @@ export const Donate = () => {
showSnackbar({
type: 'error',
body: reason
body: reason,
})
}
},
)
}

View File

@ -1,5 +1,5 @@
import { hideModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize'
import { hideModal } from '../../stores/ui'
import { Button } from '../_shared/Button'
export const Feedback = () => {
@ -14,9 +14,9 @@ export const Feedback = () => {
method,
headers: {
accept: 'application/json',
'content-type': 'application/json; charset=utf-8'
'content-type': 'application/json; charset=utf-8',
},
body: JSON.stringify({ contact: contactElement?.value, message: msgElement?.textContent })
body: JSON.stringify({ contact: contactElement?.value, message: msgElement?.textContent }),
})
hideModal()
}

View File

@ -1,10 +1,11 @@
import { clsx } from 'clsx'
import { createMemo, For } from 'solid-js'
import styles from './Footer.module.scss'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import { Subscribe } from '../_shared/Subscribe'
import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize'
import styles from './Footer.module.scss'
export const Footer = () => {
const { t, lang } = useLocalize()
@ -17,25 +18,25 @@ export const Footer = () => {
items: [
{
title: 'Manifest',
slug: '/about/manifest'
slug: '/about/manifest',
},
{
title: 'How it works',
slug: '/about/guide'
slug: '/about/guide',
},
{
title: 'Dogma',
slug: '/about/dogma'
slug: '/about/dogma',
},
{
title: 'Principles',
slug: '/about/principles'
slug: '/about/principles',
},
{
title: 'How to write an article',
slug: '/how-to-write-a-good-article'
}
]
slug: '/how-to-write-a-good-article',
},
],
},
{
@ -43,25 +44,25 @@ export const Footer = () => {
items: [
{
title: 'Suggest an idea',
slug: '/connect'
slug: '/connect',
},
{
title: 'Become an author',
slug: '/create'
slug: '/create',
},
{
title: 'Support us',
slug: '/about/help'
slug: '/about/help',
},
{
title: 'Feedback',
slug: '/#feedback'
slug: '/#feedback',
},
{
title: 'Work with us',
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform'
}
]
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform',
},
],
},
{
@ -69,46 +70,46 @@ export const Footer = () => {
items: [
{
title: 'Authors',
slug: '/authors'
slug: '/authors',
},
{
title: 'Communities',
slug: '/community'
slug: '/community',
},
{
title: 'Partners',
slug: '/about/partners'
slug: '/about/partners',
},
{
title: 'Special projects',
slug: '/about/projects'
slug: '/about/projects',
},
{
title: changeLangTitle(),
slug: changeLangLink(),
rel: 'external'
}
]
}
rel: 'external',
},
],
},
])
const SOCIAL = [
{
name: 'facebook',
href: 'https://facebook.com/discoursio'
href: 'https://facebook.com/discoursio',
},
{
name: 'vk',
href: 'https://vk.com/discoursio'
href: 'https://vk.com/discoursio',
},
{
name: 'twitter',
href: 'https://twitter.com/discours_io'
href: 'https://twitter.com/discours_io',
},
{
name: 'telegram',
href: 'https://t.me/discoursio'
}
href: 'https://t.me/discoursio',
},
]
return (
<footer class={styles.discoursFooter}>
@ -143,7 +144,7 @@ export const Footer = () => {
<div class={clsx(styles.footerCopyright, 'row')}>
<div class="col-md-18 col-lg-20">
{t(
'Independant magazine with an open horizontal cooperation about culture, science and society'
'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>

View File

@ -1,10 +1,10 @@
import styles from './Hero.module.scss'
import { showModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize'
import { useRouter } from '../../stores/router'
import { showModal } from '../../stores/ui'
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
import styles from './Hero.module.scss'
export default () => {
const { t } = useLocalize()
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
@ -17,7 +17,7 @@ export default () => {
<h4 innerHTML={t('Horizontal collaborative journalistic platform')} />
<p
innerHTML={t(
'Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects'
'Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects',
)}
/>
<div class={styles.aboutDiscoursActions}>
@ -29,7 +29,7 @@ export default () => {
onClick={() => {
showModal('auth')
changeSearchParam({
mode: 'register'
mode: 'register',
})
}}
>

View File

@ -1,12 +1,15 @@
import { clsx } from 'clsx'
import styles from './Draft.module.scss'
import type { Shout } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import { useLocalize } from '../../context/localize'
import { useConfirm } from '../../context/confirm'
import { useSnackbar } from '../../context/snackbar'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { useConfirm } from '../../context/confirm'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { router } from '../../stores/router'
import { Icon } from '../_shared/Icon'
import styles from './Draft.module.scss'
type Props = {
class?: string
@ -18,11 +21,11 @@ type Props = {
export const Draft = (props: Props) => {
const { t, formatDate } = useLocalize()
const {
actions: { showConfirm }
actions: { showConfirm },
} = useConfirm()
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const handlePublishLinkClick = (e) => {
@ -37,7 +40,7 @@ export const Draft = (props: Props) => {
confirmBody: t('Are you sure you want to delete this draft?'),
confirmButtonLabel: t('Delete'),
confirmButtonVariant: 'danger',
declineButtonVariant: 'primary'
declineButtonVariant: 'primary',
})
if (isConfirmed) {
props.onDelete(props.shout)

View File

@ -1,12 +1,15 @@
import { Buffer } from 'buffer'
import { clsx } from 'clsx'
import styles from './AudioUploader.module.scss'
import { DropArea } from '../../_shared/DropArea'
import { useLocalize } from '../../../context/localize'
import { Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { MediaItem } from '../../../pages/types'
import { composeMediaItems } from '../../../utils/composeMediaItems'
import { DropArea } from '../../_shared/DropArea'
import { AudioPlayer } from '../../Article/AudioPlayer'
import { Buffer } from 'buffer'
import styles from './AudioUploader.module.scss'
window.Buffer = Buffer

View File

@ -1,7 +1,9 @@
import { clsx } from 'clsx'
import styles from './AutoSaveNotice.module.scss'
import { Loading } from '../../_shared/Loading'
import { useLocalize } from '../../../context/localize'
import { Loading } from '../../_shared/Loading'
import styles from './AutoSaveNotice.module.scss'
type Props = {
active: boolean

View File

@ -1,9 +1,11 @@
import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import styles from './BubbleMenu.module.scss'
type Props = {
editor: Editor
ref: (el: HTMLElement) => void

View File

@ -1,12 +1,14 @@
import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import { Popover } from '../../_shared/Popover'
import { UploadModalContent } from '../UploadModalContent'
import { Modal } from '../../Nav/Modal'
import { UploadedFile } from '../../../pages/types'
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import { Modal } from '../../Nav/Modal'
import { UploadModalContent } from '../UploadModalContent'
import styles from './BubbleMenu.module.scss'
type Props = {
editor: Editor

View File

@ -1,9 +1,12 @@
import { createSignal, Show, For } from 'solid-js'
import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { clsx } from 'clsx'
import { Icon } from '../../_shared/Icon'
import { createSignal, Show, For } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import styles from './BubbleMenu.module.scss'
type Props = {
editor: Editor

View File

@ -1,52 +1,56 @@
import { createEffect, createSignal, onCleanup } from 'solid-js'
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
import uniqolor from 'uniqolor'
import * as Y from 'yjs'
import type { Doc } from 'yjs/dist/src/utils/Doc'
import { HocuspocusProvider } from '@hocuspocus/provider'
import { isTextSelection } from '@tiptap/core'
import { Bold } from '@tiptap/extension-bold'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { Italic } from '@tiptap/extension-italic'
import { Strike } from '@tiptap/extension-strike'
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Underline } from '@tiptap/extension-underline'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import { BulletList } from '@tiptap/extension-bullet-list'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { ListItem } from '@tiptap/extension-list-item'
import { CharacterCount } from '@tiptap/extension-character-count'
import { Placeholder } from '@tiptap/extension-placeholder'
import { Collaboration } from '@tiptap/extension-collaboration'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { Document } from '@tiptap/extension-document'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import Focus from '@tiptap/extension-focus'
import { Gapcursor } from '@tiptap/extension-gapcursor'
import { HardBreak } from '@tiptap/extension-hard-break'
import { Heading } from '@tiptap/extension-heading'
import { Highlight } from '@tiptap/extension-highlight'
import { Link } from '@tiptap/extension-link'
import { Document } from '@tiptap/extension-document'
import { Text } from '@tiptap/extension-text'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { isTextSelection } from '@tiptap/core'
import { Paragraph } from '@tiptap/extension-paragraph'
import Focus from '@tiptap/extension-focus'
import { Collaboration } from '@tiptap/extension-collaboration'
import { HocuspocusProvider } from '@hocuspocus/provider'
import { CustomBlockquote } from './extensions/CustomBlockquote'
import { Figure } from './extensions/Figure'
import { Figcaption } from './extensions/Figcaption'
import { Embed } from './extensions/Embed'
import { useSession } from '../../context/session'
import { useLocalize } from '../../context/localize'
import { useEditorContext } from '../../context/editor'
import { TrailingNode } from './extensions/TrailingNode'
import Article from './extensions/Article'
import { TextBubbleMenu } from './TextBubbleMenu'
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
import { EditorFloatingMenu } from './EditorFloatingMenu'
import './Prosemirror.scss'
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Image } from '@tiptap/extension-image'
import { Footnote } from './extensions/Footnote'
import { Italic } from '@tiptap/extension-italic'
import { Link } from '@tiptap/extension-link'
import { ListItem } from '@tiptap/extension-list-item'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { Paragraph } from '@tiptap/extension-paragraph'
import { Placeholder } from '@tiptap/extension-placeholder'
import { Strike } from '@tiptap/extension-strike'
import { Text } from '@tiptap/extension-text'
import { Underline } from '@tiptap/extension-underline'
import { createEffect, createSignal, onCleanup } from 'solid-js'
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
import uniqolor from 'uniqolor'
import * as Y from 'yjs'
import { useEditorContext } from '../../context/editor'
import { useLocalize } from '../../context/localize'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/snackbar'
import { handleImageUpload } from '../../utils/handleImageUpload'
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
import { EditorFloatingMenu } from './EditorFloatingMenu'
import Article from './extensions/Article'
import { CustomBlockquote } from './extensions/CustomBlockquote'
import { Embed } from './extensions/Embed'
import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure'
import { Footnote } from './extensions/Footnote'
import { TrailingNode } from './extensions/TrailingNode'
import { TextBubbleMenu } from './TextBubbleMenu'
import './Prosemirror.scss'
type Props = {
shoutId: number
initialContent?: string
@ -61,7 +65,7 @@ const allowedImageTypes = new Set([
'image/png',
'image/tiff',
'image/webp',
'image/x-icon'
'image/x-icon',
])
const yDocs: Record<string, Doc> = {}
@ -75,7 +79,7 @@ export const Editor = (props: Props) => {
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const docName = `shout-${props.shoutId}`
@ -88,47 +92,47 @@ export const Editor = (props: Props) => {
providers[docName] = new HocuspocusProvider({
url: 'wss://hocuspocus.discours.io',
name: docName,
document: yDocs[docName]
document: yDocs[docName],
})
}
const editorElRef: {
current: HTMLDivElement
} = {
current: null
current: null,
}
const textBubbleMenuRef: {
current: HTMLDivElement
} = {
current: null
current: null,
}
const incutBubbleMenuRef: {
current: HTMLElement
} = {
current: null
current: null,
}
const figureBubbleMenuRef: {
current: HTMLElement
} = {
current: null
current: null,
}
const blockquoteBubbleMenuRef: {
current: HTMLElement
} = {
current: null
current: null,
}
const floatingMenuRef: {
current: HTMLDivElement
} = {
current: null
current: null,
}
const ImageFigure = Figure.extend({
name: 'capturedImage',
content: 'figcaption image'
content: 'figcaption image',
})
const handleClipboardPaste = async () => {
@ -149,7 +153,7 @@ export const Editor = (props: Props) => {
source: blob.toString(),
name: file.name,
size: file.size,
file
file,
}
showSnackbar({ body: t('Uploading image') })
@ -166,17 +170,17 @@ export const Editor = (props: Props) => {
content: [
{
type: 'text',
text: result.originalFilename
}
]
text: result.originalFilename,
},
],
},
{
type: 'image',
attrs: {
src: result.url
}
}
]
src: result.url,
},
},
],
})
.run()
} catch (error) {
@ -190,7 +194,7 @@ export const Editor = (props: Props) => {
element: editorElRef.current,
editorProps: {
attributes: {
class: 'articleEditor'
class: 'articleEditor',
},
transformPastedHTML(html) {
return html.replaceAll(/<img.*?>/g, '')
@ -198,7 +202,7 @@ export const Editor = (props: Props) => {
handlePaste: () => {
handleClipboardPaste()
return false
}
},
},
extensions: [
Document,
@ -211,31 +215,31 @@ export const Editor = (props: Props) => {
Strike,
HorizontalRule.configure({
HTMLAttributes: {
class: 'horizontalRule'
}
class: 'horizontalRule',
},
}),
Underline,
Link.configure({
openOnClick: false
openOnClick: false,
}),
Heading.configure({
levels: [2, 3, 4]
levels: [2, 3, 4],
}),
BulletList,
OrderedList,
ListItem,
Collaboration.configure({
document: yDocs[docName]
document: yDocs[docName],
}),
CollaborationCursor.configure({
provider: providers[docName],
user: {
name: user().name,
color: uniqolor(user().slug).color
}
color: uniqolor(user().slug).color,
},
}),
Placeholder.configure({
placeholder: t('Add a link or click plus to embed media')
placeholder: t('Add a link or click plus to embed media'),
}),
Focus,
Gapcursor,
@ -243,8 +247,8 @@ export const Editor = (props: Props) => {
Highlight.configure({
multicolor: true,
HTMLAttributes: {
class: 'highlight'
}
class: 'highlight',
},
}),
ImageFigure,
Image,
@ -267,8 +271,8 @@ export const Editor = (props: Props) => {
return result
},
tippyOptions: {
sticky: true
}
sticky: true,
},
}),
BubbleMenu.configure({
pluginKey: 'blockquoteBubbleMenu',
@ -286,8 +290,8 @@ export const Editor = (props: Props) => {
if (selectedElement) {
return selectedElement.getBoundingClientRect()
}
}
}
},
},
}),
BubbleMenu.configure({
pluginKey: 'incutBubbleMenu',
@ -305,31 +309,31 @@ export const Editor = (props: Props) => {
if (selectedElement) {
return selectedElement.getBoundingClientRect()
}
}
}
},
},
}),
BubbleMenu.configure({
pluginKey: 'imageBubbleMenu',
element: figureBubbleMenuRef.current,
shouldShow: ({ editor: e, view }) => {
return view.hasFocus() && e.isActive('image')
}
},
}),
FloatingMenu.configure({
tippyOptions: {
placement: 'left'
placement: 'left',
},
element: floatingMenuRef.current
element: floatingMenuRef.current,
}),
TrailingNode,
Article
Article,
],
enablePasteRules: [Link],
content: initialContent ?? null
content: initialContent ?? null,
}))
const {
actions: { countWords, setEditor }
actions: { countWords, setEditor },
} = useEditorContext()
setEditor(editor)
@ -341,7 +345,7 @@ export const Editor = (props: Props) => {
if (html()) {
countWords({
characters: editor().storage.characterCount.characters(),
words: editor().storage.characterCount.words()
words: editor().storage.characterCount.words(),
})
}
})

View File

@ -1,17 +1,21 @@
import { createEffect, createSignal, Show } from 'solid-js'
import type { Editor } from '@tiptap/core'
import { Icon } from '../../_shared/Icon'
import { InlineForm } from '../InlineForm'
import styles from './EditorFloatingMenu.module.scss'
import { useLocalize } from '../../../context/localize'
import { Modal } from '../../Nav/Modal'
import { Menu } from './Menu'
import type { MenuItem } from './Menu/Menu'
import { showModal } from '../../../stores/ui'
import { UploadModalContent } from '../UploadModalContent'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import type { Editor } from '@tiptap/core'
import { createEffect, createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { UploadedFile } from '../../../pages/types'
import { showModal } from '../../../stores/ui'
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import { Modal } from '../../Nav/Modal'
import { InlineForm } from '../InlineForm'
import { UploadModalContent } from '../UploadModalContent'
import { Menu } from './Menu'
import styles from './EditorFloatingMenu.module.scss'
type FloatingMenuProps = {
editor: Editor
@ -80,7 +84,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
if (menuOpen()) {
setMenuOpen(false)
}
}
},
})
const handleUpload = (image: UploadedFile) => {

View File

@ -1,7 +1,8 @@
import styles from './Menu.module.scss'
import { useLocalize } from '../../../../context/localize'
import { Icon } from '../../../_shared/Icon'
import { Popover } from '../../../_shared/Popover'
import { useLocalize } from '../../../../context/localize'
import styles from './Menu.module.scss'
export type MenuItem = 'image' | 'embed' | 'horizontal-rule'

View File

@ -39,7 +39,9 @@
border: 1px solid #e9e9ee;
border-radius: 2px;
opacity: 0;
transition: height 0.3s ease-in-out, opacity 0.3s ease-in-out;
transition:
height 0.3s ease-in-out,
opacity 0.3s ease-in-out;
&.visible {
height: 32px;

View File

@ -1,9 +1,11 @@
import styles from './InlineForm.module.scss'
import { Icon } from '../../_shared/Icon'
import { createSignal, onMount } from 'solid-js'
import { clsx } from 'clsx'
import { Popover } from '../../_shared/Popover'
import { createSignal, onMount } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import styles from './InlineForm.module.scss'
type Props = {
onClose: () => void

View File

@ -1,8 +1,9 @@
import { Editor } from '@tiptap/core'
import { createEditorTransaction } from 'solid-tiptap'
import { useLocalize } from '../../../context/localize'
import { validateUrl } from '../../../utils/validateUrl'
import { InlineForm } from '../InlineForm'
import { useLocalize } from '../../../context/localize'
import { createEditorTransaction } from 'solid-tiptap'
type Props = {
editor: Editor
@ -24,7 +25,7 @@ export const InsertLinkForm = (props: Props) => {
() => props.editor,
(ed) => {
return (ed && ed.getAttributes('link').href) || ''
}
},
)
const handleClearLinkForm = () => {
if (currentUrl()) {

View File

@ -1,17 +1,19 @@
import { clsx } from 'clsx'
import { Button } from '../../_shared/Button'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import styles from './Panel.module.scss'
import { useEditorContext } from '../../../context/editor'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { getPagePath } from '@nanostores/router'
import { router } from '../../../stores/router'
import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useEditorHTML } from 'solid-tiptap'
import Typograf from 'typograf'
import { createSignal, Show } from 'solid-js'
import { useEditorContext } from '../../../context/editor'
import { useLocalize } from '../../../context/localize'
import { router } from '../../../stores/router'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Button } from '../../_shared/Button'
import { DarkModeToggle } from '../../_shared/DarkModeToggle'
import { Icon } from '../../_shared/Icon'
import styles from './Panel.module.scss'
const typograf = new Typograf({ locale: ['ru', 'en-US'] })
@ -26,11 +28,11 @@ export const Panel = (props: Props) => {
wordCounter,
editorRef,
form,
actions: { toggleEditorPanel, saveShout, publishShout }
actions: { toggleEditorPanel, saveShout, publishShout },
} = useEditorContext()
const containerRef: { current: HTMLElement } = {
current: null
current: null,
}
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
@ -39,7 +41,7 @@ export const Panel = (props: Props) => {
useOutsideClickHandler({
containerRef,
predicate: () => isEditorPanelVisible(),
handler: () => toggleEditorPanel()
handler: () => toggleEditorPanel(),
})
useEscKeyDownHandler(() => {

View File

@ -1,3 +1,15 @@
import { Blockquote } from '@tiptap/extension-blockquote'
import { Bold } from '@tiptap/extension-bold'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { CharacterCount } from '@tiptap/extension-character-count'
import { Document } from '@tiptap/extension-document'
import { Image } from '@tiptap/extension-image'
import { Italic } from '@tiptap/extension-italic'
import { Link } from '@tiptap/extension-link'
import { Paragraph } from '@tiptap/extension-paragraph'
import { Placeholder } from '@tiptap/extension-placeholder'
import { Text } from '@tiptap/extension-text'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { Portal } from 'solid-js/web'
import {
@ -5,34 +17,25 @@ import {
createTiptapEditor,
useEditorHTML,
useEditorIsEmpty,
useEditorIsFocused
useEditorIsFocused,
} from 'solid-tiptap'
import { useEditorContext } from '../../context/editor'
import { Document } from '@tiptap/extension-document'
import { Text } from '@tiptap/extension-text'
import { Paragraph } from '@tiptap/extension-paragraph'
import { Bold } from '@tiptap/extension-bold'
import { Button } from '../_shared/Button'
import { useLocalize } from '../../context/localize'
import { UploadedFile } from '../../pages/types'
import { hideModal, showModal } from '../../stores/ui'
import { Button } from '../_shared/Button'
import { Icon } from '../_shared/Icon'
import { Popover } from '../_shared/Popover'
import { Italic } from '@tiptap/extension-italic'
import { Modal } from '../Nav/Modal'
import { hideModal, showModal } from '../../stores/ui'
import { Blockquote } from '@tiptap/extension-blockquote'
import { UploadModalContent } from './UploadModalContent'
import { clsx } from 'clsx'
import styles from './SimplifiedEditor.module.scss'
import { Placeholder } from '@tiptap/extension-placeholder'
import { InsertLinkForm } from './InsertLinkForm'
import { Link } from '@tiptap/extension-link'
import { UploadedFile } from '../../pages/types'
import { Figure } from './extensions/Figure'
import { Image } from '@tiptap/extension-image'
import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure'
import { InsertLinkForm } from './InsertLinkForm'
import { TextBubbleMenu } from './TextBubbleMenu'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { CharacterCount } from '@tiptap/extension-character-count'
import { UploadModalContent } from './UploadModalContent'
import styles from './SimplifiedEditor.module.scss'
type Props = {
placeholder: string
@ -66,28 +69,28 @@ const SimplifiedEditor = (props: Props) => {
const wrapperEditorElRef: {
current: HTMLElement
} = {
current: null
current: null,
}
const editorElRef: {
current: HTMLElement
} = {
current: null
current: null,
}
const textBubbleMenuRef: {
current: HTMLDivElement
} = {
current: null
current: null,
}
const {
actions: { setEditor }
actions: { setEditor },
} = useEditorContext()
const ImageFigure = Figure.extend({
name: 'capturedImage',
content: 'figcaption image'
content: 'figcaption image',
})
const content = props.initialContent
@ -95,8 +98,8 @@ const SimplifiedEditor = (props: Props) => {
element: editorElRef.current,
editorProps: {
attributes: {
class: styles.simplifiedEditorField
}
class: styles.simplifiedEditorField,
},
},
extensions: [
Document,
@ -105,16 +108,16 @@ const SimplifiedEditor = (props: Props) => {
Bold,
Italic,
Link.configure({
openOnClick: false
openOnClick: false,
}),
CharacterCount.configure({
limit: MAX_DESCRIPTION_LIMIT
limit: MAX_DESCRIPTION_LIMIT,
}),
Blockquote.configure({
HTMLAttributes: {
class: styles.blockQuote
}
class: styles.blockQuote,
},
}),
BubbleMenu.configure({
pluginKey: 'textBubbleMenu',
@ -124,18 +127,18 @@ const SimplifiedEditor = (props: Props) => {
const { selection } = state
const { empty } = selection
return view.hasFocus() && !empty
}
},
}),
ImageFigure,
Image,
Figcaption,
Placeholder.configure({
emptyNodeClass: styles.emptyNode,
placeholder: props.placeholder
})
placeholder: props.placeholder,
}),
],
autofocus: props.autoFocus,
content: content ?? null
content: content ?? null,
}))
setEditor(editor)
@ -147,7 +150,7 @@ const SimplifiedEditor = (props: Props) => {
() => editor(),
(ed) => {
return ed && ed.isActive(name)
}
},
)
const html = useEditorHTML(() => editor())
@ -168,17 +171,17 @@ const SimplifiedEditor = (props: Props) => {
content: [
{
type: 'text',
text: image.originalFilename
}
]
text: image.originalFilename,
},
],
},
{
type: 'image',
attrs: {
src: image.url
}
}
]
src: image.url,
},
},
],
})
.run()
hideModal()
@ -238,7 +241,7 @@ const SimplifiedEditor = (props: Props) => {
const maxHeightStyle = {
overflow: 'auto',
'max-height': `${props.maxHeight}px`
'max-height': `${props.maxHeight}px`,
}
return (
@ -249,7 +252,7 @@ const SimplifiedEditor = (props: Props) => {
[styles.minimal]: props.variant === 'minimal',
[styles.bordered]: props.variant === 'bordered',
[styles.isFocused]: isFocused() || !isEmpty(),
[styles.labelVisible]: props.label && counter() > 0
[styles.labelVisible]: props.label && counter() > 0,
})}
>
<Show when={props.maxLength && editor()}>

View File

@ -1,14 +1,17 @@
import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect } from 'solid-js'
import type { Editor } from '@tiptap/core'
import styles from './TextBubbleMenu.module.scss'
import { Icon } from '../../_shared/Icon'
import { clsx } from 'clsx'
import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect } from 'solid-js'
import { createEditorTransaction } from 'solid-tiptap'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import { InsertLinkForm } from '../InsertLinkForm'
import SimplifiedEditor from '../SimplifiedEditor'
import styles from './TextBubbleMenu.module.scss'
type BubbleMenuProps = {
editor: Editor
isCommonMarkup: boolean
@ -22,7 +25,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
const isActive = (name: string, attributes?: unknown) =>
createEditorTransaction(
() => props.editor,
(editor) => editor && editor.isActive(name, attributes)
(editor) => editor && editor.isActive(name, attributes),
)
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
@ -79,7 +82,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
}
const value = ed.getAttributes('footnote').value
setFootNote(value)
}
},
)
const handleAddFootnote = (footnote) => {
@ -148,7 +151,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen(),
})}
onClick={toggleTextSizePopup}
>
@ -165,7 +168,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH1()
[styles.bubbleMenuButtonActive]: isH1(),
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
@ -182,7 +185,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH2()
[styles.bubbleMenuButtonActive]: isH2(),
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
@ -199,7 +202,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH3()
[styles.bubbleMenuButtonActive]: isH3(),
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 4 }).run()
@ -219,7 +222,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isQuote()
[styles.bubbleMenuButtonActive]: isQuote(),
})}
onClick={handleSetPunchline}
>
@ -233,7 +236,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isPunchLine()
[styles.bubbleMenuButtonActive]: isPunchLine(),
})}
onClick={handleSetQuote}
>
@ -250,7 +253,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isIncut()
[styles.bubbleMenuButtonActive]: isIncut(),
})}
onClick={() => {
props.editor.chain().focus().toggleArticle().run()
@ -274,7 +277,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isBold()
[styles.bubbleMenuButtonActive]: isBold(),
})}
onClick={() => props.editor.chain().focus().toggleBold().run()}
>
@ -288,7 +291,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isItalic()
[styles.bubbleMenuButtonActive]: isItalic(),
})}
onClick={() => props.editor.chain().focus().toggleItalic().run()}
>
@ -304,7 +307,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isHighlight()
[styles.bubbleMenuButtonActive]: isHighlight(),
})}
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
>
@ -321,7 +324,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
type="button"
onClick={() => setLinkEditorOpen(true)}
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isLink()
[styles.bubbleMenuButtonActive]: isLink(),
})}
>
<Icon name="editor-link" />
@ -336,7 +339,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isFootnote()
[styles.bubbleMenuButtonActive]: isFootnote(),
})}
onClick={handleOpenFootnoteEditor}
>
@ -349,7 +352,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: listBubbleOpen()
[styles.bubbleMenuButtonActive]: listBubbleOpen(),
})}
onClick={toggleListPopup}
>
@ -366,7 +369,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isBulletList()
[styles.bubbleMenuButtonActive]: isBulletList(),
})}
onClick={() => {
props.editor.chain().focus().toggleBulletList().run()
@ -383,7 +386,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isOrderedList()
[styles.bubbleMenuButtonActive]: isOrderedList(),
})}
onClick={() => {
props.editor.chain().focus().toggleOrderedList().run()

View File

@ -1,13 +1,17 @@
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'
import './TopicSelect.scss'
import styles from './TopicSelect.module.scss'
import { clsx } from 'clsx'
import { createSignal } from 'solid-js'
import { slugify } from '../../../utils/slugify'
import { useLocalize } from '../../../context/localize'
import { clone } from '../../../utils/clone'
import { slugify } from '../../../utils/slugify'
import '@thisbeyond/solid-select/style.css'
import './TopicSelect.scss'
import styles from './TopicSelect.module.scss'
type TopicSelectProps = {
topics: Topic[]
@ -33,7 +37,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
disable: (topic) => {
return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug)
},
createable: createValue
createable: createValue,
})
const handleChange = (selectedTopics: Topic[]) => {
@ -57,7 +61,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
return (
<div
class={clsx(styles.selectedItem, {
[styles.mainTopic]: isMainTopic
[styles.mainTopic]: isMainTopic,
})}
onClick={() => handleSelectedItemClick(item)}
>

View File

@ -1,16 +1,18 @@
import styles from './UploadModalContent.module.scss'
import { clsx } from 'clsx'
import { Icon } from '../../_shared/Icon'
import { Button } from '../../_shared/Button'
import { createSignal, Show } from 'solid-js'
import { InlineForm } from '../InlineForm'
import { hideModal } from '../../../stores/ui'
import { createDropzone, createFileUploader, UploadFile } from '@solid-primitives/upload'
import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Loading } from '../../_shared/Loading'
import { verifyImg } from '../../../utils/verifyImg'
import { UploadedFile } from '../../../pages/types'
import { hideModal } from '../../../stores/ui'
import { handleImageUpload } from '../../../utils/handleImageUpload'
import { verifyImg } from '../../../utils/verifyImg'
import { Button } from '../../_shared/Button'
import { Icon } from '../../_shared/Icon'
import { Loading } from '../../_shared/Loading'
import { InlineForm } from '../InlineForm'
import styles from './UploadModalContent.module.scss'
type Props = {
onClose: (image?: UploadedFile) => void
@ -46,7 +48,7 @@ export const UploadModalContent = (props: Props) => {
source: blob.toString(),
name: file.name,
size: file.size,
file: file
file: file,
}
await runUpload(fileToUpload)
} catch (error) {
@ -70,7 +72,7 @@ export const UploadModalContent = (props: Props) => {
} else {
setDragError(t('Image format not supported'))
}
}
},
})
const handleDrag = (event: MouseEvent) => {
if (event.type === 'dragenter' || event.type === 'dragover') {

View File

@ -1,14 +1,17 @@
import { clsx } from 'clsx'
import styles from './VideoUploader.module.scss'
import { useLocalize } from '../../../context/localize'
import { createDropzone } from '@solid-primitives/upload'
import { createSignal, For, Show } from 'solid-js'
import { useSnackbar } from '../../../context/snackbar'
import { validateUrl } from '../../../utils/validateUrl'
import type { MediaItem } from '../../../pages/types'
import { createDropzone } from '@solid-primitives/upload'
import { clsx } from 'clsx'
import { createSignal, For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSnackbar } from '../../../context/snackbar'
import { composeMediaItems } from '../../../utils/composeMediaItems'
import { validateUrl } from '../../../utils/validateUrl'
import { VideoPlayer } from '../../_shared/VideoPlayer'
import styles from './VideoUploader.module.scss'
type Props = {
video: MediaItem[]
onVideoAdd: (value: MediaItem[]) => void
@ -22,13 +25,13 @@ export const VideoUploader = (props: Props) => {
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const urlInput: {
current: HTMLInputElement
} = {
current: null
current: null,
}
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
@ -39,13 +42,13 @@ export const VideoUploader = (props: Props) => {
} else if (droppedFiles()[0].file.type.startsWith('video/')) {
await showSnackbar({
body: t(
'This functionality is currently not available, we would like to work on this issue. Use the download link.'
)
'This functionality is currently not available, we would like to work on this issue. Use the download link.',
),
})
} else {
setError(t('Video format not supported'))
}
}
},
})
const handleDrag = (event) => {
if (event.type === 'dragenter' || event.type === 'dragover') {
@ -84,8 +87,8 @@ export const VideoUploader = (props: Props) => {
onClick={() =>
showSnackbar({
body: t(
'This functionality is currently not available, we would like to work on this issue. Use the download link.'
)
'This functionality is currently not available, we would like to work on this issue. Use the download link.',
),
})
}
ref={dropzoneRef}

View File

@ -14,8 +14,8 @@ export default Node.create({
name: 'article',
defaultOptions: {
HTMLAttributes: {
'data-type': 'incut'
}
'data-type': 'incut',
},
},
group: 'block',
content: 'block+',
@ -23,8 +23,8 @@ export default Node.create({
parseHTML() {
return [
{
tag: 'article'
}
tag: 'article',
},
]
},
@ -35,11 +35,11 @@ export default Node.create({
addAttributes() {
return {
'data-float': {
default: null
default: null,
},
'data-bg': {
default: null
}
default: null,
},
}
},
@ -60,7 +60,7 @@ export default Node.create({
(value) =>
({ commands }) => {
return commands.updateAttributes(this.name, { 'data-bg': value })
}
},
}
}
},
})

View File

@ -14,18 +14,18 @@ declare module '@tiptap/core' {
export const CustomBlockquote = Blockquote.extend({
name: 'blockquote',
defaultOptions: {
HTMLAttributes: {}
HTMLAttributes: {},
},
group: 'block',
content: 'block+',
addAttributes() {
return {
'data-float': {
default: null
default: null,
},
'data-type': {
default: null
}
default: null,
},
}
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@ -41,7 +41,7 @@ export const CustomBlockquote = Blockquote.extend({
(value) =>
({ commands }) => {
return commands.updateAttributes(this.name, { 'data-float': value })
}
},
}
}
},
})

View File

@ -16,20 +16,20 @@ export const CustomImage = Image.extend({
addAttributes() {
return {
src: {
default: null
default: null,
},
alt: {
default: null
default: null,
},
width: {
default: null
default: null,
},
height: {
default: null
default: null,
},
'data-float': {
default: null
}
default: null,
},
}
},
addCommands() {
@ -39,14 +39,14 @@ export const CustomImage = Image.extend({
({ commands }) => {
return commands.insertContent({
type: this.name,
attrs: options
attrs: options,
})
},
setImageFloat:
(value) =>
({ commands }) => {
return commands.updateAttributes(this.name, { 'data-float': value })
}
},
}
}
},
})

View File

@ -25,14 +25,14 @@ export const Embed = Node.create<IframeOptions>({
return {
src: { default: null },
width: { default: null },
height: { default: null }
height: { default: null },
}
},
parseHTML() {
return [
{
tag: 'iframe'
}
tag: 'iframe',
},
]
},
renderHTML({ HTMLAttributes }) {
@ -49,7 +49,7 @@ export const Embed = Node.create<IframeOptions>({
iframe.src = node.attrs.src
div.append(iframe)
return {
dom: div
dom: div,
}
}
},
@ -64,7 +64,7 @@ export const Embed = Node.create<IframeOptions>({
tr.replaceRangeWith(selection.from, selection.to, node)
}
return true
}
},
}
}
},
})

View File

@ -12,7 +12,7 @@ export const Figcaption = Node.create({
addOptions() {
return {
HTMLAttributes: {}
HTMLAttributes: {},
}
},
@ -25,8 +25,8 @@ export const Figcaption = Node.create({
parseHTML() {
return [
{
tag: 'figcaption'
}
tag: 'figcaption',
},
]
},
@ -39,7 +39,7 @@ export const Figcaption = Node.create({
(value) =>
({ commands }) => {
return commands.focus(value)
}
},
}
}
},
})

View File

@ -12,7 +12,7 @@ export const Figure = Node.create({
name: 'figure',
addOptions() {
return {
HTMLAttributes: {}
HTMLAttributes: {},
}
},
group: 'block',
@ -22,15 +22,15 @@ export const Figure = Node.create({
addAttributes() {
return {
'data-float': null
'data-float': null,
}
},
parseHTML() {
return [
{
tag: `figure[data-type="${this.name}"]`
}
tag: `figure[data-type="${this.name}"]`,
},
]
},
@ -54,10 +54,10 @@ export const Figure = Node.create({
event.preventDefault()
}
return false
}
}
}
})
},
},
},
}),
]
},
@ -67,7 +67,7 @@ export const Figure = Node.create({
(value) =>
({ commands }) => {
return commands.updateAttributes(this.name, { 'data-float': value })
}
},
}
}
},
})

View File

@ -14,7 +14,7 @@ export const Footnote = Node.create({
name: 'footnote',
addOptions() {
return {
HTMLAttributes: {}
HTMLAttributes: {},
}
},
group: 'inline',
@ -29,18 +29,18 @@ export const Footnote = Node.create({
parseHTML: (element) => element.dataset.value || null,
renderHTML: (attributes) => {
return {
'data-value': attributes.value
'data-value': attributes.value,
}
}
}
},
},
}
},
parseHTML() {
return [
{
tag: 'footnote'
}
tag: 'footnote',
},
]
},
@ -92,7 +92,7 @@ export const Footnote = Node.create({
}
return false
}
},
}
}
},
})

View File

@ -22,7 +22,7 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
addOptions() {
return {
node: 'paragraph',
notAfter: ['paragraph']
notAfter: ['paragraph'],
}
},
@ -61,9 +61,9 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
const lastNode = tr.doc.lastChild
return !nodeEqualsType({ node: lastNode, types: disabledNodes })
}
}
})
},
},
}),
]
}
},
})

View File

@ -1,22 +1,25 @@
import { createMemo, createSignal, For, Show } from 'solid-js'
import type { Shout } from '../../../graphql/types.gen'
import { capitalize } from '../../../utils/capitalize'
import { Icon } from '../../_shared/Icon'
import { clsx } from 'clsx'
import { CardTopic } from '../CardTopic'
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { getDescription } from '../../../utils/meta'
import { FeedArticlePopup } from '../FeedArticlePopup'
import { useLocalize } from '../../../context/localize'
import { getPagePath, openPage } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router'
import { Popover } from '../../_shared/Popover'
import { Image } from '../../_shared/Image'
import { clsx } from 'clsx'
import { createMemo, createSignal, For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { router, useRouter } from '../../../stores/router'
import { capitalize } from '../../../utils/capitalize'
import { getDescription } from '../../../utils/meta'
import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image'
import { Popover } from '../../_shared/Popover'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
import { AuthorLink } from '../../Author/AhtorLink'
import stylesHeader from '../../Nav/Header/Header.module.scss'
import { CardTopic } from '../CardTopic'
import { FeedArticlePopup } from '../FeedArticlePopup'
import styles from './ArticleCard.module.scss'
import stylesHeader from '../../Nav/Header/Header.module.scss'
interface ArticleCardProps {
settings?: {
@ -45,7 +48,7 @@ interface ArticleCardProps {
}
const getTitleAndSubtitle = (
article: Shout
article: Shout,
): {
title: string
subtitle: string
@ -90,7 +93,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
event.preventDefault()
openPage(router, 'article', { slug: props.article.slug })
changeSearchParam({
scrollTo: 'comments'
scrollTo: 'comments',
})
}
@ -111,7 +114,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
[styles.shoutCardCompact]: props.settings?.isCompact,
[styles.shoutCardSingle]: props.settings?.isSingle,
[styles.shoutCardBeside]: props.settings?.isBeside,
[styles.shoutCardNoImage]: !props.article.cover
[styles.shoutCardNoImage]: !props.article.cover,
}}
>
<Show when={!props.settings?.noimage && !props.settings?.isFeedMode}>
@ -155,7 +158,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
<div
class={clsx(styles.shoutCardTitlesContainer, {
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode,
})}
>
<a href={getPagePath(router, 'article', { slug: props.article.slug })}>

View File

@ -1,14 +1,18 @@
// TODO: additional entities list column + article
import { For, Show } from 'solid-js'
import { ArticleCard } from './ArticleCard'
import { TopicCard } from '../Topic/Card'
import styles from './Beside.module.scss'
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import { clsx } from 'clsx'
import { For, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import { AuthorBadge } from '../Author/AuthorBadge'
import { TopicCard } from '../Topic/Card'
import { ArticleCard } from './ArticleCard'
import styles from './Beside.module.scss'
type Props = {
title?: string
@ -36,7 +40,7 @@ export const Beside = (props: Props) => {
'col-lg-8',
styles[
`besideRatingColumn${props.wrapper.charAt(0).toUpperCase() + props.wrapper.slice(1)}`
]
],
)}
>
<Show when={!!props.title}>

View File

@ -1,6 +1,8 @@
import { clsx } from 'clsx'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { router } from '../../stores/router'
import styles from './CardTopic.module.scss'
type CardTopicProps = {
@ -16,7 +18,7 @@ export const CardTopic = (props: CardTopicProps) => {
<div
class={clsx(styles.shoutTopic, props.class, {
[styles.shoutTopicFloorImportant]: props.isFloorImportant,
[styles.shoutTopicFeedMode]: props.isFeedMode
[styles.shoutTopicFeedMode]: props.isFeedMode,
})}
>
<a href={getPagePath(router, 'topic', { slug: props.slug })}>{props.title}</a>

View File

@ -1,9 +1,12 @@
import styles from './FeedArticlePopup.module.scss'
import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
import { useLocalize } from '../../context/localize'
import { createEffect, createSignal, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { Popup } from '../_shared/Popup'
import styles from './FeedArticlePopup.module.scss'
type FeedArticlePopupProps = {
title: string
shareUrl?: string

View File

@ -1,6 +1,8 @@
import type { JSX } from 'solid-js/jsx-runtime'
import { For, Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import type { JSX } from 'solid-js/jsx-runtime'
import { For, Show } from 'solid-js'
import { ArticleCard } from './ArticleCard'
import './Group.scss'
@ -26,7 +28,7 @@ export default (props: GroupProps) => {
noicon: true,
isFloorImportant: true,
isBigTitle: true,
nodate: true
nodate: true,
}}
/>
</div>
@ -59,7 +61,7 @@ export default (props: GroupProps) => {
isBigTitle: true,
isCompact: true,
isFloorImportant: true,
nodate: true
nodate: true,
}}
/>
)}
@ -76,7 +78,7 @@ export default (props: GroupProps) => {
isBigTitle: true,
isCompact: true,
isFloorImportant: true,
nodate: true
nodate: true,
}}
/>
)}

View File

@ -1,5 +1,7 @@
import { Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { Show } from 'solid-js'
import { ArticleCard } from './ArticleCard'
export const Row1 = (props: {
@ -19,7 +21,7 @@ export const Row1 = (props: {
isSingle: true,
nodate: props.nodate,
noAuthorLink: props.noAuthorLink,
noauthor: props.noauthor
noauthor: props.noauthor,
}}
/>
</div>

View File

@ -1,11 +1,13 @@
import { createComputed, createSignal, Show, For } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { createComputed, createSignal, Show, For } from 'solid-js'
import { ArticleCard } from './ArticleCard'
const x = [
['12', '12'],
['8', '16'],
['16', '8']
['16', '8'],
]
export const Row2 = (props: {
@ -35,7 +37,7 @@ export const Row2 = (props: {
isWithCover: props.isEqual || x[y()][i()] === '16',
nodate: props.isEqual || props.nodate,
noAuthorLink: props.noAuthorLink,
noauthor: props.noauthor
noauthor: props.noauthor,
}}
/>
</div>

View File

@ -1,6 +1,8 @@
import type { JSX } from 'solid-js/jsx-runtime'
import { For, Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import type { JSX } from 'solid-js/jsx-runtime'
import { For, Show } from 'solid-js'
import { ArticleCard } from './ArticleCard'
export const Row3 = (props: {
@ -24,7 +26,7 @@ export const Row3 = (props: {
settings={{
nodate: props.nodate,
noAuthorLink: props.noAuthorLink,
noauthor: props.noauthor
noauthor: props.noauthor,
}}
/>
</div>

View File

@ -1,4 +1,5 @@
import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from './ArticleCard'
export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {

View File

@ -1,5 +1,7 @@
import { For } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { For } from 'solid-js'
import { ArticleCard } from './ArticleCard'
export default (props: { articles: Shout[] }) => (
@ -16,7 +18,7 @@ export default (props: { articles: Shout[] }) => (
isWithCover: true,
isBigTitle: true,
isVertical: true,
nodate: true
nodate: true,
}}
/>
</div>

View File

@ -1,14 +1,16 @@
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { createSignal, For, Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { router, useRouter } from '../../../stores/router'
import { useArticlesStore } from '../../../stores/zine/articles'
import { useSeenStore } from '../../../stores/zine/seen'
import { useSession } from '../../../context/session'
import { useLocalize } from '../../../context/localize'
import styles from './Sidebar.module.scss'
import { clsx } from 'clsx'
import { Icon } from '../../_shared/Icon'
import { Userpic } from '../../Author/Userpic'
import { getPagePath } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router'
import styles from './Sidebar.module.scss'
export const Sidebar = () => {
const { t } = useLocalize()
@ -33,7 +35,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feed')}
class={clsx({
[styles.selected]: page().route === 'feed'
[styles.selected]: page().route === 'feed',
})}
>
<span class={styles.sidebarItemName}>
@ -46,7 +48,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feedMy')}
class={clsx({
[styles.selected]: page().route === 'feedMy'
[styles.selected]: page().route === 'feedMy',
})}
>
<span class={styles.sidebarItemName}>
@ -59,7 +61,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feedCollaborations')}
class={clsx({
[styles.selected]: page().route === 'feedCollaborations'
[styles.selected]: page().route === 'feedCollaborations',
})}
>
<span class={styles.sidebarItemName}>
@ -72,7 +74,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feedDiscussions')}
class={clsx({
[styles.selected]: page().route === 'feedDiscussions'
[styles.selected]: page().route === 'feedDiscussions',
})}
>
<span class={styles.sidebarItemName}>
@ -85,7 +87,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feedBookmarks')}
class={clsx({
[styles.selected]: page().route === 'feedBookmarks'
[styles.selected]: page().route === 'feedBookmarks',
})}
>
<span class={styles.sidebarItemName}>
@ -98,7 +100,7 @@ export const Sidebar = () => {
<a
href={getPagePath(router, 'feedNotifications')}
class={clsx({
[styles.selected]: page().route === 'feedNotifications'
[styles.selected]: page().route === 'feedNotifications',
})}
>
<span class={styles.sidebarItemName}>

View File

@ -1,11 +1,14 @@
import { createSignal, For, createEffect } from 'solid-js'
import styles from './CreateModalContent.module.scss'
import InviteUser from './InviteUser'
import type { Author } from '../../graphql/types.gen'
import { hideModal } from '../../stores/ui'
import { createSignal, For, createEffect } from 'solid-js'
import { useInbox } from '../../context/inbox'
import { useLocalize } from '../../context/localize'
import { hideModal } from '../../stores/ui'
import InviteUser from './InviteUser'
import styles from './CreateModalContent.module.scss'
type inviteUser = Author & { selected: boolean }
type Props = {
@ -46,7 +49,7 @@ const CreateModalContent = (props: Props) => {
const handleClick = (user) => {
setCollectionToInvite((userCollection) => {
return userCollection.map((clickedUser) =>
user.id === clickedUser.id ? { ...clickedUser, selected: !clickedUser.selected } : clickedUser
user.id === clickedUser.id ? { ...clickedUser, selected: !clickedUser.selected } : clickedUser,
)
})
}

View File

@ -1,8 +1,10 @@
import { Show, createMemo } from 'solid-js'
import './DialogCard.module.scss'
import styles from './DialogAvatar.module.scss'
import { clsx } from 'clsx'
import { Show, createMemo } from 'solid-js'
import { getImageUrl } from '../../utils/getImageUrl'
import './DialogCard.module.scss'
import styles from './DialogAvatar.module.scss'
type Props = {
name: string
@ -25,7 +27,7 @@ const colors = [
'#668cff',
'#c34cfe',
'#e699ff',
'#6633ff'
'#6633ff',
]
const getById = (letter: string) =>
@ -42,7 +44,7 @@ const DialogAvatar = (props: Props) => {
class={clsx(styles.DialogAvatar, props.class, {
[styles.online]: props.online,
[styles.bordered]: props.bordered,
[styles.small]: props.size === 'small'
[styles.small]: props.size === 'small',
})}
style={{ 'background-color': `${randomBg()}` }}
>

View File

@ -1,12 +1,16 @@
import { Show, Switch, Match, createMemo } from 'solid-js'
import DialogAvatar from './DialogAvatar'
import type { ChatMember } from '../../graphql/types.gen'
import GroupDialogAvatar from './GroupDialogAvatar'
import { clsx } from 'clsx'
import styles from './DialogCard.module.scss'
import { Show, Switch, Match, createMemo } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { AuthorBadge } from '../Author/AuthorBadge'
import DialogAvatar from './DialogAvatar'
import GroupDialogAvatar from './GroupDialogAvatar'
import styles from './DialogCard.module.scss'
type DialogProps = {
online?: boolean
message?: string
@ -22,14 +26,14 @@ type DialogProps = {
const DialogCard = (props: DialogProps) => {
const { t, formatTime } = useLocalize()
const companions = createMemo(
() => props.members && props.members.filter((member) => member.id !== props.ownId)
() => props.members && props.members.filter((member) => member.id !== props.ownId),
)
const names = createMemo(
() =>
companions()
?.map((companion) => companion.name)
.join(', ')
.join(', '),
)
return (
@ -37,7 +41,7 @@ const DialogCard = (props: DialogProps) => {
<div
class={clsx(styles.DialogCard, {
[styles.opened]: props.isOpened,
[styles.hovered]: !props.isChatHeader
[styles.hovered]: !props.isChatHeader,
})}
onClick={props.onClick}
>

View File

@ -1,7 +1,9 @@
import type { Chat } from '../../graphql/types.gen'
import styles from './DialogHeader.module.scss'
import DialogCard from './DialogCard'
import styles from './DialogHeader.module.scss'
type DialogHeader = {
chat: Chat
ownId: number

View File

@ -1,10 +1,14 @@
import { For } from 'solid-js'
import './DialogCard.module.scss'
import styles from './GroupDialogAvatar.module.scss'
import { clsx } from 'clsx'
import type { ChatMember } from '../../graphql/types.gen'
import { clsx } from 'clsx'
import { For } from 'solid-js'
import DialogAvatar from './DialogAvatar'
import './DialogCard.module.scss'
import styles from './GroupDialogAvatar.module.scss'
type Props = {
users: ChatMember[]
}

View File

@ -1,8 +1,11 @@
import styles from './InviteUser.module.scss'
import DialogAvatar from './DialogAvatar'
import type { Author } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import DialogAvatar from './DialogAvatar'
import styles from './InviteUser.module.scss'
type DialogProps = {
author: Author
selected: boolean

View File

@ -1,12 +1,16 @@
import { createSignal, Show } from 'solid-js'
import { clsx } from 'clsx'
import styles from './Message.module.scss'
import DialogAvatar from './DialogAvatar'
import type { Message as MessageType, ChatMember } from '../../graphql/types.gen'
import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import DialogAvatar from './DialogAvatar'
import { MessageActionsPopup } from './MessageActionsPopup'
import QuotedMessage from './QuotedMessage'
import { useLocalize } from '../../context/localize'
import styles from './Message.module.scss'
type Props = {
content: MessageType

View File

@ -1,7 +1,9 @@
import { createEffect, createSignal, For } from 'solid-js'
import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
import { createEffect, createSignal, For } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { Popup } from '../_shared/Popup'
export type MessageActionType = 'reply' | 'copy' | 'pin' | 'forward' | 'select' | 'delete'
@ -18,7 +20,7 @@ export const MessageActionsPopup = (props: MessageActionsPopupProps) => {
{ name: t('Pin'), action: 'pin' },
{ name: t('Forward'), action: 'forward' },
{ name: t('Select'), action: 'select' },
{ name: t('Delete'), action: 'delete' }
{ name: t('Delete'), action: 'delete' },
]
createEffect(() => {
if (props.actionSelect) props.actionSelect(selectedAction())

View File

@ -1,4 +1,5 @@
import { Show } from 'solid-js'
import styles from './MessagesFallback.module.scss'
type MessagesFallback = {

View File

@ -1,7 +1,9 @@
import { Show } from 'solid-js'
import styles from './QuotedMessage.module.scss'
import { Icon } from '../_shared/Icon'
import { clsx } from 'clsx'
import { Show } from 'solid-js'
import { Icon } from '../_shared/Icon'
import styles from './QuotedMessage.module.scss'
type QuotedMessage = {
body: string
@ -17,7 +19,7 @@ const QuotedMessage = (props: QuotedMessage) => {
class={clsx(styles.QuotedMessage, {
[styles.reply]: props.variant === 'reply',
[styles.inline]: props.variant === 'inline',
[styles.own]: props.isOwn
[styles.own]: props.isOwn,
})}
>
<Show when={props.variant === 'reply'}>

View File

@ -1,7 +1,9 @@
import styles from './Search.module.scss'
import { createSignal } from 'solid-js'
import { Icon } from '../_shared/Icon'
import styles from './Search.module.scss'
type Props = {
placeholder: string
onChange: (value: () => string) => void

View File

@ -1,9 +1,11 @@
import styles from './AuthModalHeader.module.scss'
import { Show } from 'solid-js'
import { useLocalize } from '../../../../context/localize'
import { useRouter } from '../../../../stores/router'
import { AuthModalSearchParams } from '../types'
import styles from './AuthModalHeader.module.scss'
type Props = {
modalType: 'login' | 'register'
}
@ -14,7 +16,7 @@ export const AuthModalHeader = (props: Props) => {
const { source } = searchParams()
const generateModalTextsFromSource = (
modalType: 'login' | 'register'
modalType: 'login' | 'register',
): { title: string; description: string } => {
const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account'
@ -22,53 +24,53 @@ export const AuthModalHeader = (props: Props) => {
case 'create': {
return {
title: t(`${title} to publish articles`),
description: ''
description: '',
}
}
case 'bookmark': {
return {
title: t(`${title} to add to your bookmarks`),
description: t(
'In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to'
)
'In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to',
),
}
}
case 'discussions': {
return {
title: t(`${title} to participate in discussions`),
description: t(
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses"
)
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses",
),
}
}
case 'follow': {
return {
title: t(`${title} to subscribe`),
description: t(
'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed'
)
'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed',
),
}
}
case 'subscribe': {
return {
title: t(`${title} to subscribe to new publications`),
description: t(
'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed'
)
'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed',
),
}
}
case 'vote': {
return {
title: t(`${title} to vote`),
description: t(
'This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted'
)
'This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted',
),
}
}
default: {
return {
title: t(title),
description: ''
description: '',
}
}
}

View File

@ -1,18 +1,21 @@
import styles from './AuthModal.module.scss'
import { clsx } from 'clsx'
import { hideModal } from '../../../stores/ui'
import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { useRouter } from '../../../stores/router'
import type { ConfirmEmailSearchParams } from './types'
import { ApiError } from '../../../utils/apiClient'
import { useSession } from '../../../context/session'
import { clsx } from 'clsx'
import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
import { ApiError } from '../../../utils/apiClient'
import styles from './AuthModal.module.scss'
export const EmailConfirm = () => {
const { t } = useLocalize()
const {
session,
actions: { confirmEmail }
actions: { confirmEmail },
} = useSession()
const [isTokenExpired, setIsTokenExpired] = createSignal(false)

View File

@ -1,14 +1,18 @@
import styles from './AuthModal.module.scss'
import type { AuthModalSearchParams } from './types'
import { clsx } from 'clsx'
import { createSignal, JSX, Show } from 'solid-js'
import { useRouter } from '../../../stores/router'
import { email, setEmail } from './sharedLogic'
import type { AuthModalSearchParams } from './types'
import { ApiError } from '../../../utils/apiClient'
import { signSendLink } from '../../../stores/auth'
import { useLocalize } from '../../../context/localize'
import { signSendLink } from '../../../stores/auth'
import { useRouter } from '../../../stores/router'
import { ApiError } from '../../../utils/apiClient'
import { validateEmail } from '../../../utils/validateEmail'
import { email, setEmail } from './sharedLogic'
import styles from './AuthModal.module.scss'
type FormFields = {
email: string
}
@ -83,7 +87,7 @@ export const ForgotPasswordForm = () => {
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email
'pretty-form__item--error': validationErrors().email,
})}
>
<input
@ -116,7 +120,7 @@ export const ForgotPasswordForm = () => {
onClick={(event) => {
event.preventDefault()
changeSearchParam({
mode: 'register'
mode: 'register',
})
}}
>
@ -138,7 +142,7 @@ export const ForgotPasswordForm = () => {
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'login'
mode: 'login',
})
}
>

View File

@ -1,19 +1,23 @@
import styles from './AuthModal.module.scss'
import { clsx } from 'clsx'
import { SocialProviders } from './SocialProviders'
import { ApiError } from '../../../utils/apiClient'
import { createSignal, Show } from 'solid-js'
import { email, setEmail } from './sharedLogic'
import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types'
import { hideModal } from '../../../stores/ui'
import { useSession } from '../../../context/session'
import { signSendLink } from '../../../stores/auth'
import { validateEmail } from '../../../utils/validateEmail'
import { useSnackbar } from '../../../context/snackbar'
import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { useSnackbar } from '../../../context/snackbar'
import { signSendLink } from '../../../stores/auth'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
import { ApiError } from '../../../utils/apiClient'
import { validateEmail } from '../../../utils/validateEmail'
import { Icon } from '../../_shared/Icon'
import { AuthModalHeader } from './AuthModalHeader'
import { email, setEmail } from './sharedLogic'
import { SocialProviders } from './SocialProviders'
import styles from './AuthModal.module.scss'
type FormFields = {
email: string
@ -36,11 +40,11 @@ export const LoginForm = () => {
const authFormRef: { current: HTMLFormElement } = { current: null }
const {
actions: { showSnackbar }
actions: { showSnackbar },
} = useSnackbar()
const {
actions: { signIn }
actions: { signIn },
} = useSession()
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
@ -144,7 +148,7 @@ export const LoginForm = () => {
</Show>
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email
'pretty-form__item--error': validationErrors().email,
})}
>
<input
@ -164,7 +168,7 @@ export const LoginForm = () => {
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().password
'pretty-form__item--error': validationErrors().password,
})}
>
<input
@ -198,7 +202,7 @@ export const LoginForm = () => {
class="link"
onClick={() =>
changeSearchParam({
mode: 'forgot-password'
mode: 'forgot-password',
})
}
>
@ -215,7 +219,7 @@ export const LoginForm = () => {
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'register'
mode: 'register',
})
}
>

View File

@ -1,20 +1,24 @@
import { Show, createSignal } from 'solid-js'
import type { JSX } from 'solid-js'
import styles from './AuthModal.module.scss'
import { clsx } from 'clsx'
import { SocialProviders } from './SocialProviders'
import { ApiError } from '../../../utils/apiClient'
import { email, setEmail } from './sharedLogic'
import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types'
import { hideModal } from '../../../stores/ui'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
import { register } from '../../../stores/auth'
import type { JSX } from 'solid-js'
import { clsx } from 'clsx'
import { Show, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { register } from '../../../stores/auth'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
import { ApiError } from '../../../utils/apiClient'
import { validateEmail } from '../../../utils/validateEmail'
import { AuthModalHeader } from './AuthModalHeader'
import { Icon } from '../../_shared/Icon'
import { AuthModalHeader } from './AuthModalHeader'
import { email, setEmail } from './sharedLogic'
import { SocialProviders } from './SocialProviders'
import styles from './AuthModal.module.scss'
type FormFields = {
fullName: string
email: string
@ -126,7 +130,7 @@ export const RegisterForm = () => {
await register({
name: cleanName,
email: cleanEmail,
password: password()
password: password(),
})
setIsSuccess(true)
@ -156,7 +160,7 @@ export const RegisterForm = () => {
</Show>
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().fullName
'pretty-form__item--error': validationErrors().fullName,
})}
>
<input
@ -174,7 +178,7 @@ export const RegisterForm = () => {
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email
'pretty-form__item--error': validationErrors().email,
})}
>
<input
@ -199,7 +203,7 @@ export const RegisterForm = () => {
onClick={(event) => {
event.preventDefault()
changeSearchParam({
mode: 'login'
mode: 'login',
})
}}
>
@ -211,7 +215,7 @@ export const RegisterForm = () => {
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().password
'pretty-form__item--error': validationErrors().password,
})}
>
<input
@ -252,7 +256,7 @@ export const RegisterForm = () => {
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'login'
mode: 'login',
})
}
>

View File

@ -1,9 +1,9 @@
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import { hideModal } from '../../../stores/ui'
import { apiBaseUrl } from '../../../utils/config'
import { Icon } from '../../_shared/Icon'
import styles from './SocialProviders.module.scss'
import { apiBaseUrl } from '../../../utils/config'
import { useLocalize } from '../../../context/localize'
type Provider = 'facebook' | 'google' | 'vk' | 'github'

View File

@ -1,22 +1,26 @@
import { Dynamic } from 'solid-js/web'
import { Show, Component, createEffect, createMemo } from 'solid-js'
import { hideModal } from '../../../stores/ui'
import { useRouter } from '../../../stores/router'
import { clsx } from 'clsx'
import styles from './AuthModal.module.scss'
import { LoginForm } from './LoginForm'
import { isMobile } from '../../../utils/media-query'
import { RegisterForm } from './RegisterForm'
import { ForgotPasswordForm } from './ForgotPasswordForm'
import { EmailConfirm } from './EmailConfirm'
import type { AuthModalMode, AuthModalSearchParams } from './types'
import { clsx } from 'clsx'
import { Show, Component, createEffect, createMemo } from 'solid-js'
import { Dynamic } from 'solid-js/web'
import { useLocalize } from '../../../context/localize'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
import { isMobile } from '../../../utils/media-query'
import { EmailConfirm } from './EmailConfirm'
import { ForgotPasswordForm } from './ForgotPasswordForm'
import { LoginForm } from './LoginForm'
import { RegisterForm } from './RegisterForm'
import styles from './AuthModal.module.scss'
const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
login: LoginForm,
register: RegisterForm,
'forgot-password': ForgotPasswordForm,
'confirm-email': EmailConfirm
'confirm-email': EmailConfirm,
}
export const AuthModal = () => {
@ -40,7 +44,7 @@ export const AuthModal = () => {
<div
ref={rootRef}
class={clsx(styles.view, {
row: !source
row: !source,
})}
classList={{ [styles.signUp]: mode() === 'register' || mode() === 'confirm-email' }}
>
@ -54,7 +58,7 @@ export const AuthModal = () => {
<h4>{t(`Join the global community of authors!`)}</h4>
<p class={styles.authBenefits}>
{t(
'Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine'
'Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine',
)}
.&nbsp;
{t('New stories every day and even more!')}
@ -77,7 +81,7 @@ export const AuthModal = () => {
</Show>
<div
class={clsx(styles.auth, {
'col-md-12': !source
'col-md-12': !source,
})}
>
<Dynamic component={AUTH_MODAL_MODES[mode()]} />

View File

@ -1,6 +1,7 @@
import { useConfirm } from '../../../context/confirm'
import { useLocalize } from '../../../context/localize'
import { Button } from '../../_shared/Button'
import styles from './ConfirmModal.module.scss'
export const ConfirmModal = () => {
@ -8,7 +9,7 @@ export const ConfirmModal = () => {
const {
confirmMessage,
actions: { resolveConfirm }
actions: { resolveConfirm },
} = useConfirm()
return (

View File

@ -1,5 +1,6 @@
import './Confirmed.scss'
import { onMount } from 'solid-js'
import { useLocalize } from '../../context/localize'
export const Confirmed = (props: { token?: string }) => {

View File

@ -1,29 +1,28 @@
import { Show, createSignal, createEffect, onMount, onCleanup, For } from 'solid-js'
import { getPagePath, redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { Modal } from '../Modal'
import { AuthModal } from '../AuthModal'
import { HeaderAuth } from '../HeaderAuth'
import { ConfirmModal } from '../ConfirmModal'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { Snackbar } from '../Snackbar'
import { Icon } from '../../_shared/Icon'
import type { Topic } from '../../../graphql/types.gen'
import { useModalStore } from '../../../stores/ui'
import { router, ROUTES, useRouter } from '../../../stores/router'
import { getDescription } from '../../../utils/meta'
import { getPagePath, redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { Show, createSignal, createEffect, onMount, onCleanup, For } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { router, ROUTES, useRouter } from '../../../stores/router'
import { useModalStore } from '../../../stores/ui'
import { apiClient } from '../../../utils/apiClient'
import { getDescription } from '../../../utils/meta'
import { Icon } from '../../_shared/Icon'
import { Subscribe } from '../../_shared/Subscribe'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
import { AuthModal } from '../AuthModal'
import { ConfirmModal } from '../ConfirmModal'
import { HeaderAuth } from '../HeaderAuth'
import { Modal } from '../Modal'
import { Snackbar } from '../Snackbar'
import { Link } from './Link'
import styles from './Header.module.scss'
import { apiClient } from '../../../utils/apiClient'
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
import { Link } from './Link'
import { Subscribe } from '../../_shared/Subscribe'
type Props = {
title?: string
@ -47,7 +46,7 @@ export const Header = (props: Props) => {
const { modal } = useModalStore()
const { page } = useRouter()
const {
actions: { requireAuthentication }
actions: { requireAuthentication },
} = useSession()
const { searchParams } = useRouter<HeaderSearchParams>()
@ -167,7 +166,7 @@ export const Header = (props: Props) => {
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
[styles.headerScrolledBottom]:
(getIsScrollingBottom() && getIsScrolled() && !isProfilePopupVisible()) || isSharePopupVisible(),
[styles.headerWithTitle]: Boolean(props.title)
[styles.headerWithTitle]: Boolean(props.title),
}}
>
<Modal
@ -318,7 +317,7 @@ export const Header = (props: Props) => {
<p
class={styles.mobileDescription}
innerHTML={t(
'Independant magazine with an open horizontal cooperation about culture, science and society'
'Independant magazine with an open horizontal cooperation about culture, science and society',
)}
/>
<div class={styles.mobileCopyright}>

View File

@ -1,8 +1,10 @@
import styles from './Header.module.scss'
import { router, ROUTES, useRouter } from '../../../stores/router'
import { clsx } from 'clsx'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { router, ROUTES, useRouter } from '../../../stores/router'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import styles from './Header.module.scss'
type Props = {
onMouseOver: (event?: MouseEvent, time?: number) => void

View File

@ -1,19 +1,22 @@
import styles from './Header/Header.module.scss'
import { clsx } from 'clsx'
import { router, useRouter } from '../../stores/router'
import { Icon } from '../_shared/Icon'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { ProfilePopup } from './ProfilePopup'
import { Userpic } from '../Author/Userpic'
import { showModal } from '../../stores/ui'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { useSession } from '../../context/session'
import { useLocalize } from '../../context/localize'
import { getPagePath } from '@nanostores/router'
import { Button } from '../_shared/Button'
import { clsx } from 'clsx'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { useEditorContext } from '../../context/editor'
import { Popover } from '../_shared/Popover'
import { useLocalize } from '../../context/localize'
import { useNotifications } from '../../context/notifications'
import { useSession } from '../../context/session'
import { router, useRouter } from '../../stores/router'
import { showModal } from '../../stores/ui'
import { Button } from '../_shared/Button'
import { Icon } from '../_shared/Icon'
import { Popover } from '../_shared/Popover'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { Userpic } from '../Author/Userpic'
import { ProfilePopup } from './ProfilePopup'
import styles from './Header/Header.module.scss'
type Props = {
setIsProfilePopupVisible: (value: boolean) => void
@ -32,12 +35,12 @@ export const HeaderAuth = (props: Props) => {
const { session, isSessionLoaded, isAuthenticated } = useSession()
const {
unreadNotificationsCount,
actions: { showNotificationsPanel }
actions: { showNotificationsPanel },
} = useNotifications()
const {
form,
actions: { toggleEditorPanel, saveShout, publishShout }
actions: { toggleEditorPanel, saveShout, publishShout },
} = useEditorContext()
const handleBellIconClick = (event: Event) => {
@ -158,7 +161,7 @@ export const HeaderAuth = (props: Props) => {
{renderIconedButton({
value: t('Save'),
icon: 'save',
action: handleSaveButtonClick
action: handleSaveButtonClick,
})}
</div>
@ -166,7 +169,7 @@ export const HeaderAuth = (props: Props) => {
{renderIconedButton({
value: t('Publish'),
icon: 'publish',
action: handlePublishButtonClick
action: handlePublishButtonClick,
})}
</div>

View File

@ -1,13 +1,15 @@
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
import type { JSX } from 'solid-js'
import { redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
import { router } from '../../../stores/router'
import { hideModal, useModalStore } from '../../../stores/ui'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { Icon } from '../../_shared/Icon'
import styles from './Modal.module.scss'
import { redirectPage } from '@nanostores/router'
import { router } from '../../../stores/router'
import { Icon } from '../../_shared/Icon'
interface Props {
name: string
@ -50,7 +52,7 @@ export const Modal = (props: Props) => {
[styles.narrow]: props.variant === 'narrow',
['col-auto col-md-20 offset-md-2 col-lg-14 offset-lg-5']: props.variant === 'medium',
[styles.noPadding]: props.noPadding,
[styles.maxHeight]: props.maxHeight
[styles.maxHeight]: props.maxHeight,
})}
onClick={(event) => event.stopPropagation()}
>

View File

@ -1,5 +1,6 @@
import type { JSX } from 'solid-js/jsx-runtime'
import type { ModalType } from '../../../stores/ui'
import type { JSX } from 'solid-js/jsx-runtime'
import { showModal } from '../../../stores/ui'
export default (props: { name: ModalType; children: JSX.Element }) => {

View File

@ -1,17 +1,20 @@
import { useSession } from '../../context/session'
import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
import styles from '../_shared/Popup/Popup.module.scss'
import { getPagePath } from '@nanostores/router'
import { router } from '../../stores/router'
import { useLocalize } from '../../context/localize'
import { useSession } from '../../context/session'
import { router } from '../../stores/router'
import { Popup } from '../_shared/Popup'
import styles from '../_shared/Popup/Popup.module.scss'
type ProfilePopupProps = Omit<PopupProps, 'children'>
export const ProfilePopup = (props: ProfilePopupProps) => {
const {
user,
actions: { signOut }
actions: { signOut },
} = useSession()
const { t } = useLocalize()

View File

@ -1,8 +1,10 @@
import styles from './ProfileSettingsNavigation.module.scss'
import { clsx } from 'clsx'
import { useLocalize } from '../../../context/localize'
import { useRouter } from '../../../stores/router'
import styles from './ProfileSettingsNavigation.module.scss'
export const ProfileSettingsNavigation = () => {
const { t } = useLocalize()
const { page } = useRouter()

View File

@ -1,11 +1,13 @@
import { Show } from 'solid-js'
import { useSnackbar } from '../../context/snackbar'
import styles from './Snackbar.module.scss'
import { Transition } from 'solid-transition-group'
import { clsx } from 'clsx'
import { Show } from 'solid-js'
import { Transition } from 'solid-transition-group'
import { useSnackbar } from '../../context/snackbar'
import { Icon } from '../_shared/Icon'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import styles from './Snackbar.module.scss'
export const Snackbar = () => {
const { snackbarMessage } = useSnackbar()
@ -13,7 +15,7 @@ export const Snackbar = () => {
<div
class={clsx(styles.snackbar, {
[styles.error]: snackbarMessage()?.type === 'error',
[styles.success]: snackbarMessage()?.type === 'success'
[styles.success]: snackbarMessage()?.type === 'success',
})}
>
<ShowOnlyOnClient>

View File

@ -1,9 +1,11 @@
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import styles from './Topics.module.scss'
import { clsx } from 'clsx'
import { router, useRouter } from '../../../stores/router'
import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { useLocalize } from '../../../context/localize'
import { router, useRouter } from '../../../stores/router'
import { Icon } from '../../_shared/Icon'
import styles from './Topics.module.scss'
export const Topics = () => {
const { t } = useLocalize()

View File

@ -1,7 +1,9 @@
import { clsx } from 'clsx'
import styles from './EmptyMessage.module.scss'
import { useLocalize } from '../../../context/localize'
import styles from './EmptyMessage.module.scss'
export const EmptyMessage = () => {
const { t } = useLocalize()

View File

@ -1,15 +1,18 @@
import { clsx } from 'clsx'
import type { Notification } from '../../../graphql/types.gen'
import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { NotificationType } from '../../../graphql/types.gen'
import { getPagePath, openPage } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router'
import { useNotifications } from '../../../context/notifications'
import { useLocalize } from '../../../context/localize'
import type { ArticlePageSearchParams } from '../../Article/FullArticle'
import { TimeAgo } from '../../_shared/TimeAgo'
import styles from './NotificationView.module.scss'
import { getPagePath, openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useNotifications } from '../../../context/notifications'
import { NotificationType } from '../../../graphql/types.gen'
import { router, useRouter } from '../../../stores/router'
import { GroupAvatar } from '../../_shared/GroupAvatar'
import { TimeAgo } from '../../_shared/TimeAgo'
import styles from './NotificationView.module.scss'
type Props = {
notification: Notification
@ -36,7 +39,7 @@ type NotificationData = {
export const NotificationView = (props: Props) => {
const {
actions: { markNotificationAsRead, hideNotificationsPanel }
actions: { markNotificationAsRead, hideNotificationsPanel },
} = useNotifications()
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
@ -89,7 +92,7 @@ export const NotificationView = (props: Props) => {
return (
<>
{t('NotificationNewCommentText1', {
commentsCount: props.notification.occurrences
commentsCount: props.notification.occurrences,
})}{' '}
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
{shoutTitle}
@ -99,7 +102,7 @@ export const NotificationView = (props: Props) => {
{lastUser().name}
</a>{' '}
{t('NotificationNewCommentText3', {
restUsersCount: data().users.length - 1
restUsersCount: data().users.length - 1,
})}
</>
)
@ -108,7 +111,7 @@ export const NotificationView = (props: Props) => {
return (
<>
{t('NotificationNewReplyText1', {
commentsCount: props.notification.occurrences
commentsCount: props.notification.occurrences,
})}{' '}
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
{shoutTitle}
@ -118,7 +121,7 @@ export const NotificationView = (props: Props) => {
{lastUser().name}
</a>{' '}
{t('NotificationNewReplyText3', {
restUsersCount: data().users.length - 1
restUsersCount: data().users.length - 1,
})}
</>
)
@ -158,7 +161,7 @@ export const NotificationView = (props: Props) => {
<Show when={data()}>
<div
class={clsx(styles.NotificationView, props.class, {
[styles.seen]: props.notification.seen
[styles.seen]: props.notification.seen,
})}
onClick={handleClick}
>

View File

@ -1,16 +1,19 @@
import { clsx } from 'clsx'
import styles from './NotificationsPanel.module.scss'
import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js'
import { throttle } from 'throttle-debounce'
import { useLocalize } from '../../context/localize'
import { PAGE_SIZE, useNotifications } from '../../context/notifications'
import { useSession } from '../../context/session'
import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js'
import { PAGE_SIZE, useNotifications } from '../../context/notifications'
import { NotificationView } from './NotificationView'
import { EmptyMessage } from './EmptyMessage'
import { Button } from '../_shared/Button'
import { throttle } from 'throttle-debounce'
import { useSession } from '../../context/session'
import { Icon } from '../_shared/Icon'
import { EmptyMessage } from './EmptyMessage'
import { NotificationView } from './NotificationView'
import styles from './NotificationsPanel.module.scss'
type Props = {
isOpen: boolean
@ -51,20 +54,20 @@ export const NotificationsPanel = (props: Props) => {
unreadNotificationsCount,
loadedNotificationsCount,
totalNotificationsCount,
actions: { loadNotifications, markAllNotificationsAsRead }
actions: { loadNotifications, markAllNotificationsAsRead },
} = useNotifications()
const handleHide = () => {
props.onClose()
}
const panelRef: { current: HTMLDivElement } = {
current: null
current: null,
}
useOutsideClickHandler({
containerRef: panelRef,
predicate: () => props.isOpen,
handler: () => handleHide()
handler: () => handleHide(),
})
let windowScrollTop = 0
@ -150,14 +153,14 @@ export const NotificationsPanel = (props: Props) => {
await loadNextPage()
setIsLoading(false)
}
}
)
},
),
)
return (
<div
class={clsx(styles.container, {
[styles.isOpened]: props.isOpen
[styles.isOpened]: props.isOpen,
})}
>
<div ref={(el) => (panelRef.current = el)} class={styles.panel}>

View File

@ -1,12 +1,14 @@
import { For, Show, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js'
import { clsx } from 'clsx'
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import styles from './TableOfContents.module.scss'
import { isDesktop } from '../../utils/media-query'
import { For, Show, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js'
import { throttle, debounce } from 'throttle-debounce'
import { useLocalize } from '../../context/localize'
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
import { isDesktop } from '../../utils/media-query'
import { Icon } from '../_shared/Icon'
import styles from './TableOfContents.module.scss'
interface Props {
variant: 'article' | 'editor'
parentSelector: string
@ -23,7 +25,7 @@ const scrollToHeader = (element) => {
top:
element.getBoundingClientRect().top -
document.body.getBoundingClientRect().top -
DEFAULT_HEADER_OFFSET
DEFAULT_HEADER_OFFSET,
})
}
@ -43,7 +45,7 @@ export const TableOfContents = (props: Props) => {
const updateHeadings = () => {
setHeadings(
// eslint-disable-next-line unicorn/prefer-spread
Array.from(document.querySelector(props.parentSelector).querySelectorAll<HTMLElement>('h2, h3, h4'))
Array.from(document.querySelector(props.parentSelector).querySelectorAll<HTMLElement>('h2, h3, h4')),
)
setAreHeadingsLoaded(true)
}
@ -58,8 +60,8 @@ export const TableOfContents = (props: Props) => {
createEffect(
on(
() => props.body,
() => debouncedUpdateHeadings()
)
() => debouncedUpdateHeadings(),
),
)
onMount(() => {
@ -75,7 +77,7 @@ export const TableOfContents = (props: Props) => {
>
<div
class={clsx(styles.TableOfContentsFixedWrapper, {
[styles.TableOfContentsFixedWrapperLefted]: props.variant === 'editor'
[styles.TableOfContentsFixedWrapperLefted]: props.variant === 'editor',
})}
>
<div class={styles.TableOfContentsContainer}>
@ -92,7 +94,7 @@ export const TableOfContents = (props: Props) => {
class={clsx(styles.TableOfContentsHeadingsItem, {
[styles.TableOfContentsHeadingsItemH3]: h.nodeName === 'H3',
[styles.TableOfContentsHeadingsItemH4]: h.nodeName === 'H4',
[styles.active]: index() === activeHeaderIndex()
[styles.active]: index() === activeHeaderIndex(),
})}
innerHTML={h.textContent}
onClick={(e) => {
@ -111,9 +113,9 @@ export const TableOfContents = (props: Props) => {
class={clsx(
styles.TableOfContentsPrimaryButton,
{
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible()
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible(),
},
'd-none d-xl-block'
'd-none d-xl-block',
)}
onClick={(e) => {
e.preventDefault()
@ -149,9 +151,9 @@ export const TableOfContents = (props: Props) => {
class={clsx(
styles.TableOfContentsPrimaryButton,
{
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible()
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible(),
},
'd-xl-none'
'd-xl-none',
)}
onClick={(e) => {
e.preventDefault()

View File

@ -1,19 +1,20 @@
import { createMemo, createSignal, Show } from 'solid-js'
import type { Topic } from '../../graphql/types.gen'
import { FollowingEntity } from '../../graphql/types.gen'
import { follow, unfollow } from '../../stores/zine/common'
import { clsx } from 'clsx'
import { useSession } from '../../context/session'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { Icon } from '../_shared/Icon'
import { createMemo, createSignal, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { CardTopic } from '../Feed/CardTopic'
import { CheckButton } from '../_shared/CheckButton'
import { useSession } from '../../context/session'
import { FollowingEntity } from '../../graphql/types.gen'
import { follow, unfollow } from '../../stores/zine/common'
import { capitalize } from '../../utils/capitalize'
import { Button } from '../_shared/Button'
import { CheckButton } from '../_shared/CheckButton'
import { Icon } from '../_shared/Icon'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { CardTopic } from '../Feed/CardTopic'
import styles from './Card.module.scss'
import { Button } from '../_shared/Button'
import stylesButton from '../_shared/Button/Button.module.scss'
interface TopicProps {
@ -38,7 +39,7 @@ export const TopicCard = (props: TopicProps) => {
const {
subscriptions,
isSessionLoaded,
actions: { loadSubscriptions, requireAuthentication }
actions: { loadSubscriptions, requireAuthentication },
} = useSession()
const [isSubscribing, setIsSubscribing] = createSignal(false)
@ -89,7 +90,7 @@ export const TopicCard = (props: TopicProps) => {
classList={{
row: !props.subscribeButtonBottom,
[styles.topicCompact]: props.compact,
[styles.topicInRow]: props.isTopicInRow
[styles.topicInRow]: props.isTopicInRow,
}}
>
<div
@ -97,7 +98,7 @@ export const TopicCard = (props: TopicProps) => {
[clsx('col-sm-18 col-md-24 col-lg-14 col-xl-15', styles.topicDetails)]: props.isNarrow,
[clsx('col-24 col-sm-17 col-md-18', styles.topicDetails)]: props.compact,
[clsx('col-sm-17 col-md-18', styles.topicDetails)]:
!props.subscribeButtonBottom && !props.isNarrow && !props.compact
!props.subscribeButtonBottom && !props.isNarrow && !props.compact,
}}
>
<Show when={props.topic.title && !props.isCardMode}>
@ -132,7 +133,7 @@ export const TopicCard = (props: TopicProps) => {
classList={{
'col-sm-6 col-md-24 col-lg-10 col-xl-9': props.isNarrow,
'col-24 col-sm-7 col-md-6': props.compact,
'col-sm-7 col-md-6': !props.subscribeButtonBottom && !props.isNarrow && !props.compact
'col-sm-7 col-md-6': !props.subscribeButtonBottom && !props.isNarrow && !props.compact,
}}
>
<ShowOnlyOnClient>
@ -151,7 +152,7 @@ export const TopicCard = (props: TopicProps) => {
isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.isSubscribing]: isSubscribing(),
[stylesButton.subscribed]: subscribed()
[stylesButton.subscribed]: subscribed(),
})}
disabled={isSubscribing()}
/>

View File

@ -1,7 +1,9 @@
import type { Topic } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import './FloorHeader.scss'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import './FloorHeader.scss'
export default (props: { topic: Topic; color: string }) => {
const { t } = useLocalize()

Some files were not shown because too many files have changed in this diff Show More