Feature/lint (#317)
* prettier --------- Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
parent
4162d4318c
commit
784bb435c3
110
.eslintrc.cjs
110
.eslintrc.cjs
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
"singleQuote": true,
|
||||
"proseWrap": "always",
|
||||
"printWidth": 108,
|
||||
"trailingComma": "none",
|
||||
"plugins": [],
|
||||
"overrides": [
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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')} © 2015–{new Date().getFullYear()}{' '}
|
||||
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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()}>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)}
|
||||
>
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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 })}>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Shout } from '../../graphql/types.gen'
|
||||
|
||||
import { ArticleCard } from './ArticleCard'
|
||||
|
||||
export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()}` }}
|
||||
>
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Show } from 'solid-js'
|
||||
|
||||
import styles from './MessagesFallback.module.scss'
|
||||
|
||||
type MessagesFallback = {
|
||||
|
|
|
@ -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'}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 bookmarks, you can save favorite discussions and materials that you want to return to'
|
||||
)
|
||||
'In bookmarks, you can save favorite discussions and materials that you want to return to',
|
||||
),
|
||||
}
|
||||
}
|
||||
case 'discussions': {
|
||||
return {
|
||||
title: t(`${title} to participate in discussions`),
|
||||
description: t(
|
||||
"You ll be able to participate in discussions, rate others' comments and learn about new responses"
|
||||
)
|
||||
"You ll be able to participate in discussions, rate others' comments and learn about new responses",
|
||||
),
|
||||
}
|
||||
}
|
||||
case 'follow': {
|
||||
return {
|
||||
title: t(`${title} to subscribe`),
|
||||
description: t(
|
||||
'This way you ll be able to subscribe to authors, interesting topics and customize your feed'
|
||||
)
|
||||
'This way you ll be able to subscribe to authors, interesting topics and customize your feed',
|
||||
),
|
||||
}
|
||||
}
|
||||
case 'subscribe': {
|
||||
return {
|
||||
title: t(`${title} to subscribe to new publications`),
|
||||
description: t(
|
||||
'This way you ll be able to subscribe to authors, interesting topics and customize your feed'
|
||||
)
|
||||
'This way you ll be able to subscribe to authors, interesting topics and customize your feed',
|
||||
),
|
||||
}
|
||||
}
|
||||
case 'vote': {
|
||||
return {
|
||||
title: t(`${title} to vote`),
|
||||
description: t(
|
||||
'This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted'
|
||||
)
|
||||
'This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted',
|
||||
),
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
title: t(title),
|
||||
description: ''
|
||||
description: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
>
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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',
|
||||
)}
|
||||
.
|
||||
{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()]} />
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import './Confirmed.scss'
|
||||
import { onMount } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
|
||||
export const Confirmed = (props: { token?: string }) => {
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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()}
|
||||
>
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()}
|
||||
/>
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user