Fix typography feature, scroll top button fix, slugify fix (#93)

* Fix typography feature, scroll top button fix, slugify fix

* build fix, some lint

* refactoring, lint
This commit is contained in:
Igor Lobanov 2023-05-12 15:03:46 +02:00 committed by GitHub
parent b040d5e0c9
commit 47d14b0a5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 99 additions and 46 deletions

16
package-lock.json generated
View File

@ -17,7 +17,8 @@
"formidable": "2.1.1", "formidable": "2.1.1",
"i18next": "22.4.15", "i18next": "22.4.15",
"mailgun.js": "8.2.1", "mailgun.js": "8.2.1",
"node-fetch": "3.3.1" "node-fetch": "3.3.1",
"typograf": "7.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.8", "@babel/core": "7.21.8",
@ -19579,6 +19580,14 @@
"node": ">=12.20" "node": ">=12.20"
} }
}, },
"node_modules/typograf": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/typograf/-/typograf-7.1.0.tgz",
"integrity": "sha512-R7Kbb7JKuT96hWHTWQbZrGUchCk98rM2IQ38mi0ye2LoEJRn4o3lSZ8DZxK4ufrszUlrXIvnidIZ0AKA7UHvgg==",
"engines": {
"node": ">= 4"
}
},
"node_modules/ua-parser-js": { "node_modules/ua-parser-js": {
"version": "0.7.33", "version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
@ -35067,6 +35076,11 @@
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"dev": true "dev": true
}, },
"typograf": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/typograf/-/typograf-7.1.0.tgz",
"integrity": "sha512-R7Kbb7JKuT96hWHTWQbZrGUchCk98rM2IQ38mi0ye2LoEJRn4o3lSZ8DZxK4ufrszUlrXIvnidIZ0AKA7UHvgg=="
},
"ua-parser-js": { "ua-parser-js": {
"version": "0.7.33", "version": "0.7.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",

View File

@ -37,7 +37,8 @@
"formidable": "2.1.1", "formidable": "2.1.1",
"i18next": "22.4.15", "i18next": "22.4.15",
"mailgun.js": "8.2.1", "mailgun.js": "8.2.1",
"node-fetch": "3.3.1" "node-fetch": "3.3.1",
"typograf": "7.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.8", "@babel/core": "7.21.8",

View File

@ -2,9 +2,9 @@
"...subscribing": "...subscribing", "...subscribing": "...subscribing",
"About myself": "About myself", "About myself": "About myself",
"About the project": "About the project", "About the project": "About the project",
"Add another image": "Add another image",
"Add comment": "Comment", "Add comment": "Comment",
"Add image": "Add image", "Add image": "Add image",
"Add another image": "Add another image",
"Address on Discourse": "Address on Discourse", "Address on Discourse": "Address on Discourse",
"All": "All", "All": "All",
"All authors": "All authors", "All authors": "All authors",
@ -46,9 +46,6 @@
"Create Chat": "Create Chat", "Create Chat": "Create Chat",
"Create Group": "Create a group", "Create Group": "Create a group",
"Create account": "Create an account", "Create account": "Create an account",
"Password should be at least 8 characters": "Password should be at least 8 characters",
"Password should contain at least one number": "Password should contain at least one number",
"Password should contain at least one special character: !@#$%^&*": "Password should contain at least one special character: !@#$%^&*",
"Create post": "Create post", "Create post": "Create post",
"Date of Birth": "Date of Birth", "Date of Birth": "Date of Birth",
"Delete": "Delete", "Delete": "Delete",
@ -77,6 +74,7 @@
"Feed settings": "Feed settings", "Feed settings": "Feed settings",
"Feedback": "Feedback", "Feedback": "Feedback",
"Fill email": "Fill email", "Fill email": "Fill email",
"Fix typography": "Fix typography",
"Follow": "Follow", "Follow": "Follow",
"Follow the topic": "Follow the topic", "Follow the topic": "Follow the topic",
"Followers": "Followers", "Followers": "Followers",
@ -146,6 +144,9 @@
"Partners": "Partners", "Partners": "Partners",
"Password": "Password", "Password": "Password",
"Password again": "Password again", "Password again": "Password again",
"Password should be at least 8 characters": "Password should be at least 8 characters",
"Password should contain at least one number": "Password should contain at least one number",
"Password should contain at least one special character: !@#$%^&*": "Password should contain at least one special character: !@#$%^&*",
"Passwords are not equal": "Passwords are not equal", "Passwords are not equal": "Passwords are not equal",
"Paste Embed code": "Paste Embed code", "Paste Embed code": "Paste Embed code",
"Personal": "Personal", "Personal": "Personal",
@ -163,6 +164,7 @@
"Profile": "Profile", "Profile": "Profile",
"Profile settings": "Profile settings", "Profile settings": "Profile settings",
"Publications": "Publications", "Publications": "Publications",
"Publish Settings": "Publish Settings",
"Quit": "Quit", "Quit": "Quit",
"Quotes": "Quotes", "Quotes": "Quotes",
"Reason uknown": "Reason unknown", "Reason uknown": "Reason unknown",
@ -224,6 +226,7 @@
"Try to find another way": "Try to find another way", "Try to find another way": "Try to find another way",
"Unfollow": "Unfollow", "Unfollow": "Unfollow",
"Unfollow the topic": "Unfollow the topic", "Unfollow the topic": "Unfollow the topic",
"Unnamed draft": "Unnamed draft",
"Upload": "Upload", "Upload": "Upload",
"Username": "Username", "Username": "Username",
"Userpic": "Userpic", "Userpic": "Userpic",
@ -279,7 +282,5 @@
"topics": "topics", "topics": "topics",
"user already exist": "user already exists", "user already exist": "user already exists",
"view": "view", "view": "view",
"zine": "zine", "zine": "zine"
"Unnamed draft": "Unnamed draft",
"Publish Settings": "Publish Settings"
} }

View File

@ -3,9 +3,9 @@
"A short introduction to keep the reader interested": "Небольшое вступление, чтобы заинтересовать читателя", "A short introduction to keep the reader interested": "Небольшое вступление, чтобы заинтересовать читателя",
"About myself": "О себе", "About myself": "О себе",
"About the project": "О проекте", "About the project": "О проекте",
"Add another image": "Добавить другое изображение",
"Add comment": "Комментировать", "Add comment": "Комментировать",
"Add image": "Добавить изображение", "Add image": "Добавить изображение",
"Add another image": "Добавить другое изображение",
"Add to bookmarks": "Добавить в закладки", "Add to bookmarks": "Добавить в закладки",
"Address on Discourse": "Адрес на Дискурсе", "Address on Discourse": "Адрес на Дискурсе",
"All": "Все", "All": "Все",
@ -48,9 +48,6 @@
"Create Chat": "Создать чат", "Create Chat": "Создать чат",
"Create Group": "Создать группу", "Create Group": "Создать группу",
"Create account": "Создать аккаунт", "Create account": "Создать аккаунт",
"Password should be at least 8 characters": "Пароль должен быть не менее 8 символов",
"Password should contain at least one number": "Пароль должен содержать хотя бы одну цифру",
"Password should contain at least one special character: !@#$%^&*": "Пароль должен содержать хотя бы один специальный символ: !@#$%^&*",
"Create post": "Создать публикацию", "Create post": "Создать публикацию",
"Date of Birth": "Дата рождения", "Date of Birth": "Дата рождения",
"Delete": "Удалить", "Delete": "Удалить",
@ -80,6 +77,7 @@
"Feed settings": "Настройки ленты", "Feed settings": "Настройки ленты",
"Feedback": "Обратная связь", "Feedback": "Обратная связь",
"Fill email": "Введите почту", "Fill email": "Введите почту",
"Fix typography": "Исправить типографику",
"Follow": "Подписаться", "Follow": "Подписаться",
"Follow the topic": "Подписаться на тему", "Follow the topic": "Подписаться на тему",
"Followers": "Подписчики", "Followers": "Подписчики",
@ -153,6 +151,9 @@
"Partners": "Партнёры", "Partners": "Партнёры",
"Password": "Пароль", "Password": "Пароль",
"Password again": "Пароль ещё раз", "Password again": "Пароль ещё раз",
"Password should be at least 8 characters": "Пароль должен быть не менее 8 символов",
"Password should contain at least one number": "Пароль должен содержать хотя бы одну цифру",
"Password should contain at least one special character: !@#$%^&*": "Пароль должен содержать хотя бы один специальный символ: !@#$%^&*",
"Passwords are not equal": "Пароли не совпадают", "Passwords are not equal": "Пароли не совпадают",
"Paste Embed code": "Вставьте embed код", "Paste Embed code": "Вставьте embed код",
"Personal": "Личные", "Personal": "Личные",
@ -174,6 +175,7 @@
"Publication settings": "Настройки публикации", "Publication settings": "Настройки публикации",
"Publications": "Публикации", "Publications": "Публикации",
"Publish": "Опубликовать", "Publish": "Опубликовать",
"Publish Settings": "Настройки публикации",
"Quit": "Выйти", "Quit": "Выйти",
"Quotes": "Цитаты", "Quotes": "Цитаты",
"Reason uknown": "Причина неизвестна", "Reason uknown": "Причина неизвестна",
@ -237,6 +239,7 @@
"Try to find another way": "Попробуйте найти по-другому", "Try to find another way": "Попробуйте найти по-другому",
"Unfollow": "Отписаться", "Unfollow": "Отписаться",
"Unfollow the topic": "Отписаться от темы", "Unfollow the topic": "Отписаться от темы",
"Unnamed draft": "Unnamed draft",
"Upload": "Загрузить", "Upload": "Загрузить",
"Username": "Имя пользователя", "Username": "Имя пользователя",
"Userpic": "Аватар", "Userpic": "Аватар",
@ -300,7 +303,5 @@
"topics": "темы", "topics": "темы",
"user already exist": "пользователь уже существует", "user already exist": "пользователь уже существует",
"view": "просмотр", "view": "просмотр",
"zine": "журнал", "zine": "журнал"
"Publish Settings": "Настройки публикации",
"Unnamed draft": "Unnamed draft"
} }

View File

@ -187,12 +187,14 @@ export const Editor = (props: EditorProps) => {
] ]
})) }))
const html = useEditorHTML(() => editor())
const { const {
actions: { countWords } actions: { countWords, setEditor }
} = useEditorContext() } = useEditorContext()
setEditor(editor)
const html = useEditorHTML(() => editor())
createEffect(() => { createEffect(() => {
props.onChange(html()) props.onChange(html())
if (html()) { if (html()) {

View File

@ -59,6 +59,13 @@
a { a {
text-decoration: none; text-decoration: none;
border-bottom: none; border-bottom: none;
color: rgb(255 255 255 / 35%);
font-weight: normal !important;
&:hover {
background: none;
color: #fff;
}
} }
.linkWithIcon { .linkWithIcon {
@ -73,16 +80,6 @@
margin-top: 3em; margin-top: 3em;
} }
a {
color: rgb(255 255 255 / 35%);
font-weight: normal !important;
&:hover {
background: none;
color: #fff;
}
}
&.hidden { &.hidden {
transform: translateX(100%); transform: translateX(100%);
} }

View File

@ -8,6 +8,10 @@ import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler' import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { router } from '../../../stores/router' import { router } from '../../../stores/router'
import { useEditorHTML } from 'solid-tiptap'
import Typograf from 'typograf'
const typograf = new Typograf({ locale: ['ru', 'en-US'] })
type Props = { type Props = {
shoutId: number shoutId: number
@ -18,6 +22,7 @@ export const Panel = (props: Props) => {
const { const {
isEditorPanelVisible, isEditorPanelVisible,
wordCounter, wordCounter,
editorRef,
actions: { toggleEditorPanel, saveShout, publishShout } actions: { toggleEditorPanel, saveShout, publishShout }
} = useEditorContext() } = useEditorContext()
@ -47,6 +52,12 @@ export const Panel = (props: Props) => {
publishShout() publishShout()
} }
const handleFixTypographyLinkClick = (e) => {
e.preventDefault()
const html = useEditorHTML(() => editorRef.current())
editorRef.current().commands.setContent(typograf.execute(html()))
}
return ( return (
<aside <aside
ref={(el) => (containerRef.current = el)} ref={(el) => (containerRef.current = el)}
@ -111,6 +122,11 @@ export const Panel = (props: Props) => {
{t('Publication settings')} {t('Publication settings')}
</a> </a>
</p> </p>
<p>
<a onClick={handleFixTypographyLinkClick} href="#">
{t('Fix typography')}
</a>
</p>
<p> <p>
<a>{t('Corrections history')}</a> <a>{t('Corrections history')}</a>
</p> </p>

View File

@ -1,5 +1,6 @@
.ProseMirror { .ProseMirror {
outline: none; outline: none;
min-height: 300px;
blockquote { blockquote {
@include font-size(1.6rem); @include font-size(1.6rem);

View File

@ -9,7 +9,7 @@
color: #ccc; color: #ccc;
} }
&:before { &::before {
background: #000; background: #000;
content: ''; content: '';
height: 100%; height: 100%;

View File

@ -89,14 +89,15 @@
} }
h4 { h4 {
@include font-size(1.2rem);
color: #9fa1a7; color: #9fa1a7;
cursor: pointer; cursor: pointer;
@include font-size(1.2rem);
letter-spacing: 0.05em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
position: relative; position: relative;
&:after { &::after {
content: '+'; content: '+';
font-size: 1.6em; font-size: 1.6em;
line-height: 1; line-height: 1;
@ -107,7 +108,7 @@
} }
&.opened { &.opened {
&:after { &::after {
right: 0.9rem; right: 0.9rem;
transform: rotate(45deg); transform: rotate(45deg);
} }

View File

@ -44,18 +44,18 @@ export const RegisterForm = () => {
} }
} }
function isValidPassword(password) { function isValidPassword(passwordToCheck) {
const minLength = 8 const minLength = 8
const hasNumber = /\d/ const hasNumber = /\d/
const hasSpecial = /[!@#$%^&*]/ const hasSpecial = /[!#$%&*@^]/
if (password.length < minLength) { if (passwordToCheck.length < minLength) {
return t('Password should be at least 8 characters') return t('Password should be at least 8 characters')
} }
if (!hasNumber.test(password)) { if (!hasNumber.test(passwordToCheck)) {
return t('Password should contain at least one number') return t('Password should contain at least one number')
} }
if (!hasSpecial.test(password)) { if (!hasSpecial.test(passwordToCheck)) {
return t('Password should contain at least one special character: !@#$%^&*') return t('Password should contain at least one special character: !@#$%^&*')
} }
return null return null

View File

@ -20,7 +20,8 @@ type EditViewProps = {
shout: Shout shout: Shout
} }
const scrollTop = () => { const handleScrollTopButtonClick = (e) => {
e.preventDefault()
window.scrollTo({ window.scrollTo({
top: 0, top: 0,
behavior: 'smooth' behavior: 'smooth'
@ -124,7 +125,7 @@ export const EditView = (props: EditViewProps) => {
class={clsx(styles.scrollTopButton, { class={clsx(styles.scrollTopButton, {
[styles.visible]: isScrolled() [styles.visible]: isScrolled()
})} })}
onClick={scrollTop} onClick={handleScrollTopButtonClick}
> >
<Icon name="up-button" class={styles.icon} /> <Icon name="up-button" class={styles.icon} />
<span class={styles.scrollTopButtonLabel}>{t('Scroll up')}</span> <span class={styles.scrollTopButtonLabel}>{t('Scroll up')}</span>

View File

@ -17,6 +17,7 @@
border: none; border: none;
resize: none; resize: none;
overflow: hidden; overflow: hidden;
&::placeholder { &::placeholder {
color: #858585; color: #858585;
} }

View File

@ -8,6 +8,7 @@ import { useSnackbar } from './snackbar'
import { openPage } from '@nanostores/router' import { openPage } from '@nanostores/router'
import { router, useRouter } from '../stores/router' import { router, useRouter } from '../stores/router'
import { slugify } from '../utils/slugify' import { slugify } from '../utils/slugify'
import { Editor } from '@tiptap/core'
type WordCounter = { type WordCounter = {
characters: number characters: number
@ -30,6 +31,7 @@ type EditorContextType = {
wordCounter: Accessor<WordCounter> wordCounter: Accessor<WordCounter>
form: ShoutForm form: ShoutForm
formErrors: Record<keyof ShoutForm, string> formErrors: Record<keyof ShoutForm, string>
editorRef: { current: () => Editor }
actions: { actions: {
saveShout: () => Promise<void> saveShout: () => Promise<void>
publishShout: () => Promise<void> publishShout: () => Promise<void>
@ -39,6 +41,7 @@ type EditorContextType = {
countWords: (value: WordCounter) => void countWords: (value: WordCounter) => void
setForm: SetStoreFunction<ShoutForm> setForm: SetStoreFunction<ShoutForm>
setFormErrors: SetStoreFunction<Record<keyof ShoutForm, string>> setFormErrors: SetStoreFunction<Record<keyof ShoutForm, string>>
setEditor: (editor: () => Editor) => void
} }
} }
@ -67,6 +70,8 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false) const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
const editorRef: { current: () => Editor } = { current: null }
const [form, setForm] = createStore<ShoutForm>(null) const [form, setForm] = createStore<ShoutForm>(null)
const [formErrors, setFormErrors] = createStore<Record<keyof ShoutForm, string>>(null) const [formErrors, setFormErrors] = createStore<Record<keyof ShoutForm, string>>(null)
@ -198,6 +203,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
} }
} }
const setEditor = (editor: () => Editor) => {
editorRef.current = editor
}
const actions = { const actions = {
saveShout, saveShout,
publishShout, publishShout,
@ -206,10 +215,18 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
toggleEditorPanel, toggleEditorPanel,
countWords, countWords,
setForm, setForm,
setFormErrors setFormErrors,
setEditor
} }
const value: EditorContextType = { actions, form, formErrors, isEditorPanelVisible, wordCounter } const value: EditorContextType = {
actions,
form,
formErrors,
editorRef,
isEditorPanelVisible,
wordCounter
}
return <EditorContext.Provider value={value}>{props.children}</EditorContext.Provider> return <EditorContext.Provider value={value}>{props.children}</EditorContext.Provider>
} }

View File

@ -211,8 +211,8 @@ h5 {
padding-bottom: 0.2rem; padding-bottom: 0.2rem;
position: relative; position: relative;
&:before { &::before {
background: rgb(243, 244, 246); background: rgb(243 244 246);
content: ''; content: '';
height: 100%; height: 100%;
left: 0; left: 0;

View File

@ -2,6 +2,6 @@ import { translit } from './ru2en'
export const slugify = (text) => { export const slugify = (text) => {
return translit(text.toLowerCase()) return translit(text.toLowerCase())
.replaceAll(/[^\da-z]/g, '')
.replaceAll(' ', '-') .replaceAll(' ', '-')
.replaceAll(/[^\da-z]/g, '')
} }