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 = {
|
module.exports = {
|
||||||
plugins: ['@typescript-eslint', 'import', 'sonarjs', 'unicorn', 'promise', 'solid', 'jest'],
|
plugins: ["@typescript-eslint", "import", "sonarjs", "unicorn", "promise", "solid", "jest"],
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:import/recommended',
|
"plugin:import/recommended",
|
||||||
'plugin:import/typescript',
|
"plugin:import/typescript",
|
||||||
'prettier',
|
"prettier",
|
||||||
'plugin:sonarjs/recommended',
|
"plugin:sonarjs/recommended",
|
||||||
'plugin:unicorn/recommended',
|
"plugin:unicorn/recommended",
|
||||||
'plugin:promise/recommended',
|
"plugin:promise/recommended",
|
||||||
'plugin:solid/recommended',
|
"plugin:solid/recommended",
|
||||||
'plugin:jest/recommended'
|
"plugin:jest/recommended"
|
||||||
],
|
],
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
files: ["**/*.ts", "**/*.tsx"],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2021,
|
ecmaVersion: 2021,
|
||||||
ecmaFeatures: { jsx: true },
|
ecmaFeatures: { jsx: true },
|
||||||
sourceType: 'module',
|
sourceType: "module",
|
||||||
project: './tsconfig.json'
|
project: "./tsconfig.json"
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'plugin:@typescript-eslint/recommended'
|
"plugin:@typescript-eslint/recommended"
|
||||||
// Maybe one day...
|
// Maybe one day...
|
||||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
|
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unused-vars': [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
'warn',
|
"warn",
|
||||||
{
|
{
|
||||||
argsIgnorePattern: '^_'
|
argsIgnorePattern: "^_"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
"@typescript-eslint/no-non-null-assertion": "error",
|
||||||
// TODO: Remove any usage and enable
|
// 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: {},
|
globals: {},
|
||||||
rules: {
|
rules: {
|
||||||
// Solid
|
// Solid
|
||||||
'solid/reactivity': 'off', // FIXME
|
"solid/reactivity": "off", // FIXME
|
||||||
'solid/no-innerhtml': 'off',
|
"solid/no-innerhtml": "off",
|
||||||
|
|
||||||
/** Unicorn **/
|
/** Unicorn **/
|
||||||
'unicorn/no-null': 'off',
|
"unicorn/no-null": "off",
|
||||||
'unicorn/filename-case': 'off',
|
"unicorn/filename-case": "off",
|
||||||
'unicorn/no-array-for-each': 'off',
|
"unicorn/no-array-for-each": "off",
|
||||||
'unicorn/no-array-reduce': 'off',
|
"unicorn/no-array-reduce": "off",
|
||||||
'unicorn/prefer-string-replace-all': 'warn',
|
"unicorn/prefer-string-replace-all": "warn",
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
"unicorn/prevent-abbreviations": "off",
|
||||||
'unicorn/prefer-module': 'off',
|
"unicorn/prefer-module": "off",
|
||||||
'unicorn/import-style': 'off',
|
"unicorn/import-style": "off",
|
||||||
'unicorn/numeric-separators-style': 'off',
|
"unicorn/numeric-separators-style": "off",
|
||||||
'unicorn/prefer-node-protocol': 'off',
|
"unicorn/prefer-node-protocol": "off",
|
||||||
'unicorn/prefer-dom-node-append': 'off', // FIXME
|
"unicorn/prefer-dom-node-append": "off", // FIXME
|
||||||
'unicorn/prefer-top-level-await': 'warn',
|
"unicorn/prefer-top-level-await": "warn",
|
||||||
'unicorn/consistent-function-scoping': 'warn',
|
"unicorn/consistent-function-scoping": "warn",
|
||||||
'unicorn/no-array-callback-reference': 'warn',
|
"unicorn/no-array-callback-reference": "warn",
|
||||||
'unicorn/no-array-method-this-argument': 'warn',
|
"unicorn/no-array-method-this-argument": "warn",
|
||||||
'unicorn/no-for-loop': 'off',
|
"unicorn/no-for-loop": "off",
|
||||||
|
|
||||||
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
|
"sonarjs/no-duplicate-string": ["warn", { threshold: 5 }],
|
||||||
|
|
||||||
// Promise
|
// Promise
|
||||||
// 'promise/catch-or-return': 'off', // Should be enabled
|
// 'promise/catch-or-return': 'off', // Should be enabled
|
||||||
'promise/always-return': 'off',
|
"promise/always-return": "off",
|
||||||
|
|
||||||
eqeqeq: 'error',
|
eqeqeq: "error",
|
||||||
'no-param-reassign': 'error',
|
"no-param-reassign": "error",
|
||||||
'no-nested-ternary': 'error',
|
"no-nested-ternary": "error",
|
||||||
'no-shadow': '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: {
|
settings: {
|
||||||
'import/resolver': {
|
"import/resolver": {
|
||||||
typescript: true,
|
typescript: true,
|
||||||
node: true
|
node: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"proseWrap": "always",
|
"proseWrap": "always",
|
||||||
"printWidth": 108,
|
"printWidth": 108,
|
||||||
"trailingComma": "none",
|
|
||||||
"plugins": [],
|
"plugins": [],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { renderPage } from 'vike/server'
|
import { renderPage } from 'vike/server'
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
runtime: 'edge'
|
runtime: 'edge',
|
||||||
}
|
}
|
||||||
export default async function handler(request) {
|
export default async function handler(request) {
|
||||||
const { url, cookies } = request
|
const { url, cookies } = request
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default async function handler(req, res) {
|
||||||
from: 'Discours Feedback Robot <robot@discours.io>',
|
from: 'Discours Feedback Robot <robot@discours.io>',
|
||||||
to: 'welcome@discours.io',
|
to: 'welcome@discours.io',
|
||||||
subject,
|
subject,
|
||||||
text
|
text,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -13,18 +13,18 @@ export default async (req, res) => {
|
||||||
const response = await mg.lists.members.createMember('newsletter@discours.io', {
|
const response = await mg.lists.members.createMember('newsletter@discours.io', {
|
||||||
address: email,
|
address: email,
|
||||||
subscribed: true,
|
subscribed: true,
|
||||||
upsert: 'yes'
|
upsert: 'yes',
|
||||||
})
|
})
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Email was added to newsletter list',
|
message: 'Email was added to newsletter list',
|
||||||
response: JSON.stringify(response)
|
response: JSON.stringify(response),
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
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 { Component, createEffect, createMemo } from 'solid-js'
|
||||||
import { ROUTES, useRouter } from '../stores/router'
|
|
||||||
import { Dynamic } from 'solid-js/web'
|
import { Dynamic } from 'solid-js/web'
|
||||||
|
|
||||||
import type { PageProps, RootSearchParams } from '../pages/types'
|
import { ConfirmProvider } from '../context/confirm'
|
||||||
import { HomePage } from '../pages/index.page'
|
import { EditorProvider } from '../context/editor'
|
||||||
import { AllTopicsPage } from '../pages/allTopics.page'
|
import { LocalizeProvider } from '../context/localize'
|
||||||
import { TopicPage } from '../pages/topic.page'
|
import { NotificationsProvider } from '../context/notifications'
|
||||||
import { AllAuthorsPage } from '../pages/allAuthors.page'
|
import { SessionProvider } from '../context/session'
|
||||||
import { AuthorPage } from '../pages/author.page'
|
import { SnackbarProvider } from '../context/snackbar'
|
||||||
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 { DiscussionRulesPage } from '../pages/about/discussionRules.page'
|
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
|
||||||
import { DogmaPage } from '../pages/about/dogma.page'
|
import { DogmaPage } from '../pages/about/dogma.page'
|
||||||
import { GuidePage } from '../pages/about/guide.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 { ProjectsPage } from '../pages/about/projects.page'
|
||||||
import { TermsOfUsePage } from '../pages/about/termsOfUse.page'
|
import { TermsOfUsePage } from '../pages/about/termsOfUse.page'
|
||||||
import { ThanksPage } from '../pages/about/thanks.page'
|
import { ThanksPage } from '../pages/about/thanks.page'
|
||||||
import { CreatePage } from '../pages/create.page'
|
import { AllAuthorsPage } from '../pages/allAuthors.page'
|
||||||
import { EditPage } from '../pages/edit.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 { ConnectPage } from '../pages/connect.page'
|
||||||
import { InboxPage } from '../pages/inbox.page'
|
import { CreatePage } from '../pages/create.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 { DraftsPage } from '../pages/drafts.page'
|
import { DraftsPage } from '../pages/drafts.page'
|
||||||
import { SnackbarProvider } from '../context/snackbar'
|
import { EditPage } from '../pages/edit.page'
|
||||||
import { LocalizeProvider } from '../context/localize'
|
import { ExpoPage } from '../pages/expo/expo.page'
|
||||||
import { ConfirmProvider } from '../context/confirm'
|
import { FeedPage } from '../pages/feed.page'
|
||||||
import { EditorProvider } from '../context/editor'
|
import { FourOuFourPage } from '../pages/fourOuFour.page'
|
||||||
import { NotificationsProvider } from '../context/notifications'
|
import { InboxPage } from '../pages/inbox.page'
|
||||||
import { Meta, MetaProvider } from '@solidjs/meta'
|
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
|
// TODO: lazy load
|
||||||
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||||
|
@ -80,7 +81,7 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
|
||||||
profileSettings: ProfileSettingsPage,
|
profileSettings: ProfileSettingsPage,
|
||||||
profileSecurity: ProfileSecurityPage,
|
profileSecurity: ProfileSecurityPage,
|
||||||
profileSubscriptions: ProfileSubscriptionsPage,
|
profileSubscriptions: ProfileSubscriptionsPage,
|
||||||
fourOuFour: FourOuFourPage
|
fourOuFour: FourOuFourPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const App = (props: PageProps) => {
|
export const App = (props: PageProps) => {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './AudioHeader.module.scss'
|
|
||||||
import { MediaItem } from '../../../pages/types'
|
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { createSignal, Show } from 'solid-js'
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { Topic } from '../../../graphql/types.gen'
|
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 { Image } from '../../_shared/Image'
|
||||||
|
import { CardTopic } from '../../Feed/CardTopic'
|
||||||
|
|
||||||
|
import styles from './AudioHeader.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title: string
|
title: string
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { MediaItem } from '../../../pages/types'
|
||||||
|
|
||||||
import { PlayerHeader } from './PlayerHeader'
|
import { PlayerHeader } from './PlayerHeader'
|
||||||
import { PlayerPlaylist } from './PlayerPlaylist'
|
import { PlayerPlaylist } from './PlayerPlaylist'
|
||||||
|
|
||||||
import styles from './AudioPlayer.module.scss'
|
import styles from './AudioPlayer.module.scss'
|
||||||
import { MediaItem } from '../../../pages/types'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
media: MediaItem[]
|
media: MediaItem[]
|
||||||
|
@ -35,8 +38,8 @@ export const AudioPlayer = (props: Props) => {
|
||||||
() => {
|
() => {
|
||||||
setCurrentTrackDuration(0)
|
setCurrentTrackDuration(0)
|
||||||
},
|
},
|
||||||
{ defer: true }
|
{ defer: true },
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePlayMedia = async (trackIndex: number) => {
|
const handlePlayMedia = async (trackIndex: number) => {
|
||||||
|
@ -131,7 +134,7 @@ export const AudioPlayer = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
class={styles.progressFilled}
|
class={styles.progressFilled}
|
||||||
style={{
|
style={{
|
||||||
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`
|
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { createSignal, Show } from 'solid-js'
|
|
||||||
import { clsx } from 'clsx'
|
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 { MediaItem } from '../../../pages/types'
|
||||||
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './AudioPlayer.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onPlayMedia: () => void
|
onPlayMedia: () => void
|
||||||
|
@ -18,7 +18,7 @@ type Props = {
|
||||||
|
|
||||||
export const PlayerHeader = (props: Props) => {
|
export const PlayerHeader = (props: Props) => {
|
||||||
const volumeContainerRef: { current: HTMLDivElement } = {
|
const volumeContainerRef: { current: HTMLDivElement } = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
|
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
|
||||||
|
@ -30,7 +30,7 @@ export const PlayerHeader = (props: Props) => {
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef: volumeContainerRef,
|
containerRef: volumeContainerRef,
|
||||||
predicate: () => isVolumeBarOpened(),
|
predicate: () => isVolumeBarOpened(),
|
||||||
handler: () => toggleVolumeBar()
|
handler: () => toggleVolumeBar(),
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +42,7 @@ export const PlayerHeader = (props: Props) => {
|
||||||
onClick={props.onPlayMedia}
|
onClick={props.onPlayMedia}
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.playButton,
|
styles.playButton,
|
||||||
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay
|
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay,
|
||||||
)}
|
)}
|
||||||
aria-label="Play"
|
aria-label="Play"
|
||||||
data-playing="false"
|
data-playing="false"
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { createSignal, For, Show } from 'solid-js'
|
import { createSignal, For, Show } from 'solid-js'
|
||||||
import { SharePopup, getShareUrl } from '../SharePopup'
|
|
||||||
import { getDescription } from '../../../utils/meta'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
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 { 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 SimplifiedEditor from '../../Editor/SimplifiedEditor'
|
||||||
|
import { SharePopup, getShareUrl } from '../SharePopup'
|
||||||
|
|
||||||
|
import styles from './AudioPlayer.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
media: MediaItem[]
|
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 { getPagePath } from '@nanostores/router'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
|
||||||
|
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { useConfirm } from '../../../context/confirm'
|
||||||
import { CommentRatingControl } from '../CommentRatingControl'
|
|
||||||
import { CommentDate } from '../CommentDate'
|
|
||||||
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
|
|
||||||
import { useSession } from '../../../context/session'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useReactions } from '../../../context/reactions'
|
import { useReactions } from '../../../context/reactions'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import { useSnackbar } from '../../../context/snackbar'
|
import { useSnackbar } from '../../../context/snackbar'
|
||||||
import { useConfirm } from '../../../context/confirm'
|
|
||||||
|
|
||||||
import { Author, Reaction, ReactionKind } from '../../../graphql/types.gen'
|
import { Author, Reaction, ReactionKind } from '../../../graphql/types.gen'
|
||||||
import { router } from '../../../stores/router'
|
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 styles from './Comment.module.scss'
|
||||||
import { AuthorLink } from '../../Author/AhtorLink'
|
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||||
|
|
||||||
|
@ -43,15 +41,15 @@ export const Comment = (props: Props) => {
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { createReaction, deleteReaction, updateReaction }
|
actions: { createReaction, deleteReaction, updateReaction },
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showConfirm }
|
actions: { showConfirm },
|
||||||
} = useConfirm()
|
} = useConfirm()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
|
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?'),
|
confirmBody: t('Are you sure you want to delete this comment?'),
|
||||||
confirmButtonLabel: t('Delete'),
|
confirmButtonLabel: t('Delete'),
|
||||||
confirmButtonVariant: 'danger',
|
confirmButtonVariant: 'danger',
|
||||||
declineButtonVariant: 'primary'
|
declineButtonVariant: 'primary',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
|
@ -86,7 +84,7 @@ export const Comment = (props: Props) => {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
replyTo: props.comment.id,
|
replyTo: props.comment.id,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.comment.shout.id
|
shout: props.comment.shout.id,
|
||||||
})
|
})
|
||||||
setClearEditor(true)
|
setClearEditor(true)
|
||||||
setIsReplyVisible(false)
|
setIsReplyVisible(false)
|
||||||
|
@ -107,7 +105,7 @@ export const Comment = (props: Props) => {
|
||||||
await updateReaction(props.comment.id, {
|
await updateReaction(props.comment.id, {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.comment.shout.id
|
shout: props.comment.shout.id,
|
||||||
})
|
})
|
||||||
setEditMode(false)
|
setEditMode(false)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
@ -122,7 +120,7 @@ export const Comment = (props: Props) => {
|
||||||
<li
|
<li
|
||||||
id={`comment_${comment().id}`}
|
id={`comment_${comment().id}`}
|
||||||
class={clsx(styles.comment, props.class, {
|
class={clsx(styles.comment, props.class, {
|
||||||
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen
|
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={!!body()}>
|
<Show when={!!body()}>
|
||||||
|
@ -135,7 +133,7 @@ export const Comment = (props: Props) => {
|
||||||
name={comment().createdBy.name}
|
name={comment().createdBy.name}
|
||||||
userpic={comment().createdBy.userpic}
|
userpic={comment().createdBy.userpic}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.compactUserpic]: props.compact
|
[styles.compactUserpic]: props.compact,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<small>
|
<small>
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { Show } from 'solid-js'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import type { Reaction } from '../../../graphql/types.gen'
|
import type { Reaction } from '../../../graphql/types.gen'
|
||||||
import { useLocalize } from '../../../context/localize'
|
|
||||||
import { clsx } from 'clsx'
|
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'
|
import styles from './CommentDate.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -27,7 +30,7 @@ export const CommentDate = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.commentDates, {
|
class={clsx(styles.commentDates, {
|
||||||
[styles.commentDatesLastInRow]: props.isLastInRow,
|
[styles.commentDatesLastInRow]: props.isLastInRow,
|
||||||
[styles.showOnHover]: props.showOnHover
|
[styles.showOnHover]: props.showOnHover,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<time class={styles.date}>{formattedDate(props.comment.createdAt)}</time>
|
<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 type { Reaction } from '../../graphql/types.gen'
|
||||||
import { ReactionKind } from '../../graphql/types.gen'
|
|
||||||
import { useSession } from '../../context/session'
|
import { clsx } from 'clsx'
|
||||||
import { useReactions } from '../../context/reactions'
|
|
||||||
import { createMemo } from 'solid-js'
|
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 { loadShout } from '../../stores/zine/articles'
|
||||||
import { Popup } from '../_shared/Popup'
|
import { Popup } from '../_shared/Popup'
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
|
||||||
import { VotersList } from '../_shared/VotersList'
|
import { VotersList } from '../_shared/VotersList'
|
||||||
|
|
||||||
|
import styles from './CommentRatingControl.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
comment: Reaction
|
comment: Reaction
|
||||||
}
|
}
|
||||||
|
@ -19,11 +22,11 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { user } = useSession()
|
const { user } = useSession()
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
const {
|
const {
|
||||||
reactionEntities,
|
reactionEntities,
|
||||||
actions: { createReaction, deleteReaction, loadReactionsBy }
|
actions: { createReaction, deleteReaction, loadReactionsBy },
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
|
@ -32,7 +35,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.createdBy.slug === user()?.slug &&
|
r.createdBy.slug === user()?.slug &&
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.replyTo === props.comment.id
|
r.replyTo === props.comment.id,
|
||||||
)
|
)
|
||||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||||
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
|
||||||
|
@ -43,8 +46,8 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
(r) =>
|
(r) =>
|
||||||
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.replyTo === props.comment.id
|
r.replyTo === props.comment.id,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
||||||
|
@ -53,7 +56,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.createdBy.slug === user()?.slug &&
|
r.createdBy.slug === user()?.slug &&
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.replyTo === props.comment.id
|
r.replyTo === props.comment.id,
|
||||||
)
|
)
|
||||||
return deleteReaction(reactionToDelete.id)
|
return deleteReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +71,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
await createReaction({
|
await createReaction({
|
||||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
shout: props.comment.shout.id,
|
shout: props.comment.shout.id,
|
||||||
replyTo: props.comment.id
|
replyTo: props.comment.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -77,7 +80,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
|
|
||||||
await loadShout(props.comment.shout.slug)
|
await loadShout(props.comment.shout.slug)
|
||||||
await loadReactionsBy({
|
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()}
|
disabled={!canVote() || !user()}
|
||||||
onClick={() => handleRatingChange(true)}
|
onClick={() => handleRatingChange(true)}
|
||||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
||||||
[styles.voted]: isUpvoted()
|
[styles.voted]: isUpvoted(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Popup
|
<Popup
|
||||||
|
@ -96,7 +99,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.commentRatingValue, {
|
class={clsx(styles.commentRatingValue, {
|
||||||
[styles.commentRatingPositive]: props.comment.stat.rating > 0,
|
[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}
|
{props.comment.stat.rating || 0}
|
||||||
|
@ -114,7 +117,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
disabled={!canVote() || !user()}
|
disabled={!canVote() || !user()}
|
||||||
onClick={() => handleRatingChange(false)}
|
onClick={() => handleRatingChange(false)}
|
||||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||||
[styles.voted]: isDownvoted()
|
[styles.voted]: isDownvoted(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { clsx } from 'clsx'
|
||||||
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
|
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
|
||||||
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 { useLocalize } from '../../context/localize'
|
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 SimplifiedEditor from '../Editor/SimplifiedEditor'
|
||||||
|
|
||||||
|
import { Comment } from './Comment'
|
||||||
|
|
||||||
|
import styles from './Article.module.scss'
|
||||||
|
|
||||||
type CommentsOrder = 'createdAt' | 'rating' | 'newOnly'
|
type CommentsOrder = 'createdAt' | 'rating' | 'newOnly'
|
||||||
|
|
||||||
const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {
|
const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {
|
||||||
|
@ -48,11 +51,11 @@ export const CommentsTree = (props: Props) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reactionEntities,
|
reactionEntities,
|
||||||
actions: { createReaction }
|
actions: { createReaction },
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
const comments = createMemo(() =>
|
const comments = createMemo(() =>
|
||||||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
|
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const sortedComments = createMemo(() => {
|
const sortedComments = createMemo(() => {
|
||||||
|
@ -96,7 +99,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
await createReaction({
|
await createReaction({
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.shoutId
|
shout: props.shoutId,
|
||||||
})
|
})
|
||||||
setClearEditor(true)
|
setClearEditor(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -154,7 +157,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
<Comment
|
<Comment
|
||||||
sortedComments={sortedComments()}
|
sortedComments={sortedComments()}
|
||||||
isArticleAuthor={Boolean(
|
isArticleAuthor={Boolean(
|
||||||
props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug)
|
props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug),
|
||||||
)}
|
)}
|
||||||
comment={reaction}
|
comment={reaction}
|
||||||
clickedReply={(id) => setClickedReplyId(id)}
|
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 { 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 { useLocalize } from '../../context/localize'
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
|
import { useSession } from '../../context/session'
|
||||||
import { MediaItem } from '../../pages/types'
|
import { MediaItem } from '../../pages/types'
|
||||||
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||||
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
import { getDescription } from '../../utils/meta'
|
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 { TableOfContents } from '../TableOfContents'
|
||||||
|
|
||||||
|
import { AudioHeader } from './AudioHeader'
|
||||||
import { AudioPlayer } from './AudioPlayer'
|
import { AudioPlayer } from './AudioPlayer'
|
||||||
|
import { CommentsTree } from './CommentsTree'
|
||||||
import { getShareUrl, SharePopup } from './SharePopup'
|
import { getShareUrl, SharePopup } from './SharePopup'
|
||||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
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 styles from './Article.module.scss'
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
import stylesHeader from '../Nav/Header/Header.module.scss'
|
||||||
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'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Shout
|
article: Shout
|
||||||
|
@ -46,7 +48,7 @@ const scrollTo = (el: HTMLElement) => {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: top + window.scrollY - DEFAULT_HEADER_OFFSET,
|
top: top + window.scrollY - DEFAULT_HEADER_OFFSET,
|
||||||
left: 0,
|
left: 0,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@ export const FullArticle = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
actions: { requireAuthentication }
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||||
|
@ -67,7 +69,7 @@ export const FullArticle = (props: Props) => {
|
||||||
const mainTopic = createMemo(
|
const mainTopic = createMemo(
|
||||||
() =>
|
() =>
|
||||||
props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic) ||
|
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)
|
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) {
|
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
|
||||||
scrollToComments()
|
scrollToComments()
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
scrollTo: null
|
scrollTo: null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -129,7 +131,7 @@ export const FullArticle = (props: Props) => {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (searchParams().commentId && isReactionsLoaded()) {
|
if (searchParams().commentId && isReactionsLoaded()) {
|
||||||
const commentElement = document.querySelector<HTMLElement>(
|
const commentElement = document.querySelector<HTMLElement>(
|
||||||
`[id='comment_${searchParams().commentId}']`
|
`[id='comment_${searchParams().commentId}']`,
|
||||||
)
|
)
|
||||||
|
|
||||||
changeSearchParam({ commentId: null })
|
changeSearchParam({ commentId: null })
|
||||||
|
@ -141,12 +143,12 @@ export const FullArticle = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { loadReactionsBy }
|
actions: { loadReactionsBy },
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await loadReactionsBy({
|
await loadReactionsBy({
|
||||||
by: { shout: props.article.slug }
|
by: { shout: props.article.slug },
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsReactionsLoaded(true)
|
setIsReactionsLoaded(true)
|
||||||
|
@ -165,7 +167,7 @@ export const FullArticle = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipElements: NodeListOf<HTMLElement> = document.querySelectorAll(
|
const tooltipElements: NodeListOf<HTMLElement> = document.querySelectorAll(
|
||||||
'[data-toggle="tooltip"], footnote'
|
'[data-toggle="tooltip"], footnote',
|
||||||
)
|
)
|
||||||
if (!tooltipElements) {
|
if (!tooltipElements) {
|
||||||
return
|
return
|
||||||
|
@ -190,19 +192,19 @@ export const FullArticle = (props: Props) => {
|
||||||
modifiers: [
|
modifiers: [
|
||||||
{
|
{
|
||||||
name: 'eventListeners',
|
name: 'eventListeners',
|
||||||
options: { scroll: false }
|
options: { scroll: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'offset',
|
name: 'offset',
|
||||||
options: {
|
options: {
|
||||||
offset: [0, 8]
|
offset: [0, 8],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'flip',
|
name: 'flip',
|
||||||
options: { fallbackPlacements: ['top'] }
|
options: { fallbackPlacements: ['top'] },
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
tooltip.style.visibility = 'hidden'
|
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 { 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 { createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
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 = {
|
type SharePopupProps = {
|
||||||
title: string
|
title: string
|
||||||
|
@ -26,7 +28,7 @@ export const SharePopup = (props: SharePopupProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [isVisible, setIsVisible] = createSignal(false)
|
const [isVisible, setIsVisible] = createSignal(false)
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -38,7 +40,7 @@ export const SharePopup = (props: SharePopupProps) => {
|
||||||
const [share] = createSocialShare(() => ({
|
const [share] = createSocialShare(() => ({
|
||||||
title: props.title,
|
title: props.title,
|
||||||
url: props.shareUrl,
|
url: props.shareUrl,
|
||||||
description: props.description
|
description: props.description,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const copyLink = async () => {
|
const copyLink = async () => {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, Show } from 'solid-js'
|
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 { ReactionKind, Shout } from '../../graphql/types.gen'
|
||||||
import { loadShout } from '../../stores/zine/articles'
|
import { loadShout } from '../../stores/zine/articles'
|
||||||
import { useSession } from '../../context/session'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { useReactions } from '../../context/reactions'
|
|
||||||
import { Popup } from '../_shared/Popup'
|
import { Popup } from '../_shared/Popup'
|
||||||
import { VotersList } from '../_shared/VotersList'
|
import { VotersList } from '../_shared/VotersList'
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import styles from './ShoutRatingControl.module.scss'
|
import styles from './ShoutRatingControl.module.scss'
|
||||||
|
|
||||||
interface ShoutRatingControlProps {
|
interface ShoutRatingControlProps {
|
||||||
|
@ -19,12 +21,12 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
actions: { requireAuthentication }
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reactionEntities,
|
reactionEntities,
|
||||||
actions: { createReaction, deleteReaction, loadReactionsBy }
|
actions: { createReaction, deleteReaction, loadReactionsBy },
|
||||||
} = useReactions()
|
} = useReactions()
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
|
@ -33,7 +35,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.createdBy.slug === user()?.slug &&
|
r.createdBy.slug === user()?.slug &&
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.replyTo
|
!r.replyTo,
|
||||||
)
|
)
|
||||||
|
|
||||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||||
|
@ -45,8 +47,8 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
(r) =>
|
(r) =>
|
||||||
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.replyTo
|
!r.replyTo,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
||||||
|
@ -55,7 +57,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.createdBy.slug === user()?.slug &&
|
r.createdBy.slug === user()?.slug &&
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.replyTo
|
!r.replyTo,
|
||||||
)
|
)
|
||||||
return deleteReaction(reactionToDelete.id)
|
return deleteReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
@ -69,13 +71,13 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
} else {
|
} else {
|
||||||
await createReaction({
|
await createReaction({
|
||||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
shout: props.shout.id
|
shout: props.shout.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadShout(props.shout.slug)
|
loadShout(props.shout.slug)
|
||||||
loadReactionsBy({
|
loadReactionsBy({
|
||||||
by: { shout: props.shout.slug }
|
by: { shout: props.shout.slug },
|
||||||
})
|
})
|
||||||
}, 'vote')
|
}, 'vote')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { createEffect, JSX, Show } from 'solid-js'
|
import { createEffect, JSX, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { hideModal } from '../../stores/ui'
|
|
||||||
import { useRouter } from '../../stores/router'
|
|
||||||
import { RootSearchParams } from '../../pages/types'
|
import { RootSearchParams } from '../../pages/types'
|
||||||
|
import { useRouter } from '../../stores/router'
|
||||||
|
import { hideModal } from '../../stores/ui'
|
||||||
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
|
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -25,9 +26,9 @@ export const AuthGuard = (props: Props) => {
|
||||||
changeSearchParam(
|
changeSearchParam(
|
||||||
{
|
{
|
||||||
source: 'authguard',
|
source: 'authguard',
|
||||||
modal: 'auth'
|
modal: 'auth',
|
||||||
},
|
},
|
||||||
true
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './AhtorLink.module.scss'
|
|
||||||
import { Author } from '../../../graphql/types.gen'
|
import { Author } from '../../../graphql/types.gen'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
|
|
||||||
|
import styles from './AhtorLink.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
size?: 'XS' | 'M' | 'L'
|
size?: 'XS' | 'M' | 'L'
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
import { openPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
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 styles from './AuthorBadge.module.scss'
|
||||||
import stylesButton from '../../_shared/Button/Button.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 = {
|
type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
|
@ -25,12 +27,12 @@ export const AuthorBadge = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
session,
|
session,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
actions: { loadSubscriptions, requireAuthentication }
|
actions: { loadSubscriptions, requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
const { changeSearchParam } = useRouter()
|
const { changeSearchParam } = useRouter()
|
||||||
const { t, formatDate } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const subscribed = createMemo(() =>
|
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) => {
|
const subscribe = async (really = true) => {
|
||||||
|
@ -53,7 +55,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, `inbox`)
|
openPage(router, `inbox`)
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
initChat: props.author.id.toString()
|
initChat: props.author.id.toString(),
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
@ -126,7 +128,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx(styles.actionButton, {
|
class={clsx(styles.actionButton, {
|
||||||
[styles.iconed]: props.iconButtons,
|
[styles.iconed]: props.iconButtons,
|
||||||
[stylesButton.subscribed]: subscribed()
|
[stylesButton.subscribed]: subscribed(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -151,7 +153,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx(styles.actionButton, {
|
class={clsx(styles.actionButton, {
|
||||||
[styles.iconed]: props.iconButtons,
|
[styles.iconed]: props.iconButtons,
|
||||||
[stylesButton.subscribed]: subscribed()
|
[stylesButton.subscribed]: subscribed(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import type { Author } from '../../../graphql/types.gen'
|
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 { openPage, redirectPage } from '@nanostores/router'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
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 { SubscriptionFilter } from '../../../pages/types'
|
||||||
|
import { router, useRouter } from '../../../stores/router'
|
||||||
|
import { follow, unfollow } from '../../../stores/zine/common'
|
||||||
import { isAuthor } from '../../../utils/isAuthor'
|
import { isAuthor } from '../../../utils/isAuthor'
|
||||||
import { AuthorBadge } from '../AuthorBadge'
|
import { translit } from '../../../utils/ru2en'
|
||||||
import { TopicBadge } from '../../Topic/TopicBadge'
|
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
|
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
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 styles from './AuthorCard.module.scss'
|
||||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||||
|
|
||||||
|
@ -32,7 +35,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
session,
|
session,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
isSessionLoaded,
|
isSessionLoaded,
|
||||||
actions: { loadSubscriptions, requireAuthentication }
|
actions: { loadSubscriptions, requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
||||||
|
@ -40,7 +43,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||||
|
|
||||||
const subscribed = createMemo<boolean>(() =>
|
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) => {
|
const subscribe = async (really = true) => {
|
||||||
|
@ -74,7 +77,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, `inbox`)
|
openPage(router, `inbox`)
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
initChat: props.author.id.toString()
|
initChat: props.author.id.toString(),
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
@ -218,7 +221,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
value={followButtonText()}
|
value={followButtonText()}
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[stylesButton.subscribed]: subscribed()
|
[stylesButton.subscribed]: subscribed(),
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import styles from './AuthorRatingControl.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
import styles from './AuthorRatingControl.module.scss'
|
||||||
|
|
||||||
interface AuthorRatingControlProps {
|
interface AuthorRatingControlProps {
|
||||||
author: Author
|
author: Author
|
||||||
class?: string
|
class?: string
|
||||||
|
@ -20,7 +22,7 @@ export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.rating, props.class, {
|
class={clsx(styles.rating, props.class, {
|
||||||
[styles.isUpvoted]: isUpvoted,
|
[styles.isUpvoted]: isUpvoted,
|
||||||
[styles.isDownvoted]: isDownvoted
|
[styles.isDownvoted]: isDownvoted,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { createMemo, Show } from 'solid-js'
|
|
||||||
import styles from './Userpic.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
import { createMemo, Show } from 'solid-js'
|
||||||
|
|
||||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||||
import { Loading } from '../../_shared/Loading'
|
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
|
import { Loading } from '../../_shared/Loading'
|
||||||
|
|
||||||
|
import styles from './Userpic.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string
|
name: string
|
||||||
|
@ -46,7 +48,7 @@ export const Userpic = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
|
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
|
||||||
['cursorPointer']: props.onClick
|
['cursorPointer']: props.onClick,
|
||||||
})}
|
})}
|
||||||
onClick={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 { clsx } from 'clsx'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { showModal } from '../../stores/ui'
|
||||||
|
|
||||||
|
import styles from './Banner.module.scss'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import '../../styles/help.scss'
|
import '../../styles/help.scss'
|
||||||
import { createSignal, onMount } from 'solid-js'
|
import { createSignal, onMount } from 'solid-js'
|
||||||
import { showModal } from '../../stores/ui'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
|
import { showModal } from '../../stores/ui'
|
||||||
|
|
||||||
export const Donate = () => {
|
export const Donate = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
@ -11,7 +12,7 @@ export const Donate = () => {
|
||||||
const cpOptions = {
|
const cpOptions = {
|
||||||
publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed',
|
publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed',
|
||||||
description: t('Help discours to grow'),
|
description: t('Help discours to grow'),
|
||||||
currency: 'RUB'
|
currency: 'RUB',
|
||||||
}
|
}
|
||||||
|
|
||||||
let amountSwitchElement: HTMLDivElement | undefined
|
let amountSwitchElement: HTMLDivElement | undefined
|
||||||
|
@ -22,13 +23,13 @@ export const Donate = () => {
|
||||||
const [period, setPeriod] = createSignal(monthly)
|
const [period, setPeriod] = createSignal(monthly)
|
||||||
const [amount, setAmount] = createSignal(0)
|
const [amount, setAmount] = createSignal(0)
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const initiated = () => {
|
const initiated = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const {
|
const {
|
||||||
cp: { CloudPayments }
|
cp: { CloudPayments },
|
||||||
} = window as any // Checkout(cpOptions)
|
} = window as any // Checkout(cpOptions)
|
||||||
setWidget(new CloudPayments())
|
setWidget(new CloudPayments())
|
||||||
console.log('[donate] payments initiated')
|
console.log('[donate] payments initiated')
|
||||||
|
@ -42,8 +43,8 @@ export const Donate = () => {
|
||||||
amount: amount() || 0, //сумма
|
amount: amount() || 0, //сумма
|
||||||
vat: 20, //ставка НДС
|
vat: 20, //ставка НДС
|
||||||
method: 0, // тег-1214 признак способа расчета - признак способа расчета
|
method: 0, // тег-1214 признак способа расчета - признак способа расчета
|
||||||
object: 0 // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
|
object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
|
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
|
||||||
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
|
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
|
||||||
|
@ -53,8 +54,8 @@ export const Donate = () => {
|
||||||
electronic: amount(), // Сумма оплаты электронными деньгами
|
electronic: amount(), // Сумма оплаты электронными деньгами
|
||||||
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
|
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
|
||||||
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
|
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
|
||||||
provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
|
provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,10 +94,10 @@ export const Donate = () => {
|
||||||
recurrent: {
|
recurrent: {
|
||||||
interval: period(), // local solid's signal
|
interval: period(), // local solid's signal
|
||||||
period: 1, // internal widget's
|
period: 1, // internal widget's
|
||||||
CustomerReciept: customerReciept() // чек для регулярных платежей
|
CustomerReciept: customerReciept(), // чек для регулярных платежей
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
(opts) => {
|
(opts) => {
|
||||||
// success
|
// success
|
||||||
|
@ -111,9 +112,9 @@ export const Donate = () => {
|
||||||
|
|
||||||
showSnackbar({
|
showSnackbar({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
body: reason
|
body: reason,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { hideModal } from '../../stores/ui'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { hideModal } from '../../stores/ui'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
|
|
||||||
export const Feedback = () => {
|
export const Feedback = () => {
|
||||||
|
@ -14,9 +14,9 @@ export const Feedback = () => {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/json',
|
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()
|
hideModal()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, For } from 'solid-js'
|
import { createMemo, For } from 'solid-js'
|
||||||
import styles from './Footer.module.scss'
|
|
||||||
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Subscribe } from '../_shared/Subscribe'
|
import { Subscribe } from '../_shared/Subscribe'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import styles from './Footer.module.scss'
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
|
|
||||||
export const Footer = () => {
|
export const Footer = () => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
|
@ -17,25 +18,25 @@ export const Footer = () => {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Manifest',
|
title: 'Manifest',
|
||||||
slug: '/about/manifest'
|
slug: '/about/manifest',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'How it works',
|
title: 'How it works',
|
||||||
slug: '/about/guide'
|
slug: '/about/guide',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dogma',
|
title: 'Dogma',
|
||||||
slug: '/about/dogma'
|
slug: '/about/dogma',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Principles',
|
title: 'Principles',
|
||||||
slug: '/about/principles'
|
slug: '/about/principles',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'How to write an article',
|
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: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Suggest an idea',
|
title: 'Suggest an idea',
|
||||||
slug: '/connect'
|
slug: '/connect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Become an author',
|
title: 'Become an author',
|
||||||
slug: '/create'
|
slug: '/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Support us',
|
title: 'Support us',
|
||||||
slug: '/about/help'
|
slug: '/about/help',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Feedback',
|
title: 'Feedback',
|
||||||
slug: '/#feedback'
|
slug: '/#feedback',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Work with us',
|
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: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Authors',
|
title: 'Authors',
|
||||||
slug: '/authors'
|
slug: '/authors',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Communities',
|
title: 'Communities',
|
||||||
slug: '/community'
|
slug: '/community',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Partners',
|
title: 'Partners',
|
||||||
slug: '/about/partners'
|
slug: '/about/partners',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Special projects',
|
title: 'Special projects',
|
||||||
slug: '/about/projects'
|
slug: '/about/projects',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: changeLangTitle(),
|
title: changeLangTitle(),
|
||||||
slug: changeLangLink(),
|
slug: changeLangLink(),
|
||||||
rel: 'external'
|
rel: 'external',
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const SOCIAL = [
|
const SOCIAL = [
|
||||||
{
|
{
|
||||||
name: 'facebook',
|
name: 'facebook',
|
||||||
href: 'https://facebook.com/discoursio'
|
href: 'https://facebook.com/discoursio',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vk',
|
name: 'vk',
|
||||||
href: 'https://vk.com/discoursio'
|
href: 'https://vk.com/discoursio',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'twitter',
|
name: 'twitter',
|
||||||
href: 'https://twitter.com/discours_io'
|
href: 'https://twitter.com/discours_io',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'telegram',
|
name: 'telegram',
|
||||||
href: 'https://t.me/discoursio'
|
href: 'https://t.me/discoursio',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
<footer class={styles.discoursFooter}>
|
<footer class={styles.discoursFooter}>
|
||||||
|
@ -143,7 +144,7 @@ export const Footer = () => {
|
||||||
<div class={clsx(styles.footerCopyright, 'row')}>
|
<div class={clsx(styles.footerCopyright, 'row')}>
|
||||||
<div class="col-md-18 col-lg-20">
|
<div class="col-md-18 col-lg-20">
|
||||||
{t(
|
{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()}{' '}
|
. {t('Discours')} © 2015–{new Date().getFullYear()}{' '}
|
||||||
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
<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 { useLocalize } from '../../context/localize'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
import { showModal } from '../../stores/ui'
|
||||||
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
|
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
|
||||||
|
|
||||||
|
import styles from './Hero.module.scss'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||||
|
@ -17,7 +17,7 @@ export default () => {
|
||||||
<h4 innerHTML={t('Horizontal collaborative journalistic platform')} />
|
<h4 innerHTML={t('Horizontal collaborative journalistic platform')} />
|
||||||
<p
|
<p
|
||||||
innerHTML={t(
|
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}>
|
<div class={styles.aboutDiscoursActions}>
|
||||||
|
@ -29,7 +29,7 @@ export default () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showModal('auth')
|
showModal('auth')
|
||||||
changeSearchParam({
|
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 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 { 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 { router } from '../../stores/router'
|
||||||
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './Draft.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
class?: string
|
class?: string
|
||||||
|
@ -18,11 +21,11 @@ type Props = {
|
||||||
export const Draft = (props: Props) => {
|
export const Draft = (props: Props) => {
|
||||||
const { t, formatDate } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const {
|
const {
|
||||||
actions: { showConfirm }
|
actions: { showConfirm },
|
||||||
} = useConfirm()
|
} = useConfirm()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const handlePublishLinkClick = (e) => {
|
const handlePublishLinkClick = (e) => {
|
||||||
|
@ -37,7 +40,7 @@ export const Draft = (props: Props) => {
|
||||||
confirmBody: t('Are you sure you want to delete this draft?'),
|
confirmBody: t('Are you sure you want to delete this draft?'),
|
||||||
confirmButtonLabel: t('Delete'),
|
confirmButtonLabel: t('Delete'),
|
||||||
confirmButtonVariant: 'danger',
|
confirmButtonVariant: 'danger',
|
||||||
declineButtonVariant: 'primary'
|
declineButtonVariant: 'primary',
|
||||||
})
|
})
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
props.onDelete(props.shout)
|
props.onDelete(props.shout)
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
|
import { Buffer } from 'buffer'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
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 { Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||||
|
import { DropArea } from '../../_shared/DropArea'
|
||||||
import { AudioPlayer } from '../../Article/AudioPlayer'
|
import { AudioPlayer } from '../../Article/AudioPlayer'
|
||||||
import { Buffer } from 'buffer'
|
|
||||||
|
import styles from './AudioUploader.module.scss'
|
||||||
|
|
||||||
window.Buffer = Buffer
|
window.Buffer = Buffer
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './AutoSaveNotice.module.scss'
|
|
||||||
import { Loading } from '../../_shared/Loading'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Loading } from '../../_shared/Loading'
|
||||||
|
|
||||||
|
import styles from './AutoSaveNotice.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
active: boolean
|
active: boolean
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
import styles from './BubbleMenu.module.scss'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
|
|
||||||
|
import styles from './BubbleMenu.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
ref: (el: HTMLElement) => void
|
ref: (el: HTMLElement) => void
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
import styles from './BubbleMenu.module.scss'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
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 { UploadedFile } from '../../../pages/types'
|
||||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
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 = {
|
type Props = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { createSignal, Show, For } from 'solid-js'
|
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
import styles from './BubbleMenu.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { createSignal, Show, For } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './BubbleMenu.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editor: Editor
|
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 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 { Bold } from '@tiptap/extension-bold'
|
||||||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
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 { 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 { 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 { Gapcursor } from '@tiptap/extension-gapcursor'
|
||||||
import { HardBreak } from '@tiptap/extension-hard-break'
|
import { HardBreak } from '@tiptap/extension-hard-break'
|
||||||
import { Heading } from '@tiptap/extension-heading'
|
import { Heading } from '@tiptap/extension-heading'
|
||||||
import { Highlight } from '@tiptap/extension-highlight'
|
import { Highlight } from '@tiptap/extension-highlight'
|
||||||
import { Link } from '@tiptap/extension-link'
|
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
|
||||||
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 { Image } from '@tiptap/extension-image'
|
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 { useSnackbar } from '../../context/snackbar'
|
||||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
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 = {
|
type Props = {
|
||||||
shoutId: number
|
shoutId: number
|
||||||
initialContent?: string
|
initialContent?: string
|
||||||
|
@ -61,7 +65,7 @@ const allowedImageTypes = new Set([
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/tiff',
|
'image/tiff',
|
||||||
'image/webp',
|
'image/webp',
|
||||||
'image/x-icon'
|
'image/x-icon',
|
||||||
])
|
])
|
||||||
|
|
||||||
const yDocs: Record<string, Doc> = {}
|
const yDocs: Record<string, Doc> = {}
|
||||||
|
@ -75,7 +79,7 @@ export const Editor = (props: Props) => {
|
||||||
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const docName = `shout-${props.shoutId}`
|
const docName = `shout-${props.shoutId}`
|
||||||
|
@ -88,47 +92,47 @@ export const Editor = (props: Props) => {
|
||||||
providers[docName] = new HocuspocusProvider({
|
providers[docName] = new HocuspocusProvider({
|
||||||
url: 'wss://hocuspocus.discours.io',
|
url: 'wss://hocuspocus.discours.io',
|
||||||
name: docName,
|
name: docName,
|
||||||
document: yDocs[docName]
|
document: yDocs[docName],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorElRef: {
|
const editorElRef: {
|
||||||
current: HTMLDivElement
|
current: HTMLDivElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const textBubbleMenuRef: {
|
const textBubbleMenuRef: {
|
||||||
current: HTMLDivElement
|
current: HTMLDivElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const incutBubbleMenuRef: {
|
const incutBubbleMenuRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
const figureBubbleMenuRef: {
|
const figureBubbleMenuRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
const blockquoteBubbleMenuRef: {
|
const blockquoteBubbleMenuRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const floatingMenuRef: {
|
const floatingMenuRef: {
|
||||||
current: HTMLDivElement
|
current: HTMLDivElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageFigure = Figure.extend({
|
const ImageFigure = Figure.extend({
|
||||||
name: 'capturedImage',
|
name: 'capturedImage',
|
||||||
content: 'figcaption image'
|
content: 'figcaption image',
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClipboardPaste = async () => {
|
const handleClipboardPaste = async () => {
|
||||||
|
@ -149,7 +153,7 @@ export const Editor = (props: Props) => {
|
||||||
source: blob.toString(),
|
source: blob.toString(),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
file
|
file,
|
||||||
}
|
}
|
||||||
|
|
||||||
showSnackbar({ body: t('Uploading image') })
|
showSnackbar({ body: t('Uploading image') })
|
||||||
|
@ -166,17 +170,17 @@ export const Editor = (props: Props) => {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: result.originalFilename
|
text: result.originalFilename,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: {
|
attrs: {
|
||||||
src: result.url
|
src: result.url,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -190,7 +194,7 @@ export const Editor = (props: Props) => {
|
||||||
element: editorElRef.current,
|
element: editorElRef.current,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: 'articleEditor'
|
class: 'articleEditor',
|
||||||
},
|
},
|
||||||
transformPastedHTML(html) {
|
transformPastedHTML(html) {
|
||||||
return html.replaceAll(/<img.*?>/g, '')
|
return html.replaceAll(/<img.*?>/g, '')
|
||||||
|
@ -198,7 +202,7 @@ export const Editor = (props: Props) => {
|
||||||
handlePaste: () => {
|
handlePaste: () => {
|
||||||
handleClipboardPaste()
|
handleClipboardPaste()
|
||||||
return false
|
return false
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
Document,
|
Document,
|
||||||
|
@ -211,31 +215,31 @@ export const Editor = (props: Props) => {
|
||||||
Strike,
|
Strike,
|
||||||
HorizontalRule.configure({
|
HorizontalRule.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'horizontalRule'
|
class: 'horizontalRule',
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
Underline,
|
Underline,
|
||||||
Link.configure({
|
Link.configure({
|
||||||
openOnClick: false
|
openOnClick: false,
|
||||||
}),
|
}),
|
||||||
Heading.configure({
|
Heading.configure({
|
||||||
levels: [2, 3, 4]
|
levels: [2, 3, 4],
|
||||||
}),
|
}),
|
||||||
BulletList,
|
BulletList,
|
||||||
OrderedList,
|
OrderedList,
|
||||||
ListItem,
|
ListItem,
|
||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: yDocs[docName]
|
document: yDocs[docName],
|
||||||
}),
|
}),
|
||||||
CollaborationCursor.configure({
|
CollaborationCursor.configure({
|
||||||
provider: providers[docName],
|
provider: providers[docName],
|
||||||
user: {
|
user: {
|
||||||
name: user().name,
|
name: user().name,
|
||||||
color: uniqolor(user().slug).color
|
color: uniqolor(user().slug).color,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
Placeholder.configure({
|
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,
|
Focus,
|
||||||
Gapcursor,
|
Gapcursor,
|
||||||
|
@ -243,8 +247,8 @@ export const Editor = (props: Props) => {
|
||||||
Highlight.configure({
|
Highlight.configure({
|
||||||
multicolor: true,
|
multicolor: true,
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'highlight'
|
class: 'highlight',
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
ImageFigure,
|
ImageFigure,
|
||||||
Image,
|
Image,
|
||||||
|
@ -267,8 +271,8 @@ export const Editor = (props: Props) => {
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
tippyOptions: {
|
tippyOptions: {
|
||||||
sticky: true
|
sticky: true,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'blockquoteBubbleMenu',
|
pluginKey: 'blockquoteBubbleMenu',
|
||||||
|
@ -286,8 +290,8 @@ export const Editor = (props: Props) => {
|
||||||
if (selectedElement) {
|
if (selectedElement) {
|
||||||
return selectedElement.getBoundingClientRect()
|
return selectedElement.getBoundingClientRect()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'incutBubbleMenu',
|
pluginKey: 'incutBubbleMenu',
|
||||||
|
@ -305,31 +309,31 @@ export const Editor = (props: Props) => {
|
||||||
if (selectedElement) {
|
if (selectedElement) {
|
||||||
return selectedElement.getBoundingClientRect()
|
return selectedElement.getBoundingClientRect()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'imageBubbleMenu',
|
pluginKey: 'imageBubbleMenu',
|
||||||
element: figureBubbleMenuRef.current,
|
element: figureBubbleMenuRef.current,
|
||||||
shouldShow: ({ editor: e, view }) => {
|
shouldShow: ({ editor: e, view }) => {
|
||||||
return view.hasFocus() && e.isActive('image')
|
return view.hasFocus() && e.isActive('image')
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
FloatingMenu.configure({
|
FloatingMenu.configure({
|
||||||
tippyOptions: {
|
tippyOptions: {
|
||||||
placement: 'left'
|
placement: 'left',
|
||||||
},
|
},
|
||||||
element: floatingMenuRef.current
|
element: floatingMenuRef.current,
|
||||||
}),
|
}),
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
Article
|
Article,
|
||||||
],
|
],
|
||||||
enablePasteRules: [Link],
|
enablePasteRules: [Link],
|
||||||
content: initialContent ?? null
|
content: initialContent ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { countWords, setEditor }
|
actions: { countWords, setEditor },
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
|
@ -341,7 +345,7 @@ export const Editor = (props: Props) => {
|
||||||
if (html()) {
|
if (html()) {
|
||||||
countWords({
|
countWords({
|
||||||
characters: editor().storage.characterCount.characters(),
|
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 type { MenuItem } from './Menu/Menu'
|
||||||
import { showModal } from '../../../stores/ui'
|
import type { Editor } from '@tiptap/core'
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
|
||||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
import { createEffect, createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
|
import { showModal } from '../../../stores/ui'
|
||||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
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 = {
|
type FloatingMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
@ -80,7 +84,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
if (menuOpen()) {
|
if (menuOpen()) {
|
||||||
setMenuOpen(false)
|
setMenuOpen(false)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleUpload = (image: UploadedFile) => {
|
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 { Icon } from '../../../_shared/Icon'
|
||||||
import { Popover } from '../../../_shared/Popover'
|
import { Popover } from '../../../_shared/Popover'
|
||||||
import { useLocalize } from '../../../../context/localize'
|
|
||||||
|
import styles from './Menu.module.scss'
|
||||||
|
|
||||||
export type MenuItem = 'image' | 'embed' | 'horizontal-rule'
|
export type MenuItem = 'image' | 'embed' | 'horizontal-rule'
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,9 @@
|
||||||
border: 1px solid #e9e9ee;
|
border: 1px solid #e9e9ee;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
opacity: 0;
|
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 {
|
&.visible {
|
||||||
height: 32px;
|
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 { clsx } from 'clsx'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
import { Popover } from '../../_shared/Popover'
|
||||||
|
|
||||||
|
import styles from './InlineForm.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Editor } from '@tiptap/core'
|
import { Editor } from '@tiptap/core'
|
||||||
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { validateUrl } from '../../../utils/validateUrl'
|
import { validateUrl } from '../../../utils/validateUrl'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../InlineForm'
|
||||||
import { useLocalize } from '../../../context/localize'
|
|
||||||
import { createEditorTransaction } from 'solid-tiptap'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
@ -24,7 +25,7 @@ export const InsertLinkForm = (props: Props) => {
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return (ed && ed.getAttributes('link').href) || ''
|
return (ed && ed.getAttributes('link').href) || ''
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
const handleClearLinkForm = () => {
|
const handleClearLinkForm = () => {
|
||||||
if (currentUrl()) {
|
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 { 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 { useEditorHTML } from 'solid-tiptap'
|
||||||
import Typograf from 'typograf'
|
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 { DarkModeToggle } from '../../_shared/DarkModeToggle'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './Panel.module.scss'
|
||||||
|
|
||||||
const typograf = new Typograf({ locale: ['ru', 'en-US'] })
|
const typograf = new Typograf({ locale: ['ru', 'en-US'] })
|
||||||
|
|
||||||
|
@ -26,11 +28,11 @@ export const Panel = (props: Props) => {
|
||||||
wordCounter,
|
wordCounter,
|
||||||
editorRef,
|
editorRef,
|
||||||
form,
|
form,
|
||||||
actions: { toggleEditorPanel, saveShout, publishShout }
|
actions: { toggleEditorPanel, saveShout, publishShout },
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
const containerRef: { current: HTMLElement } = {
|
const containerRef: { current: HTMLElement } = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
|
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
|
||||||
|
@ -39,7 +41,7 @@ export const Panel = (props: Props) => {
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef,
|
containerRef,
|
||||||
predicate: () => isEditorPanelVisible(),
|
predicate: () => isEditorPanelVisible(),
|
||||||
handler: () => toggleEditorPanel()
|
handler: () => toggleEditorPanel(),
|
||||||
})
|
})
|
||||||
|
|
||||||
useEscKeyDownHandler(() => {
|
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 { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { Portal } from 'solid-js/web'
|
import { Portal } from 'solid-js/web'
|
||||||
import {
|
import {
|
||||||
|
@ -5,34 +17,25 @@ import {
|
||||||
createTiptapEditor,
|
createTiptapEditor,
|
||||||
useEditorHTML,
|
useEditorHTML,
|
||||||
useEditorIsEmpty,
|
useEditorIsEmpty,
|
||||||
useEditorIsFocused
|
useEditorIsFocused,
|
||||||
} from 'solid-tiptap'
|
} from 'solid-tiptap'
|
||||||
|
|
||||||
import { useEditorContext } from '../../context/editor'
|
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 { 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 { Icon } from '../_shared/Icon'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { Italic } from '@tiptap/extension-italic'
|
|
||||||
import { Modal } from '../Nav/Modal'
|
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 { Figcaption } from './extensions/Figcaption'
|
||||||
|
import { Figure } from './extensions/Figure'
|
||||||
|
import { InsertLinkForm } from './InsertLinkForm'
|
||||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
import { UploadModalContent } from './UploadModalContent'
|
||||||
import { CharacterCount } from '@tiptap/extension-character-count'
|
|
||||||
|
import styles from './SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
@ -66,28 +69,28 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
const wrapperEditorElRef: {
|
const wrapperEditorElRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorElRef: {
|
const editorElRef: {
|
||||||
current: HTMLElement
|
current: HTMLElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const textBubbleMenuRef: {
|
const textBubbleMenuRef: {
|
||||||
current: HTMLDivElement
|
current: HTMLDivElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { setEditor }
|
actions: { setEditor },
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
const ImageFigure = Figure.extend({
|
const ImageFigure = Figure.extend({
|
||||||
name: 'capturedImage',
|
name: 'capturedImage',
|
||||||
content: 'figcaption image'
|
content: 'figcaption image',
|
||||||
})
|
})
|
||||||
|
|
||||||
const content = props.initialContent
|
const content = props.initialContent
|
||||||
|
@ -95,8 +98,8 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
element: editorElRef.current,
|
element: editorElRef.current,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: styles.simplifiedEditorField
|
class: styles.simplifiedEditorField,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
Document,
|
Document,
|
||||||
|
@ -105,16 +108,16 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
Bold,
|
Bold,
|
||||||
Italic,
|
Italic,
|
||||||
Link.configure({
|
Link.configure({
|
||||||
openOnClick: false
|
openOnClick: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
CharacterCount.configure({
|
CharacterCount.configure({
|
||||||
limit: MAX_DESCRIPTION_LIMIT
|
limit: MAX_DESCRIPTION_LIMIT,
|
||||||
}),
|
}),
|
||||||
Blockquote.configure({
|
Blockquote.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: styles.blockQuote
|
class: styles.blockQuote,
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'textBubbleMenu',
|
pluginKey: 'textBubbleMenu',
|
||||||
|
@ -124,18 +127,18 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const { empty } = selection
|
const { empty } = selection
|
||||||
return view.hasFocus() && !empty
|
return view.hasFocus() && !empty
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
ImageFigure,
|
ImageFigure,
|
||||||
Image,
|
Image,
|
||||||
Figcaption,
|
Figcaption,
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
emptyNodeClass: styles.emptyNode,
|
emptyNodeClass: styles.emptyNode,
|
||||||
placeholder: props.placeholder
|
placeholder: props.placeholder,
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
autofocus: props.autoFocus,
|
autofocus: props.autoFocus,
|
||||||
content: content ?? null
|
content: content ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
|
@ -147,7 +150,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
() => editor(),
|
() => editor(),
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return ed && ed.isActive(name)
|
return ed && ed.isActive(name)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const html = useEditorHTML(() => editor())
|
const html = useEditorHTML(() => editor())
|
||||||
|
@ -168,17 +171,17 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
text: image.originalFilename
|
text: image.originalFilename,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: {
|
attrs: {
|
||||||
src: image.url
|
src: image.url,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
hideModal()
|
hideModal()
|
||||||
|
@ -238,7 +241,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
|
|
||||||
const maxHeightStyle = {
|
const maxHeightStyle = {
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
'max-height': `${props.maxHeight}px`
|
'max-height': `${props.maxHeight}px`,
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -249,7 +252,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
[styles.minimal]: props.variant === 'minimal',
|
[styles.minimal]: props.variant === 'minimal',
|
||||||
[styles.bordered]: props.variant === 'bordered',
|
[styles.bordered]: props.variant === 'bordered',
|
||||||
[styles.isFocused]: isFocused() || !isEmpty(),
|
[styles.isFocused]: isFocused() || !isEmpty(),
|
||||||
[styles.labelVisible]: props.label && counter() > 0
|
[styles.labelVisible]: props.label && counter() > 0,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={props.maxLength && editor()}>
|
<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 type { Editor } from '@tiptap/core'
|
||||||
import styles from './TextBubbleMenu.module.scss'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect } from 'solid-js'
|
||||||
import { createEditorTransaction } from 'solid-tiptap'
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
import { InsertLinkForm } from '../InsertLinkForm'
|
import { InsertLinkForm } from '../InsertLinkForm'
|
||||||
import SimplifiedEditor from '../SimplifiedEditor'
|
import SimplifiedEditor from '../SimplifiedEditor'
|
||||||
|
|
||||||
|
import styles from './TextBubbleMenu.module.scss'
|
||||||
|
|
||||||
type BubbleMenuProps = {
|
type BubbleMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
isCommonMarkup: boolean
|
isCommonMarkup: boolean
|
||||||
|
@ -22,7 +25,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
const isActive = (name: string, attributes?: unknown) =>
|
const isActive = (name: string, attributes?: unknown) =>
|
||||||
createEditorTransaction(
|
createEditorTransaction(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(editor) => editor && editor.isActive(name, attributes)
|
(editor) => editor && editor.isActive(name, attributes),
|
||||||
)
|
)
|
||||||
|
|
||||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
||||||
|
@ -79,7 +82,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
}
|
}
|
||||||
const value = ed.getAttributes('footnote').value
|
const value = ed.getAttributes('footnote').value
|
||||||
setFootNote(value)
|
setFootNote(value)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleAddFootnote = (footnote) => {
|
const handleAddFootnote = (footnote) => {
|
||||||
|
@ -148,7 +151,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen(),
|
||||||
})}
|
})}
|
||||||
onClick={toggleTextSizePopup}
|
onClick={toggleTextSizePopup}
|
||||||
>
|
>
|
||||||
|
@ -165,7 +168,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isH1()
|
[styles.bubbleMenuButtonActive]: isH1(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||||
|
@ -182,7 +185,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isH2()
|
[styles.bubbleMenuButtonActive]: isH2(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||||
|
@ -199,7 +202,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isH3()
|
[styles.bubbleMenuButtonActive]: isH3(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleHeading({ level: 4 }).run()
|
props.editor.chain().focus().toggleHeading({ level: 4 }).run()
|
||||||
|
@ -219,7 +222,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isQuote()
|
[styles.bubbleMenuButtonActive]: isQuote(),
|
||||||
})}
|
})}
|
||||||
onClick={handleSetPunchline}
|
onClick={handleSetPunchline}
|
||||||
>
|
>
|
||||||
|
@ -233,7 +236,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isPunchLine()
|
[styles.bubbleMenuButtonActive]: isPunchLine(),
|
||||||
})}
|
})}
|
||||||
onClick={handleSetQuote}
|
onClick={handleSetQuote}
|
||||||
>
|
>
|
||||||
|
@ -250,7 +253,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isIncut()
|
[styles.bubbleMenuButtonActive]: isIncut(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleArticle().run()
|
props.editor.chain().focus().toggleArticle().run()
|
||||||
|
@ -274,7 +277,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isBold()
|
[styles.bubbleMenuButtonActive]: isBold(),
|
||||||
})}
|
})}
|
||||||
onClick={() => props.editor.chain().focus().toggleBold().run()}
|
onClick={() => props.editor.chain().focus().toggleBold().run()}
|
||||||
>
|
>
|
||||||
|
@ -288,7 +291,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isItalic()
|
[styles.bubbleMenuButtonActive]: isItalic(),
|
||||||
})}
|
})}
|
||||||
onClick={() => props.editor.chain().focus().toggleItalic().run()}
|
onClick={() => props.editor.chain().focus().toggleItalic().run()}
|
||||||
>
|
>
|
||||||
|
@ -304,7 +307,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isHighlight()
|
[styles.bubbleMenuButtonActive]: isHighlight(),
|
||||||
})}
|
})}
|
||||||
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
|
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
|
||||||
>
|
>
|
||||||
|
@ -321,7 +324,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setLinkEditorOpen(true)}
|
onClick={() => setLinkEditorOpen(true)}
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isLink()
|
[styles.bubbleMenuButtonActive]: isLink(),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon name="editor-link" />
|
<Icon name="editor-link" />
|
||||||
|
@ -336,7 +339,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isFootnote()
|
[styles.bubbleMenuButtonActive]: isFootnote(),
|
||||||
})}
|
})}
|
||||||
onClick={handleOpenFootnoteEditor}
|
onClick={handleOpenFootnoteEditor}
|
||||||
>
|
>
|
||||||
|
@ -349,7 +352,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: listBubbleOpen()
|
[styles.bubbleMenuButtonActive]: listBubbleOpen(),
|
||||||
})}
|
})}
|
||||||
onClick={toggleListPopup}
|
onClick={toggleListPopup}
|
||||||
>
|
>
|
||||||
|
@ -366,7 +369,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isBulletList()
|
[styles.bubbleMenuButtonActive]: isBulletList(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleBulletList().run()
|
props.editor.chain().focus().toggleBulletList().run()
|
||||||
|
@ -383,7 +386,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
ref={triggerRef}
|
ref={triggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isOrderedList()
|
[styles.bubbleMenuButtonActive]: isOrderedList(),
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleOrderedList().run()
|
props.editor.chain().focus().toggleOrderedList().run()
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import type { Topic } from '../../../graphql/types.gen'
|
import type { Topic } from '../../../graphql/types.gen'
|
||||||
|
|
||||||
import { createOptions, Select } from '@thisbeyond/solid-select'
|
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 { clsx } from 'clsx'
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
import { slugify } from '../../../utils/slugify'
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { clone } from '../../../utils/clone'
|
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 = {
|
type TopicSelectProps = {
|
||||||
topics: Topic[]
|
topics: Topic[]
|
||||||
|
@ -33,7 +37,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
disable: (topic) => {
|
disable: (topic) => {
|
||||||
return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug)
|
return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug)
|
||||||
},
|
},
|
||||||
createable: createValue
|
createable: createValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleChange = (selectedTopics: Topic[]) => {
|
const handleChange = (selectedTopics: Topic[]) => {
|
||||||
|
@ -57,7 +61,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.selectedItem, {
|
class={clsx(styles.selectedItem, {
|
||||||
[styles.mainTopic]: isMainTopic
|
[styles.mainTopic]: isMainTopic,
|
||||||
})}
|
})}
|
||||||
onClick={() => handleSelectedItemClick(item)}
|
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 { createDropzone, createFileUploader, UploadFile } from '@solid-primitives/upload'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Loading } from '../../_shared/Loading'
|
|
||||||
import { verifyImg } from '../../../utils/verifyImg'
|
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
|
import { hideModal } from '../../../stores/ui'
|
||||||
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
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 = {
|
type Props = {
|
||||||
onClose: (image?: UploadedFile) => void
|
onClose: (image?: UploadedFile) => void
|
||||||
|
@ -46,7 +48,7 @@ export const UploadModalContent = (props: Props) => {
|
||||||
source: blob.toString(),
|
source: blob.toString(),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
file: file
|
file: file,
|
||||||
}
|
}
|
||||||
await runUpload(fileToUpload)
|
await runUpload(fileToUpload)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -70,7 +72,7 @@ export const UploadModalContent = (props: Props) => {
|
||||||
} else {
|
} else {
|
||||||
setDragError(t('Image format not supported'))
|
setDragError(t('Image format not supported'))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const handleDrag = (event: MouseEvent) => {
|
const handleDrag = (event: MouseEvent) => {
|
||||||
if (event.type === 'dragenter' || event.type === 'dragover') {
|
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 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 { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||||
|
import { validateUrl } from '../../../utils/validateUrl'
|
||||||
import { VideoPlayer } from '../../_shared/VideoPlayer'
|
import { VideoPlayer } from '../../_shared/VideoPlayer'
|
||||||
|
|
||||||
|
import styles from './VideoUploader.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
video: MediaItem[]
|
video: MediaItem[]
|
||||||
onVideoAdd: (value: MediaItem[]) => void
|
onVideoAdd: (value: MediaItem[]) => void
|
||||||
|
@ -22,13 +25,13 @@ export const VideoUploader = (props: Props) => {
|
||||||
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const urlInput: {
|
const urlInput: {
|
||||||
current: HTMLInputElement
|
current: HTMLInputElement
|
||||||
} = {
|
} = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
|
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
|
||||||
|
@ -39,13 +42,13 @@ export const VideoUploader = (props: Props) => {
|
||||||
} else if (droppedFiles()[0].file.type.startsWith('video/')) {
|
} else if (droppedFiles()[0].file.type.startsWith('video/')) {
|
||||||
await showSnackbar({
|
await showSnackbar({
|
||||||
body: t(
|
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 {
|
} else {
|
||||||
setError(t('Video format not supported'))
|
setError(t('Video format not supported'))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const handleDrag = (event) => {
|
const handleDrag = (event) => {
|
||||||
if (event.type === 'dragenter' || event.type === 'dragover') {
|
if (event.type === 'dragenter' || event.type === 'dragover') {
|
||||||
|
@ -84,8 +87,8 @@ export const VideoUploader = (props: Props) => {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
showSnackbar({
|
showSnackbar({
|
||||||
body: t(
|
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}
|
ref={dropzoneRef}
|
||||||
|
|
|
@ -14,8 +14,8 @@ export default Node.create({
|
||||||
name: 'article',
|
name: 'article',
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
'data-type': 'incut'
|
'data-type': 'incut',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
group: 'block',
|
group: 'block',
|
||||||
content: 'block+',
|
content: 'block+',
|
||||||
|
@ -23,8 +23,8 @@ export default Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'article'
|
tag: 'article',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ export default Node.create({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
'data-float': {
|
'data-float': {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
'data-bg': {
|
'data-bg': {
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ export default Node.create({
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.updateAttributes(this.name, { 'data-bg': value })
|
return commands.updateAttributes(this.name, { 'data-bg': value })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,18 +14,18 @@ declare module '@tiptap/core' {
|
||||||
export const CustomBlockquote = Blockquote.extend({
|
export const CustomBlockquote = Blockquote.extend({
|
||||||
name: 'blockquote',
|
name: 'blockquote',
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
HTMLAttributes: {}
|
HTMLAttributes: {},
|
||||||
},
|
},
|
||||||
group: 'block',
|
group: 'block',
|
||||||
content: 'block+',
|
content: 'block+',
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
'data-float': {
|
'data-float': {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
'data-type': {
|
'data-type': {
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
@ -41,7 +41,7 @@ export const CustomBlockquote = Blockquote.extend({
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.updateAttributes(this.name, { 'data-float': value })
|
return commands.updateAttributes(this.name, { 'data-float': value })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,20 +16,20 @@ export const CustomImage = Image.extend({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
src: {
|
src: {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
alt: {
|
alt: {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
default: null
|
default: null,
|
||||||
},
|
},
|
||||||
'data-float': {
|
'data-float': {
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addCommands() {
|
addCommands() {
|
||||||
|
@ -39,14 +39,14 @@ export const CustomImage = Image.extend({
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.insertContent({
|
return commands.insertContent({
|
||||||
type: this.name,
|
type: this.name,
|
||||||
attrs: options
|
attrs: options,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setImageFloat:
|
setImageFloat:
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.updateAttributes(this.name, { 'data-float': value })
|
return commands.updateAttributes(this.name, { 'data-float': value })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,14 +25,14 @@ export const Embed = Node.create<IframeOptions>({
|
||||||
return {
|
return {
|
||||||
src: { default: null },
|
src: { default: null },
|
||||||
width: { default: null },
|
width: { default: null },
|
||||||
height: { default: null }
|
height: { default: null },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'iframe'
|
tag: 'iframe',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
@ -49,7 +49,7 @@ export const Embed = Node.create<IframeOptions>({
|
||||||
iframe.src = node.attrs.src
|
iframe.src = node.attrs.src
|
||||||
div.append(iframe)
|
div.append(iframe)
|
||||||
return {
|
return {
|
||||||
dom: div
|
dom: div,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -64,7 +64,7 @@ export const Embed = Node.create<IframeOptions>({
|
||||||
tr.replaceRangeWith(selection.from, selection.to, node)
|
tr.replaceRangeWith(selection.from, selection.to, node)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const Figcaption = Node.create({
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
HTMLAttributes: {}
|
HTMLAttributes: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ export const Figcaption = Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'figcaption'
|
tag: 'figcaption',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export const Figcaption = Node.create({
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.focus(value)
|
return commands.focus(value)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const Figure = Node.create({
|
||||||
name: 'figure',
|
name: 'figure',
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
HTMLAttributes: {}
|
HTMLAttributes: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
group: 'block',
|
group: 'block',
|
||||||
|
@ -22,15 +22,15 @@ export const Figure = Node.create({
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
'data-float': null
|
'data-float': null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: `figure[data-type="${this.name}"]`
|
tag: `figure[data-type="${this.name}"]`,
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -54,10 +54,10 @@ export const Figure = Node.create({
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ export const Figure = Node.create({
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) => {
|
({ commands }) => {
|
||||||
return commands.updateAttributes(this.name, { 'data-float': value })
|
return commands.updateAttributes(this.name, { 'data-float': value })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const Footnote = Node.create({
|
||||||
name: 'footnote',
|
name: 'footnote',
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
HTMLAttributes: {}
|
HTMLAttributes: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
group: 'inline',
|
group: 'inline',
|
||||||
|
@ -29,18 +29,18 @@ export const Footnote = Node.create({
|
||||||
parseHTML: (element) => element.dataset.value || null,
|
parseHTML: (element) => element.dataset.value || null,
|
||||||
renderHTML: (attributes) => {
|
renderHTML: (attributes) => {
|
||||||
return {
|
return {
|
||||||
'data-value': attributes.value
|
'data-value': attributes.value,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'footnote'
|
tag: 'footnote',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export const Footnote = Node.create({
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
node: 'paragraph',
|
node: 'paragraph',
|
||||||
notAfter: ['paragraph']
|
notAfter: ['paragraph'],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,9 +61,9 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
||||||
const lastNode = tr.doc.lastChild
|
const lastNode = tr.doc.lastChild
|
||||||
|
|
||||||
return !nodeEqualsType({ node: lastNode, types: disabledNodes })
|
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 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 { getPagePath, openPage } from '@nanostores/router'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { clsx } from 'clsx'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import { Image } from '../../_shared/Image'
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
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 { 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 styles from './ArticleCard.module.scss'
|
||||||
|
import stylesHeader from '../../Nav/Header/Header.module.scss'
|
||||||
|
|
||||||
interface ArticleCardProps {
|
interface ArticleCardProps {
|
||||||
settings?: {
|
settings?: {
|
||||||
|
@ -45,7 +48,7 @@ interface ArticleCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitleAndSubtitle = (
|
const getTitleAndSubtitle = (
|
||||||
article: Shout
|
article: Shout,
|
||||||
): {
|
): {
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
|
@ -90,7 +93,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
openPage(router, 'article', { slug: props.article.slug })
|
openPage(router, 'article', { slug: props.article.slug })
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
scrollTo: 'comments'
|
scrollTo: 'comments',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +114,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[styles.shoutCardCompact]: props.settings?.isCompact,
|
[styles.shoutCardCompact]: props.settings?.isCompact,
|
||||||
[styles.shoutCardSingle]: props.settings?.isSingle,
|
[styles.shoutCardSingle]: props.settings?.isSingle,
|
||||||
[styles.shoutCardBeside]: props.settings?.isBeside,
|
[styles.shoutCardBeside]: props.settings?.isBeside,
|
||||||
[styles.shoutCardNoImage]: !props.article.cover
|
[styles.shoutCardNoImage]: !props.article.cover,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={!props.settings?.noimage && !props.settings?.isFeedMode}>
|
<Show when={!props.settings?.noimage && !props.settings?.isFeedMode}>
|
||||||
|
@ -155,7 +158,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardTitlesContainer, {
|
class={clsx(styles.shoutCardTitlesContainer, {
|
||||||
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
|
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href={getPagePath(router, 'article', { slug: props.article.slug })}>
|
<a href={getPagePath(router, 'article', { slug: props.article.slug })}>
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
// TODO: additional entities list column + article
|
// 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 type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { Icon } from '../_shared/Icon'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
|
import { TopicCard } from '../Topic/Card'
|
||||||
|
|
||||||
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
|
import styles from './Beside.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -36,7 +40,7 @@ export const Beside = (props: Props) => {
|
||||||
'col-lg-8',
|
'col-lg-8',
|
||||||
styles[
|
styles[
|
||||||
`besideRatingColumn${props.wrapper.charAt(0).toUpperCase() + props.wrapper.slice(1)}`
|
`besideRatingColumn${props.wrapper.charAt(0).toUpperCase() + props.wrapper.slice(1)}`
|
||||||
]
|
],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Show when={!!props.title}>
|
<Show when={!!props.title}>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
import { router } from '../../stores/router'
|
import { router } from '../../stores/router'
|
||||||
|
|
||||||
import styles from './CardTopic.module.scss'
|
import styles from './CardTopic.module.scss'
|
||||||
|
|
||||||
type CardTopicProps = {
|
type CardTopicProps = {
|
||||||
|
@ -16,7 +18,7 @@ export const CardTopic = (props: CardTopicProps) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutTopic, props.class, {
|
class={clsx(styles.shoutTopic, props.class, {
|
||||||
[styles.shoutTopicFloorImportant]: props.isFloorImportant,
|
[styles.shoutTopicFloorImportant]: props.isFloorImportant,
|
||||||
[styles.shoutTopicFeedMode]: props.isFeedMode
|
[styles.shoutTopicFeedMode]: props.isFeedMode,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href={getPagePath(router, 'topic', { slug: props.slug })}>{props.title}</a>
|
<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 type { PopupProps } from '../_shared/Popup'
|
||||||
import { Popup } from '../_shared/Popup'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
import { createEffect, createSignal, Show } from 'solid-js'
|
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 = {
|
type FeedArticlePopupProps = {
|
||||||
title: string
|
title: string
|
||||||
shareUrl?: 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 { Shout } from '../../graphql/types.gen'
|
||||||
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
|
||||||
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
import './Group.scss'
|
import './Group.scss'
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ export default (props: GroupProps) => {
|
||||||
noicon: true,
|
noicon: true,
|
||||||
isFloorImportant: true,
|
isFloorImportant: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
nodate: true
|
nodate: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +61,7 @@ export default (props: GroupProps) => {
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isCompact: true,
|
isCompact: true,
|
||||||
isFloorImportant: true,
|
isFloorImportant: true,
|
||||||
nodate: true
|
nodate: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -76,7 +78,7 @@ export default (props: GroupProps) => {
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isCompact: true,
|
isCompact: true,
|
||||||
isFloorImportant: true,
|
isFloorImportant: true,
|
||||||
nodate: true
|
nodate: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Show } from 'solid-js'
|
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
import { Show } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row1 = (props: {
|
export const Row1 = (props: {
|
||||||
|
@ -19,7 +21,7 @@ export const Row1 = (props: {
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
nodate: props.nodate,
|
nodate: props.nodate,
|
||||||
noAuthorLink: props.noAuthorLink,
|
noAuthorLink: props.noAuthorLink,
|
||||||
noauthor: props.noauthor
|
noauthor: props.noauthor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { createComputed, createSignal, Show, For } from 'solid-js'
|
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
import { createComputed, createSignal, Show, For } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
const x = [
|
const x = [
|
||||||
['12', '12'],
|
['12', '12'],
|
||||||
['8', '16'],
|
['8', '16'],
|
||||||
['16', '8']
|
['16', '8'],
|
||||||
]
|
]
|
||||||
|
|
||||||
export const Row2 = (props: {
|
export const Row2 = (props: {
|
||||||
|
@ -35,7 +37,7 @@ export const Row2 = (props: {
|
||||||
isWithCover: props.isEqual || x[y()][i()] === '16',
|
isWithCover: props.isEqual || x[y()][i()] === '16',
|
||||||
nodate: props.isEqual || props.nodate,
|
nodate: props.isEqual || props.nodate,
|
||||||
noAuthorLink: props.noAuthorLink,
|
noAuthorLink: props.noAuthorLink,
|
||||||
noauthor: props.noauthor
|
noauthor: props.noauthor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 { Shout } from '../../graphql/types.gen'
|
||||||
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
|
||||||
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row3 = (props: {
|
export const Row3 = (props: {
|
||||||
|
@ -24,7 +26,7 @@ export const Row3 = (props: {
|
||||||
settings={{
|
settings={{
|
||||||
nodate: props.nodate,
|
nodate: props.nodate,
|
||||||
noAuthorLink: props.noAuthorLink,
|
noAuthorLink: props.noAuthorLink,
|
||||||
noauthor: props.noauthor
|
noauthor: props.noauthor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {
|
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 type { Shout } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
import { For } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export default (props: { articles: Shout[] }) => (
|
export default (props: { articles: Shout[] }) => (
|
||||||
|
@ -16,7 +18,7 @@ export default (props: { articles: Shout[] }) => (
|
||||||
isWithCover: true,
|
isWithCover: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isVertical: true,
|
isVertical: true,
|
||||||
nodate: true
|
nodate: true,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
import { getPagePath } from '@nanostores/router'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, For, Show } from 'solid-js'
|
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 { useArticlesStore } from '../../../stores/zine/articles'
|
||||||
import { useSeenStore } from '../../../stores/zine/seen'
|
import { useSeenStore } from '../../../stores/zine/seen'
|
||||||
import { useSession } from '../../../context/session'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { useLocalize } from '../../../context/localize'
|
|
||||||
import styles from './Sidebar.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import { Userpic } from '../../Author/Userpic'
|
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 = () => {
|
export const Sidebar = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
@ -33,7 +35,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feed')}
|
href={getPagePath(router, 'feed')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feed'
|
[styles.selected]: page().route === 'feed',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -46,7 +48,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feedMy')}
|
href={getPagePath(router, 'feedMy')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feedMy'
|
[styles.selected]: page().route === 'feedMy',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -59,7 +61,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feedCollaborations')}
|
href={getPagePath(router, 'feedCollaborations')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feedCollaborations'
|
[styles.selected]: page().route === 'feedCollaborations',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -72,7 +74,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feedDiscussions')}
|
href={getPagePath(router, 'feedDiscussions')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feedDiscussions'
|
[styles.selected]: page().route === 'feedDiscussions',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -85,7 +87,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feedBookmarks')}
|
href={getPagePath(router, 'feedBookmarks')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feedBookmarks'
|
[styles.selected]: page().route === 'feedBookmarks',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -98,7 +100,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'feedNotifications')}
|
href={getPagePath(router, 'feedNotifications')}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: page().route === 'feedNotifications'
|
[styles.selected]: page().route === 'feedNotifications',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<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 type { Author } from '../../graphql/types.gen'
|
||||||
import { hideModal } from '../../stores/ui'
|
|
||||||
|
import { createSignal, For, createEffect } from 'solid-js'
|
||||||
|
|
||||||
import { useInbox } from '../../context/inbox'
|
import { useInbox } from '../../context/inbox'
|
||||||
import { useLocalize } from '../../context/localize'
|
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 inviteUser = Author & { selected: boolean }
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -46,7 +49,7 @@ const CreateModalContent = (props: Props) => {
|
||||||
const handleClick = (user) => {
|
const handleClick = (user) => {
|
||||||
setCollectionToInvite((userCollection) => {
|
setCollectionToInvite((userCollection) => {
|
||||||
return userCollection.map((clickedUser) =>
|
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 { clsx } from 'clsx'
|
||||||
|
import { Show, createMemo } from 'solid-js'
|
||||||
|
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
|
import './DialogCard.module.scss'
|
||||||
|
|
||||||
|
import styles from './DialogAvatar.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string
|
name: string
|
||||||
|
@ -25,7 +27,7 @@ const colors = [
|
||||||
'#668cff',
|
'#668cff',
|
||||||
'#c34cfe',
|
'#c34cfe',
|
||||||
'#e699ff',
|
'#e699ff',
|
||||||
'#6633ff'
|
'#6633ff',
|
||||||
]
|
]
|
||||||
|
|
||||||
const getById = (letter: string) =>
|
const getById = (letter: string) =>
|
||||||
|
@ -42,7 +44,7 @@ const DialogAvatar = (props: Props) => {
|
||||||
class={clsx(styles.DialogAvatar, props.class, {
|
class={clsx(styles.DialogAvatar, props.class, {
|
||||||
[styles.online]: props.online,
|
[styles.online]: props.online,
|
||||||
[styles.bordered]: props.bordered,
|
[styles.bordered]: props.bordered,
|
||||||
[styles.small]: props.size === 'small'
|
[styles.small]: props.size === 'small',
|
||||||
})}
|
})}
|
||||||
style={{ 'background-color': `${randomBg()}` }}
|
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 type { ChatMember } from '../../graphql/types.gen'
|
||||||
import GroupDialogAvatar from './GroupDialogAvatar'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './DialogCard.module.scss'
|
import { Show, Switch, Match, createMemo } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
|
|
||||||
|
import DialogAvatar from './DialogAvatar'
|
||||||
|
import GroupDialogAvatar from './GroupDialogAvatar'
|
||||||
|
|
||||||
|
import styles from './DialogCard.module.scss'
|
||||||
|
|
||||||
type DialogProps = {
|
type DialogProps = {
|
||||||
online?: boolean
|
online?: boolean
|
||||||
message?: string
|
message?: string
|
||||||
|
@ -22,14 +26,14 @@ type DialogProps = {
|
||||||
const DialogCard = (props: DialogProps) => {
|
const DialogCard = (props: DialogProps) => {
|
||||||
const { t, formatTime } = useLocalize()
|
const { t, formatTime } = useLocalize()
|
||||||
const companions = createMemo(
|
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(
|
const names = createMemo(
|
||||||
() =>
|
() =>
|
||||||
companions()
|
companions()
|
||||||
?.map((companion) => companion.name)
|
?.map((companion) => companion.name)
|
||||||
.join(', ')
|
.join(', '),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -37,7 +41,7 @@ const DialogCard = (props: DialogProps) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.DialogCard, {
|
class={clsx(styles.DialogCard, {
|
||||||
[styles.opened]: props.isOpened,
|
[styles.opened]: props.isOpened,
|
||||||
[styles.hovered]: !props.isChatHeader
|
[styles.hovered]: !props.isChatHeader,
|
||||||
})}
|
})}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import type { Chat } from '../../graphql/types.gen'
|
import type { Chat } from '../../graphql/types.gen'
|
||||||
import styles from './DialogHeader.module.scss'
|
|
||||||
import DialogCard from './DialogCard'
|
import DialogCard from './DialogCard'
|
||||||
|
|
||||||
|
import styles from './DialogHeader.module.scss'
|
||||||
|
|
||||||
type DialogHeader = {
|
type DialogHeader = {
|
||||||
chat: Chat
|
chat: Chat
|
||||||
ownId: number
|
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 type { ChatMember } from '../../graphql/types.gen'
|
||||||
|
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { For } from 'solid-js'
|
||||||
|
|
||||||
import DialogAvatar from './DialogAvatar'
|
import DialogAvatar from './DialogAvatar'
|
||||||
|
|
||||||
|
import './DialogCard.module.scss'
|
||||||
|
|
||||||
|
import styles from './GroupDialogAvatar.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
users: ChatMember[]
|
users: ChatMember[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import styles from './InviteUser.module.scss'
|
|
||||||
import DialogAvatar from './DialogAvatar'
|
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import DialogAvatar from './DialogAvatar'
|
||||||
|
|
||||||
|
import styles from './InviteUser.module.scss'
|
||||||
|
|
||||||
type DialogProps = {
|
type DialogProps = {
|
||||||
author: Author
|
author: Author
|
||||||
selected: boolean
|
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 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 { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import DialogAvatar from './DialogAvatar'
|
||||||
import { MessageActionsPopup } from './MessageActionsPopup'
|
import { MessageActionsPopup } from './MessageActionsPopup'
|
||||||
import QuotedMessage from './QuotedMessage'
|
import QuotedMessage from './QuotedMessage'
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
|
import styles from './Message.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content: MessageType
|
content: MessageType
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { createEffect, createSignal, For } from 'solid-js'
|
|
||||||
import type { PopupProps } from '../_shared/Popup'
|
import type { PopupProps } from '../_shared/Popup'
|
||||||
import { Popup } from '../_shared/Popup'
|
|
||||||
|
import { createEffect, createSignal, For } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { Popup } from '../_shared/Popup'
|
||||||
|
|
||||||
export type MessageActionType = 'reply' | 'copy' | 'pin' | 'forward' | 'select' | 'delete'
|
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('Pin'), action: 'pin' },
|
||||||
{ name: t('Forward'), action: 'forward' },
|
{ name: t('Forward'), action: 'forward' },
|
||||||
{ name: t('Select'), action: 'select' },
|
{ name: t('Select'), action: 'select' },
|
||||||
{ name: t('Delete'), action: 'delete' }
|
{ name: t('Delete'), action: 'delete' },
|
||||||
]
|
]
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.actionSelect) props.actionSelect(selectedAction())
|
if (props.actionSelect) props.actionSelect(selectedAction())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
|
|
||||||
import styles from './MessagesFallback.module.scss'
|
import styles from './MessagesFallback.module.scss'
|
||||||
|
|
||||||
type MessagesFallback = {
|
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 { clsx } from 'clsx'
|
||||||
|
import { Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './QuotedMessage.module.scss'
|
||||||
|
|
||||||
type QuotedMessage = {
|
type QuotedMessage = {
|
||||||
body: string
|
body: string
|
||||||
|
@ -17,7 +19,7 @@ const QuotedMessage = (props: QuotedMessage) => {
|
||||||
class={clsx(styles.QuotedMessage, {
|
class={clsx(styles.QuotedMessage, {
|
||||||
[styles.reply]: props.variant === 'reply',
|
[styles.reply]: props.variant === 'reply',
|
||||||
[styles.inline]: props.variant === 'inline',
|
[styles.inline]: props.variant === 'inline',
|
||||||
[styles.own]: props.isOwn
|
[styles.own]: props.isOwn,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={props.variant === 'reply'}>
|
<Show when={props.variant === 'reply'}>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import styles from './Search.module.scss'
|
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import styles from './Search.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
onChange: (value: () => string) => void
|
onChange: (value: () => string) => void
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import styles from './AuthModalHeader.module.scss'
|
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../../context/localize'
|
import { useLocalize } from '../../../../context/localize'
|
||||||
import { useRouter } from '../../../../stores/router'
|
import { useRouter } from '../../../../stores/router'
|
||||||
import { AuthModalSearchParams } from '../types'
|
import { AuthModalSearchParams } from '../types'
|
||||||
|
|
||||||
|
import styles from './AuthModalHeader.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalType: 'login' | 'register'
|
modalType: 'login' | 'register'
|
||||||
}
|
}
|
||||||
|
@ -14,7 +16,7 @@ export const AuthModalHeader = (props: Props) => {
|
||||||
const { source } = searchParams()
|
const { source } = searchParams()
|
||||||
|
|
||||||
const generateModalTextsFromSource = (
|
const generateModalTextsFromSource = (
|
||||||
modalType: 'login' | 'register'
|
modalType: 'login' | 'register',
|
||||||
): { title: string; description: string } => {
|
): { title: string; description: string } => {
|
||||||
const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account'
|
const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account'
|
||||||
|
|
||||||
|
@ -22,53 +24,53 @@ export const AuthModalHeader = (props: Props) => {
|
||||||
case 'create': {
|
case 'create': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to publish articles`),
|
title: t(`${title} to publish articles`),
|
||||||
description: ''
|
description: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'bookmark': {
|
case 'bookmark': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to add to your bookmarks`),
|
title: t(`${title} to add to your bookmarks`),
|
||||||
description: t(
|
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': {
|
case 'discussions': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to participate in discussions`),
|
title: t(`${title} to participate in discussions`),
|
||||||
description: t(
|
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': {
|
case 'follow': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to subscribe`),
|
title: t(`${title} to subscribe`),
|
||||||
description: t(
|
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': {
|
case 'subscribe': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to subscribe to new publications`),
|
title: t(`${title} to subscribe to new publications`),
|
||||||
description: t(
|
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': {
|
case 'vote': {
|
||||||
return {
|
return {
|
||||||
title: t(`${title} to vote`),
|
title: t(`${title} to vote`),
|
||||||
description: t(
|
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: {
|
default: {
|
||||||
return {
|
return {
|
||||||
title: t(title),
|
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 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 { 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 = () => {
|
export const EmailConfirm = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
session,
|
session,
|
||||||
actions: { confirmEmail }
|
actions: { confirmEmail },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
|
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 { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
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 { 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 { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
||||||
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
|
||||||
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
|
@ -83,7 +87,7 @@ export const ForgotPasswordForm = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email
|
'pretty-form__item--error': validationErrors().email,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -116,7 +120,7 @@ export const ForgotPasswordForm = () => {
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
mode: 'register'
|
mode: 'register',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -138,7 +142,7 @@ export const ForgotPasswordForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParam({
|
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 type { AuthModalSearchParams } from './types'
|
||||||
import { hideModal } from '../../../stores/ui'
|
|
||||||
import { useSession } from '../../../context/session'
|
import { clsx } from 'clsx'
|
||||||
import { signSendLink } from '../../../stores/auth'
|
import { createSignal, Show } from 'solid-js'
|
||||||
import { validateEmail } from '../../../utils/validateEmail'
|
|
||||||
import { useSnackbar } from '../../../context/snackbar'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
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 { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
import { AuthModalHeader } from './AuthModalHeader'
|
import { AuthModalHeader } from './AuthModalHeader'
|
||||||
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
import { SocialProviders } from './SocialProviders'
|
||||||
|
|
||||||
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
email: string
|
email: string
|
||||||
|
@ -36,11 +40,11 @@ export const LoginForm = () => {
|
||||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar }
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { signIn }
|
actions: { signIn },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
const { changeSearchParam } = useRouter<AuthModalSearchParams>()
|
||||||
|
@ -144,7 +148,7 @@ export const LoginForm = () => {
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email
|
'pretty-form__item--error': validationErrors().email,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -164,7 +168,7 @@ export const LoginForm = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().password
|
'pretty-form__item--error': validationErrors().password,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -198,7 +202,7 @@ export const LoginForm = () => {
|
||||||
class="link"
|
class="link"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
mode: 'forgot-password'
|
mode: 'forgot-password',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -215,7 +219,7 @@ export const LoginForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParam({
|
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 type { AuthModalSearchParams } from './types'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import type { JSX } from 'solid-js'
|
||||||
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
|
|
||||||
import { register } from '../../../stores/auth'
|
import { clsx } from 'clsx'
|
||||||
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
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 { validateEmail } from '../../../utils/validateEmail'
|
||||||
import { AuthModalHeader } from './AuthModalHeader'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
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 = {
|
type FormFields = {
|
||||||
fullName: string
|
fullName: string
|
||||||
email: string
|
email: string
|
||||||
|
@ -126,7 +130,7 @@ export const RegisterForm = () => {
|
||||||
await register({
|
await register({
|
||||||
name: cleanName,
|
name: cleanName,
|
||||||
email: cleanEmail,
|
email: cleanEmail,
|
||||||
password: password()
|
password: password(),
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsSuccess(true)
|
setIsSuccess(true)
|
||||||
|
@ -156,7 +160,7 @@ export const RegisterForm = () => {
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().fullName
|
'pretty-form__item--error': validationErrors().fullName,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -174,7 +178,7 @@ export const RegisterForm = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email
|
'pretty-form__item--error': validationErrors().email,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -199,7 +203,7 @@ export const RegisterForm = () => {
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
changeSearchParam({
|
changeSearchParam({
|
||||||
mode: 'login'
|
mode: 'login',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -211,7 +215,7 @@ export const RegisterForm = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().password
|
'pretty-form__item--error': validationErrors().password,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -252,7 +256,7 @@ export const RegisterForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParam({
|
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 { hideModal } from '../../../stores/ui'
|
||||||
|
import { apiBaseUrl } from '../../../utils/config'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
import styles from './SocialProviders.module.scss'
|
import styles from './SocialProviders.module.scss'
|
||||||
import { apiBaseUrl } from '../../../utils/config'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
|
||||||
|
|
||||||
type Provider = 'facebook' | 'google' | 'vk' | 'github'
|
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 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 { 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> = {
|
const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
|
||||||
login: LoginForm,
|
login: LoginForm,
|
||||||
register: RegisterForm,
|
register: RegisterForm,
|
||||||
'forgot-password': ForgotPasswordForm,
|
'forgot-password': ForgotPasswordForm,
|
||||||
'confirm-email': EmailConfirm
|
'confirm-email': EmailConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthModal = () => {
|
export const AuthModal = () => {
|
||||||
|
@ -40,7 +44,7 @@ export const AuthModal = () => {
|
||||||
<div
|
<div
|
||||||
ref={rootRef}
|
ref={rootRef}
|
||||||
class={clsx(styles.view, {
|
class={clsx(styles.view, {
|
||||||
row: !source
|
row: !source,
|
||||||
})}
|
})}
|
||||||
classList={{ [styles.signUp]: mode() === 'register' || mode() === 'confirm-email' }}
|
classList={{ [styles.signUp]: mode() === 'register' || mode() === 'confirm-email' }}
|
||||||
>
|
>
|
||||||
|
@ -54,7 +58,7 @@ export const AuthModal = () => {
|
||||||
<h4>{t(`Join the global community of authors!`)}</h4>
|
<h4>{t(`Join the global community of authors!`)}</h4>
|
||||||
<p class={styles.authBenefits}>
|
<p class={styles.authBenefits}>
|
||||||
{t(
|
{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!')}
|
{t('New stories every day and even more!')}
|
||||||
|
@ -77,7 +81,7 @@ export const AuthModal = () => {
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.auth, {
|
class={clsx(styles.auth, {
|
||||||
'col-md-12': !source
|
'col-md-12': !source,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Dynamic component={AUTH_MODAL_MODES[mode()]} />
|
<Dynamic component={AUTH_MODAL_MODES[mode()]} />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useConfirm } from '../../../context/confirm'
|
import { useConfirm } from '../../../context/confirm'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
|
|
||||||
import styles from './ConfirmModal.module.scss'
|
import styles from './ConfirmModal.module.scss'
|
||||||
|
|
||||||
export const ConfirmModal = () => {
|
export const ConfirmModal = () => {
|
||||||
|
@ -8,7 +9,7 @@ export const ConfirmModal = () => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
confirmMessage,
|
confirmMessage,
|
||||||
actions: { resolveConfirm }
|
actions: { resolveConfirm },
|
||||||
} = useConfirm()
|
} = useConfirm()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import './Confirmed.scss'
|
import './Confirmed.scss'
|
||||||
import { onMount } from 'solid-js'
|
import { onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
|
||||||
export const Confirmed = (props: { token?: string }) => {
|
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 type { Topic } from '../../../graphql/types.gen'
|
||||||
|
|
||||||
import { useModalStore } from '../../../stores/ui'
|
import { getPagePath, redirectPage } from '@nanostores/router'
|
||||||
import { router, ROUTES, useRouter } from '../../../stores/router'
|
import { clsx } from 'clsx'
|
||||||
|
import { Show, createSignal, createEffect, onMount, onCleanup, For } from 'solid-js'
|
||||||
import { getDescription } from '../../../utils/meta'
|
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
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 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 = {
|
type Props = {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -47,7 +46,7 @@ export const Header = (props: Props) => {
|
||||||
const { modal } = useModalStore()
|
const { modal } = useModalStore()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const {
|
const {
|
||||||
actions: { requireAuthentication }
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { searchParams } = useRouter<HeaderSearchParams>()
|
const { searchParams } = useRouter<HeaderSearchParams>()
|
||||||
|
@ -167,7 +166,7 @@ export const Header = (props: Props) => {
|
||||||
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
|
[styles.headerScrolledTop]: !getIsScrollingBottom() && getIsScrolled(),
|
||||||
[styles.headerScrolledBottom]:
|
[styles.headerScrolledBottom]:
|
||||||
(getIsScrollingBottom() && getIsScrolled() && !isProfilePopupVisible()) || isSharePopupVisible(),
|
(getIsScrollingBottom() && getIsScrolled() && !isProfilePopupVisible()) || isSharePopupVisible(),
|
||||||
[styles.headerWithTitle]: Boolean(props.title)
|
[styles.headerWithTitle]: Boolean(props.title),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -318,7 +317,7 @@ export const Header = (props: Props) => {
|
||||||
<p
|
<p
|
||||||
class={styles.mobileDescription}
|
class={styles.mobileDescription}
|
||||||
innerHTML={t(
|
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}>
|
<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 { 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 = {
|
type Props = {
|
||||||
onMouseOver: (event?: MouseEvent, time?: number) => void
|
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 { 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 { useEditorContext } from '../../context/editor'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useNotifications } from '../../context/notifications'
|
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 = {
|
type Props = {
|
||||||
setIsProfilePopupVisible: (value: boolean) => void
|
setIsProfilePopupVisible: (value: boolean) => void
|
||||||
|
@ -32,12 +35,12 @@ export const HeaderAuth = (props: Props) => {
|
||||||
const { session, isSessionLoaded, isAuthenticated } = useSession()
|
const { session, isSessionLoaded, isAuthenticated } = useSession()
|
||||||
const {
|
const {
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
actions: { showNotificationsPanel }
|
actions: { showNotificationsPanel },
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
form,
|
form,
|
||||||
actions: { toggleEditorPanel, saveShout, publishShout }
|
actions: { toggleEditorPanel, saveShout, publishShout },
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
const handleBellIconClick = (event: Event) => {
|
const handleBellIconClick = (event: Event) => {
|
||||||
|
@ -158,7 +161,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
{renderIconedButton({
|
{renderIconedButton({
|
||||||
value: t('Save'),
|
value: t('Save'),
|
||||||
icon: 'save',
|
icon: 'save',
|
||||||
action: handleSaveButtonClick
|
action: handleSaveButtonClick,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -166,7 +169,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
{renderIconedButton({
|
{renderIconedButton({
|
||||||
value: t('Publish'),
|
value: t('Publish'),
|
||||||
icon: 'publish',
|
icon: 'publish',
|
||||||
action: handlePublishButtonClick
|
action: handlePublishButtonClick,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
|
|
||||||
|
import { redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { router } from '../../../stores/router'
|
||||||
import { hideModal, useModalStore } from '../../../stores/ui'
|
import { hideModal, useModalStore } from '../../../stores/ui'
|
||||||
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
import styles from './Modal.module.scss'
|
import styles from './Modal.module.scss'
|
||||||
import { redirectPage } from '@nanostores/router'
|
|
||||||
import { router } from '../../../stores/router'
|
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string
|
name: string
|
||||||
|
@ -50,7 +52,7 @@ export const Modal = (props: Props) => {
|
||||||
[styles.narrow]: props.variant === 'narrow',
|
[styles.narrow]: props.variant === 'narrow',
|
||||||
['col-auto col-md-20 offset-md-2 col-lg-14 offset-lg-5']: props.variant === 'medium',
|
['col-auto col-md-20 offset-md-2 col-lg-14 offset-lg-5']: props.variant === 'medium',
|
||||||
[styles.noPadding]: props.noPadding,
|
[styles.noPadding]: props.noPadding,
|
||||||
[styles.maxHeight]: props.maxHeight
|
[styles.maxHeight]: props.maxHeight,
|
||||||
})}
|
})}
|
||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
|
||||||
import type { ModalType } from '../../../stores/ui'
|
import type { ModalType } from '../../../stores/ui'
|
||||||
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
|
||||||
import { showModal } from '../../../stores/ui'
|
import { showModal } from '../../../stores/ui'
|
||||||
|
|
||||||
export default (props: { name: ModalType; children: JSX.Element }) => {
|
export default (props: { name: ModalType; children: JSX.Element }) => {
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
import type { PopupProps } from '../_shared/Popup'
|
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 { getPagePath } from '@nanostores/router'
|
||||||
import { router } from '../../stores/router'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
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'>
|
type ProfilePopupProps = Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
export const ProfilePopup = (props: ProfilePopupProps) => {
|
export const ProfilePopup = (props: ProfilePopupProps) => {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
actions: { signOut }
|
actions: { signOut },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import styles from './ProfileSettingsNavigation.module.scss'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
|
|
||||||
|
import styles from './ProfileSettingsNavigation.module.scss'
|
||||||
|
|
||||||
export const ProfileSettingsNavigation = () => {
|
export const ProfileSettingsNavigation = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { page } = useRouter()
|
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 { 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 { Icon } from '../_shared/Icon'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
|
|
||||||
|
import styles from './Snackbar.module.scss'
|
||||||
|
|
||||||
export const Snackbar = () => {
|
export const Snackbar = () => {
|
||||||
const { snackbarMessage } = useSnackbar()
|
const { snackbarMessage } = useSnackbar()
|
||||||
|
|
||||||
|
@ -13,7 +15,7 @@ export const Snackbar = () => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.snackbar, {
|
class={clsx(styles.snackbar, {
|
||||||
[styles.error]: snackbarMessage()?.type === 'error',
|
[styles.error]: snackbarMessage()?.type === 'error',
|
||||||
[styles.success]: snackbarMessage()?.type === 'success'
|
[styles.success]: snackbarMessage()?.type === 'success',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ShowOnlyOnClient>
|
<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 { 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 = () => {
|
export const Topics = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './EmptyMessage.module.scss'
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
|
||||||
|
import styles from './EmptyMessage.module.scss'
|
||||||
|
|
||||||
export const EmptyMessage = () => {
|
export const EmptyMessage = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import type { Notification } from '../../../graphql/types.gen'
|
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 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 { GroupAvatar } from '../../_shared/GroupAvatar'
|
||||||
|
import { TimeAgo } from '../../_shared/TimeAgo'
|
||||||
|
|
||||||
|
import styles from './NotificationView.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: Notification
|
notification: Notification
|
||||||
|
@ -36,7 +39,7 @@ type NotificationData = {
|
||||||
|
|
||||||
export const NotificationView = (props: Props) => {
|
export const NotificationView = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
actions: { markNotificationAsRead, hideNotificationsPanel }
|
actions: { markNotificationAsRead, hideNotificationsPanel },
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
|
|
||||||
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||||
|
@ -89,7 +92,7 @@ export const NotificationView = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{t('NotificationNewCommentText1', {
|
{t('NotificationNewCommentText1', {
|
||||||
commentsCount: props.notification.occurrences
|
commentsCount: props.notification.occurrences,
|
||||||
})}{' '}
|
})}{' '}
|
||||||
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
|
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
|
||||||
{shoutTitle}
|
{shoutTitle}
|
||||||
|
@ -99,7 +102,7 @@ export const NotificationView = (props: Props) => {
|
||||||
{lastUser().name}
|
{lastUser().name}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
{t('NotificationNewCommentText3', {
|
{t('NotificationNewCommentText3', {
|
||||||
restUsersCount: data().users.length - 1
|
restUsersCount: data().users.length - 1,
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -108,7 +111,7 @@ export const NotificationView = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{t('NotificationNewReplyText1', {
|
{t('NotificationNewReplyText1', {
|
||||||
commentsCount: props.notification.occurrences
|
commentsCount: props.notification.occurrences,
|
||||||
})}{' '}
|
})}{' '}
|
||||||
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
|
<a href={getPagePath(router, 'article', { slug: data().shout.slug })} onClick={handleLinkClick}>
|
||||||
{shoutTitle}
|
{shoutTitle}
|
||||||
|
@ -118,7 +121,7 @@ export const NotificationView = (props: Props) => {
|
||||||
{lastUser().name}
|
{lastUser().name}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
{t('NotificationNewReplyText3', {
|
{t('NotificationNewReplyText3', {
|
||||||
restUsersCount: data().users.length - 1
|
restUsersCount: data().users.length - 1,
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -158,7 +161,7 @@ export const NotificationView = (props: Props) => {
|
||||||
<Show when={data()}>
|
<Show when={data()}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.NotificationView, props.class, {
|
class={clsx(styles.NotificationView, props.class, {
|
||||||
[styles.seen]: props.notification.seen
|
[styles.seen]: props.notification.seen,
|
||||||
})}
|
})}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { clsx } from 'clsx'
|
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 { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler'
|
||||||
import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler'
|
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 { Button } from '../_shared/Button'
|
||||||
import { throttle } from 'throttle-debounce'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
|
import { EmptyMessage } from './EmptyMessage'
|
||||||
|
import { NotificationView } from './NotificationView'
|
||||||
|
|
||||||
|
import styles from './NotificationsPanel.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
@ -51,20 +54,20 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
loadedNotificationsCount,
|
loadedNotificationsCount,
|
||||||
totalNotificationsCount,
|
totalNotificationsCount,
|
||||||
actions: { loadNotifications, markAllNotificationsAsRead }
|
actions: { loadNotifications, markAllNotificationsAsRead },
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
const handleHide = () => {
|
const handleHide = () => {
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
const panelRef: { current: HTMLDivElement } = {
|
const panelRef: { current: HTMLDivElement } = {
|
||||||
current: null
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef: panelRef,
|
containerRef: panelRef,
|
||||||
predicate: () => props.isOpen,
|
predicate: () => props.isOpen,
|
||||||
handler: () => handleHide()
|
handler: () => handleHide(),
|
||||||
})
|
})
|
||||||
|
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
@ -150,14 +153,14 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
await loadNextPage()
|
await loadNextPage()
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.container, {
|
class={clsx(styles.container, {
|
||||||
[styles.isOpened]: props.isOpen
|
[styles.isOpened]: props.isOpen,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div ref={(el) => (panelRef.current = el)} class={styles.panel}>
|
<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 { clsx } from 'clsx'
|
||||||
import { DEFAULT_HEADER_OFFSET } from '../../stores/router'
|
import { For, Show, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js'
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import styles from './TableOfContents.module.scss'
|
|
||||||
import { isDesktop } from '../../utils/media-query'
|
|
||||||
import { throttle, debounce } from 'throttle-debounce'
|
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 {
|
interface Props {
|
||||||
variant: 'article' | 'editor'
|
variant: 'article' | 'editor'
|
||||||
parentSelector: string
|
parentSelector: string
|
||||||
|
@ -23,7 +25,7 @@ const scrollToHeader = (element) => {
|
||||||
top:
|
top:
|
||||||
element.getBoundingClientRect().top -
|
element.getBoundingClientRect().top -
|
||||||
document.body.getBoundingClientRect().top -
|
document.body.getBoundingClientRect().top -
|
||||||
DEFAULT_HEADER_OFFSET
|
DEFAULT_HEADER_OFFSET,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ export const TableOfContents = (props: Props) => {
|
||||||
const updateHeadings = () => {
|
const updateHeadings = () => {
|
||||||
setHeadings(
|
setHeadings(
|
||||||
// eslint-disable-next-line unicorn/prefer-spread
|
// 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)
|
setAreHeadingsLoaded(true)
|
||||||
}
|
}
|
||||||
|
@ -58,8 +60,8 @@ export const TableOfContents = (props: Props) => {
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.body,
|
() => props.body,
|
||||||
() => debouncedUpdateHeadings()
|
() => debouncedUpdateHeadings(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -75,7 +77,7 @@ export const TableOfContents = (props: Props) => {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.TableOfContentsFixedWrapper, {
|
class={clsx(styles.TableOfContentsFixedWrapper, {
|
||||||
[styles.TableOfContentsFixedWrapperLefted]: props.variant === 'editor'
|
[styles.TableOfContentsFixedWrapperLefted]: props.variant === 'editor',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div class={styles.TableOfContentsContainer}>
|
<div class={styles.TableOfContentsContainer}>
|
||||||
|
@ -92,7 +94,7 @@ export const TableOfContents = (props: Props) => {
|
||||||
class={clsx(styles.TableOfContentsHeadingsItem, {
|
class={clsx(styles.TableOfContentsHeadingsItem, {
|
||||||
[styles.TableOfContentsHeadingsItemH3]: h.nodeName === 'H3',
|
[styles.TableOfContentsHeadingsItemH3]: h.nodeName === 'H3',
|
||||||
[styles.TableOfContentsHeadingsItemH4]: h.nodeName === 'H4',
|
[styles.TableOfContentsHeadingsItemH4]: h.nodeName === 'H4',
|
||||||
[styles.active]: index() === activeHeaderIndex()
|
[styles.active]: index() === activeHeaderIndex(),
|
||||||
})}
|
})}
|
||||||
innerHTML={h.textContent}
|
innerHTML={h.textContent}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
@ -111,9 +113,9 @@ export const TableOfContents = (props: Props) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.TableOfContentsPrimaryButton,
|
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) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -149,9 +151,9 @@ export const TableOfContents = (props: Props) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.TableOfContentsPrimaryButton,
|
styles.TableOfContentsPrimaryButton,
|
||||||
{
|
{
|
||||||
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible()
|
[styles.TableOfContentsPrimaryButtonLefted]: props.variant === 'editor' && !isVisible(),
|
||||||
},
|
},
|
||||||
'd-xl-none'
|
'd-xl-none',
|
||||||
)}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import { createMemo, createSignal, Show } from 'solid-js'
|
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
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 { clsx } from 'clsx'
|
||||||
import { useSession } from '../../context/session'
|
import { createMemo, createSignal, Show } from 'solid-js'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
import { useSession } from '../../context/session'
|
||||||
import { CheckButton } from '../_shared/CheckButton'
|
import { FollowingEntity } from '../../graphql/types.gen'
|
||||||
|
import { follow, unfollow } from '../../stores/zine/common'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
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 styles from './Card.module.scss'
|
||||||
import { Button } from '../_shared/Button'
|
|
||||||
import stylesButton from '../_shared/Button/Button.module.scss'
|
import stylesButton from '../_shared/Button/Button.module.scss'
|
||||||
|
|
||||||
interface TopicProps {
|
interface TopicProps {
|
||||||
|
@ -38,7 +39,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
const {
|
const {
|
||||||
subscriptions,
|
subscriptions,
|
||||||
isSessionLoaded,
|
isSessionLoaded,
|
||||||
actions: { loadSubscriptions, requireAuthentication }
|
actions: { loadSubscriptions, requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
const [isSubscribing, setIsSubscribing] = createSignal(false)
|
||||||
|
@ -89,7 +90,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
classList={{
|
classList={{
|
||||||
row: !props.subscribeButtonBottom,
|
row: !props.subscribeButtonBottom,
|
||||||
[styles.topicCompact]: props.compact,
|
[styles.topicCompact]: props.compact,
|
||||||
[styles.topicInRow]: props.isTopicInRow
|
[styles.topicInRow]: props.isTopicInRow,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<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-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-24 col-sm-17 col-md-18', styles.topicDetails)]: props.compact,
|
||||||
[clsx('col-sm-17 col-md-18', styles.topicDetails)]:
|
[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}>
|
<Show when={props.topic.title && !props.isCardMode}>
|
||||||
|
@ -132,7 +133,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
classList={{
|
classList={{
|
||||||
'col-sm-6 col-md-24 col-lg-10 col-xl-9': props.isNarrow,
|
'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-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>
|
<ShowOnlyOnClient>
|
||||||
|
@ -151,7 +152,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx(styles.actionButton, {
|
class={clsx(styles.actionButton, {
|
||||||
[styles.isSubscribing]: isSubscribing(),
|
[styles.isSubscribing]: isSubscribing(),
|
||||||
[stylesButton.subscribed]: subscribed()
|
[stylesButton.subscribed]: subscribed(),
|
||||||
})}
|
})}
|
||||||
disabled={isSubscribing()}
|
disabled={isSubscribing()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Topic } from '../../graphql/types.gen'
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import './FloorHeader.scss'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
|
import './FloorHeader.scss'
|
||||||
|
|
||||||
export default (props: { topic: Topic; color: string }) => {
|
export default (props: { topic: Topic; color: string }) => {
|
||||||
const { t } = useLocalize()
|
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