mirror of
https://github.com/StepanovPlaton/AboutMe.git
synced 2026-04-04 04:40:51 +04:00
Update content and logic
This commit is contained in:
@@ -40,12 +40,19 @@ collections:
|
||||
- { label: "Image", name: "image", widget: "image", required: false }
|
||||
- { label: "Category", name: "category", widget: "select", options: ["actual","history","other"] }
|
||||
- { label: "Tech Stack", name: "techStack", widget: "list", default: [] }
|
||||
- { label: "Status", name: "status", widget: "select", options: ["completed","in-progress","planned","paused"] }
|
||||
- label: "Status"
|
||||
name: "status"
|
||||
widget: "select"
|
||||
options: ["completed","in-progress","planned","paused"]
|
||||
- { label: "Live Demo", name: "liveDemo", widget: "string", required: false }
|
||||
- { label: "Source Code", name: "sourceCode", widget: "string", required: false }
|
||||
- { label: "Start Date", name: "startDate", widget: "datetime" }
|
||||
- { label: "End Date", name: "endDate", widget: "datetime", required: false }
|
||||
- { label: "Featured", name: "featured", widget: "boolean", required: false, default: false }
|
||||
- label: "Featured"
|
||||
name: "featured"
|
||||
widget: "boolean"
|
||||
required: false
|
||||
default: false
|
||||
- { label: "Tags", name: "tags", widget: "list", required: false, default: [] }
|
||||
|
||||
- name: "skills"
|
||||
|
||||
@@ -133,16 +133,31 @@ const sizeClasses = getSizeClasses(size);
|
||||
</p>
|
||||
<!-- 技术栈 -->
|
||||
{project.techStack && project.techStack.length > 0 && (
|
||||
<div class="flex flex-wrap gap-1 mb-4">
|
||||
<div class="flex flex-wrap gap-1 mb-4" data-tech-stack-container data-max-visible={maxTechStack}>
|
||||
{project.techStack.slice(0, maxTechStack).map((tech) => (
|
||||
<span class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.tech}`}>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
{project.techStack.length > maxTechStack && (
|
||||
<span class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400 rounded-sm ${sizeClasses.tech}`}>
|
||||
+{project.techStack.length - maxTechStack}
|
||||
</span>
|
||||
<>
|
||||
{project.techStack.slice(maxTechStack).map((tech) => (
|
||||
<span
|
||||
class={`hidden px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.tech} tech-stack-hidden`}
|
||||
data-tech-hidden
|
||||
>
|
||||
{tech}
|
||||
</span>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400 rounded-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors ${sizeClasses.tech}`}
|
||||
data-tech-stack-toggle
|
||||
data-hidden-count={project.techStack.length - maxTechStack}
|
||||
>
|
||||
+{project.techStack.length - maxTechStack}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -181,3 +196,38 @@ const sizeClasses = getSizeClasses(size);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Раскрытие списка технологий при клике на "+"
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggleButtons = document.querySelectorAll('[data-tech-stack-toggle]');
|
||||
|
||||
toggleButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const container = button.closest('[data-tech-stack-container]');
|
||||
const hiddenElements = container?.querySelectorAll('[data-tech-hidden]');
|
||||
const hiddenCount = parseInt(button.getAttribute('data-hidden-count') || '0');
|
||||
|
||||
if (hiddenElements && hiddenElements.length > 0) {
|
||||
const isExpanded = !hiddenElements[0].classList.contains('hidden');
|
||||
|
||||
if (isExpanded) {
|
||||
// Сворачиваем: скрываем все скрытые элементы
|
||||
hiddenElements.forEach(el => {
|
||||
el.classList.add('hidden');
|
||||
});
|
||||
button.textContent = `+${hiddenCount}`;
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
// Раскрываем: показываем все скрытые элементы
|
||||
hiddenElements.forEach(el => {
|
||||
el.classList.remove('hidden');
|
||||
});
|
||||
button.textContent = '−';
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -254,12 +254,32 @@ const itemColor = item.color || "#3B82F6";
|
||||
<!-- 技能 -->
|
||||
{item.skills && item.skills.length > 0 && (
|
||||
<div class="mb-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{item.skills.map((skill) => (
|
||||
<div class="flex flex-wrap gap-1" data-skills-container>
|
||||
{item.skills.slice(0, 3).map((skill) => (
|
||||
<span class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.badge}`}>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
{item.skills.length > 3 && (
|
||||
<>
|
||||
{item.skills.slice(3).map((skill) => (
|
||||
<span
|
||||
class={`hidden px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.badge} skills-hidden`}
|
||||
data-skill-hidden
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400 rounded-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors ${sizeClasses.badge}`}
|
||||
data-skills-toggle
|
||||
data-hidden-count={item.skills.length - 3}
|
||||
>
|
||||
+{item.skills.length - 3}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -353,16 +373,31 @@ const itemColor = item.color || "#3B82F6";
|
||||
<!-- 技能和链接 -->
|
||||
<div class="flex items-center justify-between">
|
||||
{item.skills && item.skills.length > 0 && (
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div class="flex flex-wrap gap-1" data-skills-container>
|
||||
{item.skills.slice(0, 3).map((skill) => (
|
||||
<span class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.badge}`}>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
{item.skills.length > 3 && (
|
||||
<span class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400 rounded-sm ${sizeClasses.badge}`}>
|
||||
+{item.skills.length - 3}
|
||||
</span>
|
||||
<>
|
||||
{item.skills.slice(3).map((skill) => (
|
||||
<span
|
||||
class={`hidden px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-sm ${sizeClasses.badge} skills-hidden`}
|
||||
data-skill-hidden
|
||||
>
|
||||
{skill}
|
||||
</span>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
class={`px-2 py-1 bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400 rounded-sm cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors ${sizeClasses.badge}`}
|
||||
data-skills-toggle
|
||||
data-hidden-count={item.skills.length - 3}
|
||||
>
|
||||
+{item.skills.length - 3}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -383,4 +418,41 @@ const itemColor = item.color || "#3B82F6";
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
<script>
|
||||
// Раскрытие списка навыков при клике на "+"
|
||||
// Используем делегирование событий для надёжности
|
||||
document.addEventListener('click', (e) => {
|
||||
const button = (e.target as HTMLElement).closest('[data-skills-toggle]') as HTMLElement;
|
||||
if (!button) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const container = button.closest('[data-skills-container]');
|
||||
if (!container) return;
|
||||
|
||||
const hiddenElements = container.querySelectorAll('[data-skill-hidden]');
|
||||
if (hiddenElements.length === 0) return;
|
||||
|
||||
const hiddenCount = parseInt(button.getAttribute('data-hidden-count') || '0');
|
||||
const isExpanded = !hiddenElements[0].classList.contains('hidden');
|
||||
|
||||
if (isExpanded) {
|
||||
// Сворачиваем: скрываем все скрытые элементы
|
||||
hiddenElements.forEach(el => {
|
||||
el.classList.add('hidden');
|
||||
});
|
||||
button.textContent = `+${hiddenCount}`;
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
// Раскрываем: показываем все скрытые элементы
|
||||
hiddenElements.forEach(el => {
|
||||
el.classList.remove('hidden');
|
||||
});
|
||||
button.textContent = '−';
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -3,7 +3,6 @@ title: NextCloud на домашнем сервере с офисным паке
|
||||
published: 2026-02-12T19:26:00.000+04:00
|
||||
cover: /images/nextcloud.png
|
||||
tags:
|
||||
- DevOps
|
||||
- Linux
|
||||
draft: false
|
||||
---
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sourceCode": "https://git.stepanovplaton.ru/StepanovPlaton/HomeServerServices",
|
||||
"featured": true,
|
||||
"featured": false,
|
||||
"liveDemo": "",
|
||||
"startDate": "2025-12-10T08:00:00.000+04:00",
|
||||
"techStack": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sourceCode": "https://git.stepanovplaton.ru/StepanovPlaton/jelly_belly_wiki",
|
||||
"featured": false,
|
||||
"featured": true,
|
||||
"liveDemo": "https://jelly-belly-wiki.vercel.app",
|
||||
"startDate": "2024-07-08T08:00:00.000+04:00",
|
||||
"techStack": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sourceCode": "https://git.stepanovplaton.ru/StepanovPlaton/NeuralNetwork",
|
||||
"featured": false,
|
||||
"featured": true,
|
||||
"startDate": "2025-10-26T08:00:00.000+04:00",
|
||||
"techStack": [
|
||||
"C++",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sourceCode": "https://git.stepanovplaton.ru/StepanovPlaton/OSin1000Lines",
|
||||
"featured": true,
|
||||
"featured": false,
|
||||
"startDate": "2026-02-03T08:00:00.000+04:00",
|
||||
"techStack": [
|
||||
"C"
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
# Это интересно!
|
||||
|
||||
Раз уж у меня появился свой уголок в интернете, я посчитал нужным поделиться с вами классными проектами других людей. Здесь вы найдёте и другие сайты, и книги, и видеоигры, от популярных до нишевых — **всё, что я хочу показать миру**.
|
||||
Раз уж у меня появился свой уголок в интернете, решил поделиться с тобой классными проектами других людей. Здесь ты найдёшь и другие сайты, и книги, и видеоигры, от популярных до нишевых — **всё, что я хочу показать миру**. Надеюсь, тебе будет интересно!
|
||||
|
||||
## О дварфах!
|
||||
|
||||
История моей аватарки (стилистики этого сайта, моего сервера и много чего ещё) берёт своё начало с **[легендарной игры DwarfFortress](http://www.bay12games.com/dwarves/)**. Эта игра для меня пример **гениальности, усидчивости и абсурда** в одном лице.
|
||||
|
||||
<details>
|
||||
<summary>Я не могу советовать её вам (потому что это игра не для всех), но был обязан упомянуть на своём сайте</summary>
|
||||
<summary>Я не могу советовать её тебе (потому что это игра не для всех), но был обязан упомянуть на своём сайте</summary>
|
||||
|
||||
Её движок настолько сложен, что долгое время к нему не могли добавить графику. За **более чем 20 лет разработки** она готова менее чем наполовину, её делают два брата на пожертвования от фанатов, и при всём этом ей вдохновлялись создатели Minecraft и Rimworld, а в 2013 Нью-Йоркский музей современного искусства включил игру в свою коллекцию.
|
||||
|
||||
Это лучший симулятор историй. Обязательно почитайте [историю крепости Боутмёрдед](https://dtf.ru/games/22946-legendy-dwarf-fortress-saga-o-padenii-kreposti-boutmerded) — эти легенды тянут на эпичный фильм от Netflix, но это просто обычная игра в DwarfFortress. Да, здесь можно тренировать дварфов бегать на костылях быстрее, чем на ногах, стреляя в них монетками. Да, на вас может напасть огромная адская автоматически сгенерированная... попа. И да, ваши дварфы могут умереть не только от гоблинов, но и от кошек, безумия, миазмов, одинаковой выпивки и даже сушняка...
|
||||
Это лучший симулятор историй. Обязательно почитай [историю крепости Боутмёрдед](https://dtf.ru/games/22946-legendy-dwarf-fortress-saga-o-padenii-kreposti-boutmerded) — эти легенды тянут на эпичный фильм от Netflix, но это просто обычная игра в DwarfFortress. Да, здесь можно тренировать дварфов бегать на костылях быстрее, чем на ногах, стреляя в них монетками. Да, на тебя может напасть огромная адская автоматически сгенерированная... попа. И да, твои дварфы могут умереть не только от гоблинов, но и от кошек, безумия, миазмов, одинаковой выпивки и даже сушняка...
|
||||
|
||||
Эта игра не для всех, но я боюсь в неё заходить, потому что потом не могу выйти.
|
||||
|
||||
И помните... Проигрывать весело!
|
||||
И помни... Проигрывать весело!
|
||||
</details>
|
||||
|
||||
## Карандаш и Самоделкин
|
||||
|
||||
У всякой истории есть начало. Я начал свою историю отсюда: [Блог команды "Карандаш и Самоделкин"](https://karandashsamodelkin.blogspot.com). Передавайте привет маленькому мне и **большое спасибо моему отцу**!
|
||||
У всякой истории есть начало. Я начал свою историю отсюда: [Блог команды "Карандаш и Самоделкин"](https://karandashsamodelkin.blogspot.com). Передай привет маленькому мне и **огромное спасибо моему отцу**!
|
||||
|
||||
## Классные сайты
|
||||
|
||||
Интернет огромен, и в нём куча страниц. Некоторые популярны, другие не очень, но эти особенно интересны:
|
||||
Интернет огромен, и в нём куча страниц. Некоторые популярны, другие не очень, но эти особенно зацепили меня:
|
||||
|
||||
+ [Онлайн книга Linux From Scratch](https://linuxfromscratch.org) — бесплатное руководство по созданию своей GNU/Linux системы из исходного кода (с нуля). Лучшее развлечение на вечер для админа.
|
||||
+ [Этаж 796](https://floor796.com) — проект русского художника, который объединил на 796 этаже космической станции всех самых знаковых персонажей из мемов, фильмов, комиксов и сериалов. Тут залип на 2 часа...
|
||||
+ [SCP Foundation](https://scpfoundation.net) — открытая научно-фантастическая онлайн вселенная. Мурашки по коже, невозможно оторваться. Обязательно прочтите [SCP-079 — Старый ИИ](https://scpfoundation.net/scp-079) и [Хаб отдела антимеметики](https://scpfoundation.net/antimemetics-division-hub).
|
||||
+ [CashGo](https://cashgo.ru) — онлайн игра, тренажёр финансового интеллекта. Здесь я успел ухватить кусочек старого интернета с ламповыми форумами. Спасибо за детство, передавайте привет Оксюше, Пингвинатко, Успеху и Лису!
|
||||
+ [SCP Foundation](https://scpfoundation.net) — открытая научно-фантастическая онлайн вселенная. Мурашки по коже, невозможно оторваться. Обязательно прочитай [SCP-079 — Старый ИИ](https://scpfoundation.net/scp-079) и [Хаб отдела антимеметики](https://scpfoundation.net/antimemetics-division-hub).
|
||||
+ [CashGo](https://cashgo.ru) — онлайн игра, тренажёр финансового интеллекта. Здесь я успел ухватить кусочек старого интернета с ламповыми форумами. Спасибо за детство, передай привет Оксюше, Пингвинатко, Успеху и Лису!
|
||||
+ [Неолурк](https://neolurk.org) — народная википедия.
|
||||
|
||||
## IT
|
||||
@@ -38,7 +38,7 @@
|
||||
+ [KISS](https://ru.wikipedia.org/wiki/KISS_(принцип)) — делай проще, тупица.
|
||||
+ [Быстрый обратный квадратный корень](https://ru.wikipedia.org/wiki/Быстрый_обратный_квадратный_корень) — магическая функция приближённого вычисления $1/\sqrt{x}$ из Quake.
|
||||
+ [В††](https://neolurk.org/wiki/В%2B%2B) — язык программирования русских богатырей.
|
||||
+ [Suckless](https://suckless.org) — эталон минимализма. Обязательно зацените [dwm](https://dwm.suckless.org) и [st](https://st.suckless.org).
|
||||
+ [Suckless](https://suckless.org) — эталон минимализма. Обязательно зацени [dwm](https://dwm.suckless.org) и [st](https://st.suckless.org).
|
||||
+ [9600 бод и все-все-все](https://lib.ru/ANEKDOTY/9600.txt) — Винни Пух стал хакером времён FIDONET.
|
||||
+ [Nand2Tetris](https://www.nand2tetris.org) — собираем 16-битный компьютер из логических блоков, пишем компилятор, операционную систему и видеоигры. Курс от MIT.
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
## Книги
|
||||
|
||||
Я не очень люблю читать книги, но просто обожаю слушать их аудиоверсии. Это книги которые кажутся мне классными. Я специально не стал писать их в список, иначе просто не смог бы определить порядок. Некоторые из них полезные, некоторые развлекательные, некоторые известные, другие нет. Может быть ты найдёшь здесь что-то для себя!
|
||||
Я не очень люблю читать книги, но просто обожаю слушать их аудиоверсии. Это книги, которые кажутся мне классными. Я специально не стал писать их в список по порядку, иначе просто не смог бы определить, какая лучше. Некоторые из них полезные, некоторые развлекательные, некоторые известные, другие нет. Может быть, ты найдёшь здесь что-то для себя!
|
||||
|
||||
| | | |
|
||||
|---|---|---|
|
||||
@@ -94,7 +94,7 @@
|
||||
## Музыка
|
||||
|
||||
+ [ГРОТ](https://grotmusic.ru) - не просто музыка, а текст и глубокий смысл.
|
||||
+ [HTP](https://vk.com/nii_rap) - если вы из IT просто полистайте, это весело.
|
||||
+ [HTP](https://vk.com/nii_rap) - если ты из IT просто полистай, это весело.
|
||||
|
||||
## Кумиры
|
||||
|
||||
@@ -106,6 +106,6 @@
|
||||
|
||||
## Разное
|
||||
|
||||
Это просто классные вещи, зацените их!
|
||||
Это просто классные вещи, зацени их!
|
||||
|
||||
+ [Цикада 3301](https://habr.com/ru/companies/ruvds/articles/714806/) — одна из самых больших загадок интернета 2010-х.
|
||||
@@ -87,6 +87,7 @@ enum I18nKey {
|
||||
projectsInProgress = "projectsInProgress",
|
||||
projectsTechStack = "projectsTechStack",
|
||||
projectsFeatured = "projectsFeatured",
|
||||
projectsInWork = "projectsInWork",
|
||||
projectsPlanned = "projectsPlanned",
|
||||
projectsPaused = "projectsPaused",
|
||||
projectsDemo = "projectsDemo",
|
||||
|
||||
@@ -90,7 +90,8 @@ export const en: Translation = {
|
||||
[Key.projectsCompleted]: "Completed",
|
||||
[Key.projectsInProgress]: "In Progress",
|
||||
[Key.projectsTechStack]: "Tech Stack Statistics",
|
||||
[Key.projectsFeatured]: "Featured Projects",
|
||||
[Key.projectsFeatured]: "Must See Projects",
|
||||
[Key.projectsInWork]: "Currently Working On",
|
||||
[Key.projectsPlanned]: "Planned",
|
||||
[Key.projectsPaused]: "Paused",
|
||||
[Key.projectsDemo]: "Live Demo",
|
||||
|
||||
@@ -90,7 +90,8 @@ export const ja: Translation = {
|
||||
[Key.projectsCompleted]: "完了",
|
||||
[Key.projectsInProgress]: "進行中",
|
||||
[Key.projectsTechStack]: "技術スタック統計",
|
||||
[Key.projectsFeatured]: "注目プロジェクト",
|
||||
[Key.projectsFeatured]: "必見プロジェクト",
|
||||
[Key.projectsInWork]: "現在開発中",
|
||||
[Key.projectsPlanned]: "予定",
|
||||
[Key.projectsPaused]: "一時停止",
|
||||
[Key.projectsDemo]: "ライブデモ",
|
||||
|
||||
@@ -89,7 +89,8 @@ export const ru: Translation = {
|
||||
[Key.projectsCompleted]: "Завершён",
|
||||
[Key.projectsInProgress]: "В разработке",
|
||||
[Key.projectsTechStack]: "Статистика технологий",
|
||||
[Key.projectsFeatured]: "Сейчас я работаю над этим",
|
||||
[Key.projectsFeatured]: "Обязательно посмотрите эти проекты",
|
||||
[Key.projectsInWork]: "Сейчас я работаю над этим",
|
||||
[Key.projectsPlanned]: "Запланированных",
|
||||
[Key.projectsPaused]: "Приостановлен",
|
||||
[Key.projectsDemo]: "Демо",
|
||||
|
||||
@@ -90,7 +90,8 @@ export const zh: Translation = {
|
||||
[Key.projectsCompleted]: "已完成",
|
||||
[Key.projectsInProgress]: "进行中",
|
||||
[Key.projectsTechStack]: "技术栈统计",
|
||||
[Key.projectsFeatured]: "精选项目",
|
||||
[Key.projectsFeatured]: "必看项目",
|
||||
[Key.projectsInWork]: "正在开发中",
|
||||
[Key.projectsPlanned]: "计划中",
|
||||
[Key.projectsPaused]: "已暂停",
|
||||
[Key.projectsDemo]: "在线演示",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getProjectStats,
|
||||
getProjectsByCategory,
|
||||
getFeaturedProjects,
|
||||
getInWorkProjects,
|
||||
getAllTechStack,
|
||||
} from "@utils/projects";
|
||||
import { UNCATEGORIZED } from "@constants/constants";
|
||||
@@ -25,6 +26,7 @@ const subtitle = LinkPresets[LinkPreset.Projects].description;
|
||||
// 获取项目统计信息
|
||||
const stats = getProjectStats();
|
||||
const featuredProjects = getFeaturedProjects();
|
||||
const inWorkProjects = getInWorkProjects();
|
||||
const allTechStack = getAllTechStack();
|
||||
|
||||
// 定义分类顺序
|
||||
@@ -105,6 +107,22 @@ const getCategoryText = (category: string) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<!-- In-work проекты -->
|
||||
{inWorkProjects.length > 0 && (
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4">
|
||||
{i18n(I18nKey.projectsInWork)}
|
||||
<span class="text-lg font-normal text-black/60 dark:text-white/60 ml-2">
|
||||
({inWorkProjects.length})
|
||||
</span>
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{inWorkProjects.map((project) => (
|
||||
<ProjectCard project={project} size="large" showImage={true} maxTechStack={4} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<!-- 按分类展示项目 -->
|
||||
<div class="space-y-8">
|
||||
{categories.map((category) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import { getCategoryUrl } from "@utils/url";
|
||||
import { i18n } from "@i18n/translation";
|
||||
import I18nKey from "@i18n/i18nKey";
|
||||
import { UNCATEGORIZED } from "@constants/constants";
|
||||
|
||||
|
||||
// // Retrieve posts and sort them by publication date
|
||||
@@ -110,7 +111,12 @@ export async function getCategoryList(): Promise<Category[]> {
|
||||
});
|
||||
|
||||
const ret: Category[] = [];
|
||||
const uncategorizedKey = i18n(I18nKey.uncategorized);
|
||||
for (const c of lst) {
|
||||
// Исключаем категорию "Без категории" из списка
|
||||
if (c === uncategorizedKey) {
|
||||
continue;
|
||||
}
|
||||
ret.push({
|
||||
name: c,
|
||||
count: count[c],
|
||||
|
||||
@@ -58,6 +58,9 @@ export const getProjectsByCategory = (category?: string) => {
|
||||
} else {
|
||||
filteredProjects = projectsData.filter((p) => p.category === category);
|
||||
}
|
||||
// Exclude featured projects and in-progress projects from category lists
|
||||
// (they are shown in their dedicated sections)
|
||||
filteredProjects = filteredProjects.filter((p) => !p.featured && p.status !== "in-progress");
|
||||
// Sort by startDate in descending order (newest first)
|
||||
return filteredProjects.sort((a, b) => {
|
||||
const dateA = new Date(a.startDate).getTime();
|
||||
@@ -68,7 +71,18 @@ export const getProjectsByCategory = (category?: string) => {
|
||||
|
||||
// Get featured projects
|
||||
export const getFeaturedProjects = () => {
|
||||
return projectsData.filter((p) => p.featured);
|
||||
return projectsData.filter((p) => p.featured === true);
|
||||
};
|
||||
|
||||
// Get in-work projects (in-progress status, excluding featured)
|
||||
export const getInWorkProjects = () => {
|
||||
return projectsData
|
||||
.filter((p) => p.status === "in-progress" && !p.featured)
|
||||
.sort((a, b) => {
|
||||
const dateA = new Date(a.startDate).getTime();
|
||||
const dateB = new Date(b.startDate).getTime();
|
||||
return dateB - dateA;
|
||||
});
|
||||
};
|
||||
|
||||
// Get all tech stacks
|
||||
|
||||
Reference in New Issue
Block a user