Feature/progressive image (#322)

* progressive image

* progressive image v0.2

* progressive images v0.3

* SimplifiedEditor async load, hydration script moved to the bottom

* GrowingTextarea optimization

* static images moved to storj

---------

Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
Igor Lobanov 2023-11-18 15:10:02 +01:00 committed by GitHub
parent cbf6daa034
commit 8cad60bdda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 784 additions and 238 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,18 +0,0 @@
{
"theme_color": "#111111",
"background_color": "#ffffff",
"display": "standalone",
"scope": "/",
"start_url": "/",
"name": "discours.io",
"short_name": "discours.io",
"description": "Дискурс - коллаборативная журналистика",
"icons": [
{
"src": "/favicon.png",
"sizes": "200x200",
"type": "image/png",
"purpose": "any maskable"
}
]
}

View File

@ -21,7 +21,7 @@ export const AudioHeader = (props: Props) => {
return ( return (
<div class={clsx(styles.AudioHeader, { [styles.expandedImage]: expandedImage() })}> <div class={clsx(styles.AudioHeader, { [styles.expandedImage]: expandedImage() })}>
<div class={styles.cover}> <div class={styles.cover}>
<Image class={styles.image} src={props.cover} alt={props.title} width={200} /> <Image class={styles.image} src={props.cover} alt={props.title} width={100} />
<Show when={props.cover}> <Show when={props.cover}>
<button type="button" class={styles.expand} onClick={() => setExpandedImage(!expandedImage())}> <button type="button" class={styles.expand} onClick={() => setExpandedImage(!expandedImage())}>
<Icon name="expand-circle" /> <Icon name="expand-circle" />

View File

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

View File

@ -1,5 +1,5 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Show, createMemo, createSignal, onMount, For } from 'solid-js' import { Show, createMemo, createSignal, onMount, For, lazy } from 'solid-js'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions' import { useReactions } from '../../context/reactions'
@ -8,12 +8,13 @@ import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { byCreated } from '../../utils/sortby' import { byCreated } from '../../utils/sortby'
import { Button } from '../_shared/Button' import { Button } from '../_shared/Button'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import SimplifiedEditor from '../Editor/SimplifiedEditor'
import { Comment } from './Comment' import { Comment } from './Comment'
import styles from './Article.module.scss' import styles from './Article.module.scss'
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
type CommentsOrder = 'createdAt' | 'rating' | 'newOnly' type CommentsOrder = 'createdAt' | 'rating' | 'newOnly'
const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => { const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {

View File

@ -0,0 +1,40 @@
import type { CoverImageProps } from './types'
import { CoverImage1 } from './images/CoverImage1'
import { CoverImage10 } from './images/CoverImage10'
import { CoverImage11 } from './images/CoverImage11'
import { CoverImage12 } from './images/CoverImage12'
import { CoverImage2 } from './images/CoverImage2'
import { CoverImage3 } from './images/CoverImage3'
import { CoverImage4 } from './images/CoverImage4'
import { CoverImage5 } from './images/CoverImage5'
import { CoverImage6 } from './images/CoverImage6'
import { CoverImage7 } from './images/CoverImage7'
import { CoverImage8 } from './images/CoverImage8'
import { CoverImage9 } from './images/CoverImage9'
// not pretty, but I don't want to use dynamic imports
const coverImages = [
CoverImage1,
CoverImage2,
CoverImage3,
CoverImage4,
CoverImage5,
CoverImage6,
CoverImage7,
CoverImage8,
CoverImage9,
CoverImage10,
CoverImage11,
CoverImage12,
]
let counter = 0
export const CoverImage = (props: CoverImageProps) => {
const CoverImageComponent = coverImages[counter]
counter++
if (counter === coverImages.length) {
counter = 0
}
return <CoverImageComponent {...props} />
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
import { CoverImageProps } from '../types'
export const CoverImage10 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
stroke="#000"
stroke-width=".25"
stroke-miterlimit="3.864"
d="M-2.531-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM9.373-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM21.279-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM33.184-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM45.09-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM56.996-.107H65.5v8.503zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zm0 11.905H65.5v8.504zm0 11.907H65.5v8.504zm0 11.904H65.5v8.504zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zm0 11.906H65.5v8.504zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zM68.9-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM80.807-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM92.713-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM104.617-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM116.523-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504z"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage11 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="4.949"
stroke-miterlimit="3.864"
d="M2.457-.255v5.262h5.262m9.47-5.262v5.262h5.261m9.469-5.262v5.262h5.262m9.47-5.262v5.262h5.262m9.469-5.262v5.262h5.261m9.47-5.262v5.262h5.262m9.471-5.262v5.262h5.261m9.469-5.262v5.262h5.261M2.457 29.209v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 58.671v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 88.133v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 117.596v2.261m14.732-2.261v2.261m14.73-2.261v2.261m14.732-2.261v2.261m14.731-2.261v2.261m14.731-2.261v2.261m14.733-2.261v2.261m14.73-2.261v2.261M-1.909 19.737H.353m9.47-5.26v5.26h5.262m9.468-5.26v5.26h5.261m9.471-5.26v5.26h5.262m9.471-5.26v5.26h5.261m9.469-5.26v5.26h5.261m9.47-5.26v5.26h5.262m9.469-5.26v5.26h5.261m9.471-5.26v5.26h5.261M-1.909 49.199H.353m9.47-5.26v5.26h5.262m9.468-5.26v5.26h5.261m9.471-5.26v5.26h5.262m9.471-5.26v5.26h5.261m9.469-5.26v5.26h5.261m9.47-5.26v5.26h5.262m9.469-5.26v5.26h5.261m9.471-5.26v5.26h5.261M-1.909 78.663H.353m9.47-5.262v5.262h5.262m9.468-5.262v5.262h5.261m9.471-5.262v5.262h5.262m9.471-5.262v5.262h5.261m9.469-5.262v5.262h5.261m9.47-5.262v5.262h5.262m9.469-5.262v5.262h5.261m9.471-5.262v5.262h5.261M-1.909 108.126H.353m9.47-5.261v5.261h5.262m9.468-5.261v5.261h5.261m9.471-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.471-5.261v5.261h5.261"
/>
</svg>
)

View File

@ -0,0 +1,12 @@
import { CoverImageProps } from '../types'
export const CoverImage12 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
stroke="#000"
stroke-width="3.23"
stroke-miterlimit="3.864"
d="M-.509-2.401h4.578v4.578H-.509v-4.578zm0 12.819h4.578v4.578H-.509v-4.578zm0 12.817h4.578v4.578H-.509v-4.578zm0 12.82h4.578v4.578H-.509v-4.578zm0 12.819h4.578v4.578H-.509v-4.578zm0 12.818h4.578v4.579H-.509v-4.579zm0 12.82h4.578v4.578H-.509v-4.578zm0 12.818h4.578v4.579H-.509V87.33zm0 12.818h4.578v4.579H-.509v-4.579zm0 12.82h4.578v4.578H-.509v-4.578zM12.311-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM25.128-2.401h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.817h4.579v4.578h-4.579v-4.578zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579V87.33zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zM37.947-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM50.767-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM63.586-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM76.404-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM89.223-2.401h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.817h4.579v4.578h-4.579v-4.578zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579V87.33zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm12.82-115.369h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM114.86-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578z"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage2 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="2"
stroke-miterlimit="3.864"
d="m-1.753-1.524-3.592 7.179h7.18zm14.36 0-3.59 7.179h7.179zm14.362 0-3.592 7.179h7.18zm14.36 0-3.591 7.179h7.18zm14.361 0-3.591 7.179h7.18zm14.362 0-3.593 7.179h7.181zm14.36 0-3.593 7.179h7.182zm14.359 0-3.59 7.179h7.18zm14.362 0-3.591 7.179h7.18zM-1.753 27.199l-3.592 7.18h7.18zm14.36 0-3.59 7.18h7.179zm14.362 0-3.592 7.18h7.18zm14.36 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.18zm14.362 0-3.593 7.18h7.181zm14.36 0-3.593 7.18h7.182zm14.359 0-3.59 7.18h7.18zm14.362 0-3.591 7.18h7.18zM-1.753 55.921l-3.592 7.18h7.18zm14.36 0-3.59 7.18h7.179zm14.362 0-3.592 7.18h7.18zm14.36 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.18zm14.362 0-3.593 7.18h7.181zm14.36 0-3.593 7.18h7.182zm14.359 0-3.59 7.18h7.18zm14.362 0-3.591 7.18h7.18zM-1.753 84.64l-3.592 7.182h7.18zm14.36 0-3.59 7.182h7.179zm14.362 0-3.592 7.182h7.18zm14.36 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.18zm14.362 0-3.593 7.182h7.181zm14.36 0-3.593 7.182h7.182zm14.359 0-3.59 7.182h7.18zm14.362 0-3.591 7.182h7.18zM-1.753 113.363l-3.592 7.182h7.18zm14.36 0-3.59 7.182h7.179zm14.362 0-3.592 7.182h7.18zm14.36 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.18zm14.362 0-3.593 7.182h7.181zm14.36 0-3.593 7.182h7.182zm14.359 0-3.59 7.182h7.18zm14.362 0-3.591 7.182h7.18zM5.428 12.838l-3.593 7.18h7.182zm14.361 0-3.593 7.18h7.181zm14.36 0-3.592 7.18h7.181zm14.36 0-3.591 7.18h7.181zm14.361 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.179zm14.361 0-3.591 7.18h7.18zm14.361 0-3.592 7.18h7.181zm14.36 0-3.591 7.18h7.18zM5.428 41.56l-3.593 7.179h7.182zm14.361 0-3.593 7.179h7.181zm14.36 0-3.592 7.179h7.181zm14.36 0-3.591 7.179h7.181zm14.361 0-3.591 7.179h7.18zm14.361 0-3.591 7.179h7.179zm14.361 0-3.591 7.179h7.18zm14.361 0-3.592 7.179h7.181zm14.36 0-3.591 7.179h7.18zM5.428 70.279l-3.593 7.183h7.182zm14.361 0-3.593 7.183h7.181zm14.36 0-3.592 7.183h7.181zm14.36 0-3.591 7.183h7.181zm14.361 0-3.591 7.183h7.18zm14.361 0-3.591 7.183h7.179zm14.361 0-3.591 7.183h7.18zm14.361 0-3.592 7.183h7.181zm14.36 0-3.591 7.183h7.18zM5.428 99.002l-3.593 7.182h7.182zm14.361 0-3.593 7.182h7.181zm14.36 0-3.592 7.182h7.181zm14.36 0-3.591 7.182h7.181zm14.361 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.179zm14.361 0-3.591 7.182h7.18zm14.361 0-3.592 7.182h7.181zm14.36 0-3.591 7.182h7.18z"
/>
</svg>
)

View File

@ -0,0 +1,13 @@
import { CoverImageProps } from '../types'
export const CoverImage3 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
d="M3.647 8.782h5.291M1 .845v5.29m18.521 2.647h5.29M16.874.845v5.29m18.522 2.647h5.289M32.748.845v5.29M51.27 8.782h5.289M48.622.845v5.29m18.52 2.647h5.29M64.495.845v5.29m18.522 2.647h5.29M80.371.845v5.29m18.52 2.647h5.291M96.244.845v5.29m18.521 2.647h5.29M112.118.845v5.29M3.647 24.656h5.291M1 16.719v5.29m18.521 2.647h5.29m-7.937-7.937v5.289m18.522 2.648h5.289m-7.937-7.937v5.289m18.522 2.648h5.289m-7.937-7.937v5.289m18.52 2.648h5.29m-7.937-7.937v5.289m18.522 2.648h5.29m-7.936-7.937v5.289m18.52 2.648h5.291m-7.938-7.937v5.289m18.521 2.648h5.29m-7.937-7.937v5.289M3.647 40.53h5.291M1 32.593v5.29m18.521 2.647h5.29m-7.937-7.938v5.291m18.522 2.647h5.289m-7.937-7.938v5.291M51.27 40.53h5.289m-7.937-7.938v5.291m18.52 2.647h5.29m-7.937-7.938v5.291m18.522 2.647h5.29m-7.936-7.938v5.291m18.52 2.647h5.291m-7.938-7.938v5.291m18.521 2.647h5.29m-7.937-7.938v5.291M3.647 56.403h5.291M1 48.467v5.29m18.521 2.646h5.29m-7.937-7.936v5.29m18.522 2.646h5.289m-7.937-7.936v5.29m18.522 2.646h5.289m-7.937-7.936v5.29m18.52 2.646h5.29m-7.937-7.936v5.29m18.522 2.646h5.29m-7.936-7.936v5.29m18.52 2.646h5.291m-7.938-7.936v5.29m18.521 2.646h5.29m-7.937-7.936v5.29M3.647 72.277h5.291M1 64.34v5.29m18.521 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.52 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.29m-7.936-7.937v5.29m18.52 2.647h5.291m-7.938-7.937v5.29m18.521 2.647h5.29m-7.937-7.937v5.29M3.647 88.15h5.291M1 80.215v5.289m18.521 2.646h5.29m-7.937-7.935v5.289m18.522 2.646h5.289m-7.937-7.935v5.289M51.27 88.15h5.289m-7.937-7.935v5.289m18.52 2.646h5.29m-7.937-7.935v5.289m18.522 2.646h5.29m-7.936-7.935v5.289m18.52 2.646h5.291m-7.938-7.935v5.289m18.521 2.646h5.29m-7.937-7.935v5.289M3.647 104.025h5.291M1 96.088v5.29m18.521 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.52 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.29m-7.936-7.937v5.29m18.52 2.647h5.291m-7.938-7.937v5.29m18.521 2.647h5.29m-7.937-7.937v5.29M1 111.963v5.29m15.874-5.29v5.29m15.874-5.29v5.29m15.874-5.29v5.29m15.873-5.29v5.29m15.876-5.29v5.29m15.873-5.29v5.29m15.874-5.29v5.29"
fill="none"
stroke="#000"
stroke-width="3.733"
stroke-miterlimit="3.864"
/>
</svg>
)

View File

@ -0,0 +1,24 @@
import { CoverImageProps } from '../types'
export const CoverImage4 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#231F20"
stroke-width=".974"
stroke-miterlimit="3.864"
d="M-1.149-.979 1.772 120M1.611-.979 4.533 120M4.37-.979 7.293 120M7.133-.979 10.054 120m-.159-92.634 2.92 120.98m2.599-74.662 2.921 100.977M15.414-.979 18.336 120M18.174-.979 21.096 120M20.935-.979 23.856 120m1.299-92.634 2.921 120.98m2.6-58.311 2.922 120.979M29.216-.979 32.137 120M31.978-.979 34.899 120m-4.382-89.798 2.921 120.98M37.499-.979 40.42 120M40.259-.979 43.182 120M43.019-.979 45.94 120m-5.681-89.798 2.922 120.98M48.541-.979 51.462 120M51.301-.979 54.222 120M54.062-.979 56.982 120M49.84 49.547l2.921 120.98M62.343-.979 65.265 120M65.102-.979 68.023 120M67.863-.979 70.785 120M70.624-.979 73.546 120m-2.922-87.462 2.922 120.98M78.904-.979 81.826 120M81.666-.979 84.587 120M84.426-.979 87.348 120M87.188-.979 90.109 120M89.948-.979 92.869 120M92.708-.979 95.63 120m-4.383-32.145 2.922 100.979M98.229-.979 101.15 120M100.989-.979 103.91 120M103.75-.979 106.672 120M106.51-.979 109.432 120M109.271-.979 112.192 120m-5.682-64.286 2.922 120.979M114.791-.979 117.713 120m-.16-120.979L120.474 120M120.312-.979 123.234 120"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#231F20"
stroke-width=".984"
stroke-miterlimit="3.864"
d="M6.316-.016V120m11.858-60.97v120.016m8.442-165.37v120.015M31.418-.016V120M39.784-.016V120m11.517-31.662v100.014m8.281-132.156v120.016m9.742-113.424v120.016M89.987-.016V120M98.354-.016V120M106.722-.016V120M115.088-.016V120"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage5 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
d="M.679 8.708 8.666.722m0 7.986L16.65.722m0 7.986L24.635.722m0 7.986L32.622.722m0 7.986L40.607.722m0 7.986 7.99-7.986m0 7.986L56.58.722m0 7.986L64.568.722m0 7.986L72.555.722m0 7.986L80.542.722m0 7.986L88.527.722m0 7.986L96.513.722m0 7.986L104.5.722m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 16.694l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 24.681l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 32.666l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 40.653l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 48.641l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988M.679 56.625l7.987-7.984m0 7.984 7.984-7.984m0 7.984 7.985-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.99-7.984m0 7.984 7.983-7.984m0 7.984 7.988-7.984m0 7.984 7.987-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.986-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.988-7.984M.679 64.611l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 72.597l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 80.585l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988M.679 88.57l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 96.556l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 104.543l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 112.528l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 120.516l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988"
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="1.878"
stroke-miterlimit="3.864"
/>
</svg>
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,209 @@
import { CoverImageProps } from '../types'
export const CoverImage8 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M129.44-7.29c0 11.343-11.396 11.343-11.396 22.792m.001 0c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M124.886-7.29c0 11.343-11.398 11.343-11.398 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M113.487 15.502c0 11.452 11.398 11.452 11.398 22.791 0 11.342-11.398 11.342-11.398 22.796m0 0c0 11.447 11.398 11.447 11.398 22.79 0 11.341-11.398 11.341-11.398 22.788m0 0c0 11.452 11.398 11.452 11.398 22.791M120.326-7.29c0 11.343-11.401 11.343-11.401 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M108.925 15.502c0 11.452 11.401 11.452 11.401 22.791 0 11.342-11.401 11.342-11.401 22.796m0 0c0 11.447 11.401 11.447 11.401 22.79 0 11.341-11.401 11.341-11.401 22.788m0 0c0 11.452 11.401 11.452 11.401 22.791m-4.669-136.83c.018 11.343-11.377 11.361-11.358 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M104.299 15.438c.018 11.453 11.412 11.435 11.431 22.773.019 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.414 11.428 11.432 22.771.019 11.342-11.377 11.36-11.359 22.807m-.001 0c.019 11.453 11.414 11.435 11.432 22.773M111.096-7.365c.018 11.344-11.375 11.362-11.356 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M99.739 15.445c.018 11.453 11.41 11.435 11.429 22.773.018 11.341-11.375 11.359-11.356 22.814m0 .001c.019 11.447 11.411 11.428 11.429 22.771.019 11.341-11.374 11.359-11.356 22.806m-.001 0c.019 11.453 11.411 11.435 11.429 22.773m-4.778-136.74c.018 11.343-11.369 11.361-11.351 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M95.185 15.453c.019 11.452 11.404 11.434 11.423 22.772.018 11.342-11.368 11.36-11.35 22.815m0 .001c.019 11.446 11.405 11.428 11.423 22.771.019 11.341-11.368 11.36-11.351 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M95.33 106.618c.019 11.453 11.405 11.434 11.423 22.773M101.981-7.351c.018 11.344-11.375 11.363-11.356 22.811m0 0c.018 11.453 11.41 11.435 11.429 22.773.018 11.341-11.375 11.359-11.356 22.814m-.001.001c.019 11.446 11.412 11.428 11.43 22.771.019 11.341-11.375 11.359-11.357 22.806m0 0c.019 11.453 11.412 11.435 11.43 22.773M97.428-7.343C97.445 4 86.044 4.019 86.062 15.468"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M86.062 15.468C86.08 26.92 97.481 26.901 97.5 38.24c.018 11.342-11.383 11.36-11.364 22.814m0 .001c.019 11.447 11.419 11.429 11.437 22.771.02 11.341-11.382 11.359-11.364 22.807m-.001 0c.019 11.452 11.42 11.434 11.438 22.772M92.868-7.336c.018 11.343-11.384 11.362-11.365 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M81.503 15.475c.018 11.453 11.419 11.435 11.438 22.773.018 11.341-11.384 11.359-11.365 22.814m-.001 0c.019 11.446 11.42 11.428 11.438 22.771.019 11.342-11.383 11.36-11.365 22.807m-.001 0c.019 11.453 11.42 11.435 11.438 22.773M88.308-7.328c.018 11.343-11.377 11.361-11.358 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M76.949 15.481c.018 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.447 11.414 11.429 11.432 22.771.019 11.341-11.377 11.359-11.359 22.807m0 0c.019 11.452 11.414 11.434 11.432 22.772M83.748-7.321c.018 11.342-11.38 11.361-11.361 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M72.387 15.489c.018 11.453 11.415 11.434 11.434 22.773.018 11.341-11.38 11.359-11.361 22.814m-.001.001c.02 11.446 11.416 11.428 11.434 22.771.019 11.342-11.378 11.36-11.36 22.807m-.001-.001c.019 11.453 11.415 11.435 11.433 22.773M79.191-7.314c.018 11.343-11.375 11.362-11.356 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M67.835 15.496c.018 11.453 11.411 11.435 11.43 22.773.018 11.341-11.376 11.359-11.356 22.814m-.001.001c.019 11.446 11.411 11.428 11.429 22.771.019 11.341-11.374 11.359-11.356 22.806m-.001 0c.019 11.453 11.411 11.435 11.429 22.773M74.632-7.307c.017 11.343-11.377 11.362-11.359 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M63.273 15.504c.018 11.452 11.412 11.434 11.431 22.772.018 11.342-11.377 11.36-11.358 22.815m0 .001c.019 11.446 11.413 11.428 11.431 22.771.019 11.341-11.376 11.359-11.358 22.807m-.001-.001c.019 11.452 11.414 11.434 11.432 22.773M70.078-7.3C70.096 4.044 58.701 4.062 58.72 15.511"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M58.72 15.511c.018 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.413 11.428 11.431 22.771.019 11.341-11.376 11.359-11.358 22.806m-.001 0c.019 11.453 11.413 11.435 11.431 22.773M65.518-7.292c.018 11.343-11.375 11.361-11.356 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M54.161 15.518c.018 11.453 11.411 11.435 11.43 22.773.018 11.341-11.376 11.36-11.356 22.814m-.001 0c.019 11.447 11.411 11.429 11.429 22.771.019 11.341-11.374 11.359-11.356 22.807m0 .001c.019 11.452 11.411 11.434 11.429 22.772M60.964-7.285C60.981 4.058 49.581 4.076 49.6 15.525"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M49.6 15.525c.018 11.453 11.418 11.435 11.438 22.773.018 11.341-11.384 11.359-11.365 22.814m-.001.001c.019 11.446 11.42 11.428 11.438 22.771.019 11.342-11.383 11.36-11.365 22.807m-.001-.001c.019 11.453 11.42 11.435 11.438 22.773M56.398-7.278c.018 11.344-11.38 11.362-11.361 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M45.037 15.532c.018 11.453 11.415 11.435 11.434 22.773.018 11.341-11.38 11.359-11.361 22.814m-.001.001c.019 11.447 11.416 11.428 11.434 22.771.019 11.341-11.379 11.359-11.361 22.806m0 0c.02 11.453 11.416 11.435 11.434 22.773M51.842-7.271C51.859 4.072 40.467 4.091 40.485 15.54"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M40.485 15.54c.018 11.452 11.41 11.434 11.429 22.772.018 11.342-11.375 11.36-11.356 22.815m0 .001c.019 11.446 11.412 11.428 11.43 22.771.019 11.341-11.374 11.36-11.356 22.807m-.001-.001c.019 11.453 11.411 11.434 11.429 22.773M47.284-7.264c.018 11.344-11.38 11.362-11.36 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M35.924 15.547C35.941 27 47.338 26.981 47.356 38.32c.018 11.341-11.379 11.359-11.36 22.814m0 .001c.019 11.446 11.415 11.428 11.433 22.771.019 11.341-11.378 11.359-11.36 22.806m-.001 0c.019 11.453 11.416 11.435 11.434 22.773M42.722-7.256c.017 11.343-11.377 11.361-11.359 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M31.363 15.555c.019 11.452 11.412 11.434 11.432 22.772.018 11.342-11.378 11.36-11.359 22.814m0 .001c.018 11.447 11.414 11.428 11.431 22.771.019 11.341-11.377 11.359-11.359 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M31.508 106.72c.02 11.452 11.414 11.434 11.432 22.772M38.168-7.249C38.186 4.094 26.791 4.113 26.81 15.562m0 0c.019 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001 0C26.9 72.596 38.296 72.577 38.313 83.92c.019 11.342-11.377 11.36-11.359 22.807m0 0c.019 11.453 11.414 11.435 11.432 22.773M33.614-7.24C33.632 4.102 22.23 4.12 22.25 15.568"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M22.25 15.568c.019 11.453 11.418 11.435 11.437 22.773.018 11.341-11.383 11.359-11.364 22.814m-.001.001c.019 11.447 11.42 11.429 11.438 22.771.019 11.341-11.383 11.359-11.365 22.807m0 0c.019 11.452 11.42 11.434 11.438 22.772M29.055-7.234c.017 11.342-11.386 11.361-11.367 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M17.688 15.576c.018 11.453 11.421 11.434 11.439 22.774.018 11.34-11.386 11.359-11.367 22.814m0 0c.019 11.446 11.422 11.428 11.439 22.771.019 11.342-11.385 11.36-11.367 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M17.832 106.741c.019 11.453 11.422 11.435 11.439 22.773M24.492-7.227c.018 11.344-11.377 11.362-11.358 22.81m0 0c.019 11.453 11.413 11.436 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.413 11.428 11.431 22.771.02 11.341-11.376 11.359-11.358 22.806m-.001 0c.019 11.453 11.414 11.435 11.432 22.773M20.043-7.29c0 11.343-11.396 11.343-11.396 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M8.646 15.502c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M15.487-7.29c0 11.343-11.396 11.343-11.396 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M4.092 15.502c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M10.927-7.29C10.927 4.053-.47 4.053-.47 15.502"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-.47 15.502c0 11.452 11.396 11.452 11.396 22.791C10.926 49.635-.47 49.635-.47 61.089m0 0c0 11.447 11.396 11.447 11.396 22.79C10.926 95.22-.47 95.22-.47 106.667m0 0c0 11.452 11.396 11.452 11.396 22.791M6.371-7.29c0 11.343-11.393 11.343-11.393 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-5.021 15.502c0 11.452 11.393 11.452 11.393 22.791 0 11.342-11.393 11.342-11.393 22.796m0 0c0 11.447 11.393 11.447 11.393 22.79 0 11.341-11.393 11.341-11.393 22.788m0 0c0 11.452 11.393 11.452 11.393 22.791"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-9.59 15.502c0 11.452 11.403 11.452 11.403 22.791 0 11.342-11.403 11.342-11.403 22.796m0 0c0 11.447 11.403 11.447 11.403 22.79C1.813 95.22-9.59 95.22-9.59 106.667m0 0c0 11.452 11.403 11.452 11.403 22.791"
/>
</svg>
)

View File

@ -0,0 +1,13 @@
import { CoverImageProps } from '../types'
export const CoverImage9 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill="none"
stroke="#000"
stroke-width="5"
stroke-miterlimit="3.864"
d="M-23.957 4.94c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.601 43.234 3.601m.001-.001c21.724 0 21.724-3.601 43.237-3.601 21.515 0 21.515 3.601 43.237 3.601m-172.946 8.649c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.6 43.234 3.6m.001 0c21.724 0 21.724-3.6 43.237-3.6 21.515 0 21.515 3.6 43.237 3.6m-172.946 8.649c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.645c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.6 43.234 3.6m.001 0c21.724 0 21.724-3.6 43.237-3.6 21.515 0 21.515 3.6 43.237 3.6m-172.946 8.65c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.646c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.649c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604"
/>
</svg>
)

View File

@ -0,0 +1 @@
export { CoverImage } from './CoverImage'

View File

@ -0,0 +1,3 @@
export type CoverImageProps = {
class?: string
}

View File

@ -2,8 +2,10 @@ import type { Author, Shout } from '../../graphql/types.gen'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { createPopper } from '@popperjs/core' import { createPopper } from '@popperjs/core'
import { Link } from '@solidjs/meta'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js' import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js'
import { isServer } from 'solid-js/web'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions' import { useReactions } from '../../context/reactions'
@ -52,6 +54,8 @@ const scrollTo = (el: HTMLElement) => {
}) })
} }
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
export const FullArticle = (props: Props) => { export const FullArticle = (props: Props) => {
const [selectedImage, setSelectedImage] = createSignal('') const [selectedImage, setSelectedImage] = createSignal('')
@ -95,6 +99,26 @@ export const FullArticle = (props: Props) => {
return props.article.body return props.article.body
}) })
const imageUrls = createMemo(() => {
if (!body()) {
return []
}
if (isServer) {
const result: string[] = []
let match: RegExpMatchArray
while ((match = imgSrcRegExp.exec(body())) !== null) {
result.push(match[1])
}
return result
}
const imageElements = document.querySelectorAll<HTMLImageElement>('#shoutBody img')
// eslint-disable-next-line unicorn/prefer-spread
return Array.from(imageElements).map((img) => img.src)
})
const media = createMemo<MediaItem[]>(() => { const media = createMemo<MediaItem[]>(() => {
try { try {
return JSON.parse(props.article.media) return JSON.parse(props.article.media)
@ -261,6 +285,7 @@ export const FullArticle = (props: Props) => {
return ( return (
<> <>
<For each={imageUrls()}>{(imageUrl) => <Link rel="preload" as="image" href={imageUrl} />}</For>
<div class="wide-container"> <div class="wide-container">
<div class="row position-relative"> <div class="row position-relative">
<article <article
@ -296,7 +321,7 @@ export const FullArticle = (props: Props) => {
props.article.layout !== 'image' props.article.layout !== 'image'
} }
> >
<Image width={1600} alt={props.article.title} src={props.article.cover} /> <Image width={800} alt={props.article.title} src={props.article.cover} />
</Show> </Show>
</div> </div>
</Show> </Show>

View File

@ -2,6 +2,7 @@ import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { showModal } from '../../stores/ui' import { showModal } from '../../stores/ui'
import { Image } from '../_shared/Image'
import styles from './Banner.module.scss' import styles from './Banner.module.scss'
@ -22,7 +23,11 @@ export default () => {
</p> </p>
</div> </div>
<div class={clsx(styles.discoursBannerImage, 'col-lg-12 offset-lg-2')}> <div class={clsx(styles.discoursBannerImage, 'col-lg-12 offset-lg-2')}>
<img src="/discours-banner.jpg" alt={t('Discours')} /> <Image
src="https://images.discours.io/unsafe/production/image/discours-banner.jpg"
alt={t('Discours')}
width={600}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -27,6 +27,7 @@ import { hideModal, showModal } from '../../stores/ui'
import { Button } from '../_shared/Button' import { Button } from '../_shared/Button'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { Popover } from '../_shared/Popover' import { Popover } from '../_shared/Popover'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { Modal } from '../Nav/Modal' import { Modal } from '../Nav/Modal'
import { Figcaption } from './extensions/Figcaption' import { Figcaption } from './extensions/Figcaption'
@ -59,13 +60,16 @@ type Props = {
isCancelButtonVisible?: boolean isCancelButtonVisible?: boolean
} }
export const MAX_DESCRIPTION_LIMIT = 400 const DEFAULT_MAX_LENGTH = 400
const SimplifiedEditor = (props: Props) => { const SimplifiedEditor = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const [counter, setCounter] = createSignal<number>() const [counter, setCounter] = createSignal<number>()
const [shouldShowLinkBubbleMenu, setShouldShowLinkBubbleMenu] = createSignal(false) const [shouldShowLinkBubbleMenu, setShouldShowLinkBubbleMenu] = createSignal(false)
const isCancelButtonVisible = createMemo(() => props.isCancelButtonVisible !== false) const isCancelButtonVisible = createMemo(() => props.isCancelButtonVisible !== false)
const maxLength = props.maxLength ?? DEFAULT_MAX_LENGTH
const wrapperEditorElRef: { const wrapperEditorElRef: {
current: HTMLElement current: HTMLElement
} = { } = {
@ -116,9 +120,8 @@ const SimplifiedEditor = (props: Props) => {
Link.configure({ Link.configure({
openOnClick: false, openOnClick: false,
}), }),
CharacterCount.configure({ CharacterCount.configure({
limit: MAX_DESCRIPTION_LIMIT, limit: maxLength,
}), }),
Blockquote.configure({ Blockquote.configure({
HTMLAttributes: { HTMLAttributes: {
@ -262,132 +265,134 @@ const SimplifiedEditor = (props: Props) => {
setShouldShowLinkBubbleMenu(true) setShouldShowLinkBubbleMenu(true)
} }
return ( return (
<div <ShowOnlyOnClient>
ref={(el) => (wrapperEditorElRef.current = el)} <div
class={clsx(styles.SimplifiedEditor, { ref={(el) => (wrapperEditorElRef.current = el)}
[styles.smallHeight]: props.smallHeight, class={clsx(styles.SimplifiedEditor, {
[styles.minimal]: props.variant === 'minimal', [styles.smallHeight]: props.smallHeight,
[styles.bordered]: props.variant === 'bordered', [styles.minimal]: props.variant === 'minimal',
[styles.isFocused]: isFocused() || !isEmpty(), [styles.bordered]: props.variant === 'bordered',
[styles.labelVisible]: props.label && counter() > 0, [styles.isFocused]: isFocused() || !isEmpty(),
})} [styles.labelVisible]: props.label && counter() > 0,
> })}
<Show when={props.maxLength && editor()}> >
<div class={styles.limit}>{MAX_DESCRIPTION_LIMIT - counter()}</div> <Show when={props.maxLength && editor()}>
</Show> <div class={styles.limit}>{maxLength - counter()}</div>
<Show when={props.label && counter() > 0}> </Show>
<div class={styles.label}>{props.label}</div> <Show when={props.label && counter() > 0}>
</Show> <div class={styles.label}>{props.label}</div>
<div style={props.maxHeight && maxHeightStyle} ref={(el) => (editorElRef.current = el)} /> </Show>
<Show when={!props.onlyBubbleControls}> <div style={props.maxHeight && maxHeightStyle} ref={(el) => (editorElRef.current = el)} />
<div class={clsx(styles.controls, { [styles.alwaysVisible]: props.controlsAlwaysVisible })}> <Show when={!props.onlyBubbleControls}>
<div class={styles.actions}> <div class={clsx(styles.controls, { [styles.alwaysVisible]: props.controlsAlwaysVisible })}>
<Popover content={t('Bold')}> <div class={styles.actions}>
{(triggerRef: (el) => void) => ( <Popover content={t('Bold')}>
<button
ref={triggerRef}
type="button"
class={clsx(styles.actionButton, { [styles.active]: isBold() })}
onClick={() => editor().chain().focus().toggleBold().run()}
>
<Icon name="editor-bold" />
</button>
)}
</Popover>
<Popover content={t('Italic')}>
{(triggerRef: (el) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
onClick={() => editor().chain().focus().toggleItalic().run()}
>
<Icon name="editor-italic" />
</button>
)}
</Popover>
<Popover content={t('Add url')}>
{(triggerRef: (el) => void) => (
<button
ref={triggerRef}
type="button"
onClick={handleShowLinkBubble}
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
>
<Icon name="editor-link" />
</button>
)}
</Popover>
<Show when={props.quoteEnabled}>
<Popover content={t('Add blockquote')}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (
<button <button
ref={triggerRef} ref={triggerRef}
type="button" type="button"
onClick={() => editor().chain().focus().toggleBlockquote().run()} class={clsx(styles.actionButton, { [styles.active]: isBold() })}
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })} onClick={() => editor().chain().focus().toggleBold().run()}
> >
<Icon name="editor-quote" /> <Icon name="editor-bold" />
</button> </button>
)} )}
</Popover> </Popover>
</Show> <Popover content={t('Italic')}>
<Show when={props.imageEnabled}> {(triggerRef) => (
<Popover content={t('Add image')}>
{(triggerRef: (el) => void) => (
<button <button
ref={triggerRef} ref={triggerRef}
type="button" type="button"
onClick={() => showModal('simplifiedEditorUploadImage')} class={clsx(styles.actionButton, { [styles.active]: isItalic() })}
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })} onClick={() => editor().chain().focus().toggleItalic().run()}
> >
<Icon name="editor-image-dd-full" /> <Icon name="editor-italic" />
</button> </button>
)} )}
</Popover> </Popover>
<Popover content={t('Add url')}>
{(triggerRef) => (
<button
ref={triggerRef}
type="button"
onClick={handleShowLinkBubble}
class={clsx(styles.actionButton, { [styles.active]: isLink() })}
>
<Icon name="editor-link" />
</button>
)}
</Popover>
<Show when={props.quoteEnabled}>
<Popover content={t('Add blockquote')}>
{(triggerRef) => (
<button
ref={triggerRef}
type="button"
onClick={() => editor().chain().focus().toggleBlockquote().run()}
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
>
<Icon name="editor-quote" />
</button>
)}
</Popover>
</Show>
<Show when={props.imageEnabled}>
<Popover content={t('Add image')}>
{(triggerRef) => (
<button
ref={triggerRef}
type="button"
onClick={() => showModal('simplifiedEditorUploadImage')}
class={clsx(styles.actionButton, { [styles.active]: isBlockquote() })}
>
<Icon name="editor-image-dd-full" />
</button>
)}
</Popover>
</Show>
</div>
<Show when={!props.onChange}>
<div class={styles.buttons}>
<Show when={isCancelButtonVisible()}>
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} />
</Show>
<Button
value={props.submitButtonText ?? t('Send')}
variant="primary"
disabled={isEmpty()}
onClick={() => props.onSubmit(html())}
/>
</div>
</Show> </Show>
</div> </div>
<Show when={!props.onChange}> </Show>
<div class={styles.buttons}> <Show when={props.imageEnabled}>
<Show when={isCancelButtonVisible()}> <Portal>
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} /> <Modal variant="narrow" name="simplifiedEditorUploadImage">
</Show> <UploadModalContent
<Button onClose={(value) => {
value={props.submitButtonText ?? t('Send')} renderImage(value)
variant="primary" }}
disabled={isEmpty()}
onClick={() => props.onSubmit(html())}
/> />
</div> </Modal>
</Show> </Portal>
</div> </Show>
</Show> <Show when={props.onlyBubbleControls}>
<Show when={props.imageEnabled}> <TextBubbleMenu
<Portal> shouldShow={true}
<Modal variant="narrow" name="simplifiedEditorUploadImage"> isCommonMarkup={true}
<UploadModalContent editor={editor()}
onClose={(value) => { ref={(el) => (textBubbleMenuRef.current = el)}
renderImage(value) />
}} </Show>
/> <LinkBubbleMenuModule
</Modal> shouldShow={shouldShowLinkBubbleMenu()}
</Portal>
</Show>
<Show when={props.onlyBubbleControls}>
<TextBubbleMenu
shouldShow={true}
isCommonMarkup={true}
editor={editor()} editor={editor()}
ref={(el) => (textBubbleMenuRef.current = el)} ref={(el) => (linkBubbleMenuRef.current = el)}
onClose={() => setShouldShowLinkBubbleMenu(false)}
/> />
</Show> </div>
<LinkBubbleMenuModule </ShowOnlyOnClient>
shouldShow={shouldShowLinkBubbleMenu()}
editor={editor()}
ref={(el) => (linkBubbleMenuRef.current = el)}
onClose={() => setShouldShowLinkBubbleMenu(false)}
/>
</div>
) )
} }

View File

@ -1,17 +1,18 @@
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect } from 'solid-js' import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect, lazy } from 'solid-js'
import { createEditorTransaction } from 'solid-tiptap' import { createEditorTransaction } from 'solid-tiptap'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon' import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover' import { Popover } from '../../_shared/Popover'
import { InsertLinkForm } from '../InsertLinkForm' import { InsertLinkForm } from '../InsertLinkForm'
import SimplifiedEditor from '../SimplifiedEditor'
import styles from './TextBubbleMenu.module.scss' import styles from './TextBubbleMenu.module.scss'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
type BubbleMenuProps = { type BubbleMenuProps = {
editor: Editor editor: Editor
isCommonMarkup: boolean isCommonMarkup: boolean

View File

@ -94,7 +94,6 @@
} }
.shoutCardCover { .shoutCardCover {
background: rgb(0 0 0 / 30%);
height: 0; height: 0;
margin-bottom: 1.6rem; margin-bottom: 1.6rem;
overflow: hidden; overflow: hidden;
@ -103,6 +102,10 @@
transform-origin: 50% 50%; transform-origin: 50% 50%;
transition: transform 1s ease-in-out; transition: transform 1s ease-in-out;
&.loading {
background: rgb(0 0 0 / 20%);
}
img { img {
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
@ -825,3 +828,7 @@
.shoutTopicTop { .shoutTopicTop {
margin-bottom: 0.4rem !important; margin-bottom: 0.4rem !important;
} }
.placeholderCoverImage {
position: absolute;
}

View File

@ -12,6 +12,7 @@ import { getDescription } from '../../../utils/meta'
import { Icon } from '../../_shared/Icon' import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image' import { Image } from '../../_shared/Image'
import { Popover } from '../../_shared/Popover' import { Popover } from '../../_shared/Popover'
import { CoverImage } from '../../Article/CoverImage'
import { getShareUrl, SharePopup } from '../../Article/SharePopup' import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { ShoutRatingControl } from '../../Article/ShoutRatingControl' import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
import { AuthorLink } from '../../Author/AhtorLink' import { AuthorLink } from '../../Author/AhtorLink'
@ -21,7 +22,8 @@ import { FeedArticlePopup } from '../FeedArticlePopup'
import styles from './ArticleCard.module.scss' import styles from './ArticleCard.module.scss'
import stylesHeader from '../../Nav/Header/Header.module.scss' import stylesHeader from '../../Nav/Header/Header.module.scss'
interface ArticleCardProps { export type ArticleCardProps = {
// TODO: refactor this, please
settings?: { settings?: {
noicon?: boolean noicon?: boolean
noimage?: boolean noimage?: boolean
@ -44,9 +46,17 @@ interface ArticleCardProps {
withViewed?: boolean withViewed?: boolean
noAuthorLink?: boolean noAuthorLink?: boolean
} }
desktopCoverSize: 'XS' | 'S' | 'M' | 'L'
article: Shout article: Shout
} }
const desktopCoverImageWidths: Record<ArticleCardProps['desktopCoverSize'], number> = {
XS: 300,
S: 400,
M: 600,
L: 800,
}
const getTitleAndSubtitle = ( const getTitleAndSubtitle = (
article: Shout, article: Shout,
): { ): {
@ -98,11 +108,12 @@ export const ArticleCard = (props: ArticleCardProps) => {
} }
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false) const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
return ( return (
<section <section
class={clsx(styles.shoutCard, `${props.settings?.additionalClass || ''}`)} class={clsx(styles.shoutCard, props.settings?.additionalClass, {
classList={{
[styles.shoutCardShort]: props.settings?.isShort, [styles.shoutCardShort]: props.settings?.isShort,
[styles.shoutCardPhotoBottom]: props.settings?.noimage && props.settings?.photoBottom, [styles.shoutCardPhotoBottom]: props.settings?.noimage && props.settings?.photoBottom,
[styles.shoutCardFeed]: props.settings?.isFeedMode, [styles.shoutCardFeed]: props.settings?.isFeedMode,
@ -115,13 +126,29 @@ 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,
}} })}
> >
<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 class={styles.shoutCardCover}> <div
<Show when={props.article.cover}> class={clsx(styles.shoutCardCover, {
<Image src={props.article.cover} alt={title} width={1200} /> [styles.loading]: props.article.cover && isCoverImageLoading(),
})}
>
<Show
when={props.article.cover && !isCoverImageLoadError()}
fallback={<CoverImage class={styles.placeholderCoverImage} />}
>
<Image
src={props.article.cover}
alt={title}
width={desktopCoverImageWidths[props.desktopCoverSize]}
onError={() => {
setIsCoverImageLoadError(true)
setIsCoverImageLoading(false)
}}
onLoad={() => setIsCoverImageLoading(false)}
/>
</Show> </Show>
</div> </div>
</div> </div>
@ -214,7 +241,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
</div> </div>
</Show> </Show>
<div class={styles.shoutCardCover}> <div class={styles.shoutCardCover}>
<Image src={props.article.cover} alt={title} width={1200} loading="lazy" /> <Image src={props.article.cover} alt={title} width={600} loading="lazy" />
</div> </div>
</div> </div>
</Show> </Show>

View File

@ -85,12 +85,14 @@ export const Beside = (props: Props) => {
<ArticleCard <ArticleCard
article={value as Shout} article={value as Shout}
settings={{ noimage: true, nodate: props.nodate }} settings={{ noimage: true, nodate: props.nodate }}
desktopCoverSize="XS"
/> />
</Show> </Show>
<Show when={props.wrapper === 'top-article' && value?.slug}> <Show when={props.wrapper === 'top-article' && value?.slug}>
<ArticleCard <ArticleCard
article={value as Shout} article={value as Shout}
settings={{ noimage: true, noauthor: true, nodate: true, isShort: true }} settings={{ noimage: true, noauthor: true, nodate: true, isShort: true }}
desktopCoverSize="XS"
/> />
</Show> </Show>
</li> </li>
@ -103,6 +105,7 @@ export const Beside = (props: Props) => {
<ArticleCard <ArticleCard
article={props.beside} article={props.beside}
settings={{ isBigTitle: true, isBeside: true, nodate: props.nodate }} settings={{ isBigTitle: true, isBeside: true, nodate: props.nodate }}
desktopCoverSize="L"
/> />
</div> </div>
</div> </div>

View File

@ -30,6 +30,7 @@ export default (props: GroupProps) => {
isBigTitle: true, isBigTitle: true,
nodate: true, nodate: true,
}} }}
desktopCoverSize="M"
/> />
</div> </div>
@ -43,6 +44,7 @@ export default (props: GroupProps) => {
<ArticleCard <ArticleCard
article={a} article={a}
settings={{ nosubtitle: false, noicon: true, isBigTitle: true, nodate: true }} settings={{ nosubtitle: false, noicon: true, isBigTitle: true, nodate: true }}
desktopCoverSize="XS"
/> />
</div> </div>
</div> </div>
@ -63,6 +65,7 @@ export default (props: GroupProps) => {
isFloorImportant: true, isFloorImportant: true,
nodate: true, nodate: true,
}} }}
desktopCoverSize="XS"
/> />
)} )}
</For> </For>
@ -80,6 +83,7 @@ export default (props: GroupProps) => {
isFloorImportant: true, isFloorImportant: true,
nodate: true, nodate: true,
}} }}
desktopCoverSize="XS"
/> />
)} )}
</For> </For>

View File

@ -23,6 +23,7 @@ export const Row1 = (props: {
noAuthorLink: props.noAuthorLink, noAuthorLink: props.noAuthorLink,
noauthor: props.noauthor, noauthor: props.noauthor,
}} }}
desktopCoverSize="L"
/> />
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@ import type { Shout } from '../../graphql/types.gen'
import { createComputed, createSignal, Show, For } from 'solid-js' import { createComputed, createSignal, Show, For } from 'solid-js'
import { ArticleCard } from './ArticleCard' import { ArticleCard } from './ArticleCard'
import { ArticleCardProps } from './ArticleCard/ArticleCard'
const x = [ const x = [
['12', '12'], ['12', '12'],
@ -19,6 +20,7 @@ export const Row2 = (props: {
}) => { }) => {
const [y, setY] = createSignal(0) const [y, setY] = createSignal(0)
// FIXME: random can break hydration
createComputed(() => setY(Math.floor(Math.random() * x.length))) createComputed(() => setY(Math.floor(Math.random() * x.length)))
return ( return (
@ -28,20 +30,37 @@ export const Row2 = (props: {
<div class="row"> <div class="row">
<For each={props.articles}> <For each={props.articles}>
{(a, i) => { {(a, i) => {
// FIXME: refactor this, too ugly now
const className = `col-md-${props.isEqual ? '12' : x[y()][i()]}`
let desktopCoverSize: ArticleCardProps['desktopCoverSize']
switch (className) {
case 'col-md-8': {
desktopCoverSize = 'S'
break
}
case 'col-md-12': {
desktopCoverSize = 'M'
break
}
default: {
desktopCoverSize = 'L'
}
}
return ( return (
<Show when={!!a}> <div class={className}>
<div class={`col-md-${props.isEqual ? '12' : x[y()][i()]}`}> <ArticleCard
<ArticleCard article={a}
article={a} settings={{
settings={{ isWithCover: props.isEqual || x[y()][i()] === '16',
isWithCover: props.isEqual || x[y()][i()] === '16', nodate: props.isEqual || props.nodate,
nodate: props.isEqual || props.nodate, noAuthorLink: props.noAuthorLink,
noAuthorLink: props.noAuthorLink, noauthor: props.noauthor,
noauthor: props.noauthor, }}
}} desktopCoverSize={desktopCoverSize}
/> />
</div> </div>
</Show>
) )
}} }}
</For> </For>

View File

@ -28,6 +28,7 @@ export const Row3 = (props: {
noAuthorLink: props.noAuthorLink, noAuthorLink: props.noAuthorLink,
noauthor: props.noauthor, noauthor: props.noauthor,
}} }}
desktopCoverSize="S"
/> />
</div> </div>
)} )}

View File

@ -8,23 +8,34 @@ export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<ArticleCard article={props.articles[0]} settings={{ nodate: props.nodate }} /> <ArticleCard
article={props.articles[0]}
settings={{ nodate: props.nodate }}
desktopCoverSize="XS"
/>
<ArticleCard <ArticleCard
article={props.articles[1]} article={props.articles[1]}
settings={{ noimage: true, withBorder: true, nodate: props.nodate }} settings={{ noimage: true, withBorder: true, nodate: props.nodate }}
desktopCoverSize="XS"
/> />
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<ArticleCard <ArticleCard
article={props.articles[2]} article={props.articles[2]}
settings={{ isBigTitle: true, nodate: props.nodate }} settings={{ isBigTitle: true, nodate: props.nodate }}
desktopCoverSize="M"
/> />
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<ArticleCard article={props.articles[3]} settings={{ nodate: props.nodate }} /> <ArticleCard
article={props.articles[3]}
settings={{ nodate: props.nodate }}
desktopCoverSize="XS"
/>
<ArticleCard <ArticleCard
article={props.articles[4]} article={props.articles[4]}
settings={{ noimage: true, withBorder: true, nodate: props.nodate }} settings={{ noimage: true, withBorder: true, nodate: props.nodate }}
desktopCoverSize="XS"
/> />
</div> </div>
</div> </div>

View File

@ -20,6 +20,7 @@ export default (props: { articles: Shout[] }) => (
isVertical: true, isVertical: true,
nodate: true, nodate: true,
}} }}
desktopCoverSize="S"
/> />
</div> </div>
)} )}

View File

@ -40,7 +40,8 @@
.authImage { .authImage {
@include font-size(1.5rem); @include font-size(1.5rem);
background: var(--background-color-invert) url('/auth-page.jpg') center no-repeat; background: var(--background-color-invert)
url('https://images.discours.io/unsafe/1600x/production/image/auth-page.jpg') center no-repeat;
background-size: cover; background-size: cover;
color: var(--default-color-invert); color: var(--default-color-invert);
display: flex; display: flex;

View File

@ -2,7 +2,7 @@ import type { Shout, Topic } from '../../graphql/types.gen'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal' import deepEqual from 'fast-deep-equal'
import { Accessor, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { Accessor, createMemo, createSignal, lazy, onCleanup, onMount, Show } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import { ShoutForm, useEditorContext } from '../../context/editor' import { ShoutForm, useEditorContext } from '../../context/editor'
@ -14,14 +14,12 @@ import { getImageUrl } from '../../utils/getImageUrl'
import { isDesktop } from '../../utils/media-query' import { isDesktop } from '../../utils/media-query'
import { slugify } from '../../utils/slugify' import { slugify } from '../../utils/slugify'
import { DropArea } from '../_shared/DropArea' import { DropArea } from '../_shared/DropArea'
import { GrowingTextarea } from '../_shared/GrowingTextarea'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { Popover } from '../_shared/Popover' import { Popover } from '../_shared/Popover'
import { ImageSwiper } from '../_shared/SolidSwiper' import { ImageSwiper } from '../_shared/SolidSwiper'
import { Editor, Panel } from '../Editor' import { Editor, Panel } from '../Editor'
import { AudioUploader } from '../Editor/AudioUploader' import { AudioUploader } from '../Editor/AudioUploader'
import { AutoSaveNotice } from '../Editor/AutoSaveNotice' import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
import SimplifiedEditor from '../Editor/SimplifiedEditor'
import { VideoUploader } from '../Editor/VideoUploader' import { VideoUploader } from '../Editor/VideoUploader'
import { TableOfContents } from '../TableOfContents' import { TableOfContents } from '../TableOfContents'
@ -29,6 +27,9 @@ import { PublishSettings } from './PublishSettings'
import styles from './Edit.module.scss' import styles from './Edit.module.scss'
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../_shared/GrowingTextarea/GrowingTextarea'))
type Props = { type Props = {
shout: Shout shout: Shout
} }

View File

@ -145,6 +145,7 @@ export const Expo = (props: Props) => {
<ArticleCard <ArticleCard
article={shout} article={shout}
settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }} settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }}
desktopCoverSize="XS"
/> />
</div> </div>
)} )}
@ -157,6 +158,7 @@ export const Expo = (props: Props) => {
<ArticleCard <ArticleCard
article={shout} article={shout}
settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }} settings={{ nodate: true, nosubtitle: true, noAuthorLink: true }}
desktopCoverSize="XS"
/> />
</div> </div>
)} )}

View File

@ -9,6 +9,7 @@
.feedNavigation { .feedNavigation {
@include font-size(1.4rem); @include font-size(1.4rem);
font-weight: 500; font-weight: 500;
h4 { h4 {

View File

@ -41,7 +41,10 @@ const getOrderBy = (by: FeedSearchParams['by']) => {
} }
type Props = { type Props = {
loadShouts: (options: LoadShoutsOptions) => Promise<{ hasMore: boolean; newShouts: Shout[] }> loadShouts: (options: LoadShoutsOptions) => Promise<{
hasMore: boolean
newShouts: Shout[]
}>
} }
export const FeedView = (props: Props) => { export const FeedView = (props: Props) => {
@ -148,7 +151,9 @@ export const FeedView = (props: Props) => {
<Show when={!isLoading()} fallback={<Loading />}> <Show when={!isLoading()} fallback={<Loading />}>
<Show when={sortedArticles().length > 0}> <Show when={sortedArticles().length > 0}>
<For each={sortedArticles().slice(0, 4)}> <For each={sortedArticles().slice(0, 4)}>
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />} {(article) => (
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
)}
</For> </For>
<div class={styles.asideSection}> <div class={styles.asideSection}>
@ -172,7 +177,9 @@ export const FeedView = (props: Props) => {
</div> </div>
<For each={sortedArticles().slice(4)}> <For each={sortedArticles().slice(4)}>
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />} {(article) => (
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
)}
</For> </For>
</Show> </Show>

View File

@ -105,11 +105,8 @@ export const HomeView = (props: Props) => {
return ( return (
<Show when={sortedArticles().length > 0}> <Show when={sortedArticles().length > 0}>
<Topics /> <Topics />
<Row5 articles={sortedArticles().slice(0, 5)} nodate={true} /> <Row5 articles={sortedArticles().slice(0, 5)} nodate={true} />
<Hero /> <Hero />
<Show when={sortedArticles().length > PRERENDERED_ARTICLES_COUNT}> <Show when={sortedArticles().length > PRERENDERED_ARTICLES_COUNT}>
<Beside <Beside
beside={sortedArticles()[5]} beside={sortedArticles()[5]}
@ -169,7 +166,6 @@ export const HomeView = (props: Props) => {
<Row2 articles={sortedArticles().slice(29, 31)} nodate={true} /> <Row2 articles={sortedArticles().slice(29, 31)} nodate={true} />
<Row3 articles={sortedArticles().slice(31, 34)} nodate={true} /> <Row3 articles={sortedArticles().slice(31, 34)} nodate={true} />
</Show> </Show>
<For each={pages()}> <For each={pages()}>
{(page) => ( {(page) => (
<> <>
@ -183,7 +179,6 @@ export const HomeView = (props: Props) => {
</> </>
)} )}
</For> </For>
<Show when={isLoadMoreButtonVisible()}> <Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container"> <p class="load-more-container">
<button class="button" onClick={loadMore}> <button class="button" onClick={loadMore}>

View File

@ -1,7 +1,7 @@
import type { Author, Chat, Message as MessageType } from '../../graphql/types.gen' import type { Author, Chat, Message as MessageType } from '../../graphql/types.gen'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { For, createSignal, Show, onMount, createEffect, createMemo } from 'solid-js' import { For, createSignal, Show, onMount, createEffect, createMemo, lazy } from 'solid-js'
import { useInbox } from '../../context/inbox' import { useInbox } from '../../context/inbox'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
@ -10,7 +10,6 @@ import { loadRecipients } from '../../stores/inbox'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { showModal } from '../../stores/ui' import { showModal } from '../../stores/ui'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import SimplifiedEditor from '../Editor/SimplifiedEditor'
import CreateModalContent from '../Inbox/CreateModalContent' import CreateModalContent from '../Inbox/CreateModalContent'
import DialogCard from '../Inbox/DialogCard' import DialogCard from '../Inbox/DialogCard'
import DialogHeader from '../Inbox/DialogHeader' import DialogHeader from '../Inbox/DialogHeader'
@ -22,6 +21,8 @@ import { Modal } from '../Nav/Modal'
import styles from '../../styles/Inbox.module.scss' import styles from '../../styles/Inbox.module.scss'
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
type InboxSearchParams = { type InboxSearchParams = {
initChat: string initChat: string
chat: string chat: string

View File

@ -1,6 +1,6 @@
import { redirectPage } from '@nanostores/router' import { redirectPage } from '@nanostores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createSignal, onMount, Show } from 'solid-js' import { createSignal, lazy, onMount, Show } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import { ShoutForm, useEditorContext } from '../../../context/editor' import { ShoutForm, useEditorContext } from '../../../context/editor'
@ -12,17 +12,19 @@ import { router } from '../../../stores/router'
import { hideModal, showModal } from '../../../stores/ui' import { hideModal, showModal } from '../../../stores/ui'
import { apiClient } from '../../../utils/apiClient' import { apiClient } from '../../../utils/apiClient'
import { Button } from '../../_shared/Button' import { Button } from '../../_shared/Button'
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
import { Icon } from '../../_shared/Icon' import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image' import { Image } from '../../_shared/Image'
import { TopicSelect, UploadModalContent } from '../../Editor' import { TopicSelect, UploadModalContent } from '../../Editor'
import SimplifiedEditor, { MAX_DESCRIPTION_LIMIT } from '../../Editor/SimplifiedEditor'
import { Modal } from '../../Nav/Modal' import { Modal } from '../../Nav/Modal'
import { EMPTY_TOPIC } from '../Edit' import { EMPTY_TOPIC } from '../Edit'
import styles from './PublishSettings.module.scss' import styles from './PublishSettings.module.scss'
import stylesBeside from '../../Feed/Beside.module.scss' import stylesBeside from '../../Feed/Beside.module.scss'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))
const DESCRIPTION_MAX_LENGTH = 400
type Props = { type Props = {
shoutId: number shoutId: number
form: ShoutForm form: ShoutForm
@ -42,7 +44,7 @@ export const PublishSettings = (props: Props) => {
if (!props.form.description) { if (!props.form.description) {
const cleanFootnotes = props.form.body.replaceAll(/<footnote data-value=".*?">.*?<\/footnote>/g, '') const cleanFootnotes = props.form.body.replaceAll(/<footnote data-value=".*?">.*?<\/footnote>/g, '')
const leadText = cleanFootnotes.replaceAll(/<\/?[^>]+(>|$)/gi, ' ') const leadText = cleanFootnotes.replaceAll(/<\/?[^>]+(>|$)/gi, ' ')
return shorten(leadText, MAX_DESCRIPTION_LIMIT).trim() return shorten(leadText, DESCRIPTION_MAX_LENGTH).trim()
} }
return props.form.description return props.form.description
} }
@ -143,7 +145,7 @@ export const PublishSettings = (props: Props) => {
> >
<Show when={settingsForm.coverImageUrl ?? initialData.coverImageUrl}> <Show when={settingsForm.coverImageUrl ?? initialData.coverImageUrl}>
<div class={styles.shoutCardCover}> <div class={styles.shoutCardCover}>
<Image src={settingsForm.coverImageUrl} alt={initialData.title} width={1600} /> <Image src={settingsForm.coverImageUrl} alt={initialData.title} width={800} />
</div> </div>
</Show> </Show>
<div class={styles.text}> <div class={styles.text}>
@ -191,7 +193,7 @@ export const PublishSettings = (props: Props) => {
label={t('Description')} label={t('Description')}
initialContent={composeDescription()} initialContent={composeDescription()}
onChange={(value) => setForm('description', value)} onChange={(value) => setForm('description', value)}
maxLength={MAX_DESCRIPTION_LIMIT} maxLength={DESCRIPTION_MAX_LENGTH}
/> />
</div> </div>

View File

@ -92,7 +92,7 @@ export const SearchView = (props: Props) => {
<For each={sortedArticles()}> <For each={sortedArticles()}>
{(article) => ( {(article) => (
<div class="col-md-6"> <div class="col-md-6">
<ArticleCard article={article} /> <ArticleCard article={article} desktopCoverSize="L" />
</div> </div>
)} )}
</For> </For>

View File

@ -1,6 +1,8 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createEffect, createSignal, Show } from 'solid-js' import { createEffect, createSignal, Show } from 'solid-js'
import { ShowOnlyOnClient } from '../ShowOnlyOnClient'
import styles from './GrowingTextarea.module.scss' import styles from './GrowingTextarea.module.scss'
type Props = { type Props = {
@ -15,7 +17,7 @@ type Props = {
textAreaRef?: (el: HTMLTextAreaElement) => void textAreaRef?: (el: HTMLTextAreaElement) => void
} }
export const GrowingTextarea = (props: Props) => { const GrowingTextarea = (props: Props) => {
const [value, setValue] = createSignal<string>('') const [value, setValue] = createSignal<string>('')
const [isFocused, setIsFocused] = createSignal(false) const [isFocused, setIsFocused] = createSignal(false)
@ -41,47 +43,51 @@ export const GrowingTextarea = (props: Props) => {
} }
return ( return (
<div <ShowOnlyOnClient>
class={clsx(styles.GrowingTextarea, { <div
[styles.bordered]: props.variant === 'bordered', class={clsx(styles.GrowingTextarea, {
[styles.hasFieldName]: props.fieldName && value().length > 0, [styles.bordered]: props.variant === 'bordered',
})} [styles.hasFieldName]: props.fieldName && value().length > 0,
> })}
<Show when={props.fieldName && value().length > 0}> >
<div class={styles.fieldName}>{props.fieldName}</div> <Show when={props.fieldName && value().length > 0}>
</Show> <div class={styles.fieldName}>{props.fieldName}</div>
<div class={clsx(styles.growWrap, props.class)} data-replicated-value={value()}> </Show>
<textarea <div class={clsx(styles.growWrap, props.class)} data-replicated-value={value()}>
ref={props.textAreaRef} <textarea
rows={1} ref={props.textAreaRef}
maxlength={props.maxLength} rows={1}
autocomplete="off" maxlength={props.maxLength}
class={clsx(styles.textInput, props.class)} autocomplete="off"
value={ class={clsx(styles.textInput, props.class)}
props.initialValue && props.maxLength value={
? props.initialValue?.slice(0, props.maxLength) props.initialValue && props.maxLength
: props.initialValue ? props.initialValue?.slice(0, props.maxLength)
} : props.initialValue
onKeyDown={props.allowEnterKey ? handleKeyDown : null} }
onInput={(event) => handleChangeValue(event)} onKeyDown={props.allowEnterKey ? handleKeyDown : null}
onChange={(event) => props.value(event.target.value)} onInput={(event) => handleChangeValue(event)}
placeholder={props.placeholder} onChange={(event) => props.value(event.target.value)}
onFocus={() => setIsFocused(true)} placeholder={props.placeholder}
onBlur={() => setIsFocused(false)} onFocus={() => setIsFocused(true)}
/> onBlur={() => setIsFocused(false)}
</div> />
<Show when={(props.maxLength && value() && isFocused()) || props.variant === 'bordered'}>
<div
class={clsx(styles.maxLength, {
[styles.visible]: isFocused(),
[styles.limited]: value().length === props.maxLength,
})}
>
<Show when={props.variant === 'bordered'} fallback={`${value().length} / ${props.maxLength}`}>
{`${props.maxLength - value().length}`}
</Show>
</div> </div>
</Show> <Show when={(props.maxLength && value() && isFocused()) || props.variant === 'bordered'}>
</div> <div
class={clsx(styles.maxLength, {
[styles.visible]: isFocused(),
[styles.limited]: value().length === props.maxLength,
})}
>
<Show when={props.variant === 'bordered'} fallback={`${value().length} / ${props.maxLength}`}>
{`${props.maxLength - value().length}`}
</Show>
</div>
</Show>
</div>
</ShowOnlyOnClient>
) )
} }
export default GrowingTextarea // for async load

View File

@ -1 +0,0 @@
export { GrowingTextarea } from './GrowingTextarea'

View File

@ -1,5 +1,6 @@
import type { JSX } from 'solid-js' import type { JSX } from 'solid-js'
import { Link } from '@solidjs/meta'
import { splitProps } from 'solid-js' import { splitProps } from 'solid-js'
import { getImageUrl } from '../../../utils/getImageUrl' import { getImageUrl } from '../../../utils/getImageUrl'
@ -11,6 +12,20 @@ type Props = JSX.ImgHTMLAttributes<HTMLImageElement> & {
export const Image = (props: Props) => { export const Image = (props: Props) => {
const [local, others] = splitProps(props, ['src', 'alt']) const [local, others] = splitProps(props, ['src', 'alt'])
const src = getImageUrl(local.src, { width: others.width })
return <img src={src} alt={local.alt} {...others} /> const imageUrl = getImageUrl(local.src, { width: others.width })
const imageSrcSet = [1, 2, 3]
.map(
(pixelDensity) =>
`${getImageUrl(local.src, { width: others.width * pixelDensity })} ${pixelDensity}x`,
)
.join(', ')
return (
<>
<Link rel="preload" as="image" imagesrcset={imageSrcSet} />
<img src={imageUrl} alt={local.alt} srcSet={imageSrcSet} {...others} />
</>
)
} }

View File

@ -17,11 +17,13 @@ type Props = {
export const ArticleCardSwiper = (props: Props) => { export const ArticleCardSwiper = (props: Props) => {
const mainSwipeRef: { current: SwiperRef } = { current: null } const mainSwipeRef: { current: SwiperRef } = { current: null }
onMount(async () => { onMount(async () => {
const { register } = await import('swiper/element/bundle') const { register } = await import('swiper/element/bundle')
register() register()
SwiperCore.use([Pagination, Navigation, Manipulation]) SwiperCore.use([Pagination, Navigation, Manipulation])
}) })
return ( return (
<div class={clsx(styles.Swiper, styles.articleMode, styles.ArticleCardSwiper)}> <div class={clsx(styles.Swiper, styles.articleMode, styles.ArticleCardSwiper)}>
<Show when={props.title}> <Show when={props.title}>
@ -63,6 +65,7 @@ export const ArticleCardSwiper = (props: Props) => {
isWithCover: true, isWithCover: true,
nodate: true, nodate: true,
}} }}
desktopCoverSize="L"
/> />
</swiper-slide> </swiper-slide>
)} )}

View File

@ -1,6 +1,6 @@
import { createFileUploader } from '@solid-primitives/upload' import { createFileUploader } from '@solid-primitives/upload'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createEffect, createSignal, For, Show, on, onMount } from 'solid-js' import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js'
import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
@ -10,7 +10,6 @@ import { composeMediaItems } from '../../../utils/composeMediaItems'
import { getImageUrl } from '../../../utils/getImageUrl' import { getImageUrl } from '../../../utils/getImageUrl'
import { handleImageUpload } from '../../../utils/handleImageUpload' import { handleImageUpload } from '../../../utils/handleImageUpload'
import { validateFiles } from '../../../utils/validateFile' import { validateFiles } from '../../../utils/validateFile'
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
import { DropArea } from '../DropArea' import { DropArea } from '../DropArea'
import { Icon } from '../Icon' import { Icon } from '../Icon'
import { Image } from '../Image' import { Image } from '../Image'
@ -21,6 +20,8 @@ import { SwiperRef } from './swiper'
import styles from './Swiper.module.scss' import styles from './Swiper.module.scss'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
type Props = { type Props = {
images: MediaItem[] images: MediaItem[]
editorMode?: boolean editorMode?: boolean
@ -178,7 +179,7 @@ export const ImageSwiper = (props: Props) => {
// @ts-ignore // @ts-ignore
<swiper-slide lazy="true" virtual-index={index()}> <swiper-slide lazy="true" virtual-index={index()}>
<div class={styles.image}> <div class={styles.image}>
<Image src={slide.url} alt={slide.title} width={1600} /> <Image src={slide.url} alt={slide.title} width={800} />
<Show when={props.editorMode}> <Show when={props.editorMode}>
<Popover content={t('Delete')}> <Popover content={t('Delete')}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (

View File

@ -48,7 +48,7 @@ export const ArticlePage = (props: PageProps) => {
return ( return (
<PageLayout <PageLayout
title={props.seo.title} title={props.seo?.title}
headerTitle={article()?.title || ''} headerTitle={article()?.title || ''}
slug={article()?.slug} slug={article()?.slug}
articleBody={article()?.body} articleBody={article()?.body}

View File

@ -57,7 +57,7 @@ export const AuthorPage = (props: PageProps) => {
const usePrerenderedData = props.author?.slug === slug() const usePrerenderedData = props.author?.slug === slug()
return ( return (
<PageLayout title={props.seo.title}> <PageLayout title={props.seo?.title}>
<ReactionsProvider> <ReactionsProvider>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<AuthorView <AuthorView

View File

@ -1,18 +1,16 @@
import { createFileUploader } from '@solid-primitives/upload' import { createFileUploader } from '@solid-primitives/upload'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal' import deepEqual from 'fast-deep-equal'
import { For, createSignal, Show, onMount, onCleanup, createEffect, Switch, Match } from 'solid-js' import { For, createSignal, Show, onMount, onCleanup, createEffect, Switch, Match, lazy } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel' import FloatingPanel from '../../components/_shared/FloatingPanel/FloatingPanel'
import { GrowingTextarea } from '../../components/_shared/GrowingTextarea'
import { Icon } from '../../components/_shared/Icon' import { Icon } from '../../components/_shared/Icon'
import { Loading } from '../../components/_shared/Loading' import { Loading } from '../../components/_shared/Loading'
import { PageLayout } from '../../components/_shared/PageLayout' import { PageLayout } from '../../components/_shared/PageLayout'
import { Popover } from '../../components/_shared/Popover' import { Popover } from '../../components/_shared/Popover'
import { SocialNetworkInput } from '../../components/_shared/SocialNetworkInput' import { SocialNetworkInput } from '../../components/_shared/SocialNetworkInput'
import { AuthGuard } from '../../components/AuthGuard' import { AuthGuard } from '../../components/AuthGuard'
import SimplifiedEditor from '../../components/Editor/SimplifiedEditor'
import { ProfileSettingsNavigation } from '../../components/Nav/ProfileSettingsNavigation' import { ProfileSettingsNavigation } from '../../components/Nav/ProfileSettingsNavigation'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useProfileForm } from '../../context/profile' import { useProfileForm } from '../../context/profile'
@ -26,6 +24,9 @@ import { validateUrl } from '../../utils/validateUrl'
import styles from './Settings.module.scss' import styles from './Settings.module.scss'
const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea'))
export const ProfileSettingsPage = () => { export const ProfileSettingsPage = () => {
const { t } = useLocalize() const { t } = useLocalize()
const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false) const [addLinkForm, setAddLinkForm] = createSignal<boolean>(false)

View File

@ -53,7 +53,7 @@ export const TopicPage = (props: PageProps) => {
const usePrerenderedData = props.topic?.slug === slug() const usePrerenderedData = props.topic?.slug === slug()
return ( return (
<PageLayout title={props.seo.title}> <PageLayout title={props.seo?.title}>
<ReactionsProvider> <ReactionsProvider>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<TopicView <TopicView

View File

@ -62,10 +62,10 @@ export const render = async (pageContext: PageContext) => {
<html lang="${lng}"> <html lang="${lng}">
<head> <head>
${dangerouslySkipEscape(getAssets())} ${dangerouslySkipEscape(getAssets())}
${dangerouslySkipEscape(generateHydrationScript())}
</head> </head>
<body> <body>
<div id="root">${dangerouslySkipEscape(rootContent)}</div> <div id="root">${dangerouslySkipEscape(rootContent)}</div>
${dangerouslySkipEscape(generateHydrationScript())}
</body> </body>
</html>` </html>`
} }

View File

@ -1,4 +1,3 @@
@import 'fonts';
@import 'globals'; @import 'globals';
@import 'bootstrap/scss/functions'; @import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables'; @import 'bootstrap/scss/variables';

View File

@ -1,3 +1,4 @@
@import 'fonts';
@import 'bootstrap/scss/mixins/lists'; @import 'bootstrap/scss/mixins/lists';
@import 'bootstrap/scss/mixins/container'; @import 'bootstrap/scss/mixins/container';
@import 'bootstrap/scss/mixins/utilities'; @import 'bootstrap/scss/mixins/utilities';