diff --git a/src/components/Author/Userpic/Userpic.tsx b/src/components/Author/Userpic/Userpic.tsx index fbec00c9..11e3bc4c 100644 --- a/src/components/Author/Userpic/Userpic.tsx +++ b/src/components/Author/Userpic/Userpic.tsx @@ -22,7 +22,7 @@ export const Userpic = (props: Props) => { const letters = () => { if (!props.name) return const names = props.name ? props.name.split(' ') : [] - return `${names[0][0 ?? names[0][0]]}.${names.length > 1 ? `${names[1][0]}.` : ''}` + return `${names[0][0] ? names[0][0] : ''}.${names.length > 1 ? `${names[1][0]}.` : ''}` } const avatarSize = createMemo(() => { diff --git a/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx b/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx index 4d1b4f2e..6247c74e 100644 --- a/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx @@ -22,7 +22,7 @@ export const BlockquoteBubbleMenu = (props: Props) => { type="button" class={styles.bubbleMenuButton} onClick={() => { - props.editor.chain().focus().setBlockQuoteFloat('left').run() + props.editor?.chain().focus().setBlockQuoteFloat('left').run() }} > @@ -35,7 +35,7 @@ export const BlockquoteBubbleMenu = (props: Props) => { ref={triggerRef} type="button" class={styles.bubbleMenuButton} - onClick={() => props.editor.chain().focus().setBlockQuoteFloat(null).run()} + onClick={() => props.editor?.chain().focus().setBlockQuoteFloat(null).run()} > @@ -47,7 +47,7 @@ export const BlockquoteBubbleMenu = (props: Props) => { ref={triggerRef} type="button" class={styles.bubbleMenuButton} - onClick={() => props.editor.chain().focus().setBlockQuoteFloat('right').run()} + onClick={() => props.editor?.chain().focus().setBlockQuoteFloat('right').run()} > diff --git a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx index 27ce2f28..706c9048 100644 --- a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx @@ -33,7 +33,7 @@ export const FigureBubbleMenu = (props: Props) => { ref={triggerRef} type="button" class={styles.bubbleMenuButton} - onClick={() => props.editor.chain().focus().setFigureFloat('left').run()} + onClick={() => props.editor?.chain().focus().setFigureFloat('left').run()} > @@ -45,7 +45,7 @@ export const FigureBubbleMenu = (props: Props) => { ref={triggerRef} type="button" class={styles.bubbleMenuButton} - onClick={() => props.editor.chain().focus().setFigureFloat(null).run()} + onClick={() => props.editor?.chain().focus().setFigureFloat(null).run()} > @@ -57,7 +57,7 @@ export const FigureBubbleMenu = (props: Props) => { ref={triggerRef} type="button" class={styles.bubbleMenuButton} - onClick={() => props.editor.chain().focus().setFigureFloat('right').run()} + onClick={() => props.editor?.chain().focus().setFigureFloat('right').run()} > @@ -67,7 +67,7 @@ export const FigureBubbleMenu = (props: Props) => { diff --git a/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx b/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx index 01517729..4a80a680 100644 --- a/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx @@ -19,7 +19,7 @@ export const IncutBubbleMenu = (props: Props) => { const { t } = useLocalize() const [substratBubbleOpen, setSubstratBubbleOpen] = createSignal(false) const handleChangeBg = (bg: string | null) => { - props.editor.chain().focus().setArticleBg(bg).run() + props.editor?.chain().focus().setArticleBg(bg).run() setSubstratBubbleOpen(false) } return ( @@ -27,14 +27,14 @@ export const IncutBubbleMenu = (props: Props) => { @@ -42,7 +42,7 @@ export const IncutBubbleMenu = (props: Props) => { diff --git a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx index 633fa63e..7d72ab0a 100644 --- a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx +++ b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx @@ -91,9 +91,9 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { const handleAddFootnote = (footnote: string) => { if (footNote()) { - props.editor.chain().focus().updateFootnote({ value: footnote }).run() + props.editor?.chain().focus().updateFootnote({ value: footnote }).run() } else { - props.editor.chain().focus().setFootnote({ value: footnote }).run() + props.editor?.chain().focus().setFootnote({ value: footnote }).run() } setFootNote() setLinkEditorOpen(false) @@ -108,16 +108,16 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { const handleSetPunchline = () => { if (isPunchLine()) { - props.editor.chain().focus().toggleBlockquote('punchline').run() + props.editor?.chain().focus().toggleBlockquote('punchline').run() } - props.editor.chain().focus().toggleBlockquote('quote').run() + props.editor?.chain().focus().toggleBlockquote('quote').run() toggleTextSizePopup() } const handleSetQuote = () => { if (isQuote()) { - props.editor.chain().focus().toggleBlockquote('quote').run() + props.editor?.chain().focus().toggleBlockquote('quote').run() } - props.editor.chain().focus().toggleBlockquote('punchline').run() + props.editor?.chain().focus().toggleBlockquote('punchline').run() toggleTextSizePopup() } @@ -130,13 +130,13 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { }) const handleOpenLinkForm = () => { - props.editor.chain().focus().addTextWrap({ class: 'highlight-fake-selection' }).run() + props.editor?.chain().focus().addTextWrap({ class: 'highlight-fake-selection' }).run() setLinkEditorOpen(true) } const handleCloseLinkForm = () => { setLinkEditorOpen(false) - props.editor.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run() + props.editor?.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run() } return ( @@ -188,7 +188,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isH1() })} onClick={() => { - props.editor.chain().focus().toggleHeading({ level: 2 }).run() + props.editor?.chain().focus().toggleHeading({ level: 2 }).run() toggleTextSizePopup() }} > @@ -205,7 +205,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isH2() })} onClick={() => { - props.editor.chain().focus().toggleHeading({ level: 3 }).run() + props.editor?.chain().focus().toggleHeading({ level: 3 }).run() toggleTextSizePopup() }} > @@ -222,7 +222,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isH3() })} onClick={() => { - props.editor.chain().focus().toggleHeading({ level: 4 }).run() + props.editor?.chain().focus().toggleHeading({ level: 4 }).run() toggleTextSizePopup() }} > @@ -273,7 +273,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isIncut() })} onClick={() => { - props.editor.chain().focus().toggleArticle().run() + props.editor?.chain().focus().toggleArticle().run() toggleTextSizePopup() }} > @@ -296,7 +296,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { class={clsx(styles.bubbleMenuButton, { [styles.bubbleMenuButtonActive]: isBold() })} - onClick={() => props.editor.chain().focus().toggleBold().run()} + onClick={() => props.editor?.chain().focus().toggleBold().run()} > @@ -310,7 +310,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { class={clsx(styles.bubbleMenuButton, { [styles.bubbleMenuButtonActive]: isItalic() })} - onClick={() => props.editor.chain().focus().toggleItalic().run()} + onClick={() => props.editor?.chain().focus().toggleItalic().run()} > @@ -326,7 +326,9 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { class={clsx(styles.bubbleMenuButton, { [styles.bubbleMenuButtonActive]: isHighlight() })} - onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()} + onClick={() => + props.editor?.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run() + } >
@@ -389,7 +391,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isBulletList() })} onClick={() => { - props.editor.chain().focus().toggleBulletList().run() + props.editor?.chain().focus().toggleBulletList().run() toggleListPopup() }} > @@ -406,7 +408,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => { [styles.bubbleMenuButtonActive]: isOrderedList() })} onClick={() => { - props.editor.chain().focus().toggleOrderedList().run() + props.editor?.chain().focus().toggleOrderedList().run() toggleListPopup() }} > diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 28e953f6..e4a401d7 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -7,6 +7,7 @@ import { Popover } from '~/components/_shared/Popover' import { useLocalize } from '~/context/localize' import { useSession } from '~/context/session' import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen' +import { sentenceSeparator } from '~/intl/chars' import { capitalize } from '~/utils/capitalize' import { descFromBody } from '~/utils/meta' import { CoverImage } from '../../Article/CoverImage' diff --git a/src/components/SearchModal/SearchModal.module.scss b/src/components/SearchModal/SearchModal.module.scss index a35c919c..ddfc00eb 100644 --- a/src/components/SearchModal/SearchModal.module.scss +++ b/src/components/SearchModal/SearchModal.module.scss @@ -1,6 +1,4 @@ @mixin search-filter-control { - @include font-size(1.4rem); - height: 4rem; padding: 0 2rem; background: rgb(64 64 64 / 50%); @@ -9,6 +7,8 @@ font-weight: 500; white-space: nowrap; + @include font-size(1.4rem); + &:hover { background: #404040; } @@ -23,8 +23,6 @@ } .searchInput { - @include font-size(4.8rem); - width: 100%; padding: 0 0 0.5rem; background: none; @@ -34,6 +32,8 @@ font-weight: bold; outline: none; + @include font-size(4.8rem); + &::placeholder { color: rgb(255 255 255 / 32%); } @@ -60,10 +60,10 @@ } .searchDescription { - @include font-size(1.6rem); - margin-bottom: 44px; color: rgb(255 255 255 / 64%); + + @include font-size(1.6rem); } .topicsList { diff --git a/src/components/Topic/TopicBadge/TopicBadge.module.scss b/src/components/Topic/TopicBadge/TopicBadge.module.scss index f4c205bc..06280c1c 100644 --- a/src/components/Topic/TopicBadge/TopicBadge.module.scss +++ b/src/components/Topic/TopicBadge/TopicBadge.module.scss @@ -72,7 +72,7 @@ line-height: 1.4; margin: 0.8rem 0; - -webkit-line-clamp: 2; + line-clamp: 2; } } @@ -165,4 +165,4 @@ word-break: keep-all; } } -} \ No newline at end of file +} diff --git a/src/components/TopicsNav/TopicsNav.tsx b/src/components/TopicsNav/TopicsNav.tsx index 5357e22f..b0818bb4 100644 --- a/src/components/TopicsNav/TopicsNav.tsx +++ b/src/components/TopicsNav/TopicsNav.tsx @@ -5,16 +5,15 @@ import { Icon } from '~/components/_shared/Icon' import { useLocalize } from '~/context/localize' import { useTopics } from '~/context/topics' import type { Topic } from '~/graphql/schema/core.gen' +import { ruChars } from '~/intl/chars' import { getRandomItemsFromArray } from '~/utils/random' import styles from './TopicsNav.module.scss' -const russianChars = /[ЁА-яё]/ - export const RandomTopics = () => { const { sortedTopics } = useTopics() const { lang, t } = useLocalize() const tag = (topic: Topic) => - russianChars.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title + ruChars.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title const [randomTopics, setRandomTopics] = createSignal([]) createEffect( on(sortedTopics, (ttt: Topic[]) => { diff --git a/src/components/Views/AllTopics/AllTopics.tsx b/src/components/Views/AllTopics/AllTopics.tsx index 126e359b..d1e9421e 100644 --- a/src/components/Views/AllTopics/AllTopics.tsx +++ b/src/components/Views/AllTopics/AllTopics.tsx @@ -6,6 +6,7 @@ import { SearchField } from '~/components/_shared/SearchField' import { useLocalize } from '~/context/localize' import { useTopics } from '~/context/topics' import type { Topic } from '~/graphql/schema/core.gen' +import { enChars, ruChars } from '~/intl/chars' import { dummyFilter } from '~/intl/dummyFilter' import { scrollHandler } from '~/utils/scroll' import { TopicBadge } from '../../Topic/TopicBadge' @@ -21,9 +22,6 @@ export const ABC = { en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#' } -const russianChars = /[^ËА-яё]/ -const latinChars = /[^A-z]/ - export const AllTopics = (props: Props) => { const { t, lang } = useLocalize() const alphabet = createMemo(() => ABC[lang()]) @@ -38,8 +36,8 @@ export const AllTopics = (props: Props) => { return topics().reduce( (acc, topic) => { let letter = lang() === 'en' ? topic.slug[0].toUpperCase() : (topic?.title?.[0] || '').toUpperCase() - if (russianChars.test(letter) && lang() === 'ru') letter = '#' - if (latinChars.test(letter) && lang() === 'en') letter = '#' + if (enChars.test(letter) && lang() === 'ru') letter = '#' + if (ruChars.test(letter) && lang() === 'en') letter = '#' if (!acc[letter]) acc[letter] = [] acc[letter].push(topic) return acc diff --git a/src/components/Views/Profile/ProfileSettings.tsx b/src/components/Views/Profile/ProfileSettings.tsx index fb7b962e..94c7eddf 100644 --- a/src/components/Views/Profile/ProfileSettings.tsx +++ b/src/components/Views/Profile/ProfileSettings.tsx @@ -340,8 +340,16 @@ export const ProfileSettings = () => { />

{t('About')}

- updateFormField('about', value)} placeholder={t('About')} /> diff --git a/src/components/_shared/DropArea/DropArea.tsx b/src/components/_shared/DropArea/DropArea.tsx index 0c1159cd..a49005cb 100644 --- a/src/components/_shared/DropArea/DropArea.tsx +++ b/src/components/_shared/DropArea/DropArea.tsx @@ -37,9 +37,8 @@ export const DropArea = (props: Props) => { const runUpload = async (files: UploadFile[]) => { try { setLoading(true) - const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload const tkn = session()?.access_token as string - // Since handler returns a promise, we need to await the results + const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload tkn && Promise.all(files.map((file) => handler(file, tkn))) .then(props.onUpload) diff --git a/src/context/following.tsx b/src/context/following.tsx index f4eb4853..4ae3e25c 100644 --- a/src/context/following.tsx +++ b/src/context/following.tsx @@ -172,7 +172,7 @@ export const FollowingProvider = (props: { children: JSX.Element }) => { } break } - // case 'AUTHOR': + // case 'AUTHOR': { default: { if (value) { if (!updatedFollows.authors?.some((author) => author.slug === slug)) { diff --git a/src/intl/chars.ts b/src/intl/chars.ts new file mode 100644 index 00000000..37e85f66 --- /dev/null +++ b/src/intl/chars.ts @@ -0,0 +1,6 @@ +export const allChars = /[^\dA-zА-я]/ +export const slugChars = /[^\da-z]/g +export const enChars = /[^A-z]/ +export const ruChars = /[^ËА-яё]/ +export const sentenceSeparator = /{!|\?|:|;}\s/ +export const cyrillicRegex = /[\u0400-\u04FF]/ // Range for Cyrillic characters diff --git a/src/intl/translate.ts b/src/intl/translate.ts index 59c6d875..6440b64e 100644 --- a/src/intl/translate.ts +++ b/src/intl/translate.ts @@ -1,12 +1,8 @@ import { Author } from '~/graphql/schema/core.gen' import { capitalize } from '~/utils/capitalize' +import { allChars, cyrillicRegex, enChars, ruChars } from './chars' import { translit } from './translit' -const cyrillicRegex = /[\u0400-\u04FF]/ // Range for Cyrillic characters -const allChars = /[^\dA-zА-я]/ -const rusChars = /[^ËА-яё]/ -const enChars = /[^A-z]/ - export const isCyrillic = (s: string): boolean => { return cyrillicRegex.test(s) } @@ -32,7 +28,7 @@ export const authorLetterReduce = (acc: { [x: string]: Author[] }, author: Autho letter = found[0].toUpperCase() } } - if (rusChars.test(letter) && lng === 'ru') letter = '@' + if (ruChars.test(letter) && lng === 'ru') letter = '@' if (enChars.test(letter) && lng === 'en') letter = '@' if (!acc[letter]) acc[letter] = [] diff --git a/src/intl/translit.ts b/src/intl/translit.ts index 0cbfc5ac..7e3ad936 100644 --- a/src/intl/translit.ts +++ b/src/intl/translit.ts @@ -1,4 +1,5 @@ import translitConfig from './abc-translit.json' +import { ruChars, slugChars } from './chars' const ru2en: { [key: string]: string } = translitConfig const rusChars = /[ЁА-яё]/ @@ -7,7 +8,7 @@ export const translit = (str: string) => { return '' } - const isCyrillic = rusChars.test(str) + const isCyrillic = ruChars.test(str) if (!isCyrillic) { return str @@ -17,7 +18,5 @@ export const translit = (str: string) => { } export const slugify = (text: string) => { - return translit(text.toLowerCase()) - .replaceAll(' ', '-') - .replaceAll(/[^\da-z]/g, '') + return translit(text.toLowerCase()).replaceAll(' ', '-').replaceAll(slugChars, '') } diff --git a/src/routes/feed/my/[...mode]/[...order].tsx b/src/routes/feed/my/[...mode]/[...order].tsx index bc7ea929..52daa16f 100644 --- a/src/routes/feed/my/[...mode]/[...order].tsx +++ b/src/routes/feed/my/[...mode]/[...order].tsx @@ -54,7 +54,7 @@ export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>) const order = createMemo(() => { return ( - (paramOrderPattern.test(props.params.order) + (paramPattern.test(props.params.order) ? props.params.order === 'hot' ? 'last_comment' : props.params.order diff --git a/tests/1-page-sections.spec.ts b/tests/1-page-sections.spec.ts index 8454dab4..a16f3854 100644 --- a/tests/1-page-sections.spec.ts +++ b/tests/1-page-sections.spec.ts @@ -5,7 +5,7 @@ import { type Page, expect, test } from '@playwright/test' /* Global starting test config */ let page: Page -const discoursPattern = /Дискурс/ + function httpsGet(url: string): Promise { return new Promise((resolve, reject) => { https @@ -50,7 +50,8 @@ test.beforeAll(async ({ browser }) => { page = await browser.newPage() test.setTimeout(150000) await page.goto(baseURL) - await expect(page).toHaveTitle(discoursPattern) + // biome-ignore lint/performance/useTopLevelRegex: + await expect(page).toHaveTitle(/Дискурс/) console.log('Localhost server started successfully!') }) test.afterAll(async () => { diff --git a/tests/2-auth-topic-actions.spec.ts b/tests/2-auth-topic-actions.spec.ts index afa0f196..30196cf2 100644 --- a/tests/2-auth-topic-actions.spec.ts +++ b/tests/2-auth-topic-actions.spec.ts @@ -5,7 +5,6 @@ import { type Page, expect, test } from '@playwright/test' /* Global starting test config */ let page: Page -const discoursPattern = /Дискурс/ function httpsGet(url: string): Promise { return new Promise((resolve, reject) => { https @@ -50,7 +49,8 @@ test.beforeAll(async ({ browser }) => { page = await browser.newPage() test.setTimeout(150000) await page.goto(baseURL) - await expect(page).toHaveTitle(discoursPattern) + // biome-ignore lint/performance/useTopLevelRegex: + await expect(page).toHaveTitle(/Дискурс/) await page.getByRole('link', { name: 'Войти' }).click() console.log('Localhost server started successfully!') await page.close() diff --git a/tests/3-auth-drafts-actions.spec.ts b/tests/3-auth-drafts-actions.spec.ts index ee88e6d0..c9b592bc 100644 --- a/tests/3-auth-drafts-actions.spec.ts +++ b/tests/3-auth-drafts-actions.spec.ts @@ -6,8 +6,6 @@ import { type Page, expect, test } from '@playwright/test' let page: Page -const discoursPattern = /Дискурс/ - function httpsGet(url: string): Promise { return new Promise((resolve, reject) => { https @@ -52,7 +50,8 @@ test.beforeAll(async ({ browser }) => { page = await browser.newPage() test.setTimeout(150000) await page.goto(baseURL) - await expect(page).toHaveTitle(discoursPattern) + // biome-ignore lint/performance/useTopLevelRegex: + await expect(page).toHaveTitle(/Дискурс/) console.log('Localhost server started successfully!') await page.close() }) diff --git a/tests/4-auth-author-actions.spec.ts b/tests/4-auth-author-actions.spec.ts index 50d79726..d47516e0 100644 --- a/tests/4-auth-author-actions.spec.ts +++ b/tests/4-auth-author-actions.spec.ts @@ -8,8 +8,6 @@ let page: Page /* Global starting test config */ -const discoursPattern = /Дискурс/ - function httpsGet(url: string): Promise { return new Promise((resolve, reject) => { https @@ -56,7 +54,8 @@ test.beforeAll(async ({ browser }) => { page = await context.newPage() test.setTimeout(150000) await page.goto(baseURL) - await expect(page).toHaveTitle(discoursPattern) + // biome-ignore lint/performance/useTopLevelRegex: + await expect(page).toHaveTitle(/Дискурс/) await page.getByRole('link', { name: 'Войти' }).click() console.log('Localhost server started successfully!') await page.close()