feat: добавить skill для SLM Design
- добавлена сборка self-contained skill для Claude Code и opencode - добавлен install-ready архив skill в public/slm-design/skill - обновлена карточка SLM Design с меню действий открыть/скачать - добавлен static fallback главной страницы из общего конфига - подключены Mantine Menu и Phosphor Icons для действий карточки
This commit is contained in:
142
index.html
142
index.html
@@ -86,6 +86,50 @@
|
|||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.static-actions-list,
|
||||||
|
.static-action-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-list {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-list-nested {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-sections {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-section,
|
||||||
|
.static-action-group {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-title {
|
||||||
|
color: color-mix(in srgb, LinkText 80%, CanvasText);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-action-group-title {
|
||||||
|
color: color-mix(in srgb, CanvasText 58%, Canvas);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.static-shell a {
|
.static-shell a {
|
||||||
color: LinkText;
|
color: LinkText;
|
||||||
}
|
}
|
||||||
@@ -111,6 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- STATIC_DOCS_START -->
|
||||||
<section aria-labelledby="static-docs-title">
|
<section aria-labelledby="static-docs-title">
|
||||||
<h2 id="static-docs-title">Список документаций</h2>
|
<h2 id="static-docs-title">Список документаций</h2>
|
||||||
<ul class="static-docs">
|
<ul class="static-docs">
|
||||||
@@ -118,57 +163,96 @@
|
|||||||
<article>
|
<article>
|
||||||
<div class="static-meta">Архитектура · Доступно</div>
|
<div class="static-meta">Архитектура · Доступно</div>
|
||||||
<h2><a href="/slm-design/">SLM Design</a></h2>
|
<h2><a href="/slm-design/">SLM Design</a></h2>
|
||||||
<p>
|
<p>Архитектура frontend-приложений, где слои задают направление зависимостей, модули становятся границами ответственности, а явный DI через фабрики удерживает домены изолированными и предсказуемыми.</p>
|
||||||
Архитектура frontend-приложений, где слои задают направление зависимостей,
|
<ul class="static-actions-list">
|
||||||
модули становятся границами ответственности, а явный DI через фабрики удерживает домены изолированными и предсказуемыми.
|
<li>
|
||||||
</p>
|
<span class="static-action-title">Открыть</span>
|
||||||
<div class="static-links" aria-label="LLM-артефакты SLM Design">
|
<ul class="static-action-list static-action-list-nested">
|
||||||
<a href="/slm-design/llms.txt">llms.txt</a>
|
<li>
|
||||||
<a href="/slm-design/llms-full.txt">llms-full.txt</a>
|
<span class="static-action-group-title">Читать</span>
|
||||||
</div>
|
<ul class="static-action-list"><li><a href="/slm-design/" target="_blank" rel="noopener noreferrer">SLM Документация</a></li></ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="static-action-group-title">Skill для CLI-агентов</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/slm-design/skill/.opencode/skills/slm-design/SKILL.md" target="_blank" rel="noopener noreferrer">slm-design/SKILL.md</a></li></ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="static-action-group-title">AI агентам</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/slm-design/llms.txt" target="_blank" rel="noopener noreferrer">llms.txt</a></li><li><a href="/slm-design/llms-full.txt" target="_blank" rel="noopener noreferrer">llms-full.txt</a></li></ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="static-action-title">Скачать</span>
|
||||||
|
<ul class="static-action-list static-action-list-nested">
|
||||||
|
<li>
|
||||||
|
<span class="static-action-group-title">Документация MD</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/slm-design/slm-design.zip" download>slm-design.zip</a></li></ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="static-action-group-title">Skills (Claude code / OpenCode)</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/slm-design/skill/slm-design.skill.zip" download>slm-design.skill.zip</a></li></ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="static-card">
|
<li class="static-card">
|
||||||
<article>
|
<article>
|
||||||
<div class="static-meta">Стайлгайд · Доступно</div>
|
<div class="static-meta">Стайлгайд · Доступно</div>
|
||||||
<h2><a href="/nextjs-style-guide/">NextJS Style Guide</a></h2>
|
<h2><a href="/nextjs-style-guide/">NextJS Style Guide</a></h2>
|
||||||
<p>
|
<p>Практический стайлгайд для разработки frontend-приложений на Next.js и TypeScript.</p>
|
||||||
Практический стайлгайд для разработки frontend-приложений на Next.js и TypeScript.
|
<ul class="static-actions-list">
|
||||||
</p>
|
<li>
|
||||||
<div class="static-links" aria-label="LLM-артефакты NextJS Style Guide">
|
<span class="static-action-title">AI</span>
|
||||||
<a href="/nextjs-style-guide/llms.txt">llms.txt</a>
|
<ul class="static-action-list"><li><a href="/nextjs-style-guide/llms.txt" target="_blank" rel="noopener noreferrer">llms.txt</a></li><li><a href="/nextjs-style-guide/llms-full.txt" target="_blank" rel="noopener noreferrer">llms-full.txt</a></li></ul>
|
||||||
<a href="/nextjs-style-guide/llms-full.txt">llms-full.txt</a>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="static-card">
|
<li class="static-card">
|
||||||
<article>
|
<article>
|
||||||
<div class="static-meta">Стайлгайд · Скоро</div>
|
<div class="static-meta">Стайлгайд · Скоро</div>
|
||||||
<h2>React Style Guide</h2>
|
<h2>React Style Guide</h2>
|
||||||
<p>
|
<p>Практический стайлгайд для разработки frontend-приложений на React и TypeScript.</p>
|
||||||
Практический стайлгайд для разработки frontend-приложений на React и TypeScript.
|
<ul class="static-actions-list">
|
||||||
</p>
|
<li>
|
||||||
|
<span class="static-action-title">AI</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/react-style-guide/llms.txt" target="_blank" rel="noopener noreferrer">llms.txt</a></li><li><a href="/react-style-guide/llms-full.txt" target="_blank" rel="noopener noreferrer">llms-full.txt</a></li></ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="static-card">
|
<li class="static-card">
|
||||||
<article>
|
<article>
|
||||||
<div class="static-meta">Макеты · Доступно</div>
|
<div class="static-meta">Макеты · Доступно</div>
|
||||||
<h2><a href="/figma-adaptive-standards/">Figma Adaptive Standards</a></h2>
|
<h2><a href="/figma-adaptive-standards/">Figma Adaptive Standards</a></h2>
|
||||||
<p>
|
<p>Стандарты и требования к подготовке адаптивных макетов в Figma: брейкпоинты, ресайз в диапазоне, Auto Layout/Constraints, компоненты, сетка, типографика, состояния UI, A11y и передача в разработку.</p>
|
||||||
Стандарты и требования к подготовке адаптивных макетов в Figma: брейкпоинты,
|
<ul class="static-actions-list">
|
||||||
ресайз в диапазоне, Auto Layout/Constraints, компоненты, сетка, типографика, состояния UI, A11y и передача в разработку.
|
<li>
|
||||||
</p>
|
<span class="static-action-title">AI</span>
|
||||||
<div class="static-links" aria-label="LLM-артефакты Figma Adaptive Standards">
|
<ul class="static-action-list"><li><a href="/figma-adaptive-standards/llms.txt" target="_blank" rel="noopener noreferrer">llms.txt</a></li><li><a href="/figma-adaptive-standards/llms-full.txt" target="_blank" rel="noopener noreferrer">llms-full.txt</a></li></ul>
|
||||||
<a href="/figma-adaptive-standards/llms.txt">llms.txt</a>
|
</li>
|
||||||
<a href="/figma-adaptive-standards/llms-full.txt">llms-full.txt</a>
|
</ul>
|
||||||
</div>
|
</article>
|
||||||
|
</li>
|
||||||
|
<li class="static-card">
|
||||||
|
<article>
|
||||||
|
<div class="static-meta">Стратегия · Доступно</div>
|
||||||
|
<h2><a href="/template-sync-strategy/">Template Sync Strategy</a></h2>
|
||||||
|
<p>Стратегия как поддерживать проекты на общей шаблонной базе: отделять изменения шаблона от бизнес-кода и проводить обновления через контролируемый merge-процесс.</p>
|
||||||
|
<ul class="static-actions-list">
|
||||||
|
<li>
|
||||||
|
<span class="static-action-title">AI</span>
|
||||||
|
<ul class="static-action-list"><li><a href="/template-sync-strategy/llms.txt" target="_blank" rel="noopener noreferrer">llms.txt</a></li><li><a href="/template-sync-strategy/llms-full.txt" target="_blank" rel="noopener noreferrer">llms-full.txt</a></li></ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- STATIC_DOCS_END -->
|
||||||
|
|
||||||
<footer class="static-footer">
|
<footer class="static-footer">
|
||||||
Автор документации: <a href="https://gromlab.ru/gromov">Сергей Громов</a>
|
Автор документации: <a href="https://gromlab.ru/gromov">Сергей Громов</a>
|
||||||
|
|||||||
329
package-lock.json
generated
329
package-lock.json
generated
@@ -8,6 +8,9 @@
|
|||||||
"name": "all-docs",
|
"name": "all-docs",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mantine/core": "^9.2.1",
|
||||||
|
"@mantine/hooks": "^9.2.1",
|
||||||
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6"
|
||||||
},
|
},
|
||||||
@@ -191,7 +194,6 @@
|
|||||||
"integrity": "sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==",
|
"integrity": "sha512-gA8oJOV1LnQQkDf91iebNnFInHuW0gRPEgLSOQ7EfipCEjYTHm5swm1DlH9H5RaRw4RrHuzHBegnlzc0MAstcg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/client-common": "5.52.1",
|
"@algolia/client-common": "5.52.1",
|
||||||
"@algolia/requester-browser-xhr": "5.52.1",
|
"@algolia/requester-browser-xhr": "5.52.1",
|
||||||
@@ -314,7 +316,6 @@
|
|||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -591,17 +592,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@docsearch/js/node_modules/scheduler": {
|
|
||||||
"version": "0.23.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
|
||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"loose-envify": "^1.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
@@ -1257,6 +1247,59 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||||
|
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||||
|
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.7.5",
|
||||||
|
"@floating-ui/utils": "^0.2.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react": {
|
||||||
|
"version": "0.27.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz",
|
||||||
|
"integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react-dom": "^2.1.8",
|
||||||
|
"@floating-ui/utils": "^0.2.11",
|
||||||
|
"tabbable": "^6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=17.0.0",
|
||||||
|
"react-dom": ">=17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
|
||||||
|
"integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.7.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -1370,6 +1413,46 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mantine/core": {
|
||||||
|
"version": "9.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-9.2.1.tgz",
|
||||||
|
"integrity": "sha512-CicPg9i2dM2pGp1jj+dMiR/63OFDsPjgJke4v5+0nbfJ+C7gn4C+7ltrp4RIETDMZHcj0fFuDRG0qtbiyBxvWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/react": "^0.27.19",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"react-number-format": "^5.4.5",
|
||||||
|
"react-remove-scroll": "^2.7.2",
|
||||||
|
"type-fest": "^5.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@mantine/hooks": "9.2.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mantine/hooks": {
|
||||||
|
"version": "9.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-9.2.1.tgz",
|
||||||
|
"integrity": "sha512-IX/ztVG9eWmQTRsN7G8odyW4JckNvN8qv5A2ULzXyazjtAKLuaUpuMz0c6XhRp10J0g4bVfV3rhrTgWeImqxqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@phosphor-icons/react": {
|
||||||
|
"version": "2.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz",
|
||||||
|
"integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8",
|
||||||
|
"react-dom": ">= 16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.27",
|
"version": "1.0.0-beta.27",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||||
@@ -1939,24 +2022,14 @@
|
|||||||
"version": "24.12.4",
|
"version": "24.12.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
|
||||||
"version": "15.7.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
|
||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -2022,7 +2095,6 @@
|
|||||||
"version": "8.59.3",
|
"version": "8.59.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.59.3",
|
"@typescript-eslint/scope-manager": "8.59.3",
|
||||||
"@typescript-eslint/types": "8.59.3",
|
"@typescript-eslint/types": "8.59.3",
|
||||||
@@ -2502,7 +2574,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2541,7 +2612,6 @@
|
|||||||
"integrity": "sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==",
|
"integrity": "sha512-fHA8+kXTbjagw3jkLiaS7KKrH8qe2DyOsiUhGlN4cdT77PEsfqXZl7ewDk1hsg+pJnPlnE50XtLxjR91iJOpmg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/abtesting": "1.18.1",
|
"@algolia/abtesting": "1.18.1",
|
||||||
"@algolia/client-abtesting": "5.52.1",
|
"@algolia/client-abtesting": "5.52.1",
|
||||||
@@ -2664,7 +2734,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.10.12",
|
"baseline-browser-mapping": "^2.10.12",
|
||||||
"caniuse-lite": "^1.0.30001782",
|
"caniuse-lite": "^1.0.30001782",
|
||||||
@@ -2784,6 +2853,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clsx": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -2858,7 +2936,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
@@ -2906,6 +2984,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/detect-node-es": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/devlop": {
|
"node_modules/devlop": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
||||||
@@ -3016,7 +3100,6 @@
|
|||||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3375,7 +3458,6 @@
|
|||||||
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
|
"integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tabbable": "^6.4.0"
|
"tabbable": "^6.4.0"
|
||||||
}
|
}
|
||||||
@@ -3422,6 +3504,15 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-nonce": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-tsconfig": {
|
"node_modules/get-tsconfig": {
|
||||||
"version": "4.14.0",
|
"version": "4.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
||||||
@@ -3806,20 +3897,6 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/loose-envify": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"loose-envify": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -4693,7 +4770,6 @@
|
|||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -4794,7 +4870,6 @@
|
|||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.6",
|
"version": "19.2.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -4809,6 +4884,16 @@
|
|||||||
"react": "^19.2.6"
|
"react": "^19.2.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-number-format": {
|
||||||
|
"version": "5.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.5.tgz",
|
||||||
|
"integrity": "sha512-y8O2yHHj3w0aE9XO8d2BCcUOOdQTRSVq+WIuMlLVucAm5XNjJAy+BoOJiuQMldVYVOKTMyvVNfnbl2Oqp+YxGw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@@ -4819,6 +4904,75 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.7",
|
||||||
|
"react-style-singleton": "^2.2.3",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.3",
|
||||||
|
"use-sidecar": "^1.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-remove-scroll-bar": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-style-singleton": "^2.2.2",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-style-singleton": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"get-nonce": "^1.0.0",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regex": {
|
"node_modules/regex": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
|
||||||
@@ -5206,9 +5360,20 @@
|
|||||||
"version": "6.4.0",
|
"version": "6.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
|
||||||
"integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
|
"integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tagged-tag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.16",
|
"version": "0.2.16",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -5264,6 +5429,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/tsx": {
|
"node_modules/tsx": {
|
||||||
"version": "4.21.0",
|
"version": "4.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
@@ -5728,13 +5899,27 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/type-fest": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"tagged-tag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -5923,6 +6108,49 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-callback-ref": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/use-sidecar": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-node-es": "^1.1.0",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vfile": {
|
"node_modules/vfile": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
||||||
@@ -5959,7 +6187,6 @@
|
|||||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
@@ -6091,7 +6318,6 @@
|
|||||||
"integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
|
"integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.34",
|
"@vue/compiler-dom": "3.5.34",
|
||||||
"@vue/compiler-sfc": "3.5.34",
|
"@vue/compiler-sfc": "3.5.34",
|
||||||
@@ -6207,7 +6433,6 @@
|
|||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "npm run prepare:static-index && vite",
|
||||||
"build": "tsx build.ts",
|
"build": "tsx build.ts",
|
||||||
"app:build": "tsc -b && vite build",
|
"app:build": "npm run prepare:static-index && tsc -b && vite build",
|
||||||
|
"prepare:static-index": "tsx scripts/prepare-static-index.ts",
|
||||||
"build:slm-design": "tsx projects/slm-design/build.ts",
|
"build:slm-design": "tsx projects/slm-design/build.ts",
|
||||||
"build:nextjs-style-guide": "tsx projects/nextjs-style-guide/build.ts",
|
"build:nextjs-style-guide": "tsx projects/nextjs-style-guide/build.ts",
|
||||||
"build:figma-adaptive-standards": "tsx projects/figma-adaptive-standards/build.ts",
|
"build:figma-adaptive-standards": "tsx projects/figma-adaptive-standards/build.ts",
|
||||||
@@ -15,6 +16,9 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mantine/core": "^9.2.1",
|
||||||
|
"@mantine/hooks": "^9.2.1",
|
||||||
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6"
|
"react-dom": "^19.2.6"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function collectFiles(dir: string, baseDir = dir, archiveRoot = path.basename(di
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: `${archiveRoot}/${relativePath}`,
|
name: archiveRoot ? `${archiveRoot}/${relativePath}` : relativePath,
|
||||||
content: fs.readFileSync(entryPath),
|
content: fs.readFileSync(entryPath),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,3 +18,5 @@ if (config.archive) {
|
|||||||
writeZipFromDirectory(path.join(docsDir, 'content'), zipPath, config.slug);
|
writeZipFromDirectory(path.join(docsDir, 'content'), zipPath, config.slug);
|
||||||
console.log(`Собран ${path.relative(rootDir, zipPath)}`);
|
console.log(`Собран ${path.relative(rootDir, zipPath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await import('./scripts/build-skill');
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ Scoped Layered Module Design — модульная архитектура фр
|
|||||||
|
|
||||||
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты → монорепозитории.
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты → монорепозитории.
|
||||||
|
|
||||||
|
## AI Skill
|
||||||
|
|
||||||
|
Готовый self-contained skill для Claude Code и opencode можно скачать как архив: [slm-design.skill.zip](/slm-design/skill/slm-design.skill.zip).
|
||||||
|
|
||||||
|
Архив можно распаковать в корень другого проекта. Он добавит рабочие файлы `.claude/skills/slm-design/SKILL.md` и `.opencode/skills/slm-design/SKILL.md`.
|
||||||
|
|
||||||
## Преимущества
|
## Преимущества
|
||||||
|
|
||||||
### Вертикальная организация домена
|
### Вертикальная организация домена
|
||||||
|
|||||||
140
projects/slm-design/scripts/build-skill.ts
Normal file
140
projects/slm-design/scripts/build-skill.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { writeZipFromDirectory } from '../../_shared/lib/zip';
|
||||||
|
import config from '../project.config';
|
||||||
|
|
||||||
|
const projectDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const rootDir = path.resolve(projectDir, '../..');
|
||||||
|
const templatePath = path.join(projectDir, 'skill', 'slm-design.skill.md');
|
||||||
|
const outputDir = path.join(rootDir, 'public', config.slug, 'skill');
|
||||||
|
const skillZipPath = path.join(outputDir, 'slm-design.skill.zip');
|
||||||
|
|
||||||
|
const description = 'Use this skill ONLY when working in a project that uses SLM Design (Scoped Layered Module Design) — a frontend architecture with layers app, layouts, screens, widgets, business, infra, ui, shared; modules with public API via index.ts; business-domain factories; and segments like ui/, parts/, hooks/, services/, mappers/. Apply when designing new modules, deciding where to place code, reviewing imports and dependency direction, refactoring existing SLM code, or planning monorepo placement (apps/, packages/ui, packages/infra, packages/shared). Project signals that SLM is active (strongest first): src/screens/ + src/widgets/ combination (highly specific to SLM — FSD uses pages/, Atomic and Clean don\'t have this layer); *.factory.ts files in business modules with index.ts exporting only factory and type-only exports; src/screens/ alongside src/ui/ and src/shared/; in monorepo, the same structure inside apps/{app}/src/. Note: src/business/ is a strong confirmation when present, but small or early-stage SLM projects may not have it yet — absence of business/ does NOT rule out SLM. Conflicting signals indicating other architectures: src/features/ or src/entities/ or src/pages/ (FSD); src/atoms/ or src/molecules/ (Atomic); src/domain/ or src/useCases/ (Clean). User signals (Russian or English): "SLM", "SLM Design", "Scoped Layered Module Design", "куда положить", "where to place", "какой слой", "which layer", "это модуль или компонент", "module or component", "можно ли так импортировать", "can I import", "deep import", "фабрика", "factory", "публичный API", "public API", "parts/", "business-домен", "business domain", "композиция фабрик", "factory composition". Do NOT use this skill for: projects on Feature-Sliced Design (FSD), Atomic Design, Clean Architecture, or any other frontend architecture — SLM has its own rules and is NOT a synonym for these; legacy codebases without explicit SLM structure (do not propose migration unless the user explicitly asks); small isolated tasks like styling, single-file bug fixes, CSS, or build tooling where architectural placement is not the question; backend architecture. When project architecture is ambiguous, ask the user before applying SLM rules.';
|
||||||
|
|
||||||
|
const canonPages = [
|
||||||
|
{
|
||||||
|
anchor: 'canon-architecture-index',
|
||||||
|
source: 'canons/architecture/index.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-architecture-layers',
|
||||||
|
source: 'canons/architecture/layers.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-architecture-modules',
|
||||||
|
source: 'canons/architecture/modules.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-architecture-segments',
|
||||||
|
source: 'canons/architecture/segments.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-architecture-monorepo',
|
||||||
|
source: 'canons/architecture/monorepo.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-examples-react-factory',
|
||||||
|
source: 'canons/examples/react/factory.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-examples-react-factory-composition',
|
||||||
|
source: 'canons/examples/react/factory-composition.md',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'canon-examples-react-composition-provider',
|
||||||
|
source: 'canons/examples/react/composition-provider.md',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const routeAnchors = new Map([
|
||||||
|
['/architecture', 'canon-architecture-index'],
|
||||||
|
['/architecture/', 'canon-architecture-index'],
|
||||||
|
['/architecture/layers', 'canon-architecture-layers'],
|
||||||
|
['/architecture/modules', 'canon-architecture-modules'],
|
||||||
|
['/architecture/segments', 'canon-architecture-segments'],
|
||||||
|
['/architecture/monorepo', 'canon-architecture-monorepo'],
|
||||||
|
['/examples/react/factory', 'canon-examples-react-factory'],
|
||||||
|
['/examples/react/factory-composition', 'canon-examples-react-factory-composition'],
|
||||||
|
['/examples/react/composition-provider', 'canon-examples-react-composition-provider'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function stripFrontmatter(content: string) {
|
||||||
|
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformOutsideCode(content: string, transformLine: (line: string) => string) {
|
||||||
|
let inFence = false;
|
||||||
|
|
||||||
|
return content
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => {
|
||||||
|
if (/^\s*(```|~~~)/.test(line)) {
|
||||||
|
inFence = !inFence;
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inFence) return line;
|
||||||
|
|
||||||
|
return transformLine(line);
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shiftHeadings(content: string) {
|
||||||
|
return transformOutsideCode(content, (line) => line.replace(/^(#{1,6})\s/, '##$1 '));
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteMarkdownLinks(content: string) {
|
||||||
|
return transformOutsideCode(content, (line) => line.replace(/\]\((\/[^)\s#]+\/?)(#[^)\s]+)?\)/g, (match, route: string, hash = '') => {
|
||||||
|
const normalizedRoute = route.length > 1 && route.endsWith('/') ? route.slice(0, -1) : route;
|
||||||
|
const anchor = routeAnchors.get(route) ?? routeAnchors.get(normalizedRoute);
|
||||||
|
|
||||||
|
if (!anchor) return match;
|
||||||
|
|
||||||
|
return `](${hash || `#${anchor}`})`;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareCanon(source: string, anchor: string) {
|
||||||
|
const sourcePath = path.join(projectDir, source);
|
||||||
|
const content = fs.readFileSync(sourcePath, 'utf8');
|
||||||
|
const prepared = rewriteMarkdownLinks(shiftHeadings(stripFrontmatter(content).trim()));
|
||||||
|
|
||||||
|
return `<a id="${anchor}"></a>\n\n${prepared}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSkillBody() {
|
||||||
|
const template = fs.readFileSync(templatePath, 'utf8').trim();
|
||||||
|
const canonContent = canonPages.map((page) => prepareCanon(page.source, page.anchor)).join('\n\n---\n\n');
|
||||||
|
|
||||||
|
return template.replace('<!-- SLM_CANON_CONTENT -->', canonContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSkill(frontmatter: string) {
|
||||||
|
return `${frontmatter.trim()}\n\n${createSkillBody()}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(filePath: string, content: string) {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, content, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSkill() {
|
||||||
|
const frontmatter = `---\nname: slm-design\ndescription: ${description}\n---`;
|
||||||
|
const skillContent = createSkill(frontmatter);
|
||||||
|
|
||||||
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
||||||
|
|
||||||
|
writeFile(path.join(outputDir, '.claude', 'skills', 'slm-design', 'SKILL.md'), skillContent);
|
||||||
|
writeFile(path.join(outputDir, '.opencode', 'skills', 'slm-design', 'SKILL.md'), skillContent);
|
||||||
|
|
||||||
|
fs.rmSync(skillZipPath, { force: true });
|
||||||
|
writeZipFromDirectory(outputDir, skillZipPath, '');
|
||||||
|
|
||||||
|
console.log(`Собран ${path.relative(rootDir, outputDir)}`);
|
||||||
|
console.log(`Собран ${path.relative(rootDir, skillZipPath)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSkill();
|
||||||
98
projects/slm-design/skill/slm-design.skill.md
Normal file
98
projects/slm-design/skill/slm-design.skill.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# SLM Design Skill
|
||||||
|
|
||||||
|
SLM Design — новая архитектура. Не заменяй её правилами Feature-Sliced Design, Atomic Design, Clean Architecture или другими привычными схемами. Если в проекте нет локальной документации SLM, считай этот файл единственным источником правды.
|
||||||
|
|
||||||
|
## Определение Контекста
|
||||||
|
|
||||||
|
Перед применением правил убедись, что проект использует SLM Design.
|
||||||
|
|
||||||
|
1. Проверь характерные слои SLM. Сильные сигналы: одновременное наличие `src/screens/` и `src/widgets/` (комбинация специфична именно для SLM — в FSD это `pages/`, в Atomic и Clean таких слоёв нет); файлы `*.factory.ts` в business-модулях с `index.ts`, экспортирующим только фабрику и type-only. Дополнительные сигналы: `src/ui/`, `src/shared/`, `src/layouts/` как слои. В монорепо проверяй то же внутри `apps/{app}/src/`.
|
||||||
|
ВАЖНО: отсутствие `src/business/` НЕ означает, что проект не на SLM. В маленьких или ранних проектах бизнес-доменов может ещё не быть.
|
||||||
|
2. Проверь, что business-модули (если они есть) имеют `*.factory.ts` в корне и `index.ts` экспортирует только фабрику и type-only экспорты.
|
||||||
|
3. Если видишь признаки другой архитектуры — `features/`, `entities/`, `pages/` (FSD), `atoms/`, `molecules/` (Atomic), `domain/`, `useCases/` (Clean) — это НЕ SLM. Не применяй правила этого skill.
|
||||||
|
4. Если структура неоднозначна или проект новый — спроси пользователя, какая архитектура используется, прежде чем применять правила.
|
||||||
|
5. Если пользователь работает в legacy-коде без SLM и НЕ просит миграцию — не предлагай рефакторинг по SLM. Соблюдай существующие паттерны кода.
|
||||||
|
## Порядок Работы
|
||||||
|
|
||||||
|
1. Найди границу приложения: `src/` или `apps/{app}/src`.
|
||||||
|
2. Определи ответственность изменения: запуск приложения, layout, screen, widget, business-domain, infra-service, UI-kit или shared resource.
|
||||||
|
3. Размещай код на самом низком подходящем уровне и поднимай выше только при реальной потребности переиспользования.
|
||||||
|
4. Проверяй, является сущность модулем или компонентом. Если сущность получает данные, владеет сценарием, композирует зависимости или имеет внутреннюю архитектуру — это модуль, а не компонент в `ui/`.
|
||||||
|
5. Все внешние импорты между модулями делай только через публичный API (`index.ts`). Deep imports запрещены.
|
||||||
|
6. Для `business` runtime-зависимостей между доменами используй фабрики. Не импортируй runtime-код одного business-домена напрямую в другой.
|
||||||
|
7. Перед завершением проверь слой, модульную границу, сегменты, публичный API, направление импортов и monorepo-ограничения.
|
||||||
|
|
||||||
|
## Жёсткие Правила
|
||||||
|
|
||||||
|
- Направление зависимостей внутри приложения: `app → [ layouts | screens ] → widgets → business → infra → ui → shared`.
|
||||||
|
- `layouts` и `screens` параллельны и не импортируют друг друга.
|
||||||
|
- Модули одного слоя в группе «Композиция» изолированы друг от друга.
|
||||||
|
- Runtime-импорты между `business`-доменами запрещены. Cross-domain runtime-зависимости передаются только через аргументы фабрики.
|
||||||
|
- `import type` в группе «Ядро» разрешён в обоих направлениях, потому что не создаёт runtime-зависимость.
|
||||||
|
- Каждый внешний импорт модуля идёт через `index.ts` модуля или публичный API пакета.
|
||||||
|
- `business/{name}/index.ts` экспортирует только фабрику и type-only экспорты.
|
||||||
|
- Компонент в `ui/` родительского модуля не импортирует проектный код за пределами родительского модуля, не получает данные, не вызывает сценарные хуки и не содержит бизнес-логику.
|
||||||
|
- `parts/` содержит только вложенные модули, не произвольные `.tsx`, стили или хуки.
|
||||||
|
- `shared/` не знает о продукте, бизнес-доменах, UI-kit сущностях и runtime-состоянии.
|
||||||
|
- В monorepo SLM применяется внутри каждого `apps/{app}/src`.
|
||||||
|
- В `packages/*` можно выносить только общие `ui`, `infra` и `shared`. `business`, `app`, `layouts`, `screens`, `widgets` не выносятся в пакеты.
|
||||||
|
|
||||||
|
## Выбор Места Для Кода
|
||||||
|
|
||||||
|
- Код нужен одной странице или layout: оставь его внутри `screens/{name}/parts/` или `layouts/{name}/parts/`.
|
||||||
|
- Абстрактный UI без бизнес-логики и сценариев: `ui/{name}/`.
|
||||||
|
- Составной блок интерфейса без принадлежности конкретному домену, используемый в нескольких screens/layouts: `widgets/{name}/`.
|
||||||
|
- Код принадлежит бизнес-домену: `business/{domain}/`, даже если переиспользуется.
|
||||||
|
- Технический сервис приложения: `infra/{service}/`.
|
||||||
|
- Чистая утилита или фундаментальный ресурс без знания о продукте: `shared/`.
|
||||||
|
- В monorepo общий UI/infra/shared код, потенциально нужный двум и более frontend-приложениям: `packages/ui/*`, `packages/infra/*`, `packages/shared`.
|
||||||
|
|
||||||
|
## Шаблон Business-Модуля
|
||||||
|
|
||||||
|
```text
|
||||||
|
business/{name}/
|
||||||
|
├── {name}.factory.ts
|
||||||
|
├── hooks/
|
||||||
|
├── services/
|
||||||
|
├── mappers/
|
||||||
|
├── types/
|
||||||
|
│ ├── {name}-api.type.ts
|
||||||
|
│ ├── {name}-deps.type.ts
|
||||||
|
│ └── {name}-factory.type.ts
|
||||||
|
├── ui/
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Фабрика лежит в корне business-модуля и возвращает публичный runtime API. Если модулю нужны другие домены, принимай зависимости аргументом фабрики доменными именами.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { customerFactory } from './customer.factory'
|
||||||
|
|
||||||
|
export type { Customer } from './types/customer.type'
|
||||||
|
export type { CustomerApi } from './types/customer-api.type'
|
||||||
|
export type { CustomerDeps } from './types/customer-deps.type'
|
||||||
|
export type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Чеклист Ревью
|
||||||
|
|
||||||
|
Проверяй архитектуру в таком порядке:
|
||||||
|
|
||||||
|
1. Правильно ли выбран слой по ответственности?
|
||||||
|
2. Не вынесен ли код выше, чем нужно для текущего использования?
|
||||||
|
3. Не лежит ли модульная сущность в `ui/` как компонент?
|
||||||
|
4. Не содержит ли компонент в `ui/` данные, сценарии, внешние импорты или вложенную архитектуру?
|
||||||
|
5. Есть ли у каждого модуля публичный API и нет ли deep imports?
|
||||||
|
6. Соблюдено ли направление зависимостей?
|
||||||
|
7. Нет ли runtime-импортов между business-доменами?
|
||||||
|
8. Экспортирует ли `business/index.ts` только фабрику и типы?
|
||||||
|
9. Не попали ли продуктовые типы, конфиги или стили в `shared/`?
|
||||||
|
10. В monorepo не вынесены ли `business`, `screens`, `layouts` или `widgets` в `packages/*`?
|
||||||
|
|
||||||
|
При ревью сначала перечисляй нарушения с файлами и причиной. Затем предлагай минимальное исправление.
|
||||||
|
|
||||||
|
## Каноническая Спецификация
|
||||||
|
|
||||||
|
Ниже находится полная спецификация SLM Design из канонов проекта. Не сокращай и не переинтерпретируй эти правила.
|
||||||
|
|
||||||
|
<!-- SLM_CANON_CONTENT -->
|
||||||
135
scripts/prepare-static-index.ts
Normal file
135
scripts/prepare-static-index.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import { docs, type DocAction, type DocActionCollection, type DocActionGroup } from '../src/config/docs.config'
|
||||||
|
|
||||||
|
const rootDir = path.resolve(import.meta.dirname, '..')
|
||||||
|
const indexPath = path.join(rootDir, 'index.html')
|
||||||
|
const startMarker = '<!-- STATIC_DOCS_START -->'
|
||||||
|
const endMarker = '<!-- STATIC_DOCS_END -->'
|
||||||
|
|
||||||
|
function escapeHtml(value: string) {
|
||||||
|
return value
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionGroup(action: DocAction | DocActionGroup): action is DocActionGroup {
|
||||||
|
return 'actions' in action
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitActionCollection(collection: DocActionCollection | undefined) {
|
||||||
|
if (!collection?.length) return { actions: [], groups: [] }
|
||||||
|
|
||||||
|
if (isActionGroup(collection[0])) {
|
||||||
|
const groups = collection as DocActionGroup[]
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: [],
|
||||||
|
groups: groups.filter((group) => group.actions.length > 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: collection as DocAction[],
|
||||||
|
groups: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLink(action: DocAction, type: 'download' | 'open') {
|
||||||
|
const attrs = [
|
||||||
|
`href="${escapeHtml(action.href)}"`,
|
||||||
|
type === 'open' ? 'target="_blank"' : '',
|
||||||
|
type === 'open' ? 'rel="noopener noreferrer"' : '',
|
||||||
|
type === 'download' ? 'download' : '',
|
||||||
|
].filter(Boolean).join(' ')
|
||||||
|
|
||||||
|
return `<li><a ${attrs}>${escapeHtml(action.label)}</a></li>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActionList(actions: DocAction[], type: 'download' | 'open') {
|
||||||
|
return `<ul class="static-action-list">${actions.map((action) => renderLink(action, type)).join('')}</ul>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActionCollection(collection: DocActionCollection | undefined, title: string, type: 'download' | 'open') {
|
||||||
|
const { actions, groups } = splitActionCollection(collection)
|
||||||
|
|
||||||
|
if (actions.length === 0 && groups.length === 0) return ''
|
||||||
|
|
||||||
|
const content = groups.length > 0
|
||||||
|
? groups.map((group) => `
|
||||||
|
<li>
|
||||||
|
<span class="static-action-group-title">${escapeHtml(group.title)}</span>
|
||||||
|
${renderActionList(group.actions, type)}
|
||||||
|
</li>`).join('')
|
||||||
|
: actions.map((action) => renderLink(action, type)).join('')
|
||||||
|
|
||||||
|
return `
|
||||||
|
<li>
|
||||||
|
<span class="static-action-title">${title}</span>
|
||||||
|
<ul class="static-action-list static-action-list-nested">${content}
|
||||||
|
</ul>
|
||||||
|
</li>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDocLinks(title: string, links: DocAction[]) {
|
||||||
|
if (links.length === 0) return ''
|
||||||
|
|
||||||
|
return `
|
||||||
|
<li>
|
||||||
|
<span class="static-action-title">AI</span>
|
||||||
|
${renderActionList(links, 'open')}
|
||||||
|
</li>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDoc(doc: typeof docs[number]) {
|
||||||
|
const title = escapeHtml(doc.title)
|
||||||
|
const heading = doc.href ? `<a href="${escapeHtml(doc.href)}">${title}</a>` : title
|
||||||
|
const actionGroups = doc.actionGroups
|
||||||
|
const actions = [
|
||||||
|
renderActionCollection(actionGroups?.open, 'Открыть', 'open'),
|
||||||
|
renderActionCollection(actionGroups?.download, 'Скачать', 'download'),
|
||||||
|
doc.actionGroups ? '' : renderDocLinks(doc.title, doc.links),
|
||||||
|
].filter(Boolean).join('')
|
||||||
|
const actionBlock = actions ? `
|
||||||
|
<ul class="static-actions-list">${actions}
|
||||||
|
</ul>` : ''
|
||||||
|
|
||||||
|
return `
|
||||||
|
<li class="static-card">
|
||||||
|
<article>
|
||||||
|
<div class="static-meta">${escapeHtml(doc.label)} · ${escapeHtml(doc.status)}</div>
|
||||||
|
<h2>${heading}</h2>
|
||||||
|
<p>${escapeHtml(doc.description)}</p>${actionBlock}
|
||||||
|
</article>
|
||||||
|
</li>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStaticDocs() {
|
||||||
|
return `<section aria-labelledby="static-docs-title">
|
||||||
|
<h2 id="static-docs-title">Список документаций</h2>
|
||||||
|
<ul class="static-docs">${docs.map(renderDoc).join('')}
|
||||||
|
</ul>
|
||||||
|
</section>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexHtml = fs.readFileSync(indexPath, 'utf8')
|
||||||
|
const startIndex = indexHtml.indexOf(startMarker)
|
||||||
|
const endIndex = indexHtml.indexOf(endMarker)
|
||||||
|
|
||||||
|
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
||||||
|
throw new Error(`Не найдены маркеры ${startMarker} / ${endMarker} в index.html`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextIndexHtml = [
|
||||||
|
indexHtml.slice(0, startIndex + startMarker.length),
|
||||||
|
'\n ',
|
||||||
|
renderStaticDocs().replaceAll('\n', '\n '),
|
||||||
|
'\n ',
|
||||||
|
indexHtml.slice(endIndex),
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
fs.writeFileSync(indexPath, nextIndexHtml, 'utf8')
|
||||||
|
console.log('Подготовлен static fallback в index.html')
|
||||||
99
src/App.css
99
src/App.css
@@ -359,6 +359,99 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docMenu {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuButton {
|
||||||
|
--button-bg: var(--page-bg);
|
||||||
|
--button-bd: 1px solid var(--border-soft);
|
||||||
|
--button-color: var(--text-primary);
|
||||||
|
--button-hover: var(--page-bg);
|
||||||
|
--button-hover-color: var(--doc-accent);
|
||||||
|
--button-padding-x: 10px;
|
||||||
|
|
||||||
|
min-height: 33px;
|
||||||
|
height: 33px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--border-soft) !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
background: var(--page-bg) !important;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: normal;
|
||||||
|
transition: border-color 150ms ease, color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuButton:hover {
|
||||||
|
border-color: var(--doc-accent) !important;
|
||||||
|
color: var(--doc-accent) !important;
|
||||||
|
background: var(--page-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuItem {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallback {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
width: 100%;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackSection,
|
||||||
|
.docMenuFallbackGroup {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackSectionTitle,
|
||||||
|
.docMenuFallbackTitle {
|
||||||
|
color: var(--doc-accent);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 760;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackTitle {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackLinks {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackLink {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--page-bg);
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: border-color 150ms ease, color 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docMenuFallbackLink:hover {
|
||||||
|
border-color: var(--doc-accent);
|
||||||
|
color: var(--doc-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.docLink {
|
.docLink {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border: 1px solid var(--border-soft);
|
border: 1px solid var(--border-soft);
|
||||||
@@ -477,4 +570,10 @@
|
|||||||
.docLink {
|
.docLink {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.docMenu,
|
||||||
|
.docMenu .mantine-Menu-root,
|
||||||
|
.docMenu .mantine-Button-root {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
323
src/App.tsx
323
src/App.tsx
@@ -1,6 +1,12 @@
|
|||||||
import { useEffect, useLayoutEffect, useState } from 'react'
|
import { Fragment, useEffect, useLayoutEffect, useState, type ReactNode } from 'react'
|
||||||
|
import { Button, createTheme, MantineProvider, Menu } from '@mantine/core'
|
||||||
|
import { ArrowSquareOutIcon } from '@phosphor-icons/react/ArrowSquareOut'
|
||||||
|
import { DownloadSimpleIcon } from '@phosphor-icons/react/DownloadSimple'
|
||||||
|
import { EyeIcon } from '@phosphor-icons/react/Eye'
|
||||||
|
import { FileTextIcon } from '@phosphor-icons/react/FileText'
|
||||||
|
import { FileZipIcon } from '@phosphor-icons/react/FileZip'
|
||||||
|
|
||||||
import { docs } from './config/docs.config'
|
import { docs, type DocAction, type DocActionCollection, type DocActionGroup, type DocActionGroups } from './config/docs.config'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
type ThemeMode = 'auto' | 'dark' | 'light'
|
type ThemeMode = 'auto' | 'dark' | 'light'
|
||||||
@@ -11,6 +17,10 @@ const THEME_STORAGE_KEY = 'vitepress-theme-appearance'
|
|||||||
const LEGACY_THEME_STORAGE_KEY = 'all-docs-theme'
|
const LEGACY_THEME_STORAGE_KEY = 'all-docs-theme'
|
||||||
const repositoryUrl = 'https://gromlab.ru/gromov/docs'
|
const repositoryUrl = 'https://gromlab.ru/gromov/docs'
|
||||||
const authorUrl = 'https://gromlab.ru/gromov'
|
const authorUrl = 'https://gromlab.ru/gromov'
|
||||||
|
const mantineTheme = createTheme({
|
||||||
|
fontFamily: 'var(--sans)',
|
||||||
|
primaryColor: 'indigo',
|
||||||
|
})
|
||||||
|
|
||||||
const themeOptions: ReadonlyArray<{
|
const themeOptions: ReadonlyArray<{
|
||||||
value: Exclude<ThemeMode, 'auto'>
|
value: Exclude<ThemeMode, 'auto'>
|
||||||
@@ -149,8 +159,11 @@ function useTheme() {
|
|||||||
return { theme, resolvedTheme, setTheme }
|
return { theme, resolvedTheme, setTheme }
|
||||||
}
|
}
|
||||||
|
|
||||||
function ThemeToggle() {
|
function ThemeToggle({ theme, resolvedTheme, setTheme }: {
|
||||||
const { theme, resolvedTheme, setTheme } = useTheme()
|
theme: ThemeMode
|
||||||
|
resolvedTheme: ResolvedTheme
|
||||||
|
setTheme: (theme: ThemeMode) => void
|
||||||
|
}) {
|
||||||
const toggleTheme = (value: Exclude<ThemeMode, 'auto'>) => {
|
const toggleTheme = (value: Exclude<ThemeMode, 'auto'>) => {
|
||||||
setTheme(theme === value ? 'auto' : value)
|
setTheme(theme === value ? 'auto' : value)
|
||||||
}
|
}
|
||||||
@@ -183,6 +196,157 @@ function GithubIcon() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getActionIcon(type: 'download' | 'open'): ReactNode {
|
||||||
|
if (type === 'download') return <FileZipIcon size={16} />
|
||||||
|
|
||||||
|
return <FileTextIcon size={16} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionGroup(action: DocAction | DocActionGroup): action is DocActionGroup {
|
||||||
|
return 'actions' in action
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitActionCollection(collection: DocActionCollection | undefined) {
|
||||||
|
if (!collection?.length) return { actions: [], groups: [] }
|
||||||
|
|
||||||
|
if (isActionGroup(collection[0])) {
|
||||||
|
const groups = collection as DocActionGroup[]
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: [],
|
||||||
|
groups: groups.filter((group) => group.actions.length > 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
actions: collection as DocAction[],
|
||||||
|
groups: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocActionsMenuButton({ actions = [], groups = [], label, type }: {
|
||||||
|
actions?: DocAction[]
|
||||||
|
groups?: DocActionGroup[]
|
||||||
|
label: string
|
||||||
|
type: 'download' | 'open'
|
||||||
|
}) {
|
||||||
|
const [opened, setOpened] = useState(false)
|
||||||
|
const buttonIcon = type === 'download' ? <DownloadSimpleIcon size={16} /> : <EyeIcon size={16} />
|
||||||
|
const renderAction = (action: DocAction, keyPrefix: string) => (
|
||||||
|
<Menu.Item
|
||||||
|
component="a"
|
||||||
|
href={action.href}
|
||||||
|
download={type === 'download' ? '' : undefined}
|
||||||
|
target={type === 'open' ? '_blank' : undefined}
|
||||||
|
rel={type === 'open' ? 'noopener noreferrer' : undefined}
|
||||||
|
leftSection={getActionIcon(type)}
|
||||||
|
rightSection={type === 'open' && action.href.endsWith('.md') ? <ArrowSquareOutIcon size={16} /> : undefined}
|
||||||
|
key={`${keyPrefix}-${action.href}`}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Menu.Item>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
opened={opened}
|
||||||
|
onChange={setOpened}
|
||||||
|
position="bottom-start"
|
||||||
|
shadow="md"
|
||||||
|
width={260}
|
||||||
|
withinPortal
|
||||||
|
classNames={{ item: 'docMenuItem' }}
|
||||||
|
>
|
||||||
|
<Menu.Target>
|
||||||
|
<Button className={`docMenuButton docMenuButton-${type}`} leftSection={buttonIcon} size="xs" variant="outline">
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</Menu.Target>
|
||||||
|
|
||||||
|
<Menu.Dropdown>
|
||||||
|
{groups.length > 0
|
||||||
|
? groups.map((group, groupIndex) => (
|
||||||
|
<Fragment key={group.title}>
|
||||||
|
{groupIndex > 0 && <Menu.Divider />}
|
||||||
|
<Menu.Label>{group.title}</Menu.Label>
|
||||||
|
{group.actions.map((action) => renderAction(action, group.title))}
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
|
: actions.map((action) => renderAction(action, label))}
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocActionsFallback({ groups }: { groups: DocActionGroups }) {
|
||||||
|
const open = splitActionCollection(groups.open)
|
||||||
|
const download = splitActionCollection(groups.download)
|
||||||
|
const renderActions = (actions: DocAction[], type: 'download' | 'open') => actions.map((action) => (
|
||||||
|
<a
|
||||||
|
className="docMenuFallbackLink"
|
||||||
|
href={action.href}
|
||||||
|
target={type === 'open' ? '_blank' : undefined}
|
||||||
|
rel={type === 'open' ? 'noopener noreferrer' : undefined}
|
||||||
|
download={type === 'download' ? '' : undefined}
|
||||||
|
key={`${type}-${action.href}`}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
const renderGroups = (groups: DocActionGroup[], type: 'download' | 'open') => groups.map((group) => (
|
||||||
|
<div className="docMenuFallbackGroup" key={`${type}-${group.title}`}>
|
||||||
|
<div className="docMenuFallbackTitle">{group.title}</div>
|
||||||
|
<div className="docMenuFallbackLinks">{renderActions(group.actions, type)}</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="docMenuFallback" aria-label="Действия">
|
||||||
|
{(open.groups.length > 0 || open.actions.length > 0) && (
|
||||||
|
<div className="docMenuFallbackSection">
|
||||||
|
<div className="docMenuFallbackSectionTitle">Открыть</div>
|
||||||
|
{open.groups.length > 0
|
||||||
|
? renderGroups(open.groups, 'open')
|
||||||
|
: <div className="docMenuFallbackLinks">{renderActions(open.actions, 'open')}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(download.groups.length > 0 || download.actions.length > 0) && (
|
||||||
|
<div className="docMenuFallbackSection">
|
||||||
|
<div className="docMenuFallbackSectionTitle">Скачать</div>
|
||||||
|
{download.groups.length > 0
|
||||||
|
? renderGroups(download.groups, 'download')
|
||||||
|
: <div className="docMenuFallbackLinks">{renderActions(download.actions, 'download')}</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocActionsMenu({ groups }: { groups: DocActionGroups }) {
|
||||||
|
const [isHydrated, setIsHydrated] = useState(false)
|
||||||
|
const open = splitActionCollection(groups.open)
|
||||||
|
const download = splitActionCollection(groups.download)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeoutId = window.setTimeout(() => setIsHydrated(true), 0)
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timeoutId)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!isHydrated) return <DocActionsFallback groups={groups} />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="docMenu">
|
||||||
|
{(open.groups.length > 0 || open.actions.length > 0) && (
|
||||||
|
<DocActionsMenuButton actions={open.actions} groups={open.groups} label="Открыть" type="open" />
|
||||||
|
)}
|
||||||
|
{(download.groups.length > 0 || download.actions.length > 0) && (
|
||||||
|
<DocActionsMenuButton actions={download.actions} groups={download.groups} label="Скачать" type="download" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function DocIcon({ mark }: { mark: string }) {
|
function DocIcon({ mark }: { mark: string }) {
|
||||||
if (mark === 'SLM') {
|
if (mark === 'SLM') {
|
||||||
return (
|
return (
|
||||||
@@ -237,86 +401,93 @@ function DocIcon({ mark }: { mark: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const theme = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<MantineProvider theme={mantineTheme} forceColorScheme={theme.resolvedTheme}>
|
||||||
<section className="hero" aria-labelledby="page-title">
|
<main className="page">
|
||||||
<h1 className="title" id="page-title">Документация</h1>
|
<section className="hero" aria-labelledby="page-title">
|
||||||
<p className="lead">
|
<h1 className="title" id="page-title">Документация</h1>
|
||||||
Единое пространство для идей, черновиков и первых версий документаций,
|
<p className="lead">
|
||||||
которые ещё формируются и постепенно становятся самостоятельными материалами.
|
Единое пространство для идей, черновиков и первых версий документаций,
|
||||||
</p>
|
которые ещё формируются и постепенно становятся самостоятельными материалами.
|
||||||
<div className="controls">
|
</p>
|
||||||
<a className="repoLink" href={repositoryUrl} target="_blank" rel="noopener noreferrer">
|
<div className="controls">
|
||||||
<GithubIcon />
|
<a className="repoLink" href={repositoryUrl} target="_blank" rel="noopener noreferrer">
|
||||||
<span>Репозиторий</span>
|
<GithubIcon />
|
||||||
</a>
|
<span>Репозиторий</span>
|
||||||
<ThemeToggle />
|
</a>
|
||||||
</div>
|
<ThemeToggle {...theme} />
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="docsPanel" aria-label="Быстрые переходы">
|
<section className="docsPanel" aria-label="Быстрые переходы">
|
||||||
<div className="docsPanelHeader">
|
<div className="docsPanelHeader">
|
||||||
<span>Документация</span>
|
<span>Документация</span>
|
||||||
<span>{docs.length} направления</span>
|
<span>{docs.length} направления</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{docs.map((doc) => {
|
{docs.map((doc) => {
|
||||||
const isAvailable = Boolean(doc.href)
|
const isAvailable = Boolean(doc.href)
|
||||||
|
const hasActionGroups = Boolean(doc.actionGroups?.open?.length || doc.actionGroups?.download?.length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className="docItem"
|
className="docItem"
|
||||||
data-accent={doc.accent}
|
data-accent={doc.accent}
|
||||||
data-state={isAvailable ? 'available' : 'planned'}
|
data-state={isAvailable ? 'available' : 'planned'}
|
||||||
key={doc.href ?? doc.title}
|
key={doc.title}
|
||||||
>
|
>
|
||||||
{isAvailable && (
|
{isAvailable && (
|
||||||
<a className="docCardLink" href={doc.href} aria-label={`Открыть ${doc.title}`} />
|
<a className="docCardLink" href={doc.href} aria-label={`Открыть ${doc.title}`} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="docMain">
|
<div className="docMain">
|
||||||
<span className="docMark" aria-hidden="true">
|
<span className="docMark" aria-hidden="true">
|
||||||
<DocIcon mark={doc.mark} />
|
<DocIcon mark={doc.mark} />
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
<div className="docMeta">
|
|
||||||
{doc.label}
|
|
||||||
</div>
|
|
||||||
<h2>{doc.title}</h2>
|
|
||||||
<p>{doc.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="docActions">
|
|
||||||
{isAvailable ? (
|
|
||||||
<a className="docStatus docStatusLink" href={doc.href}>
|
|
||||||
Открыть ->
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span className="docStatus" aria-disabled="true">
|
|
||||||
{doc.status}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
<div>
|
||||||
|
<div className="docMeta">
|
||||||
{doc.links.length > 0 && (
|
{doc.label}
|
||||||
<div className="docLinks" aria-label="LLM-артефакты">
|
</div>
|
||||||
{doc.links.map((link) => (
|
<h2>{doc.title}</h2>
|
||||||
<a className="docLink" href={link.href} key={link.href}>
|
<p>{doc.description}</p>
|
||||||
{link.label}
|
|
||||||
</a>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer className="footer">
|
<div className="docActions">
|
||||||
Автор документации: <a href={authorUrl}>Сергей Громов</a>
|
{hasActionGroups ? (
|
||||||
</footer>
|
<DocActionsMenu groups={doc.actionGroups ?? {}} />
|
||||||
</main>
|
) : isAvailable ? (
|
||||||
|
<a className="docStatus docStatusLink" href={doc.href}>
|
||||||
|
Открыть ->
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="docStatus" aria-disabled="true">
|
||||||
|
{doc.status}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{doc.links.length > 0 && (
|
||||||
|
<div className="docLinks" aria-label="LLM-артефакты">
|
||||||
|
{doc.links.map((link) => (
|
||||||
|
<a className="docLink" href={link.href} key={link.href}>
|
||||||
|
{link.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer className="footer">
|
||||||
|
Автор документации: <a href={authorUrl}>Сергей Громов</a>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
</MantineProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,23 @@ export type DocLink = {
|
|||||||
href: string
|
href: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DocAction = {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocActionGroup = {
|
||||||
|
title: string
|
||||||
|
actions: DocAction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocActionCollection = DocAction[] | DocActionGroup[]
|
||||||
|
|
||||||
|
export type DocActionGroups = {
|
||||||
|
open?: DocActionCollection
|
||||||
|
download?: DocActionCollection
|
||||||
|
}
|
||||||
|
|
||||||
export type DocCard = {
|
export type DocCard = {
|
||||||
title: string
|
title: string
|
||||||
label: string
|
label: string
|
||||||
@@ -12,6 +29,7 @@ export type DocCard = {
|
|||||||
status: string
|
status: string
|
||||||
accent: string
|
accent: string
|
||||||
links: DocLink[]
|
links: DocLink[]
|
||||||
|
actionGroups?: DocActionGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
export const docs: DocCard[] = [
|
export const docs: DocCard[] = [
|
||||||
@@ -23,10 +41,44 @@ export const docs: DocCard[] = [
|
|||||||
href: '/slm-design/',
|
href: '/slm-design/',
|
||||||
status: 'Доступно',
|
status: 'Доступно',
|
||||||
accent: 'violet',
|
accent: 'violet',
|
||||||
links: [
|
links: [],
|
||||||
{ label: 'llms.txt', href: '/slm-design/llms.txt' },
|
actionGroups: {
|
||||||
{ label: 'llms-full.txt', href: '/slm-design/llms-full.txt' },
|
open: [
|
||||||
],
|
{
|
||||||
|
title: 'Читать',
|
||||||
|
actions: [
|
||||||
|
{ label: 'SLM Документация', href: '/slm-design/' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Skill для CLI-агентов',
|
||||||
|
actions: [
|
||||||
|
{ label: 'slm-design/SKILL.md', href: '/slm-design/skill/.opencode/skills/slm-design/SKILL.md' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI агентам',
|
||||||
|
actions: [
|
||||||
|
{ label: 'llms.txt', href: '/slm-design/llms.txt' },
|
||||||
|
{ label: 'llms-full.txt', href: '/slm-design/llms-full.txt' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
download: [
|
||||||
|
{
|
||||||
|
title: 'Документация MD',
|
||||||
|
actions: [
|
||||||
|
{ label: 'slm-design.zip', href: '/slm-design/slm-design.zip' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Skills (Claude code / OpenCode)',
|
||||||
|
actions: [
|
||||||
|
{ label: 'slm-design.skill.zip', href: '/slm-design/skill/slm-design.skill.zip' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'NextJS Style Guide',
|
title: 'NextJS Style Guide',
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import '@mantine/core/styles/baseline.css'
|
||||||
|
import '@mantine/core/styles/default-css-variables.css'
|
||||||
|
import '@mantine/core/styles/global.css'
|
||||||
|
import '@mantine/core/styles/Button.css'
|
||||||
|
import '@mantine/core/styles/Menu.css'
|
||||||
|
import '@mantine/core/styles/Popover.css'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user