Compare commits

...

27 Commits

Author SHA1 Message Date
d518d5c2bc trig-deploy
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 18s
2024-02-27 15:44:25 +03:00
8fbcde234e footer-fix 2024-02-24 13:50:14 +03:00
93f6a1b080 small-footer
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 18s
2024-02-24 11:22:41 +03:00
3a71161da9 Merge branch 'dev' into feature/email-templates 2024-02-24 11:20:23 +03:00
02067ace1f no-comment
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 17s
2024-02-23 11:31:08 +03:00
79518e07f2 email-footer-fix
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 18s
2024-02-23 11:24:01 +03:00
424537c513 Merge branch 'dev' of github.com:Discours/discoursio-webapp into feature/email-templates 2024-02-23 11:22:16 +03:00
0b4fa8bfa3 lockfix 2024-02-17 16:35:10 +03:00
d7680ea396 Merge branch 'dev' into feature/email-templates
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 20s
2024-02-17 16:29:35 +03:00
1a393c75c5 linter-wider
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 21s
2024-02-16 22:53:42 +03:00
7f85c543ed vite-fix
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 23s
2024-02-16 22:46:57 +03:00
52b2a6d16c fixed-reset-template 2024-02-16 22:11:04 +03:00
59db2c598d Merge branch 'dev' into feature/email-templates 2024-02-16 21:56:28 +03:00
21e0f4f5da Merge remote-tracking branch 'hub/dev' into feature/email-templates 2024-02-15 21:34:33 +03:00
39e2e37a26 ci-fix-5
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 18s
2024-02-08 02:25:34 +03:00
5e2cec5b5d ci-fix-3
Some checks failed
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Failing after 12s
2024-02-08 02:23:18 +03:00
cf37edaeca ci-fix-2
Some checks failed
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Failing after 12s
2024-02-08 02:18:04 +03:00
4055f2c3fc ci-fix
Some checks failed
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Failing after 3s
2024-02-08 02:16:42 +03:00
f0584b8aff revert
Some checks failed
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Failing after 2s
2024-02-08 02:14:19 +03:00
8bf1dab381 -traled 2024-02-08 02:12:10 +03:00
3c8807da21 Remove space in text
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 9s
2024-02-07 23:45:30 +03:00
d65aea5fb0 Merge branch 'feature/email-templates' of github.com:Discours/discoursio-webapp into feature/email-templates 2024-02-07 23:42:07 +03:00
90c4d93872 Fixed links
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 9s
2024-02-07 23:40:37 +03:00
d9fe833d2e Add links addresses 2024-02-07 23:26:57 +03:00
86ee656a3a ci-update
All checks were successful
deploy / test (push) Has been skipped
deploy / Update templates on Mailgun (push) Successful in 8s
2024-02-07 00:15:54 +03:00
0118cf42c6 Removed empty braces 2024-02-06 23:41:15 +03:00
6d3f7ceffe Email templates
Some checks failed
deploy / test (push) Successful in 2m10s
deploy / Update templates on Mailgun (push) Failing after 6s
2024-02-05 23:17:56 +03:00
185 changed files with 1302 additions and 1416 deletions

View File

@ -5,6 +5,7 @@ on: [push]
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.ref != 'refs/heads/feature/email-templates'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
@ -35,37 +36,42 @@ jobs:
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates' if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
continue-on-error: true continue-on-error: true
steps: steps:
- name: Checkout - uses: actions/checkout@v3
uses: actions/checkout@v2 - uses: actions/setup-node@v3
with:
node-version: '18'
- name: "Email confirmation template" - name: Run templates build
run: npm run templates
- name: "authorizer_email_confirmation template"
uses: gyto/mailgun-template-action@v2 uses: gyto/mailgun-template-action@v2
with: with:
html-file: "./templates/authorizer_email_confirmation.html" html-file: "./templates/dist/authorizer_email_confirmation.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io" mailgun-domain: "discours.io"
mailgun-template: "authorizer_email_confirmation" mailgun-template: "authorizer_email_confirmation"
- name: "Password reset template" - name: "authorizer_password_reset template"
uses: gyto/mailgun-template-action@v2 uses: gyto/mailgun-template-action@v2
with: with:
html-file: "./templates/authorizer_password_reset.html" html-file: "./templates/dist/authorizer_password_reset.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io" mailgun-domain: "discours.io"
mailgun-template: "authorizer_password_reset" mailgun-template: "authorizer_password_reset"
- name: "First publication notification" - name: "email_first_publication template deploy"
uses: gyto/mailgun-template-action@v2 uses: gyto/mailgun-template-action@v2
with: with:
html-file: "./templates/first_publication_notification.html" html-file: "./templates/dist/authorizer_first_publication.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io" mailgun-domain: "discours.io"
mailgun-template: "first_publication_notification" mailgun-template: "email_first_publication"
- name: "New comment notification template" - name: "new_comment_notification template"
uses: gyto/mailgun-template-action@v2 uses: gyto/mailgun-template-action@v2
with: with:
html-file: "./templates/new_comment_notification.html" html-file: "./templates/dist/authorizer_new_comment.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }} mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io" mailgun-domain: "discours.io"
mailgun-template: "new_comment_notification" mailgun-template: "new_comment_notification"

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ bun.lockb
/blob-report/ /blob-report/
/playwright/.cache/ /playwright/.cache/
/plawright-report/ /plawright-report/
/templates/dist/*

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"files": { "files": {
"include": ["*.tsx", "*.ts", "*.js", "*.json"], "include": ["*.tsx", "*.ts", "*.js", "*.json"],
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.d.ts"] "ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "templates"]
}, },
"vcs": { "vcs": {
"defaultBranch": "dev", "defaultBranch": "dev",
@ -22,7 +22,7 @@
"formatter": { "formatter": {
"semicolons": "asNeeded", "semicolons": "asNeeded",
"quoteStyle": "single", "quoteStyle": "single",
"trailingComma": "all", "trailingComma": "none",
"enabled": true, "enabled": true,
"jsxQuoteStyle": "double", "jsxQuoteStyle": "double",
"arrowParentheses": "always" "arrowParentheses": "always"

20
package-lock.json generated
View File

@ -93,7 +93,7 @@
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"prosemirror-history": "1.3.2", "prosemirror-history": "1.3.2",
"prosemirror-trailing-node": "2.0.7", "prosemirror-trailing-node": "2.0.7",
"prosemirror-view": "1.32.7", "prosemirror-view": "1.33.1",
"rollup": "4.11.0", "rollup": "4.11.0",
"sass": "1.69.5", "sass": "1.69.5",
"solid-js": "1.8.15", "solid-js": "1.8.15",
@ -5858,9 +5858,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.672", "version": "1.4.673",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.672.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.673.tgz",
"integrity": "sha512-YYCy+goe3UqZqa3MOQCI5Mx/6HdBLzXL/mkbGCEWL3sP3Z1BP9zqAzeD3YEmLZlespYGFtyM8tRp5i2vfaUGCA==", "integrity": "sha512-zjqzx4N7xGdl5468G+vcgzDhaHkaYgVcf9MqgexcTqsl2UHSCmOj/Bi3HAprg4BZCpC7HyD8a6nZl6QAZf72gw==",
"dev": true "dev": true
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@ -10142,9 +10142,9 @@
} }
}, },
"node_modules/prosemirror-view": { "node_modules/prosemirror-view": {
"version": "1.32.7", "version": "1.33.1",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.32.7.tgz", "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.1.tgz",
"integrity": "sha512-pvxiOoD4shW41X5bYDjRQk3DSG4fMqxh36yPMt7VYgU3dWRmqFzWJM/R6zeo1KtC8nyk717ZbQND3CC9VNeptw==", "integrity": "sha512-62qkYgSJIkwIMMCpuGuPzc52DiK1Iod6TWoIMxP4ja6BTD4yO8kCUL64PZ/WhH/dJ9fW0CDO39FhH1EMyhUFEg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"prosemirror-model": "^1.16.0", "prosemirror-model": "^1.16.0",
@ -12163,9 +12163,9 @@
} }
}, },
"node_modules/web-streams-polyfill": { "node_modules/web-streams-polyfill": {
"version": "3.3.2", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"

View File

@ -12,20 +12,21 @@
"dev": "vite", "dev": "vite",
"e2e": "npx playwright test --project=chromium", "e2e": "npx playwright test --project=chromium",
"fix": "npm run check:code:fix && stylelint **/*.{scss,css} --fix", "fix": "npm run check:code:fix && stylelint **/*.{scss,css} --fix",
"format": "npx @biomejs/biome format src/. --write", "format": "npx @biomejs/biome format . --write",
"hygen": "HYGEN_TMPLS=gen hygen", "hygen": "HYGEN_TMPLS=gen hygen",
"postinstall": "npm run codegen && npx patch-package", "postinstall": "npm run codegen && npx patch-package",
"check:code": "npx @biomejs/biome check src --log-kind=compact --verbose", "check:code": "npx @biomejs/biome check src --log-kind=compact --verbose",
"check:code:fix": "npx @biomejs/biome check src --log-kind=compact --verbose --apply-unsafe", "check:code:fix": "npx @biomejs/biome check src --log-kind=compact --verbose --apply-unsafe",
"lint": "npm run lint:code && stylelint **/*.{scss,css}", "lint": "npm run lint:code && stylelint **/*.{scss,css}",
"lint:code": "npx @biomejs/biome lint src --log-kind=compact --verbose", "lint:code": "npx @biomejs/biome lint . --log-kind=compact --verbose",
"lint:code:fix": "npx @biomejs/biome lint src --apply-unsafe --log-kind=compact --verbose", "lint:code:fix": "npx @biomejs/biome lint . --apply-unsafe --log-kind=compact --verbose",
"lint:styles": "stylelint **/*.{scss,css}", "lint:styles": "stylelint **/*.{scss,css}",
"lint:styles:fix": "stylelint **/*.{scss,css} --fix", "lint:styles:fix": "stylelint **/*.{scss,css} --fix",
"preview": "vite preview", "preview": "vite preview",
"start": "vite", "start": "vite",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch" "typecheck:watch": "tsc --noEmit --watch",
"templates": "node ./templates/compile.cjs"
}, },
"dependencies": { "dependencies": {
"form-data": "4.0.0", "form-data": "4.0.0",
@ -111,7 +112,7 @@
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"prosemirror-history": "1.3.2", "prosemirror-history": "1.3.2",
"prosemirror-trailing-node": "2.0.7", "prosemirror-trailing-node": "2.0.7",
"prosemirror-view": "1.32.7", "prosemirror-view": "1.33.1",
"rollup": "4.11.0", "rollup": "4.11.0",
"sass": "1.69.5", "sass": "1.69.5",
"solid-js": "1.8.15", "solid-js": "1.8.15",

View File

@ -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 mobile viewports. */ /* Test against mobile 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 your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
// webServer: { // webServer: {

View File

@ -84,7 +84,7 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
profileSettings: ProfileSettingsPage, profileSettings: ProfileSettingsPage,
profileSecurity: ProfileSecurityPage, profileSecurity: ProfileSecurityPage,
profileSubscriptions: ProfileSubscriptionsPage, profileSubscriptions: ProfileSubscriptionsPage,
fourOuFour: FourOuFourPage, fourOuFour: FourOuFourPage
} }
type Props = PageProps & { is404: boolean } type Props = PageProps & { is404: boolean }

View File

@ -38,8 +38,8 @@ export const AudioPlayer = (props: Props) => {
() => { () => {
setCurrentTrackDuration(0) setCurrentTrackDuration(0)
}, },
{ defer: true }, { defer: true }
), )
) )
const handlePlayMedia = async (trackIndex: number) => { const handlePlayMedia = async (trackIndex: number) => {
@ -134,7 +134,7 @@ export const AudioPlayer = (props: Props) => {
<div <div
class={styles.progressFilled} class={styles.progressFilled}
style={{ style={{
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`, width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`
}} }}
/> />
</div> </div>

View File

@ -18,7 +18,7 @@ type Props = {
export const PlayerHeader = (props: Props) => { export const PlayerHeader = (props: Props) => {
const volumeContainerRef: { current: HTMLDivElement } = { const volumeContainerRef: { current: HTMLDivElement } = {
current: null, current: null
} }
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false) const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
@ -30,7 +30,7 @@ export const PlayerHeader = (props: Props) => {
useOutsideClickHandler({ useOutsideClickHandler({
containerRef: volumeContainerRef, containerRef: volumeContainerRef,
predicate: () => isVolumeBarOpened(), predicate: () => isVolumeBarOpened(),
handler: () => toggleVolumeBar(), handler: () => toggleVolumeBar()
}) })
return ( return (
@ -42,7 +42,7 @@ export const PlayerHeader = (props: Props) => {
onClick={props.onPlayMedia} onClick={props.onPlayMedia}
class={clsx( class={clsx(
styles.playButton, styles.playButton,
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay, props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay
)} )}
aria-label="Play" aria-label="Play"
data-playing="false" data-playing="false"

View File

@ -43,7 +43,7 @@ export const PlayerPlaylist = (props: Props) => {
gtag('event', 'select_item', { gtag('event', 'select_item', {
item_list_id: props.articleSlug, item_list_id: props.articleSlug,
item_list_name: getMediaTitle(mi, index), item_list_name: getMediaTitle(mi, index),
items: props.media.map((it, ix) => getMediaTitle(it, ix)), items: props.media.map((it, ix) => getMediaTitle(it, ix))
}) })
} }
return ( return (

View File

@ -46,7 +46,7 @@ export const Comment = (props: Props) => {
const canEdit = createMemo( const canEdit = createMemo(
() => () =>
Boolean(author()?.id) && Boolean(author()?.id) &&
(props.comment?.created_by?.id === author().id || session()?.user?.roles.includes('editor')), (props.comment?.created_by?.id === author().id || session()?.user?.roles.includes('editor'))
) )
const comment = createMemo(() => props.comment) const comment = createMemo(() => props.comment)
@ -59,7 +59,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) => {
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
}) })
setClearEditor(true) setClearEditor(true)
setIsReplyVisible(false) setIsReplyVisible(false)
@ -102,7 +102,7 @@ export const Comment = (props: Props) => {
id: props.comment.id, id: props.comment.id,
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
body: value, body: value,
shout: props.comment.shout.id, shout: props.comment.shout.id
}) })
setEditMode(false) setEditMode(false)
setLoading(false) setLoading(false)
@ -126,7 +126,7 @@ export const Comment = (props: Props) => {
name={comment().created_by.name} name={comment().created_by.name}
userpic={comment().created_by.pic} userpic={comment().created_by.pic}
class={clsx({ class={clsx({
[styles.compactUserpic]: props.compact, [styles.compactUserpic]: props.compact
})} })}
/> />
<small> <small>

View File

@ -30,7 +30,7 @@ export const CommentDate = (props: Props) => {
<div <div
class={clsx(styles.commentDates, { class={clsx(styles.commentDates, {
[styles.commentDatesLastInRow]: props.isLastInRow, [styles.commentDatesLastInRow]: props.isLastInRow,
[styles.showOnHover]: props.showOnHover, [styles.showOnHover]: props.showOnHover
})} })}
> >
<time class={styles.date}>{formattedDate(props.comment.created_at * 1000)}</time> <time class={styles.date}>{formattedDate(props.comment.created_at * 1000)}</time>

View File

@ -28,7 +28,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.created_by.slug === author()?.slug && r.created_by.slug === author()?.slug &&
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))
@ -39,8 +39,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) => {
@ -49,7 +49,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.created_by.slug === author()?.slug && r.created_by.slug === author()?.slug &&
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
) )
return deleteReaction(reactionToDelete.id) return deleteReaction(reactionToDelete.id)
} }
@ -64,7 +64,7 @@ export const CommentRatingControl = (props: Props) => {
await createReaction({ await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike, kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.comment.shout.id, shout: props.comment.shout.id,
reply_to: props.comment.id, reply_to: props.comment.id
}) })
} }
} catch { } catch {
@ -73,7 +73,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 }
}) })
} }
@ -84,7 +84,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!(canVote() && author())} disabled={!(canVote() && author())}
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
@ -92,7 +92,7 @@ export const CommentRatingControl = (props: Props) => {
<div <div
class={clsx(styles.commentRatingValue, { class={clsx(styles.commentRatingValue, {
[styles.commentRatingPositive]: props.comment.stat.rating > 0, [styles.commentRatingPositive]: props.comment.stat.rating > 0,
[styles.commentRatingNegative]: props.comment.stat.rating < 0, [styles.commentRatingNegative]: props.comment.stat.rating < 0
})} })}
> >
{props.comment.stat.rating || 0} {props.comment.stat.rating || 0}
@ -110,7 +110,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!(canVote() && author())} disabled={!(canVote() && author())}
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>

View File

@ -52,7 +52,7 @@ export const CommentsTree = (props: Props) => {
const { reactionEntities, createReaction } = useReactions() const { reactionEntities, createReaction } = 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(() => {
@ -93,7 +93,7 @@ export const CommentsTree = (props: Props) => {
await createReaction({ await createReaction({
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
body: value, body: value,
shout: props.shoutId, shout: props.shoutId
}) })
setClearEditor(true) setClearEditor(true)
} catch (error) { } catch (error) {
@ -151,7 +151,7 @@ export const CommentsTree = (props: Props) => {
<Comment <Comment
sortedComments={sortedComments()} sortedComments={sortedComments()}
isArticleAuthor={Boolean( isArticleAuthor={Boolean(
props.articleAuthors.some((a) => a?.slug === reaction.created_by.slug), props.articleAuthors.some((a) => a?.slug === reaction.created_by.slug)
)} )}
comment={reaction} comment={reaction}
clickedReply={(id) => setClickedReplyId(id)} clickedReply={(id) => setClickedReplyId(id)}

View File

@ -26,7 +26,7 @@ const coverImages = [
CoverImage9, CoverImage9,
CoverImage10, CoverImage10,
CoverImage11, CoverImage11,
CoverImage12, CoverImage12
] ]
let counter = 0 let counter = 0

View File

@ -61,7 +61,7 @@ const scrollTo = (el: HTMLElement) => {
window.scrollTo({ window.scrollTo({
top: top - DEFAULT_HEADER_OFFSET, top: top - DEFAULT_HEADER_OFFSET,
left: 0, left: 0,
behavior: 'smooth', behavior: 'smooth'
}) })
} }
@ -83,7 +83,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(() => {
@ -167,7 +167,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) {
@ -185,7 +185,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
@ -210,19 +210,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'
@ -307,8 +307,8 @@ export const FullArticle = (props: Props) => {
() => props.article, () => props.article,
() => { () => {
updateIframeSizes() updateIframeSizes()
}, }
), )
) )
onMount(async () => { onMount(async () => {
@ -326,7 +326,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()) const description = getDescription(props.article.description || body())

View File

@ -29,7 +29,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))
@ -37,8 +37,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) => {
@ -47,7 +47,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
) )
return deleteReaction(reactionToDelete.id) return deleteReaction(reactionToDelete.id)
} }
@ -62,13 +62,13 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
} else { } else {
await createReaction({ await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike, kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.shout.id, shout: props.shout.id
}) })
} }
loadShout(props.shout.slug) loadShout(props.shout.slug)
loadReactionsBy({ loadReactionsBy({
by: { shout: props.shout.slug }, by: { shout: props.shout.slug }
}) })
setIsLoading(false) setIsLoading(false)

View File

@ -26,9 +26,9 @@ export const AuthGuard = (props: Props) => {
changeSearchParams( changeSearchParams(
{ {
source: 'authguard', source: 'authguard',
m: 'auth', m: 'auth'
}, },
true, true
) )
} }
} else { } else {

View File

@ -50,7 +50,7 @@ export const AuthorBadge = (props: Props) => {
requireAuthentication(() => { requireAuthentication(() => {
openPage(router, 'inbox') openPage(router, 'inbox')
changeSearchParams({ changeSearchParams({
initChat: props.author.id.toString(), initChat: props.author.id.toString()
}) })
}, 'discussions') }, 'discussions')
} }
@ -71,9 +71,9 @@ export const AuthorBadge = (props: Props) => {
on( on(
() => props.isFollowed, () => props.isFollowed,
() => { () => {
setIsFollowed(props.isFollowed?.value) setIsFollowed(props.isFollowed.value)
}, }
), )
) )
const handleFollowClick = () => { const handleFollowClick = () => {
@ -110,7 +110,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 * 1000)), date: formatDate(new Date(props.author.created_at * 1000))
})} })}
</div> </div>
} }
@ -153,7 +153,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true} isSubscribeButton={true}
class={clsx(styles.actionButton, { class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons, [styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: isFollowed(), [stylesButton.subscribed]: isFollowed()
})} })}
/> />
} }
@ -178,7 +178,7 @@ export const AuthorBadge = (props: Props) => {
isSubscribeButton={true} isSubscribeButton={true}
class={clsx(styles.actionButton, { class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons, [styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: isFollowed(), [stylesButton.subscribed]: isFollowed()
})} })}
/> />
</Show> </Show>

View File

@ -63,7 +63,7 @@ export const AuthorCard = (props: Props) => {
requireAuthentication(() => { requireAuthentication(() => {
openPage(router, 'inbox') openPage(router, 'inbox')
changeSearchParams({ changeSearchParams({
initChat: props.author.id.toString(), initChat: props.author.id.toString()
}) })
}, 'discussions') }, 'discussions')
} }
@ -206,7 +206,7 @@ export const AuthorCard = (props: Props) => {
value={followButtonText()} value={followButtonText()}
isSubscribeButton={true} isSubscribeButton={true}
class={clsx({ class={clsx({
[stylesButton.subscribed]: isFollowed(), [stylesButton.subscribed]: isFollowed()
})} })}
/> />
</Show> </Show>
@ -254,7 +254,7 @@ export const AuthorCard = (props: Props) => {
author={follower} author={follower}
isFollowed={{ isFollowed={{
loaded: Boolean(authorSubs()), loaded: Boolean(authorSubs()),
value: isOwnerSubscribed(follower.id), value: isOwnerSubscribed(follower.id)
}} }}
/> />
)} )}
@ -303,7 +303,7 @@ export const AuthorCard = (props: Props) => {
<AuthorBadge <AuthorBadge
isFollowed={{ isFollowed={{
loaded: Boolean(authorSubs()), loaded: Boolean(authorSubs()),
value: isOwnerSubscribed(subscription.id), value: isOwnerSubscribed(subscription.id)
}} }}
author={subscription} author={subscription}
/> />
@ -311,7 +311,7 @@ export const AuthorCard = (props: Props) => {
<TopicBadge <TopicBadge
isFollowed={{ isFollowed={{
loaded: Boolean(authorSubs()), loaded: Boolean(authorSubs()),
value: isOwnerSubscribed(subscription.id), value: isOwnerSubscribed(subscription.id)
}} }}
topic={subscription} topic={subscription}
/> />

View File

@ -27,7 +27,7 @@ export const AuthorLink = (props: Props) => {
return ( return (
<div <div
class={clsx(styles.AuthorLink, props.class, styles[props.size ?? 'M'], { class={clsx(styles.AuthorLink, props.class, styles[props.size ?? 'M'], {
[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}`}>

View File

@ -30,7 +30,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

View File

@ -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>

View File

@ -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}
> >

View File

@ -28,7 +28,7 @@ export const AuthorsList = (props: Props) => {
const result = await apiClient.loadAuthorsBy({ const result = await apiClient.loadAuthorsBy({
by: { order: queryType }, by: { order: queryType },
limit: PAGE_SIZE, limit: PAGE_SIZE,
offset: offset, offset: offset
}) })
if (queryType === 'shouts') { if (queryType === 'shouts') {
@ -44,7 +44,7 @@ export const AuthorsList = (props: Props) => {
const queryType = props.query const queryType = props.query
const nextPage = currentPage()[queryType] + 1 const nextPage = currentPage()[queryType] + 1
fetchAuthors(queryType, nextPage).then(() => fetchAuthors(queryType, nextPage).then(() =>
setCurrentPage({ ...currentPage(), [queryType]: nextPage }), setCurrentPage({ ...currentPage(), [queryType]: nextPage })
) )
} }
@ -70,7 +70,7 @@ export const AuthorsList = (props: Props) => {
author={author} author={author}
isFollowed={{ isFollowed={{
loaded: !loading(), loaded: !loading(),
value: isOwnerSubscribed(author.id), value: isOwnerSubscribed(author.id)
}} }}
/> />
</div> </div>

View File

@ -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() // чек для регулярных платежей
}, }
}, }
}, }
}, },
(opts) => { (opts) => {
// success // success
@ -116,9 +116,9 @@ export const Donate = () => {
showSnackbar({ showSnackbar({
type: 'error', type: 'error',
body: reason, body: reason
}) })
}, }
) )
} }

View File

@ -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()
} }

View File

@ -18,25 +18,25 @@ export const Footer = () => {
items: [ items: [
{ {
title: 'Discours Manifest', title: 'Discours Manifest',
slug: '/about/manifest', slug: '/about/manifest'
}, },
{ {
title: 'How it works', title: 'How it works',
slug: '/about/guide', slug: '/about/guide'
}, },
{ {
title: 'Dogma', title: 'Dogma',
slug: '/about/dogma', slug: '/about/dogma'
}, },
{ {
title: 'Principles', title: 'Principles',
slug: '/about/principles', slug: '/about/principles'
}, },
{ {
title: 'How to write an article', title: 'How to write an article',
slug: '/how-to-write-a-good-article', slug: '/how-to-write-a-good-article'
}, }
], ]
}, },
{ {
@ -44,21 +44,21 @@ export const Footer = () => {
items: [ items: [
{ {
title: 'Suggest an idea', title: 'Suggest an idea',
slug: '/connect', slug: '/connect'
}, },
{ {
title: 'Become an author', title: 'Become an author',
slug: '/create', slug: '/create'
}, },
{ {
title: 'Support Discours', title: 'Support Discours',
slug: '/about/help', slug: '/about/help'
}, },
{ {
title: 'Work with us', title: 'Work with us',
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform', slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform'
}, }
], ]
}, },
{ {
@ -66,46 +66,46 @@ export const Footer = () => {
items: [ items: [
{ {
title: 'Authors', title: 'Authors',
slug: '/authors', slug: '/authors'
}, },
{ {
title: 'Communities', title: 'Communities',
slug: '/community', slug: '/community'
}, },
{ {
title: 'Partners', title: 'Partners',
slug: '/about/partners', slug: '/about/partners'
}, },
{ {
title: 'Special projects', title: 'Special projects',
slug: '/about/projects', slug: '/about/projects'
}, },
{ {
title: changeLangTitle(), title: changeLangTitle(),
slug: changeLangLink(), slug: changeLangLink(),
rel: 'external', rel: 'external'
}, }
], ]
}, }
]) ])
const social = [ const social = [
{ {
name: 'facebook', name: 'facebook',
href: 'https://facebook.com/discoursio', href: 'https://facebook.com/discoursio'
}, },
{ {
name: 'vk', name: 'vk',
href: 'https://vk.com/discoursio', href: 'https://vk.com/discoursio'
}, },
{ {
name: 'twitter', name: 'twitter',
href: 'https://twitter.com/discours_io', href: 'https://twitter.com/discours_io'
}, },
{ {
name: 'telegram', name: 'telegram',
href: 'https://t.me/discoursio', href: 'https://t.me/discoursio'
}, }
] ]
return ( return (
<footer class={styles.discoursFooter}> <footer class={styles.discoursFooter}>
@ -140,7 +140,7 @@ export const Footer = () => {
<div class={clsx(styles.footerCopyright, 'row')}> <div class={clsx(styles.footerCopyright, 'row')}>
<div class="col-md-18 col-lg-20"> <div class="col-md-18 col-lg-20">
{t( {t(
'Independant magazine with an open horizontal cooperation about culture, science and society', 'Independant magazine with an open horizontal cooperation about culture, science and society'
)} )}
. {t('Discours')} &copy; 2015&ndash;{new Date().getFullYear()}{' '} . {t('Discours')} &copy; 2015&ndash;{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>

View File

@ -17,7 +17,7 @@ export default () => {
<h4 innerHTML={t('Horizontal collaborative journalistic platform')} /> <h4 innerHTML={t('Horizontal collaborative journalistic platform')} />
<p <p
innerHTML={t( innerHTML={t(
'Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects', 'Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects'
)} )}
/> />
<div class={styles.aboutDiscoursActions}> <div class={styles.aboutDiscoursActions}>
@ -29,7 +29,7 @@ export default () => {
onClick={() => { onClick={() => {
showModal('auth') showModal('auth')
changeSearchParams({ changeSearchParams({
mode: 'register', mode: 'register'
}) })
}} }}
> >

View File

@ -39,7 +39,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)

View File

@ -67,7 +67,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> = {}
@ -93,42 +93,42 @@ export const Editor = (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 || ''
}) })
} }
const editorElRef: { const editorElRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null, current: null
} }
const textBubbleMenuRef: { const textBubbleMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null, current: null
} }
const incutBubbleMenuRef: { const incutBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null, current: null
} }
const figureBubbleMenuRef: { const figureBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null, current: null
} }
const blockquoteBubbleMenuRef: { const blockquoteBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null, current: null
} }
const floatingMenuRef: { const floatingMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null, current: null
} }
const handleClipboardPaste = async () => { const handleClipboardPaste = async () => {
@ -149,7 +149,7 @@ export const Editor = (props: Props) => {
source: blob.toString(), source: blob.toString(),
name: file.name, name: file.name,
size: file.size, size: file.size,
file, file
} }
showSnackbar({ body: t('Uploading image') }) showSnackbar({ body: t('Uploading image') })
@ -164,13 +164,13 @@ export const Editor = (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) {
@ -184,7 +184,7 @@ export const Editor = (props: Props) => {
element: editorElRef.current, element: editorElRef.current,
editorProps: { editorProps: {
attributes: { attributes: {
class: 'articleEditor', class: 'articleEditor'
}, },
transformPastedHTML(html) { transformPastedHTML(html) {
return html.replaceAll(/<img.*?>/g, '') return html.replaceAll(/<img.*?>/g, '')
@ -192,7 +192,7 @@ export const Editor = (props: Props) => {
handlePaste: () => { handlePaste: () => {
handleClipboardPaste() handleClipboardPaste()
return false return false
}, }
}, },
extensions: [ extensions: [
Document, Document,
@ -207,34 +207,34 @@ export const Editor = (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,
@ -242,8 +242,8 @@ export const Editor = (props: Props) => {
Highlight.configure({ Highlight.configure({
multicolor: true, multicolor: true,
HTMLAttributes: { HTMLAttributes: {
class: 'highlight', class: 'highlight'
}, }
}), }),
Image, Image,
Iframe, Iframe,
@ -275,8 +275,8 @@ export const Editor = (props: Props) => {
return result return result
}, },
tippyOptions: { tippyOptions: {
sticky: true, sticky: true
}, }
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'blockquoteBubbleMenu', pluginKey: 'blockquoteBubbleMenu',
@ -294,8 +294,8 @@ export const Editor = (props: Props) => {
if (selectedElement) { if (selectedElement) {
return selectedElement.getBoundingClientRect() return selectedElement.getBoundingClientRect()
} }
}, }
}, }
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'incutBubbleMenu', pluginKey: 'incutBubbleMenu',
@ -313,27 +313,27 @@ export const Editor = (props: Props) => {
if (selectedElement) { if (selectedElement) {
return selectedElement.getBoundingClientRect() return selectedElement.getBoundingClientRect()
} }
}, }
}, }
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'imageBubbleMenu', pluginKey: 'imageBubbleMenu',
element: figureBubbleMenuRef.current, element: figureBubbleMenuRef.current,
shouldShow: ({ editor: e, view }) => { shouldShow: ({ editor: e, view }) => {
return view.hasFocus() && e.isActive('image') return view.hasFocus() && e.isActive('image')
}, }
}), }),
FloatingMenu.configure({ FloatingMenu.configure({
tippyOptions: { tippyOptions: {
placement: 'left', placement: 'left'
}, },
element: floatingMenuRef.current, element: floatingMenuRef.current
}), }),
TrailingNode, TrailingNode,
Article, Article
], ],
enablePasteRules: [Link], enablePasteRules: [Link],
content: initialContent ?? null, content: initialContent ?? null
})) }))
const { countWords, setEditor } = useEditorContext() const { countWords, setEditor } = useEditorContext()
@ -346,7 +346,7 @@ export const Editor = (props: Props) => {
if (html()) { if (html()) {
countWords({ countWords({
characters: editor().storage.characterCount.characters(), characters: editor().storage.characterCount.characters(),
words: editor().storage.characterCount.words(), words: editor().storage.characterCount.words()
}) })
} }
}) })

View File

@ -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) => {

View File

@ -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()) {

View File

@ -35,7 +35,7 @@ export const Panel = (props: Props) => {
useOutsideClickHandler({ useOutsideClickHandler({
containerRef, containerRef,
predicate: () => isEditorPanelVisible(), predicate: () => isEditorPanelVisible(),
handler: () => toggleEditorPanel(), handler: () => toggleEditorPanel()
}) })
useEscKeyDownHandler(() => { useEscKeyDownHandler(() => {

View File

@ -17,7 +17,7 @@ import {
createTiptapEditor, createTiptapEditor,
useEditorHTML, useEditorHTML,
useEditorIsEmpty, useEditorIsEmpty,
useEditorIsFocused, useEditorIsFocused
} from 'solid-tiptap' } from 'solid-tiptap'
import { useEditorContext } from '../../context/editor' import { useEditorContext } from '../../context/editor'
@ -73,32 +73,32 @@ const SimplifiedEditor = (props: Props) => {
const wrapperEditorElRef: { const wrapperEditorElRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null, current: null
} }
const editorElRef: { const editorElRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null, current: null
} }
const textBubbleMenuRef: { const textBubbleMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null, current: null
} }
const linkBubbleMenuRef: { const linkBubbleMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null, current: null
} }
const { setEditor } = useEditorContext() const { setEditor } = useEditorContext()
const ImageFigure = Figure.extend({ const ImageFigure = Figure.extend({
name: 'capturedImage', name: 'capturedImage',
content: 'figcaption image', content: 'figcaption image'
}) })
const content = props.initialContent const content = props.initialContent
@ -106,8 +106,8 @@ const SimplifiedEditor = (props: Props) => {
element: editorElRef.current, element: editorElRef.current,
editorProps: { editorProps: {
attributes: { attributes: {
class: styles.simplifiedEditorField, class: styles.simplifiedEditorField
}, }
}, },
extensions: [ extensions: [
Document, Document,
@ -116,18 +116,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: maxLength, limit: maxLength
}), }),
Blockquote.configure({ Blockquote.configure({
HTMLAttributes: { HTMLAttributes: {
class: styles.blockQuote, class: styles.blockQuote
}, }
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'textBubbleMenu', pluginKey: 'textBubbleMenu',
@ -137,7 +137,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',
@ -148,19 +148,19 @@ 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: content ?? null, content: content ?? null
})) }))
setEditor(editor) setEditor(editor)
@ -172,7 +172,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())
@ -191,13 +191,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()
@ -260,7 +260,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 = () => {
@ -282,7 +282,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()}>

View File

@ -26,7 +26,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
const isActive = (name: string, attributes?: unknown) => const isActive = (name: string, attributes?: unknown) =>
createEditorTransaction( createEditorTransaction(
() => props.editor, () => props.editor,
(editor) => editor?.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) => { const handleAddFootnote = (footnote) => {
@ -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()

View File

@ -37,7 +37,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
disable: (topic) => { disable: (topic) => {
return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug) return props.selectedTopics.some((selectedTopic) => selectedTopic.slug === topic.slug)
}, },
createable: createValue, createable: createValue
}) })
const handleChange = (selectedTopics: Topic[]) => { const handleChange = (selectedTopics: Topic[]) => {
@ -61,7 +61,7 @@ export const TopicSelect = (props: TopicSelectProps) => {
return ( return (
<div <div
class={clsx(styles.selectedItem, { class={clsx(styles.selectedItem, {
[styles.mainTopic]: isMainTopic, [styles.mainTopic]: isMainTopic
})} })}
onClick={() => handleSelectedItemClick(item)} onClick={() => handleSelectedItemClick(item)}
> >

View File

@ -48,7 +48,7 @@ export const UploadModalContent = (props: Props) => {
source: blob.toString(), source: blob.toString(),
name: file.name, name: file.name,
size: file.size, size: file.size,
file: file, file: file
} }
await runUpload(fileToUpload) await runUpload(fileToUpload)
} catch (error) { } catch (error) {
@ -72,7 +72,7 @@ export const UploadModalContent = (props: Props) => {
} else { } else {
setDragError(t('Image format not supported')) setDragError(t('Image format not supported'))
} }
}, }
}) })
const handleDrag = (event: MouseEvent) => { const handleDrag = (event: MouseEvent) => {
if (event.type === 'dragenter' || event.type === 'dragover') { if (event.type === 'dragenter' || event.type === 'dragover') {

View File

@ -29,7 +29,7 @@ export const VideoUploader = (props: Props) => {
const urlInput: { const urlInput: {
current: HTMLInputElement current: HTMLInputElement
} = { } = {
current: null, current: null
} }
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({ const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
@ -40,13 +40,13 @@ export const VideoUploader = (props: Props) => {
} else if (droppedFiles()[0].file.type.startsWith('video/')) { } else if (droppedFiles()[0].file.type.startsWith('video/')) {
await showSnackbar({ await showSnackbar({
body: t( body: t(
'This functionality is currently not available, we would like to work on this issue. Use the download link.', 'This functionality is currently not available, we would like to work on this issue. Use the download link.'
), )
}) })
} else { } else {
setError(t('Video format not supported')) setError(t('Video format not supported'))
} }
}, }
}) })
const handleDrag = (event) => { const handleDrag = (event) => {
if (event.type === 'dragenter' || event.type === 'dragover') { if (event.type === 'dragenter' || event.type === 'dragover') {
@ -85,8 +85,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}

View File

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

View File

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

View File

@ -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 })
},
} }
}, }
}
}) })

View File

@ -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)
},
} }
}, }
}
}) })

View File

@ -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 }
} }
}, },
@ -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 })
},
} }
}, }
}
}) })

View File

@ -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
},
} }
}, }
}
}) })

View File

@ -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
},
} }
}, }
}
}) })

View File

@ -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
}, }
}
} }
},
}) })

View File

@ -43,7 +43,7 @@ export const ToggleTextWrap = Extension.create({
return true return true
} }
return false return false
},
} }
}, }
}
}) })

View File

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

View File

@ -57,11 +57,11 @@ const desktopCoverImageWidths: Record<ArticleCardProps['desktopCoverSize'], numb
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
@ -101,7 +101,7 @@ const LAYOUT_ASPECT = {
music: styles.aspectRatio1x1, music: 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) => {
@ -117,7 +117,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(
@ -125,14 +125,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 scrollToComments = (event) => { const scrollToComments = (event) => {
event.preventDefault() event.preventDefault()
openPage(router, 'article', { slug: props.article.slug }) openPage(router, 'article', { slug: props.article.slug })
changeSearchParams({ changeSearchParams({
scrollTo: 'comments', scrollTo: 'comments'
}) })
} }
return ( return (
@ -150,14 +150,14 @@ 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
})} })}
> >
<Show when={!(props.settings?.noimage || props.settings?.isFeedMode)}> <Show when={!(props.settings?.noimage || props.settings?.isFeedMode)}>
<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
@ -207,7 +207,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
<div <div
class={clsx(styles.shoutCardTitlesContainer, { class={clsx(styles.shoutCardTitlesContainer, {
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode, [styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
})} })}
> >
<a href={getPagePath(router, 'article', { slug: props.article.slug })}> <a href={getPagePath(router, 'article', { slug: props.article.slug })}>

View File

@ -43,7 +43,7 @@ export const Beside = (props: Props) => {
'col-lg-8', 'col-lg-8',
styles[ styles[
`besideRatingColumn${props.wrapper.charAt(0).toUpperCase() + props.wrapper.slice(1)}` `besideRatingColumn${props.wrapper.charAt(0).toUpperCase() + props.wrapper.slice(1)}`
], ]
)} )}
> >
<Show when={!!props.title}> <Show when={!!props.title}>
@ -67,7 +67,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]}>
@ -89,7 +89,7 @@ export const Beside = (props: Props) => {
<AuthorBadge <AuthorBadge
author={value as Author} author={value as Author}
isFollowed={{ isFollowed={{
value: isOwnerSubscribed(value.id), value: isOwnerSubscribed(value.id)
}} }}
/> />
</Show> </Show>

View File

@ -18,7 +18,7 @@ export const CardTopic = (props: CardTopicProps) => {
<div <div
class={clsx(styles.shoutTopic, props.class, { class={clsx(styles.shoutTopic, props.class, {
[styles.shoutTopicFloorImportant]: props.isFloorImportant, [styles.shoutTopicFloorImportant]: props.isFloorImportant,
[styles.shoutTopicFeedMode]: props.isFeedMode, [styles.shoutTopicFeedMode]: props.isFeedMode
})} })}
> >
<a href={getPagePath(router, 'topic', { slug: props.slug })}>{props.title}</a> <a href={getPagePath(router, 'topic', { slug: props.slug })}>{props.title}</a>

View File

@ -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"
/> />

View File

@ -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"
/> />

View File

@ -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}
/> />

View File

@ -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"
/> />

View File

@ -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"
/> />

View File

@ -35,7 +35,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feed')} href={getPagePath(router, 'feed')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feed', [styles.selected]: page().route === 'feed'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>
@ -48,7 +48,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feedMy')} href={getPagePath(router, 'feedMy')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feedMy', [styles.selected]: page().route === 'feedMy'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>
@ -61,7 +61,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feedCollaborations')} href={getPagePath(router, 'feedCollaborations')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feedCollaborations', [styles.selected]: page().route === 'feedCollaborations'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>
@ -74,7 +74,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feedDiscussions')} href={getPagePath(router, 'feedDiscussions')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feedDiscussions', [styles.selected]: page().route === 'feedDiscussions'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>
@ -87,7 +87,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feedBookmarks')} href={getPagePath(router, 'feedBookmarks')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feedBookmarks', [styles.selected]: page().route === 'feedBookmarks'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>
@ -100,7 +100,7 @@ export const Sidebar = () => {
<a <a
href={getPagePath(router, 'feedNotifications')} href={getPagePath(router, 'feedNotifications')}
class={clsx({ class={clsx({
[styles.selected]: page().route === 'feedNotifications', [styles.selected]: page().route === 'feedNotifications'
})} })}
> >
<span class={styles.sidebarItemName}> <span class={styles.sidebarItemName}>

View File

@ -51,7 +51,7 @@ const CreateModalContent = (props: Props) => {
const handleClick = (user) => { const handleClick = (user) => {
setCollectionToInvite((userCollection) => { setCollectionToInvite((userCollection) => {
return userCollection.map((clickedUser) => return userCollection.map((clickedUser) =>
user.id === clickedUser.id ? { ...clickedUser, selected: !clickedUser.selected } : clickedUser, user.id === clickedUser.id ? { ...clickedUser, selected: !clickedUser.selected } : clickedUser
) )
}) })
} }

View File

@ -27,7 +27,7 @@ const colors = [
'#668cff', '#668cff',
'#c34cfe', '#c34cfe',
'#e699ff', '#e699ff',
'#6633ff', '#6633ff'
] ]
const getById = (letter: string) => const getById = (letter: string) =>
@ -44,7 +44,7 @@ const DialogAvatar = (props: Props) => {
class={clsx(styles.DialogAvatar, props.class, { class={clsx(styles.DialogAvatar, props.class, {
[styles.online]: props.online, [styles.online]: props.online,
[styles.bordered]: props.bordered, [styles.bordered]: props.bordered,
[styles.small]: props.size === 'small', [styles.small]: props.size === 'small'
})} })}
style={{ 'background-color': `${randomBg()}` }} style={{ 'background-color': `${randomBg()}` }}
> >

View File

@ -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}
> >

View File

@ -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()) if (props.actionSelect) props.actionSelect(selectedAction())

View File

@ -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'}>

View File

@ -16,7 +16,7 @@ export const AuthModalHeader = (props: Props) => {
const { source } = searchParams() const { source } = searchParams()
const generateModalTextsFromSource = ( const generateModalTextsFromSource = (
modalType: 'login' | 'register', modalType: 'login' | 'register'
): { title: string; description: string } => { ): { title: string; description: string } => {
const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account' const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account'
@ -24,53 +24,53 @@ export const AuthModalHeader = (props: Props) => {
case 'create': { case 'create': {
return { return {
title: t(`${title} to publish articles`), title: t(`${title} to publish articles`),
description: '', description: ''
} }
} }
case 'bookmark': { case 'bookmark': {
return { return {
title: t(`${title} to add to your bookmarks`), title: t(`${title} to add to your bookmarks`),
description: t( description: t(
'In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to', 'In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to'
), )
} }
} }
case 'discussions': { case 'discussions': {
return { return {
title: t(`${title} to participate in discussions`), title: t(`${title} to participate in discussions`),
description: t( description: t(
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses", "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses"
), )
} }
} }
case 'follow': { case 'follow': {
return { return {
title: t(`${title} to subscribe`), title: t(`${title} to subscribe`),
description: t( description: t(
'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed', 'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed'
), )
} }
} }
case 'subscribe': { 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&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed', 'This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed'
), )
} }
} }
case 'vote': { case 'vote': {
return { return {
title: t(`${title} to vote`), title: t(`${title} to vote`),
description: t( description: t(
'This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted', 'This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted'
), )
} }
} }
default: { default: {
return { return {
title: t(title), title: t(title),
description: '', description: ''
} }
} }
} }

View File

@ -62,7 +62,7 @@ export const ChangePasswordForm = () => {
<h4>{t('Enter a new password')}</h4> <h4>{t('Enter a new password')}</h4>
<div class={styles.authSubtitle}> <div class={styles.authSubtitle}>
{t( {t(
'Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password', 'Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password'
)} )}
</div> </div>
<Show when={validationErrors()}> <Show when={validationErrors()}>
@ -83,7 +83,7 @@ export const ChangePasswordForm = () => {
class={styles.authLink} class={styles.authLink}
onClick={() => onClick={() =>
changeSearchParams({ changeSearchParams({
mode: 'login', mode: 'login'
}) })
} }
> >

View File

@ -100,7 +100,7 @@ export const LoginForm = () => {
if (errors.some((error) => error.message.includes('bad user credentials'))) { if (errors.some((error) => error.message.includes('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')
})) }))
} else { } else {
setSubmitError(t('Error')) setSubmitError(t('Error'))
@ -136,7 +136,7 @@ export const LoginForm = () => {
</Show> </Show>
<div <div
class={clsx('pretty-form__item', { class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email, 'pretty-form__item--error': validationErrors().email
})} })}
> >
<input <input
@ -171,7 +171,7 @@ export const LoginForm = () => {
class="link" class="link"
onClick={() => onClick={() =>
changeSearchParams({ changeSearchParams({
mode: 'send-reset-link', mode: 'send-reset-link'
}) })
} }
> >
@ -188,7 +188,7 @@ export const LoginForm = () => {
class={styles.authLink} class={styles.authLink}
onClick={() => onClick={() =>
changeSearchParams({ changeSearchParams({
mode: 'register', mode: 'register'
}) })
} }
> >

View File

@ -55,15 +55,15 @@ export const PasswordField = (props: Props) => {
() => { () => {
props.errorMessage?.(error()) props.errorMessage?.(error())
}, },
{ defer: true }, { defer: true }
), )
) )
return ( return (
<div class={clsx(styles.PassportField, props.class)}> <div class={clsx(styles.PassportField, props.class)}>
<div <div
class={clsx('pretty-form__item', { class={clsx('pretty-form__item', {
'pretty-form__item--error': error() && props.variant !== 'login', 'pretty-form__item--error': error() && props.variant !== 'login'
})} })}
> >
<input <input

View File

@ -99,7 +99,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 { errors } = await signUp(opts) const { errors } = await signUp(opts)
if (errors) return if (errors) return
@ -114,7 +114,7 @@ export const RegisterForm = () => {
const handleResendLink = async (_ev) => { const handleResendLink = async (_ev) => {
const response: GenericResponse = await resendVerifyEmail({ const response: GenericResponse = await resendVerifyEmail({
email: email(), email: email(),
identifier: 'basic_signup', identifier: 'basic_signup'
}) })
setIsSuccess(response?.message === 'Verification email has been sent. Please check your inbox') setIsSuccess(response?.message === 'Verification email has been sent. Please check your inbox')
} }
@ -131,7 +131,7 @@ export const RegisterForm = () => {
{t('resend confirmation link')} {t('resend confirmation link')}
</span> </span>
</> </>
), )
})) }))
break break
@ -144,7 +144,7 @@ export const RegisterForm = () => {
{t('enter')} {t('enter')}
</span> </span>
</> </>
), )
})) }))
break break
case 'registered': case 'registered':
@ -157,7 +157,7 @@ export const RegisterForm = () => {
{t('Set the new password').toLocaleLowerCase()} {t('Set the new password').toLocaleLowerCase()}
</span> </span>
</> </>
), )
})) }))
break break
default: default:
@ -187,7 +187,7 @@ export const RegisterForm = () => {
</Show> </Show>
<div <div
class={clsx('pretty-form__item', { class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().fullName, 'pretty-form__item--error': validationErrors().fullName
})} })}
> >
<input <input
@ -206,7 +206,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'
}) })
} }
> >

View File

@ -58,7 +58,7 @@ export const SendResetLinkForm = () => {
try { try {
const { data, errors } = await forgotPassword({ const { data, errors } = await forgotPassword({
email: email(), email: email(),
redirect_uri: window.location.origin, redirect_uri: window.location.origin
}) })
console.debug('[SendResetLinkForm] authorizer response:', data) console.debug('[SendResetLinkForm] authorizer response:', data)
if (errors?.some((error) => error.message.includes('bad user credentials'))) { if (errors?.some((error) => error.message.includes('bad user credentials'))) {
@ -83,7 +83,7 @@ export const SendResetLinkForm = () => {
<div class={styles.authSubtitle}>{t(message()) || t('Please give us your email address')}</div> <div class={styles.authSubtitle}>{t(message()) || t('Please give us your email address')}</div>
<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
@ -105,7 +105,7 @@ export const SendResetLinkForm = () => {
class={'link'} class={'link'}
onClick={() => onClick={() =>
changeSearchParams({ changeSearchParams({
mode: 'login', mode: 'login'
}) })
} }
> >
@ -132,7 +132,7 @@ export const SendResetLinkForm = () => {
class={styles.authLink} class={styles.authLink}
onClick={() => onClick={() =>
changeSearchParams({ changeSearchParams({
mode: 'login', mode: 'login'
}) })
} }
> >

View File

@ -22,7 +22,7 @@ const AUTH_MODAL_MODES: Record<AuthModalMode, Component> = {
register: RegisterForm, register: RegisterForm,
'send-reset-link': SendResetLinkForm, 'send-reset-link': SendResetLinkForm,
'confirm-email': EmailConfirm, 'confirm-email': EmailConfirm,
'change-password': ChangePasswordForm, 'change-password': ChangePasswordForm
} }
export const AuthModal = () => { export const AuthModal = () => {
@ -46,7 +46,7 @@ export const AuthModal = () => {
ref={(el) => (rootRef.current = el)} ref={(el) => (rootRef.current = el)}
class={clsx(styles.view, { class={clsx(styles.view, {
row: !source, row: !source,
[styles.signUp]: mode() === 'register' || mode() === 'confirm-email', [styles.signUp]: mode() === 'register' || mode() === 'confirm-email'
})} })}
> >
<Show when={!source}> <Show when={!source}>
@ -59,7 +59,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'
)} )}
.&nbsp; .&nbsp;
{t('New stories every day and even more!')} {t('New stories every day and even more!')}
@ -82,7 +82,7 @@ export const AuthModal = () => {
</Show> </Show>
<div <div
class={clsx(styles.auth, { class={clsx(styles.auth, {
'col-md-12': !source, 'col-md-12': !source
})} })}
> >
<Dynamic component={AUTH_MODAL_MODES[mode()]} /> <Dynamic component={AUTH_MODAL_MODES[mode()]} />

View File

@ -165,7 +165,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
@ -320,7 +320,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}>

View File

@ -53,7 +53,7 @@ export const HeaderAuth = (props: Props) => {
const isSaveButtonVisible = createMemo(() => isAuthenticated() && isEditorPage()) const isSaveButtonVisible = createMemo(() => isAuthenticated() && isEditorPage())
const isCreatePostButtonVisible = createMemo(() => isAuthenticated() && !isEditorPage()) const isCreatePostButtonVisible = createMemo(() => isAuthenticated() && !isEditorPage())
const isAuthenticatedControlsVisible = createMemo( const isAuthenticatedControlsVisible = createMemo(
() => isAuthenticated() && session()?.user?.email_verified, () => isAuthenticated() && session()?.user?.email_verified
) )
const handleBurgerButtonClick = () => { const handleBurgerButtonClick = () => {
@ -147,7 +147,7 @@ export const HeaderAuth = (props: Props) => {
{renderIconedButton({ {renderIconedButton({
value: t('Save'), value: t('Save'),
icon: 'save', icon: 'save',
action: handleSaveButtonClick, action: handleSaveButtonClick
})} })}
</div> </div>
@ -155,7 +155,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>

View File

@ -56,7 +56,7 @@ export const Modal = (props: Props) => {
<Show when={visible()}> <Show when={visible()}>
<div <div
class={clsx(styles.backdrop, [styles[`modal-${props.name}`]], { class={clsx(styles.backdrop, [styles[`modal-${props.name}`]], {
[styles.isMobile]: isMobileView(), [styles.isMobile]: isMobileView()
})} })}
onClick={handleHide} onClick={handleHide}
> >
@ -66,7 +66,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()}
> >

View File

@ -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 = () => {
@ -59,7 +59,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)
@ -68,8 +68,8 @@ export const SearchModal = () => {
}, },
{ {
ssrLoadFrom: 'initial', ssrLoadFrom: 'initial',
initialValue: null, initialValue: null
}, }
) )
let searchEl: HTMLInputElement let searchEl: HTMLInputElement
@ -123,7 +123,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'
)} )}
/> />
@ -137,7 +137,7 @@ export const SearchModal = () => {
settings={{ settings={{
isFloorImportant: true, isFloorImportant: true,
isSingle: true, isSingle: true,
nodate: true, nodate: true
}} }}
/> />
</div> </div>

View File

@ -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>

View File

@ -55,20 +55,20 @@ export const NotificationsPanel = (props: Props) => {
loadedNotificationsCount, loadedNotificationsCount,
totalNotificationsCount, totalNotificationsCount,
loadNotificationsGrouped, loadNotificationsGrouped,
markSeenAll, markSeenAll
} = useNotifications() } = useNotifications()
const handleHide = () => { const handleHide = () => {
props.onClose() props.onClose()
} }
const panelRef: { current: HTMLDivElement } = { const panelRef: { current: HTMLDivElement } = {
current: null, current: null
} }
useOutsideClickHandler({ useOutsideClickHandler({
containerRef: panelRef, containerRef: panelRef,
predicate: () => props.isOpen, predicate: () => props.isOpen,
handler: () => handleHide(), handler: () => handleHide()
}) })
let windowScrollTop = 0 let windowScrollTop = 0
@ -101,13 +101,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))
) )
}) })
@ -158,14 +158,14 @@ export const NotificationsPanel = (props: Props) => {
await loadNextPage() await loadNextPage()
setIsLoading(false) setIsLoading(false)
} }
}, }
), )
) )
return ( return (
<div <div
class={clsx(styles.container, { class={clsx(styles.container, {
[styles.isOpened]: props.isOpen, [styles.isOpened]: props.isOpen
})} })}
> >
<div ref={(el) => (panelRef.current = el)} class={styles.panel}> <div ref={(el) => (panelRef.current = el)} class={styles.panel}>

View File

@ -100,7 +100,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) {
setForm(clone(prevForm)) setForm(clone(prevForm))
@ -140,7 +140,7 @@ export const ProfileSettings = () => {
const handleBeforeUnload = (event) => { const handleBeforeUnload = (event) => {
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?'
) )
} }
} }
@ -191,8 +191,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}>
@ -235,7 +235,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">

View File

@ -25,7 +25,7 @@ const scrollToHeader = (element) => {
top: top:
element.getBoundingClientRect().top - element.getBoundingClientRect().top -
document.body.getBoundingClientRect().top - document.body.getBoundingClientRect().top -
DEFAULT_HEADER_OFFSET, DEFAULT_HEADER_OFFSET
}) })
} }
@ -46,8 +46,8 @@ export const TableOfContents = (props: Props) => {
setHeadings( setHeadings(
// eslint-disable-next-line unicorn/prefer-spread // eslint-disable-next-line unicorn/prefer-spread
Array.from( Array.from(
document.querySelector(props.parentSelector).querySelectorAll<HTMLElement>('h1, h2, h3, h4'), document.querySelector(props.parentSelector).querySelectorAll<HTMLElement>('h1, h2, h3, h4')
), )
) )
setAreHeadingsLoaded(true) setAreHeadingsLoaded(true)
} }
@ -62,8 +62,8 @@ export const TableOfContents = (props: Props) => {
createEffect( createEffect(
on( on(
() => props.body, () => props.body,
() => debouncedUpdateHeadings(), () => debouncedUpdateHeadings()
), )
) )
onMount(() => { onMount(() => {
@ -79,7 +79,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}>
@ -96,7 +96,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) => {
@ -115,9 +115,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()
@ -153,9 +153,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()

View File

@ -35,7 +35,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 { author, requireAuthentication } = useSession() const { author, requireAuthentication } = useSession()
const { setFollowing, loading: subLoading } = useFollowing() const { setFollowing, loading: subLoading } = useFollowing()
@ -74,7 +74,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
@ -85,7 +85,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}>
@ -120,7 +120,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>
@ -143,7 +143,7 @@ export const TopicCard = (props: TopicProps) => {
isSubscribeButton={true} isSubscribeButton={true}
class={clsx(styles.actionButton, { class={clsx(styles.actionButton, {
[styles.isSubscribing]: subLoading(), [styles.isSubscribing]: subLoading(),
[stylesButton.subscribed]: followed(), [stylesButton.subscribed]: followed()
})} })}
// disabled={subLoading()} // disabled={subLoading()}
/> />

View File

@ -46,8 +46,8 @@ export const TopicBadge = (props: Props) => {
() => props.isFollowed, () => props.isFollowed,
() => { () => {
setIsFollowed(props.isFollowed.value) setIsFollowed(props.isFollowed.value)
}, }
), )
) )
const title = () => const title = () =>
@ -61,11 +61,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 })}')`
} }
} }
/> />

View File

View File

@ -33,7 +33,7 @@ export const AllAuthors = (props: Props) => {
const { searchParams, changeSearchParams } = useRouter<AllAuthorsPageSearchParams>() const { searchParams, changeSearchParams } = useRouter<AllAuthorsPageSearchParams>()
const { sortedAuthors } = useAuthorsStore({ const { sortedAuthors } = useAuthorsStore({
authors: props.authors, authors: props.authors,
sortBy: searchParams().by || 'name', sortBy: searchParams().by || 'name'
}) })
const [searchQuery, setSearchQuery] = createSignal('') const [searchQuery, setSearchQuery] = createSignal('')
@ -51,7 +51,7 @@ export const AllAuthors = (props: Props) => {
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => { const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
return sortedAuthors().reduce( return sortedAuthors().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>

View File

@ -39,13 +39,13 @@ export const AllTopics = (props: Props) => {
const { sortedTopics } = useTopicsStore({ const { sortedTopics } = useTopicsStore({
topics: props.topics, topics: props.topics,
sortBy: searchParams().by || 'shouts', sortBy: searchParams().by || 'shouts'
}) })
createEffect(() => { createEffect(() => {
if (!searchParams().by) { if (!searchParams().by) {
changeSearchParams({ changeSearchParams({
by: 'shouts', by: 'shouts'
}) })
} }
}) })
@ -64,7 +64,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[] }
) )
}) })
@ -112,7 +112,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 (
@ -193,7 +193,7 @@ export const AllTopics = (props: Props) => {
topic={topic} topic={topic}
isFollowed={{ isFollowed={{
loaded: filteredResults().length > 0, loaded: filteredResults().length > 0,
value: isOwnerSubscribed(topic.slug), value: isOwnerSubscribed(topic.slug)
}} }}
showStat={true} showStat={true}
/> />

View File

@ -72,7 +72,7 @@ export const AuthorView = (props: Props) => {
try { try {
const [subscriptionsResult, followersResult] = await Promise.all([ const [subscriptionsResult, followersResult] = await Promise.all([
apiClient.getAuthorFollows({ slug }), apiClient.getAuthorFollows({ slug }),
apiClient.getAuthorFollowers({ slug }), apiClient.getAuthorFollowers({ slug })
]) ])
const { authors, topics } = subscriptionsResult const { authors, topics } = subscriptionsResult
@ -98,7 +98,7 @@ export const AuthorView = (props: Props) => {
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
filters: { author: props.authorSlug }, filters: { author: props.authorSlug },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length, offset: sortedArticles().length
}) })
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
restoreScrollPosition() restoreScrollPosition()
@ -115,12 +115,12 @@ export const AuthorView = (props: Props) => {
}) })
const pages = createMemo<Shout[][]>(() => const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
) )
const fetchComments = async (commenter: Author) => { const fetchComments = async (commenter: Author) => {
const data = await apiClient.getReactionsBy({ const data = await apiClient.getReactionsBy({
by: { comment: false, created_by: commenter.id }, by: { comment: false, created_by: commenter.id }
}) })
setCommented(data) setCommented(data)
} }
@ -135,7 +135,7 @@ export const AuthorView = (props: Props) => {
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))

View File

@ -38,7 +38,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_INTERVAL = 5000 const AUTO_SAVE_INTERVAL = 5000
@ -46,7 +46,7 @@ const handleScrollTopButtonClick = (e) => {
e.preventDefault() e.preventDefault()
window.scrollTo({ window.scrollTo({
top: 0, top: 0,
behavior: 'smooth', behavior: 'smooth'
}) })
} }
@ -61,7 +61,7 @@ export const EditView = (props: Props) => {
setFormErrors, setFormErrors,
saveDraft, saveDraft,
saveDraftToLocalStorage, saveDraftToLocalStorage,
getDraftFromLocalStorage, getDraftFromLocalStorage
} = useEditorContext() } = useEditorContext()
const shoutTopics = props.shout.topics || [] const shoutTopics = props.shout.topics || []
const draft = getDraftFromLocalStorage(props.shout.id) const draft = getDraftFromLocalStorage(props.shout.id)
@ -81,7 +81,7 @@ export const EditView = (props: Props) => {
body: props.shout.body, body: props.shout.body,
coverImageUrl: props.shout.cover, coverImageUrl: props.shout.cover,
media: props.shout.media, media: props.shout.media,
layout: props.shout.layout, layout: props.shout.layout
}) })
} }
@ -112,7 +112,7 @@ export const EditView = (props: Props) => {
const handleBeforeUnload = (event) => { const handleBeforeUnload = (event) => {
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?'
) )
} }
} }
@ -151,7 +151,7 @@ export const EditView = (props: Props) => {
const [baseAudioFields, setBaseAudioFields] = createSignal({ const [baseAudioFields, setBaseAudioFields] = createSignal({
artist: '', artist: '',
date: '', date: '',
genre: '', genre: ''
}) })
const handleBaseFieldsChange = (key, value) => { const handleBaseFieldsChange = (key, value) => {
@ -225,7 +225,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}
> >
@ -350,8 +350,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')}>

View File

@ -41,7 +41,7 @@ export const Expo = (props: Props) => {
// }) // })
const { sortedArticles } = useArticlesStore({ const { sortedArticles } = useArticlesStore({
shouts: props.shouts || [], shouts: props.shouts || [],
layout: props.layout, layout: props.layout
}) })
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => { const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
@ -61,7 +61,7 @@ export const Expo = (props: Props) => {
const options: LoadShoutsOptions = { const options: LoadShoutsOptions = {
filters: getLoadShoutsFilters(), filters: getLoadShoutsFilters(),
limit: count, limit: count,
offset: sortedArticles().length, offset: sortedArticles().length
} }
options.filters = props.layout options.filters = props.layout
@ -82,7 +82,7 @@ export const Expo = (props: Props) => {
const options: LoadShoutsOptions = { const options: LoadShoutsOptions = {
filters: getLoadShoutsFilters(), filters: getLoadShoutsFilters(),
limit: 10, limit: 10,
random_limit: 100, random_limit: 100
} }
const result = await apiClient.getRandomTopShouts({ options }) const result = await apiClient.getRandomTopShouts({ options })
@ -96,7 +96,7 @@ export const Expo = (props: Props) => {
const options: LoadShoutsOptions = { const options: LoadShoutsOptions = {
filters: getLoadShoutsFilters({ after }), filters: getLoadShoutsFilters({ after }),
limit: 10, limit: 10,
random_limit: 10, random_limit: 10
} }
const result = await apiClient.getRandomTopShouts({ options }) const result = await apiClient.getRandomTopShouts({ options })
@ -104,7 +104,7 @@ export const Expo = (props: Props) => {
} }
const pages = createMemo<Shout[][]>(() => const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
) )
onMount(() => { onMount(() => {
@ -135,8 +135,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(() => {

View File

@ -101,12 +101,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 { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>() const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
@ -168,14 +168,14 @@ export const FeedView = (props: Props) => {
resetSortedArticles() resetSortedArticles()
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: sortedArticles().length, offset: sortedArticles().length
} }
const orderBy = getOrderBy(searchParams().by) const orderBy = getOrderBy(searchParams().by)
@ -189,7 +189,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'
} }
} }
@ -208,8 +208,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)
@ -217,7 +217,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')
@ -250,7 +250,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={getPagePath(router, page().route)}>{t('Recent')}</a> <a href={getPagePath(router, page().route)}>{t('Recent')}</a>
@ -260,7 +260,7 @@ export const FeedView = (props: Props) => {
{/*</li>*/} {/*</li>*/}
<li <li
class={clsx({ class={clsx({
'view-switcher__item--selected': searchParams().by === 'rating', 'view-switcher__item--selected': searchParams().by === 'rating'
})} })}
> >
<span class="link" onClick={() => changeSearchParams({ by: 'rating' })}> <span class="link" onClick={() => changeSearchParams({ by: 'rating' })}>
@ -269,7 +269,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' })}>
@ -362,7 +362,7 @@ export const FeedView = (props: Props) => {
<div class={clsx('text-truncate', styles.commentBody)}> <div class={clsx('text-truncate', styles.commentBody)}>
<a <a
href={`${getPagePath(router, 'article', { href={`${getPagePath(router, 'article', {
slug: comment.shout.slug, slug: comment.shout.slug
})}?commentId=${comment.id}`} })}?commentId=${comment.id}`}
innerHTML={comment.body} innerHTML={comment.body}
/> />

View File

@ -9,7 +9,7 @@ import {
loadShouts, loadShouts,
loadTopArticles, loadTopArticles,
loadTopMonthArticles, loadTopMonthArticles,
useArticlesStore, useArticlesStore
} from '../../stores/zine/articles' } from '../../stores/zine/articles'
import { useTopAuthorsStore } from '../../stores/zine/topAuthors' import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
@ -44,7 +44,7 @@ const LOAD_MORE_PAGE_SIZE = 16 // Row1 + Row3 + Row2 + Beside (3 + 1) + Row1 + R
export const HomeView = (props: Props) => { export const HomeView = (props: Props) => {
const { sortedArticles, topArticles, topCommentedArticles, topMonthArticles, topViewedArticles } = const { sortedArticles, topArticles, topCommentedArticles, topMonthArticles, topViewedArticles } =
useArticlesStore({ useArticlesStore({
shouts: props.shouts, shouts: props.shouts
}) })
const { topTopics } = useTopicsStore() const { topTopics } = useTopicsStore()
@ -62,7 +62,7 @@ export const HomeView = (props: Props) => {
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
filters: { featured: true }, filters: { featured: true },
limit: CLIENT_LOAD_ARTICLES_COUNT, limit: CLIENT_LOAD_ARTICLES_COUNT,
offset: sortedArticles().length, offset: sortedArticles().length
}) })
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
@ -84,7 +84,7 @@ export const HomeView = (props: Props) => {
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
filters: { featured: true }, filters: { featured: true },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length, offset: sortedArticles().length
}) })
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
@ -95,8 +95,8 @@ export const HomeView = (props: Props) => {
splitToPages( splitToPages(
sortedArticles(), sortedArticles(),
PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT, PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT,
LOAD_MORE_PAGE_SIZE, LOAD_MORE_PAGE_SIZE
), )
) )
return ( return (

View File

@ -56,7 +56,7 @@ export const InboxView = (props: Props) => {
const { changeSearchParams, searchParams } = useRouter<InboxSearchParams>() const { changeSearchParams, searchParams } = useRouter<InboxSearchParams>()
const messagesContainerRef: { current: HTMLDivElement } = { const messagesContainerRef: { current: HTMLDivElement } = {
current: null, current: null
} }
const getQuery = (query) => { const getQuery = (query) => {
@ -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 {
await getMessages(chat.id) await getMessages(chat.id)
@ -77,7 +77,7 @@ export const InboxView = (props: Props) => {
} finally { } finally {
messagesContainerRef.current.scroll({ messagesContainerRef.current.scroll({
top: messagesContainerRef.current.scrollHeight, top: messagesContainerRef.current.scrollHeight,
behavior: 'instant', behavior: 'instant'
}) })
} }
} }
@ -86,7 +86,7 @@ export const InboxView = (props: Props) => {
sendMessage({ sendMessage({
body: message, body: message,
chat_id: currentDialog()?.id.toString(), chat_id: currentDialog()?.id.toString(),
reply_to: messageToReply()?.id, reply_to: messageToReply()?.id
}) })
setClear(true) setClear(true)
setMessageToReply(null) setMessageToReply(null)
@ -107,7 +107,7 @@ export const InboxView = (props: Props) => {
await loadChats() await loadChats()
changeSearchParams({ changeSearchParams({
initChat: null, initChat: null,
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) await handleOpenChat(chatToOpen)
@ -147,11 +147,11 @@ export const InboxView = (props: Props) => {
} }
messagesContainerRef.current.scroll({ messagesContainerRef.current.scroll({
top: messagesContainerRef.current.scrollHeight, top: messagesContainerRef.current.scrollHeight,
behavior: 'smooth', behavior: 'smooth'
}) })
}, }
), ),
{ defer: true }, { defer: true }
) )
const handleScrollMessageContainer = () => { const handleScrollMessageContainer = () => {
if ( if (
@ -166,7 +166,7 @@ export const InboxView = (props: Props) => {
const handleScrollToNew = () => { const handleScrollToNew = () => {
messagesContainerRef.current.scroll({ messagesContainerRef.current.scroll({
top: messagesContainerRef.current.scrollHeight, top: messagesContainerRef.current.scrollHeight,
behavior: 'smooth', behavior: 'smooth'
}) })
setIsScrollToNewVisible(false) setIsScrollToNewVisible(false)
} }

View File

@ -38,7 +38,7 @@ const shorten = (str: string, maxLen: number) => {
const EMPTY_TOPIC: Topic = { const EMPTY_TOPIC: Topic = {
id: -1, id: -1,
slug: '', slug: ''
} }
const emptyConfig = { const emptyConfig = {
coverImageUrl: '', coverImageUrl: '',
@ -47,7 +47,7 @@ const emptyConfig = {
title: '', title: '',
subtitle: '', subtitle: '',
description: '', description: '',
selectedTopics: [], selectedTopics: []
} }
export const PublishSettings = (props: Props) => { export const PublishSettings = (props: Props) => {
@ -74,7 +74,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: []
} }
}) })
@ -105,7 +105,7 @@ export const PublishSettings = (props: Props) => {
setSettingsForm((prev) => { setSettingsForm((prev) => {
return { return {
...prev, ...prev,
mainTopic: newSelectedTopics[0], mainTopic: newSelectedTopics[0]
} }
}) })
} }
@ -118,7 +118,7 @@ export const PublishSettings = (props: Props) => {
const handleBackClick = () => { const handleBackClick = () => {
redirectPage(router, 'edit', { redirectPage(router, 'edit', {
shoutId: props.shoutId.toString(), shoutId: props.shoutId.toString()
}) })
} }
const handleCancelClick = () => { const handleCancelClick = () => {
@ -163,7 +163,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}>
@ -183,7 +183,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>
@ -229,7 +229,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}>

View File

@ -40,7 +40,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)
@ -80,14 +80,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>

View File

@ -57,8 +57,8 @@ export const TopicView = (props: Props) => {
lang() === 'en' lang() === 'en'
? topic()?.slug.replace(/-/, ' ') ? topic()?.slug.replace(/-/, ' ')
: topic()?.title || topic()?.slug.replace(/-/, ' '), : topic()?.title || topic()?.slug.replace(/-/, ' '),
true, true
)}`, )}`
) )
const loadMore = async () => { const loadMore = async () => {
@ -67,7 +67,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: sortedArticles().length, offset: sortedArticles().length
}) })
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
@ -89,7 +89,7 @@ export const TopicView = (props: Props) => {
}) })
*/ */
const pages = createMemo<Shout[][]>(() => const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
) )
const ogImage = () => const ogImage = () =>
@ -120,14 +120,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'
}) })
} }
> >

View File

@ -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}

View File

@ -63,14 +63,14 @@ export const DropArea = (props: Props) => {
const { files, selectFiles } = createFileUploader({ const { files, 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())
}, }
}) })
const handleDrag = (event) => { const handleDrag = (event) => {
if (event.type === 'dragenter' || event.type === 'dragover') { if (event.type === 'dragenter' || event.type === 'dragover') {

View File

@ -47,7 +47,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>

View File

@ -20,7 +20,7 @@ export const DropdownSelect = (props: Props) => {
const [isDropDownVisible, setIsDropDownVisible] = createSignal(false) const [isDropDownVisible, setIsDropDownVisible] = createSignal(false)
const containerRef: { current: HTMLElement } = { const containerRef: { current: HTMLElement } = {
current: null, current: null
} }
const handleShowDropdown = () => { const handleShowDropdown = () => {
@ -30,7 +30,7 @@ export const DropdownSelect = (props: Props) => {
useOutsideClickHandler({ useOutsideClickHandler({
containerRef, containerRef,
predicate: () => isDropDownVisible(), predicate: () => isDropDownVisible(),
handler: () => setIsDropDownVisible(false), handler: () => setIsDropDownVisible(false)
}) })
return ( return (

View File

@ -16,7 +16,7 @@ export default (props: Props) => {
return ( return (
<div <div
class={clsx(styles.PanelWrapper, { class={clsx(styles.PanelWrapper, {
[styles.PanelWrapperVisible]: props.isVisible, [styles.PanelWrapperVisible]: props.isVisible
})} })}
> >
<Button <Button

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