utils-refactored
This commit is contained in:
parent
95612eb7b8
commit
2d7fbc42a8
|
@ -1,7 +1,7 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
|
||||
import { MediaItem } from '~/types/mediaitem'
|
||||
import styles from './AudioPlayer.module.scss'
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useLocalize } from '~/context/localize'
|
|||
import { useReactions } from '~/context/reactions'
|
||||
import { useSession } from '~/context/session'
|
||||
import { Author, Reaction, ReactionKind, ReactionSort } from '~/graphql/schema/core.gen'
|
||||
import { byCreated, byStat } from '~/lib/sortby'
|
||||
import { byCreated, byStat } from '~/lib/sortBy'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import { useSession } from '~/context/session'
|
|||
import { Author, FollowingEntity } from '~/graphql/schema/core.gen'
|
||||
import { isCyrillic } from '~/intl/translate'
|
||||
import { translit } from '~/intl/translit'
|
||||
import { mediaMatches } from '~/utils/media-query'
|
||||
import { mediaMatches } from '~/lib/mediaQuery'
|
||||
import { Userpic } from '../Userpic'
|
||||
import styles from './AuthorBadge.module.scss'
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import { Show } from 'solid-js'
|
|||
import { isServer } from 'solid-js/web'
|
||||
import { DropArea } from '~/components/_shared/DropArea'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { composeMediaItems } from '~/lib/composeMediaItems'
|
||||
import { MediaItem } from '~/types/mediaitem'
|
||||
import { composeMediaItems } from '~/utils/composeMediaItems'
|
||||
import { AudioPlayer } from '../../Article/AudioPlayer'
|
||||
import styles from './AudioUploader.module.scss'
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import { renderUploadedImage } from '~/components/Editor/renderUploadedImage'
|
|||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useUI } from '~/context/ui'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
import { UploadedFile } from '~/types/upload'
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { InlineForm } from '../InlineForm'
|
||||
import { UploadModalContent } from '../UploadModalContent'
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Editor } from '@tiptap/core'
|
|||
import { createEditorTransaction } from 'solid-tiptap'
|
||||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { validateUrl } from '~/utils/validateUrl'
|
||||
import { validateUrl } from '~/utils/validate'
|
||||
import { InlineForm } from '../InlineForm'
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -9,8 +9,8 @@ import { Icon } from '~/components/_shared/Icon'
|
|||
import { useEditorContext } from '~/context/editor'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useUI } from '~/context/ui'
|
||||
import { useEscKeyDownHandler } from '~/utils/useEscKeyDownHandler'
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
import styles from './Panel.module.scss'
|
||||
|
||||
const typograf = new Typograf({ locale: ['ru', 'en-US'] })
|
||||
|
|
|
@ -10,7 +10,6 @@ import { useSession } from '~/context/session'
|
|||
import { useUI } from '~/context/ui'
|
||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||
import { UploadedFile } from '~/types/upload'
|
||||
import { verifyImg } from '~/utils/verifyImg'
|
||||
import { InlineForm } from '../InlineForm'
|
||||
|
||||
import styles from './UploadModalContent.module.scss'
|
||||
|
@ -19,6 +18,9 @@ type Props = {
|
|||
onClose: (image?: UploadedFile) => void
|
||||
}
|
||||
|
||||
const verify = (url: string) =>
|
||||
fetch(url, { method: 'HEAD' }).then((res) => res.headers.get('Content-Type')?.startsWith('image'))
|
||||
|
||||
export const UploadModalContent = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { hideModal } = useUI()
|
||||
|
@ -87,7 +89,7 @@ export const UploadModalContent = (props: Props) => {
|
|||
}
|
||||
|
||||
const handleValidate = async (value: string) => {
|
||||
const validationResult = await verifyImg(value)
|
||||
const validationResult = await verify(value)
|
||||
if (!validationResult) {
|
||||
return t('Invalid image URL')
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import { For, Show, createSignal } from 'solid-js'
|
|||
import { VideoPlayer } from '~/components/_shared/VideoPlayer'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSnackbar } from '~/context/ui'
|
||||
import { composeMediaItems } from '~/utils/composeMediaItems'
|
||||
import { validateUrl } from '~/utils/validateUrl'
|
||||
import { composeMediaItems } from '~/lib/composeMediaItems'
|
||||
import { validateUrl } from '~/utils/validate'
|
||||
|
||||
import { MediaItem } from '~/types/mediaitem'
|
||||
import styles from './VideoUploader.module.scss'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { JSX, Show, createSignal } from 'solid-js'
|
|||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import { useSnackbar, useUI } from '~/context/ui'
|
||||
import { validateEmail } from '~/utils/validateEmail'
|
||||
import { validateEmail } from '~/utils/validate'
|
||||
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useSearchParams } from '@solidjs/router'
|
|||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import { useUI } from '~/context/ui'
|
||||
import { validateEmail } from '~/utils/validateEmail'
|
||||
import { validateEmail } from '~/utils/validate'
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
import { SocialProviders } from './SocialProviders'
|
||||
|
|
|
@ -3,7 +3,7 @@ import { JSX, Show, createSignal, onMount } from 'solid-js'
|
|||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import { validateEmail } from '~/utils/validateEmail'
|
||||
import { validateEmail } from '~/utils/validate'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
|
||||
import { useSearchParams } from '@solidjs/router'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Dynamic } from 'solid-js/web'
|
|||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { AuthModalSource, useUI } from '~/context/ui'
|
||||
import { isMobile } from '~/utils/media-query'
|
||||
import { isMobile } from '~/lib/mediaQuery'
|
||||
import { ChangePasswordForm } from './ChangePasswordForm'
|
||||
import { EmailConfirm } from './EmailConfirm'
|
||||
import { LoginForm } from './LoginForm'
|
||||
|
|
|
@ -4,8 +4,8 @@ import type { JSX } from 'solid-js'
|
|||
import { Show } from 'solid-js'
|
||||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { useUI } from '~/context/ui'
|
||||
import { isPortrait } from '~/utils/media-query'
|
||||
import { useEscKeyDownHandler } from '~/utils/useEscKeyDownHandler'
|
||||
import { isPortrait } from '~/lib/mediaQuery'
|
||||
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
||||
import styles from './Modal.module.scss'
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Button } from '~/components/_shared/Button'
|
|||
import { Icon } from '~/components/_shared/Icon'
|
||||
import { useFeed } from '~/context/feed'
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { byScore } from '~/lib/sortby'
|
||||
import { byScore } from '~/lib/sortBy'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed'
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ import { throttle } from 'throttle-debounce'
|
|||
import { useLocalize } from '~/context/localize'
|
||||
import { PAGE_SIZE, useNotifications } from '~/context/notifications'
|
||||
import { useSession } from '~/context/session'
|
||||
import { useEscKeyDownHandler } from '~/utils/useEscKeyDownHandler'
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { debounce, throttle } from 'throttle-debounce'
|
|||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { DEFAULT_HEADER_OFFSET } from '~/context/ui'
|
||||
import { isDesktop } from '~/utils/media-query'
|
||||
import { isDesktop } from '~/lib/mediaQuery'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
|
||||
import styles from './TableOfContents.module.scss'
|
||||
|
|
|
@ -7,8 +7,8 @@ import { useLocalize } from '~/context/localize'
|
|||
import { useSession } from '~/context/session'
|
||||
import { FollowingEntity, Topic } from '~/graphql/schema/core.gen'
|
||||
import { getImageUrl } from '~/lib/getImageUrl'
|
||||
import { mediaMatches } from '~/lib/mediaQuery'
|
||||
import { capitalize } from '~/utils/capitalize'
|
||||
import { mediaMatches } from '~/utils/media-query'
|
||||
import styles from './TopicBadge.module.scss'
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useLocalize } from '~/context/localize'
|
|||
import type { Author } from '~/graphql/schema/core.gen'
|
||||
import { authorLetterReduce, translateAuthor } from '~/intl/translate'
|
||||
import { dummyFilter } from '~/lib/dummyFilter'
|
||||
import { byFirstChar, byStat } from '~/lib/sortby'
|
||||
import { byFirstChar, byStat } from '~/lib/sortBy'
|
||||
import { scrollHandler } from '~/utils/scroll'
|
||||
import styles from './AllAuthors.module.scss'
|
||||
import stylesAuthorList from './AuthorsList.module.scss'
|
||||
|
|
|
@ -13,9 +13,9 @@ import loadShoutsQuery from '~/graphql/query/core/articles-load-by'
|
|||
import getAuthorFollowersQuery from '~/graphql/query/core/author-followers'
|
||||
import getAuthorFollowsQuery from '~/graphql/query/core/author-follows'
|
||||
import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { byCreated } from '~/lib/sortby'
|
||||
import { byCreated } from '~/lib/sortBy'
|
||||
import { paginate } from '~/utils/paginate'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { splitToPages } from '~/utils/splitToPages'
|
||||
import stylesArticle from '../../Article/Article.module.scss'
|
||||
import { Comment } from '../../Article/Comment'
|
||||
import { AuthorCard } from '../../Author/AuthorCard'
|
||||
|
@ -59,7 +59,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
|||
// derivatives
|
||||
const me = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
paginate(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
|
||||
// fx
|
||||
|
|
|
@ -10,8 +10,8 @@ import { useGraphQL } from '~/context/graphql'
|
|||
import { useLocalize } from '~/context/localize'
|
||||
import getMyShoutQuery from '~/graphql/query/core/article-my'
|
||||
import type { Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { isDesktop } from '~/lib/mediaQuery'
|
||||
import { clone } from '~/utils/clone'
|
||||
import { isDesktop } from '~/utils/media-query'
|
||||
import { Panel } from '../../Editor'
|
||||
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
|
|
|
@ -26,10 +26,10 @@ import getMyShoutQuery from '~/graphql/query/core/article-my'
|
|||
import type { Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { slugify } from '~/intl/translit'
|
||||
import { getImageUrl } from '~/lib/getImageUrl'
|
||||
import { isDesktop } from '~/lib/mediaQuery'
|
||||
import { LayoutType } from '~/types/common'
|
||||
import { MediaItem } from '~/types/mediaitem'
|
||||
import { clone } from '~/utils/clone'
|
||||
import { isDesktop } from '~/utils/media-query'
|
||||
import { Editor, Panel } from '../../Editor'
|
||||
import { AudioUploader } from '../../Editor/AudioUploader'
|
||||
import { AutoSaveNotice } from '../../Editor/AutoSaveNotice'
|
||||
|
|
|
@ -12,7 +12,7 @@ import getShoutsQuery from '~/graphql/query/core/articles-load-by'
|
|||
import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top'
|
||||
import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
||||
import { LayoutType } from '~/types/common'
|
||||
import { getUnixtime } from '~/utils/getServerDate'
|
||||
import { getUnixtime } from '~/utils/date'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { ArticleCard } from '../../Feed/ArticleCard'
|
||||
import styles from './Expo.module.scss'
|
||||
|
|
|
@ -16,7 +16,7 @@ import { useTopics } from '~/context/topics'
|
|||
import { useUI } from '~/context/ui'
|
||||
import { loadUnratedShouts } from '~/graphql/api/private'
|
||||
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
||||
import { byCreated } from '~/lib/sortby'
|
||||
import { byCreated } from '~/lib/sortBy'
|
||||
import { FeedSearchParams } from '~/routes/feed/(feed)'
|
||||
import { CommentDate } from '../../Article/CommentDate'
|
||||
import { getShareUrl } from '../../Article/SharePopup'
|
||||
|
|
|
@ -6,7 +6,7 @@ import { loadShouts } from '~/graphql/api/public'
|
|||
import { Author, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { SHOUTS_PER_PAGE } from '~/routes/(main)'
|
||||
import { capitalize } from '~/utils/capitalize'
|
||||
import { splitToPages } from '~/utils/splitToPages'
|
||||
import { paginate } from '~/utils/paginate'
|
||||
import Banner from '../Discours/Banner'
|
||||
import Hero from '../Discours/Hero'
|
||||
import { Beside } from '../Feed/Beside'
|
||||
|
@ -62,11 +62,7 @@ export const HomeView = (props: HomeViewProps) => {
|
|||
)
|
||||
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(
|
||||
props.featuredShouts || [],
|
||||
SHOUTS_PER_PAGE + CLIENT_LOAD_ARTICLES_COUNT,
|
||||
LOAD_MORE_PAGE_SIZE
|
||||
)
|
||||
paginate(props.featuredShouts || [], SHOUTS_PER_PAGE + CLIENT_LOAD_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Loading } from '~/components/_shared/Loading'
|
|||
import { useLocalize } from '~/context/localize'
|
||||
import { useSession } from '~/context/session'
|
||||
import { DEFAULT_HEADER_OFFSET, useSnackbar, useUI } from '~/context/ui'
|
||||
import { validateEmail } from '~/utils/validateEmail'
|
||||
import { validateEmail } from '~/utils/validate'
|
||||
import styles from './Settings.module.scss'
|
||||
|
||||
type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email'
|
||||
|
|
|
@ -23,7 +23,7 @@ import { getImageUrl } from '~/lib/getImageUrl'
|
|||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||
import { profileSocialLinks } from '~/lib/profileSocialLinks'
|
||||
import { clone } from '~/utils/clone'
|
||||
import { validateUrl } from '~/utils/validateUrl'
|
||||
import { validateUrl } from '~/utils/validate'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation'
|
||||
import { Button } from '../../_shared/Button'
|
||||
|
@ -188,6 +188,14 @@ export const ProfileSettings = () => {
|
|||
updateFormField('links', link, true)
|
||||
}
|
||||
|
||||
const slugUpdate = (ev: InputEvent) => {
|
||||
const input = (ev.target || ev.currentTarget) as HTMLInputElement
|
||||
const value = input.value
|
||||
const newValue = value.startsWith('@') || value.startsWith('!') ? value.substring(1) : value
|
||||
input.value = newValue
|
||||
updateFormField('slug', newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={Object.keys(form).length > 0 && isFormInitialized()} fallback={<Loading />}>
|
||||
<>
|
||||
|
@ -293,7 +301,7 @@ export const ProfileSettings = () => {
|
|||
<h4>{t('Address on Discours')}</h4>
|
||||
<div class="pretty-form__item">
|
||||
<div class={styles.discoursName}>
|
||||
<label for="user-address">https://{hostname()}/author/</label>
|
||||
<label for="user-address">{hostname()}/@</label>
|
||||
<div class={styles.discoursNameField}>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -301,7 +309,7 @@ export const ProfileSettings = () => {
|
|||
id="user-address"
|
||||
data-lpignore="true"
|
||||
autocomplete="one-time-code2"
|
||||
onInput={(event) => updateFormField('slug', event.currentTarget.value)}
|
||||
onInput={slugUpdate}
|
||||
value={form.slug || ''}
|
||||
ref={(el) => (slugInputRef = el)}
|
||||
class="nolabel"
|
||||
|
|
|
@ -144,7 +144,12 @@ export const PublishSettings = (props: Props) => {
|
|||
const handleSaveDraft = () => {
|
||||
saveShout({ ...props.form, ...settingsForm })
|
||||
}
|
||||
|
||||
const removeSpecial = (ev: InputEvent) => {
|
||||
const input = ev.target as HTMLInputElement
|
||||
const value = input.value
|
||||
const newValue = value.startsWith('@') || value.startsWith('!') ? value.substring(1) : value
|
||||
input.value = newValue
|
||||
}
|
||||
return (
|
||||
<form class={clsx(styles.PublishSettings, 'inputs-wrapper')}>
|
||||
<div class="wide-container">
|
||||
|
@ -235,7 +240,7 @@ export const PublishSettings = (props: Props) => {
|
|||
|
||||
<h4>{t('Slug')}</h4>
|
||||
<div class="pretty-form__item">
|
||||
<input type="text" name="slug" id="slug" value={settingsForm.slug} />
|
||||
<input type="text" name="slug" id="slug" value={settingsForm.slug} onInput={removeSpecial} />
|
||||
<label for="slug">{t('Slug')}</label>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ import { useTopics } from '~/context/topics'
|
|||
import { loadAuthors, loadFollowersByTopic, loadShouts } from '~/graphql/api/public'
|
||||
import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||
import { SHOUTS_PER_PAGE } from '~/routes/(main)'
|
||||
import { getUnixtime } from '~/utils/getServerDate'
|
||||
import { getUnixtime } from '~/utils/date'
|
||||
import { paginate } from '~/utils/paginate'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { splitToPages } from '~/utils/splitToPages'
|
||||
import styles from '../../styles/Topic.module.scss'
|
||||
import { Beside } from '../Feed/Beside'
|
||||
import { Row1 } from '../Feed/Row1'
|
||||
|
@ -140,7 +140,7 @@ export const TopicView = (props: Props) => {
|
|||
})
|
||||
*/
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
paginate(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
return (
|
||||
<div class={styles.topicPage}>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useLocalize } from '~/context/localize'
|
|||
import { useSession } from '~/context/session'
|
||||
import { handleFileUpload } from '~/lib/handleFileUpload'
|
||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||
import { validateFiles } from '~/utils/validateFile'
|
||||
import { validateUploads } from '~/lib/validateUploads'
|
||||
|
||||
import styles from './DropArea.module.scss'
|
||||
|
||||
|
@ -62,7 +62,7 @@ export const DropArea = (props: Props) => {
|
|||
setDropAreaError(t('Many files, choose only one'))
|
||||
return
|
||||
}
|
||||
const isValid = validateFiles(props.fileType, selectedFiles)
|
||||
const isValid = validateUploads(props.fileType, selectedFiles)
|
||||
if (isValid) {
|
||||
await runUpload(selectedFiles)
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { For, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
|
||||
import styles from './DropdownSelect.module.scss'
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { clsx } from 'clsx'
|
|||
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from 'solid-js'
|
||||
|
||||
import { getImageUrl } from '~/lib/getImageUrl'
|
||||
import { useEscKeyDownHandler } from '~/utils/useEscKeyDownHandler'
|
||||
import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler'
|
||||
import { Icon } from '../Icon'
|
||||
|
||||
import styles from './Lightbox.module.scss'
|
||||
|
|
|
@ -2,7 +2,7 @@ import { JSX, Show, createSignal } from 'solid-js'
|
|||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSnackbar } from '~/context/ui'
|
||||
import { validateEmail } from '~/utils/validateEmail'
|
||||
import { validateEmail } from '~/utils/validate'
|
||||
import { Button } from '../Button'
|
||||
import { Icon } from '../Icon'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { JSX, Show, createEffect, createSignal } from 'solid-js'
|
||||
|
||||
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
||||
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
|
||||
|
||||
import styles from './Popup.module.scss'
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@ import { Manipulation, Navigation, Pagination } from 'swiper/modules'
|
|||
|
||||
import { useLocalize } from '~/context/localize'
|
||||
import { useSnackbar } from '~/context/ui'
|
||||
import { composeMediaItems } from '~/lib/composeMediaItems'
|
||||
import { getImageUrl } from '~/lib/getImageUrl'
|
||||
import { handleImageUpload } from '~/lib/handleImageUpload'
|
||||
import { composeMediaItems } from '~/utils/composeMediaItems'
|
||||
import { validateFiles } from '~/utils/validateFile'
|
||||
import { validateUploads } from '~/lib/validateUploads'
|
||||
import { DropArea } from '../DropArea'
|
||||
import { Icon } from '../Icon'
|
||||
import { Image } from '../Image'
|
||||
|
@ -90,7 +90,7 @@ export const EditorSwiper = (props: Props) => {
|
|||
})
|
||||
|
||||
const initUpload = async (selectedFiles: UploadFile[]) => {
|
||||
const isValid = validateFiles('image', selectedFiles)
|
||||
const isValid = validateUploads('image', selectedFiles)
|
||||
|
||||
if (!isValid) {
|
||||
await showSnackbar({ type: 'error', body: t('Invalid file type') })
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
Shout,
|
||||
Topic
|
||||
} from '~/graphql/schema/core.gen'
|
||||
import { byStat } from '~/lib/sortby'
|
||||
import { byStat } from '~/lib/sortBy'
|
||||
import { useFeed } from './feed'
|
||||
|
||||
const TOP_AUTHORS_COUNT = 5
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
Shout,
|
||||
Topic
|
||||
} from '~/graphql/schema/core.gen'
|
||||
import { byStat } from '../lib/sortby'
|
||||
import { byStat } from '../lib/sortBy'
|
||||
import { useGraphQL } from './graphql'
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 5
|
||||
|
|
|
@ -76,16 +76,21 @@ export const ProfileProvider = (props: { children: JSX.Element }) => {
|
|||
}
|
||||
})
|
||||
|
||||
// TODO: validation error for `!` and `@`
|
||||
|
||||
const updateFormField = (fieldName: string, value: string, remove?: boolean) => {
|
||||
let val = value
|
||||
if (fieldName === 'slug' && value.startsWith('@')) val = value.substring(1)
|
||||
if (fieldName === 'slug' && value.startsWith('!')) val = value.substring(1)
|
||||
if (fieldName === 'links') {
|
||||
setForm((prev) => {
|
||||
const updatedLinks = remove
|
||||
? (prev.links || []).filter((item) => item !== value)
|
||||
: [...(prev.links || []), value]
|
||||
? (prev.links || []).filter((item) => item !== val)
|
||||
: [...(prev.links || []), val]
|
||||
return { ...prev, links: updatedLinks }
|
||||
})
|
||||
} else {
|
||||
setForm((prev) => ({ ...prev, [fieldName]: value }))
|
||||
setForm((prev) => ({ ...prev, [fieldName]: val }))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { loadTopics } from '~/graphql/api/public'
|
||||
import { Topic } from '~/graphql/schema/core.gen'
|
||||
import { getRandomTopicsFromArray } from '~/lib/getRandomTopicsFromArray'
|
||||
import { byTopicStatDesc } from '../lib/sortby'
|
||||
import { byTopicStatDesc } from '../lib/sortBy'
|
||||
|
||||
type TopicsContextType = {
|
||||
topicEntities: Accessor<{ [topicSlug: string]: Topic }>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { UploadFile } from '@solid-primitives/upload'
|
||||
|
||||
export const validateFiles = (fileType: string, files: UploadFile[]): boolean => {
|
||||
const imageExtensions = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp'])
|
||||
const docExtensions = new Set(['doc', 'docx', 'pdf', 'txt'])
|
||||
export const imageExtensions = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp'])
|
||||
export const docExtensions = new Set(['doc', 'docx', 'pdf', 'txt'])
|
||||
|
||||
export const validateUploads = (fileType: string, files: UploadFile[]): boolean => {
|
||||
for (const file of files) {
|
||||
let isValid: boolean
|
||||
|
|
@ -3,7 +3,7 @@ import { Show, Suspense, createEffect, createSignal, onMount } from 'solid-js'
|
|||
import { useTopics } from '~/context/topics'
|
||||
import { loadShouts, loadTopics } from '~/graphql/api/public'
|
||||
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
||||
import { byStat } from '~/lib/sortby'
|
||||
import { byStat } from '~/lib/sortBy'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||
import { HomeView, HomeViewProps } from '../components/Views/Home'
|
||||
import { Loading } from '../components/_shared/Loading'
|
||||
|
|
29
src/types.ts
29
src/types.ts
|
@ -1,29 +0,0 @@
|
|||
export interface CommentDefinition {
|
||||
user: string
|
||||
time_ago: string
|
||||
content: string
|
||||
comments: CommentDefinition[]
|
||||
}
|
||||
|
||||
export interface StoryDefinition {
|
||||
id: string
|
||||
points: string
|
||||
url: string
|
||||
title: string
|
||||
domain: string
|
||||
type: string
|
||||
time_ago: string
|
||||
user: string
|
||||
comments_count: number
|
||||
comments: CommentDefinition[]
|
||||
}
|
||||
|
||||
export interface UserDefinition {
|
||||
error: string
|
||||
id: string
|
||||
created: string
|
||||
karma: number
|
||||
about: string
|
||||
}
|
||||
|
||||
export type StoryTypes = 'top' | 'new' | 'show' | 'ask' | 'job'
|
2
src/utils/date.ts
Normal file
2
src/utils/date.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const getShortDate = (date: Date) => date.toISOString().slice(0, 10) // 2023-12-31
|
||||
export const getUnixtime = (date: Date) => Math.floor(date.getTime() / 1000) as number
|
|
@ -1,3 +0,0 @@
|
|||
// Usage in tsx: {getNumeralsDeclension(NUMBER, ['яблоко', 'яблока', 'яблок'])}
|
||||
export const getNumeralsDeclension = (number: number, words: string[], cases = [2, 0, 1, 1, 1, 2]) =>
|
||||
words[number % 100 > 4 && number % 100 < 20 ? 2 : cases[number % 10 < 5 ? number % 10 : 5]]
|
|
@ -1,8 +0,0 @@
|
|||
export const getServerDate = (date: Date): string => {
|
||||
// 2023-12-31
|
||||
return date.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
export const getUnixtime = (date: Date): number => {
|
||||
return Math.floor(date.getTime() / 1000)
|
||||
}
|
10
src/utils/paginate.ts
Normal file
10
src/utils/paginate.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export function paginate<T>(arr: T[], startIndex: number, pageSize: number): T[][] {
|
||||
return arr.slice(startIndex).reduce((acc, item, index) => {
|
||||
if (index % pageSize === 0) {
|
||||
acc.push([])
|
||||
}
|
||||
|
||||
acc?.at(-1)?.push(item)
|
||||
return acc
|
||||
}, [] as T[][])
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
export function splitToPages<T>(arr: T[], startIndex: number, pageSize: number): T[][] {
|
||||
return arr.slice(startIndex).reduce((acc, article, index) => {
|
||||
if (index % pageSize === 0) {
|
||||
acc.push([])
|
||||
}
|
||||
|
||||
acc?.at(-1)?.push(article)
|
||||
return acc
|
||||
}, [] as T[][])
|
||||
}
|
10
src/utils/validate.ts
Normal file
10
src/utils/validate.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export const validateEmail = (email: string) => {
|
||||
if (!email) return false
|
||||
|
||||
return /^[\w%+.-]+@[\d.a-z-]+\.[a-z]{2,}$/i.test(email)
|
||||
}
|
||||
|
||||
export const validateUrl = (value: string) => {
|
||||
// TODO: make it better
|
||||
return value.includes('.') && !value.includes(' ')
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export const validateEmail = (email: string) => {
|
||||
if (!email) {
|
||||
return false
|
||||
}
|
||||
|
||||
return /^[\w%+.-]+@[\d.a-z-]+\.[a-z]{2,}$/i.test(email)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export const validateUrl = (value: string) => {
|
||||
return value.includes('.') && !value.includes(' ')
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
export const verifyImg = (url: string) => {
|
||||
return fetch(url, { method: 'HEAD' }).then((res) => {
|
||||
return res.headers.get('Content-Type')?.startsWith('image')
|
||||
})
|
||||
}
|
||||
|
||||
const supportedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'tiff', 'bpg']
|
||||
export const isImageExtension = (value: string) => {
|
||||
return supportedExtensions.some((extension) => value.includes(extension))
|
||||
}
|
Loading…
Reference in New Issue
Block a user