This commit is contained in:
Untone 2024-09-15 21:54:40 +03:00
commit 199f845610
21 changed files with 78 additions and 71 deletions

View File

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

View File

@ -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()
}}
>
<Icon name="editor-image-align-left" />
@ -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()}
>
<Icon name="editor-image-align-center" />
</button>
@ -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()}
>
<Icon name="editor-image-align-right" />
</button>

View File

@ -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()}
>
<Icon name="editor-image-align-left" />
</button>
@ -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()}
>
<Icon name="editor-image-align-center" />
</button>
@ -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()}
>
<Icon name="editor-image-align-right" />
</button>
@ -67,7 +67,7 @@ export const FigureBubbleMenu = (props: Props) => {
<button
type="button"
class={styles.bubbleMenuButton}
onClick={() => props.editor.chain().focus().setFigcaptionFocus(true).run()}
onClick={() => props.editor?.chain().focus().setFigcaptionFocus(true).run()}
>
<span style={{ color: 'white' }}>{t('Add signature')}</span>
</button>

View File

@ -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) => {
<button
type="button"
class={styles.bubbleMenuButton}
onClick={() => props.editor.chain().focus().setArticleFloat('half-left').run()}
onClick={() => props.editor?.chain().focus().setArticleFloat('half-left').run()}
>
<Icon name="editor-image-half-align-left" />
</button>
<button
type="button"
class={styles.bubbleMenuButton}
onClick={() => props.editor.chain().focus().setArticleFloat(null).run()}
onClick={() => props.editor?.chain().focus().setArticleFloat(null).run()}
>
<Icon name="editor-image-align-center" />
</button>
@ -42,7 +42,7 @@ export const IncutBubbleMenu = (props: Props) => {
<button
type="button"
class={styles.bubbleMenuButton}
onClick={() => props.editor.chain().focus().setArticleFloat('half-right').run()}
onClick={() => props.editor?.chain().focus().setArticleFloat('half-right').run()}
>
<Icon name="editor-image-half-align-right" />
</button>

View File

@ -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()}
>
<Icon name="editor-bold" />
</button>
@ -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()}
>
<Icon name="editor-italic" />
</button>
@ -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()
}
>
<div class={styles.toggleHighlight} />
</button>
@ -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()
}}
>

View File

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

View File

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

View File

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

View File

@ -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<Topic[]>([])
createEffect(
on(sortedTopics, (ttt: Topic[]) => {

View File

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

View File

@ -340,8 +340,16 @@ export const ProfileSettings = () => {
/>
<h4>{t('About')}</h4>
<MiniEditor
content={about() || ''}
<SimplifiedEditor
resetToInitial={clearAbout()}
noLimits={true}
variant="bordered"
onlyBubbleControls={true}
smallHeight={true}
placeholder={t('About')}
label={t('About')}
initialContent={form.about || ''}
autoFocus={false}
onChange={(value) => updateFormField('about', value)}
placeholder={t('About')}
/>

View File

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

View File

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

6
src/intl/chars.ts Normal file
View File

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

View File

@ -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] = []

View File

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

View File

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

View File

@ -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<void> {
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: <explanation>
await expect(page).toHaveTitle(/Дискурс/)
console.log('Localhost server started successfully!')
})
test.afterAll(async () => {

View File

@ -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<void> {
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: <explanation>
await expect(page).toHaveTitle(/Дискурс/)
await page.getByRole('link', { name: 'Войти' }).click()
console.log('Localhost server started successfully!')
await page.close()

View File

@ -6,8 +6,6 @@ import { type Page, expect, test } from '@playwright/test'
let page: Page
const discoursPattern = /Дискурс/
function httpsGet(url: string): Promise<void> {
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: <explanation>
await expect(page).toHaveTitle(/Дискурс/)
console.log('Localhost server started successfully!')
await page.close()
})

View File

@ -8,8 +8,6 @@ let page: Page
/* Global starting test config */
const discoursPattern = /Дискурс/
function httpsGet(url: string): Promise<void> {
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: <explanation>
await expect(page).toHaveTitle(/Дискурс/)
await page.getByRole('link', { name: 'Войти' }).click()
console.log('Localhost server started successfully!')
await page.close()