diff --git a/.eslintrc.js b/.eslintrc.js index 1c34b9a..477d7a8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,12 +10,12 @@ module.exports = { plugins: ['@typescript-eslint'], parserOptions: { sourceType: 'module', - ecmaVersion: 2024, + ecmaVersion: 2020, extraFileExtensions: ['.svelte'] }, env: { browser: true, - es2024: true, + es2017: true, node: true }, rules: { diff --git a/package-lock.json b/package-lock.json index 6a6592f..5438b0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,6 @@ "": { "name": "teasanctuary-ru", "version": "0.0.1", - "dependencies": { - "@formatjs/intl-durationformat": "^0.7.6" - }, "devDependencies": { "@iconify/svelte": "^4.2.0", "@react2svelte/swipeable": "^0.1.4", @@ -622,47 +619,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", - "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", - "license": "MIT", - "dependencies": { - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.2", - "decimal.js": "^10.4.3", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", - "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/intl-durationformat": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@formatjs/intl-durationformat/-/intl-durationformat-0.7.6.tgz", - "integrity": "sha512-jatAN3E84X6aP2UOGK1jTrwD1a7BiG3qWUSEDAhtyNd1BgYeS5wQPtXlnuGF1QRx0DjnwwNOIssyd7oQoRlQeg==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/intl-localematcher": "0.6.2", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", - "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2004,12 +1960,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4147,6 +4097,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/type-check": { diff --git a/package.json b/package.json index b211b22..ee4f7bc 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,5 @@ "typescript": "^5.7.3", "vite": "^6.2.0" }, - "type": "module", - "dependencies": { - "@formatjs/intl-durationformat": "^0.7.6" - } + "type": "module" } diff --git a/src/app.d.ts b/src/app.d.ts index 520d030..9dfaddb 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,18 +1,10 @@ -import type { - DurationFormatConstructor, - DurationFormatOptions as _DurationFormatOptions, - DurationInput as _DurationInput, -} from '@formatjs/intl-durationformat/src/types'; - declare global { - // rndtrash: Терпим. https://github.com/microsoft/TypeScript/issues/60608 - namespace Intl { - const DurationFormat: DurationFormatConstructor; - type DurationFormatOptions = _DurationFormatOptions; - type DurationInput = _DurationInput; - } - namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface Platform {} + interface Route { label: string; icon: string; diff --git a/src/blogs/tsmc_1.md b/src/blogs/tsmc_1.md deleted file mode 100644 index 28b19a6..0000000 --- a/src/blogs/tsmc_1.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: 'Mapping Challenge #1' -description: 'Наш первый конкурс для авторов карт под Half-Life Deathmatch!' -date: '2025-11-26 12:00:00 GMT+3' - -type: 'event' -dateEventFrom: '2025-11-26 12:00:00 GMT+3' -dateEventTo: '2025-12-26 12:00:00 GMT+3' - -projects: ['ts-hldm'] -thumbnail: 'poster.webp' -thumbnailAlt: 'Постер TSMC #1: Модель info_player_start пробивается через стену с помощью молота в виде логотипа Valve Hammer Editor' ---- - -![Тизер TSMC #1: С 26 ноября по 26 декабря 2025 года, Tea Sanctuary Mapping Challenge #1](challenge_teaser.webp) - -# Tea Sanctuary Mapping Challenge #1 - -Добро пожаловать на страницу первого конкурса по левел-дизайну от __Tea Sanctuary__! Ваша задача: за __30 дней__ создать с нуля карту для __Half-Life 1: Deathmatch__ по __зимней теме__. Авторы трёх лучших карт получат главный приз: игру в __Steam__ за __2000 рублей__!💸 - -На конкурс приглашаются как опытные мододелы, так и новички, желающие опробовать себя в создании карт для любимой игры. __Туториал для новичков__ по использованию редактора [TrenchBroom](https://github.com/TrenchBroom/TrenchBroom/) будет выложен в ближайшее время. Не переключайтесь! - -Конкурс проводится с целью привлечения внимания к проектам Tea Sanctuary, и расширению его сообщества. Мы будем рады видеть талантливых участников в наших рядах! - -Вне зависимости от успешности этого мероприятия, мы планируем и дальше радовать вас новыми событиями, так что обязательно следите за нашими новостями!📰 - -# Призы - -Для авторов карт, прошедших начальный отбор: -- Карта вместе с ресурсами будет добавлена на наш сервер [Half-Life Deathmatch](https://hl.teasanctuary.ru) в основной и в особый, зимний пул карт. -- В [нашем Discord-сообществе](https://teasanctuary.ru/discord) и [Telegram-чате](https://t.me/tea_sanctuary) вы получите __отличительную роль__ - -Для авторов трёх лучших карт по выбору жюри и администрации: -- __Игры в [Steam](https://store.steampowered.com/)__ на выбор участника (не более четырёх), доступные в регионе РФ, стоимостью суммарно меньше __двух тысяч рублей__ (₽2000) в региональной цене на момент выдачи приза -- Также __отличительная роль__, но чуть круче😁 - -# Тема - -Для первого конкурса мы взяли незамысловатую тему: __зима__.⛄ - -Что для вас значит это слово? -- Праздничная обстановка накануне Рождества или Нового Года? -- Зимняя сказка в тихом лесу? -- Предпраздничный аврал в исследовательском центре Чёрной Мезы? -- Тоскливые панельки в заснеженном городе? - - А может, и не заснеженном, а так, *по-южному*, весь в лужах и грязи, но с новогодними декорациями? - -Мы призываем вас проявить творческий подход в осмыслении этой темы. Не ограничивайтесь устоявшимися шаблонами и существующими текстурами — удивите нас! Главное, чтобы было и глазу приятно, и игралось весело! - -# Правила - - -Правила снизу работают по джентльменскому соглашению: если вы не согласны с правилами или с решениями судей и администрации — то вы можете отказаться от участия в конкурсе, а мы не будем пользоваться вашими трудами, и разойдёмся мы как в море корабли. - - -## Краткие правила - - 1. Работа над непосредственно картой должна проводиться с __26 ноября 2025 года в 12:00 по Москве__ вплоть до __26 декабря 2025 года в 12:00 по Москве__. Можно дорабатывать карту и дальше, но оценивать мы будем только то, что получили в определённый срок - 2. Можно использовать использовать сторонние ресурсы (текстуры, звуки, модели), которыми вы обладаете. Например, купленные с лицензией, встроенные в игру __Half-Life__, либо созданные до или во время проведения конкурса - 3. Все части работы, от геометрии карты и ресурсов, до названия работы и имени/псевдонима самого автора, не должны нарушать законы, действующие на территории РФ. Также убедительно просим обойти стороной аморальные темы *(надеюсь, перечислять не придётся)* - 4. Работать над картой можно как в соло, так и __командой до 4-х человек__. Один участник может быть автором только одной работы, участвовать в разработке нескольких карт запрещено - 5. Отказ от участия и дисквалификация участника запрещает нам, организаторам, использовать его работы в дальнейшем. Отказаться можно __до__ проведения церемонии вознаграждения. _Получил приз и запретил нам использовать свою карту? Ну ты придумал!_ - 6. Последнее слово всегда за администрацией и членами жюри. Ну это так, чтобы уберечь себя от инцидентов на первом же конкурсе! - ------- - -## Формальные правила - -### 1. Основные положения - - 1. Участие в конкурсе определяется отправкой выполненной работы организаторам конкурса в любом [канале связи](/contact) на выбор конкурсанта - 2. Для участия в конкурсе не требуется заблаговременная запись: достаточно отправить работу организаторам до окончания конкурса - 3. До проведения оценки работ и церемонии награждения, участник имеет право отказаться от участия в конкурсе и дальнейшего использования его работ в проектах Tea Sanctuary - 4. Отправляя свою работу на конкурс, вы соглашаетесь с тем, что ваша работа будет безвозмездно использована в рамках сервера Tea Sanctuary DM и прочих проектов объединения Tea Sanctuary - 5. Объединение Tea Sanctuary также имеет право удалить карту со своих ресурсов в случае обнаружения нарушения правил __после__ вознаграждения - 6. Судьи и администрация ресурса имеет право отстранить участника от участия в конкурсе, не называя причину. Дисквалификация эквивалентна добровольному отказу от участия (отсутствие вознаграждения, отправленная работа не будет использоваться в проектах и т.д.) - 7. Отправляя работу под псевдонимом либо реальным именем, вы разрешаете нам использовать его в наших материалах (объявление победителей и прочие упоминания). - -### 2. Требования к работе - - 1. Работой называется архив формата `.zip` или `.7z`, содержащий результаты трудов конкурсанта, оформленные в специальном формате, либо же файл формата `.bsp`, если участник не использовал дополнительных ресурсов - 2. В рамках правил "картой" называется как сам файл карты с расширением `.bsp`, так и сопутствующие ресурсы, требуемые для её работы (файлы текстур, звуки и модели) - 3. Работа над картой должна начаться не раньше __26 ноября 2025 года в 12:00 по Москве__ - 4. Для участия в конкурсе, работа должна быть отправлена организаторам конкурса через доступные каналы связи не позднее __26 декабря 2025 года в 12:00 по Москве__. Допускается продление срока принятия работ, о чём участники будут уведомлены, а данная страница будет обновлена соответствующим образом - 5. Организаторы и члены жюри должны иметь возможность загрузить работу участника бесплатно. О проблемах с доступом со стороны организаторов участник должен быть осведомлён. В случае отсутствия решения проблемы с доступом по истечению срока приёма работ, участник будет дисквалифицирован - 6. Допускается работа над картой в команде составом не более четырёх (4) человек - 7. В период от начала конкурса и до проведения церемонии награждения, автор(ы) обязуются не публиковать выполненную работу на другие конкурсы. Разрешается распространение работы на других ресурсах только при указании того, что она выполнена в рамках конкурса __Tea Sanctuary__. Допускается распространение фото- и видео-материалов, созданных на основе работы. _(например, видео плейтеста с друзьями или скриншот ранней версии)_ Правило не действует после проведения церемонии награждения - 8. Все аспекты работы, в том числе псевдоним автора, не должны нарушать законы, действующие на территории РФ, а также не должны никого оскорблять - 9. Нежелательно встраивание в работу рекламы _(в виде текста, фигур, звуков и т.д.)_. Приветствуется безвозмездные упоминания автора/ов карты, их творческих коллективов и проектов, а также [коллектива Tea Sanctuary](https://teasanctuary.ru). __Категорически запрещена__ реклама казино, проектов, связанных с криптовалютой, а также запрещённых на территории РФ сервисов. Если не уверены в уместности рекламы, обратитесь к организаторам. - -### 3. Технические требования - - 1. Несоответствие карты техническим требованиям ведёт к дисквалификации участника - 2. Непосредственно геометрия уровня (содержимое файла с расширением `.bsp`) должна быть выполнена в объявленных рамках проведения соревнования - 3. Автор карты должен иметь право на использование ресурсов (текстур, моделей, звуков). При возникновении подозрений со стороны организаторов, автор обязан предоставить доказательство права на использование - 4. Автор не может применять технологии генеративного искусственного интеллекта в работе (как для генерации ресурсов, так и для создания непосредственно самой карты) - 5. Рекомендуется разрабатывать карту с рассчётом на то, что на карте могут играть одновременно тридцать два (32) игрока - 6. Карта должна работать на последней Steam-версии __Half-Life 1__ на протяжении конкурса - 7. Работа не должна вносить изменения в ресурсы игры в частности, в данные игрока в целом, а также каким-либо образом нарушать работу игры, сервера и устройства игрока _(одним словом: эксплоиты)_ - 8. Организаторы могут незначительно поменять название карты на своё усмотрение в случае возникновения конфликтов с названиями существующих карт - 9. Если карта использует дополнительные ресурсы, то автор обязан предоставить файл формата `.res` с перечислением всех использованных файлов _(с целью прекеширования и обеспечения работы FastDL)_ - 10. Участник не ограничен в выборе инструментов для выполнения работы за исключением тех, что явно запрещены правилами выше. - -### 4. Начальный отбор - - 1. Начальный отбор проводится организаторами конкурса по прошествию даты окончания конкурса - 2. Организатор обязан провести начальный отбор работ в течение недели с момента начала этапа - 3. В период начального отбора все участники конкурса опрашиваются на подтверждение дальнейшего участия в конкурсе - 4. Для проведения отбора берётся последняя версия файлов, отправленная участником до завершения периода приёма работ - 5. Начальный отбор проводится по критериям, описанным в пункте __2. Требования к работе__ и __3. Технические требования__ - 6. Организатор обязан сообщить участнику о факте прохождения, либо непрохождения начального отбора - 7. Организатор *может, но не обязан* сообщить участнику причину непрохождения начального отбора. - - -От rndtrash: одной из такой причин могут быть слова "экспериментальный юмор", которым трудно дать точное определение. -Скажем, это хаотичное нагромождение текстур, звуков, геометрии и мемов с отсылками, и просто троллинг, -который ну никак не клеится в весёлый геймплей. -

-При всём уважении к далее перечисленным авторам, половина их работ может послужить эталонным примером того, что мы не захотим принять: -CryoKeen, -Robootto -
- -### 5. Оценка работ - - 1. Этап оценки работ начинается сразу после объявления об окончании начального отбора - 2. В оценке работ не могут участвовать лица, отправившие до этого работу на данный конкурс - 3. Участники по запросу _(либо в публичных каналах связи)_ могут узнать состав жюри, оценивающих работы, в течение периода оценки работ - 4. Процесс оценки проводится взакрытую. Итоговый результат участник может узнать после заверешения церемонии награждения - 5. Ранжирование работ и дальнейший выбор призёров проводится на основе общей суммы баллов. Ранжирование проводится до достижения полного консенсуса среди судей и организаторов - 6. Критерии оценки для каждой работы следующие: __Соответствие теме__, __Геймплей__, __Визуал__, __Звуковое сопровождение__. Вес каждого критерия не является публичной информацией - 7. Судьи обязаны ознакомиться с каждой работой в течение двух недель с начала данного этапа. Если судья не оценил хоть одну из присланных работ — его мнение не учитывается при оценке ни одной из работ - 8. В исключительном случае отсутствия оценок от судей, оценка работ проводится непосредственно организаторами - 9. Конечные результаты будут опубликованы публично по окончанию крайнего срока оценки, либо по получению оценок от всех судей. - -### 6. Вознаграждение - - 1. На вознаграждение могут претендовать только участники, прошедшие __начальный отбор__ - 2. У участников после объявления организаторами призёров есть календарная неделя на то, чтобы связаться с организаторами, договориться о выбранном призе, и получить его. По истечению недели без связи приз автоматически считается вручённым - 3. Призёру предлагаются на выбор не более четырёх (4) игр, доступных на площадке Steam от компании Valve. Выбранные игры должны быть покупаемыми с аккаунта, зарегистрированным в РФ, а сумма стоимости всех игр в регионе РФ на момент вручения не должна превышать __две тысячи рублей__ (₽2000) - 4. Если работа над картой велась совместно несколькими участниками, то приз можно разделить между сокомандниками. Сумма стоимости игр для всех участников команды также не должна превышать __две тысячи рублей__ (₽2000) diff --git a/src/lib/components/BlogCard.svelte b/src/lib/components/BlogCard.svelte index aa704c9..dd02d40 100644 --- a/src/lib/components/BlogCard.svelte +++ b/src/lib/components/BlogCard.svelte @@ -10,9 +10,7 @@ import { BLOG_POST_FRESHNESS_MILLIS, blogPostTypeToIcon, - blogPostTypeToString, - EventStatus, - postEventStatus + blogPostTypeToString } from '$lib/util/Blogs'; import DateWidget from '$lib/components/DateWidget.svelte'; import Icon from '@iconify/svelte'; @@ -22,26 +20,21 @@ post, size = BlogCardSize.Both, fullHeight = false - }: { post: App.BlogPost; size?: BlogCardSize; fullHeight?: boolean } = $props(); + }: { post: App.BlogPost; size: BlogCardSize; fullHeight: boolean } = $props(); const type: App.BlogPostType = post.type ?? 'article'; let isPostNew = $state(false); let isPostUpdated = $state(false); let isPostFresh = $derived(isPostNew || isPostUpdated); - let eventStatus: EventStatus | undefined = $state(undefined); - let isPostEventOfInterest = $derived( - type === 'event' && - (eventStatus === EventStatus.NotStarted || eventStatus === EventStatus.InProgress) - ); - let eventHasStarted = $derived(eventStatus === EventStatus.InProgress); + // TODO: rndtrash: события и их актуальность onMount(() => { + // rndtrash: Выполняем проверки на клиенте, чтобы плашка не скомпилировалась и потом не потеряла актуальность const dateNow = new Date().valueOf(); isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS; isPostUpdated = post.dateChanged != null && dateNow - new Date(post.dateChanged).valueOf() <= BLOG_POST_FRESHNESS_MILLIS; - eventStatus = postEventStatus(post); }); /** @@ -78,15 +71,7 @@ href="/blog/{post.slug}" class="blog-card {fullHeight ? 'min-h-full' : ''} - {isPostEventOfInterest - ? eventHasStarted - ? 'eventOngoing' - : 'eventPreStart' - : isPostUpdated - ? 'updated' - : isPostNew - ? 'new' - : ''} + {isPostUpdated ? 'updated' : isPostNew ? 'new' : ''} {shortClass('flex-col justify-baseline')} {fullClass('flex-row justify-stretch', 'sm:flex-row sm:justify-stretch')}" > @@ -102,15 +87,7 @@ alt={post.thumbnailAlt ?? 'Миниатюра поста'} /> {/if} - {#if isPostEventOfInterest} -
- {#if eventStatus === EventStatus.NotStarted} - СКОРО НАЧАЛО - {:else} - СОБЫТИЕ - {/if} -
- {:else if isPostFresh} + {#if isPostFresh}
{#if isPostUpdated} ОБНОВЛЕНО @@ -122,40 +99,25 @@
- {#if isPostEventOfInterest} + + {#if post.dateChanged} - - {:else} - - {#if post.dateChanged} - - {/if} -
- - - {blogPostTypeToString(type)} - -
{/if} +
+ + + {blogPostTypeToString(type)} + +

{post.title}

@@ -177,7 +139,7 @@ } &.updated { - box-shadow: 0 0 0 calc(var(--spacing) * 1) var(--color-purple-600); + box-shadow: 0 0 0 4px var(--color-purple-600); .toast { @apply bg-purple-600; @@ -185,29 +147,13 @@ } &.new { - box-shadow: 0 0 0 calc(var(--spacing) * 1) var(--color-amber-600); + box-shadow: 0 0 0 4px var(--color-amber-600); .toast { @apply bg-amber-600; } } - &.eventPreStart { - box-shadow: 0 0 0 calc(var(--spacing) * 1) var(--color-rose-600); - - .toast { - @apply bg-rose-600; - } - } - - &.eventOngoing { - box-shadow: 0 0 0 calc(var(--spacing) * 1) var(--color-teal-600); - - .toast { - @apply bg-teal-600; - } - } - img.thumbnail { @apply absolute h-full w-full object-cover transition-transform; diff --git a/src/lib/components/CountdownClock.svelte b/src/lib/components/CountdownClock.svelte deleted file mode 100644 index 71c95c9..0000000 --- a/src/lib/components/CountdownClock.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - -
- {#if daysLeft > 0} -
{dateDurationLong.format({ days: daysLeft })}
- {/if} -
-
{hoursLeft[0]}
-
{hoursLeft[1]}
-
:
-
{minutesLeft[0]}
-
{minutesLeft[1]}
-
:
-
{secondsLeft[0]}
-
{secondsLeft[1]}
-
-
- - diff --git a/src/lib/components/DateWidget.svelte b/src/lib/components/DateWidget.svelte index 90065a3..69ab90d 100644 --- a/src/lib/components/DateWidget.svelte +++ b/src/lib/components/DateWidget.svelte @@ -1,49 +1,32 @@
- {#if icon} - - {/if} + {dateString - ? (showTime ? dateFormatLong : dateFormatShort).format(new Date(dateString)) + ? new Date(dateString).toLocaleString(undefined, { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'Не опубликован!'}
diff --git a/src/lib/util/Blogs.ts b/src/lib/util/Blogs.ts index 223ca87..5878e26 100644 --- a/src/lib/util/Blogs.ts +++ b/src/lib/util/Blogs.ts @@ -1,51 +1,20 @@ export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp"; export const BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня -export enum EventStatus { - NotEvent = 0, - NotStarted, - InProgress, - IsOver -} - -export function postIsEventOfInterest(post: App.BlogPost): boolean { - const status = postEventStatus(post); - return status === EventStatus.NotStarted || status === EventStatus.InProgress; -} - -export function postEventStatus(post: App.BlogPost): EventStatus { - if (post.type !== 'event' || post.dateEventFrom === undefined || post.dateEventTo === undefined) { - return EventStatus.NotEvent; - } - - const currentTime = new Date().valueOf(); - const eventStart = new Date(post.dateEventFrom).valueOf(); - if (currentTime < eventStart) return EventStatus.NotStarted; - - const eventEnd = new Date(post.dateEventTo).valueOf(); - if (currentTime < eventEnd) return EventStatus.InProgress; - - return EventStatus.IsOver; -} - export type PostComparer = (a: App.BlogPost, b: App.BlogPost) => number; export const sortPostsByPostDate: PostComparer = (a, b) => new Date(b.date!).valueOf() - new Date(a.date!).valueOf(); -export const sortPostsByPostAndUpdateDate: PostComparer = (a, b) => laterDate(b.date!, b.dateChanged) - laterDate(a.date!, a.dateChanged); -function laterDate(a: string, ...dates: (string | undefined)[]): number { +function laterDate(a: string, b?: string): number { const dateA = new Date(a).valueOf(); - if (dates.length <= 0) return dateA; + if (!b) return dateA; - let max = dateA; - for (const d of dates) { - if (!d) continue; - const date = new Date(d).valueOf(); - max = Math.max(max, date); - }; - return max; + const dateB = new Date(b).valueOf(); + return Math.max(dateA, dateB); } +export const sortPostsByPostAndUpdateDate: PostComparer = (a, b) => laterDate(b.date!, b.dateChanged) - laterDate(a.date!, a.dateChanged); + export async function fetchPostsSorted(postComparer?: PostComparer) { const allPosts = await fetchPosts(); diff --git a/src/lib/util/Dates.ts b/src/lib/util/Dates.ts deleted file mode 100644 index bcf9e71..0000000 --- a/src/lib/util/Dates.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { shouldPolyfill } from '@formatjs/intl-durationformat/should-polyfill' -if (shouldPolyfill()) { - import('@formatjs/intl-durationformat/polyfill-force'); -} - -export const dateFormatShort = new Intl.DateTimeFormat(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric' -}); - -export const dateFormatLong = new Intl.DateTimeFormat(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' -}); - -export const dateDurationLong = new Intl.DurationFormat(undefined, { - style: "long" -}); - -export const dateDurationLongBackup = new Intl.DurationFormat(undefined, { - style: "long", - seconds: "long" -}); - -export const durationHumanReadable = (a: Date, b: Date) => durationHumanReadableMillis(b.valueOf() - a.valueOf()); - -export function durationHumanReadableMillis(dur: number): string { - dur = Math.max(dur, 0); - const seconds = dur / 1000; - const minutes = dur / (1000 * 60); - const hours = dur / (1000 * 60 * 60); - const days = dur / (1000 * 60 * 60 * 24); - - const formatObject = { days: Math.floor(days), hours: Math.floor(hours) % 24, minutes: Math.floor(minutes) % 60, seconds: Math.floor(seconds) % 60 }; - const result = dateDurationLong.format(formatObject); - // Если на выходе получаем пустую строку, то хоть выведем 0 секунд - return result === '' ? dateDurationLongBackup.format(formatObject) : result; -} diff --git a/src/lib/util/LinkResolver.ts b/src/lib/util/LinkResolver.ts index 2e138d5..9ada1da 100644 --- a/src/lib/util/LinkResolver.ts +++ b/src/lib/util/LinkResolver.ts @@ -2,8 +2,6 @@ const icons: Record = { none: 'material-symbols:link', 'steamcommunity.com': 'simple-icons:steam', - 'steampowered.com': 'simple-icons:steam', - 't.me': 'simple-icons:telegram', 'twitter.com': 'simple-icons:x', 'x.com': 'simple-icons:x', 'github.com': 'simple-icons:github', @@ -46,9 +44,7 @@ const specialResolvers: Record string> = { // Игнорируем имя пользователя 'bsky.app': (url) => 'bsky.app', 'bsky.social': (url) => 'bsky.social', - 'itch.io': (url) => 'itch.io', - 'steamcommunity.com': (url) => 'steamcommunity.com', - 'steampowered.com': (url) => 'steampowered.com', + 'itch.io': (url) => 'itch.io' } function getIconFromUrl(url: URL): string | undefined { diff --git a/src/pages/index.md b/src/pages/index.md index ae91af5..e206d50 100644 --- a/src/pages/index.md +++ b/src/pages/index.md @@ -36,7 +36,4 @@ __Tea Sanctuary__ — это также и сообщество едином Общие вопросы можно задавать в [сообществе Tea Sanctuary](https://teasanctuary.ru/discord). Там же можете написать личное сообщение администраторам. -Есть и другие способы следить за нашими новостями — например, [канал в Telegram](https://t.me/tea_sanctuary), -с комментариями и отдельным чатом. - Наши соцсети и почту для более важных обращений можно найти на странице [Контакты](/contact). diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a0b9cab..8afa883 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -25,7 +25,6 @@ property="og:image" content={page.data.thumbnail ?? 'https://teasanctuary.ru/common/logo.png'} /> - diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f6024d4..c03fc29 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,24 +1,11 @@ @@ -80,7 +67,6 @@
Сообщество - Канал GitHub @@ -98,14 +84,12 @@
-

ПОСЛЕДНИЕ ПОСТЫ

+

ПОСЛЕДНИЕ ПОСТЫ

- {#each posts as post, i} + {#each page.data.posts as post, i}
- {#key post} - - {/key} +
{/each}
@@ -129,13 +113,13 @@ prose-p:mt-0 prose-p:mb-8 bg-slate-50 - px-2 pt-8 pb-4 text-base - text-slate-950 sm:px-4 sm:text-xl" + text-slate-950 + px-2 sm:px-4 sm:text-xl" > -
+
- + \ No newline at end of file diff --git a/src/routes/+page.ts b/src/routes/+page.ts index a94c88b..e430cc1 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,6 +1,6 @@ import { fetchPostsSorted, sortPostsByPostAndUpdateDate } from "$src/lib/util/Blogs"; -const LATEST_POSTS_COUNT = 5; +const LATEST_POSTS_COUNT = 3; export async function load() { let md: any diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts index 916e748..c5653b5 100644 --- a/src/routes/blog/+page.server.ts +++ b/src/routes/blog/+page.server.ts @@ -1,4 +1,4 @@ -import { fetchPostsSorted } from "$lib/util/Blogs"; +import { fetchPostsSorted } from "$src/lib/util/Blogs"; export async function load() { return { title: "Блог", description: "Новости и заметки проектов Tea Sanctuary", posts: await fetchPostsSorted() }; diff --git a/src/routes/blog/+page.svelte b/src/routes/blog/+page.svelte index 3eed89a..da243da 100644 --- a/src/routes/blog/+page.svelte +++ b/src/routes/blog/+page.svelte @@ -1,10 +1,8 @@
@@ -54,20 +39,6 @@ пропускать новые посты! -{#if !!eventPosts && eventPosts.length > 0} -
-

АКТУАЛЬНЫЕ СОБЫТИЯ

- -
- {#each eventPosts as post, i} -
- -
- {/each} -
-
-{/if} -
{#each groupedPosts.entries() as [monthYear, postsInMonthYear]}

{/each} -

+
\ No newline at end of file diff --git a/src/routes/blog/[slug]/+page.svelte b/src/routes/blog/[slug]/+page.svelte index 2013493..0b0b105 100644 --- a/src/routes/blog/[slug]/+page.svelte +++ b/src/routes/blog/[slug]/+page.svelte @@ -1,20 +1,12 @@ @@ -97,50 +63,6 @@ {/each}
-{#if type === 'event'} -
- {#if eventStatus === EventStatus.NotStarted || eventStatus === EventStatus.InProgress} - - ДО {eventStatus === EventStatus.NotStarted ? 'НАЧАЛА' : 'КОНЦА'} ОСТАЛОСЬ - - - {:else if eventStatus === EventStatus.IsOver} - СОБЫТИЕ ЗАВЕРШЕНО - {/if} - - Событие {eventStatus === EventStatus.IsOver ? 'проводилось' : 'проводится'} с - - по - - ({durationHumanReadable( - new Date(data.blogPost.dateEventFrom!), - new Date(data.blogPost.dateEventTo!) - )}) - -
-{/if} - {#if page.data.blogPost.projects?.length > 0}

В данной заметке упоминаются наши проекты:

@@ -171,4 +93,4 @@ sm:text-xl lg:p-8" > - + \ No newline at end of file diff --git a/src/routes/blog/rss.xml/+server.ts b/src/routes/blog/rss.xml/+server.ts index ac85b3f..52aa732 100644 --- a/src/routes/blog/rss.xml/+server.ts +++ b/src/routes/blog/rss.xml/+server.ts @@ -1,4 +1,4 @@ -import { EventStatus, fetchPostsSorted, postEventStatus, resolveBlogPath } from "$lib/util/Blogs"; +import { fetchPostsSorted, resolveBlogPath } from "$src/lib/util/Blogs"; export const prerender = true; @@ -41,14 +41,6 @@ function makeAuthors(post: App.BlogPost): string { return `\n${authorsString}`; } -function makeEventDescription(post: App.BlogPost): string { - if (postEventStatus(post) === EventStatus.NotEvent) - return ''; - - const dateToUtcString = (s: string) => new Date(s).toUTCString(); - return `

Событие проводится с ${dateToUtcString(post.dateEventFrom!)} по ${dateToUtcString(post.dateEventFrom!)}.`; -} - export async function GET({ setHeaders }) { setHeaders({ 'Cache-Control': 'max-age=0, s-maxage=3600', @@ -65,7 +57,7 @@ export async function GET({ setHeaders }) { 1800 ${posts.map((post) => ` ${escapeXml(post.title)} -${makeAuthors(post)} +${makeAuthors(post)} https://teasanctuary.ru/blog/${post.slug} https://teasanctuary.ru/blog/${post.slug} ${(new Date(post.date!)).toUTCString()} diff --git a/src/routes/contact/+page.svelte b/src/routes/contact/+page.svelte index 92dd560..4b8c87e 100644 --- a/src/routes/contact/+page.svelte +++ b/src/routes/contact/+page.svelte @@ -1,4 +1,5 @@ @@ -14,31 +15,17 @@
+Страница находится в разработке! +
- На данный момент вы можете связаться с администрацией сайта и участниками команды - следующими способами: -
    -
  1. - Через - - нашу гильдию в Discord - -
  2. -
  3. - Через - - публичный канал Tea Sanctuary в Telegram - -
  4. -
  5. - По персональным контактам, указанным на странице - Команда. -
  6. -
+ На данный момент вы можете связаться с администрацией сайта и участниками команды через + + нашу гильдию в Discord + :
+
+ Вы также можете ознакомиться с социальными сетями каждого отдельного участника команды + на странице Команда. +
-
+ \ No newline at end of file diff --git a/static/blog/tsmc_1/challenge_teaser.webp b/static/blog/tsmc_1/challenge_teaser.webp deleted file mode 100644 index 38c448a..0000000 Binary files a/static/blog/tsmc_1/challenge_teaser.webp and /dev/null differ diff --git a/static/blog/tsmc_1/poster.webp b/static/blog/tsmc_1/poster.webp deleted file mode 100644 index f0275e2..0000000 Binary files a/static/blog/tsmc_1/poster.webp and /dev/null differ