diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml
index 2d2d4bc8..1220b122 100644
--- a/.github/workflows/node-ci.yml
+++ b/.github/workflows/node-ci.yml
@@ -16,7 +16,7 @@ jobs:
run: npm run typecheck
- name: Lint with Biome
- run: npx biome ci .
+ run: npm run check:code
- name: Lint styles
run: npm run lint:styles
diff --git a/biome.json b/biome.json
index ba7464a5..ca4262bb 100644
--- a/biome.json
+++ b/biome.json
@@ -2,7 +2,7 @@
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
"files": {
"include": ["*.tsx", "*.ts", "*.js", "*.json"],
- "ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.d.ts"]
+ "ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.gen.ts", "*.d.ts"]
},
"vcs": {
"defaultBranch": "dev",
diff --git a/package.json b/package.json
index 5e79d11f..1da95480 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"hygen": "HYGEN_TMPLS=gen hygen",
"postinstall": "npm run codegen && npx patch-package",
"check:code": "npx @biomejs/biome check src --log-kind=compact --verbose",
- "check:code:fix": "npx @biomejs/biome check src --log-kind=compact --verbose --apply-unsafe",
+ "check:code:fix": "npx @biomejs/biome check src --log-kind=compact",
"lint": "npm run lint:code && stylelint **/*.{scss,css}",
"lint:code": "npx @biomejs/biome lint src --log-kind=compact --verbose",
"lint:code:fix": "npx @biomejs/biome lint src --apply-unsafe --log-kind=compact --verbose",
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index 86a191f6..2690613b 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -83,6 +83,7 @@
"Coming soon": "Coming soon",
"Comment successfully deleted": "Comment successfully deleted",
"Commentator": "Commentator",
+ "Commenting": "Commenting",
"Comments": "Comments",
"CommentsWithCount": "{count, plural, =0 {{count} comments} one {{count} comment} few {{count} comments} other {{count} comments}}",
"Communities": "Communities",
diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json
index 8a7d26c8..13ba2de4 100644
--- a/public/locales/ru/translation.json
+++ b/public/locales/ru/translation.json
@@ -87,6 +87,7 @@
"Comment successfully deleted": "Комментарий успешно удален",
"Comment": "Комментировать",
"Commentator": "Комментатор",
+ "Commenting": "Комментирование",
"Comments": "Комментарии",
"CommentsWithCount": "{count, plural, =0 {{count} комментариев} one {{count} комментарий} few {{count} комментария} other {{count} комментариев}}",
"Communities": "Сообщества",
diff --git a/public/robots.txt b/public/robots.txt
index c2a49f4f..1f53798b 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -1,2 +1,2 @@
User-agent: *
-Allow: /
+Disallow: /
diff --git a/src/components/Article/Article.module.scss b/src/components/Article/Article.module.scss
index ad85c0a3..55b8dd95 100644
--- a/src/components/Article/Article.module.scss
+++ b/src/components/Article/Article.module.scss
@@ -22,6 +22,7 @@ img {
.articleContent {
img:not([data-disable-lightbox='true']) {
cursor: zoom-in;
+ width: 100%;
}
}
diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx
index f855f170..bc7fd403 100644
--- a/src/components/Article/FullArticle.tsx
+++ b/src/components/Article/FullArticle.tsx
@@ -54,6 +54,7 @@ type IframeSize = {
export type ArticlePageSearchParams = {
scrollTo: 'comments'
commentId: string
+ slide?: string
}
const scrollTo = (el: HTMLElement) => {
@@ -329,7 +330,7 @@ export const FullArticle = (props: Props) => {
width: 1200,
})
- const description = getDescription(props.article.description || body())
+ const description = getDescription(props.article.description || body() || media()[0]?.body)
const ogTitle = props.article.title
const keywords = getKeywords(props.article)
const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` })
diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx
index ed13e10d..65e14027 100644
--- a/src/components/Author/AuthorCard/AuthorCard.tsx
+++ b/src/components/Author/AuthorCard/AuthorCard.tsx
@@ -135,7 +135,9 @@ export const AuthorCard = (props: Props) => {
)}
- {t('SubscriberWithCount', { count: props.followers.length ?? 0 })}
+ {t('SubscriberWithCount', {
+ count: props.followers.length ?? 0,
+ })}
@@ -170,7 +172,9 @@ export const AuthorCard = (props: Props) => {
}}
- {t('SubscriptionWithCount', { count: props?.following.length ?? 0 })}
+ {t('SubscriptionWithCount', {
+ count: props?.following.length ?? 0,
+ })}
@@ -236,7 +240,9 @@ export const AuthorCard = (props: Props) => {
title={props.author.name}
description={props.author.bio}
imageUrl={props.author.pic}
- shareUrl={getShareUrl({ pathname: `/author/${props.author.slug}` })}
+ shareUrl={getShareUrl({
+ pathname: `/author/${props.author.slug}`,
+ })}
trigger={}
/>
@@ -264,13 +270,21 @@ export const AuthorCard = (props: Props) => {
<>
{t('Subscriptions')}
- -
+
-
{props.following.length}
- -
+
-
@@ -278,7 +292,11 @@ export const AuthorCard = (props: Props) => {
{props.following.filter((s) => 'name' in s).length}
- -
+
-
diff --git a/src/components/Nav/Header/Header.module.scss b/src/components/Nav/Header/Header.module.scss
index 018adc78..010a8164 100644
--- a/src/components/Nav/Header/Header.module.scss
+++ b/src/components/Nav/Header/Header.module.scss
@@ -8,7 +8,7 @@
z-index: 10003;
.wide-container {
- background: #fff;
+ background: var(--background-color);
@include media-breakpoint-down(lg) {
padding: 0 divide($container-padding-x, 2);
@@ -114,6 +114,11 @@
position: absolute;
right: 0;
}
+
+ .control {
+ align-items: center;
+ display: flex;
+ }
}
.mainNavigationWrapper {
@@ -192,15 +197,8 @@
padding: divide($container-padding-x, 2) !important;
}
- @include media-breakpoint-up(md) {
- span,
- button {
- padding: 0 0.4rem;
- }
- }
-
:global(.view-switcher) {
- margin: 0 -0.5rem;
+ margin: 0;
overflow: hidden;
padding: 0;
}
@@ -299,9 +297,6 @@
.burgerContainer {
box-sizing: content-box;
display: inline-flex;
- padding-left: 0;
-
- // float: right;
@include media-breakpoint-up(sm) {
padding-left: divide($container-padding-x, 2);
@@ -430,12 +425,15 @@
width: 100%;
@include media-breakpoint-up(xl) {
- right: 2rem;
+ right: 9rem;
}
.control {
- cursor: pointer;
border: 0;
+ cursor: pointer;
+ height: 3.2rem;
+ margin: 0 0.6rem;
+ width: 3.2rem;
&:hover {
background: none;
@@ -451,11 +449,7 @@
}
.control + .control {
- margin-left: 1.2rem;
-
- @include media-breakpoint-up(sm) {
- margin-left: 2rem;
- }
+ margin: 0 0.6rem;
}
img {
@@ -497,10 +491,15 @@
}
}
+ .settingsControlContainer {
+ margin-left: 1rem !important;
+ margin-right: 2rem !important;
+ }
+
.settingsControl {
border-radius: 100%;
- padding: 0.8rem !important;
min-width: 4rem !important;
+ padding: 0.8rem !important;
&:hover {
background: var(--background-color-invert);
@@ -516,12 +515,18 @@
align-items: center;
border-radius: 100%;
display: flex;
- height: 2.4em;
+ height: 2.8rem;
justify-content: center;
- margin-left: 0.3rem;
+ margin: 0 0.4rem;
position: relative;
transition: margin-left 0.3s;
- width: 2.4em;
+ width: 2.8rem;
+
+ @include media-breakpoint-up(md) {
+ height: 3.2rem;
+ margin: 0 0.7rem;
+ width: 3.2rem;
+ }
@include media-breakpoint-down(sm) {
margin-left: 0.4rem !important;
@@ -543,12 +548,13 @@
a:link {
border: none;
cursor: pointer;
- height: auto;
+ height: 100%;
margin: 0;
padding: 0;
+ width: 100%;
&:hover {
- background: none !important;
+ background: none;
.icon {
display: none;
@@ -571,6 +577,20 @@
}
}
+.userControlItemSearch {
+ margin: 0 1rem 0 2.2rem;
+}
+
+.userControlItemUserpic {
+ height: 3.2rem;
+ width: 3.2rem;
+
+ @include media-breakpoint-up(md) {
+ height: 4rem;
+ width: 4rem;
+ }
+}
+
.userControlItemInbox,
.userControlItemSearch {
@include media-breakpoint-down(sm) {
@@ -579,7 +599,16 @@
}
.userControlItemVerbose {
- margin-left: 0.9em !important;
+ align-items: stretch;
+ display: flex;
+ height: 3.2rem;
+ margin-left: 1rem !important;
+ width: 3.2rem;
+
+ @include media-breakpoint-up(md) {
+ height: 4rem;
+ width: 4rem;
+ }
&:first-child {
margin-left: 0 !important;
@@ -590,6 +619,7 @@
@include media-breakpoint-up(xl) {
background: none;
+ margin-left: 0.8rem !important;
}
.icon {
@@ -611,10 +641,14 @@
}
@include media-breakpoint-up(xl) {
- margin-left: 0.5em !important;
- margin-right: 0.5em;
+ margin-left: 3rem !important;
+ margin-right: 0;
width: auto;
+ &:last-child {
+ margin-right: 0;
+ }
+
.icon {
display: none !important;
}
@@ -629,6 +663,37 @@
}
}
+ a:link,
+ a:visited,
+ button {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+
+ @include media-breakpoint-up(xl) {
+ border-radius: 2rem;
+ box-shadow: inset 0 0 0 2px #000;
+ padding: 0 2rem;
+ }
+
+ &:hover {
+ background-color: var(--link-hover-background);
+
+ &,
+ .textLabel {
+ color: #fff !important;
+ }
+
+ .icon {
+ display: none;
+ }
+
+ .iconHover {
+ display: block;
+ }
+ }
+ }
+
button {
margin: 0 !important;
}
@@ -636,27 +701,6 @@
a::before {
display: none;
}
-
- a:hover,
- button:hover {
- .icon {
- display: none;
- }
-
- .iconHover {
- display: block;
- }
-
- .textLabel {
- color: var(--link-hover-color);
- }
- }
-
- a:hover {
- .textLabel {
- background-color: var(--link-hover-background);
- }
- }
}
.subnavigation {
@@ -746,3 +790,65 @@
position: relative;
top: 0.15em;
}
+
+.editorPopup {
+ border: 1px solid rgb(0 0 0 / 15%) !important;
+ border-radius: 1.6rem;
+ line-height: 1.3;
+ min-width: 28rem;
+ padding: 1.6rem !important;
+}
+
+.editorModePopupOpener {
+ display: inline-block;
+ margin-right: 2rem;
+ position: relative;
+ text-align: right;
+ width: 9em;
+}
+
+.editorModePopupOpenerIcon {
+ height: 2rem;
+ left: 100%;
+ margin-left: 0.2em;
+ top: 0;
+ transform: rotate(90deg);
+ position: absolute;
+ width: 2rem;
+}
+
+.editorModesList {
+ li {
+ cursor: pointer;
+ margin-bottom: 1.6rem;
+ padding-left: 3rem !important;
+ position: relative;
+
+ &:hover {
+ opacity: 0.6;
+ }
+ }
+
+ .editorModesSelected {
+ cursor: default;
+ opacity: 0.6;
+ }
+}
+
+.editorModeTitle {
+ color: #000;
+ margin-bottom: 0.5rem;
+}
+
+.editorModeDescription {
+ color: #696969;
+ font-size: 1.2rem;
+}
+
+.editorModeIcon {
+ height: 2.4rem;
+ left: 0;
+ position: absolute;
+ top: -0.2em;
+ width: 2.4rem;
+}
diff --git a/src/components/Nav/HeaderAuth.tsx b/src/components/Nav/HeaderAuth.tsx
index 31a0acc6..7f439b94 100644
--- a/src/components/Nav/HeaderAuth.tsx
+++ b/src/components/Nav/HeaderAuth.tsx
@@ -14,10 +14,9 @@ import { Icon } from '../_shared/Icon'
import { Popover } from '../_shared/Popover'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
-import { ProfilePopup } from './ProfilePopup'
-
-import { useSnackbar } from '../../context/snackbar'
+import { Popup } from '../_shared/Popup'
import styles from './Header/Header.module.scss'
+import { ProfilePopup } from './ProfilePopup'
type Props = {
setIsProfilePopupVisible: (value: boolean) => void
@@ -51,7 +50,7 @@ export const HeaderAuth = (props: Props) => {
const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings')
const isNotificationsVisible = createMemo(() => isAuthenticated() && !isEditorPage())
const isSaveButtonVisible = createMemo(() => isAuthenticated() && isEditorPage())
- const isCreatePostButtonVisible = createMemo(() => isAuthenticated() && !isEditorPage())
+ const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
const isAuthenticatedControlsVisible = createMemo(
() => isAuthenticated() && session()?.user?.email_verified,
)
@@ -65,6 +64,7 @@ export const HeaderAuth = (props: Props) => {
}
const [width, setWidth] = createSignal(0)
+ const [editorMode, setEditorMode] = createSignal(t('Editing'))
onMount(() => {
const handleResize = () => setWidth(window.innerWidth)
@@ -106,7 +106,7 @@ export const HeaderAuth = (props: Props) => {
-
+
{t('Create post')}
@@ -117,7 +117,7 @@ export const HeaderAuth = (props: Props) => {
-
+
@@ -143,13 +143,47 @@ export const HeaderAuth = (props: Props) => {
-
- {renderIconedButton({
- value: t('Save'),
- icon: 'save',
- action: handleSaveButtonClick,
- })}
-
+
{renderIconedButton({
@@ -159,12 +193,18 @@ export const HeaderAuth = (props: Props) => {
})}
-
+
{(ref) => (
}
+ value={}
variant={'light'}
onClick={handleBurgerButtonClick}
class={styles.settingsControl}
@@ -173,16 +213,29 @@ export const HeaderAuth = (props: Props) => {
+
+
+
+
+
-
- {t('Enter')}
-
- {/**/}
-
-
+
+
+
}
>
@@ -195,28 +248,31 @@ export const HeaderAuth = (props: Props) => {
-
{
- props.setIsProfilePopupVisible(isVisible)
- }}
- containerCssClass={styles.control}
- trigger={
-
- }
- />
+
+
+ {
+ props.setIsProfilePopupVisible(isVisible)
+ }}
+ containerCssClass={styles.control}
+ trigger={
+
+ }
+ />
+
diff --git a/src/components/Nav/Snackbar.module.scss b/src/components/Nav/Snackbar.module.scss
index a0fb8e64..9af5719b 100644
--- a/src/components/Nav/Snackbar.module.scss
+++ b/src/components/Nav/Snackbar.module.scss
@@ -1,5 +1,4 @@
.snackbar {
- min-height: 2px;
background-color: var(--default-color);
color: #fff;
font-size: 2rem;
diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx
index 70389ccb..9d405a9b 100644
--- a/src/components/ProfileSettings/ProfileSettings.tsx
+++ b/src/components/ProfileSettings/ProfileSettings.tsx
@@ -1,7 +1,18 @@
import { createFileUploader } from '@solid-primitives/upload'
import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal'
-import { For, Match, Show, Switch, createEffect, createSignal, lazy, onCleanup, onMount } from 'solid-js'
+import {
+ For,
+ Match,
+ Show,
+ Switch,
+ createEffect,
+ createSignal,
+ lazy,
+ on,
+ onCleanup,
+ onMount,
+} from 'solid-js'
import { createStore } from 'solid-js/store'
import { useConfirm } from '../../context/confirm'
@@ -33,6 +44,7 @@ export const ProfileSettings = () => {
const { t } = useLocalize()
const [prevForm, setPrevForm] = createStore({})
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
+ const [isSaving, setIsSaving] = createSignal(false)
const [social, setSocial] = createSignal([])
const [addLinkForm, setAddLinkForm] = createSignal(false)
const [incorrectUrl, setIncorrectUrl] = createSignal(false)
@@ -70,16 +82,20 @@ export const ProfileSettings = () => {
const handleSubmit = async (event: Event) => {
event.preventDefault()
+ setIsSaving(true)
if (nameInputRef.current.value.length === 0) {
setNameError(t('Required'))
nameInputRef.current.focus()
+ setIsSaving(false)
return
}
if (slugInputRef.current.value.length === 0) {
setSlugError(t('Required'))
slugInputRef.current.focus()
+ setIsSaving(false)
return
}
+
try {
await submit(form)
setPrevForm(clone(form))
@@ -91,6 +107,8 @@ export const ProfileSettings = () => {
return
}
showSnackbar({ type: 'error', body: t('Error') })
+ } finally {
+ setIsSaving(false)
}
await loadAuthor() // renews author's profile
@@ -149,12 +167,15 @@ export const ProfileSettings = () => {
onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload))
})
- createEffect(() => {
- if (!deepEqual(form, prevForm)) {
- setIsFloatingPanelVisible(true)
- }
- })
-
+ createEffect(
+ on(
+ () => deepEqual(form, prevForm),
+ () => {
+ setIsFloatingPanelVisible(!deepEqual(form, prevForm))
+ },
+ { defer: true },
+ ),
+ )
const handleDeleteSocialLink = (link) => {
updateFormField('links', link, true)
}
@@ -174,7 +195,7 @@ export const ProfileSettings = () => {
{t('Profile settings')}
{t('Here you can customize your profile the way you want.')}
-