ga-integration
This commit is contained in:
parent
a5eaeab5cd
commit
e38fce68c6
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"*.{js,ts,cjs,mjs,d.mts,jsx,tsx,json,jsonc}": [
|
|
||||||
"npx @biomejs/biome check ./src && tsc"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -13,7 +13,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 {
|
||||||
|
|
|
@ -11,18 +11,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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ export default defineConfig({
|
||||||
ssr: true,
|
ssr: true,
|
||||||
server: {
|
server: {
|
||||||
preset: isVercel ? 'vercel_edge' : isBun ? 'bun' : 'node',
|
preset: isVercel ? 'vercel_edge' : isBun ? 'bun' : 'node',
|
||||||
port: 3000,
|
port: 3000
|
||||||
},
|
},
|
||||||
devOverlay: true,
|
devOverlay: true,
|
||||||
build: {
|
build: {
|
||||||
chunkSizeWarningLimit: 1024,
|
chunkSizeWarningLimit: 1024,
|
||||||
target: 'esnext',
|
target: 'esnext'
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
envPrefix: 'PUBLIC_',
|
envPrefix: 'PUBLIC_',
|
||||||
|
@ -22,12 +22,12 @@ export default defineConfig({
|
||||||
include: ['path', 'stream', 'util'],
|
include: ['path', 'stream', 'util'],
|
||||||
exclude: ['http'],
|
exclude: ['http'],
|
||||||
globals: {
|
globals: {
|
||||||
Buffer: true,
|
Buffer: true
|
||||||
},
|
},
|
||||||
overrides: {
|
overrides: {
|
||||||
fs: 'memfs',
|
fs: 'memfs'
|
||||||
},
|
},
|
||||||
protocolImports: true,
|
protocolImports: true
|
||||||
}),
|
}),
|
||||||
sassDts()
|
sassDts()
|
||||||
],
|
],
|
||||||
|
@ -35,13 +35,13 @@ export default defineConfig({
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
scss: {
|
scss: {
|
||||||
additionalData: '@import "src/styles/imports";\n',
|
additionalData: '@import "src/styles/imports";\n',
|
||||||
includePaths: ['./public', './src/styles'],
|
includePaths: ['./public', './src/styles']
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
chunkSizeWarningLimit: 1024,
|
chunkSizeWarningLimit: 1024,
|
||||||
target: 'esnext',
|
target: 'esnext'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as SolidStartInlineConfig)
|
} as SolidStartInlineConfig)
|
||||||
|
|
41
biome.json
41
biome.json
|
@ -1,21 +1,8 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json",
|
"$schema": "https://biomejs.dev/schemas/1.8.2/schema.json",
|
||||||
"files": {
|
"files": {
|
||||||
"include": [
|
"include": ["*.tsx", "*.ts", "*.js", "*.json"],
|
||||||
"*.tsx",
|
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.gen.ts", "*.d.ts"]
|
||||||
"*.ts",
|
|
||||||
"*.js",
|
|
||||||
"*.json"
|
|
||||||
],
|
|
||||||
"ignore": [
|
|
||||||
"./dist",
|
|
||||||
"./node_modules",
|
|
||||||
".husky",
|
|
||||||
"docs",
|
|
||||||
"gen",
|
|
||||||
"*.gen.ts",
|
|
||||||
"*.d.ts"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"defaultBranch": "dev",
|
"defaultBranch": "dev",
|
||||||
|
@ -23,19 +10,13 @@
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"ignore": [
|
"ignore": ["./api", "./gen"]
|
||||||
"./api",
|
|
||||||
"./gen"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"indentStyle": "space",
|
"indentStyle": "space",
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
"lineWidth": 108,
|
"lineWidth": 108,
|
||||||
"ignore": [
|
"ignore": ["./src/graphql/schema", "./gen"]
|
||||||
"./src/graphql/schema",
|
|
||||||
"./gen"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
|
@ -43,17 +24,12 @@
|
||||||
"quoteStyle": "single",
|
"quoteStyle": "single",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"jsxQuoteStyle": "double",
|
"jsxQuoteStyle": "double",
|
||||||
"arrowParentheses": "always"
|
"arrowParentheses": "always",
|
||||||
|
"trailingCommas": "none"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"ignore": [
|
"ignore": ["*.scss", "*.md", ".DS_Store", "*.svg", "*.d.ts"],
|
||||||
"*.scss",
|
|
||||||
"*.md",
|
|
||||||
".DS_Store",
|
|
||||||
"*.svg",
|
|
||||||
"*.d.ts"
|
|
||||||
],
|
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
"all": true,
|
"all": true,
|
||||||
|
@ -85,6 +61,7 @@
|
||||||
"noBarrelFile": "off"
|
"noBarrelFile": "off"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
|
"noNamespaceImport": "warn",
|
||||||
"useBlockStatements": "off",
|
"useBlockStatements": "off",
|
||||||
"noImplicitBoolean": "off",
|
"noImplicitBoolean": "off",
|
||||||
"useNamingConvention": "off",
|
"useNamingConvention": "off",
|
||||||
|
@ -98,4 +75,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"@solid-primitives/media": "^2.2.9",
|
"@solid-primitives/media": "^2.2.9",
|
||||||
"@solid-primitives/memo": "^1.3.8",
|
"@solid-primitives/memo": "^1.3.8",
|
||||||
"@solid-primitives/pagination": "^0.3.0",
|
"@solid-primitives/pagination": "^0.3.0",
|
||||||
|
"@solid-primitives/script-loader": "^2.2.0",
|
||||||
"@solid-primitives/share": "^2.0.6",
|
"@solid-primitives/share": "^2.0.6",
|
||||||
"@solid-primitives/storage": "^3.7.1",
|
"@solid-primitives/storage": "^3.7.1",
|
||||||
"@solid-primitives/upload": "^0.0.117",
|
"@solid-primitives/upload": "^0.0.117",
|
||||||
|
@ -112,11 +113,7 @@
|
||||||
"yjs": "13.6.18",
|
"yjs": "13.6.18",
|
||||||
"y-prosemirror": "1.2.9"
|
"y-prosemirror": "1.2.9"
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": ["@biomejs/biome", "esbuild", "protobufjs"],
|
||||||
"@biomejs/biome",
|
|
||||||
"esbuild",
|
|
||||||
"protobufjs"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"idb": "^8.0.0",
|
"idb": "^8.0.0",
|
||||||
|
@ -125,4 +122,4 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20.x"
|
"node": "20.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,25 +27,25 @@ export default defineConfig({
|
||||||
// baseURL: 'http://127.0.0.1:3000',
|
// baseURL: 'http://127.0.0.1:3000',
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry'
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices['Desktop Chrome'] }
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'firefox',
|
name: 'firefox',
|
||||||
use: { ...devices['Desktop Firefox'] },
|
use: { ...devices['Desktop Firefox'] }
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'webkit',
|
name: 'webkit',
|
||||||
use: { ...devices['Desktop Safari'] },
|
use: { ...devices['Desktop Safari'] }
|
||||||
},
|
}
|
||||||
|
|
||||||
/* Test against many viewports.
|
/* Test against many viewports.
|
||||||
// {
|
// {
|
||||||
|
@ -66,7 +66,7 @@ export default defineConfig({
|
||||||
// name: 'Google Chrome',
|
// name: 'Google Chrome',
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
// },
|
// },
|
||||||
],
|
]
|
||||||
|
|
||||||
/* Run local dev server before starting the tests */
|
/* Run local dev server before starting the tests */
|
||||||
//webServer: {
|
//webServer: {
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Props = {
|
||||||
onMediaItemFieldChange?: (
|
onMediaItemFieldChange?: (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MediaItem | string | number | symbol,
|
field: keyof MediaItem | string | number | symbol,
|
||||||
value: string,
|
value: string
|
||||||
) => void
|
) => void
|
||||||
onChangeMediaIndex?: (direction: 'up' | 'down', index: number) => void
|
onChangeMediaIndex?: (direction: 'up' | 'down', index: number) => void
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ export const AudioPlayer = (props: Props) => {
|
||||||
const handleMediaItemFieldChange = (
|
const handleMediaItemFieldChange = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MediaItem | string | number | symbol,
|
field: keyof MediaItem | string | number | symbol,
|
||||||
value: string,
|
value: string
|
||||||
) => {
|
) => {
|
||||||
props.onMediaItemFieldChange?.(index, field, value)
|
props.onMediaItemFieldChange?.(index, field, value)
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,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>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const PlayerHeader = (props: Props) => {
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef: volumeContainerRef,
|
containerRef: volumeContainerRef,
|
||||||
predicate: () => isVolumeBarOpened(),
|
predicate: () => isVolumeBarOpened(),
|
||||||
handler: () => toggleVolumeBar(),
|
handler: () => toggleVolumeBar()
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -38,7 +38,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"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
MutationCreate_ReactionArgs,
|
MutationCreate_ReactionArgs,
|
||||||
MutationUpdate_ReactionArgs,
|
MutationUpdate_ReactionArgs,
|
||||||
Reaction,
|
Reaction,
|
||||||
ReactionKind,
|
ReactionKind
|
||||||
} from '../../../graphql/schema/core.gen'
|
} from '../../../graphql/schema/core.gen'
|
||||||
import { AuthorLink } from '../../Author/AuthorLink'
|
import { AuthorLink } from '../../Author/AuthorLink'
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
|
@ -54,7 +54,7 @@ export const Comment = (props: Props) => {
|
||||||
const canEdit = createMemo(
|
const canEdit = createMemo(
|
||||||
() =>
|
() =>
|
||||||
Boolean(author()?.id) &&
|
Boolean(author()?.id) &&
|
||||||
(props.comment?.created_by?.slug === author()?.slug || session()?.user?.roles?.includes('editor')),
|
(props.comment?.created_by?.slug === author()?.slug || session()?.user?.roles?.includes('editor'))
|
||||||
)
|
)
|
||||||
|
|
||||||
const body = createMemo(() => (editedBody() ? editedBody()?.trim() : props.comment.body?.trim() || ''))
|
const body = createMemo(() => (editedBody() ? editedBody()?.trim() : props.comment.body?.trim() || ''))
|
||||||
|
@ -66,7 +66,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) {
|
||||||
|
@ -80,7 +80,7 @@ export const Comment = (props: Props) => {
|
||||||
await showSnackbar({
|
await showSnackbar({
|
||||||
type: notificationType,
|
type: notificationType,
|
||||||
body: notificationMessage,
|
body: notificationMessage,
|
||||||
duration: 3,
|
duration: 3
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!error && props.onDelete) {
|
if (!error && props.onDelete) {
|
||||||
|
@ -102,8 +102,8 @@ export const Comment = (props: Props) => {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
reply_to: props.comment.id,
|
reply_to: props.comment.id,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.comment.shout.id,
|
shout: props.comment.shout.id
|
||||||
},
|
}
|
||||||
} as MutationCreate_ReactionArgs)
|
} as MutationCreate_ReactionArgs)
|
||||||
setClearEditor(true)
|
setClearEditor(true)
|
||||||
setIsReplyVisible(false)
|
setIsReplyVisible(false)
|
||||||
|
@ -126,8 +126,8 @@ export const Comment = (props: Props) => {
|
||||||
id: props.comment.id || 0,
|
id: props.comment.id || 0,
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.comment.shout.id,
|
shout: props.comment.shout.id
|
||||||
},
|
}
|
||||||
} as MutationUpdate_ReactionArgs)
|
} as MutationUpdate_ReactionArgs)
|
||||||
if (reaction) {
|
if (reaction) {
|
||||||
setEditedBody(value)
|
setEditedBody(value)
|
||||||
|
@ -144,7 +144,7 @@ export const Comment = (props: Props) => {
|
||||||
id={`comment_${props.comment.id}`}
|
id={`comment_${props.comment.id}`}
|
||||||
class={clsx(styles.comment, props.class, {
|
class={clsx(styles.comment, props.class, {
|
||||||
[styles.isNew]:
|
[styles.isNew]:
|
||||||
(props.lastSeen || Date.now()) > (props.comment.updated_at || props.comment.created_at),
|
(props.lastSeen || Date.now()) > (props.comment.updated_at || props.comment.created_at)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={!!body()}>
|
<Show when={!!body()}>
|
||||||
|
@ -157,7 +157,7 @@ export const Comment = (props: Props) => {
|
||||||
name={props.comment.created_by.name || ''}
|
name={props.comment.created_by.name || ''}
|
||||||
userpic={props.comment.created_by.pic || ''}
|
userpic={props.comment.created_by.pic || ''}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.compactUserpic]: props.compact,
|
[styles.compactUserpic]: props.compact
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<small>
|
<small>
|
||||||
|
|
|
@ -28,7 +28,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.created_at * 1000)}</time>
|
<time class={styles.date}>{formattedDate(props.comment.created_at * 1000)}</time>
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.created_by.id === uid() &&
|
r.created_by.id === uid() &&
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.reply_to === props.comment.id,
|
r.reply_to === 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))
|
||||||
|
@ -41,8 +41,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.reply_to === props.comment.id,
|
r.reply_to === props.comment.id
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
const deleteCommentReaction = async (reactionKind: ReactionKind) => {
|
||||||
|
@ -51,7 +51,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.created_by.id === uid() &&
|
r.created_by.id === uid() &&
|
||||||
r.shout.id === props.comment.shout.id &&
|
r.shout.id === props.comment.shout.id &&
|
||||||
r.reply_to === props.comment.id,
|
r.reply_to === props.comment.id
|
||||||
)
|
)
|
||||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
@ -67,8 +67,8 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
|
||||||
shout: props.comment.shout.id,
|
shout: props.comment.shout.id,
|
||||||
reply_to: props.comment.id,
|
reply_to: props.comment.id
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -77,7 +77,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 +88,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
disabled={!(canVote() && uid())}
|
disabled={!(canVote() && uid())}
|
||||||
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 +96,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.commentRatingValue, {
|
class={clsx(styles.commentRatingValue, {
|
||||||
[styles.commentRatingPositive]: (props.comment?.stat?.rating || 0) > 0,
|
[styles.commentRatingPositive]: (props.comment?.stat?.rating || 0) > 0,
|
||||||
[styles.commentRatingNegative]: (props.comment?.stat?.rating || 0) < 0,
|
[styles.commentRatingNegative]: (props.comment?.stat?.rating || 0) < 0
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{props.comment?.stat?.rating || 0}
|
{props.comment?.stat?.rating || 0}
|
||||||
|
@ -114,7 +114,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
disabled={!(canVote() && uid())}
|
disabled={!(canVote() && uid())}
|
||||||
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>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
const { reactionEntities, createReaction, loadReactionsBy } = useReactions()
|
const { reactionEntities, createReaction, loadReactionsBy } = 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(() => {
|
||||||
|
@ -80,8 +80,8 @@ export const CommentsTree = (props: Props) => {
|
||||||
reaction: {
|
reaction: {
|
||||||
kind: ReactionKind.Comment,
|
kind: ReactionKind.Comment,
|
||||||
body: value,
|
body: value,
|
||||||
shout: props.shoutId,
|
shout: props.shoutId
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
setClearEditor(true)
|
setClearEditor(true)
|
||||||
await loadReactionsBy({ by: { shout: props.shoutSlug } })
|
await loadReactionsBy({ by: { shout: props.shoutSlug } })
|
||||||
|
|
|
@ -26,7 +26,7 @@ const coverImages = [
|
||||||
CoverImage9,
|
CoverImage9,
|
||||||
CoverImage10,
|
CoverImage10,
|
||||||
CoverImage11,
|
CoverImage11,
|
||||||
CoverImage12,
|
CoverImage12
|
||||||
]
|
]
|
||||||
|
|
||||||
let counter = 0
|
let counter = 0
|
||||||
|
|
|
@ -61,7 +61,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'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ export const FullArticle = (props: Props) => {
|
||||||
Boolean(author()?.id) &&
|
Boolean(author()?.id) &&
|
||||||
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
||||||
props.article?.created_by?.id === author().id ||
|
props.article?.created_by?.id === author().id ||
|
||||||
session()?.user?.roles?.includes('editor')),
|
session()?.user?.roles?.includes('editor'))
|
||||||
)
|
)
|
||||||
|
|
||||||
const mainTopic = createMemo(() => {
|
const mainTopic = createMemo(() => {
|
||||||
|
@ -156,7 +156,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}']`
|
||||||
)
|
)
|
||||||
|
|
||||||
if (commentElement) {
|
if (commentElement) {
|
||||||
|
@ -174,7 +174,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
|
||||||
|
@ -199,19 +199,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'
|
||||||
|
@ -297,8 +297,8 @@ export const FullArticle = (props: Props) => {
|
||||||
() => props.article,
|
() => props.article,
|
||||||
() => {
|
() => {
|
||||||
updateIframeSizes()
|
updateIframeSizes()
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -330,7 +330,7 @@ export const FullArticle = (props: Props) => {
|
||||||
title: props.article.title,
|
title: props.article.title,
|
||||||
topic: mainTopic()?.title || '',
|
topic: mainTopic()?.title || '',
|
||||||
author: props.article?.authors?.[0]?.name || '',
|
author: props.article?.authors?.[0]?.name || '',
|
||||||
width: 1200,
|
width: 1200
|
||||||
})
|
})
|
||||||
|
|
||||||
const description = getDescription(props.article.description || body() || media()[0]?.body)
|
const description = getDescription(props.article.description || body() || media()[0]?.body)
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.created_by.id === author()?.id &&
|
r.created_by.id === author()?.id &&
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.reply_to,
|
!r.reply_to
|
||||||
)
|
)
|
||||||
|
|
||||||
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
|
||||||
|
@ -39,8 +39,8 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
|
|
||||||
const shoutRatingReactions = createMemo(() =>
|
const shoutRatingReactions = createMemo(() =>
|
||||||
Object.values(reactionEntities).filter(
|
Object.values(reactionEntities).filter(
|
||||||
(r) => ['LIKE', 'DISLIKE'].includes(r.kind) && r.shout.id === props.shout.id && !r.reply_to,
|
(r) => ['LIKE', 'DISLIKE'].includes(r.kind) && r.shout.id === props.shout.id && !r.reply_to
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
const deleteShoutReaction = async (reactionKind: ReactionKind) => {
|
||||||
|
@ -49,7 +49,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
r.kind === reactionKind &&
|
r.kind === reactionKind &&
|
||||||
r.created_by.id === author()?.id &&
|
r.created_by.id === author()?.id &&
|
||||||
r.shout.id === props.shout.id &&
|
r.shout.id === props.shout.id &&
|
||||||
!r.reply_to,
|
!r.reply_to
|
||||||
)
|
)
|
||||||
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
if (reactionToDelete) return deleteReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,14 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
await createReaction({
|
await createReaction({
|
||||||
reaction: {
|
reaction: {
|
||||||
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 }
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
|
@ -26,14 +26,14 @@ export const AuthGuard = (props: Props) => {
|
||||||
changeSearchParams(
|
changeSearchParams(
|
||||||
{
|
{
|
||||||
source: 'authguard',
|
source: 'authguard',
|
||||||
m: 'auth',
|
m: 'auth'
|
||||||
},
|
},
|
||||||
{ replace: true },
|
{ replace: true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return <Show when={author() || props.disabled}>{props.children}</Show>
|
return <Show when={author() || props.disabled}>{props.children}</Show>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
const { follow, unfollow, follows, following } = useFollowing()
|
const { follow, unfollow, follows, following } = useFollowing()
|
||||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||||
const [isFollowed, setIsFollowed] = createSignal<boolean>(
|
const [isFollowed, setIsFollowed] = createSignal<boolean>(
|
||||||
Boolean(follows?.authors?.some((authorEntity) => Boolean(authorEntity.id === props.author?.id))),
|
Boolean(follows?.authors?.some((authorEntity) => Boolean(authorEntity.id === props.author?.id)))
|
||||||
)
|
)
|
||||||
createEffect(() => setIsMobileView(!mediaMatches.sm))
|
createEffect(() => setIsMobileView(!mediaMatches.sm))
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -42,11 +42,11 @@ export const AuthorBadge = (props: Props) => {
|
||||||
[() => follows?.authors, () => props.author, following],
|
[() => follows?.authors, () => props.author, following],
|
||||||
([followingAuthors, currentAuthor, _]) => {
|
([followingAuthors, currentAuthor, _]) => {
|
||||||
setIsFollowed(
|
setIsFollowed(
|
||||||
Boolean(followingAuthors?.some((followedAuthor) => followedAuthor.id === currentAuthor?.id)),
|
Boolean(followingAuthors?.some((followedAuthor) => followedAuthor.id === currentAuthor?.id))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const [, changeSearchParams] = useSearchParams()
|
const [, changeSearchParams] = useSearchParams()
|
||||||
|
@ -58,7 +58,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
navigate('/inbox')
|
navigate('/inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author?.id.toString(),
|
initChat: props.author?.id.toString()
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
fallback={
|
fallback={
|
||||||
<div class={styles.bio}>
|
<div class={styles.bio}>
|
||||||
{t('Registered since {date}', {
|
{t('Registered since {date}', {
|
||||||
date: formatDate(new Date((props.author.created_at || 0) * 1000)),
|
date: formatDate(new Date((props.author.created_at || 0) * 1000))
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
navigate('/inbox')
|
navigate('/inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author?.id.toString(),
|
initChat: props.author?.id.toString()
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'all',
|
'view-switcher__item--selected': followsFilter() === 'all'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('all')}>
|
<button type="button" onClick={() => setFollowsFilter('all')}>
|
||||||
|
@ -137,7 +137,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'authors',
|
'view-switcher__item--selected': followsFilter() === 'authors'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
||||||
|
@ -147,7 +147,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'topics',
|
'view-switcher__item--selected': followsFilter() === 'topics'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
||||||
|
@ -236,7 +236,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
value={followButtonText()}
|
value={followButtonText()}
|
||||||
isSubscribeButton={true}
|
isSubscribeButton={true}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[stylesButton.followed]: isFollowed(),
|
[stylesButton.followed]: isFollowed()
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -265,7 +265,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
description={props.author.bio || ''}
|
description={props.author.bio || ''}
|
||||||
imageUrl={props.author.pic || ''}
|
imageUrl={props.author.pic || ''}
|
||||||
shareUrl={getShareUrl({
|
shareUrl={getShareUrl({
|
||||||
pathname: `/author/${props.author.slug}`,
|
pathname: `/author/${props.author.slug}`
|
||||||
})}
|
})}
|
||||||
trigger={<Button variant="secondary" value={t('Share')} />}
|
trigger={<Button variant="secondary" value={t('Share')} />}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const AuthorLink = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.AuthorLink, props.class, styles[(props.size ?? 'M') as keyof Props['size']], {
|
class={clsx(styles.AuthorLink, props.class, styles[(props.size ?? 'M') as keyof Props['size']], {
|
||||||
[styles.authorLinkFloorImportant]: props.isFloorImportant,
|
[styles.authorLinkFloorImportant]: props.isFloorImportant
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a class={styles.link} href={`/author/${props.author.slug}`}>
|
<a class={styles.link} href={`/author/${props.author.slug}`}>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
|
||||||
const value = isUpvote ? 1 : -1
|
const value = isUpvote ? 1 : -1
|
||||||
const _resp = await mutation(rateAuthorMutation, {
|
const _resp = await mutation(rateAuthorMutation, {
|
||||||
rated_slug: props.author?.slug,
|
rated_slug: props.author?.slug,
|
||||||
value,
|
value
|
||||||
}).toPromise()
|
}).toPromise()
|
||||||
setRating((r) => (r || 0) + value)
|
setRating((r) => (r || 0) + value)
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,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
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const AuthorShoutsRating = (props: AuthorShoutsRating) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.rating, props.class, {
|
class={clsx(styles.rating, props.class, {
|
||||||
[styles.isUpvoted]: isUpvoted(),
|
[styles.isUpvoted]: isUpvoted(),
|
||||||
[styles.isDownvoted]: !isUpvoted(),
|
[styles.isDownvoted]: !isUpvoted()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.ratingValue}>{props.author?.stat?.rating_shouts}</span>
|
<span class={styles.ratingValue}>{props.author?.stat?.rating_shouts}</span>
|
||||||
|
|
|
@ -48,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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const AuthorsList = (props: Props) => {
|
||||||
const resp = await query(loadAuthorsByQuery, {
|
const resp = await query(loadAuthorsByQuery, {
|
||||||
by: { order: queryType },
|
by: { order: queryType },
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
offset,
|
offset
|
||||||
})
|
})
|
||||||
const result = resp?.data?.load_authors_by
|
const result = resp?.data?.load_authors_by
|
||||||
if ((result?.length || 0) > 0) {
|
if ((result?.length || 0) > 0) {
|
||||||
|
@ -52,7 +52,7 @@ export const AuthorsList = (props: Props) => {
|
||||||
const loadMoreAuthors = () => {
|
const loadMoreAuthors = () => {
|
||||||
const nextPage = currentPage()[props.query] + 1
|
const nextPage = currentPage()[props.query] + 1
|
||||||
fetchAuthors(props.query, nextPage).then(() =>
|
fetchAuthors(props.query, nextPage).then(() =>
|
||||||
setCurrentPage({ ...currentPage(), [props.query]: nextPage }),
|
setCurrentPage({ ...currentPage(), [props.query]: nextPage })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ export const AuthorsList = (props: Props) => {
|
||||||
setCurrentPage((prev) => ({ ...prev, [query]: 0 }))
|
setCurrentPage((prev) => ({ ...prev, [query]: 0 }))
|
||||||
fetchAuthors(query, 0).then(() => setCurrentPage((prev) => ({ ...prev, [query]: 1 })))
|
fetchAuthors(query, 0).then(() => setCurrentPage((prev) => ({ ...prev, [query]: 1 })))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers())
|
const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers())
|
||||||
|
|
|
@ -17,7 +17,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
|
||||||
|
@ -45,8 +45,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 покупателя, если нужно отправить письмо с чеком
|
||||||
|
@ -56,8 +56,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 знака после запятой)
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -98,10 +98,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() // чек для регулярных платежей
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
(opts: any) => {
|
(opts: any) => {
|
||||||
|
@ -118,9 +118,9 @@ export const Donate = () => {
|
||||||
|
|
||||||
showSnackbar({
|
showSnackbar({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
body: reason,
|
body: reason
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,11 @@ import { Icon } from '../_shared/Icon'
|
||||||
import { Newsletter } from '../_shared/Newsletter'
|
import { Newsletter } from '../_shared/Newsletter'
|
||||||
import styles from './Footer.module.scss'
|
import styles from './Footer.module.scss'
|
||||||
|
|
||||||
|
|
||||||
const social = [
|
const social = [
|
||||||
{ name: 'facebook', href: 'https://facebook.com/discoursio' },
|
{ name: 'facebook', href: 'https://facebook.com/discoursio' },
|
||||||
{ name: 'vk', href: 'https://vk.com/discoursio' },
|
{ name: 'vk', href: 'https://vk.com/discoursio' },
|
||||||
{ name: 'twitter', href: 'https://twitter.com/discours_io' },
|
{ name: 'twitter', href: 'https://twitter.com/discours_io' },
|
||||||
{ name: 'telegram', href: 'https://t.me/discoursio' },
|
{ name: 'telegram', href: 'https://t.me/discoursio' }
|
||||||
]
|
]
|
||||||
type FooterItem = {
|
type FooterItem = {
|
||||||
title: string
|
title: string
|
||||||
|
@ -19,7 +18,7 @@ type FooterItem = {
|
||||||
}
|
}
|
||||||
export const FooterView = () => {
|
export const FooterView = () => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const [footerLinks, setFooterLinks] = createSignal<Array<{ header: string, items: FooterItem[]}>>([])
|
const [footerLinks, setFooterLinks] = createSignal<Array<{ header: string; items: FooterItem[] }>>([])
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setFooterLinks([
|
setFooterLinks([
|
||||||
|
@ -30,8 +29,8 @@ export const FooterView = () => {
|
||||||
{ title: t('How it works'), slug: '/about/guide' },
|
{ title: t('How it works'), slug: '/about/guide' },
|
||||||
{ title: t('Dogma'), slug: '/about/dogma' },
|
{ title: t('Dogma'), slug: '/about/dogma' },
|
||||||
{ title: t('Principles'), slug: '/about/principles' },
|
{ title: t('Principles'), slug: '/about/principles' },
|
||||||
{ title: t('How to write an article'), slug: '/how-to-write-a-good-article' },
|
{ title: t('How to write an article'), slug: '/how-to-write-a-good-article' }
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: t('Participating'),
|
header: t('Participating'),
|
||||||
|
@ -39,8 +38,11 @@ export const FooterView = () => {
|
||||||
{ title: t('Suggest an idea'), slug: '/connect' },
|
{ title: t('Suggest an idea'), slug: '/connect' },
|
||||||
{ title: t('Become an author'), slug: '/create' },
|
{ title: t('Become an author'), slug: '/create' },
|
||||||
{ title: t('Support Discours'), slug: '/about/help' },
|
{ title: t('Support Discours'), slug: '/about/help' },
|
||||||
{ title: t('Work with us'), slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform' },
|
{
|
||||||
],
|
title: t('Work with us'),
|
||||||
|
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: t('Sections'),
|
header: t('Sections'),
|
||||||
|
@ -49,14 +51,17 @@ export const FooterView = () => {
|
||||||
{ title: t('Communities'), slug: '/community' },
|
{ title: t('Communities'), slug: '/community' },
|
||||||
{ title: t('Partners'), slug: '/about/partners' },
|
{ title: t('Partners'), slug: '/about/partners' },
|
||||||
{ title: t('Special projects'), slug: '/about/projects' },
|
{ title: t('Special projects'), slug: '/about/projects' },
|
||||||
{ title: lang() === 'ru' ? 'English' : 'Русский', slug: `?lng=${lang() === 'ru' ? 'en' : 'ru'}`, rel: 'external' },
|
{
|
||||||
],
|
title: lang() === 'ru' ? 'English' : 'Русский',
|
||||||
},
|
slug: `?lng=${lang() === 'ru' ? 'en' : 'ru'}`,
|
||||||
|
rel: 'external'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<footer class={styles.discoursFooter}>
|
<footer class={styles.discoursFooter}>
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -89,7 +94,7 @@ export const FooterView = () => {
|
||||||
<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>
|
||||||
|
@ -97,7 +102,12 @@ export const FooterView = () => {
|
||||||
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
||||||
<For each={social}>
|
<For each={social}>
|
||||||
{(provider) => (
|
{(provider) => (
|
||||||
<div class={clsx(styles.socialItem, styles[`socialItem${provider.name}` as keyof typeof styles])}>
|
<div
|
||||||
|
class={clsx(
|
||||||
|
styles.socialItem,
|
||||||
|
styles[`socialItem${provider.name}` as keyof typeof styles]
|
||||||
|
)}
|
||||||
|
>
|
||||||
<a href={provider.href}>
|
<a href={provider.href}>
|
||||||
<Icon name={`${provider.name}-white`} class={styles.icon} />
|
<Icon name={`${provider.name}-white`} class={styles.icon} />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -16,7 +16,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}>
|
||||||
|
@ -28,7 +28,7 @@ export default () => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showModal('auth')
|
showModal('auth')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'register',
|
mode: 'register'
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -36,7 +36,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)
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const AudioUploader = (props: Props) => {
|
||||||
const handleMediaItemFieldChange = (
|
const handleMediaItemFieldChange = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof MediaItem | string | symbol | number,
|
field: keyof MediaItem | string | symbol | number,
|
||||||
value: string,
|
value: string
|
||||||
) => {
|
) => {
|
||||||
props.onAudioChange(index, { ...props.audio[index], [field]: value })
|
props.onAudioChange(index, { ...props.audio[index], [field]: value })
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,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> = {}
|
||||||
|
@ -91,7 +91,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
url: 'wss://hocuspocus.discours.io',
|
url: 'wss://hocuspocus.discours.io',
|
||||||
name: docName,
|
name: docName,
|
||||||
document: yDocs[docName],
|
document: yDocs[docName],
|
||||||
token: session()?.access_token || '',
|
token: session()?.access_token || ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ export const EditorComponent = (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') })
|
||||||
|
@ -135,13 +135,13 @@ export const EditorComponent = (props: Props) => {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: { src: result.url },
|
attrs: { src: result.url }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'figcaption',
|
type: 'figcaption',
|
||||||
content: [{ type: 'text', text: result.originalFilename }],
|
content: [{ type: 'text', text: result.originalFilename }]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -158,7 +158,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
element: ee,
|
element: ee,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: 'articleEditor',
|
class: 'articleEditor'
|
||||||
},
|
},
|
||||||
transformPastedHTML(html) {
|
transformPastedHTML(html) {
|
||||||
return html.replaceAll(/<img.*?>/g, '')
|
return html.replaceAll(/<img.*?>/g, '')
|
||||||
|
@ -166,7 +166,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
handlePaste: () => {
|
handlePaste: () => {
|
||||||
handleClipboardPaste()
|
handleClipboardPaste()
|
||||||
return false
|
return false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
Document,
|
Document,
|
||||||
|
@ -181,34 +181,34 @@ export const EditorComponent = (props: Props) => {
|
||||||
Strike,
|
Strike,
|
||||||
HorizontalRule.configure({
|
HorizontalRule.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'horizontalRule',
|
class: 'horizontalRule'
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
Underline,
|
Underline,
|
||||||
Link.extend({
|
Link.extend({
|
||||||
inclusive: false,
|
inclusive: false
|
||||||
}).configure({
|
}).configure({
|
||||||
autolink: true,
|
autolink: true,
|
||||||
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: author().name,
|
name: author().name,
|
||||||
color: uniqolor(author().slug).color,
|
color: uniqolor(author().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,
|
||||||
|
@ -216,8 +216,8 @@ export const EditorComponent = (props: Props) => {
|
||||||
Highlight.configure({
|
Highlight.configure({
|
||||||
multicolor: true,
|
multicolor: true,
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'highlight',
|
class: 'highlight'
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
Image,
|
Image,
|
||||||
Iframe,
|
Iframe,
|
||||||
|
@ -253,8 +253,8 @@ export const EditorComponent = (props: Props) => {
|
||||||
onHide: () => {
|
onHide: () => {
|
||||||
const fe = freshEditor() as Editor
|
const fe = freshEditor() as Editor
|
||||||
fe?.commands.focus()
|
fe?.commands.focus()
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'blockquoteBubbleMenu',
|
pluginKey: 'blockquoteBubbleMenu',
|
||||||
|
@ -262,7 +262,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
shouldShow: ({ editor: e, view, state }) => {
|
shouldShow: ({ editor: e, view, state }) => {
|
||||||
const { empty } = state.selection
|
const { empty } = state.selection
|
||||||
return view.hasFocus() && !empty && e.isActive('blockquote')
|
return view.hasFocus() && !empty && e.isActive('blockquote')
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'figureBubbleMenu',
|
pluginKey: 'figureBubbleMenu',
|
||||||
|
@ -270,7 +270,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
shouldShow: ({ editor: e, view, state }) => {
|
shouldShow: ({ editor: e, view, state }) => {
|
||||||
const { empty } = state.selection
|
const { empty } = state.selection
|
||||||
return view.hasFocus() && !empty && e.isActive('figure')
|
return view.hasFocus() && !empty && e.isActive('figure')
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'incutBubbleMenu',
|
pluginKey: 'incutBubbleMenu',
|
||||||
|
@ -278,7 +278,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
shouldShow: ({ editor: e, view, state }) => {
|
shouldShow: ({ editor: e, view, state }) => {
|
||||||
const { empty } = state.selection
|
const { empty } = state.selection
|
||||||
return view.hasFocus() && !empty && e.isActive('figcaption')
|
return view.hasFocus() && !empty && e.isActive('figcaption')
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
FloatingMenu.configure({
|
FloatingMenu.configure({
|
||||||
element: floatingMenuRef,
|
element: floatingMenuRef,
|
||||||
|
@ -290,10 +290,10 @@ export const EditorComponent = (props: Props) => {
|
||||||
if (!(isRootDepth && empty)) return false
|
if (!(isRootDepth && empty)) return false
|
||||||
|
|
||||||
return !(e.isActive('codeBlock') || e.isActive('heading'))
|
return !(e.isActive('codeBlock') || e.isActive('heading'))
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
Article,
|
Article
|
||||||
],
|
],
|
||||||
onTransaction: ({ transaction }) => {
|
onTransaction: ({ transaction }) => {
|
||||||
if (transaction.docChanged) {
|
if (transaction.docChanged) {
|
||||||
|
@ -305,7 +305,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: initialContent,
|
content: initialContent
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (freshEditor) {
|
if (freshEditor) {
|
||||||
|
@ -317,7 +317,7 @@ export const EditorComponent = (props: Props) => {
|
||||||
setEditor(freshEditor() as Editor)
|
setEditor(freshEditor() as Editor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
|
|
@ -57,14 +57,14 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
attrs: {
|
attrs: {
|
||||||
src: emb.src,
|
src: emb.src,
|
||||||
width: emb.width,
|
width: emb.width,
|
||||||
height: emb.height,
|
height: emb.height
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'figcaption',
|
type: 'figcaption',
|
||||||
content: [{ type: 'text', text: t('Description') }],
|
content: [{ type: 'text', text: t('Description') }]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
setMenuOpen(false)
|
setMenuOpen(false)
|
||||||
setSelectedMenuItem()
|
setSelectedMenuItem()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleUpload = (image: UploadedFile) => {
|
const handleUpload = (image: UploadedFile) => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const InsertLinkForm = (props: Props) => {
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return ed?.getAttributes('link').href || ''
|
return ed?.getAttributes('link').href || ''
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
const handleClearLinkForm = () => {
|
const handleClearLinkForm = () => {
|
||||||
if (currentUrl()) {
|
if (currentUrl()) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const Panel = (props: Props) => {
|
||||||
toggleEditorPanel,
|
toggleEditorPanel,
|
||||||
saveShout,
|
saveShout,
|
||||||
saveDraft,
|
saveDraft,
|
||||||
publishShout,
|
publishShout
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
let containerRef: HTMLElement | undefined
|
let containerRef: HTMLElement | undefined
|
||||||
|
@ -42,7 +42,7 @@ export const Panel = (props: Props) => {
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef,
|
containerRef,
|
||||||
predicate: () => isEditorPanelVisible(),
|
predicate: () => isEditorPanelVisible(),
|
||||||
handler: () => toggleEditorPanel(),
|
handler: () => toggleEditorPanel()
|
||||||
})
|
})
|
||||||
|
|
||||||
useEscKeyDownHandler(() => {
|
useEscKeyDownHandler(() => {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
createTiptapEditor,
|
createTiptapEditor,
|
||||||
useEditorHTML,
|
useEditorHTML,
|
||||||
useEditorIsEmpty,
|
useEditorIsEmpty,
|
||||||
useEditorIsFocused,
|
useEditorIsFocused
|
||||||
} from 'solid-tiptap'
|
} from 'solid-tiptap'
|
||||||
|
|
||||||
import { UploadedFile } from '~/types/upload'
|
import { UploadedFile } from '~/types/upload'
|
||||||
|
@ -82,7 +82,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
|
|
||||||
const ImageFigure = Figure.extend({
|
const ImageFigure = Figure.extend({
|
||||||
name: 'capturedImage',
|
name: 'capturedImage',
|
||||||
content: 'figcaption image',
|
content: 'figcaption image'
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -94,8 +94,8 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
element: ee,
|
element: ee,
|
||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class: styles.simplifiedEditorField,
|
class: styles.simplifiedEditorField
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
extensions: [
|
extensions: [
|
||||||
Document,
|
Document,
|
||||||
|
@ -104,18 +104,18 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
Bold,
|
Bold,
|
||||||
Italic,
|
Italic,
|
||||||
Link.extend({
|
Link.extend({
|
||||||
inclusive: false,
|
inclusive: false
|
||||||
}).configure({
|
}).configure({
|
||||||
autolink: true,
|
autolink: true,
|
||||||
openOnClick: false,
|
openOnClick: false
|
||||||
}),
|
}),
|
||||||
CharacterCount.configure({
|
CharacterCount.configure({
|
||||||
limit: props.noLimits ? null : maxLength,
|
limit: props.noLimits ? null : maxLength
|
||||||
}),
|
}),
|
||||||
Blockquote.configure({
|
Blockquote.configure({
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: styles.blockQuote,
|
class: styles.blockQuote
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'textBubbleMenu',
|
pluginKey: 'textBubbleMenu',
|
||||||
|
@ -125,7 +125,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
const { empty } = selection
|
const { empty } = selection
|
||||||
return view.hasFocus() && !empty
|
return view.hasFocus() && !empty
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
pluginKey: 'linkBubbleMenu',
|
pluginKey: 'linkBubbleMenu',
|
||||||
|
@ -136,27 +136,27 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
return !empty && shouldShowLinkBubbleMenu()
|
return !empty && shouldShowLinkBubbleMenu()
|
||||||
},
|
},
|
||||||
tippyOptions: {
|
tippyOptions: {
|
||||||
placement: 'bottom',
|
placement: 'bottom'
|
||||||
},
|
}
|
||||||
}),
|
}),
|
||||||
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: props.initialContent || null,
|
content: props.initialContent || null
|
||||||
}))
|
}))
|
||||||
const editorInstance = freshEditor()
|
const editorInstance = freshEditor()
|
||||||
if (!editorInstance) return
|
if (!editorInstance) return
|
||||||
setEditor(editorInstance)
|
setEditor(editorInstance)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const isEmpty = useEditorIsEmpty(() => editor())
|
const isEmpty = useEditorIsEmpty(() => editor())
|
||||||
|
@ -167,7 +167,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
() => editor(),
|
() => editor(),
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return ed?.isActive(name)
|
return ed?.isActive(name)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const html = useEditorHTML(() => editor())
|
const html = useEditorHTML(() => editor())
|
||||||
|
@ -186,13 +186,13 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: 'image',
|
type: 'image',
|
||||||
attrs: { src: image.url },
|
attrs: { src: image.url }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'figcaption',
|
type: 'figcaption',
|
||||||
content: [{ type: 'text', text: image.originalFilename }],
|
content: [{ type: 'text', text: image.originalFilename }]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
.run()
|
.run()
|
||||||
hideModal()
|
hideModal()
|
||||||
|
@ -259,7 +259,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
|
|
||||||
const maxHeightStyle = {
|
const maxHeightStyle = {
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
'max-height': `${props.maxHeight}px`,
|
'max-height': `${props.maxHeight}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleShowLinkBubble = () => {
|
const handleShowLinkBubble = () => {
|
||||||
|
@ -281,7 +281,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()}>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
const isActive = (name: string, attributes?: Record<string, string | number>) =>
|
const isActive = (name: string, attributes?: Record<string, string | number>) =>
|
||||||
createEditorTransaction(
|
createEditorTransaction(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(editor) => editor?.isActive(name, attributes),
|
(editor) => editor?.isActive(name, attributes)
|
||||||
)
|
)
|
||||||
|
|
||||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
||||||
|
@ -86,7 +86,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: string) => {
|
const handleAddFootnote = (footnote: string) => {
|
||||||
|
@ -168,7 +168,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}
|
||||||
>
|
>
|
||||||
|
@ -185,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]: isH1(),
|
[styles.bubbleMenuButtonActive]: isH1()
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||||
|
@ -202,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]: isH2(),
|
[styles.bubbleMenuButtonActive]: isH2()
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||||
|
@ -219,7 +219,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()
|
||||||
|
@ -239,7 +239,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}
|
||||||
>
|
>
|
||||||
|
@ -253,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]: isPunchLine(),
|
[styles.bubbleMenuButtonActive]: isPunchLine()
|
||||||
})}
|
})}
|
||||||
onClick={handleSetQuote}
|
onClick={handleSetQuote}
|
||||||
>
|
>
|
||||||
|
@ -270,7 +270,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()
|
||||||
|
@ -294,7 +294,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()}
|
||||||
>
|
>
|
||||||
|
@ -308,7 +308,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()}
|
||||||
>
|
>
|
||||||
|
@ -324,7 +324,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()}
|
||||||
>
|
>
|
||||||
|
@ -341,7 +341,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleOpenLinkForm}
|
onClick={handleOpenLinkForm}
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
[styles.bubbleMenuButtonActive]: isLink(),
|
[styles.bubbleMenuButtonActive]: isLink()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon name="editor-link" />
|
<Icon name="editor-link" />
|
||||||
|
@ -356,7 +356,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}
|
||||||
>
|
>
|
||||||
|
@ -369,7 +369,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}
|
||||||
>
|
>
|
||||||
|
@ -386,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]: isBulletList(),
|
[styles.bubbleMenuButtonActive]: isBulletList()
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.editor.chain().focus().toggleBulletList().run()
|
props.editor.chain().focus().toggleBulletList().run()
|
||||||
|
@ -403,7 +403,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()
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
|
|
||||||
const filteredTopics = () => {
|
const filteredTopics = () => {
|
||||||
return props.topics.filter((topic: Topic) =>
|
return props.topics.filter((topic: Topic) =>
|
||||||
topic?.title?.toLowerCase().includes(searchTerm().toLowerCase()),
|
topic?.title?.toLowerCase().includes(searchTerm().toLowerCase())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.selectedTopic, {
|
class={clsx(styles.selectedTopic, {
|
||||||
[styles.mainTopic]: props.mainTopic?.slug === topic.slug,
|
[styles.mainTopic]: props.mainTopic?.slug === topic.slug
|
||||||
})}
|
})}
|
||||||
onClick={() => handleMainTopicChange(topic)}
|
onClick={() => handleMainTopicChange(topic)}
|
||||||
>
|
>
|
||||||
|
@ -76,8 +76,8 @@ export const TopicSelect = (props: TopicSelectProps) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.option, {
|
class={clsx(styles.option, {
|
||||||
[styles.disabled]: props.selectedTopics.some(
|
[styles.disabled]: props.selectedTopics.some(
|
||||||
(selectedTopic) => selectedTopic.slug === topic.slug,
|
(selectedTopic) => selectedTopic.slug === topic.slug
|
||||||
),
|
)
|
||||||
})}
|
})}
|
||||||
onClick={() => handleChange(topic)}
|
onClick={() => handleChange(topic)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -46,13 +46,13 @@ export const UploadModalContent = (props: Props) => {
|
||||||
const data = await fetch(value)
|
const data = await fetch(value)
|
||||||
const blob = await data.blob()
|
const blob = await data.blob()
|
||||||
const file = new File([blob], 'convertedFromUrl', {
|
const file = new File([blob], 'convertedFromUrl', {
|
||||||
type: data.headers.get('Content-Type') || undefined,
|
type: data.headers.get('Content-Type') || undefined
|
||||||
})
|
})
|
||||||
const fileToUpload: UploadFile = {
|
const fileToUpload: UploadFile = {
|
||||||
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) {
|
||||||
|
@ -76,7 +76,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') {
|
||||||
|
|
|
@ -33,13 +33,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: DragEvent) => {
|
const handleDrag = (event: DragEvent) => {
|
||||||
if (event.type === 'dragenter' || event.type === 'dragover') {
|
if (event.type === 'dragenter' || event.type === 'dragover') {
|
||||||
|
@ -78,8 +78,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}
|
||||||
|
|
|
@ -18,8 +18,8 @@ export default Node.create({
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'article',
|
tag: 'article'
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -29,18 +29,18 @@ export default Node.create({
|
||||||
|
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
'data-type': 'incut',
|
'data-type': 'incut'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
'data-float': {
|
'data-float': {
|
||||||
default: null,
|
default: null
|
||||||
},
|
},
|
||||||
'data-bg': {
|
'data-bg': {
|
||||||
default: null,
|
default: null
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,7 +61,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 })
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -23,11 +23,11 @@ export const CustomBlockquote = Blockquote.extend({
|
||||||
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({
|
||||||
setBlockQuoteFloat:
|
setBlockQuoteFloat:
|
||||||
(value) =>
|
(value) =>
|
||||||
({ commands }) =>
|
({ commands }) =>
|
||||||
commands.updateAttributes(this.name, { 'data-float': value }),
|
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 })
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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',
|
||||||
|
@ -24,7 +24,7 @@ export const Figure = Node.create({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
'data-float': null,
|
'data-float': null,
|
||||||
'data-type': { default: null },
|
'data-type': { default: null }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// @ts-ignore FIXME: why
|
// @ts-ignore FIXME: why
|
||||||
|
@ -45,8 +45,8 @@ export const Figure = Node.create({
|
||||||
dataType = 'iframe'
|
dataType = 'iframe'
|
||||||
}
|
}
|
||||||
return { 'data-type': dataType }
|
return { 'data-type': dataType }
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
renderHTML({ HTMLAttributes }) {
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
@ -69,10 +69,10 @@ export const Figure = Node.create({
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -82,7 +82,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
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,33 +24,33 @@ export const Iframe = Node.create<IframeOptions>({
|
||||||
return {
|
return {
|
||||||
allowFullscreen: true,
|
allowFullscreen: true,
|
||||||
HTMLAttributes: {
|
HTMLAttributes: {
|
||||||
class: 'iframe-wrapper',
|
class: 'iframe-wrapper'
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
src: {
|
src: {
|
||||||
default: null,
|
default: null
|
||||||
},
|
},
|
||||||
frameborder: {
|
frameborder: {
|
||||||
default: 0,
|
default: 0
|
||||||
},
|
},
|
||||||
allowfullscreen: {
|
allowfullscreen: {
|
||||||
default: this.options.allowFullscreen,
|
default: this.options.allowFullscreen,
|
||||||
parseHTML: () => this.options.allowFullscreen,
|
parseHTML: () => this.options.allowFullscreen
|
||||||
},
|
},
|
||||||
width: { default: null },
|
width: { default: null },
|
||||||
height: { default: null },
|
height: { default: null }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHTML() {
|
parseHTML() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
tag: 'iframe',
|
tag: 'iframe'
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export const Iframe = Node.create<IframeOptions>({
|
||||||
tr.replaceRangeWith(selection.from, selection.to, node)
|
tr.replaceRangeWith(selection.from, selection.to, node)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,8 +12,8 @@ export const Span = Mark.create({
|
||||||
return { class: dom.getAttribute('class') }
|
return { class: dom.getAttribute('class') }
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ export const Span = Mark.create({
|
||||||
addAttributes() {
|
addAttributes() {
|
||||||
return {
|
return {
|
||||||
class: {
|
class: {
|
||||||
default: null,
|
default: null
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -43,7 +43,7 @@ export const ToggleTextWrap = Extension.create({
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
|
|
||||||
function nodeEqualsType({
|
function nodeEqualsType({
|
||||||
types,
|
types,
|
||||||
node,
|
node
|
||||||
}: {
|
}: {
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME: any in editor extension
|
// biome-ignore lint/suspicious/noExplicitAny: FIXME: any in editor extension
|
||||||
types: any
|
types: any
|
||||||
|
@ -30,7 +30,7 @@ export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
||||||
addOptions() {
|
addOptions() {
|
||||||
return {
|
return {
|
||||||
node: 'paragraph',
|
node: 'paragraph',
|
||||||
notAfter: ['paragraph'],
|
notAfter: ['paragraph']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -69,9 +69,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 })
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -53,11 +53,11 @@ const desktopCoverImageWidths: Record<string, number> = {
|
||||||
XS: 300,
|
XS: 300,
|
||||||
S: 400,
|
S: 400,
|
||||||
M: 600,
|
M: 600,
|
||||||
L: 800,
|
L: 800
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitleAndSubtitle = (
|
const getTitleAndSubtitle = (
|
||||||
article: Shout,
|
article: Shout
|
||||||
): {
|
): {
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
|
@ -98,7 +98,7 @@ const LAYOUT_ASPECT: { [key: string]: string } = {
|
||||||
audio: styles.aspectRatio1x1,
|
audio: styles.aspectRatio1x1,
|
||||||
literature: styles.aspectRatio16x9,
|
literature: styles.aspectRatio16x9,
|
||||||
video: styles.aspectRatio16x9,
|
video: styles.aspectRatio16x9,
|
||||||
image: styles.aspectRatio4x3,
|
image: styles.aspectRatio4x3
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ArticleCard = (props: ArticleCardProps) => {
|
export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
@ -115,7 +115,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() =>
|
const formattedDate = createMemo<string>(() =>
|
||||||
props.article?.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
props.article?.published_at ? formatDate(new Date(props.article.published_at * 1000)) : ''
|
||||||
)
|
)
|
||||||
|
|
||||||
const canEdit = createMemo(
|
const canEdit = createMemo(
|
||||||
|
@ -123,14 +123,14 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
Boolean(author()?.id) &&
|
Boolean(author()?.id) &&
|
||||||
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
||||||
props.article?.created_by?.id === author().id ||
|
props.article?.created_by?.id === author().id ||
|
||||||
session()?.user?.roles?.includes('editor')),
|
session()?.user?.roles?.includes('editor'))
|
||||||
)
|
)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
navigate(`/article/${props.article.slug}`)
|
navigate(`/article/${props.article.slug}`)
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
scrollTo: 'comments',
|
scrollTo: 'comments'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[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,
|
||||||
[aspectRatio()]: props.withAspectRatio,
|
[aspectRatio()]: props.withAspectRatio
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* Cover Image */}
|
{/* Cover Image */}
|
||||||
|
@ -162,7 +162,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<div class={styles.shoutCardCoverContainer}>
|
<div class={styles.shoutCardCoverContainer}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardCover, {
|
class={clsx(styles.shoutCardCover, {
|
||||||
[styles.loading]: props.article.cover && isCoverImageLoading(),
|
[styles.loading]: props.article.cover && isCoverImageLoading()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
|
@ -217,7 +217,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
{/* Title and Subtitle */}
|
{/* Title and Subtitle */}
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardTitlesContainer, {
|
class={clsx(styles.shoutCardTitlesContainer, {
|
||||||
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode,
|
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<A href={`/article${props.article.slug}`}>
|
<A href={`/article${props.article.slug}`}>
|
||||||
|
@ -249,7 +249,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
size={'XS'}
|
size={'XS'}
|
||||||
author={a as Author}
|
author={a as Author}
|
||||||
isFloorImportant={Boolean(
|
isFloorImportant={Boolean(
|
||||||
props.settings?.isFloorImportant || props.settings?.isWithCover,
|
props.settings?.isFloorImportant || props.settings?.isWithCover
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -40,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)}` as keyof typeof styles
|
`besideRatingColumn${props.wrapper?.charAt(0)?.toUpperCase() + props.wrapper.slice(1)}` as keyof typeof styles
|
||||||
],
|
]
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Show when={!!props.title}>
|
<Show when={!!props.title}>
|
||||||
|
@ -64,7 +64,7 @@ export const Beside = (props: Props) => {
|
||||||
</Show>
|
</Show>
|
||||||
<ul
|
<ul
|
||||||
class={clsx(styles.besideColumn, {
|
class={clsx(styles.besideColumn, {
|
||||||
[styles.besideColumnTopViewed]: props.wrapper === 'top-article',
|
[styles.besideColumnTopViewed]: props.wrapper === 'top-article'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<For each={[...props.values]}>
|
<For each={[...props.values]}>
|
||||||
|
|
|
@ -15,7 +15,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={`/topic/${props.slug}`}>{props.title}</A>
|
<A href={`/topic/${props.slug}`}>{props.title}</A>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default (props: GroupProps) => {
|
||||||
nosubtitle: false,
|
nosubtitle: false,
|
||||||
noicon: true,
|
noicon: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
nodate: true,
|
nodate: true
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="M"
|
desktopCoverSize="M"
|
||||||
/>
|
/>
|
||||||
|
@ -60,7 +60,7 @@ export default (props: GroupProps) => {
|
||||||
noimage: true,
|
noimage: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isCompact: true,
|
isCompact: true,
|
||||||
nodate: true,
|
nodate: true
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="XS"
|
desktopCoverSize="XS"
|
||||||
/>
|
/>
|
||||||
|
@ -77,7 +77,7 @@ export default (props: GroupProps) => {
|
||||||
noimage: true,
|
noimage: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isCompact: true,
|
isCompact: true,
|
||||||
nodate: true,
|
nodate: true
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="XS"
|
desktopCoverSize="XS"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -36,14 +36,14 @@ const data: PlaceholderData = {
|
||||||
text: 'Placeholder feed',
|
text: 'Placeholder feed',
|
||||||
buttonLabelAuthor: 'Popular authors',
|
buttonLabelAuthor: 'Popular authors',
|
||||||
buttonLabelFeed: 'Create own feed',
|
buttonLabelFeed: 'Create own feed',
|
||||||
href: '/authors?by=followers',
|
href: '/authors?by=followers'
|
||||||
},
|
},
|
||||||
feedCollaborations: {
|
feedCollaborations: {
|
||||||
image: 'placeholder-experts.webp',
|
image: 'placeholder-experts.webp',
|
||||||
header: 'Find collaborators',
|
header: 'Find collaborators',
|
||||||
text: 'Placeholder feedCollaborations',
|
text: 'Placeholder feedCollaborations',
|
||||||
buttonLabel: 'Find co-authors',
|
buttonLabel: 'Find co-authors',
|
||||||
href: '/authors?by=name',
|
href: '/authors?by=name'
|
||||||
},
|
},
|
||||||
feedDiscussions: {
|
feedDiscussions: {
|
||||||
image: 'placeholder-discussions.webp',
|
image: 'placeholder-discussions.webp',
|
||||||
|
@ -51,7 +51,7 @@ const data: PlaceholderData = {
|
||||||
text: 'Placeholder feedDiscussions',
|
text: 'Placeholder feedDiscussions',
|
||||||
buttonLabelAuthor: 'Current discussions',
|
buttonLabelAuthor: 'Current discussions',
|
||||||
buttonLabelFeed: 'Enter',
|
buttonLabelFeed: 'Enter',
|
||||||
href: '/feed?by=last_comment',
|
href: '/feed?by=last_comment'
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
image: 'placeholder-join.webp',
|
image: 'placeholder-join.webp',
|
||||||
|
@ -62,9 +62,9 @@ const data: PlaceholderData = {
|
||||||
profileLinks: [
|
profileLinks: [
|
||||||
{
|
{
|
||||||
href: '/how-to-write-a-good-article',
|
href: '/how-to-write-a-good-article',
|
||||||
label: 'How to write a good article',
|
label: 'How to write a good article'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
authorComments: {
|
authorComments: {
|
||||||
image: 'placeholder-discussions.webp',
|
image: 'placeholder-discussions.webp',
|
||||||
|
@ -75,14 +75,14 @@ const data: PlaceholderData = {
|
||||||
profileLinks: [
|
profileLinks: [
|
||||||
{
|
{
|
||||||
href: '/about/discussion-rules',
|
href: '/about/discussion-rules',
|
||||||
label: 'Discussion rules',
|
label: 'Discussion rules'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/about/discussion-rules#ban',
|
href: '/about/discussion-rules#ban',
|
||||||
label: 'Block rules',
|
label: 'Block rules'
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Placeholder = (props: PlaceholderProps) => {
|
export const Placeholder = (props: PlaceholderProps) => {
|
||||||
|
@ -96,7 +96,7 @@ export const Placeholder = (props: PlaceholderProps) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.placeholder,
|
styles.placeholder,
|
||||||
styles[`placeholder--${props.type}` as keyof typeof styles],
|
styles[`placeholder--${props.type}` as keyof typeof styles],
|
||||||
styles[`placeholder--${props.mode}-mode` as keyof typeof styles],
|
styles[`placeholder--${props.mode}-mode` as keyof typeof styles]
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class={styles.placeholderCover}>
|
<div class={styles.placeholderCover}>
|
||||||
|
@ -128,7 +128,7 @@ export const Placeholder = (props: PlaceholderProps) => {
|
||||||
{t(
|
{t(
|
||||||
session()?.access_token
|
session()?.access_token
|
||||||
? placeholderData()?.buttonLabelAuthor || ''
|
? placeholderData()?.buttonLabelAuthor || ''
|
||||||
: placeholderData()?.buttonLabelFeed || '',
|
: placeholderData()?.buttonLabelFeed || ''
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ export const Placeholder = (props: PlaceholderProps) => {
|
||||||
{t(
|
{t(
|
||||||
session()?.access_token
|
session()?.access_token
|
||||||
? placeholderData()?.buttonLabelAuthor || ''
|
? placeholderData()?.buttonLabelAuthor || ''
|
||||||
: placeholderData()?.buttonLabelFeed || '',
|
: placeholderData()?.buttonLabelFeed || ''
|
||||||
)}
|
)}
|
||||||
<Show when={props.mode === 'profile'}>
|
<Show when={props.mode === 'profile'}>
|
||||||
<Icon name="arrow-right-2" class={styles.icon} />
|
<Icon name="arrow-right-2" class={styles.icon} />
|
||||||
|
|
|
@ -21,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
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="L"
|
desktopCoverSize="L"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const Row2 = (props: {
|
||||||
isWithCover: props.isEqual || className === 'col-md-16',
|
isWithCover: props.isEqual || className === 'col-md-16',
|
||||||
nodate: props.isEqual || props.nodate,
|
nodate: props.isEqual || props.nodate,
|
||||||
noAuthorLink: props.noAuthorLink,
|
noAuthorLink: props.noAuthorLink,
|
||||||
noauthor: props.noauthor,
|
noauthor: props.noauthor
|
||||||
}}
|
}}
|
||||||
desktopCoverSize={desktopCoverSize}
|
desktopCoverSize={desktopCoverSize}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,7 +28,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
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="S"
|
desktopCoverSize="S"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default (props: { articles: Shout[] }) => (
|
||||||
isWithCover: true,
|
isWithCover: true,
|
||||||
isBigTitle: true,
|
isBigTitle: true,
|
||||||
isVertical: true,
|
isVertical: true,
|
||||||
nodate: true,
|
nodate: true
|
||||||
}}
|
}}
|
||||||
desktopCoverSize="S"
|
desktopCoverSize="S"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const Sidebar = () => {
|
||||||
<A
|
<A
|
||||||
href={'feed'}
|
href={'feed'}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: matchFeed(),
|
[styles.selected]: matchFeed()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -47,7 +47,7 @@ export const Sidebar = () => {
|
||||||
<A
|
<A
|
||||||
href={'/feed/my'}
|
href={'/feed/my'}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: matchFeedMy(),
|
[styles.selected]: matchFeedMy()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -60,7 +60,7 @@ export const Sidebar = () => {
|
||||||
<A
|
<A
|
||||||
href={'/feed/collabs'}
|
href={'/feed/collabs'}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: matchFeedCollabs(),
|
[styles.selected]: matchFeedCollabs()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
@ -73,7 +73,7 @@ export const Sidebar = () => {
|
||||||
<a
|
<a
|
||||||
href={'/feed/discussions'}
|
href={'/feed/discussions'}
|
||||||
class={clsx({
|
class={clsx({
|
||||||
[styles.selected]: matchFeedDiscussions(),
|
[styles.selected]: matchFeedDiscussions()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
|
|
|
@ -50,7 +50,7 @@ const CreateModalContent = (props: Props) => {
|
||||||
const handleClick = (user: inviteUser) => {
|
const handleClick = (user: inviteUser) => {
|
||||||
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
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,13 @@ const colors = [
|
||||||
'#668cff',
|
'#668cff',
|
||||||
'#c34cfe',
|
'#c34cfe',
|
||||||
'#e699ff',
|
'#e699ff',
|
||||||
'#6633ff',
|
'#6633ff'
|
||||||
]
|
]
|
||||||
|
|
||||||
const getById = (letter: string) =>
|
const getById = (letter: string) =>
|
||||||
colors[
|
colors[
|
||||||
Math.abs(
|
Math.abs(
|
||||||
Number(BigInt(((letter || '').toLowerCase()?.codePointAt(0) || 97) - 97) % BigInt(colors.length)),
|
Number(BigInt(((letter || '').toLowerCase()?.codePointAt(0) || 97) - 97) % BigInt(colors.length))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -48,7 +48,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()}` }}
|
||||||
>
|
>
|
||||||
|
@ -62,7 +62,7 @@ const DialogAvatar = (props: Props) => {
|
||||||
? getImageUrl(props.url || '', { width: 40, height: 40 })
|
? getImageUrl(props.url || '', { width: 40, height: 40 })
|
||||||
: props.url
|
: props.url
|
||||||
}
|
}
|
||||||
)`,
|
)`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -27,7 +27,7 @@ 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?.filter((member: ChatMember) => member.id !== props.ownId),
|
props.members?.filter((member: ChatMember) => member.id !== props.ownId)
|
||||||
)
|
)
|
||||||
|
|
||||||
const names = createMemo<string>(() => (companions() || []).map((companion) => companion.name).join(', '))
|
const names = createMemo<string>(() => (companions() || []).map((companion) => companion.name).join(', '))
|
||||||
|
@ -37,7 +37,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}
|
||||||
>
|
>
|
||||||
|
|
|
@ -20,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() || 'select')
|
if (props.actionSelect) props.actionSelect(selectedAction() || 'select')
|
||||||
|
|
|
@ -19,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'}>
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const AuthModalHeader = (props: Props) => {
|
||||||
const [searchParams] = useSearchParams<{ source: string }>()
|
const [searchParams] = useSearchParams<{ source: string }>()
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
|
@ -20,53 +20,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: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const ChangePasswordForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'login',
|
mode: 'login'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export const LoginForm = () => {
|
||||||
if (value === '' || !validateEmail(value)) {
|
if (value === '' || !validateEmail(value)) {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
email: t('Invalid email'),
|
email: t('Invalid email')
|
||||||
}))
|
}))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export const LoginForm = () => {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
password: t('Please enter password'),
|
password: t('Please enter password')
|
||||||
}))
|
}))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ export const LoginForm = () => {
|
||||||
case 'bad user credentials': {
|
case 'bad user credentials': {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
password: t('Something went wrong, check email and password'),
|
password: t('Something went wrong, check email and password')
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ export const LoginForm = () => {
|
||||||
<span class={'link'} onClick={handleSendLinkAgainClick}>
|
<span class={'link'} onClick={handleSendLinkAgainClick}>
|
||||||
{t('Send link again')}
|
{t('Send link again')}
|
||||||
</span>
|
</span>
|
||||||
</div>,
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ export const LoginForm = () => {
|
||||||
<AuthModalHeader modalType="login" />
|
<AuthModalHeader modalType="login" />
|
||||||
<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
|
||||||
|
@ -177,7 +177,7 @@ export const LoginForm = () => {
|
||||||
class="link"
|
class="link"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
mode: 'send-reset-link',
|
mode: 'send-reset-link'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -194,7 +194,7 @@ export const LoginForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
mode: 'register',
|
mode: 'register'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -91,7 +91,7 @@ export const PasswordField = (props: Props) => {
|
||||||
<Show when={error()}>
|
<Show when={error()}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.registerPassword, styles.validationError, {
|
class={clsx(styles.registerPassword, styles.validationError, {
|
||||||
'form-message--error': props.setError,
|
'form-message--error': props.setError
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{error()}
|
{error()}
|
||||||
|
|
|
@ -91,7 +91,7 @@ export const RegisterForm = () => {
|
||||||
email: cleanEmail,
|
email: cleanEmail,
|
||||||
password: password(),
|
password: password(),
|
||||||
confirm_password: password(),
|
confirm_password: password(),
|
||||||
redirect_uri: window?.location?.origin || '',
|
redirect_uri: window?.location?.origin || ''
|
||||||
}
|
}
|
||||||
const success = await signUp(opts)
|
const success = await signUp(opts)
|
||||||
setIsSuccess(success)
|
setIsSuccess(success)
|
||||||
|
@ -106,7 +106,7 @@ export const RegisterForm = () => {
|
||||||
const handleResendLink = async (_ev: any) => {
|
const handleResendLink = async (_ev: any) => {
|
||||||
const success: boolean = await resendVerifyEmail({
|
const success: boolean = await resendVerifyEmail({
|
||||||
email: email(),
|
email: email(),
|
||||||
identifier: 'basic_signup',
|
identifier: 'basic_signup'
|
||||||
})
|
})
|
||||||
setIsSuccess(success)
|
setIsSuccess(success)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ export const RegisterForm = () => {
|
||||||
{t('resend confirmation link')}
|
{t('resend confirmation link')}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
)
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ export const RegisterForm = () => {
|
||||||
{t('enter')}
|
{t('enter')}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
)
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ export const RegisterForm = () => {
|
||||||
{t('Set the new password')}
|
{t('Set the new password')}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
)
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ export const RegisterForm = () => {
|
||||||
<AuthModalHeader modalType="register" />
|
<AuthModalHeader modalType="register" />
|
||||||
<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
|
||||||
|
@ -204,7 +204,7 @@ export const RegisterForm = () => {
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email && !emailStatus(),
|
'pretty-form__item--error': validationErrors().email && !emailStatus()
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -250,7 +250,7 @@ export const RegisterForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'login',
|
mode: 'login'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -12,7 +12,7 @@ export const SendEmailConfirm = () => {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
'align-items': 'center',
|
'align-items': 'center',
|
||||||
'justify-content': 'center',
|
'justify-content': 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class={styles.text}>{t('Link sent, check your email')}</div>
|
<div class={styles.text}>{t('Link sent, check your email')}</div>
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const SendResetLinkForm = () => {
|
||||||
try {
|
try {
|
||||||
const result = await forgotPassword({
|
const result = await forgotPassword({
|
||||||
email: email(),
|
email: email(),
|
||||||
redirect_uri: window?.location?.origin || '',
|
redirect_uri: window?.location?.origin || ''
|
||||||
})
|
})
|
||||||
if (result) {
|
if (result) {
|
||||||
setMessage(result || '')
|
setMessage(result || '')
|
||||||
|
@ -91,7 +91,7 @@ export const SendResetLinkForm = () => {
|
||||||
</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
|
||||||
|
@ -112,7 +112,7 @@ export const SendResetLinkForm = () => {
|
||||||
class={'link'}
|
class={'link'}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'register',
|
mode: 'register'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -140,7 +140,7 @@ export const SendResetLinkForm = () => {
|
||||||
class={styles.authLink}
|
class={styles.authLink}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
mode: 'login',
|
mode: 'login'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -35,7 +35,7 @@ const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
|
||||||
'send-reset-link': SendResetLinkForm,
|
'send-reset-link': SendResetLinkForm,
|
||||||
'confirm-email': EmailConfirm,
|
'confirm-email': EmailConfirm,
|
||||||
'send-confirm-email': SendEmailConfirm,
|
'send-confirm-email': SendEmailConfirm,
|
||||||
'change-password': ChangePasswordForm,
|
'change-password': ChangePasswordForm
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthModal = () => {
|
export const AuthModal = () => {
|
||||||
|
@ -60,7 +60,7 @@ export const AuthModal = () => {
|
||||||
ref={(el) => (rootRef = el)}
|
ref={(el) => (rootRef = el)}
|
||||||
class={clsx(styles.view, {
|
class={clsx(styles.view, {
|
||||||
row: !searchParams?.source,
|
row: !searchParams?.source,
|
||||||
[styles.signUp]: mode() === 'register' || mode() === 'confirm-email',
|
[styles.signUp]: mode() === 'register' || mode() === 'confirm-email'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={!searchParams?.source}>
|
<Show when={!searchParams?.source}>
|
||||||
|
@ -73,7 +73,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!')}
|
||||||
|
@ -96,7 +96,7 @@ export const AuthModal = () => {
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.auth, {
|
class={clsx(styles.auth, {
|
||||||
'col-md-12': !searchParams?.source,
|
'col-md-12': !searchParams?.source
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Dynamic component={AUTH_MODAL_MODES[mode() as AuthModalMode]} />
|
<Dynamic component={AUTH_MODAL_MODES[mode() as AuthModalMode]} />
|
||||||
|
|
|
@ -169,7 +169,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
|
||||||
|
@ -324,7 +324,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}>
|
||||||
|
@ -337,7 +337,7 @@ export const Header = (props: Props) => {
|
||||||
<Show when={props.title}>
|
<Show when={props.title}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.articleControls, 'col-auto', {
|
class={clsx(styles.articleControls, 'col-auto', {
|
||||||
[styles.articleControlsAuthorized]: session()?.user?.id,
|
[styles.articleControlsAuthorized]: session()?.user?.id
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<SharePopup
|
<SharePopup
|
||||||
|
|
|
@ -52,7 +52,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
const isSaveButtonVisible = createMemo(() => session()?.access_token && isEditorPage())
|
const isSaveButtonVisible = createMemo(() => session()?.access_token && isEditorPage())
|
||||||
const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
|
const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
|
||||||
const isAuthenticatedControlsVisible = createMemo(
|
const isAuthenticatedControlsVisible = createMemo(
|
||||||
() => session()?.access_token && session()?.user?.email_verified,
|
() => session()?.access_token && session()?.user?.email_verified
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleBurgerButtonClick = () => {
|
const handleBurgerButtonClick = () => {
|
||||||
|
@ -108,7 +108,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.userControlItem,
|
styles.userControlItem,
|
||||||
styles.userControlItemVerbose,
|
styles.userControlItemVerbose,
|
||||||
styles.userControlItemCreate,
|
styles.userControlItemCreate
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<A href={'/create'}>
|
<A href={'/create'}>
|
||||||
|
@ -191,7 +191,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
{renderIconedButton({
|
{renderIconedButton({
|
||||||
value: t('Publish'),
|
value: t('Publish'),
|
||||||
icon: 'publish',
|
icon: 'publish',
|
||||||
action: () => publishShout(form),
|
action: () => publishShout(form)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.userControlItem,
|
styles.userControlItem,
|
||||||
styles.settingsControlContainer,
|
styles.settingsControlContainer,
|
||||||
styles.userControlItemVerbose,
|
styles.userControlItemVerbose
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Popover content={t('Settings')}>
|
<Popover content={t('Settings')}>
|
||||||
|
@ -221,7 +221,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.userControlItem,
|
styles.userControlItem,
|
||||||
styles.userControlItemVerbose,
|
styles.userControlItemVerbose,
|
||||||
styles.userControlItemCreate,
|
styles.userControlItemCreate
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<A href={'/create'}>
|
<A href={'/create'}>
|
||||||
|
@ -249,7 +249,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<Show when={!isSaveButtonVisible()}>
|
<Show when={!isSaveButtonVisible()}>
|
||||||
<div
|
<div
|
||||||
class={clsx(
|
class={clsx(
|
||||||
styles.userControlItem,
|
styles.userControlItem
|
||||||
// styles.userControlItemInbox
|
// styles.userControlItemInbox
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const Modal = (props: Props) => {
|
||||||
<Show when={modal() === props.name}>
|
<Show when={modal() === props.name}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.backdrop, [styles[`modal-${props.name}` as keyof typeof styles]], {
|
class={clsx(styles.backdrop, [styles[`modal-${props.name}` as keyof typeof styles]], {
|
||||||
[styles.isMobile]: props.isResponsive && isPortrait(),
|
[styles.isMobile]: props.isResponsive && isPortrait()
|
||||||
})}
|
})}
|
||||||
onClick={handleHide}
|
onClick={handleHide}
|
||||||
>
|
>
|
||||||
|
@ -42,7 +42,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()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import styles from './SearchModal.module.scss'
|
||||||
const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) =>
|
const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) =>
|
||||||
`<span>${str.replaceAll(
|
`<span>${str.replaceAll(
|
||||||
new RegExp(intersection, 'gi'),
|
new RegExp(intersection, 'gi'),
|
||||||
(casePreservedMatch) => `<span class="blackModeIntersection">${casePreservedMatch}</span>`,
|
(casePreservedMatch) => `<span class="blackModeIntersection">${casePreservedMatch}</span>`
|
||||||
)}</span>`
|
)}</span>`
|
||||||
|
|
||||||
const prepareSearchResults = (list: Shout[], searchValue: string) =>
|
const prepareSearchResults = (list: Shout[], searchValue: string) =>
|
||||||
|
@ -33,15 +33,15 @@ const prepareSearchResults = (list: Shout[], searchValue: string) =>
|
||||||
title: article.title
|
title: article.title
|
||||||
? getSearchCoincidences({
|
? getSearchCoincidences({
|
||||||
str: article.title,
|
str: article.title,
|
||||||
intersection: searchValue,
|
intersection: searchValue
|
||||||
})
|
})
|
||||||
: '',
|
: '',
|
||||||
subtitle: article.subtitle
|
subtitle: article.subtitle
|
||||||
? getSearchCoincidences({
|
? getSearchCoincidences({
|
||||||
str: article.subtitle,
|
str: article.subtitle,
|
||||||
intersection: searchValue,
|
intersection: searchValue
|
||||||
})
|
})
|
||||||
: '',
|
: ''
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const SearchModal = () => {
|
export const SearchModal = () => {
|
||||||
|
@ -60,7 +60,7 @@ export const SearchModal = () => {
|
||||||
const { hasMore, newShouts } = await loadShoutsSearch({
|
const { hasMore, newShouts } = await loadShoutsSearch({
|
||||||
limit: FEED_PAGE_SIZE,
|
limit: FEED_PAGE_SIZE,
|
||||||
text: inputValue(),
|
text: inputValue(),
|
||||||
offset: offset(),
|
offset: offset()
|
||||||
})
|
})
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setOffset(newShouts.length)
|
setOffset(newShouts.length)
|
||||||
|
@ -69,8 +69,8 @@ export const SearchModal = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ssrLoadFrom: 'initial',
|
ssrLoadFrom: 'initial',
|
||||||
initialValue: [],
|
initialValue: []
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let searchEl: HTMLInputElement
|
let searchEl: HTMLInputElement
|
||||||
|
@ -124,7 +124,7 @@ export const SearchModal = () => {
|
||||||
<p
|
<p
|
||||||
class={styles.searchDescription}
|
class={styles.searchDescription}
|
||||||
innerHTML={t(
|
innerHTML={t(
|
||||||
'To find publications, art, comments, authors and topics of interest to you, just start typing your query',
|
'To find publications, art, comments, authors and topics of interest to you, just start typing your query'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ export const SearchModal = () => {
|
||||||
settings={{
|
settings={{
|
||||||
isFloorImportant: true,
|
isFloorImportant: true,
|
||||||
isSingle: true,
|
isSingle: true,
|
||||||
nodate: true,
|
nodate: true
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,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>
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
loadedNotificationsCount,
|
loadedNotificationsCount,
|
||||||
totalNotificationsCount,
|
totalNotificationsCount,
|
||||||
loadNotificationsGrouped,
|
loadNotificationsGrouped,
|
||||||
markSeenAll,
|
markSeenAll
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
const handleHide = () => {
|
const handleHide = () => {
|
||||||
props.onClose()
|
props.onClose()
|
||||||
|
@ -66,7 +66,7 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
useOutsideClickHandler({
|
useOutsideClickHandler({
|
||||||
containerRef: panelRef,
|
containerRef: panelRef,
|
||||||
predicate: () => props.isOpen,
|
predicate: () => props.isOpen,
|
||||||
handler: () => handleHide(),
|
handler: () => handleHide()
|
||||||
})
|
})
|
||||||
|
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
@ -99,13 +99,13 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
|
|
||||||
const yesterdayNotifications = createMemo(() => {
|
const yesterdayNotifications = createMemo(() => {
|
||||||
return sortedNotifications().filter((notification) =>
|
return sortedNotifications().filter((notification) =>
|
||||||
isYesterday(new Date(notification.updated_at * 1000)),
|
isYesterday(new Date(notification.updated_at * 1000))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const earlierNotifications = createMemo(() => {
|
const earlierNotifications = createMemo(() => {
|
||||||
return sortedNotifications().filter((notification) =>
|
return sortedNotifications().filter((notification) =>
|
||||||
isEarlier(new Date(notification.updated_at * 1000)),
|
isEarlier(new Date(notification.updated_at * 1000))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
await loadNotificationsGrouped({
|
await loadNotificationsGrouped({
|
||||||
after: after() || hourAgo(),
|
after: after() || hourAgo(),
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
offset: loadedNotificationsCount(),
|
offset: loadedNotificationsCount()
|
||||||
})
|
})
|
||||||
if (loadedNotificationsCount() < totalNotificationsCount()) {
|
if (loadedNotificationsCount() < totalNotificationsCount()) {
|
||||||
const hasMore = (scrollContainerRef?.scrollHeight || 0) <= (scrollContainerRef?.offsetHeight || 0)
|
const hasMore = (scrollContainerRef?.scrollHeight || 0) <= (scrollContainerRef?.offsetHeight || 0)
|
||||||
|
@ -158,13 +158,13 @@ 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 = el)} class={styles.panel}>
|
<div ref={(el) => (panelRef = el)} class={styles.panel}>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
lazy,
|
lazy,
|
||||||
on,
|
on,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
onMount,
|
onMount
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ export const ProfileSettings = () => {
|
||||||
const isConfirmed = await showConfirm({
|
const isConfirmed = await showConfirm({
|
||||||
confirmBody: t('Do you really want to reset all changes?'),
|
confirmBody: t('Do you really want to reset all changes?'),
|
||||||
confirmButtonVariant: 'primary',
|
confirmButtonVariant: 'primary',
|
||||||
declineButtonVariant: 'secondary',
|
declineButtonVariant: 'secondary'
|
||||||
})
|
})
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
setClearAbout(true)
|
setClearAbout(true)
|
||||||
|
@ -165,7 +165,7 @@ export const ProfileSettings = () => {
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
if (!deepEqual(form, prevForm)) {
|
if (!deepEqual(form, prevForm)) {
|
||||||
event.returnValue = t(
|
event.returnValue = t(
|
||||||
'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?',
|
'There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,8 +181,8 @@ export const ProfileSettings = () => {
|
||||||
if (Object.keys(prevForm).length > 0) {
|
if (Object.keys(prevForm).length > 0) {
|
||||||
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
setIsFloatingPanelVisible(!deepEqual(form, prevForm))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleDeleteSocialLink = (link: string) => {
|
const handleDeleteSocialLink = (link: string) => {
|
||||||
|
@ -221,8 +221,8 @@ export const ProfileSettings = () => {
|
||||||
style={{
|
style={{
|
||||||
'background-image': `url(${getImageUrl(form.pic || '', {
|
'background-image': `url(${getImageUrl(form.pic || '', {
|
||||||
width: 180,
|
width: 180,
|
||||||
height: 180,
|
height: 180
|
||||||
})})`,
|
})})`
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class={styles.controls}>
|
<div class={styles.controls}>
|
||||||
|
@ -265,7 +265,7 @@ export const ProfileSettings = () => {
|
||||||
<h4>{t('Name')}</h4>
|
<h4>{t('Name')}</h4>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{t(
|
{t(
|
||||||
'Your name will appear on your profile page and as your signature in publications, comments and responses.',
|
'Your name will appear on your profile page and as your signature in publications, comments and responses.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
|
|
|
@ -26,7 +26,7 @@ const scrollToHeader = (element: HTMLElement) => {
|
||||||
top:
|
top:
|
||||||
element.getBoundingClientRect().top -
|
element.getBoundingClientRect().top -
|
||||||
document.body.getBoundingClientRect().top -
|
document.body.getBoundingClientRect().top -
|
||||||
DEFAULT_HEADER_OFFSET,
|
DEFAULT_HEADER_OFFSET
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export const TableOfContents = (props: Props) => {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
setHeadings(
|
setHeadings(
|
||||||
// eslint-disable-next-line unicorn/prefer-spread
|
// eslint-disable-next-line unicorn/prefer-spread
|
||||||
Array.from(parent.querySelectorAll<HTMLElement>('h1, h2, h3, h4')),
|
Array.from(parent.querySelectorAll<HTMLElement>('h1, h2, h3, h4'))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,8 +66,8 @@ export const TableOfContents = (props: Props) => {
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.body,
|
() => props.body,
|
||||||
(_) => debouncedUpdateHeadings(),
|
(_) => debouncedUpdateHeadings()
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -83,7 +83,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}>
|
||||||
|
@ -100,7 +100,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) => {
|
||||||
|
@ -119,9 +119,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()
|
||||||
|
@ -157,9 +157,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()
|
||||||
|
|
|
@ -33,7 +33,7 @@ interface TopicProps {
|
||||||
export const TopicCard = (props: TopicProps) => {
|
export const TopicCard = (props: TopicProps) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const title = createMemo(() =>
|
const title = createMemo(() =>
|
||||||
capitalize(lang() === 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
|
capitalize(lang() === 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || '')
|
||||||
)
|
)
|
||||||
const { session, requireAuthentication } = useSession()
|
const { session, requireAuthentication } = useSession()
|
||||||
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||||
|
@ -45,7 +45,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
const followed = follows?.topics?.some((topics) => topics.id === props.topic?.id)
|
const followed = follows?.topics?.some((topics) => topics.id === props.topic?.id)
|
||||||
setIsFollowed(Boolean(followed))
|
setIsFollowed(Boolean(followed))
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleFollowClick = () => {
|
const handleFollowClick = () => {
|
||||||
|
@ -63,7 +63,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
|
||||||
|
@ -74,7 +74,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
props.subscribeButtonBottom ||
|
props.subscribeButtonBottom ||
|
||||||
props.isNarrow ||
|
props.isNarrow ||
|
||||||
props.compact
|
props.compact
|
||||||
),
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={title() && !props.isCardMode}>
|
<Show when={title() && !props.isCardMode}>
|
||||||
|
@ -109,7 +109,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>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const FullTopic = (props: Props) => {
|
||||||
<div class={styles.topicDetailsItem}>
|
<div class={styles.topicDetailsItem}>
|
||||||
<Icon name="feed-all" class={styles.topicDetailsIcon} />
|
<Icon name="feed-all" class={styles.topicDetailsIcon} />
|
||||||
{t('some posts', {
|
{t('some posts', {
|
||||||
count: props.topic?.stat?.shouts ?? 0,
|
count: props.topic?.stat?.shouts ?? 0
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const TopicBadge = (props: Props) => {
|
||||||
const followed = follows?.topics?.some((topics) => topics.id === props.topic?.id)
|
const followed = follows?.topics?.some((topics) => topics.id === props.topic?.id)
|
||||||
setIsFollowed(followed)
|
setIsFollowed(followed)
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleFollowClick = () => {
|
const handleFollowClick = () => {
|
||||||
|
@ -58,11 +58,11 @@ export const TopicBadge = (props: Props) => {
|
||||||
href={`/topic/${props.topic.slug}`}
|
href={`/topic/${props.topic.slug}`}
|
||||||
class={clsx(styles.picture, {
|
class={clsx(styles.picture, {
|
||||||
[styles.withImage]: props.topic.pic,
|
[styles.withImage]: props.topic.pic,
|
||||||
[styles.smallSize]: isMobileView(),
|
[styles.smallSize]: isMobileView()
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
(props.topic?.pic || '') && {
|
(props.topic?.pic || '') && {
|
||||||
'background-image': `url('${getImageUrl(props.topic?.pic || '', { width: 40, height: 40 })}')`,
|
'background-image': `url('${getImageUrl(props.topic?.pic || '', { width: 40, height: 40 })}')`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const AllAuthors = (props: Props) => {
|
||||||
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
|
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
|
||||||
return filteredAuthors().reduce(
|
return filteredAuthors().reduce(
|
||||||
(acc, author) => authorLetterReduce(acc, author, lang()),
|
(acc, author) => authorLetterReduce(acc, author, lang()),
|
||||||
{} as { [letter: string]: Author[] },
|
{} as { [letter: string]: Author[] }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -87,21 +87,21 @@ export const AllAuthors = (props: Props) => {
|
||||||
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
<ul class={clsx(styles.viewSwitcher, 'view-switcher')}>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
['view-switcher__item--selected']: !searchParams?.by || searchParams?.by === 'shouts',
|
['view-switcher__item--selected']: !searchParams?.by || searchParams?.by === 'shouts'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
<a href="/authors?by=shouts">{t('By shouts')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
['view-switcher__item--selected']: searchParams?.by === 'followers',
|
['view-switcher__item--selected']: searchParams?.by === 'followers'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href="/authors?by=followers">{t('By popularity')}</a>
|
<a href="/authors?by=followers">{t('By popularity')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
['view-switcher__item--selected']: searchParams?.by === 'name',
|
['view-switcher__item--selected']: searchParams?.by === 'name'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<a href="/authors?by=name">{t('By name')}</a>
|
<a href="/authors?by=name">{t('By name')}</a>
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Props = {
|
||||||
export const TOPICS_PER_PAGE = 20
|
export const TOPICS_PER_PAGE = 20
|
||||||
export const ABC = {
|
export const ABC = {
|
||||||
ru: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#',
|
ru: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#',
|
||||||
en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#',
|
en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AllTopics = (props: Props) => {
|
export const AllTopics = (props: Props) => {
|
||||||
|
@ -42,7 +42,7 @@ export const AllTopics = (props: Props) => {
|
||||||
acc[letter].push(topic)
|
acc[letter].push(topic)
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
{} as { [letter: string]: Topic[] },
|
{} as { [letter: string]: Topic[] }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ export const AllTopics = (props: Props) => {
|
||||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||||
const ogTitle = t('Themes and plots')
|
const ogTitle = t('Themes and plots')
|
||||||
const description = t(
|
const description = t(
|
||||||
'Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about',
|
'Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about'
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -67,7 +67,7 @@ export const AuthorView = (props: Props) => {
|
||||||
const resp = await query(loadShoutsQuery, {
|
const resp = await query(loadShoutsQuery, {
|
||||||
filters: { author: props.authorSlug },
|
filters: { author: props.authorSlug },
|
||||||
limit: LOAD_MORE_PAGE_SIZE,
|
limit: LOAD_MORE_PAGE_SIZE,
|
||||||
offset: sortedFeed().length,
|
offset: sortedFeed().length
|
||||||
})
|
})
|
||||||
const hasMore = resp?.data?.load_shouts_by?.hasMore
|
const hasMore = resp?.data?.load_shouts_by?.hasMore
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
setIsLoadMoreButtonVisible(hasMore)
|
||||||
|
@ -92,7 +92,7 @@ export const AuthorView = (props: Props) => {
|
||||||
await loadAuthor(s)
|
await loadAuthor(s)
|
||||||
setIsFetching(false) // Сброс состояния загрузки после завершения
|
setIsFetching(false) // Сброс состояния загрузки после завершения
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
// 3 // after fetch loading following data
|
// 3 // after fetch loading following data
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -112,8 +112,8 @@ export const AuthorView = (props: Props) => {
|
||||||
console.info(`[Author] followers for @${slug()} fetched`)
|
console.info(`[Author] followers for @${slug()} fetched`)
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// догружает ленту и комментарии
|
// догружает ленту и комментарии
|
||||||
|
@ -125,14 +125,14 @@ export const AuthorView = (props: Props) => {
|
||||||
await loadMore()
|
await loadMore()
|
||||||
|
|
||||||
const resp = await query(loadReactionsBy, {
|
const resp = await query(loadReactionsBy, {
|
||||||
by: { comment: true, created_by: profile.id },
|
by: { comment: true, created_by: profile.id }
|
||||||
}).toPromise()
|
}).toPromise()
|
||||||
const ccc = resp?.data?.load_reactions_by
|
const ccc = resp?.data?.load_reactions_by
|
||||||
if (ccc) setCommented(ccc)
|
if (ccc) setCommented(ccc)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// { defer: true },
|
// { defer: true },
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let bioContainerRef: HTMLDivElement
|
let bioContainerRef: HTMLDivElement
|
||||||
|
@ -149,13 +149,13 @@ export const AuthorView = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const pages = createMemo<Shout[][]>(() =>
|
const pages = createMemo<Shout[][]>(() =>
|
||||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE),
|
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||||
)
|
)
|
||||||
|
|
||||||
const ogImage = createMemo(() =>
|
const ogImage = createMemo(() =>
|
||||||
author()?.pic
|
author()?.pic
|
||||||
? getImageUrl(author()?.pic || '', { width: 1200 })
|
? getImageUrl(author()?.pic || '', { width: 1200 })
|
||||||
: getImageUrl('production/image/logo_image.png'),
|
: getImageUrl('production/image/logo_image.png')
|
||||||
)
|
)
|
||||||
const description = createMemo(() => getDescription(author()?.bio || ''))
|
const description = createMemo(() => getDescription(author()?.bio || ''))
|
||||||
const handleDeleteComment = (id: number) => {
|
const handleDeleteComment = (id: number) => {
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { useLocalize } from '~/context/localize'
|
||||||
|
|
||||||
export const ConnectView = () => {
|
export const ConnectView = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const [state, setState] = createSignal<'initial' | 'loading' | 'success' | 'error'>('initial')
|
const [state, setState] = createSignal<'initial' | 'loading' | 'success' | 'error'>('initial')
|
||||||
let formRef: HTMLFormElement | null
|
let formRef: HTMLFormElement | null = null
|
||||||
|
|
||||||
const handleFormSubmit = async (e: SubmitEvent) => {
|
const handleFormSubmit = async (e: SubmitEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setState('loading')
|
setState('loading')
|
||||||
|
@ -14,51 +14,53 @@ export const ConnectView = () => {
|
||||||
const postData = formRef
|
const postData = formRef
|
||||||
? Array.from(formRef.elements).reduce(
|
? Array.from(formRef.elements).reduce(
|
||||||
(acc, element) => {
|
(acc, element) => {
|
||||||
const formField = element as unknown as { name: string; value: string }
|
const formField = element as HTMLInputElement
|
||||||
if (formField.name) {
|
if (formField.name) {
|
||||||
acc[formField.name] = formField.value
|
acc[formField.name] = formField.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
{} as Record<string, string>,
|
{} as Record<string, string>
|
||||||
)
|
)
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
const requestOptions = {
|
const requestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(postData),
|
body: JSON.stringify(postData)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await fetch('/api/feedback', requestOptions)
|
try {
|
||||||
|
const result = await fetch('/api/feedback', requestOptions)
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
console.error('[handleFormSubmit]', result)
|
console.error('[handleFormSubmit]', result)
|
||||||
|
setState('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setState('success')
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[handleFormSubmit]', error)
|
||||||
setState('error')
|
setState('error')
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setState('success')
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article class="wide-container container--static-page">
|
<article class="wide-container container--static-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-20 col-md-16 col-lg-14 col-xl-12 offset-md-5">
|
<div class="col-sm-20 col-md-16 col-lg-14 col-xl-12 offset-md-5">
|
||||||
<Show when={state() === 'loading' || state() === 'initial' || state() === 'error'}>
|
<Show when={state() !== 'success'}>
|
||||||
<h1>
|
<h1>
|
||||||
<span class="wrapped">{t('Suggest an idea')}</span>
|
<span class="wrapped">{t('Suggest an idea')}</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{t(
|
{t(
|
||||||
'Want to suggest, discuss or advise something? Share a topic or an idea? Please send us a message!',
|
'Want to suggest, discuss or advise something? Share a topic or an idea? Please send us a message!'
|
||||||
)}
|
)}
|
||||||
{t('Specify your e-mail and we will reply.')}
|
{t('Specify your e-mail and we will reply.')}
|
||||||
</p>
|
</p>
|
||||||
|
@ -97,7 +99,10 @@ export const ConnectView = () => {
|
||||||
<br />
|
<br />
|
||||||
{t('Something went wrong, please try again')}
|
{t('Something went wrong, please try again')}
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={state() === 'success'}>{t('Thank you for reaching us')}!</Show>
|
<Show when={state() === 'success'}>
|
||||||
|
<br />
|
||||||
|
{t('Thank you for reaching us')}!
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
|
@ -35,8 +35,8 @@ export const DraftsView = () => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const { publishShoutById, deleteShout } = useEditorContext()
|
const { publishShoutById, deleteShout } = useEditorContext()
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
lazy,
|
lazy,
|
||||||
on,
|
on,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
onMount,
|
onMount
|
||||||
} from 'solid-js'
|
} from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import { debounce } from 'throttle-debounce'
|
import { debounce } from 'throttle-debounce'
|
||||||
|
@ -50,7 +50,7 @@ type Props = {
|
||||||
export const MAX_HEADER_LIMIT = 100
|
export const MAX_HEADER_LIMIT = 100
|
||||||
export const EMPTY_TOPIC: Topic = {
|
export const EMPTY_TOPIC: Topic = {
|
||||||
id: -1,
|
id: -1,
|
||||||
slug: '',
|
slug: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTO_SAVE_DELAY = 3000
|
const AUTO_SAVE_DELAY = 3000
|
||||||
|
@ -59,7 +59,7 @@ const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => {
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ export const EditView = (props: Props) => {
|
||||||
setFormErrors,
|
setFormErrors,
|
||||||
saveDraft,
|
saveDraft,
|
||||||
saveDraftToLocalStorage,
|
saveDraftToLocalStorage,
|
||||||
getDraftFromLocalStorage,
|
getDraftFromLocalStorage
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
const [shoutTopics, setShoutTopics] = createSignal<Topic[]>([])
|
const [shoutTopics, setShoutTopics] = createSignal<Topic[]>([])
|
||||||
const [draft, setDraft] = createSignal()
|
const [draft, setDraft] = createSignal()
|
||||||
|
@ -112,15 +112,15 @@ export const EditView = (props: Props) => {
|
||||||
body: shout.body || '',
|
body: shout.body || '',
|
||||||
coverImageUrl: shout.cover || '',
|
coverImageUrl: shout.cover || '',
|
||||||
media: shout.media || '',
|
media: shout.media || '',
|
||||||
layout: shout.layout,
|
layout: shout.layout
|
||||||
}
|
}
|
||||||
setForm((_) => draftForm)
|
setForm((_) => draftForm)
|
||||||
console.debug('draft from props data: ', draftForm)
|
console.debug('draft from props data: ', draftForm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -133,8 +133,8 @@ export const EditView = (props: Props) => {
|
||||||
console.debug('draft from localstorage: ', draftForm)
|
console.debug('draft from localstorage: ', draftForm)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
|
@ -153,8 +153,8 @@ export const EditView = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -170,7 +170,7 @@ export const EditView = (props: Props) => {
|
||||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||||
if (!deepEqual(prevForm, form)) {
|
if (!deepEqual(prevForm, form)) {
|
||||||
event.returnValue = t(
|
event.returnValue = t(
|
||||||
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?',
|
'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ export const EditView = (props: Props) => {
|
||||||
const [baseAudioFields, setBaseAudioFields] = createSignal({
|
const [baseAudioFields, setBaseAudioFields] = createSignal({
|
||||||
artist: '',
|
artist: '',
|
||||||
date: '',
|
date: '',
|
||||||
genre: '',
|
genre: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleBaseFieldsChange = (key: string, value: string) => {
|
const handleBaseFieldsChange = (key: string, value: string) => {
|
||||||
|
@ -281,7 +281,7 @@ export const EditView = (props: Props) => {
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<button
|
<button
|
||||||
class={clsx(styles.scrollTopButton, {
|
class={clsx(styles.scrollTopButton, {
|
||||||
[styles.visible]: isScrolled(),
|
[styles.visible]: isScrolled()
|
||||||
})}
|
})}
|
||||||
onClick={handleScrollTopButtonClick}
|
onClick={handleScrollTopButtonClick}
|
||||||
>
|
>
|
||||||
|
@ -404,8 +404,8 @@ export const EditView = (props: Props) => {
|
||||||
class={styles.cover}
|
class={styles.cover}
|
||||||
style={{
|
style={{
|
||||||
'background-image': `url(${getImageUrl(form.coverImageUrl || '', {
|
'background-image': `url(${getImageUrl(form.coverImageUrl || '', {
|
||||||
width: 1600,
|
width: 1600
|
||||||
})})`,
|
})})`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Popover content={t('Delete cover')}>
|
<Popover content={t('Delete cover')}>
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const Expo = (props: Props) => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: getLoadShoutsFilters(),
|
filters: getLoadShoutsFilters(),
|
||||||
limit: count,
|
limit: count,
|
||||||
offset: expoShouts().length,
|
offset: expoShouts().length
|
||||||
}
|
}
|
||||||
|
|
||||||
options.filters = props.layout
|
options.filters = props.layout
|
||||||
|
@ -76,7 +76,7 @@ export const Expo = (props: Props) => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: { ...getLoadShoutsFilters(), featured: true },
|
filters: { ...getLoadShoutsFilters(), featured: true },
|
||||||
limit: 10,
|
limit: 10,
|
||||||
random_limit: 100,
|
random_limit: 100
|
||||||
}
|
}
|
||||||
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
||||||
setFavoriteTopArticles(resp?.data?.load_shouts_random_top || [])
|
setFavoriteTopArticles(resp?.data?.load_shouts_random_top || [])
|
||||||
|
@ -89,7 +89,7 @@ export const Expo = (props: Props) => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: { ...getLoadShoutsFilters({ after }), reacted: true },
|
filters: { ...getLoadShoutsFilters({ after }), reacted: true },
|
||||||
limit: 10,
|
limit: 10,
|
||||||
random_limit: 10,
|
random_limit: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
||||||
|
@ -114,8 +114,8 @@ export const Expo = (props: Props) => {
|
||||||
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
|
loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE)
|
||||||
loadRandomTopArticles()
|
loadRandomTopArticles()
|
||||||
loadRandomTopMonthArticles()
|
loadRandomTopMonthArticles()
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
|
|
|
@ -89,12 +89,12 @@ export const FeedView = (props: Props) => {
|
||||||
const periods: PeriodItem[] = [
|
const periods: PeriodItem[] = [
|
||||||
{ value: 'week', title: t('This week') },
|
{ value: 'week', title: t('This week') },
|
||||||
monthPeriod,
|
monthPeriod,
|
||||||
{ value: 'year', title: t('This year') },
|
{ value: 'year', title: t('This year') }
|
||||||
]
|
]
|
||||||
|
|
||||||
const visibilities: VisibilityItem[] = [
|
const visibilities: VisibilityItem[] = [
|
||||||
{ value: 'community', title: t('All') },
|
{ value: 'community', title: t('All') },
|
||||||
{ value: 'featured', title: t('Published') },
|
{ value: 'featured', title: t('Published') }
|
||||||
]
|
]
|
||||||
const { query } = useGraphQL()
|
const { query } = useGraphQL()
|
||||||
const [searchParams, changeSearchParams] = useSearchParams<FeedSearchParams>()
|
const [searchParams, changeSearchParams] = useSearchParams<FeedSearchParams>()
|
||||||
|
@ -152,8 +152,8 @@ export const FeedView = (props: Props) => {
|
||||||
([s, seen]) => {
|
([s, seen]) => {
|
||||||
if (s?.access_token && !(seen?.length > 0)) loadUnratedArticles()
|
if (s?.access_token && !(seen?.length > 0)) loadUnratedArticles()
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: declare some details
|
// TODO: declare some details
|
||||||
|
@ -164,14 +164,14 @@ export const FeedView = (props: Props) => {
|
||||||
setSearchResults([])
|
setSearchResults([])
|
||||||
loadMore()
|
loadMore()
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const loadFeedShouts = () => {
|
const loadFeedShouts = () => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
limit: FEED_PAGE_SIZE,
|
limit: FEED_PAGE_SIZE,
|
||||||
offset: sortedFeed()?.length || 0,
|
offset: sortedFeed()?.length || 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams?.by) {
|
if (searchParams?.by) {
|
||||||
|
@ -185,7 +185,7 @@ export const FeedView = (props: Props) => {
|
||||||
} else if (visibilityMode) {
|
} else if (visibilityMode) {
|
||||||
options.filters = {
|
options.filters = {
|
||||||
...options.filters,
|
...options.filters,
|
||||||
featured: visibilityMode === 'featured',
|
featured: visibilityMode === 'featured'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,8 +205,8 @@ export const FeedView = (props: Props) => {
|
||||||
|
|
||||||
loadReactionsBy({
|
loadReactionsBy({
|
||||||
by: {
|
by: {
|
||||||
shouts: newShouts.map((s) => s.slug),
|
shouts: newShouts.map((s) => s.slug)
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
setIsLoadMoreButtonVisible(hasMore)
|
||||||
|
@ -214,7 +214,7 @@ export const FeedView = (props: Props) => {
|
||||||
|
|
||||||
const ogImage = getImageUrl('production/image/logo_image.png')
|
const ogImage = getImageUrl('production/image/logo_image.png')
|
||||||
const description = t(
|
const description = t(
|
||||||
'Independent media project about culture, science, art and society with horizontal editing',
|
'Independent media project about culture, science, art and society with horizontal editing'
|
||||||
)
|
)
|
||||||
const ogTitle = t('Feed')
|
const ogTitle = t('Feed')
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ export const FeedView = (props: Props) => {
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected':
|
'view-switcher__item--selected':
|
||||||
searchParams?.by === 'publish_date' || !searchParams?.by,
|
searchParams?.by === 'publish_date' || !searchParams?.by
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<A href={loc.pathname}>{t('Recent')}</A>
|
<A href={loc.pathname}>{t('Recent')}</A>
|
||||||
|
@ -262,7 +262,7 @@ export const FeedView = (props: Props) => {
|
||||||
{/*</li>*/}
|
{/*</li>*/}
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': searchParams?.by === 'likes',
|
'view-switcher__item--selected': searchParams?.by === 'likes'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
|
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
|
||||||
|
@ -271,7 +271,7 @@ export const FeedView = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': searchParams?.by === 'last_comment',
|
'view-switcher__item--selected': searchParams?.by === 'last_comment'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
|
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
|
||||||
|
|
|
@ -49,22 +49,22 @@ export const HomeView = (props: HomeViewProps) => {
|
||||||
const shoutsByTopicLoader = loadShouts({
|
const shoutsByTopicLoader = loadShouts({
|
||||||
filters: { topic: topic.slug, featured: true },
|
filters: { topic: topic.slug, featured: true },
|
||||||
limit: 5,
|
limit: 5,
|
||||||
offset: 0,
|
offset: 0
|
||||||
})
|
})
|
||||||
const shouts = await shoutsByTopicLoader()
|
const shouts = await shoutsByTopicLoader()
|
||||||
setRandomTopicArticles(shouts || [])
|
setRandomTopicArticles(shouts || [])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true }
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const pages = createMemo<Shout[][]>(() =>
|
const pages = createMemo<Shout[][]>(() =>
|
||||||
splitToPages(
|
splitToPages(
|
||||||
props.featuredShouts || [],
|
props.featuredShouts || [],
|
||||||
SHOUTS_PER_PAGE + CLIENT_LOAD_ARTICLES_COUNT,
|
SHOUTS_PER_PAGE + CLIENT_LOAD_ARTICLES_COUNT,
|
||||||
LOAD_MORE_PAGE_SIZE,
|
LOAD_MORE_PAGE_SIZE
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type {
|
||||||
Chat,
|
Chat,
|
||||||
ChatMember,
|
ChatMember,
|
||||||
Message as MessageType,
|
Message as MessageType,
|
||||||
MutationCreate_MessageArgs,
|
MutationCreate_MessageArgs
|
||||||
} from '../../../graphql/schema/chat.gen'
|
} from '../../../graphql/schema/chat.gen'
|
||||||
import type { Author } from '../../../graphql/schema/core.gen'
|
import type { Author } from '../../../graphql/schema/core.gen'
|
||||||
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
|
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
|
||||||
|
@ -68,7 +68,7 @@ export const InboxView = (props: Props) => {
|
||||||
const handleOpenChat = async (chat: Chat) => {
|
const handleOpenChat = async (chat: Chat) => {
|
||||||
setCurrentDialog(chat)
|
setCurrentDialog(chat)
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
chat: chat.id,
|
chat: chat.id
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
const mmm = await getMessages?.(chat.id)
|
const mmm = await getMessages?.(chat.id)
|
||||||
|
@ -80,7 +80,7 @@ export const InboxView = (props: Props) => {
|
||||||
} finally {
|
} finally {
|
||||||
messagesContainerRef?.scroll({
|
messagesContainerRef?.scroll({
|
||||||
top: messagesContainerRef?.scrollHeight,
|
top: messagesContainerRef?.scrollHeight,
|
||||||
behavior: 'instant',
|
behavior: 'instant'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ export const InboxView = (props: Props) => {
|
||||||
sendMessage?.({
|
sendMessage?.({
|
||||||
body: message,
|
body: message,
|
||||||
reply_to: messageToReply()?.id,
|
reply_to: messageToReply()?.id,
|
||||||
chat_id: currentDialog()?.id || '',
|
chat_id: currentDialog()?.id || ''
|
||||||
} as MutationCreate_MessageArgs)
|
} as MutationCreate_MessageArgs)
|
||||||
setClear(true)
|
setClear(true)
|
||||||
setMessageToReply(null)
|
setMessageToReply(null)
|
||||||
|
@ -111,7 +111,7 @@ export const InboxView = (props: Props) => {
|
||||||
await loadChats()
|
await loadChats()
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: undefined,
|
initChat: undefined,
|
||||||
chat: newChat.chat.id,
|
chat: newChat.chat.id
|
||||||
})
|
})
|
||||||
const chatToOpen = chats().find((chat) => chat.id === newChat.chat.id)
|
const chatToOpen = chats().find((chat) => chat.id === newChat.chat.id)
|
||||||
await handleOpenChat(chatToOpen as Chat)
|
await handleOpenChat(chatToOpen as Chat)
|
||||||
|
@ -148,11 +148,11 @@ export const InboxView = (props: Props) => {
|
||||||
if (messagesContainerRef) {
|
if (messagesContainerRef) {
|
||||||
messagesContainerRef?.scroll({
|
messagesContainerRef?.scroll({
|
||||||
top: messagesContainerRef.scrollHeight,
|
top: messagesContainerRef.scrollHeight,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
const handleScrollMessageContainer = () => {
|
const handleScrollMessageContainer = () => {
|
||||||
if (
|
if (
|
||||||
|
@ -167,7 +167,7 @@ export const InboxView = (props: Props) => {
|
||||||
const handleScrollToNew = () => {
|
const handleScrollToNew = () => {
|
||||||
messagesContainerRef?.scroll({
|
messagesContainerRef?.scroll({
|
||||||
top: messagesContainerRef?.scrollHeight,
|
top: messagesContainerRef?.scrollHeight,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
setIsScrollToNewVisible(false)
|
setIsScrollToNewVisible(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const ProfileSubscriptions = () => {
|
||||||
} else {
|
} else {
|
||||||
setFiltered(flat)
|
setFiltered(flat)
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -61,7 +61,7 @@ export const ProfileSubscriptions = () => {
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'all',
|
'view-switcher__item--selected': followsFilter() === 'all'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('all')}>
|
<button type="button" onClick={() => setFollowsFilter('all')}>
|
||||||
|
@ -70,7 +70,7 @@ export const ProfileSubscriptions = () => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'authors',
|
'view-switcher__item--selected': followsFilter() === 'authors'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
<button type="button" onClick={() => setFollowsFilter('authors')}>
|
||||||
|
@ -79,7 +79,7 @@ export const ProfileSubscriptions = () => {
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': followsFilter() === 'topics',
|
'view-switcher__item--selected': followsFilter() === 'topics'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
<button type="button" onClick={() => setFollowsFilter('topics')}>
|
||||||
|
|
|
@ -36,7 +36,7 @@ const shorten = (str: string, maxLen: number) => {
|
||||||
|
|
||||||
const EMPTY_TOPIC: Topic = {
|
const EMPTY_TOPIC: Topic = {
|
||||||
id: -1,
|
id: -1,
|
||||||
slug: '',
|
slug: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormConfig {
|
interface FormConfig {
|
||||||
|
@ -56,7 +56,7 @@ const emptyConfig: FormConfig = {
|
||||||
title: '',
|
title: '',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
description: '',
|
description: '',
|
||||||
selectedTopics: [],
|
selectedTopics: []
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PublishSettings = (props: Props) => {
|
export const PublishSettings = (props: Props) => {
|
||||||
|
@ -85,7 +85,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
title: props.form?.title || '',
|
title: props.form?.title || '',
|
||||||
subtitle: props.form?.subtitle || '',
|
subtitle: props.form?.subtitle || '',
|
||||||
description: composeDescription() || '',
|
description: composeDescription() || '',
|
||||||
selectedTopics: [],
|
selectedTopics: []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
setSettingsForm((prev) => {
|
setSettingsForm((prev) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
mainTopic: newSelectedTopics[0],
|
mainTopic: newSelectedTopics[0]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardCoverContainer, {
|
class={clsx(styles.shoutCardCoverContainer, {
|
||||||
[styles.hasImage]: settingsForm.coverImageUrl,
|
[styles.hasImage]: settingsForm.coverImageUrl
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={settingsForm.coverImageUrl ?? initialData().coverImageUrl}>
|
<Show when={settingsForm.coverImageUrl ?? initialData().coverImageUrl}>
|
||||||
|
@ -193,7 +193,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{t(
|
{t(
|
||||||
'Choose a title image for the article. You can immediately see how the publication card will look like.',
|
'Choose a title image for the article. You can immediately see how the publication card will look like.'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ export const PublishSettings = (props: Props) => {
|
||||||
<h4>{t('Topics')}</h4>
|
<h4>{t('Topics')}</h4>
|
||||||
<p class="description">
|
<p class="description">
|
||||||
{t(
|
{t(
|
||||||
'Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title',
|
'Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title'
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div class={styles.inputContainer}>
|
<div class={styles.inputContainer}>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const SearchView = (props: Props) => {
|
||||||
const { hasMore } = await loadShoutsSearch({
|
const { hasMore } = await loadShoutsSearch({
|
||||||
text: query(),
|
text: query(),
|
||||||
offset: offset(),
|
offset: offset(),
|
||||||
limit: LOAD_MORE_PAGE_SIZE,
|
limit: LOAD_MORE_PAGE_SIZE
|
||||||
})
|
})
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
setIsLoadMoreButtonVisible(hasMore)
|
||||||
setOffset(offset() + LOAD_MORE_PAGE_SIZE)
|
setOffset(offset() + LOAD_MORE_PAGE_SIZE)
|
||||||
|
@ -76,14 +76,14 @@ export const SearchView = (props: Props) => {
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li
|
<li
|
||||||
classList={{
|
classList={{
|
||||||
'view-switcher__item--selected': searchParams?.by === 'relevance',
|
'view-switcher__item--selected': searchParams?.by === 'relevance'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a href="?by=relevance">{t('By relevance')}</a>
|
<a href="?by=relevance">{t('By relevance')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
classList={{
|
classList={{
|
||||||
'view-switcher__item--selected': searchParams?.by === 'rating',
|
'view-switcher__item--selected': searchParams?.by === 'rating'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a href="?by=rating">{t('Top rated')}</a>
|
<a href="?by=rating">{t('Top rated')}</a>
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const TopicView = (props: Props) => {
|
||||||
await loadTopicAuthors()
|
await loadTopicAuthors()
|
||||||
loadRandom()
|
loadRandom()
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const [followers, setFollowers] = createSignal<Author[]>(props.followers || [])
|
const [followers, setFollowers] = createSignal<Author[]>(props.followers || [])
|
||||||
|
@ -84,7 +84,7 @@ export const TopicView = (props: Props) => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: { featured: true, topic: topic },
|
filters: { featured: true, topic: topic },
|
||||||
limit: 10,
|
limit: 10,
|
||||||
random_limit: 100,
|
random_limit: 100
|
||||||
}
|
}
|
||||||
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
const resp = await query(getRandomTopShoutsQuery, { options }).toPromise()
|
||||||
setFavoriteTopArticles(resp?.data?.l)
|
setFavoriteTopArticles(resp?.data?.l)
|
||||||
|
@ -97,7 +97,7 @@ export const TopicView = (props: Props) => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: { after: after, featured: true, topic: topic },
|
filters: { after: after, featured: true, topic: topic },
|
||||||
limit: 10,
|
limit: 10,
|
||||||
random_limit: 10,
|
random_limit: 10
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await query(loadShoutsRandomQuery, { options }).toPromise()
|
const resp = await query(loadShoutsRandomQuery, { options }).toPromise()
|
||||||
|
@ -117,8 +117,8 @@ export const TopicView = (props: Props) => {
|
||||||
lang() === 'en'
|
lang() === 'en'
|
||||||
? (topic() as Topic)?.slug.replace(/-/, ' ')
|
? (topic() as Topic)?.slug.replace(/-/, ' ')
|
||||||
: (topic() as Topic)?.title || (topic() as Topic)?.slug.replace(/-/, ' '),
|
: (topic() as Topic)?.title || (topic() as Topic)?.slug.replace(/-/, ' '),
|
||||||
true,
|
true
|
||||||
)}`,
|
)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
|
@ -127,7 +127,7 @@ export const TopicView = (props: Props) => {
|
||||||
const { hasMore } = await loadShouts({
|
const { hasMore } = await loadShouts({
|
||||||
filters: { topic: topic()?.slug },
|
filters: { topic: topic()?.slug },
|
||||||
limit: LOAD_MORE_PAGE_SIZE,
|
limit: LOAD_MORE_PAGE_SIZE,
|
||||||
offset: sortedFeed().length, // FIXME: use feedByTopic
|
offset: sortedFeed().length // FIXME: use feedByTopic
|
||||||
})
|
})
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
setIsLoadMoreButtonVisible(hasMore)
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ export const TopicView = (props: Props) => {
|
||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
const pages = createMemo<Shout[][]>(() =>
|
const pages = createMemo<Shout[][]>(() =>
|
||||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE),
|
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||||
)
|
)
|
||||||
|
|
||||||
const ogImage = () =>
|
const ogImage = () =>
|
||||||
|
@ -181,14 +181,14 @@ export const TopicView = (props: Props) => {
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li
|
<li
|
||||||
classList={{
|
classList={{
|
||||||
'view-switcher__item--selected': searchParams?.by === 'recent' || !searchParams?.by,
|
'view-switcher__item--selected': searchParams?.by === 'recent' || !searchParams?.by
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
by: 'recent',
|
by: 'recent'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -37,9 +37,9 @@ export const Button = (props: Props) => {
|
||||||
styles[props.variant ?? 'primary'],
|
styles[props.variant ?? 'primary'],
|
||||||
{
|
{
|
||||||
[styles.loading]: props.loading,
|
[styles.loading]: props.loading,
|
||||||
[styles.subscribeButton]: props.isSubscribeButton,
|
[styles.subscribeButton]: props.isSubscribeButton
|
||||||
},
|
},
|
||||||
props.class,
|
props.class
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.value}
|
{props.value}
|
||||||
|
|
|
@ -72,14 +72,14 @@ export const DropArea = (props: Props) => {
|
||||||
|
|
||||||
const { selectFiles } = createFileUploader({
|
const { selectFiles } = createFileUploader({
|
||||||
multiple: true,
|
multiple: true,
|
||||||
accept: `${props.fileType}/*`,
|
accept: `${props.fileType}/*`
|
||||||
})
|
})
|
||||||
|
|
||||||
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
|
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
|
||||||
onDrop: async () => {
|
onDrop: async () => {
|
||||||
setDragActive(false)
|
setDragActive(false)
|
||||||
await initUpload(droppedFiles())
|
await initUpload(droppedFiles())
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const DropDown = <TOption extends Option = Option>(props: Props<TOption>)
|
||||||
{props.currentOption.title}{' '}
|
{props.currentOption.title}{' '}
|
||||||
<Chevron
|
<Chevron
|
||||||
class={clsx(styles.chevron, {
|
class={clsx(styles.chevron, {
|
||||||
[styles.rotate]: isPopupVisible(),
|
[styles.rotate]: isPopupVisible()
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@ export const DropDown = <TOption extends Option = Option>(props: Props<TOption>)
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class={clsx(popupStyles.action, {
|
class={clsx(popupStyles.action, {
|
||||||
[styles.active]: props.currentOption.value === option.value,
|
[styles.active]: props.currentOption.value === option.value
|
||||||
})}
|
})}
|
||||||
onClick={() => props.onChange(option)}
|
onClick={() => props.onChange(option)}
|
||||||
>
|
>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user