From 9acfdafc2e15fbcf12bcb2615182fb1c91bcab49 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Wed, 13 Dec 2023 13:39:31 +0300 Subject: [PATCH 1/6] Feature/og meta tags (#329) Add SEO metatags --- package-lock.json | 32 +- package.json | 4 +- public/locales/en/translation.json | 39 +- public/locales/ru/translation.json | 37 +- public/robots.txt | 2 +- src/components/Article/FullArticle.tsx | 26 +- src/components/Discours/Footer.tsx | 4 +- .../Feed/ArticleCard/ArticleCard.tsx | 5 +- .../Feed/Sidebar/Sidebar.module.scss | 15 +- .../Nav/ConfirmModal/ConfirmModal.module.scss | 1 + .../ProfileSettings/ProfileSettings.tsx | 2 +- src/components/Topic/Card.tsx | 2 +- src/components/Views/AllAuthors.tsx | 205 ++++---- src/components/Views/AllTopics.tsx | 190 +++---- src/components/Views/Author/Author.tsx | 18 + src/components/Views/Feed.tsx | 20 + .../ProfileSubscriptions.tsx | 2 +- src/components/Views/StaticPage.tsx | 13 +- src/components/Views/Topic.tsx | 198 ++++---- .../_shared/SolidSwiper/ImageSwiper.tsx | 2 +- .../_shared/SolidSwiper/Swiper.module.scss | 1 + src/context/profile.tsx | 2 +- src/pages/about/discussionRules.page.tsx | 211 ++++---- src/pages/about/dogma.page.tsx | 109 ++-- src/pages/about/guide.page.tsx | 394 ++++++++------- src/pages/about/help.page.tsx | 220 ++++---- src/pages/about/manifest.page.tsx | 265 +++++----- src/pages/about/partners.page.tsx | 24 +- src/pages/about/principles.page.tsx | 325 ++++++------ src/pages/about/termsOfUse.page.tsx | 476 ++++++++---------- src/pages/about/thanks.page.tsx | 112 +++-- src/pages/allAuthors.page.tsx | 7 +- src/pages/allTopics.page.tsx | 9 +- src/pages/author.page.tsx | 1 + src/pages/create.page.tsx | 17 +- src/pages/topic.page.tsx | 25 +- src/utils/meta.ts | 20 +- 37 files changed, 1645 insertions(+), 1390 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1e5a7ef..7f88e173 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,7 @@ "prosemirror-view": "1.30.2", "rollup": "3.21.6", "sass": "1.69.5", - "solid-js": "1.8.5", + "solid-js": "1.8.7", "solid-popper": "0.3.0", "solid-tiptap": "0.6.0", "solid-transition-group": "0.2.3", @@ -120,7 +120,7 @@ "typescript": "5.2.2", "typograf": "7.1.0", "uniqolor": "1.1.0", - "vike": "0.4.146", + "vike": "0.4.148", "vite": "4.5.0", "vite-plugin-mkcert": "1.16.0", "vite-plugin-sass-dts": "1.3.11", @@ -1434,9 +1434,9 @@ "dev": true }, "node_modules/@brillout/picocolors": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@brillout/picocolors/-/picocolors-1.0.9.tgz", - "integrity": "sha512-Lt/W5JsA75hcDJ2cOAlE4TqSMl6c9K+rXGRo/cU2fApnmhbRcNdkR4UHQDAwtWfZyUKWaxdwSui+jp+74J1pZg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@brillout/picocolors/-/picocolors-1.0.10.tgz", + "integrity": "sha512-dh+JJlsBf3QYX+91Ezma8RLKNOjGDoBBmORv/NzRpQuasdyzwQCMXGGjsDu12ZhGz92TqQbL9pv79rvbheI21A==", "dev": true }, "node_modules/@brillout/require-shim": { @@ -16994,9 +16994,9 @@ } }, "node_modules/seroval": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.12.3.tgz", - "integrity": "sha512-5WDeMpv7rmEylsypRj1iwRVHE/QLsMLiZ+9savlNNQEVdgGia1iRMb7qyaAagY0wu/7+QTe6d2wldk/lgaLb6g==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.15.1.tgz", + "integrity": "sha512-OPVtf0qmeC7RW+ScVX+7aOS+xoIM7pWcZ0jOWg2aTZigCydgRB04adfteBRbecZnnrO1WuGQ+C3tLeBBzX2zSQ==", "dev": true, "engines": { "node": ">=10" @@ -17186,13 +17186,13 @@ } }, "node_modules/solid-js": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.5.tgz", - "integrity": "sha512-xvtJvzJzWbsn35oKFhW9kNwaxG1Z/YLMsDp4tLVcYZTMPzvzQ8vEZuyDQ6nt7xDArVgZJ7TUFrJUwrui/oq53A==", + "version": "1.8.7", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.7.tgz", + "integrity": "sha512-9dzrSVieh2zj3SnJ02II6xZkonR6c+j/91b7XZUNcC6xSaldlqjjGh98F1fk5cRJ8ZTkzqF5fPIWDxEOs6QZXA==", "dev": true, "dependencies": { "csstype": "^3.1.0", - "seroval": "^0.12.0" + "seroval": "^0.15.1" } }, "node_modules/solid-popper": { @@ -18596,14 +18596,14 @@ } }, "node_modules/vike": { - "version": "0.4.146", - "resolved": "https://registry.npmjs.org/vike/-/vike-0.4.146.tgz", - "integrity": "sha512-1vaktcDy/eitSzVaUppKJWu+6vfwxKC4kV6uzAqj3RIK+WgteH3vF6IMZ0jnjjzCpeDRCIByN8PF4c0CtzRfHA==", + "version": "0.4.148", + "resolved": "https://registry.npmjs.org/vike/-/vike-0.4.148.tgz", + "integrity": "sha512-2KkrY6zB+fTVwQzzcr5zAxpIT2buezDcUP5u4oFzDdxhqcV7r5CGq2PWQ3/ALA8jP/Agz0ZdiMbXUNGJFI+uVw==", "dev": true, "dependencies": { "@brillout/import": "0.2.3", "@brillout/json-serializer": "^0.5.8", - "@brillout/picocolors": "^1.0.9", + "@brillout/picocolors": "^1.0.10", "@brillout/require-shim": "^0.1.2", "@brillout/vite-plugin-import-build": "^0.2.20", "acorn": "^8.8.2", diff --git a/package.json b/package.json index 3ce3e90e..52328787 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "prosemirror-view": "1.30.2", "rollup": "3.21.6", "sass": "1.69.5", - "solid-js": "1.8.5", + "solid-js": "1.8.7", "solid-popper": "0.3.0", "solid-tiptap": "0.6.0", "solid-transition-group": "0.2.3", @@ -141,7 +141,7 @@ "typescript": "5.2.2", "typograf": "7.1.0", "uniqolor": "1.1.0", - "vike": "0.4.146", + "vike": "0.4.148", "vite": "4.5.0", "vite-plugin-mkcert": "1.16.0", "vite-plugin-sass-dts": "1.3.11", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index d93d00b4..ee37c254 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,4 +1,5 @@ { + "A guide to horizontal editorial: how an open journal works": "A guide to horizontal editorial: how an open journal works", "About": "About", "About the project": "About the project", "Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title": "Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title", @@ -17,7 +18,7 @@ "Add signature": "Add signature", "Add subtitle": "Add subtitle", "Add url": "Add url", - "Address on Discourse": "Address on Discourse", + "Address on Discours": "Address on Discours", "Album name": "Название aльбома", "Alignment center": "Alignment center", "Alignment left": "Alignment left", @@ -71,8 +72,11 @@ "Comment successfully deleted": "Comment successfully deleted", "Comments": "Comments", "Communities": "Communities", + "Community Discussion Rules": "Community Discussion Rules", "Community Principles": "Community Principles", + "Community values and rules of engagement for the open editorial team": "Community values and rules of engagement for the open editorial team", "Confirm": "Confirm", + "Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom": "Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom", "Cooperate": "Cooperate", "Copy": "Copy", "Copy link": "Copy link", @@ -97,11 +101,13 @@ "Delete userpic": "Delete userpic", "Description": "Description", "Discours": "Discours", + "Discours Manifest": "Discours Manifest", + "Discours Partners": "Discours Partners", "Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects.
We are convinced that one voice is good, but many is better. We create the most amazing stories together", "Discours is created with our common effort": "Discours exists because of our common effort", + "Discours – an open magazine about culture, science and society": "Discours – an open magazine about culture, science and society", "Discussing": "Discussing", "Discussion rules": "Discussion rules", - "Discussion rules in social networks": "Discussion rules", "Discussions": "Discussions", "Do you really want to reset all changes?": "Do you really want to reset all changes?", "Dogma": "Dogma", @@ -152,7 +158,7 @@ "Help": "Помощь", "Help to edit": "Help to edit", "Here you can customize your profile the way you want.": "Here you can customize your profile the way you want.", - "Here you can manage all your Discourse subscriptions": "Here you can manage all your Discourse subscriptions", + "Here you can manage all your Discours subscriptions": "Here you can manage all your Discours subscriptions", "Here you can upload your photo": "Here you can upload your photo", "Hide table of contents": "Hide table of contents", "Highlight": "Highlight", @@ -160,11 +166,13 @@ "Horizontal collaborative journalistic platform": "Horizontal collaborative journalism platform", "Hot topics": "Hot topics", "Hotkeys": "Горячие клавиши", + "How Discours works": "How Discours works", "How can I help/skills": "How can I help/skills", "How it works": "How it works", "How to help": "How to help?", "How to write a good article": "Как написать хорошую статью", "How to write an article": "How to write an article", + "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!": "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!", "I have an account": "I have an account!", "I have no account yet": "I don't have an account yet", "I know the password": "I know the password", @@ -173,6 +181,7 @@ "Inbox": "Inbox", "Incut": "Incut", "Independant magazine with an open horizontal cooperation about culture, science and society": "Independant magazine with an open horizontal cooperation about culture, science and society", + "Independent media project about culture, science, art and society with horizontal editing": "Independent media project about culture, science, art and society with horizontal editing", "Insert footnote": "Insert footnote", "Insert video link": "Insert video link", "Interview": "Interview", @@ -195,13 +204,14 @@ "Let's log in": "Let's log in", "Link copied": "Link copied", "Link sent, check your email": "Link sent, check your email", + "List of authors of the open editorial community": "List of authors of the open editorial community", "Lists": "Lists", "Literature": "Literature", "Load more": "Show more", "Loading": "Loading", "Logout": "Logout", "Looks like you forgot to upload the video": "Looks like you forgot to upload the video", - "Manifest": "Manifest", + "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board": "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board", "Manifesto": "Manifesto", "Many files, choose only one": "Many files, choose only one", "Mark as read": "Mark as read", @@ -236,6 +246,7 @@ "Ordered list": "Ordered list", "Our regular contributor": "Our regular contributor", "Paragraphs": "Абзацев", + "Participate in the Discours: share information, join the editorial team": "Участвуйте в Дискурсе: делитесь информацией, присоединяйтесь к редакции", "Participating": "Participating", "Participation": "Participation", "Partners": "Partners", @@ -261,6 +272,7 @@ "Popular": "Popular", "Popular authors": "Popular authors", "Principles": "Community principles", + "Professional principles that the open editorial team follows in its work": "Professional principles that the open editorial team follows in its work", "Profile": "Profile", "Profile settings": "Profile settings", "Publications": "Publications", @@ -281,6 +293,7 @@ "Required": "Required", "Resend code": "Send confirmation", "Restore password": "Restore password", + "Rules of the journal Discours": "Rules of the journal Discours", "Save draft": "Save draft", "Save settings": "Save settings", "Saving...": "Saving...", @@ -291,6 +304,7 @@ "Sections": "Sections", "Security": "Security", "Select": "Select", + "Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!", "Send": "Send", "Send link again": "Send link again", "Settings": "Settings", @@ -325,12 +339,17 @@ "Success": "Success", "Successfully authorized": "Authorization successful", "Suggest an idea": "Suggest an idea", + "Support Discours": "Support Discours", "Support the project": "Support the project", - "Support us": "Help the magazine", "Terms of use": "Site rules", "Text checking": "Text checking", "Thank you": "Thank you", + "Thank you!": "Thank you!", "The address is already taken": "The address is already taken", + "The most interesting publications on the topic": "The most interesting publications on the topic {topicName}", + "Thematic table of contents of the magazine. Here you can find all the topics that community authors have written about.": "Thematic table of contents of the magazine. Here you can find all the topics that community authors have written about.", + "Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about": "Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about", + "Themes and plots": "Themes and plots", "Theory": "Theory", "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?", "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?", @@ -379,8 +398,9 @@ "Welcome to Discours to subscribe to new publications": "Welcome to Discours to subscribe to new publications", "Welcome to Discours to vote": "Welcome to Discours to vote", "Where": "From", + "Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities": "Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities", "Words": "Слов", - "Work with us": "Cooperate with Discourse", + "Work with us": "Cooperate with Discours", "Write a comment...": "Write a comment...", "Write a short introduction": "Write a short introduction", "Write about the topic": "Write about the topic", @@ -411,7 +431,8 @@ "community": "community", "contents": "contents", "delimiter": "delimiter", - "discussion": "discourse", + "discussion": "Discours", + "dogma keywords": "Discours.io, dogma, editorial principles, code of ethics, journalism, community", "drafts": "drafts", "earlier": "earlier", "email not confirmed": "email not confirmed", @@ -427,6 +448,7 @@ "italic": "italic", "journal": "journal", "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", + "keywords": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography", "literature": "literature", "marker list": "marker list", "min. 1400×1400 pix": "мин. 1400×1400 пикс.", @@ -436,6 +458,7 @@ "or sign in with social networks": "or sign in with social networks", "personal data usage and email notifications": "to process personal data and receive email notifications", "post": "post", + "principles keywords": "Discours.io, communities, values, editorial rules, polyphony, creation", "register": "register", "repeat": "repeat", "shout": "post", @@ -450,7 +473,9 @@ "subscription_rp": "subscription", "subscriptions": "subscriptions", "terms of use": "terms of use", + "terms of use keywords": "Discours.io, site rules, terms of use", "today": "today", + "topicKeywords": "{topic}, Discours.io, articles, journalism, research", "topics": "topics", "user already exist": "user already exists", "video": "video", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index edecc10d..560ff2b6 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -1,4 +1,5 @@ { + "A guide to horizontal editorial: how an open journal works": "Гид по горизонтальной редакции: как работает открытый журнал", "A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя", "About": "О себе", "About the project": "О проекте", @@ -19,7 +20,7 @@ "Add subtitle": "Добавить подзаголовок", "Add to bookmarks": "Добавить в закладки", "Add url": "Добавить ссылку", - "Address on Discourse": "Адрес на Дискурсе", + "Address on Discours": "Адрес на Дискурсе", "Album name": "Название альбома", "Alignment center": "По центру", "Alignment left": "По левому краю", @@ -75,8 +76,11 @@ "Comment successfully deleted": "Комментарий успешно удален", "Comments": "Комментарии", "Communities": "Сообщества", + "Community Discussion Rules": "Правила дискуссий в сообществе", "Community Principles": "Принципы сообщества", + "Community values and rules of engagement for the open editorial team": "Ценности сообщества и правила взаимодействия открытой редакции", "Confirm": "Подтвердить", + "Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom": "Внесите вклад в свободный самиздат. Поддержите Дискурс — независимое некоммерческое издание, которое работает только для вас. Станьте опорой открытой редакции", "Cooperate": "Соучаствовать", "Copy": "Скопировать", "Copy link": "Скопировать ссылку", @@ -101,11 +105,14 @@ "Delete userpic": "Удалить аватар", "Description": "Описание", "Discours": "Дискурс", + "Discours Manifest": "Манифест Дискурса", + "Discours Partners": "Партнеры Дискурса", "Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов.
Мы убеждены, один голос хорошо, а много — лучше. Самые потрясающиe истории мы создаём вместе.", "Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу", + "Discours – an open magazine about culture, science and society": "Дискурс – открытый журнал о культуре, науке и обществе", + "Discours_theme": "Тема дискурса", "Discussing": "Обсуждаемое", "Discussion rules": "Правила дискуссий", - "Discussion rules in social networks": "Правила сообществ самиздата в соцсетях", "Discussions": "Дискуссии", "Do you really want to reset all changes?": "Вы действительно хотите сбросить все изменения?", "Dogma": "Догма", @@ -160,7 +167,7 @@ "Help": "Помощь", "Help to edit": "Помочь редактировать", "Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.", - "Here you can manage all your Discourse subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе", + "Here you can manage all your Discours subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе", "Here you can upload your photo": "Здесь вы можете загрузить свою фотографию", "Hide table of contents": "Скрыть главление", "Highlight": "Подсветка", @@ -168,11 +175,13 @@ "Horizontal collaborative journalistic platform": "Открытая платформа
для независимой журналистики", "Hot topics": "Горячие темы", "Hotkeys": "Горячие клавиши", + "How Discours works": "Как устроен Дискурс", "How can I help/skills": "Чем могу помочь/навыки", "How it works": "Как это работает", "How to help": "Как помочь?", "How to write a good article": "Как написать хорошую статью", "How to write an article": "Как написать статью", + "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!": "Сотни людей из разных стран и городов делятся своими знаниями и искусством на Дискурсе. Присоединяйтесь!", "I have an account": "У меня есть аккаунт!", "I have no account yet": "У меня еще нет аккаунта", "I know the password": "Я знаю пароль!", @@ -181,6 +190,7 @@ "Inbox": "Входящие", "Incut": "Подверстка", "Independant magazine with an open horizontal cooperation about culture, science and society": "Независимый журнал с открытой горизонтальной редакцией о культуре, науке и обществе", + "Independent media project about culture, science, art and society with horizontal editing": "Независимый медиапроект о культуре, науке, искусстве и обществе с горизонтальной редакцией", "Insert footnote": "Вставить сноску", "Insert video link": "Вставить ссылку на видео", "Interview": "Интервью", @@ -205,13 +215,14 @@ "Let's log in": "Давайте авторизуемся", "Link copied": "Ссылка скопирована", "Link sent, check your email": "Ссылка отправлена, проверьте почту", + "List of authors of the open editorial community": "Список авторов сообщества открытой редакции", "Lists": "Списки", "Literature": "Литература", "Load more": "Показать ещё", "Loading": "Загрузка", "Logout": "Выход", "Looks like you forgot to upload the video": "Похоже, что вы забыли загрузить видео", - "Manifest": "Манифест", + "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board": "Манифест самиздата: принципы и миссия открытого журнала с горизонтальной редакцией", "Manifesto": "Манифест", "Many files, choose only one": "Много файлов, выберете один", "Mark as read": "Отметить прочитанным", @@ -247,6 +258,7 @@ "Ordered list": "Нумерованный список", "Our regular contributor": "Наш постоянный автор", "Paragraphs": "Абзацев", + "Participate in the Discours: share information, join the editorial team": "Participate in the Discours: share information, join the editorial team", "Participating": "Участвовать", "Participation": "Соучастие", "Partners": "Партнёры", @@ -273,6 +285,7 @@ "Popular authors": "Популярные авторы", "Preview": "Предпросмотр", "Principles": "Принципы сообщества", + "Professional principles that the open editorial team follows in its work": "Профессиональные принципы, которым открытая редакция следует в работе", "Profile": "Профиль", "Profile settings": "Настройки профиля", "Profile successfully saved": "Профиль успешно сохранён", @@ -297,6 +310,7 @@ "Required": "Поле обязательно для заполнения", "Resend code": "Выслать подтверждение", "Restore password": "Восстановить пароль", + "Rules of the journal Discours": "Правила журнала Дискурс", "Save": "Сохранить", "Save draft": "Сохранить черновик", "Save settings": "Сохранить настройки", @@ -308,6 +322,7 @@ "Sections": "Разделы", "Security": "Безопасность", "Select": "Выбрать", + "Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Самиздат существуют благодаря помощи замечательных людей со всего мира. Спасибо Вам!", "Send": "Отправить", "Send link again": "Прислать ссылку ещё раз", "Settings": "Настройки", @@ -344,12 +359,17 @@ "Success": "Успешно", "Successfully authorized": "Авторизация успешна", "Suggest an idea": "Предложить идею", + "Support Discours": "Поддержите Дискурс", "Support the project": "Поддержать проект", - "Support us": "Помочь журналу", "Terms of use": "Правила сайта", "Text checking": "Проверка текста", "Thank you": "Благодарности", + "Thank you!": "Спасибо Вам!", "The address is already taken": "Адрес уже занят", + "The most interesting publications on the topic": "Самые интересные публикации по теме {topicName}", + "Thematic table of contents of the magazine. Here you can find all the topics that community authors have written about.": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества.", + "Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества", + "Themes and plots": "Темы и сюжеты", "Theory": "Теории", "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "В настройках публикации есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?", @@ -399,6 +419,7 @@ "Welcome to Discours to vote": "Войдите в Дискурс, чтобы голосовать", "Welcome!": "Добро пожаловать!", "Where": "Откуда", + "Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities": "За что можно заслужить дырку в карме и как получить лучи благодарности за вклад в дискуссии в сообществах самиздата", "Words": "Слов", "Work with us": "Сотрудничать с Дискурсом", "Write a comment...": "Написать комментарий...", @@ -434,8 +455,8 @@ "create_chat": "Создать чат", "create_group": "Создать группу", "delimiter": "разделитель", - "discourse_theme": "Тема дискурса", "discussion": "дискурс", + "dogma keywords": "Discours.io, догма, редакционные принципы, этический кодекс, журналистика, сообщество", "drafts": "черновики", "earlier": "ранее", "email not confirmed": "email не подтвержден", @@ -451,6 +472,7 @@ "italic": "курсив", "journal": "журнал", "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", + "keywords": "Discours.io, журнал Дискурс, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотографии", "literature": "литература", "marker list": "маркир. список", "min. 1400×1400 pix": "мин. 1400×1400 пикс.", @@ -461,6 +483,7 @@ "or sign in with social networks": "или войдите через соцсеть", "personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений", "post": "пост", + "principles keywords": "Discours.io, сообщества, ценности, правила редакции, многоголосие, созидание", "register": "зарегистрируйтесь", "repeat": "повторить", "shout": "пост", @@ -475,7 +498,9 @@ "subscribers": "подписчиков", "subscribing...": "Подписка...", "terms of use": "правилами пользования сайтом", + "terms of use keywords": "Discours.io, правила сайта, terms of use", "today": "сегодня", + "topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования", "topics": "темы", "user already exist": "пользователь уже существует", "video": "видео", diff --git a/public/robots.txt b/public/robots.txt index 1f53798b..c2a49f4f 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,2 +1,2 @@ User-agent: * -Disallow: / +Allow: / diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index c3d2a161..4c628601 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -2,7 +2,7 @@ import type { Author, Shout } from '../../graphql/types.gen' import { getPagePath } from '@nanostores/router' import { createPopper } from '@popperjs/core' -import { Link } from '@solidjs/meta' +import { Link, Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js' import { isServer } from 'solid-js/web' @@ -13,7 +13,7 @@ import { useSession } from '../../context/session' import { MediaItem } from '../../pages/types' import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router' import { getImageUrl } from '../../utils/getImageUrl' -import { getDescription } from '../../utils/meta' +import { getDescription, getKeywords } from '../../utils/meta' import { Icon } from '../_shared/Icon' import { Image } from '../_shared/Image' import { Lightbox } from '../_shared/Lightbox' @@ -283,8 +283,26 @@ export const FullArticle = (props: Props) => { } } + const ogImage = props.article.cover + ? getImageUrl(props.article.cover, { width: 1200 }) + : getImageUrl('production/image/logo_image.png') + const description = getDescription(props.article.description || body()) + const ogTitle = props.article.title + const keywords = getKeywords(props.article) + return ( <> + + + + + + + + + + + {(imageUrl) => }
@@ -441,7 +459,7 @@ export const FullArticle = (props: Props) => {
{ isOwner={canEdit()} containerCssClass={clsx(stylesHeader.control, styles.articlePopupOpener)} title={props.article.title} - description={getDescription(props.article.body)} + description={description} imageUrl={props.article.cover} shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })} trigger={ diff --git a/src/components/Discours/Footer.tsx b/src/components/Discours/Footer.tsx index 3cc5d508..dff22121 100644 --- a/src/components/Discours/Footer.tsx +++ b/src/components/Discours/Footer.tsx @@ -17,7 +17,7 @@ export const Footer = () => { header: 'About the project', items: [ { - title: 'Manifest', + title: 'Discours Manifest', slug: '/about/manifest', }, { @@ -51,7 +51,7 @@ export const Footer = () => { slug: '/create', }, { - title: 'Support us', + title: 'Support Discours', slug: '/about/help', }, { diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index eef04408..5bf55248 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -111,6 +111,7 @@ export const ArticleCard = (props: ArticleCardProps) => { const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false) const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true) + const description = getDescription(props.article.body) return (
{ setIsActionPopupActive(value)} @@ -347,7 +348,7 @@ export const ArticleCard = (props: ArticleCardProps) => { isOwner={canEdit()} containerCssClass={stylesHeader.control} title={title} - description={getDescription(props.article.body)} + description={description} imageUrl={props.article.cover} shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })} isVisible={(value) => setIsActionPopupActive(value)} diff --git a/src/components/Feed/Sidebar/Sidebar.module.scss b/src/components/Feed/Sidebar/Sidebar.module.scss index 5cfd6330..e6675249 100644 --- a/src/components/Feed/Sidebar/Sidebar.module.scss +++ b/src/components/Feed/Sidebar/Sidebar.module.scss @@ -29,12 +29,6 @@ white-space: nowrap; } - a:hover { - .sidebarItemName { - background: #000; - } - } - .userpic { margin-right: 1.2rem; } @@ -104,12 +98,13 @@ } &:hover { - img { - filter: invert(1); + .sidebarItemName, + .counter { + background: var(--background-color-invert); } - .counter { - background: #000; + img { + filter: invert(1); } } } diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss index 828d712d..a0f030b8 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss +++ b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss @@ -4,6 +4,7 @@ .confirmModalTitle { @include font-size(3.2rem); + color: var(--default-color); font-weight: 700; margin: 0 3rem; diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx index c0910595..1cc18e4d 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/ProfileSettings/ProfileSettings.tsx @@ -256,7 +256,7 @@ export const ProfileSettings = () => {
-

{t('Address on Discourse')}

+

{t('Address on Discours')}

diff --git a/src/components/Topic/Card.tsx b/src/components/Topic/Card.tsx index 584aaed5..58b19c50 100644 --- a/src/components/Topic/Card.tsx +++ b/src/components/Topic/Card.tsx @@ -103,7 +103,7 @@ export const TopicCard = (props: TopicProps) => { >

- {capitalize(props.topic.title || '')} + {capitalize(props.topic.title || '', true)}

diff --git a/src/components/Views/AllAuthors.tsx b/src/components/Views/AllAuthors.tsx index 6d01a23f..b3d40f15 100644 --- a/src/components/Views/AllAuthors.tsx +++ b/src/components/Views/AllAuthors.tsx @@ -1,5 +1,6 @@ import type { Author } from '../../graphql/types.gen' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { createEffect, createMemo, createSignal, For, Show } from 'solid-js' @@ -7,7 +8,9 @@ import { useLocalize } from '../../context/localize' import { useRouter } from '../../stores/router' import { setAuthorsSort, useAuthorsStore } from '../../stores/zine/authors' import { dummyFilter } from '../../utils/dummyFilter' +import { getImageUrl } from '../../utils/getImageUrl' import { scrollHandler } from '../../utils/scroll' +import { Loading } from '../_shared/Loading' import { SearchField } from '../_shared/SearchField' import { AuthorBadge } from '../Author/AuthorBadge' @@ -17,14 +20,15 @@ type AllAuthorsPageSearchParams = { by: '' | 'name' | 'shouts' | 'followers' } -type AllAuthorsViewProps = { +type Props = { authors: Author[] + isLoaded: boolean } const PAGE_SIZE = 20 const ALPHABET = [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@'] -export const AllAuthorsView = (props: AllAuthorsViewProps) => { +export const AllAuthorsView = (props: Props) => { const { t, lang } = useLocalize() const [limit, setLimit] = createSignal(PAGE_SIZE) const { searchParams, changeSearchParam } = useRouter() @@ -82,113 +86,126 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => { }) const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE) - const AllAuthorsHead = () => ( -
-
-

{t('Authors')}

-

{t('Subscribe who you like to tune your personal feed')}

- -
-
- ) + + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Authors') + const description = t('List of authors of the open editorial community') return (
- 0}> + + + + + + + + + + + }>
- - - -
- +
+
+

{t('Authors')}

+

{t('Subscribe who you like to tune your personal feed')}

+
+
- - {(letter) => ( -
-

{letter}

-
-
-
-
- - {(author) => ( -
-
- {author.name} - {author.stat.shouts} + 0}> + + + + + {(letter) => ( +
+

{letter}

+
+
+
+
+ + {(author) => ( +
+
+ {author.name} + {author.stat.shouts} +
-
- )} - + )} + +
-
- )} -
- + )} + + - - - {(author) => ( -
-
- + + + {(author) => ( +
+
+ +
-
- )} - - + )} + + - limit() && searchParams().by !== 'name'}> -
-
- + limit() && searchParams().by !== 'name'}> +
+
+ +
-
+
diff --git a/src/components/Views/AllTopics.tsx b/src/components/Views/AllTopics.tsx index c440f4f5..6fad3908 100644 --- a/src/components/Views/AllTopics.tsx +++ b/src/components/Views/AllTopics.tsx @@ -1,5 +1,6 @@ import type { Topic } from '../../graphql/types.gen' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { createEffect, createMemo, createSignal, For, Show } from 'solid-js' @@ -8,7 +9,9 @@ import { useSession } from '../../context/session' import { useRouter } from '../../stores/router' import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics' import { dummyFilter } from '../../utils/dummyFilter' +import { getImageUrl } from '../../utils/getImageUrl' import { scrollHandler } from '../../utils/scroll' +import { Loading } from '../_shared/Loading' import { SearchField } from '../_shared/SearchField' import { TopicCard } from '../Topic/Card' @@ -18,14 +21,15 @@ type AllTopicsPageSearchParams = { by: 'shouts' | 'authors' | 'title' | '' } -type AllTopicsViewProps = { +type Props = { topics: Topic[] + isLoaded: boolean } const PAGE_SIZE = 20 const ALPHABET = [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#'] -export const AllTopicsView = (props: AllTopicsViewProps) => { +export const AllTopicsView = (props: Props) => { const { t, lang } = useLocalize() const { searchParams, changeSearchParam } = useRouter() const [limit, setLimit] = createSignal(PAGE_SIZE) @@ -103,100 +107,118 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
) + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Themes and plots') + const description = t( + 'Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about', + ) + return (
-
-
- + + + + + + + + + + + }> +
+
+ - 0}> - - + 0}> + + - - {(letter) => ( -
-

{letter}

-
-
-
- - {(topic) => ( -
- {topic.title} - {topic.stat.shouts} -
- )} -
+ + {(letter) => ( +
+

{letter}

+
+
+
+ + {(topic) => ( +
+ {topic.title} + {topic.stat.shouts} +
+ )} +
+
+ )} +
+ + + +
+
+ + {(topic) => ( + <> + +
+ + {t('shoutsWithCount', { count: topic.stat.shouts })} + + + {t('authorsWithCount', { count: topic.stat.authors })} + + + {t('followersWithCount', { count: topic.stat.followers })} + +
+ + )} +
- )} - - - - -
-
- - {(topic) => ( - <> - -
- - {t('shoutsWithCount', { count: topic.stat.shouts })} - - - {t('authorsWithCount', { count: topic.stat.authors })} - - - {t('followersWithCount', { count: topic.stat.followers })} - -
- - )} -
-
-
+ - limit() && searchParams().by !== 'title'}> -
- -
+ limit() && searchParams().by !== 'title'}> +
+ +
+
- +
-
+
) } diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx index a0463ffc..80943eaf 100644 --- a/src/components/Views/Author/Author.tsx +++ b/src/components/Views/Author/Author.tsx @@ -1,6 +1,7 @@ import type { Author, Shout, Topic } from '../../../graphql/types.gen' import { getPagePath } from '@nanostores/router' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { Show, createMemo, createSignal, Switch, onMount, For, Match, createEffect } from 'solid-js' @@ -9,6 +10,8 @@ import { router, useRouter } from '../../../stores/router' import { loadShouts, useArticlesStore } from '../../../stores/zine/articles' import { useAuthorsStore } from '../../../stores/zine/authors' import { apiClient } from '../../../utils/apiClient' +import { getImageUrl } from '../../../utils/getImageUrl' +import { getDescription } from '../../../utils/meta' import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' import { splitToPages } from '../../../utils/splitToPages' import { Loading } from '../../_shared/Loading' @@ -127,8 +130,23 @@ export const AuthorView = (props: Props) => { } }) + const ogImage = props.author?.userpic + ? getImageUrl(props.author.userpic, { width: 1200 }) + : getImageUrl('production/image/logo_image.png') + const description = getDescription(props.author?.bio) + const ogTitle = props.author?.name + return (
+ + + + + + + + +
}> <> diff --git a/src/components/Views/Feed.tsx b/src/components/Views/Feed.tsx index f4672681..7a3869e9 100644 --- a/src/components/Views/Feed.tsx +++ b/src/components/Views/Feed.tsx @@ -1,6 +1,7 @@ import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../graphql/types.gen' import { getPagePath } from '@nanostores/router' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { createEffect, createSignal, For, on, onMount, Show } from 'solid-js' @@ -10,6 +11,9 @@ import { router, useRouter } from '../../stores/router' import { useArticlesStore, resetSortedArticles } from '../../stores/zine/articles' import { useTopAuthorsStore } from '../../stores/zine/topAuthors' import { useTopicsStore } from '../../stores/zine/topics' +import { capitalize } from '../../utils/capitalize' +import { getImageUrl } from '../../utils/getImageUrl' +import { getDescription } from '../../utils/meta' import { Icon } from '../_shared/Icon' import { Loading } from '../_shared/Loading' import { CommentDate } from '../Article/CommentDate' @@ -113,8 +117,24 @@ export const FeedView = (props: Props) => { setTopComments(comments) }) + const ogImage = getImageUrl('production/image/logo_image.png') + const description = t( + 'Independent media project about culture, science, art and society with horizontal editing', + ) + const ogTitle = t('Feed') + return (
+ + + + + + + + + +
diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx index f3d7ab51..7f3f48ca 100644 --- a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx +++ b/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx @@ -72,7 +72,7 @@ export const ProfileSubscriptions = () => {

{t('My subscriptions')}

-

{t('Here you can manage all your Discourse subscriptions')}

+

{t('Here you can manage all your Discours subscriptions')}

}>
  • diff --git a/src/components/Views/StaticPage.tsx b/src/components/Views/StaticPage.tsx index 11bd6d72..c8e1f2f3 100644 --- a/src/components/Views/StaticPage.tsx +++ b/src/components/Views/StaticPage.tsx @@ -3,22 +3,21 @@ import { JSX } from 'solid-js' import { PageLayout } from '../_shared/PageLayout' import { TableOfContents } from '../TableOfContents' -export const StaticPage = (props: { +type Props = { title: string children: JSX.Element - layoutChildren: JSX.Element -}) => { - let articleBodyElement: HTMLElement | undefined +} +export const StaticPage = (props: Props) => { + const articleBodyElement: { current: HTMLElement } = { current: null } return ( - {props.layoutChildren}
    (articleBodyElement.current = el)} > {props.children}
    @@ -27,7 +26,7 @@ export const StaticPage = (props: {
    diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 2a95835d..27717294 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -1,15 +1,20 @@ import type { Shout, Topic } from '../../graphql/types.gen' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' -import { For, Show, createMemo, onMount, createSignal } from 'solid-js' +import { For, Show, createMemo, onMount, createSignal, createEffect } from 'solid-js' import { useLocalize } from '../../context/localize' import { useRouter } from '../../stores/router' import { loadShouts, useArticlesStore } from '../../stores/zine/articles' import { useAuthorsStore } from '../../stores/zine/authors' import { useTopicsStore } from '../../stores/zine/topics' +import { capitalize } from '../../utils/capitalize' +import { getImageUrl } from '../../utils/getImageUrl' +import { getDescription } from '../../utils/meta' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { splitToPages } from '../../utils/splitToPages' +import { Loading } from '../_shared/Loading' import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper' import { Beside } from '../Feed/Beside' import { Row1 } from '../Feed/Row1' @@ -23,16 +28,18 @@ type TopicsPageSearchParams = { by: 'comments' | '' | 'recent' | 'viewed' | 'rating' | 'commented' } -interface TopicProps { +interface Props { topic: Topic shouts: Shout[] topicSlug: string + isLoaded: boolean + title: (val: string) => string } export const PRERENDERED_ARTICLES_COUNT = 28 const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3 -export const TopicView = (props: TopicProps) => { +export const TopicView = (props: Props) => { const { t } = useLocalize() const { searchParams, changeSearchParam } = useRouter() @@ -80,101 +87,124 @@ export const TopicView = (props: TopicProps) => { splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), ) + const pageTitle = `#${capitalize(topic().title, true)}` + createEffect(() => props.title(pageTitle)) + + const ogImage = topic().pic + ? getImageUrl(topic().pic, { width: 1200 }) + : getImageUrl('production/image/logo_image.png') + const description = topic().body + ? getDescription(topic().body) + : t('The most interesting publications on the topic', { topicName: pageTitle }) + const ogTitle = pageTitle + return (
    - - -
    -
    -
    -
      -
    • - -
    • - {/*TODO: server sort*/} - {/*
    • */} - {/* */} - {/*
    • */} - {/*
    • */} - {/* */} - {/*
    • */} - {/*
    • */} - {/* */} - {/*
    • */} -
    -
    -
    -
    - {`${t('Show')} `} - {t('All posts')} + +
  • + {/*TODO: server sort*/} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */} +
+
+
+
+ {`${t('Show')} `} + {t('All posts')} +
-
- - + + - + - + - + - - + + - 15}> - - - - + 15}> + + + + - - {(page) => ( - <> - - - - - )} - + + {(page) => ( + <> + + + + + )} + - -

- -

+ +

+ +

+
diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 888929c6..93864056 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx' -import { createEffect, createSignal, For, Show, on, onMount, lazy, onCleanup } from 'solid-js' +import { createEffect, createSignal, For, Show, on, onMount, onCleanup } from 'solid-js' import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' import { throttle } from 'throttle-debounce' diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index 6ae644b0..500efb7d 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -85,6 +85,7 @@ } } } + &.mobileView { .container { flex-direction: column-reverse; diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 12a3d08d..02abc246 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -3,7 +3,7 @@ import type { ProfileInput } from '../graphql/types.gen' import { createContext, createEffect, createMemo, JSX, useContext } from 'solid-js' import { createStore } from 'solid-js/store' -import { loadAuthor, useAuthorsStore } from '../stores/zine/authors' +import { loadAuthor } from '../stores/zine/authors' import { apiClient } from '../utils/apiClient' import { useSession } from './session' diff --git a/src/pages/about/discussionRules.page.tsx b/src/pages/about/discussionRules.page.tsx index 900399ac..8c04f674 100644 --- a/src/pages/about/discussionRules.page.tsx +++ b/src/pages/about/discussionRules.page.tsx @@ -1,121 +1,130 @@ -import { PageLayout } from '../../components/_shared/PageLayout' +import { Meta } from '@solidjs/meta' + +import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const DiscussionRulesPage = () => { const { t } = useLocalize() - const title = t('Discussion rules in social networks') + + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Community Discussion Rules') + const description = t( + 'Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities', + ) + return ( - +
-
-
-

- -

+ + + + + + + + + + +

{ogTitle}

+ +

+ Открытая редакция существует благодаря дружному сообществу авторов и читателей — + вдумчивых и сознательных людей, приверженных ценностям гуманизма, демократии и прав + человека. Мы очень ценим атмосферу осмысленного общения, которая здесь сложилась. Чтобы + сохранить ее такой же уютной и творческой, мы составили правила общения + в сообществе, руководствуясь которыми каждый мог бы соучаствовать в плодотворных + дискуссиях, не задевая других. Ключевой принцип этих правил предельно прост — + уважайте ближних, постарайтесь не нарушать законы Российской Федерации без крайней + на то необходимости и помните, что в дискуссиях чутких и здравомыслящих + людей рождается истина. +

+ +

За что можно получить дырку в карме и выиграть бан в сообществе

+
    +
  1. - Открытая редакция существует благодаря дружному сообществу авторов - и читателей — вдумчивых и сознательных людей, приверженных ценностям - гуманизма, демократии и прав человека. Мы очень ценим атмосферу осмысленного - общения, которая здесь сложилась. Чтобы сохранить ее такой же уютной - и творческой, мы составили правила общения в сообществе, руководствуясь - которыми каждый мог бы соучаствовать в плодотворных дискуссиях, не задевая - других. Ключевой принцип этих правил предельно прост — уважайте ближних, - постарайтесь не нарушать законы Российской Федерации без крайней - на то необходимости и помните, что в дискуссиях чутких - и здравомыслящих людей рождается истина. + Оскорбления, личные нападки, травля и угрозы. В любом виде. Конкретного человека или + социальной группы — не суть. Агрессия, переход на личности + и токсичность едва ли способствуют плодотворному общению.

    +
  2. -

    За что можно получить дырку в карме и выиграть бан в сообществе

    -
      -
    1. -

      - Оскорбления, личные нападки, травля и угрозы. В любом виде. Конкретного человека - или социальной группы — не суть. Агрессия, переход на личности - и токсичность едва ли способствуют плодотворному общению. -

      -
    2. +
    3. +

      + Шовинизм, расизм, сексизм, гомофобия, пропаганда ненависти, педофилии, суицида, + распространение детской порнографии и другого человеконенавистнического контента. +

      +
    4. -
    5. -

      - Шовинизм, расизм, сексизм, гомофобия, пропаганда ненависти, педофилии, суицида, - распространение детской порнографии и другого человеконенавистнического контента. -

      -
    6. +
    7. +

      + Спам, реклама, фейкньюз, ссылки на пропагандистские СМИ, вбросы дезинформации, специально + уводящий от темы флуд, провокации, разжигание конфликтов, намеренный срыв дискуссий. +

      +
    8. -
    9. -

      - Спам, реклама, фейкньюз, ссылки на пропагандистские СМИ, вбросы дезинформации, - специально уводящий от темы флуд, провокации, разжигание конфликтов, намеренный срыв - дискуссий. -

      -
    10. +
    11. +

      + Неаргументированная критика и комментарии вроде «отстой», «зачем + я это увидел/а», «не читал, но осуждаю», «либераху + порвало», «лол», «скатились», «первый нах» + и тому подобные. Односложные реплики не подразумевают возможность обогащающего + диалога, не продуктивны и никак не помогают авторам делать материалы лучше, + а читателям — разобраться. +

      +
    12. +
    -
  3. -

    - Неаргументированная критика и комментарии вроде «отстой», «зачем - я это увидел/а», «не читал, но осуждаю», «либераху - порвало», «лол», «скатились», «первый нах» - и тому подобные. Односложные реплики не подразумевают возможность обогащающего - диалога, не продуктивны и никак не помогают авторам делать материалы лучше, - а читателям — разобраться. -

    -
  4. -
+

За что можно получить лучи добра и благодарности в сообществе

+
    +
  1. +

    + Вежливость и конструктивность. Мы выступаем за конструктивный + диалог, аргументированные комментарии и доброжелательное отношение друг к другу. + Задавайте содержательные вопросы, пишите развернутые комментарии, подкрепляйте + их аргументами, чтобы диалог был полезен всем участникам, помогая глубже понять тему + и разобраться в вопросе. И, пожалуйста, уважайте собеседника, даже если он вам + лично не импонирует: только так получаются продуктивные дискуссии. +

    +
  2. -

    За что можно получить лучи добра и благодарности в сообществе

    -
      -
    1. -

      - Вежливость и конструктивность. Мы выступаем - за конструктивный диалог, аргументированные комментарии и доброжелательное - отношение друг к другу. Задавайте содержательные вопросы, пишите развернутые - комментарии, подкрепляйте их аргументами, чтобы диалог был полезен всем участникам, - помогая глубже понять тему и разобраться в вопросе. И, пожалуйста, уважайте - собеседника, даже если он вам лично не импонирует: только так получаются - продуктивные дискуссии. -

      -
    2. +
    3. +

      + Обмен знаниями и историями. Осмысленные высказывания по теме поста, + оригинальные рассуждения, рассказы о личном опыте и проектах, обмен профессиональной + экспертизой, наблюдения и реальные истории из жизни — чем больше + мы делимся друг с другом знаниями, тем интереснее и плодотворнее становится + наше общение. Помните, что каждый вдумчивый ответ повышает качество дискуссий + в сообществе и делает чтение самиздата ещё интереснее. +

      +
    4. -
    5. -

      - Обмен знаниями и историями. Осмысленные высказывания по теме - поста, оригинальные рассуждения, рассказы о личном опыте и проектах, обмен - профессиональной экспертизой, наблюдения и реальные истории - из жизни — чем больше мы делимся друг с другом знаниями, тем - интереснее и плодотворнее становится наше общение. Помните, что каждый вдумчивый - ответ повышает качество дискуссий в сообществе и делает чтение самиздата ещё - интереснее. -

      -
    6. +
    7. +

      + Чувство юмора и добродушие. Остроумие и дружелюбие не только + направляют дискуссии в продуктивное русло, но и улучшают настроение. + Не вредите негативом, которого в интернете и без нас хватает, + и не травите на корню классные инициативы — всё великое начинается + с малого. Мы за поддерживающую и вдохновляющую атмосферу + в сообществе. Надеемся, вы тоже. +

      +
    8. -
    9. -

      - Чувство юмора и добродушие. Остроумие и дружелюбие - не только направляют дискуссии в продуктивное русло, но и улучшают - настроение. Не вредите негативом, которого в интернете и без нас хватает, - и не травите на корню классные инициативы — всё великое - начинается с малого. Мы за поддерживающую и вдохновляющую атмосферу - в сообществе. Надеемся, вы тоже. -

      -
    10. - -
    11. -

      - Благодарность и поддержка. Если публикация вам зашла, - не стесняйтесь ставить лайки, делиться понравившимися материалами, благодарить - авторов, читателей, художников и редакторов в комментариях. Цените - и поддерживайте классные проекты, сильные тексты, новое искусство, осмысленные - комментарии и вклад других в самиздат — сотрудничество делает нас - сильнее и усиливает звучание идей и смыслов, которые помогают лучше понимать - мир. -

      -
    12. -
    -
-
+
  • +

    + Благодарность и поддержка. Если публикация вам зашла, + не стесняйтесь ставить лайки, делиться понравившимися материалами, благодарить авторов, + читателей, художников и редакторов в комментариях. Цените и поддерживайте + классные проекты, сильные тексты, новое искусство, осмысленные комментарии и вклад других + в самиздат — сотрудничество делает нас сильнее и усиливает звучание идей + и смыслов, которые помогают лучше понимать мир. +

    +
  • +
    -
    + ) } diff --git a/src/pages/about/dogma.page.tsx b/src/pages/about/dogma.page.tsx index 01b32d66..a36f1a3c 100644 --- a/src/pages/about/dogma.page.tsx +++ b/src/pages/about/dogma.page.tsx @@ -1,57 +1,70 @@ -import { PageLayout } from '../../components/_shared/PageLayout' -import { useLocalize } from '../../context/localize' +import { Meta } from '@solidjs/meta' + +import { StaticPage } from '../../components/Views/StaticPage' +import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' -// TODO: l10n export const DogmaPage = () => { const { t } = useLocalize() + + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Dogma') + const description = t('Professional principles that the open editorial team follows in its work') + return ( - + + + + + + + + + + +
    -
    -
    -

    Редакционные принципы

    -

    - Дискурс — журнал с открытой горизонтальной редакцией. Содержание журнала определяется прямым - голосованием его авторов. Мы нередко занимаем различные позиции по разным проблемам, но - придерживаемся общих профессиональных принципов: -

    -
      -
    1. - На первое место ставим факты. Наша задача — не судить, а наблюдать и непредвзято - фиксировать происходящее. Все утверждения и выводы, которые мы делаем, подтверждаются - фактами, цифрами, мнениями экспертов или ссылками на авторитетные источники. -
    2. -
    3. - Ответственно относимся к источникам. - Мы выбираем только надежные источники, проверяем информацию и рассказываем, как и откуда мы - её получили, кроме случаев, когда это может нанести вред источникам. Тогда мы не раскроем - их, даже в суде. -
    4. -
    5. - Выбираем компетентных и независимых экспертов, понимая всю степень ответственности - перед аудиторией. -
    6. -
    7. - - Даем возможность высказаться всем заинтересованным сторонам, но не присоединяемся ни к - чьему лагерю. - - Ко всем событиям, компаниям и людям мы относимся с одинаковым скептицизмом. -
    8. -
    9. - Всегда исправляем ошибки, если мы их допустили. - Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку — отправьте{' '} - ремарку автору или напишите нам на{' '} - - welcome@discours.io - - . -
    10. -
    -
    -
    +

    Редакционные принципы

    +

    + Дискурс — журнал с открытой горизонтальной редакцией. Содержание журнала определяется прямым + голосованием его авторов. Мы нередко занимаем различные позиции по разным проблемам, но + придерживаемся общих профессиональных принципов: +

    +
      +
    1. + На первое место ставим факты. Наша задача — не судить, а наблюдать и непредвзято + фиксировать происходящее. Все утверждения и выводы, которые мы делаем, подтверждаются фактами, + цифрами, мнениями экспертов или ссылками на авторитетные источники. +
    2. +
    3. + Ответственно относимся к источникам. + Мы выбираем только надежные источники, проверяем информацию и рассказываем, как и откуда мы её + получили, кроме случаев, когда это может нанести вред источникам. Тогда мы не раскроем их, даже + в суде. +
    4. +
    5. + Выбираем компетентных и независимых экспертов, понимая всю степень ответственности перед + аудиторией. +
    6. +
    7. + + Даем возможность высказаться всем заинтересованным сторонам, но не присоединяемся ни к чьему + лагерю. + + Ко всем событиям, компаниям и людям мы относимся с одинаковым скептицизмом. +
    8. +
    9. + Всегда исправляем ошибки, если мы их допустили. + Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку — отправьте{' '} + ремарку автору или напишите нам на{' '} + + welcome@discours.io + + . +
    10. +
    -
    + ) } diff --git a/src/pages/about/guide.page.tsx b/src/pages/about/guide.page.tsx index 328330ce..ab54cc4c 100644 --- a/src/pages/about/guide.page.tsx +++ b/src/pages/about/guide.page.tsx @@ -2,56 +2,60 @@ import { Meta } from '@solidjs/meta' import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const GuidePage = () => { const { t } = useLocalize() - const title = t('How it works') + + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('How Discours works') + const description = t('A guide to horizontal editorial: how an open journal works') return ( - - - - - - - - - - } - > -

    - Как устроен Дискурс -

    + + <> + + + + + + + + + + +
    +

    + {ogTitle} +

    -

    - Дискурс — независимый журнал о культуре, науке, искусстве и обществе с  - открытой редакцией. У нас нет главного редактора, инвестора - и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий Дискурс - основан на принципах прямой демократии: в нашем горизонтальном сообществе все редакционные - вопросы решаются открытым голосованием авторов журнала. Вот как это работает. -

    -

    Как устроен сайт Дискурса

    -

    Дискурс состоит из четырех основных разделов:

    -
      -
    • - Темы -  — у нас публикуются исследования, обзоры, эссе, интервью, репортажи, аналитика - и другие материалы о культуре, науке, искусстве и обществе. + Дискурс — независимый журнал о культуре, науке, искусстве и обществе + с  + открытой редакцией. У нас нет главного редактора, инвестора + и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий + Дискурс основан на принципах прямой демократии: в нашем горизонтальном сообществе все + редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает.

      -
    • -
    • -

      - Искусство -  — здесь, например, представлены художественные произведения: литература, живопись, - музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству, которое создают - российские художники, писатели, режиссёры и музыканты. -

      -
    • - {/* +

      Как устроен сайт Дискурса

      +

      Дискурс состоит из четырех основных разделов:

      +
        +
      • +

        + Темы +  — у нас публикуются исследования, обзоры, эссе, интервью, репортажи, + аналитика и другие материалы о культуре, науке, искусстве и обществе. +

        +
      • +
      • +

        + Искусство +  — здесь, например, представлены художественные произведения: литература, + живопись, музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству, + которое создают российские художники, писатели, режиссёры и музыканты. +

        +
      • + {/*
      • События — в этом разделе @@ -72,169 +76,173 @@ export const GuidePage = () => {

      • */} -
      -

      - Материалы в Дискурсе объединяются по темам - — ключевым словам, которые располагаются в конце материалов и связывают - материалы по жанрам (например, интервью,{' '} - репортажи, эссе,{' '} - ликбезы - ), по тематике (кино, философия,{' '} - история, абсурдизм,{' '} - секс и т.д.) или в серии (как « - Законы мира» или « - За линией Маннергейма - »). Темы объединяют сотни публикаций, помогают ориентироваться в журнале и следить - за интересными материалами. -

      - -
      -

      Как стать автором журнала

      -

      - Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров, - философов, ученых и других замечательных людей. Каждый может прислать{' '} - свой материал в журнал. Формат и тематика не имеют значения, единственное, что - важно — хороший ли материал. Если сообщество - поддержит вашу публикацию, она выйдет в журнале и станет доступна тысячам наших - читателей. -

      -
      - -

      Как проходит голосование

      -

      - Все присылаемые в Дискурс материалы попадают в  - «Редакцию». Это внутренний раздел сайта, где участники сообщества - решают, что будет опубликовано в Дискурсе. Как только работа получает одобрение как минимум - пятерых авторов открытой редакции, она немедленно публикуется в журнале. Если же материал - набирает более 20% голосов «против», он не выходит и может быть - отправлен на доработку. Жестких сроков рассмотрения материалов у нас нет, иногда это - занимает час, иногда месяц, обычно — несколько дней. -

      -
      -

      - Как только сообщество поддержит публикацию, вы получите приглашение в интернет-редакцию - и сможете голосовать за новые материалы. -

      -
      - -

      Как мы делаем тексты друг друга лучше

      -

      - Дискурс — журнал с совместным редактированием. Совершенствовать тексты нам помогает{' '} - система ремарок. Вы можете выделить часть текста в любой статье и оставить - к ней замечание, вопрос или предложение — автор текста получит совет на почту - и сможет его учесть. Так мы устраняем опечатки, неточности и советуем друг другу, как - сделать тексты качественнее и интереснее. -

      -

      - Среди участников сообщества есть профессиональные редакторы, которые помогают авторам делать тексты - лучше. Если вашему материалу потребуется доработка, они помогут отредактировать текст, подобрать - иллюстрации, придумать заголовок и красиво сверстать публикацию. Если вы хотите обсудить - текст, прежде чем загрузить материал в интернет-редакцию — разместите его - в google-документе, откройте доступ к редактированию по ссылке и напишите нам - на  - - welcome@discours.io - - . -

      -

      - Если у вас возникают трудности с тем, чтобы подобрать к своему материалу иллюстрации, - тоже пишите на  - - почту - - — наши коллеги-художники могут вам помочь{' '} - - в режиме совместного редактирования - - . -

      - -

      Что сообщество дает авторам

      -
        -
      • +

      - Право определять, каким будет журнал. Дискурс — это общественная - институция, созданная людьми и ради людей, функционирующая на условиях прямой - демократии. Авторы публикуют статьи и художественные проекты, участвуют в обсуждениях, - голосуют за работы коллег и таким образом вносят свой вклад в развитие проекта, - определяя содержание и направление журнала. + Материалы в Дискурсе объединяются по темам + — ключевым словам, которые располагаются в конце материалов и связывают + материалы по жанрам (например, интервью,{' '} + репортажи, эссе,{' '} + ликбезы + ), по тематике (кино, философия + , история, абсурдизм,{' '} + секс и т.д.) или в серии (как « + Законы мира» или « + За линией Маннергейма + »). Темы объединяют сотни публикаций, помогают ориентироваться в журнале + и следить за интересными материалами.

      - -
    • + +
      +

      Как стать автором журнала

      +

      + Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров, + философов, ученых и других замечательных людей. Каждый может{' '} + прислать свой материал в журнал. Формат и тематика + не имеют значения, единственное, что важно —{' '} + хороший ли материал. Если сообщество поддержит + вашу публикацию, она выйдет в журнале и станет доступна тысячам наших читателей. +

      +
      + +

      Как проходит голосование

      - Возможность обратиться к широкой аудитории. Дискурс читают десятки тысяч - людей, и с каждым днем их становится больше. + Все присылаемые в Дискурс материалы попадают в  + «Редакцию». Это внутренний раздел сайта, где участники сообщества + решают, что будет опубликовано в Дискурсе. Как только работа получает одобрение как минимум + пятерых авторов открытой редакции, она немедленно публикуется в журнале. Если же + материал набирает более 20% голосов «против», он не выходит + и может быть отправлен на доработку. Жестких сроков рассмотрения материалов у нас + нет, иногда это занимает час, иногда месяц, обычно — несколько дней.

      -
    • -
    • +
      +

      + Как только сообщество поддержит публикацию, вы получите приглашение + в интернет-редакцию и сможете голосовать за новые материалы. +

      +
      + +

      Как мы делаем тексты друг друга лучше

      - Поддержка редакции. Дискурс предоставляет авторам аккредитацию - на мероприятия, базу контактов, юридическую поддержку, ознакомление с книжными, кино- - и музыкальными новинками до их выхода в свет. Если что-то из этого вам - понадобится, пишите на почту{' '} + Дискурс — журнал с совместным редактированием. Совершенствовать тексты нам + помогает система ремарок. Вы можете выделить часть текста в любой статье + и оставить к ней замечание, вопрос или предложение — автор текста получит + совет на почту и сможет его учесть. Так мы устраняем опечатки, неточности + и советуем друг другу, как сделать тексты качественнее и интереснее. +

      +

      + Среди участников сообщества есть профессиональные редакторы, которые помогают авторам делать + тексты лучше. Если вашему материалу потребуется доработка, они помогут отредактировать текст, + подобрать иллюстрации, придумать заголовок и красиво сверстать публикацию. Если + вы хотите обсудить текст, прежде чем загрузить материал в интернет-редакцию — + разместите его в google-документе, откройте доступ к редактированию по ссылке + и напишите нам на  welcome@discours.io -  — поможем. + .

      -
    • -
    • - Пресс-карты для корреспондентов. Три опубликованные статьи позволяют авторам - Дискурса получить официальные удостоверения журналистов (пресс-карты) на следующий год. - Пресс-карты удостоверяют, что вы журналист и можете пользоваться всеми теми правами, - которые гарантирует Закон о СМИ. Кроме того, многие культурные институции (музеи, галереи - и др.) предоставляют журналистам право свободного входа. + Если у вас возникают трудности с тем, чтобы подобрать к своему материалу + иллюстрации, тоже пишите на  + + почту + + — наши коллеги-художники могут вам помочь{' '} + + в режиме совместного редактирования + + .

      -
    • -
    • -

      - Помощь сотен специалистов в разных областях. В основе Дискурса лежит - идея совместного редактирования. Участники редакционного сообщества — несколько сотен - журналистов, исследователей, художников, литераторов из разных стран — изучают - материалы друг друга до публикации и помогают сделать их качественнее - и интереснее. Так, в редакции нередко складываются творческие союзы: например, авторов - текстов и художников, создающих для них иллюстрации. -

      -
    • -
    • -

      - Пространство общения полное выдающихся людей. Дискурс — большое - живое сообщество интеллектуалов, разбросанных по всему земному шару. Вступив - в редакцию, вы сможете познакомиться со множеством интересных людей, которые - определяют повестку завтрашнего дня, вдохновляют окружающих, создают новое и изучают - старое, ищут знания и готовы ими делиться, чтобы менять мир в соответствии - со своими идеалами. -

      -
    • -
    -

    Как быть в курсе

    -

    - За свежими публикациями Дискурса можно следить не только на сайте, - но и на страницах в  - - Фейсбуке - - ,{' '} - - ВКонтакте - {' '} - и  - - Телеграме - - . А ещё раз в месяц мы отправляем почтовую рассылку{' '} - с дайджестом лучших материалов. -

    -

    - Если вы хотите сотрудничать, что-то обсудить или предложить — пожалуйста, пишите на  - - welcome@discours.io - - . Мы обязательно ответим. -

    +

    Что сообщество дает авторам

    +
      +
    • +

      + Право определять, каким будет журнал. Дискурс — это общественная + институция, созданная людьми и ради людей, функционирующая на условиях прямой + демократии. Авторы публикуют статьи и художественные проекты, участвуют + в обсуждениях, голосуют за работы коллег и таким образом вносят свой вклад + в развитие проекта, определяя содержание и направление журнала. +

      +
    • +
    • +

      + Возможность обратиться к широкой аудитории. Дискурс читают десятки + тысяч людей, и с каждым днем их становится больше. +

      +
    • +
    • +

      + Поддержка редакции. Дискурс предоставляет авторам аккредитацию + на мероприятия, базу контактов, юридическую поддержку, ознакомление с книжными, + кино- и музыкальными новинками до их выхода в свет. Если что-то + из этого вам понадобится, пишите на почту{' '} + + welcome@discours.io + +  — поможем. +

      +
    • +
    • +

      + Пресс-карты для корреспондентов. Три опубликованные статьи позволяют + авторам Дискурса получить официальные удостоверения журналистов (пресс-карты) + на следующий год. Пресс-карты удостоверяют, что вы журналист и можете + пользоваться всеми теми правами, которые гарантирует Закон о СМИ. Кроме того, многие + культурные институции (музеи, галереи и др.) предоставляют журналистам право свободного + входа. +

      +
    • +
    • +

      + Помощь сотен специалистов в разных областях. В основе Дискурса + лежит идея совместного редактирования. Участники редакционного сообщества — + несколько сотен журналистов, исследователей, художников, литераторов из разных стран + — изучают материалы друг друга до публикации и помогают сделать + их качественнее и интереснее. Так, в редакции нередко складываются творческие + союзы: например, авторов текстов и художников, создающих для них иллюстрации. +

      +
    • +
    • +

      + Пространство общения полное выдающихся людей. Дискурс — большое + живое сообщество интеллектуалов, разбросанных по всему земному шару. Вступив + в редакцию, вы сможете познакомиться со множеством интересных людей, которые + определяют повестку завтрашнего дня, вдохновляют окружающих, создают новое и изучают + старое, ищут знания и готовы ими делиться, чтобы менять мир в соответствии + со своими идеалами. +

      +
    • +
    + +

    Как быть в курсе

    +

    + За свежими публикациями Дискурса можно следить не только на сайте, + но и на страницах в  + + Фейсбуке + + ,{' '} + + ВКонтакте + {' '} + и  + + Телеграме + + . А ещё раз в месяц мы отправляем почтовую рассылку{' '} + с дайджестом лучших материалов. +

    +

    + Если вы хотите сотрудничать, что-то обсудить или предложить — пожалуйста, пишите + на  + + welcome@discours.io + + . Мы обязательно ответим. +

    +
    +
    ) } diff --git a/src/pages/about/help.page.tsx b/src/pages/about/help.page.tsx index f89401cf..5e734e18 100644 --- a/src/pages/about/help.page.tsx +++ b/src/pages/about/help.page.tsx @@ -3,124 +3,138 @@ import { Meta } from '@solidjs/meta' import { Donate } from '../../components/Discours/Donate' import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const HelpPage = () => { const { t } = useLocalize() - // TODO: l10n - return ( - - - - - } - > -

    - Как вы можете поддержать Дискурс? -

    + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Support Discours') + const description = t( + 'Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom', + ) + + return ( + + <> + + + + + + + + + + +
    +

    + Как вы можете поддержать Дискурс? +

    -

    - Дискурс — уникальное независимое издание с горизонтальной редакцией, существующее - в интересах своих читателей. Ваша поддержка действительно много значит — - не только для редакции Дискурса, но и для сохранения свободной мысли - и некоммерческого искусства в нашем обществе. -

    -

    - Дискурс существует на добровольных началах. Никакой медиахолдинг, фонд или государственная - структура не финансирует нас — благодаря этому мы можем писать о том, что - важно, а не о том, что выгодно. Сообщество наших волонтеров ежедневно трудится, чтобы - рассказывать вам интересные, не освещенные другими изданиями истории — - но мы не сможем делать это без вашей помощи. Пожертвования читателей составляют - основу нашего бюджета и позволяют нам существовать. -

    -

    - Если вам нравится то, что мы делаем и вы хотите, чтобы Дискурс продолжался, - пожалуйста, поддержите проект. -

    -
    -
    - -
    -
    -

    На что пойдут деньги?

    -

    - Ваши пожертвования пойдут на оплату серверов, содержание офиса, зарплату редакции - и налоги, оплату юридического сопровождения и труда бухгалтера, совершенствование сайта, - аренду помещения для открытой редакции, на печать альманаха Дискурс с лучшими текстами - авторов за полгода, а также на другие редакционные и технические расходы. -

    -

    Ваша помощь позволит нам

    -
      -
    • -

      Оставаться бесплатным изданием.

      - Мы делаем открытый журнал для всех желающих, а также собираем искусство лучших авторов - по всему миру. Ваша поддержка позволяет нам становиться лучше. + Дискурс — уникальное независимое издание с горизонтальной редакцией, + существующее в интересах своих читателей. Ваша поддержка действительно много + значит — не только для редакции Дискурса, но и для сохранения + свободной мысли и некоммерческого искусства в нашем обществе.

      -
    • -
    • -

      Создавать еще больше контента.

      - Каждый день к нам присоединяются новые люди, и чем больше нас становится, тем больше - мы творим и строже оцениваем результаты творчества друг друга. В результате - повышается и количество, и качество контента. Каждый день мы трудимся, чтобы открывать - нашим читателям новые грани окружающего мира. + Дискурс существует на добровольных началах. Никакой медиахолдинг, фонд или государственная + структура не финансирует нас — благодаря этому мы можем писать о том, + что важно, а не о том, что выгодно. Сообщество наших волонтеров ежедневно + трудится, чтобы рассказывать вам интересные, не освещенные другими изданиями + истории — но мы не сможем делать это без вашей помощи. Пожертвования + читателей составляют основу нашего бюджета и позволяют нам существовать.

      -
    • -
    • -

      Развивать форматы и расширять деятельность Дискурса.

      - Мы создаем различные спецпроекты и регулярно проводим необычные мероприятия. - Мы хотим приносить пользу человечеству всеми возможными способами. + Если вам нравится то, что мы делаем и вы хотите, чтобы Дискурс продолжался, + пожалуйста, поддержите проект.

      -
    • -
    • -

      Модернизировать сайт.

      +
      +
      + +
      +
      +

      На что пойдут деньги?

      - Мы совершенствуем платформу и стараемся сделать проект максимально удобным для вас. - Мы работаем над мобильной версией, новым дизайном, фукционалом, системой регистрации, - навигации и рекомендаций, которые сделают наше общение еще увлекательней. + Ваши пожертвования пойдут на оплату серверов, содержание офиса, зарплату редакции + и налоги, оплату юридического сопровождения и труда бухгалтера, совершенствование + сайта, аренду помещения для открытой редакции, на печать альманаха Дискурс с лучшими + текстами авторов за полгода, а также на другие редакционные и технические + расходы.

      -
    • -
    • -

      Выпускать альманах.

      +

      Ваша помощь позволит нам

      +
        +
      • +

        Оставаться бесплатным изданием.

        +

        + Мы делаем открытый журнал для всех желающих, а также собираем искусство лучших + авторов по всему миру. Ваша поддержка позволяет нам становиться лучше. +

        +
      • +
      • +

        Создавать еще больше контента.

        +

        + Каждый день к нам присоединяются новые люди, и чем больше нас становится, тем + больше мы творим и строже оцениваем результаты творчества друг друга. + В результате повышается и количество, и качество контента. Каждый день мы + трудимся, чтобы открывать нашим читателям новые грани окружающего мира. +

        +
      • +
      • +

        Развивать форматы и расширять деятельность Дискурса.

        +

        + Мы создаем различные спецпроекты и регулярно проводим необычные мероприятия. + Мы хотим приносить пользу человечеству всеми возможными способами. +

        +
      • +
      • +

        Модернизировать сайт.

        +

        + Мы совершенствуем платформу и стараемся сделать проект максимально удобным для + вас. Мы работаем над мобильной версией, новым дизайном, фукционалом, системой + регистрации, навигации и рекомендаций, которые сделают наше общение еще увлекательней. +

        +
      • +
      • +

        Выпускать альманах.

        +

        + Выпускать раз в полугодие печатный альманах Дискурс с 33 лучшими текстами + сайта. +

        +
      • +
      • +

        Захватить весь мир

        +

        и принести «Дискурс» в каждый дом.

        +
      • +
      +

      Войдите в попечительский совет Дискурса

      - Выпускать раз в полугодие печатный альманах Дискурс с 33 лучшими текстами сайта. + Вы хотите сделать крупное пожертвование? Станьте попечителем Дискурса —{' '} + + напишите нам + + , мы будем рады единомышленникам.

      -
    • -
    • -

      Захватить весь мир

      -

      и принести «Дискурс» в каждый дом.

      -
    • -
    -

    Войдите в попечительский совет Дискурса

    -

    - Вы хотите сделать крупное пожертвование? Станьте попечителем Дискурса —{' '} - - напишите нам - - , мы будем рады единомышленникам. -

    -

    Как ещё можно поддержать Дискурс?

    -

    - Есть много других способов поддержать Дискурс и труд наших авторов. Например, вы можете - периодически рассказывать о проекте своим друзьям в соцсетях, делиться хорошими - материалами или — что еще лучше — публиковать свои статьи - в «Дискурсе». Но главное, что вы можете сделать для Дискурса, — - читать нас. Мы вкладываем в журнал душу, и внимание каждого читателя убеждает нас - в правильности выбранного пути. Не переключайтесь. -

    -

    - Если вы хотите помочь проекту, но у вас возникли вопросы, напишите нам письмо - по адресу{' '} - - welcome@discours.io - - . -

    +

    Как ещё можно поддержать Дискурс?

    +

    + Есть много других способов поддержать Дискурс и труд наших авторов. Например, + вы можете периодически рассказывать о проекте своим друзьям в соцсетях, делиться + хорошими материалами или — что еще лучше — публиковать свои статьи + в «Дискурсе». Но главное, что вы можете сделать для Дискурса, — + читать нас. Мы вкладываем в журнал душу, и внимание каждого читателя убеждает нас + в правильности выбранного пути. Не переключайтесь. +

    +

    + Если вы хотите помочь проекту, но у вас возникли вопросы, напишите нам письмо + по адресу{' '} + + welcome@discours.io + + . +

    +
    +
    ) } diff --git a/src/pages/about/manifest.page.tsx b/src/pages/about/manifest.page.tsx index a3bcebaa..034a1fb5 100644 --- a/src/pages/about/manifest.page.tsx +++ b/src/pages/about/manifest.page.tsx @@ -1,145 +1,164 @@ +import { Meta } from '@solidjs/meta' + import { Subscribe } from '../../components/_shared/Subscribe' import { Feedback } from '../../components/Discours/Feedback' import { Modal } from '../../components/Nav/Modal' import Opener from '../../components/Nav/Modal/Opener' import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const ManifestPage = () => { const { t } = useLocalize() + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Discours Manifest') + const description = t( + 'Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board', + ) + return ( - - - - - - - - - } - > -

    - Манифест -

    + + <> + + + + + + + + + + + + + + + + +
    +

    + Манифест +

    -

    - Дискурс — независимый художественно-аналитический журнал с горизонтальной редакцией, - основанный на принципах свободы слова, прямой демократии и совместного редактирования. - Дискурс создаётся открытым медиасообществом ученых, журналистов, музыкантов, писателей, - предпринимателей, философов, инженеров, художников и специалистов со всего мира, - объединившихся, чтобы вместе делать общий журнал и объяснять с разных точек зрения - мозаичную картину современности. -

    -

    - Мы пишем о культуре, науке и обществе, рассказываем о новых идеях - и современном искусстве, публикуем статьи, исследования, репортажи, интервью людей, чью прямую - речь стоит услышать, и работы художников из разных стран — от фильмов - и музыки до живописи и фотографии. Помогая друг другу делать публикации качественнее - и общим голосованием выбирая лучшие материалы для журнала, мы создаём новую горизонтальную - журналистику, чтобы честно рассказывать о важном и интересном. -

    -

    - Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем и идеологических - рамок. Каждый может прислать материал в журнал и  - присоединиться к редакции. Предоставляя трибуну для независимой - журналистики и художественных проектов, мы помогаем людям рассказывать свои истории так, - чтобы они были услышаны. Мы убеждены: чем больше голосов будет звучать на Дискурсе, тем - громче в полифонии мнений будет слышна истина. -

    +

    + Дискурс — независимый художественно-аналитический журнал с горизонтальной + редакцией, основанный на принципах свободы слова, прямой демократии и совместного + редактирования. Дискурс создаётся открытым медиасообществом ученых, журналистов, музыкантов, + писателей, предпринимателей, философов, инженеров, художников и специалистов со всего + мира, объединившихся, чтобы вместе делать общий журнал и объяснять с разных точек + зрения мозаичную картину современности. +

    +

    + Мы пишем о культуре, науке и обществе, рассказываем о новых идеях + и современном искусстве, публикуем статьи, исследования, репортажи, интервью людей, чью + прямую речь стоит услышать, и работы художников из разных стран — + от фильмов и музыки до живописи и фотографии. Помогая друг другу делать + публикации качественнее и общим голосованием выбирая лучшие материалы для журнала, + мы создаём новую горизонтальную журналистику, чтобы честно рассказывать о важном + и интересном. +

    +

    + Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем и идеологических + рамок. Каждый может прислать материал в журнал и  + присоединиться к редакции. Предоставляя трибуну для независимой + журналистики и художественных проектов, мы помогаем людям рассказывать свои истории + так, чтобы они были услышаны. Мы убеждены: чем больше голосов будет звучать + на Дискурсе, тем громче в полифонии мнений будет слышна истина. +

    -

    - Как участвовать в самиздате -

    +

    + Как участвовать в самиздате +

    -

    - Дискурс создается открытым сообществом энтузиастов новой независимой - журналистики. Участвовать в открытой редакции и помогать журналу можно следующими - способами: -

    -
    - -

    Предлагать материалы

    -
    -

    - Создавайте свои статьи и художественные работы — лучшие из - них будут опубликованы в журнале. Дискурс — некоммерческое издание, авторы - публикуются в журнале на общественных началах, получая при этом{' '} - поддержку редакции, право голоса, множество других возможностей - и читателей по всему миру. -

    -
    +

    + Дискурс создается открытым сообществом энтузиастов новой независимой + журналистики. Участвовать в открытой редакции и помогать журналу можно следующими + способами: +

    +
    + +

    Предлагать материалы

    +
    +

    + Создавайте свои статьи и художественные работы — лучшие + из них будут опубликованы в журнале. Дискурс — некоммерческое издание, авторы + публикуются в журнале на общественных началах, получая при этом{' '} + поддержку редакции, право голоса, множество других + возможностей и читателей по всему миру. +

    +
    -
    - - - -

    - Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,{' '} - поддержите нашу работу. Ваши пожертвования пойдут на выпуск новых - материалов, оплату серверов, труда программистов, дизайнеров и редакторов. -

    -
    +
    + + + +

    + Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,{' '} + поддержите нашу работу. Ваши пожертвования пойдут на выпуск + новых материалов, оплату серверов, труда программистов, дизайнеров и редакторов. +

    +
    -
    - -

    Сотрудничать с журналом

    -
    -

    - Мы всегда открыты для сотрудничества и рады единомышленникам. Если вы хотите помогать журналу - с редактурой, корректурой, иллюстрациями, переводами, версткой, подкастами, мероприятиями, - фандрайзингом или как-то ещё — скорее пишите нам на  - welcome@discours.io. -

    -

    - Если вы представляете некоммерческую организацию и хотите сделать с нами совместный - проект, получить информационную поддержку или предложить другую форму сотрудничества —{' '} - пишите. -

    -

    - Если вы разработчик и хотите помогать с развитием сайта Дискурса,{' '} - присоединяйтесь к IT-команде самиздата. Открытый - код платформы для независимой журналистики, а также всех наших спецпроектов - и медиаинструментов находится{' '} - в свободном доступе на GitHub. -

    -
    +
    + +

    Сотрудничать с журналом

    +
    +

    + Мы всегда открыты для сотрудничества и рады единомышленникам. Если вы хотите помогать + журналу с редактурой, корректурой, иллюстрациями, переводами, версткой, подкастами, + мероприятиями, фандрайзингом или как-то ещё — скорее пишите нам на  + welcome@discours.io. +

    +

    + Если вы представляете некоммерческую организацию и хотите сделать с нами совместный + проект, получить информационную поддержку или предложить другую форму + сотрудничества — пишите. +

    +

    + Если вы разработчик и хотите помогать с развитием сайта Дискурса,{' '} + присоединяйтесь к IT-команде самиздата. + Открытый код платформы для независимой журналистики, а также всех наших спецпроектов + и медиаинструментов находится{' '} + в свободном доступе на GitHub. +

    +
    -
    - -

    Как еще можно помочь

    -
    -

    - Советуйте Дискурс друзьям и знакомым. Обсуждайте и распространяйте наши - публикации — все материалы открытой редакции можно читать и перепечатывать - бесплатно. Подпишитесь на самиздат ВКонтакте, в  - Фейсбуке и в  - Телеграме, а также на  - рассылку лучших материалов, чтобы не пропустить ничего - интересного. -

    -

    - Рассказывайте о впечатлениях{' '} - от материалов открытой редакции, делитесь идеями, - интересными темами, о которых хотели бы узнать больше, и историями, которые нужно - рассказать. -

    -
    +
    + +

    Как еще можно помочь

    +
    +

    + Советуйте Дискурс друзьям и знакомым. Обсуждайте и распространяйте наши + публикации — все материалы открытой редакции можно читать и перепечатывать + бесплатно. Подпишитесь на самиздат ВКонтакте, + в  + Фейсбуке и в  + Телеграме, а также на  + рассылку лучших материалов, чтобы не пропустить ничего + интересного. +

    +

    + Рассказывайте о впечатлениях{' '} + от материалов открытой редакции, делитесь идеями, + интересными темами, о которых хотели бы узнать больше, и историями, которые нужно + рассказать. +

    +
    -

    - Будем на связи -

    +

    + Будем на связи +

    -

    - Если вы хотите предложить материал, сотрудничать, рассказать о проблеме, которую нужно - осветить, сообщить об ошибке или баге, что-то обсудить, уточнить или посоветовать, пожалуйста,{' '} - напишите нам здесь или на почту{' '} - welcome@discours.io. Мы обязательно ответим - и постараемся реализовать все хорошие задумки. -

    +

    + Если вы хотите предложить материал, сотрудничать, рассказать о проблеме, которую нужно + осветить, сообщить об ошибке или баге, что-то обсудить, уточнить или посоветовать, + пожалуйста, напишите нам здесь или на почту{' '} + welcome@discours.io. Мы обязательно ответим + и постараемся реализовать все хорошие задумки. +

    +
    +
    ) } diff --git a/src/pages/about/partners.page.tsx b/src/pages/about/partners.page.tsx index c027768b..f8cf2ce5 100644 --- a/src/pages/about/partners.page.tsx +++ b/src/pages/about/partners.page.tsx @@ -1,18 +1,36 @@ -import { PageLayout } from '../../components/_shared/PageLayout' +import { Meta } from '@solidjs/meta' + +import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const PartnersPage = () => { const { t } = useLocalize() + + const ogTitle = t('Partners') + const ogImage = getImageUrl('production/image/logo_image.png') + const description = t('Discours Partners') + return ( - +
    + + + + + + + + + +

    {t('Partners')}

    -
    +
    ) } diff --git a/src/pages/about/principles.page.tsx b/src/pages/about/principles.page.tsx index b08af719..c704d457 100644 --- a/src/pages/about/principles.page.tsx +++ b/src/pages/about/principles.page.tsx @@ -1,179 +1,190 @@ -import { PageLayout } from '../../components/_shared/PageLayout' +import { Meta } from '@solidjs/meta' + +import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const PrinciplesPage = () => { const { t } = useLocalize() + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Community Principles') + const description = t('Community values and rules of engagement for the open editorial team') + return ( - +
    -
    -
    -

    - {t('Principles')} -

    - -
      -
    1. -

      - Горизонтальность. Мы все разные, и это классно. Вертикалей - в мире достаточно, мы — горизонтальное сообщество и ценим наши - различия, потому что знаем — в них наша сила. Благодаря разнообразию сотен - голосов, усиливающих друг друга, в сообществе складывается неповторимая синергия, - которая помогает вместе достигать большего. -

      -
    2. -
    3. -

      - Многоголосие. Мы ценим свободу слова и аргументированные - мнения. Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию - позиций, знаний и опыта, которые открывают более полную картину реальности. -

      -
    4. -
    5. -

      - Взаимопомощь. Мы помогаем друг другу, потому что хотим, чтобы - в мире было еще больше хорошего. Обсуждая что-то, мы всегда интересуемся, чем - можем помочь. В самиздате можно найти специалистов практически в любых сферах - и получить поддержку от сотен людей. Благодаря коллективной экспертизе - глобального сообщества в самиздате выходят крутейшие публикации, которыми можно вечно - гордиться. -

      -
    6. -
    7. -

      - Взаимоуважение. Мы ценим, искренне уважаем друг друга и вместо - борщевиков враждебности культивируем цветы добра, мира, знания и юмора. Нам некогда - доказывать друг другу, кто круче. Гораздо приятнее сотрудничать, помогать и создавать - что-то важное, интересное и полезное. -

      -
    8. -
    9. -

      - Созидание. Мы создаем, потому что любим создавать. Мы открыто - делимся опытом, дарим идеи, обмениваемся мнениями и благодарим за критику, - используя ее для совершенствования мастерства и саморазвития. Мы знаем, что - мир не идеальное место, и делаем всё возможное, чтобы он стал лучше. -

      -
    10. -
    - -

    - Как у нас принято себя вести -

    + + + + + + + + + + +

    + {ogTitle} +

    +
      +
    1. - Открытая редакция объединяет сотни потрясающих людей со всего мира, которые делают - крутейшие вещи. Это пространство, где доверяют, вдохновляют, исследуют и создают новое - вместе. Поскольку все в сообществе очень разные, как-то мы собрались и решили - зафиксировать базовые ценности открытой редакции, а заодно придумали универсальные - правила взаимодействия, чтобы общение было не только плодотворным, - но и приятным для всех участников сообщества. + Горизонтальность. Мы все разные, и это классно. Вертикалей + в мире достаточно, мы — горизонтальное сообщество и ценим наши различия, + потому что знаем — в них наша сила. Благодаря разнообразию сотен голосов, + усиливающих друг друга, в сообществе складывается неповторимая синергия, которая помогает + вместе достигать большего.

      -
        -
      1. -

        - Действуем, помогаем и делимся. В редакции мы создаем свои - проекты и помогаем другим создавать свои — советами, делом, участием, - вовлеченностью. Мы открыто делимся опытом, мнениями и идеями, потому что ценим - силу сотрудничества и знаем, что идеи реализуются скорее, лучше и веселее, если - над ними трудиться сообща. -

        -
      2. + +
      3. +

        + Многоголосие. Мы ценим свободу слова и аргументированные мнения. + Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию позиций, + знаний и опыта, которые открывают более полную картину реальности. +

        +
      4. +
      5. +

        + Взаимопомощь. Мы помогаем друг другу, потому что хотим, чтобы + в мире было еще больше хорошего. Обсуждая что-то, мы всегда интересуемся, чем можем + помочь. В самиздате можно найти специалистов практически в любых сферах + и получить поддержку от сотен людей. Благодаря коллективной экспертизе глобального + сообщества в самиздате выходят крутейшие публикации, которыми можно вечно гордиться. +

        +
      6. +
      7. +

        + Взаимоуважение. Мы ценим, искренне уважаем друг друга и вместо + борщевиков враждебности культивируем цветы добра, мира, знания и юмора. Нам некогда + доказывать друг другу, кто круче. Гораздо приятнее сотрудничать, помогать и создавать + что-то важное, интересное и полезное. +

        +
      8. +
      9. +

        + Созидание. Мы создаем, потому что любим создавать. Мы открыто + делимся опытом, дарим идеи, обмениваемся мнениями и благодарим за критику, используя + ее для совершенствования мастерства и саморазвития. Мы знаем, что мир + не идеальное место, и делаем всё возможное, чтобы он стал лучше. +

        +
      10. +
      -
    2. -

      - Общаемся дружелюбно. Помните, по ту сторону монитора находятся - реальные люди. Неуважение ранит других так же, как ранило бы вас самих. Поэтому - не стоит кричать (даже капслоком), заполнять эфир желчью и бросаться - грубостями — так вы рискуете не только растерять доверие окружающих, - но и остаться непонятым. -

      -
    3. +

      + Как у нас принято себя вести +

      -
    4. -

      - Критикуем и реагируем конструктивно. Самиздат про то, чтобы - разбираться в сложных вещах всем сообществом, поэтому мы тактично и без - агрессии делимся мнениями, стараясь убедительно аргументировать позиции. - И с благодарностью принимаем критику, используя ее для улучшения наших - проектов. Мы верим, что каждый участник сообщества имеет добрые намерения, - и придерживаемся принципов доброжелательной критики, стараемся делиться - советами — лучшим средством для самосовершенствования. Обоснованная критика - помогает и адресату, и всем участникам сообщества досконально изучить тему - и глубже разобраться в проблеме. -

      -
    5. +

      + Открытая редакция объединяет сотни потрясающих людей со всего мира, которые делают крутейшие + вещи. Это пространство, где доверяют, вдохновляют, исследуют и создают новое вместе. + Поскольку все в сообществе очень разные, как-то мы собрались и решили зафиксировать + базовые ценности открытой редакции, а заодно придумали универсальные правила взаимодействия, + чтобы общение было не только плодотворным, но и приятным для всех участников + сообщества. +

      +
        +
      1. +

        + Действуем, помогаем и делимся. В редакции мы создаем свои + проекты и помогаем другим создавать свои — советами, делом, участием, + вовлеченностью. Мы открыто делимся опытом, мнениями и идеями, потому что ценим силу + сотрудничества и знаем, что идеи реализуются скорее, лучше и веселее, если над ними + трудиться сообща. +

        +
      2. -
      3. -

        - Решаем трудности не агрессией, а диалогом. Обесценивать мнения - и оскорблять других людей только потому, что вы с ними - не согласны, — не лучший способ донести свою точку зрения. Конечно, - важно высказаться, если вас что-то не устраивает и откровенно бесит. - Но прежде чем сжигать оппонента гневом, попробуйте понять, почему этот - «нехороший человек» так поступает. Возможно, аргументы собеседника окажутся - убедительными или вам удастся изменить его мнение. В любом случае конфликты решаются - в диалогах и проходят, а налаженное взаимопонимание останется надолго. -

        -
      4. +
      5. +

        + Общаемся дружелюбно. Помните, по ту сторону монитора находятся + реальные люди. Неуважение ранит других так же, как ранило бы вас самих. Поэтому + не стоит кричать (даже капслоком), заполнять эфир желчью и бросаться + грубостями — так вы рискуете не только растерять доверие окружающих, + но и остаться непонятым. +

        +
      6. -
      7. -

        - Не переходим на личности — это признак токсичности. - Всегда мудрее обсуждать точку зрения человека, а не его самого, даже если - он вам не импонирует. Предвзятое отношение ограничивает кругозор, добавляет - преждевременные морщины и не помогает окружающим стать лучше. Вежливость - и взаимоуважение — краеугольная основа вдумчивых и осмысленных - дискуссий. -

        -
      8. +
      9. +

        + Критикуем и реагируем конструктивно. Самиздат про то, чтобы + разбираться в сложных вещах всем сообществом, поэтому мы тактично и без + агрессии делимся мнениями, стараясь убедительно аргументировать позиции. + И с благодарностью принимаем критику, используя ее для улучшения наших + проектов. Мы верим, что каждый участник сообщества имеет добрые намерения, + и придерживаемся принципов доброжелательной критики, стараемся делиться + советами — лучшим средством для самосовершенствования. Обоснованная критика + помогает и адресату, и всем участникам сообщества досконально изучить тему + и глубже разобраться в проблеме. +

        +
      10. -
      11. -

        - Благодарим за помощь. Благодарите коллег даже за самые, - казалось бы, простые вещи. «Спасибо» не зря называют волшебным - словом — на искренней благодарности держится любое подлинное - сотрудничество. Поддержка воодушевляет на новые подвиги и напоминает, что мир - делают прекрасным не машины, а живые люди. -

        -
      12. +
      13. +

        + Решаем трудности не агрессией, а диалогом. Обесценивать мнения + и оскорблять других людей только потому, что вы с ними + не согласны, — не лучший способ донести свою точку зрения. Конечно, важно + высказаться, если вас что-то не устраивает и откровенно бесит. Но прежде чем + сжигать оппонента гневом, попробуйте понять, почему этот «нехороший человек» так + поступает. Возможно, аргументы собеседника окажутся убедительными или вам удастся изменить его + мнение. В любом случае конфликты решаются в диалогах и проходят, + а налаженное взаимопонимание останется надолго. +

        +
      14. -
      15. -

        - Даем еще один шанс. Все совершают ошибки, и за один проступок - не стоит вычеркивать людей из жизни. Ошибки нужны, чтобы на них учиться - и делать выводы. Однако если многократно и систематически нарушать правила - сообщества, наверняка можно заслужить минусы в карму от других участников - и потерять доступ к сообществу. -

        -
      16. +
      17. +

        + Не переходим на личности — это признак токсичности. + Всегда мудрее обсуждать точку зрения человека, а не его самого, даже если + он вам не импонирует. Предвзятое отношение ограничивает кругозор, добавляет + преждевременные морщины и не помогает окружающим стать лучше. Вежливость + и взаимоуважение — краеугольная основа вдумчивых и осмысленных дискуссий. +

        +
      18. -
      19. -

        - Вместе создаем идеальную среду общения. Открытая редакция — - это утопическое пространство обогащающей и осмысленной коммуникации. Атмосфера - горизонтального сообщества складывается из действий каждого, поэтому - мы действуем так, чтобы способствовать сотворчеству, коллективному познанию - и развитию самиздата и нашей альтернативной интеллектуальной медиасреды. -

        -
      20. +
      21. +

        + Благодарим за помощь. Благодарите коллег даже за самые, + казалось бы, простые вещи. «Спасибо» не зря называют волшебным + словом — на искренней благодарности держится любое подлинное сотрудничество. + Поддержка воодушевляет на новые подвиги и напоминает, что мир делают прекрасным + не машины, а живые люди. +

        +
      22. -
      23. -

        - Помним, что всё в сообществе зависит от нас. Если нам чего-то - не хватает, мы начинаем действовать — рассказываем об идее, - находим единомышленников, готовим и запускаем проект. Так в сообществе - становится на одну крутую активность больше. Так появилось наше сообщество. Так - появился самиздат и все проекты открытой редакции. Чтобы в сообществе случилось - что-то прекрасное, достаточно просто положить этому начало. -

        -
      24. -
      -
    -
    +
  • +

    + Даем еще один шанс. Все совершают ошибки, и за один проступок + не стоит вычеркивать людей из жизни. Ошибки нужны, чтобы на них учиться + и делать выводы. Однако если многократно и систематически нарушать правила + сообщества, наверняка можно заслужить минусы в карму от других участников + и потерять доступ к сообществу. +

    +
  • + +
  • +

    + Вместе создаем идеальную среду общения. Открытая редакция — это + утопическое пространство обогащающей и осмысленной коммуникации. Атмосфера + горизонтального сообщества складывается из действий каждого, поэтому мы действуем + так, чтобы способствовать сотворчеству, коллективному познанию и развитию самиздата + и нашей альтернативной интеллектуальной медиасреды. +

    +
  • + +
  • +

    + Помним, что всё в сообществе зависит от нас. Если нам чего-то + не хватает, мы начинаем действовать — рассказываем об идее, находим + единомышленников, готовим и запускаем проект. Так в сообществе становится + на одну крутую активность больше. Так появилось наше сообщество. Так появился самиздат + и все проекты открытой редакции. Чтобы в сообществе случилось что-то прекрасное, + достаточно просто положить этому начало. +

    +
  • +
    -
    +
    ) } diff --git a/src/pages/about/termsOfUse.page.tsx b/src/pages/about/termsOfUse.page.tsx index cf6e294b..b640a0a4 100644 --- a/src/pages/about/termsOfUse.page.tsx +++ b/src/pages/about/termsOfUse.page.tsx @@ -1,283 +1,243 @@ import { Meta } from '@solidjs/meta' -import { createSignal, Show } from 'solid-js' -import { Icon } from '../../components/_shared/Icon' -import { PageLayout } from '../../components/_shared/PageLayout' +import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const TermsOfUsePage = () => { const { t } = useLocalize() - const [indexExpanded, setIndexExpanded] = createSignal(true) - const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded) + const ogTitle = t('Terms of use') + const ogImage = getImageUrl('production/image/logo_image.png') + const description = t('Rules of the journal Discours') - const title = t('Terms of use') return ( - - - - - + + + + + + + + + + + +
    -
    - - -
    -

    - Пользовательское соглашение -

    +

    + Пользовательское соглашение +

    +

    + Дискурс — это сообщество творческих людей, объединенных идеей делать интересный журнал + для всех желающих. Авторы Дискурса сообща посредством прямого голосования определяют содержание + журнала. +

    +

    Для того, чтобы Дискурс работал без помех, разработаны настоящие Правила.

    +

    Определения

    +

    + Сайт — портал discours.io +

    +

    + Пользователь — лицо, пользующееся Сайтом, либо юридическое лицо, + обладающее правами на интеллектуальную собственность. +

    +

    + Публикация контента — размещение Пользователем посредством Сайта + объектов авторских прав и другой информации для других пользователей. +

    +

    + Издательство — администрация сайта, которая занимается технической + и издательской деятельностью для обеспечения функционирования Сайта и Альманаха. + Издательство не вмешивается в принятие редакционных решений авторским сообществом. +

    +

    + Альманах «Дискурс» (свидетельство о регистрации СМИ: ПИ № + ФС77-63947 от 18.12.15) — печатное периодическое издание, которое выходит раз + в год и состоит из лучших публикаций на Сайте за это время. +

    + +
      +
    1. - Дискурс — это сообщество творческих людей, объединенных идеей делать интересный - журнал для всех желающих. Авторы Дискурса сообща посредством прямого голосования определяют - содержание журнала. + Вся информация на сайте (включая тексты, изображения, видеоматериалы, аудиозаписи, + программный код, дизайн сайта и т.д.) является объектом интеллектуальной собственности + ее правообладателей и охраняется законодательством РФ.

      -

      Для того, чтобы Дискурс работал без помех, разработаны настоящие Правила.

      -

      Определения

      +
    2. +
    3. - Сайт — портал discours.io + Публикуя контент на сайте, Пользователь на безвозмездной основе предоставляет + Издательству право на воспроизведение, распространение, перевод, редактирование контента. + Данное право предоставляется Издательству на весь срок действия авторских прав + Пользователя.

      +
    4. +
    5. - Пользователь — лицо, пользующееся Сайтом, либо юридическое лицо, - обладающее правами на интеллектуальную собственность. + Пользователь предоставляет Издательству право редактировать контент, в том числе вносить + в него изменения, сокращения и дополнения, снабжать его иллюстрациями + и пояснениями, исправлять ошибки и уточнять фактические сведения, при условии, что + этим не искажается авторский замысел.

      -

      - Публикация контента — размещение Пользователем посредством Сайта - объектов авторских прав и другой информации для других пользователей. -

      -

      - Издательство — администрация сайта, которая занимается технической - и издательской деятельностью для обеспечения функционирования Сайта и Альманаха. - Издательство не вмешивается в принятие редакционных решений авторским сообществом. -

      -

      - Альманах «Дискурс» (свидетельство о регистрации СМИ: ПИ - № ФС77-63947 от 18.12.15) — печатное периодическое издание, которое - выходит раз в год и состоит из лучших публикаций на Сайте за это - время. -

      - -
        -
      1. -

        - Вся информация на сайте (включая тексты, изображения, видеоматериалы, аудиозаписи, - программный код, дизайн сайта и т.д.) является объектом интеллектуальной - собственности ее правообладателей и охраняется законодательством РФ. -

        -
      2. -
      3. -

        - Публикуя контент на сайте, Пользователь на безвозмездной основе предоставляет - Издательству право на воспроизведение, распространение, перевод, редактирование - контента. Данное право предоставляется Издательству на весь срок действия авторских - прав Пользователя. -

        -
      4. -
      5. -

        - Пользователь предоставляет Издательству право редактировать контент, в том числе - вносить в него изменения, сокращения и дополнения, снабжать его иллюстрациями - и пояснениями, исправлять ошибки и уточнять фактические сведения, при условии, - что этим не искажается авторский замысел. -

        -
      6. -
      7. -

        - Обнародование контента осуществляется Издательством в соответствии с условиями - лицензии{' '} - - Creative Commons BY-NC-ND 4.0 - - . Все материалы сайта предназначены исключительно для личного некоммерческого - использования. Права на дизайн и программный код сайта принадлежат Издательству. -

        -
      8. -
      9. -

        - Все аудиовизуальные произведения являются собственностью своих авторов - и правообладателей и используются только в образовательных - и информационных целях. Если вы являетесь собственником того или иного - произведения и не согласны с его размещением на сайте, пожалуйста, - напишите на  - - welcome@discours.io - - . -

        -
      10. -
      11. -

        - Цитирование, распространение, доведение до всеобщего сведения материалов Cайта - приветствуется. При использовании материалов сайта необходимо указать имя автора - и активную ссылку на материал на Сайте. -

        -
      12. -
      -

      Правила поведения

      -
        -
      1. -

        - Находясь на Сайте, Пользователь подтверждает свое совершеннолетие, правоспособность, - а также согласие с настоящими Правилами и политикой конфиденциальности - и готовность нести полную ответственность за их соблюдение. -

        -
      2. -
      3. -

        На сайте запрещено:

        -
          -
        • - Публиковать контент, авторские права на который принадлежат третьим лицам, без - согласия этих лиц. Если авторские права на контент принадлежат нескольким лицам, - то его публикация предполагает согласие их всех. -
        • -
        • Размещать коммерческую и политическую рекламу.
        • -
        • - Целенаправленно препятствовать нормальному функционированию сообщества и сайта - discours.io -
        • -
        • Выдавать себя за другого человека и представляться его именем.
        • -
        • - Размещать информацию, которая не соответствует целям создания Сайта, ущемляет - интересы других пользователей или третьих лиц, нарушает законы Российской Федерации. -
        • -
        -
      4. -
      5. -

        - Пользователь несет всю ответственность за содержание публикуемого контента - и свое взаимодействие с другими пользователями, и обязуется возместить все - расходы в случае предъявления каких-либо претензий третьими лицами. Издательство - не несет ответственности за содержание публикуемой пользователями информации, - в том числе за размещенные на сайте комментарии. Переписка между - Пользователем и Издательством считается юридически значимой. Настоящие Правила могут - быть изменены Издательством, изменения вступают в силу с момента публикации - на Сайте. -

        -
      6. -
      7. -

        - Если Пользователь очевидно и целенаправленно нарушает правила, Издательство может - и принять в отношении автора следующие меры: вынести предупреждение - и обязать автора устранить допущенное нарушение, удалить контент, нарушающий правила, - заблокировать или удалить аккаунт нарушителя. -

        -
      8. -
      -

      Политика конфиденциальности

      -
        -
      1. -

        Сайт может собирать у пользователей следующие данные:

        -
          -
        • -

          - Данные, которые пользователи сообщают о себе сами при подаче заявки, регистрации, - авторизации или заполнения профиля, в том числе ФИО и контактную информацию. - Конфиденциальные данные, такие как идентификатор и электронный адрес, - используются для идентификации пользователя. Данные профиля, размещённые публично - по желанию пользователя, которое выражается фактом их предоставления, - используется для демонстрации другим пользователям той информации о себе, которую - пользователь готов предоставить. -

          -
        • -
        • -

          - Данные, собранные автоматическим путем, такие, как cookie-файлы. Эти - неперсонализированные данные могут использоваться для сбора статистики - и улучшения работы сайта. -

          -
        • -
        -
      2. -
      3. -

        - Издательство обеспечивает конфиденциальность персональных данных и применяет все - необходимые организационные и технические меры по их защите. -

        -
      4. -
      5. -

        - По желанию пользователя Издательство готово удалить любую информацию о нем, - собранную автоматическим путем. Для этого следует написать на адрес электронной почты{' '} - - welcome@discours.io - - . -

        -
      6. -
      7. -

        - Если в информации, предоставляемой Издательству Пользователем, содержатся - персональные данные последнего, то фактом их предоставления он соглашается - на их обработку любым способом, не запрещенным законодательством РФ. -

        -

        - Общедоступные видео на сайте могут транслироваться с YouTube и регулируются{' '} - - политикой конфиденциальности Google - - . Загрузка видео на сайт также означает согласие с  - - Условиями использования YouTube - - . -

        -
      8. -
      9. -

        - Данные, которые мы получаем от вас, мы используем только - в соответствии с принципами обработки данных, указанными в этом документе. -

        -
      10. -
      -

      Обратная связь

      +
    6. +
    7. - Любые вопросы и предложения по поводу функционирования сайта можно направить - по электронной почте{' '} + Обнародование контента осуществляется Издательством в соответствии с условиями + лицензии{' '} + + Creative Commons BY-NC-ND 4.0 + + . Все материалы сайта предназначены исключительно для личного некоммерческого использования. + Права на дизайн и программный код сайта принадлежат Издательству. +

      +
    8. +
    9. +

      + Все аудиовизуальные произведения являются собственностью своих авторов и правообладателей + и используются только в образовательных и информационных целях. Если + вы являетесь собственником того или иного произведения и не согласны с его + размещением на сайте, пожалуйста, напишите на  welcome@discours.io - {' '} - или через форму «предложить идею». + + .

      -
    -
    + +
  • +

    + Цитирование, распространение, доведение до всеобщего сведения материалов Cайта + приветствуется. При использовании материалов сайта необходимо указать имя автора + и активную ссылку на материал на Сайте. +

    +
  • + +

    Правила поведения

    +
      +
    1. +

      + Находясь на Сайте, Пользователь подтверждает свое совершеннолетие, правоспособность, + а также согласие с настоящими Правилами и политикой конфиденциальности + и готовность нести полную ответственность за их соблюдение. +

      +
    2. +
    3. +

      На сайте запрещено:

      +
        +
      • + Публиковать контент, авторские права на который принадлежат третьим лицам, без согласия + этих лиц. Если авторские права на контент принадлежат нескольким лицам, то его + публикация предполагает согласие их всех. +
      • +
      • Размещать коммерческую и политическую рекламу.
      • +
      • + Целенаправленно препятствовать нормальному функционированию сообщества и сайта + discours.io +
      • +
      • Выдавать себя за другого человека и представляться его именем.
      • +
      • + Размещать информацию, которая не соответствует целям создания Сайта, ущемляет интересы + других пользователей или третьих лиц, нарушает законы Российской Федерации. +
      • +
      +
    4. +
    5. +

      + Пользователь несет всю ответственность за содержание публикуемого контента и свое + взаимодействие с другими пользователями, и обязуется возместить все расходы + в случае предъявления каких-либо претензий третьими лицами. Издательство не несет + ответственности за содержание публикуемой пользователями информации, в том числе + за размещенные на сайте комментарии. Переписка между Пользователем + и Издательством считается юридически значимой. Настоящие Правила могут быть изменены + Издательством, изменения вступают в силу с момента публикации на Сайте. +

      +
    6. +
    7. +

      + Если Пользователь очевидно и целенаправленно нарушает правила, Издательство может + и принять в отношении автора следующие меры: вынести предупреждение и обязать + автора устранить допущенное нарушение, удалить контент, нарушающий правила, заблокировать или + удалить аккаунт нарушителя. +

      +
    8. +
    +

    Политика конфиденциальности

    +
      +
    1. +

      Сайт может собирать у пользователей следующие данные:

      +
        +
      • +

        + Данные, которые пользователи сообщают о себе сами при подаче заявки, регистрации, + авторизации или заполнения профиля, в том числе ФИО и контактную информацию. + Конфиденциальные данные, такие как идентификатор и электронный адрес, используются + для идентификации пользователя. Данные профиля, размещённые публично по желанию + пользователя, которое выражается фактом их предоставления, используется для + демонстрации другим пользователям той информации о себе, которую пользователь готов + предоставить. +

        +
      • +
      • +

        + Данные, собранные автоматическим путем, такие, как cookie-файлы. Эти неперсонализированные + данные могут использоваться для сбора статистики и улучшения работы сайта. +

        +
      • +
      +
    2. +
    3. +

      + Издательство обеспечивает конфиденциальность персональных данных и применяет все + необходимые организационные и технические меры по их защите. +

      +
    4. +
    5. +

      + По желанию пользователя Издательство готово удалить любую информацию о нем, + собранную автоматическим путем. Для этого следует написать на адрес электронной почты{' '} + + welcome@discours.io + + . +

      +
    6. +
    7. +

      + Если в информации, предоставляемой Издательству Пользователем, содержатся персональные + данные последнего, то фактом их предоставления он соглашается + на их обработку любым способом, не запрещенным законодательством РФ. +

      +

      + Общедоступные видео на сайте могут транслироваться с YouTube и регулируются{' '} + + политикой конфиденциальности Google + + . Загрузка видео на сайт также означает согласие с  + + Условиями использования YouTube + + . +

      +
    8. +
    9. +

      + Данные, которые мы получаем от вас, мы используем только в соответствии + с принципами обработки данных, указанными в этом документе. +

      +
    10. +
    +

    Обратная связь

    +

    + Любые вопросы и предложения по поводу функционирования сайта можно направить + по электронной почте{' '} + + welcome@discours.io + {' '} + или через форму «предложить идею». +

    -
    +
    ) } diff --git a/src/pages/about/thanks.page.tsx b/src/pages/about/thanks.page.tsx index 96288037..5e2278b9 100644 --- a/src/pages/about/thanks.page.tsx +++ b/src/pages/about/thanks.page.tsx @@ -1,24 +1,34 @@ import { Meta } from '@solidjs/meta' -import { PageLayout } from '../../components/_shared/PageLayout' +import { StaticPage } from '../../components/Views/StaticPage' import { useLocalize } from '../../context/localize' +import { getImageUrl } from '../../utils/getImageUrl' export const ThanksPage = () => { const { t } = useLocalize() - const title = t('Thank you') + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Thank you') + const description = t( + 'Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!', + ) + return ( - - - - - +
    -
    -
    -

    - {title} -

    - {/* + + + + + + + + + + +

    + {ogTitle} +

    + {/*

    Команда

    Константин Ворович — исполнительный директор, @@ -43,47 +53,43 @@ export const ThanksPage = () => { >

    */} -

    Неоценимый вклад в Дискурс внесли и вносят

    -

    - Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин, - Павел Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик, - Вячеслав Еременко, Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр - Коренков, Ирэна Лесневская, Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений - Медведев, Павел Никулин, Николай Носачевский, Андрей Орловский, Михаил Панин, Антон Панов, - Павел Пепперштейн, Любовь Покровская, Илья Розовский, Денис Светличный, Павел Соколов, Сергей - Стрельников, Глеб Струнников, Николай Тарковский, Кирилл Филимонов, Алексей Хапов, Екатерина - Харитонова -

    -

    Авторы

    -

    - Мы безмерно благодарны{' '} - - каждому автору - {' '} - за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью - и основным источником мотивации, бескорыстная помощь и основанный на энтузиазме - труд бесценны. Именно вы своим трудом каждый день делаете Дискурс таким, какой - он есть. -

    -

    Иллюстраторы

    -

    - Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова, - Мария Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья - Diliago, Антон Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита - Поздняков, Матвей Сапегин, Татьяна Сафонова, Виктория Шибаева -

    -

    Меценаты

    -

    - Дискурс существует исключительно на пожертвования читателей. Мы бесконечно - признательны всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент - журнала. Благодаря вам мы развиваем платформу качественной журналистики, которая помогает - самым разным авторам быть услышанными. Стать нашим меценатом и подписаться - на ежемесячную поддержку проекта можно здесь. -

    -
    -
    +

    Неоценимый вклад в Дискурс внесли и вносят

    +

    + Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин, Павел + Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик, Вячеслав + Еременко, Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр Коренков, Ирэна + Лесневская, Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений Медведев, Павел Никулин, + Николай Носачевский, Андрей Орловский, Михаил Панин, Антон Панов, Павел Пепперштейн, Любовь + Покровская, Илья Розовский, Денис Светличный, Павел Соколов, Сергей Стрельников, Глеб Струнников, + Николай Тарковский, Кирилл Филимонов, Алексей Хапов, Екатерина Харитонова +

    +

    Авторы

    +

    + Мы безмерно благодарны{' '} + + каждому автору + {' '} + за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью + и основным источником мотивации, бескорыстная помощь и основанный на энтузиазме + труд бесценны. Именно вы своим трудом каждый день делаете Дискурс таким, какой он есть. +

    +

    Иллюстраторы

    +

    + Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова, Мария + Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья Diliago, + Антон Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита Поздняков, + Матвей Сапегин, Татьяна Сафонова, Виктория Шибаева +

    +

    Меценаты

    +

    + Дискурс существует исключительно на пожертвования читателей. Мы бесконечно признательны + всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент журнала. + Благодаря вам мы развиваем платформу качественной журналистики, которая помогает самым разным + авторам быть услышанными. Стать нашим меценатом и подписаться на ежемесячную поддержку + проекта можно здесь. +

    -
    + ) } diff --git a/src/pages/allAuthors.page.tsx b/src/pages/allAuthors.page.tsx index c4a1f197..0384eb7c 100644 --- a/src/pages/allAuthors.page.tsx +++ b/src/pages/allAuthors.page.tsx @@ -1,8 +1,7 @@ import type { PageProps } from './types' -import { createSignal, onMount, Show } from 'solid-js' +import { createSignal, onMount } from 'solid-js' -import { Loading } from '../components/_shared/Loading' import { PageLayout } from '../components/_shared/PageLayout' import { AllAuthorsView } from '../components/Views/AllAuthors' import { useLocalize } from '../context/localize' @@ -24,9 +23,7 @@ export const AllAuthorsPage = (props: PageProps) => { return ( - }> - - + ) } diff --git a/src/pages/allTopics.page.tsx b/src/pages/allTopics.page.tsx index 98c082df..08b7f9fe 100644 --- a/src/pages/allTopics.page.tsx +++ b/src/pages/allTopics.page.tsx @@ -1,8 +1,7 @@ import type { PageProps } from './types' -import { createSignal, onMount, Show } from 'solid-js' +import { createSignal, onMount } from 'solid-js' -import { Loading } from '../components/_shared/Loading' import { PageLayout } from '../components/_shared/PageLayout' import { AllTopicsView } from '../components/Views/AllTopics' import { useLocalize } from '../context/localize' @@ -23,10 +22,8 @@ export const AllTopicsPage = (props: PageProps) => { }) return ( - - }> - - + + ) } diff --git a/src/pages/author.page.tsx b/src/pages/author.page.tsx index a13e531f..3be8caf9 100644 --- a/src/pages/author.page.tsx +++ b/src/pages/author.page.tsx @@ -5,6 +5,7 @@ import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } import { Loading } from '../components/_shared/Loading' import { PageLayout } from '../components/_shared/PageLayout' import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author' +import { useLocalize } from '../context/localize' import { ReactionsProvider } from '../context/reactions' import { useRouter } from '../stores/router' import { loadShouts, resetSortedArticles } from '../stores/zine/articles' diff --git a/src/pages/create.page.tsx b/src/pages/create.page.tsx index 8efcf156..2864e2da 100644 --- a/src/pages/create.page.tsx +++ b/src/pages/create.page.tsx @@ -1,4 +1,5 @@ import { redirectPage } from '@nanostores/router' +import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { Button } from '../components/_shared/Button' @@ -8,6 +9,7 @@ import { AuthGuard } from '../components/AuthGuard' import { useLocalize } from '../context/localize' import { router } from '../stores/router' import { apiClient } from '../utils/apiClient' +import { getImageUrl } from '../utils/getImageUrl' import { LayoutType } from './types' @@ -22,9 +24,22 @@ const handleCreate = async (layout: LayoutType) => { export const CreatePage = () => { const { t } = useLocalize() + const ogImage = getImageUrl('production/image/logo_image.png') + const ogTitle = t('Choose a post type') + const description = t('Participate in the Discours: share information, join the editorial team') return ( - + + + + + + + + + + +

    {t('Choose a post type')}

    diff --git a/src/pages/topic.page.tsx b/src/pages/topic.page.tsx index aeaf268e..12e029ef 100644 --- a/src/pages/topic.page.tsx +++ b/src/pages/topic.page.tsx @@ -1,24 +1,25 @@ import type { PageProps } from './types' -import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } from 'solid-js' +import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' -import { Loading } from '../components/_shared/Loading' import { PageLayout } from '../components/_shared/PageLayout' import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic' +import { useLocalize } from '../context/localize' import { ReactionsProvider } from '../context/reactions' import { useRouter } from '../stores/router' import { loadShouts, resetSortedArticles } from '../stores/zine/articles' import { loadTopic } from '../stores/zine/topics' +import { capitalize } from '../utils/capitalize' export const TopicPage = (props: PageProps) => { const { page } = useRouter() - + const { t } = useLocalize() const slug = createMemo(() => page().params['slug'] as string) const [isLoaded, setIsLoaded] = createSignal( Boolean(props.topicShouts) && Boolean(props.topic) && props.topic.slug === slug(), ) - + const [pageTitle, setPageTitle] = createSignal() const preload = () => Promise.all([ loadShouts({ filters: { topic: slug() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 }), @@ -53,15 +54,15 @@ export const TopicPage = (props: PageProps) => { const usePrerenderedData = props.topic?.slug === slug() return ( - + - }> - - + setPageTitle(title)} + isLoaded={isLoaded()} + topic={usePrerenderedData ? props.topic : null} + shouts={usePrerenderedData ? props.topicShouts : null} + topicSlug={slug()} + /> ) diff --git a/src/utils/meta.ts b/src/utils/meta.ts index a411f676..b536e9da 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -1,10 +1,24 @@ +import { Shout } from '../graphql/types.gen' + +const MAX_DESCRIPTION_LENGTH = 150 export const getDescription = (body: string): string => { if (!body) { return '' } const descriptionWordsArray = body - .slice(0, 150) // meta description is roughly 155 characters long - .replaceAll(/<[^>]*>/g, '') + .replaceAll(/<[^>]*>/g, ' ') + .replaceAll(/\s+/g, ' ') .split(' ') - return descriptionWordsArray.splice(0, descriptionWordsArray.length - 1).join(' ') + '...' + // ¯\_(ツ)_/¯ maybe need to remove the punctuation + let description = '' + let i = 0 + while (i < descriptionWordsArray.length && description.length < MAX_DESCRIPTION_LENGTH) { + description += descriptionWordsArray[i] + ' ' + i++ + } + return description.trim() +} + +export const getKeywords = (shout: Shout): string => { + return shout.topics.map((topic) => topic.title).join(', ') } From f9b9d129dd38d34fe459245c29fec03336775a08 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:08:58 +0300 Subject: [PATCH 2/6] Feed metateags (#330) --- src/components/Article/Comment/Comment.module.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Article/Comment/Comment.module.scss b/src/components/Article/Comment/Comment.module.scss index 168ae67c..50170022 100644 --- a/src/components/Article/Comment/Comment.module.scss +++ b/src/components/Article/Comment/Comment.module.scss @@ -4,7 +4,6 @@ transition: background-color 0.3s; position: relative; list-style: none; - background: rgb(0 0 0 / 10%); @include media-breakpoint-down(sm) { padding-right: 0; From e5846deab7e503437cd327049f6ffad112bdbbbc Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Wed, 13 Dec 2023 23:57:33 +0100 Subject: [PATCH 3/6] Expo random top articles (#331) * WIP * done --------- Co-authored-by: Igor Lobanov --- src/components/App.tsx | 1 - src/components/Views/Expo/Expo.tsx | 114 ++++++++++++++---- src/components/Views/Search.tsx | 5 +- src/graphql/query/articles-load-random-top.ts | 46 +++++++ src/graphql/types.gen.ts | 16 ++- src/pages/expo/expo.page.route.ts | 3 +- src/pages/expo/expo.page.server.ts | 8 +- src/pages/expo/expo.page.tsx | 2 +- src/pages/expo/expoLayout.page.route.ts | 4 - src/pages/expo/expoLayout.page.server.ts | 21 ---- src/pages/search.page.server.ts | 2 +- src/pages/search.page.tsx | 2 +- src/stores/router.ts | 3 +- src/stores/zine/articles.ts | 7 +- src/utils/apiClient.ts | 12 ++ src/utils/getServerDate.ts | 4 + 16 files changed, 183 insertions(+), 67 deletions(-) create mode 100644 src/graphql/query/articles-load-random-top.ts delete mode 100644 src/pages/expo/expoLayout.page.route.ts delete mode 100644 src/pages/expo/expoLayout.page.server.ts create mode 100644 src/utils/getServerDate.ts diff --git a/src/components/App.tsx b/src/components/App.tsx index 9fe54ab2..f6a22fe1 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -50,7 +50,6 @@ const pagesMap: Record> = { authorAbout: AuthorPage, inbox: InboxPage, expo: ExpoPage, - expoLayout: ExpoPage, connect: ConnectPage, create: CreatePage, edit: EditPage, diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 51c9a6bb..5f22dcf8 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -3,49 +3,89 @@ import { clsx } from 'clsx' import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js' import { useLocalize } from '../../../context/localize' -import { LoadShoutsOptions, Shout } from '../../../graphql/types.gen' +import { LoadRandomTopShoutsParams, LoadShoutsOptions, Shout } from '../../../graphql/types.gen' import { LayoutType } from '../../../pages/types' -import { router, useRouter } from '../../../stores/router' +import { router } from '../../../stores/router' import { loadShouts, resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles' +import { apiClient } from '../../../utils/apiClient' +import { getServerDate } from '../../../utils/getServerDate' import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' import { splitToPages } from '../../../utils/splitToPages' import { Button } from '../../_shared/Button' import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' import { Loading } from '../../_shared/Loading' +import { ArticleCardSwiper } from '../../_shared/SolidSwiper/ArticleCardSwiper' import { ArticleCard } from '../../Feed/ArticleCard' import styles from './Expo.module.scss' type Props = { shouts: Shout[] + layout: LayoutType } -export const PRERENDERED_ARTICLES_COUNT = 28 + +export const PRERENDERED_ARTICLES_COUNT = 32 const LOAD_MORE_PAGE_SIZE = 16 + export const Expo = (props: Props) => { const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts)) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) + const [randomTopArticles, setRandomTopArticles] = createSignal([]) + const [randomTopMonthArticles, setRandomTopMonthArticles] = createSignal([]) + const { t } = useLocalize() - const { page: getPage } = useRouter() - const getLayout = createMemo(() => getPage().params['layout'] as LayoutType) + const { sortedArticles } = useArticlesStore({ shouts: isLoaded() ? props.shouts : [], }) - const loadMore = async (count) => { + const loadMore = async (count: number) => { saveScrollPosition() const options: LoadShoutsOptions = { limit: count, offset: sortedArticles().length, } - options.filters = getLayout() ? { layout: getLayout() } : { excludeLayout: 'article' } + options.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } const { hasMore } = await loadShouts(options) setIsLoadMoreButtonVisible(hasMore) restoreScrollPosition() } + const loadRandomTopArticles = async () => { + const params: LoadRandomTopShoutsParams = { + filters: { + visibility: 'public', + }, + limit: 10, + fromRandomCount: 100, + } + params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } + + const result = await apiClient.getRandomTopShouts(params) + setRandomTopArticles(result) + } + + const loadRandomTopMonthArticles = async () => { + const now = new Date() + const fromDate = getServerDate(new Date(now.setMonth(now.getMonth() - 1))) + + const params: LoadRandomTopShoutsParams = { + filters: { + visibility: 'public', + fromDate, + }, + limit: 10, + fromRandomCount: 10, + } + params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } + + const result = await apiClient.getRandomTopShouts(params) + setRandomTopMonthArticles(result) + } + const pages = createMemo(() => splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE), ) @@ -65,12 +105,21 @@ export const Expo = (props: Props) => { } }) + onMount(() => { + loadRandomTopArticles() + loadRandomTopMonthArticles() + }) + createEffect( on( - () => getLayout(), + () => props.layout, () => { resetSortedArticles() + setRandomTopArticles([]) + setRandomTopMonthArticles([]) loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE) + loadRandomTopArticles() + loadRandomTopMonthArticles() }, { defer: true }, ), @@ -89,49 +138,49 @@ export const Expo = (props: Props) => { 0} fallback={}>
    - + {(shout) => (
    {
    )}
    + 0} keyed={true}> + + + + {(shout) => ( +
    + +
    + )} +
    + 0} keyed={true}> + + {(page) => ( diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index f87909a1..d8d179a7 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -36,10 +36,7 @@ export const SearchView = (props: Props) => { const loadMore = async () => { saveScrollPosition() const { hasMore } = await loadShouts({ - filters: { - title: query(), - body: query(), - }, + filters: {}, offset: offset(), limit: LOAD_MORE_PAGE_SIZE, }) diff --git a/src/graphql/query/articles-load-random-top.ts b/src/graphql/query/articles-load-random-top.ts new file mode 100644 index 00000000..9ac58789 --- /dev/null +++ b/src/graphql/query/articles-load-random-top.ts @@ -0,0 +1,46 @@ +import { gql } from '@urql/core' + +export default gql` + query LoadRandomTopShoutsQuery($params: LoadRandomTopShoutsParams) { + loadRandomTopShouts(params: $params) { + id + title + lead + description + subtitle + slug + layout + cover + lead + # community + mainTopic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + userpic + createdAt + bio + } + createdAt + publishedAt + stat { + viewed + reacted + rating + commented + } + } + } +` diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts index fd7bb869..32fa65cf 100644 --- a/src/graphql/types.gen.ts +++ b/src/graphql/types.gen.ts @@ -116,14 +116,19 @@ export enum FollowingEntity { Topic = 'TOPIC', } +export type LoadRandomTopShoutsParams = { + filters?: InputMaybe + fromRandomCount?: InputMaybe + limit: Scalars['Int']['input'] +} + export type LoadShoutsFilters = { author?: InputMaybe - body?: InputMaybe - days?: InputMaybe excludeLayout?: InputMaybe + fromDate?: InputMaybe layout?: InputMaybe reacted?: InputMaybe - title?: InputMaybe + toDate?: InputMaybe topic?: InputMaybe visibility?: InputMaybe } @@ -367,6 +372,7 @@ export type Query = { loadMessagesBy: Result loadMySubscriptions?: Maybe loadNotifications: NotificationsQueryResult + loadRandomTopShouts: Array> loadReactionsBy: Array> loadRecipients: Result loadShout?: Maybe @@ -419,6 +425,10 @@ export type QueryLoadNotificationsArgs = { params: NotificationsQueryParams } +export type QueryLoadRandomTopShoutsArgs = { + params?: InputMaybe +} + export type QueryLoadReactionsByArgs = { by: ReactionBy limit?: InputMaybe diff --git a/src/pages/expo/expo.page.route.ts b/src/pages/expo/expo.page.route.ts index 8270f299..9e19ae4d 100644 --- a/src/pages/expo/expo.page.route.ts +++ b/src/pages/expo/expo.page.route.ts @@ -1,4 +1,5 @@ import { ROUTES } from '../../stores/router' import { getServerRoute } from '../../utils/getServerRoute' -export default getServerRoute(ROUTES.expo) +// yes, it's a hack +export default getServerRoute(ROUTES.expo.replace(':layout?', '*')) diff --git a/src/pages/expo/expo.page.server.ts b/src/pages/expo/expo.page.server.ts index 1f771a42..ce6b3b94 100644 --- a/src/pages/expo/expo.page.server.ts +++ b/src/pages/expo/expo.page.server.ts @@ -4,12 +4,16 @@ import type { PageProps } from '../types' import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Expo/Expo' import { apiClient } from '../../utils/apiClient' -export const onBeforeRender = async (_pageContext: PageContext) => { +export const onBeforeRender = async (pageContext: PageContext) => { + const { layout } = pageContext.routeParams + const expoShouts = await apiClient.getShouts({ - filters: { excludeLayout: 'article' }, + filters: layout ? { layout } : { excludeLayout: 'article' }, limit: PRERENDERED_ARTICLES_COUNT, }) + const pageProps: PageProps = { expoShouts, seo: { title: '' } } + return { pageContext: { pageProps, diff --git a/src/pages/expo/expo.page.tsx b/src/pages/expo/expo.page.tsx index 658e68e0..5ed9e2ec 100644 --- a/src/pages/expo/expo.page.tsx +++ b/src/pages/expo/expo.page.tsx @@ -37,7 +37,7 @@ export const ExpoPage = (props: PageProps) => { return ( - + ) } diff --git a/src/pages/expo/expoLayout.page.route.ts b/src/pages/expo/expoLayout.page.route.ts deleted file mode 100644 index afdbd8b6..00000000 --- a/src/pages/expo/expoLayout.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.expoLayout) diff --git a/src/pages/expo/expoLayout.page.server.ts b/src/pages/expo/expoLayout.page.server.ts deleted file mode 100644 index fbdd5118..00000000 --- a/src/pages/expo/expoLayout.page.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { PageContext } from '../../renderer/types' -import type { PageProps } from '../types' - -import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Expo/Expo' -import { apiClient } from '../../utils/apiClient' - -export const onBeforeRender = async (pageContext: PageContext) => { - const { layout } = pageContext.routeParams - const expoShouts = await apiClient.getShouts({ - filters: { layout: layout }, - limit: PRERENDERED_ARTICLES_COUNT, - }) - - const pageProps: PageProps = { expoShouts, seo: { title: '' } } - - return { - pageContext: { - pageProps, - }, - } -} diff --git a/src/pages/search.page.server.ts b/src/pages/search.page.server.ts index 7c4f7115..a3cfc224 100644 --- a/src/pages/search.page.server.ts +++ b/src/pages/search.page.server.ts @@ -6,7 +6,7 @@ import { apiClient } from '../utils/apiClient' export const onBeforeRender = async (pageContext: PageContext) => { const { q } = pageContext.routeParams - const searchResults = await apiClient.getShouts({ filters: { title: q, body: q }, limit: 50 }) + const searchResults = await apiClient.getShouts({ filters: {}, limit: 50 }) const pageProps: PageProps = { searchResults, seo: { title: '' } } diff --git a/src/pages/search.page.tsx b/src/pages/search.page.tsx index d7a72e3a..84b0fe92 100644 --- a/src/pages/search.page.tsx +++ b/src/pages/search.page.tsx @@ -24,7 +24,7 @@ export const SearchPage = (props: PageProps) => { return } - await loadShouts({ filters: { title: q(), body: q() }, limit: 50, offset: 0 }) + await loadShouts({ filters: {}, limit: 50, offset: 0 }) setIsLoaded(true) }) diff --git a/src/stores/router.ts b/src/stores/router.ts index 23e98b9c..4803ed82 100644 --- a/src/stores/router.ts +++ b/src/stores/router.ts @@ -37,8 +37,7 @@ export const ROUTES = { projects: '/about/projects', termsOfUse: '/about/terms-of-use', thanks: '/about/thanks', - expo: '/expo', - expoLayout: '/expo/:layout', + expo: '/expo/:layout?', profileSettings: '/profile/settings', profileSecurity: '/profile/security', profileSubscriptions: '/profile/subscriptions', diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index 48f6e316..530b61bd 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -4,6 +4,7 @@ import { createLazyMemo } from '@solid-primitives/memo' import { createSignal } from 'solid-js' import { apiClient } from '../../utils/apiClient' +import { getServerDate } from '../../utils/getServerDate' import { byStat } from '../../utils/sortby' import { addAuthorsByTopic } from './authors' @@ -193,11 +194,13 @@ type InitialState = { const TOP_MONTH_ARTICLES_COUNT = 10 export const loadTopMonthArticles = async (): Promise => { + const now = new Date() + const fromDate = getServerDate(new Date(now.setMonth(now.getMonth() - 1))) + const articles = await apiClient.getShouts({ filters: { visibility: 'public', - // TODO: replace with from, to - days: 30, + fromDate, }, order_by: 'rating_stat', limit: TOP_MONTH_ARTICLES_COUNT, diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index 5256ee95..d77e44bd 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -19,6 +19,7 @@ import type { NotificationsQueryParams, NotificationsQueryResult, MySubscriptionsQueryResult, + LoadRandomTopShoutsParams, } from '../graphql/types.gen' import createArticle from '../graphql/mutation/article-create' @@ -43,6 +44,8 @@ import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient' import { publicGraphQLClient } from '../graphql/publicGraphQLClient' import shoutLoad from '../graphql/query/article-load' import shoutsLoadBy from '../graphql/query/articles-load-by' +import shoutsLoadRandomTop from '../graphql/query/articles-load-random-top' +import articlesLoadRandomTop from '../graphql/query/articles-load-random-top' import authCheckEmailQuery from '../graphql/query/auth-check-email' import authLoginQuery from '../graphql/query/auth-login' import authorBySlug from '../graphql/query/author-by-slug' @@ -350,6 +353,15 @@ export const apiClient = { return resp.data.loadShouts }, + getRandomTopShouts: async (params: LoadRandomTopShoutsParams): Promise => { + const resp = await publicGraphQLClient.query(articlesLoadRandomTop, { params }).toPromise() + if (resp.error) { + console.error(resp) + } + + return resp.data.loadRandomTopShouts + }, + getMyFeed: async (options: LoadShoutsOptions) => { const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() diff --git a/src/utils/getServerDate.ts b/src/utils/getServerDate.ts new file mode 100644 index 00000000..feea507c --- /dev/null +++ b/src/utils/getServerDate.ts @@ -0,0 +1,4 @@ +export const getServerDate = (date: Date): string => { + // 2023-12-31 + return date.toISOString().slice(0, 10) +} From d2977b9b21c184e88bfd89a49c081c363a58bf32 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Thu, 14 Dec 2023 19:45:50 +0100 Subject: [PATCH 4/6] Feature/unrated (#334) * Rate first markup * WIP * lint * unrated articles + random top fixes --------- Co-authored-by: ilya-bkv Co-authored-by: Igor Lobanov --- public/locales/en/translation.json | 1 + public/locales/ru/translation.json | 1 + .../Feed/ArticleCard/ArticleCard.tsx | 2 +- src/components/Views/Expo/Expo.tsx | 37 ++++++++----- .../Views/{ => Feed}/Feed.module.scss | 0 src/components/Views/{ => Feed}/Feed.tsx | 55 ++++++++++++------- src/components/Views/Feed/index.ts | 1 + src/components/Views/Search.tsx | 2 +- .../_shared/SolidSwiper/Swiper.module.scss | 2 +- src/graphql/query/articles-load-unrated.ts | 46 ++++++++++++++++ src/graphql/types.gen.ts | 5 ++ src/pages/author.page.tsx | 1 - src/pages/expo/expo.page.tsx | 18 ++++-- src/pages/feed.page.tsx | 8 +-- src/pages/topic.page.tsx | 3 - src/stores/zine/articles.ts | 10 +--- src/utils/apiClient.ts | 11 +++- 17 files changed, 143 insertions(+), 60 deletions(-) rename src/components/Views/{ => Feed}/Feed.module.scss (100%) rename src/components/Views/{ => Feed}/Feed.tsx (83%) create mode 100644 src/components/Views/Feed/index.ts create mode 100644 src/graphql/query/articles-load-unrated.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index ee37c254..6b7a4173 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -41,6 +41,7 @@ "Back": "Back", "Back to editor": "Back to editor", "Back to main page": "Back to main page", + "Be the first to rate": "Be the first to rate", "Become an author": "Become an author", "Bold": "Bold", "Bookmarked": "Saved", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 560ff2b6..5f9fd460 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -44,6 +44,7 @@ "Back": "Назад", "Back to editor": "Вернуться в редактор", "Back to main page": "Вернуться на главную", + "Be the first to rate": "Оцените первым", "Become an author": "Стать автором", "Bold": "Жирный", "Bookmarked": "Сохранено", diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 5bf55248..00654f34 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -46,7 +46,7 @@ export type ArticleCardProps = { withViewed?: boolean noAuthorLink?: boolean } - desktopCoverSize: 'XS' | 'S' | 'M' | 'L' + desktopCoverSize?: 'XS' | 'S' | 'M' | 'L' article: Shout } diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 5f22dcf8..06c592e2 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -3,7 +3,12 @@ import { clsx } from 'clsx' import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js' import { useLocalize } from '../../../context/localize' -import { LoadRandomTopShoutsParams, LoadShoutsOptions, Shout } from '../../../graphql/types.gen' +import { + LoadRandomTopShoutsParams, + LoadShoutsFilters, + LoadShoutsOptions, + Shout, +} from '../../../graphql/types.gen' import { LayoutType } from '../../../pages/types' import { router } from '../../../stores/router' import { loadShouts, resetSortedArticles, useArticlesStore } from '../../../stores/zine/articles' @@ -24,7 +29,7 @@ type Props = { layout: LayoutType } -export const PRERENDERED_ARTICLES_COUNT = 32 +export const PRERENDERED_ARTICLES_COUNT = 24 const LOAD_MORE_PAGE_SIZE = 16 export const Expo = (props: Props) => { @@ -40,15 +45,26 @@ export const Expo = (props: Props) => { shouts: isLoaded() ? props.shouts : [], }) + const getLoadShoutsFilters = (filters: LoadShoutsFilters = {}): LoadShoutsFilters => { + const result = { ...filters } + + if (props.layout) { + filters.layout = props.layout + } else { + filters.excludeLayout = 'article' + } + + return result + } + const loadMore = async (count: number) => { saveScrollPosition() const options: LoadShoutsOptions = { + filters: getLoadShoutsFilters(), limit: count, offset: sortedArticles().length, } - options.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } - const { hasMore } = await loadShouts(options) setIsLoadMoreButtonVisible(hasMore) restoreScrollPosition() @@ -56,13 +72,10 @@ export const Expo = (props: Props) => { const loadRandomTopArticles = async () => { const params: LoadRandomTopShoutsParams = { - filters: { - visibility: 'public', - }, + filters: getLoadShoutsFilters(), limit: 10, fromRandomCount: 100, } - params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } const result = await apiClient.getRandomTopShouts(params) setRandomTopArticles(result) @@ -73,14 +86,10 @@ export const Expo = (props: Props) => { const fromDate = getServerDate(new Date(now.setMonth(now.getMonth() - 1))) const params: LoadRandomTopShoutsParams = { - filters: { - visibility: 'public', - fromDate, - }, + filters: getLoadShoutsFilters({ fromDate }), limit: 10, fromRandomCount: 10, } - params.filters = props.layout ? { layout: props.layout } : { excludeLayout: 'article' } const result = await apiClient.getRandomTopShouts(params) setRandomTopMonthArticles(result) @@ -103,9 +112,7 @@ export const Expo = (props: Props) => { if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) { loadMore(LOAD_MORE_PAGE_SIZE) } - }) - onMount(() => { loadRandomTopArticles() loadRandomTopMonthArticles() }) diff --git a/src/components/Views/Feed.module.scss b/src/components/Views/Feed/Feed.module.scss similarity index 100% rename from src/components/Views/Feed.module.scss rename to src/components/Views/Feed/Feed.module.scss diff --git a/src/components/Views/Feed.tsx b/src/components/Views/Feed/Feed.tsx similarity index 83% rename from src/components/Views/Feed.tsx rename to src/components/Views/Feed/Feed.tsx index 7a3869e9..a2253965 100644 --- a/src/components/Views/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -1,32 +1,32 @@ -import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../graphql/types.gen' +import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/types.gen' import { getPagePath } from '@nanostores/router' import { Meta } from '@solidjs/meta' import { clsx } from 'clsx' import { createEffect, createSignal, For, on, onMount, Show } from 'solid-js' -import { useLocalize } from '../../context/localize' -import { useReactions } from '../../context/reactions' -import { router, useRouter } from '../../stores/router' -import { useArticlesStore, resetSortedArticles } from '../../stores/zine/articles' -import { useTopAuthorsStore } from '../../stores/zine/topAuthors' -import { useTopicsStore } from '../../stores/zine/topics' -import { capitalize } from '../../utils/capitalize' -import { getImageUrl } from '../../utils/getImageUrl' -import { getDescription } from '../../utils/meta' -import { Icon } from '../_shared/Icon' -import { Loading } from '../_shared/Loading' -import { CommentDate } from '../Article/CommentDate' -import { AuthorLink } from '../Author/AhtorLink' -import { AuthorBadge } from '../Author/AuthorBadge' -import { ArticleCard } from '../Feed/ArticleCard' -import { Sidebar } from '../Feed/Sidebar' +import { useLocalize } from '../../../context/localize' +import { useReactions } from '../../../context/reactions' +import { router, useRouter } from '../../../stores/router' +import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles' +import { useTopAuthorsStore } from '../../../stores/zine/topAuthors' +import { useTopicsStore } from '../../../stores/zine/topics' +import { apiClient } from '../../../utils/apiClient' +import { getImageUrl } from '../../../utils/getImageUrl' +import { Icon } from '../../_shared/Icon' +import { Loading } from '../../_shared/Loading' +import { CommentDate } from '../../Article/CommentDate' +import { AuthorLink } from '../../Author/AhtorLink' +import { AuthorBadge } from '../../Author/AuthorBadge' +import { ArticleCard } from '../../Feed/ArticleCard' +import { Sidebar } from '../../Feed/Sidebar' import styles from './Feed.module.scss' -import stylesBeside from '../../components/Feed/Beside.module.scss' -import stylesTopic from '../Feed/CardTopic.module.scss' +import stylesBeside from '../../Feed/Beside.module.scss' +import stylesTopic from '../../Feed/CardTopic.module.scss' export const FEED_PAGE_SIZE = 20 +const UNRATED_ARTICLES_COUNT = 5 type FeedSearchParams = { by: 'publish_date' | 'rating' | 'last_comment' @@ -51,7 +51,7 @@ type Props = { }> } -export const FeedView = (props: Props) => { +export const Feed = (props: Props) => { const { t } = useLocalize() const { page, searchParams } = useRouter() const [isLoading, setIsLoading] = createSignal(false) @@ -62,13 +62,20 @@ export const FeedView = (props: Props) => { const { topAuthors } = useTopAuthorsStore() const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [topComments, setTopComments] = createSignal([]) + const [unratedArticles, setUnratedArticles] = createSignal([]) const { actions: { loadReactionsBy }, } = useReactions() + const loadUnratedArticles = async () => { + const result = await apiClient.getUnratedShouts(UNRATED_ARTICLES_COUNT) + setUnratedArticles(result) + } + onMount(() => { loadMore() + loadUnratedArticles() }) createEffect( @@ -271,6 +278,14 @@ export const FeedView = (props: Props) => { + 0}> +
    +

    {t('Be the first to rate')}

    + + {(article) => } + +
    +
    diff --git a/src/components/Views/Feed/index.ts b/src/components/Views/Feed/index.ts new file mode 100644 index 00000000..f0fa7232 --- /dev/null +++ b/src/components/Views/Feed/index.ts @@ -0,0 +1 @@ +export { Feed } from './Feed' diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index d8d179a7..be54593b 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -29,7 +29,7 @@ export const SearchView = (props: Props) => { const { searchParams } = useRouter() let searchEl: HTMLInputElement - const handleQueryChange = (_ev) => { + const handleQueryChange = () => { setQuery(searchEl.value) } diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index 500efb7d..4928695e 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -101,7 +101,7 @@ padding: 0; & swiper-slide { - //bind to html element + // bind to html element width: unset !important; } diff --git a/src/graphql/query/articles-load-unrated.ts b/src/graphql/query/articles-load-unrated.ts new file mode 100644 index 00000000..36d00301 --- /dev/null +++ b/src/graphql/query/articles-load-unrated.ts @@ -0,0 +1,46 @@ +import { gql } from '@urql/core' + +export default gql` + query LoadUnratedShoutsQuery($limit: Int!) { + loadUnratedShouts(limit: $limit) { + id + title + lead + description + subtitle + slug + layout + cover + lead + # community + mainTopic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + userpic + createdAt + bio + } + createdAt + publishedAt + stat { + viewed + reacted + rating + commented + } + } + } +` diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts index 32fa65cf..609977af 100644 --- a/src/graphql/types.gen.ts +++ b/src/graphql/types.gen.ts @@ -377,6 +377,7 @@ export type Query = { loadRecipients: Result loadShout?: Maybe loadShouts: Array> + loadUnratedShouts: Array> markdownBody: Scalars['String']['output'] myFeed?: Maybe>> searchMessages: Result @@ -449,6 +450,10 @@ export type QueryLoadShoutsArgs = { options?: InputMaybe } +export type QueryLoadUnratedShoutsArgs = { + limit: Scalars['Int']['input'] +} + export type QueryMarkdownBodyArgs = { body: Scalars['String']['input'] } diff --git a/src/pages/author.page.tsx b/src/pages/author.page.tsx index 3be8caf9..a13e531f 100644 --- a/src/pages/author.page.tsx +++ b/src/pages/author.page.tsx @@ -5,7 +5,6 @@ import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } import { Loading } from '../components/_shared/Loading' import { PageLayout } from '../components/_shared/PageLayout' import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author' -import { useLocalize } from '../context/localize' import { ReactionsProvider } from '../context/reactions' import { useRouter } from '../stores/router' import { loadShouts, resetSortedArticles } from '../stores/zine/articles' diff --git a/src/pages/expo/expo.page.tsx b/src/pages/expo/expo.page.tsx index 5ed9e2ec..d6bf64b0 100644 --- a/src/pages/expo/expo.page.tsx +++ b/src/pages/expo/expo.page.tsx @@ -1,6 +1,6 @@ import type { PageProps } from '../types' -import { createMemo } from 'solid-js' +import { createEffect, createMemo, on } from 'solid-js' import { PageLayout } from '../../components/_shared/PageLayout' import { Topics } from '../../components/Nav/Topics' @@ -14,7 +14,7 @@ export const ExpoPage = (props: PageProps) => { const { page } = useRouter() const getLayout = createMemo(() => page().params['layout'] as LayoutType) - const title = createMemo(() => { + const getTitle = () => { switch (getLayout()) { case 'music': { return t('Audio') @@ -32,10 +32,20 @@ export const ExpoPage = (props: PageProps) => { return t('Art') } } - }) + } + + createEffect( + on( + () => getLayout(), + () => { + document.title = getTitle() + }, + { defer: true }, + ), + ) return ( - + diff --git a/src/pages/feed.page.tsx b/src/pages/feed.page.tsx index ee3c8bf1..3daf019d 100644 --- a/src/pages/feed.page.tsx +++ b/src/pages/feed.page.tsx @@ -2,7 +2,7 @@ import { createEffect, Match, on, onCleanup, Switch } from 'solid-js' import { PageLayout } from '../components/_shared/PageLayout' import { AuthGuard } from '../components/AuthGuard' -import { FeedView } from '../components/Views/Feed' +import { Feed } from '../components/Views/Feed' import { useLocalize } from '../context/localize' import { ReactionsProvider } from '../context/reactions' import { LoadShoutsOptions } from '../graphql/types.gen' @@ -40,13 +40,13 @@ export const FeedPage = () => { return ( - }> + }> - + - + diff --git a/src/pages/topic.page.tsx b/src/pages/topic.page.tsx index 12e029ef..574eb12a 100644 --- a/src/pages/topic.page.tsx +++ b/src/pages/topic.page.tsx @@ -4,16 +4,13 @@ import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from ' import { PageLayout } from '../components/_shared/PageLayout' import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic' -import { useLocalize } from '../context/localize' import { ReactionsProvider } from '../context/reactions' import { useRouter } from '../stores/router' import { loadShouts, resetSortedArticles } from '../stores/zine/articles' import { loadTopic } from '../stores/zine/topics' -import { capitalize } from '../utils/capitalize' export const TopicPage = (props: PageProps) => { const { page } = useRouter() - const { t } = useLocalize() const slug = createMemo(() => page().params['slug'] as string) const [isLoaded, setIsLoaded] = createSignal( diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index 530b61bd..e8fd9842 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -1,4 +1,4 @@ -import type { Author, Shout, ShoutInput, LoadShoutsOptions } from '../../graphql/types.gen' +import type { Author, Shout, LoadShoutsOptions } from '../../graphql/types.gen' import { createLazyMemo } from '@solid-primitives/memo' import { createSignal } from 'solid-js' @@ -179,14 +179,6 @@ export const resetSortedArticles = () => { setSortedArticles([]) } -export const createArticle = async ({ article }: { article: ShoutInput }) => { - try { - await apiClient.createArticle({ article }) - } catch (error) { - console.error(error) - } -} - type InitialState = { shouts?: Shout[] } diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index d77e44bd..ee966de1 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -44,8 +44,8 @@ import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient' import { publicGraphQLClient } from '../graphql/publicGraphQLClient' import shoutLoad from '../graphql/query/article-load' import shoutsLoadBy from '../graphql/query/articles-load-by' -import shoutsLoadRandomTop from '../graphql/query/articles-load-random-top' import articlesLoadRandomTop from '../graphql/query/articles-load-random-top' +import articlesLoadUnrated from '../graphql/query/articles-load-unrated' import authCheckEmailQuery from '../graphql/query/auth-check-email' import authLoginQuery from '../graphql/query/auth-login' import authorBySlug from '../graphql/query/author-by-slug' @@ -362,6 +362,15 @@ export const apiClient = { return resp.data.loadRandomTopShouts }, + getUnratedShouts: async (limit: number): Promise => { + const resp = await publicGraphQLClient.query(articlesLoadUnrated, { limit }).toPromise() + if (resp.error) { + console.error(resp) + } + + return resp.data.loadUnratedShouts + }, + getMyFeed: async (options: LoadShoutsOptions) => { const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() From 63494e9d04a5a7a81c25e025706af61ed79286e9 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Sat, 16 Dec 2023 14:57:08 +0100 Subject: [PATCH 5/6] publicGraphQLClient, privateGraphQLClient -> graphQLClient (#336) * publicGraphQLClient, privateGraphQLClient -> graphQLClient * ssr fix --------- Co-authored-by: Igor Lobanov --- src/context/session.tsx | 2 +- ...ivateGraphQLClient.ts => graphQLClient.ts} | 17 ++-- src/graphql/publicGraphQLClient.ts | 20 ---- src/utils/apiClient.ts | 95 +++++++++---------- 4 files changed, 58 insertions(+), 76 deletions(-) rename src/graphql/{privateGraphQLClient.ts => graphQLClient.ts} (79%) delete mode 100644 src/graphql/publicGraphQLClient.ts diff --git a/src/context/session.tsx b/src/context/session.tsx index 0be22560..be7c7e03 100644 --- a/src/context/session.tsx +++ b/src/context/session.tsx @@ -12,7 +12,7 @@ import { useContext, } from 'solid-js' -import { resetToken, setToken } from '../graphql/privateGraphQLClient' +import { resetToken, setToken } from '../graphql/graphQLClient' import { showModal } from '../stores/ui' import { apiClient } from '../utils/apiClient' diff --git a/src/graphql/privateGraphQLClient.ts b/src/graphql/graphQLClient.ts similarity index 79% rename from src/graphql/privateGraphQLClient.ts rename to src/graphql/graphQLClient.ts index 38da3a31..cb5eaa3b 100644 --- a/src/graphql/privateGraphQLClient.ts +++ b/src/graphql/graphQLClient.ts @@ -1,23 +1,24 @@ import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core' import { devtoolsExchange } from '@urql/devtools' +import { isServer } from 'solid-js/web' import { isDev, apiBaseUrl } from '../utils/config' -const TOKEN_LOCAL_STORAGE_KEY = 'token' - const exchanges: Exchange[] = [dedupExchange, fetchExchange] if (isDev) { exchanges.unshift(devtoolsExchange) } +const TOKEN_LOCAL_STORAGE_KEY = 'token' + export const getToken = (): string => { return localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY) } export const setToken = (token: string) => { if (!token) { - console.error('[privateGraphQLClient] setToken: token is null!') + console.error('[graphQLClient] setToken: token is null!') } localStorage.setItem(TOKEN_LOCAL_STORAGE_KEY, token) @@ -32,16 +33,20 @@ const options: ClientOptions = { maskTypename: true, requestPolicy: 'cache-and-network', fetchOptions: () => { + if (isServer) { + return {} + } // localStorage is the source of truth for now // to change token call setToken, for example after login - const token = localStorage.getItem(TOKEN_LOCAL_STORAGE_KEY) + const token = getToken() if (!token) { - console.error('[privateGraphQLClient] fetchOptions: token is null!') + return {} } + const headers = { Authorization: token } return { headers } }, exchanges, } -export const privateGraphQLClient = createClient(options) +export const graphQLClient = createClient(options) diff --git a/src/graphql/publicGraphQLClient.ts b/src/graphql/publicGraphQLClient.ts deleted file mode 100644 index ce58878a..00000000 --- a/src/graphql/publicGraphQLClient.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } from '@urql/core' -import { devtoolsExchange } from '@urql/devtools' - -import { isDev, apiBaseUrl } from '../utils/config' -// import { cache } from './cache' - -const exchanges: Exchange[] = [dedupExchange, fetchExchange] //, cache] - -if (isDev) { - exchanges.unshift(devtoolsExchange) -} - -const options: ClientOptions = { - url: apiBaseUrl, - maskTypename: true, - requestPolicy: 'cache-and-network', - exchanges, -} - -export const publicGraphQLClient = createClient(options) diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts index ee966de1..01430f7b 100644 --- a/src/utils/apiClient.ts +++ b/src/utils/apiClient.ts @@ -22,6 +22,7 @@ import type { LoadRandomTopShoutsParams, } from '../graphql/types.gen' +import { getToken, graphQLClient } from '../graphql/graphQLClient' import createArticle from '../graphql/mutation/article-create' import deleteShout from '../graphql/mutation/article-delete' import updateArticle from '../graphql/mutation/article-update' @@ -40,8 +41,6 @@ import reactionDestroy from '../graphql/mutation/reaction-destroy' import reactionUpdate from '../graphql/mutation/reaction-update' import unfollowMutation from '../graphql/mutation/unfollow' import updateProfile from '../graphql/mutation/update-profile' -import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient' -import { publicGraphQLClient } from '../graphql/publicGraphQLClient' import shoutLoad from '../graphql/query/article-load' import shoutsLoadBy from '../graphql/query/articles-load-by' import articlesLoadRandomTop from '../graphql/query/articles-load-random-top' @@ -86,7 +85,7 @@ export class ApiError extends Error { export const apiClient = { authLogin: async ({ email, password }: { email: string; password: string }): Promise => { - const response = await publicGraphQLClient.query(authLoginQuery, { email, password }).toPromise() + const response = await graphQLClient.query(authLoginQuery, { email, password }).toPromise() // console.debug('[api-client] authLogin', { response }) if (response.error) { if ( @@ -118,7 +117,7 @@ export const apiClient = { password: string name: string }): Promise => { - const response = await publicGraphQLClient + const response = await graphQLClient .mutation(authRegisterMutation, { email, password, name }) .toPromise() @@ -131,17 +130,17 @@ export const apiClient = { } }, authSignOut: async () => { - const response = await publicGraphQLClient.query(authLogoutQuery, {}).toPromise() + const response = await graphQLClient.query(authLogoutQuery, {}).toPromise() return response.data.signOut }, authCheckEmail: async ({ email }) => { // check if email is used - const response = await publicGraphQLClient.query(authCheckEmailQuery, { email }).toPromise() + const response = await graphQLClient.query(authCheckEmailQuery, { email }).toPromise() return response.data.isEmailUsed }, authSendLink: async ({ email, lang, template }) => { // send link with code on email - const response = await publicGraphQLClient + const response = await graphQLClient .mutation(authSendLinkMutation, { email, lang, template }) .toPromise() @@ -161,7 +160,7 @@ export const apiClient = { }, confirmEmail: async ({ token }: { token: string }) => { // confirm email with code from link - const response = await publicGraphQLClient.mutation(authConfirmEmailMutation, { token }).toPromise() + const response = await graphQLClient.mutation(authConfirmEmailMutation, { token }).toPromise() if (response.error) { // TODO: better error communication if (response.error.message === '[GraphQL] check token lifetime') { @@ -183,7 +182,7 @@ export const apiClient = { }, getRandomTopics: async ({ amount }: { amount: number }) => { - const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise() + const response = await graphQLClient.query(topicsRandomQuery, { amount }).toPromise() if (!response.data) { console.error('[api-client] getRandomTopics', response.error) @@ -195,11 +194,11 @@ export const apiClient = { // subscribe follow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { - const response = await privateGraphQLClient.mutation(followMutation, { what, slug }).toPromise() + const response = await graphQLClient.mutation(followMutation, { what, slug }).toPromise() return response.data.follow }, unfollow: async ({ what, slug }: { what: FollowingEntity; slug: string }) => { - const response = await privateGraphQLClient.mutation(unfollowMutation, { what, slug }).toPromise() + const response = await graphQLClient.mutation(unfollowMutation, { what, slug }).toPromise() return response.data.unfollow }, @@ -209,7 +208,7 @@ export const apiClient = { } // renew session with auth token in header (!) - const response = await privateGraphQLClient.mutation(mySession, {}).toPromise() + const response = await graphQLClient.mutation(mySession, {}).toPromise() if (response.error) { throw new ApiError('unknown', response.error.message) @@ -222,37 +221,37 @@ export const apiClient = { return response.data.getSession }, getAllTopics: async () => { - const response = await publicGraphQLClient.query(topicsAll, {}).toPromise() + const response = await graphQLClient.query(topicsAll, {}).toPromise() if (response.error) { console.debug('[api-client] getAllTopics', response.error) } return response.data.topicsAll }, getAllAuthors: async () => { - const response = await publicGraphQLClient.query(authorsAll, {}).toPromise() + const response = await graphQLClient.query(authorsAll, {}).toPromise() if (response.error) { console.debug('[api-client] getAllAuthors', response.error) } return response.data.authorsAll }, getAuthor: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(authorBySlug, { slug }).toPromise() + const response = await graphQLClient.query(authorBySlug, { slug }).toPromise() return response.data.getAuthor }, getAuthorFollowers: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(userSubscribers, { slug }).toPromise() + const response = await graphQLClient.query(userSubscribers, { slug }).toPromise() return response.data.userFollowers }, getAuthorFollowingUsers: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(userFollowedAuthors, { slug }).toPromise() + const response = await graphQLClient.query(userFollowedAuthors, { slug }).toPromise() return response.data.userFollowedAuthors }, getAuthorFollowingTopics: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(userFollowedTopics, { slug }).toPromise() + const response = await graphQLClient.query(userFollowedTopics, { slug }).toPromise() return response.data.userFollowedTopics }, updateProfile: async (input: ProfileInput) => { - const response = await privateGraphQLClient.mutation(updateProfile, { profile: input }).toPromise() + const response = await graphQLClient.mutation(updateProfile, { profile: input }).toPromise() if (response.error) { if ( response.error.message.includes('duplicate key value violates unique constraint "user_slug_key"') @@ -265,11 +264,11 @@ export const apiClient = { return response.data.updateProfile }, getTopic: async ({ slug }: { slug: string }): Promise => { - const response = await publicGraphQLClient.query(topicBySlug, { slug }).toPromise() + const response = await graphQLClient.query(topicBySlug, { slug }).toPromise() return response.data.getTopic }, createArticle: async ({ article }: { article: ShoutInput }): Promise => { - const response = await privateGraphQLClient.mutation(createArticle, { shout: article }).toPromise() + const response = await graphQLClient.mutation(createArticle, { shout: article }).toPromise() return response.data.createShout.shout }, updateArticle: async ({ @@ -281,57 +280,55 @@ export const apiClient = { shoutInput?: ShoutInput publish: boolean }): Promise => { - const response = await privateGraphQLClient + const response = await graphQLClient .mutation(updateArticle, { shoutId, shoutInput, publish }) .toPromise() console.debug('[updateArticle]:', response.data) return response.data.updateShout.shout }, deleteShout: async ({ shoutId }: { shoutId: number }): Promise => { - const response = await privateGraphQLClient.mutation(deleteShout, { shoutId }).toPromise() + const response = await graphQLClient.mutation(deleteShout, { shoutId }).toPromise() console.debug('[deleteShout]:', response) }, getDrafts: async (): Promise => { - const response = await privateGraphQLClient.query(draftsLoad, {}).toPromise() + const response = await graphQLClient.query(draftsLoad, {}).toPromise() console.debug('[getDrafts]:', response) return response.data.loadDrafts }, createReaction: async (input: ReactionInput) => { - const response = await privateGraphQLClient.mutation(reactionCreate, { reaction: input }).toPromise() + const response = await graphQLClient.mutation(reactionCreate, { reaction: input }).toPromise() console.debug('[createReaction]:', response) return response.data.createReaction.reaction }, destroyReaction: async (id: number) => { - const response = await privateGraphQLClient.mutation(reactionDestroy, { id: id }).toPromise() + const response = await graphQLClient.mutation(reactionDestroy, { id: id }).toPromise() console.debug('[destroyReaction]:', response) return response.data.deleteReaction.reaction }, updateReaction: async (id: number, input: ReactionInput) => { - const response = await privateGraphQLClient - .mutation(reactionUpdate, { id: id, reaction: input }) - .toPromise() + const response = await graphQLClient.mutation(reactionUpdate, { id: id, reaction: input }).toPromise() console.debug('[updateReaction]:', response) return response.data.updateReaction.reaction }, getAuthorsBy: async (options: QueryLoadAuthorsByArgs) => { - const resp = await publicGraphQLClient.query(authorsLoadBy, options).toPromise() + const resp = await graphQLClient.query(authorsLoadBy, options).toPromise() return resp.data.loadAuthorsBy }, getShoutBySlug: async (slug: string) => { - const resp = await publicGraphQLClient + const resp = await graphQLClient .query(shoutLoad, { slug, }) .toPromise() - // if (resp.error) { - // console.error(resp) - // } + if (resp.error) { + console.error(resp) + } return resp.data.loadShout }, getShoutById: async (shoutId: number) => { - const resp = await publicGraphQLClient + const resp = await graphQLClient .query(shoutLoad, { shoutId, }) @@ -345,7 +342,7 @@ export const apiClient = { }, getShouts: async (options: LoadShoutsOptions) => { - const resp = await publicGraphQLClient.query(shoutsLoadBy, { options }).toPromise() + const resp = await graphQLClient.query(shoutsLoadBy, { options }).toPromise() if (resp.error) { console.error(resp) } @@ -354,7 +351,7 @@ export const apiClient = { }, getRandomTopShouts: async (params: LoadRandomTopShoutsParams): Promise => { - const resp = await publicGraphQLClient.query(articlesLoadRandomTop, { params }).toPromise() + const resp = await graphQLClient.query(articlesLoadRandomTop, { params }).toPromise() if (resp.error) { console.error(resp) } @@ -363,7 +360,7 @@ export const apiClient = { }, getUnratedShouts: async (limit: number): Promise => { - const resp = await publicGraphQLClient.query(articlesLoadUnrated, { limit }).toPromise() + const resp = await graphQLClient.query(articlesLoadUnrated, { limit }).toPromise() if (resp.error) { console.error(resp) } @@ -372,7 +369,7 @@ export const apiClient = { }, getMyFeed: async (options: LoadShoutsOptions) => { - const resp = await privateGraphQLClient.query(myFeed, { options }).toPromise() + const resp = await graphQLClient.query(myFeed, { options }).toPromise() if (resp.error) { console.error(resp) @@ -382,17 +379,17 @@ export const apiClient = { }, getReactionsBy: async ({ by, limit }: { by: ReactionBy; limit?: number }) => { - const resp = await publicGraphQLClient + const resp = await graphQLClient .query(reactionsLoadBy, { by, limit: limit ?? 1000, offset: 0 }) .toPromise() return resp.data.loadReactionsBy }, getNotifications: async (params: NotificationsQueryParams): Promise => { - const resp = await privateGraphQLClient.query(notifications, { params }).toPromise() + const resp = await graphQLClient.query(notifications, { params }).toPromise() return resp.data.loadNotifications }, markNotificationAsRead: async (notificationId: number): Promise => { - await privateGraphQLClient + await graphQLClient .mutation(markNotificationAsRead, { notificationId, }) @@ -400,37 +397,37 @@ export const apiClient = { }, markAllNotificationsAsRead: async (): Promise => { - await privateGraphQLClient.mutation(markAllNotificationsAsRead, {}).toPromise() + await graphQLClient.mutation(markAllNotificationsAsRead, {}).toPromise() }, getMySubscriptions: async (): Promise => { - const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise() + const resp = await graphQLClient.query(mySubscriptions, {}).toPromise() // console.debug(resp.data) return resp.data.loadMySubscriptions }, // inbox getChats: async (options: QueryLoadChatsArgs): Promise => { - const resp = await privateGraphQLClient.query(myChats, options).toPromise() + const resp = await graphQLClient.query(myChats, options).toPromise() return resp.data.loadChats.chats }, createChat: async (options: MutationCreateChatArgs) => { - const resp = await privateGraphQLClient.mutation(createChat, options).toPromise() + const resp = await graphQLClient.mutation(createChat, options).toPromise() return resp.data.createChat }, createMessage: async (options: MutationCreateMessageArgs) => { - const resp = await privateGraphQLClient.mutation(createMessage, options).toPromise() + const resp = await graphQLClient.mutation(createMessage, options).toPromise() return resp.data.createMessage.message }, getChatMessages: async (options: QueryLoadMessagesByArgs) => { - const resp = await privateGraphQLClient.query(chatMessagesLoadBy, options).toPromise() + const resp = await graphQLClient.query(chatMessagesLoadBy, options).toPromise() return resp.data.loadMessagesBy.messages }, getRecipients: async (options: QueryLoadRecipientsArgs) => { - const resp = await privateGraphQLClient.query(loadRecipients, options).toPromise() + const resp = await graphQLClient.query(loadRecipients, options).toPromise() return resp.data.loadRecipients.members }, } From ffde754a432b5aa5ef63238568ae52c0b4500cc8 Mon Sep 17 00:00:00 2001 From: Igor Lobanov Date: Sat, 16 Dec 2023 15:06:41 +0100 Subject: [PATCH 6/6] expo random tops fixes (#337) Co-authored-by: Igor Lobanov --- src/components/Views/Expo/Expo.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 06c592e2..1d347b82 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -58,7 +58,6 @@ export const Expo = (props: Props) => { } const loadMore = async (count: number) => { - saveScrollPosition() const options: LoadShoutsOptions = { filters: getLoadShoutsFilters(), limit: count, @@ -67,6 +66,11 @@ export const Expo = (props: Props) => { const { hasMore } = await loadShouts(options) setIsLoadMoreButtonVisible(hasMore) + } + + const loadMoreWithoutScrolling = async (count: number) => { + saveScrollPosition() + await loadMore(count) restoreScrollPosition() } @@ -137,7 +141,7 @@ export const Expo = (props: Props) => { }) const handleLoadMoreClick = () => { - loadMore(LOAD_MORE_PAGE_SIZE) + loadMoreWithoutScrolling(LOAD_MORE_PAGE_SIZE) } return ( @@ -206,8 +210,8 @@ export const Expo = (props: Props) => {
    )} - 0} keyed={true}> - + 0} keyed={true}> + {(shout) => ( @@ -220,8 +224,8 @@ export const Expo = (props: Props) => {
    )} - 0} keyed={true}> - + 0} keyed={true}> + {(page) => (