mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
chore: add prettier, eslint, fix some components
This commit is contained in:
@@ -10,6 +10,23 @@
|
|||||||
Не использовать npm.
|
Не использовать npm.
|
||||||
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
||||||
тесты: `pnpm --dir web test`.
|
тесты: `pnpm --dir web test`.
|
||||||
|
- Форматирование: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`,
|
||||||
|
конфиг `web/.prettierrc`, игнор `web/.prettierignore`). Проверка без записи:
|
||||||
|
`pnpm --dir web exec prettier --check .`.
|
||||||
|
- Линтинг: `pnpm --dir web lint` (ESLint, flat-конфиг `web/eslint.config.js`).
|
||||||
|
Устроен инкрементально:
|
||||||
|
- На **весь код** — парсинг TS/Svelte + правило
|
||||||
|
`@typescript-eslint/consistent-type-imports` (запрет инлайн-тип-импортов).
|
||||||
|
- Полные `recommended`-наборы (`eslint` + `typescript-eslint` + `eslint-plugin-svelte`)
|
||||||
|
навешены **только на новый код** (`src/lib/components/kit/**`,
|
||||||
|
`src/routes/preview/**`), чтобы старый код не засыпался предсуществующими
|
||||||
|
ошибками. Когда старый дизайн удалён (C19), scoped-блок убирается и
|
||||||
|
`recommended` включается на весь код.
|
||||||
|
- **Scoped-пути двигаются вместе с папками** (см. `plan-redesign.md` §10):
|
||||||
|
на C17 `src/routes/preview/**` → `src/routes/**` (preview переезжает на
|
||||||
|
реальные маршруты), на C19 при переименовании `kit/`→`ui/` — и компонентный
|
||||||
|
glob. Не оставлять устаревшие пути в `eslint.config.js`.
|
||||||
|
- Для `*.svelte` выключен `prefer-const` (пропсы в Svelte 5 пишутся через `let`).
|
||||||
|
|
||||||
## Правила кода
|
## Правила кода
|
||||||
|
|
||||||
@@ -20,20 +37,27 @@
|
|||||||
|
|
||||||
```svelte
|
```svelte
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string;
|
label: string;
|
||||||
accent?: boolean;
|
accent?: boolean;
|
||||||
children?: import('svelte').Snippet;
|
children?: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { label, accent = false, children }: Props = $props();
|
let { label, accent = false, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Не использовать инлайн-дженерик `$props<{ ... }>()` — он тяжело читается и
|
Не использовать инлайн-дженерик `$props<{ ... }>()` — он тяжело читается и
|
||||||
разносит тип и деструктуризацию по разным местам.
|
разносит тип и деструктуризацию по разным местам. Также **не использовать
|
||||||
|
инлайн-импорты в типах** (`children?: import('svelte').Snippet;`) — все
|
||||||
|
`import type` поднимаются наверх файла. Это правило **включено в ESLint**
|
||||||
|
(`@typescript-eslint/consistent-type-imports`, `prefer: 'type-imports'`).
|
||||||
|
|
||||||
> Позже планируется линтер/правило (eslint-plugin-svelte / custom rule),
|
> Правило «всегда `interface Props` + `let {...}: Props = $props()`» стандартным
|
||||||
> запрещающее `$props<...>()` и требующее `interface Props`.
|
> ESLint-правилом не покрывается — остаётся конвенцией. Кастомное правило
|
||||||
|
> (eslint-plugin-svelte / свой visitor) — TODO, позже.
|
||||||
|
|
||||||
### Дизайн: новый визуальный язык
|
### Дизайн: новый визуальный язык
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -342,13 +342,23 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
|
|
||||||
- [ ] **[C17]** Перенести `preview/*` → реальные маршруты (move), удалить
|
- [ ] **[C17]** Перенести `preview/*` → реальные маршруты (move), удалить
|
||||||
`(old)/`, корневой `+layout.svelte` импортирует `design2.css` + новый shell.
|
`(old)/`, корневой `+layout.svelte` импортирует `design2.css` + новый shell.
|
||||||
Убрать флаг/кнопку.
|
Убрать флаг/кнопку. **И обновить `newCode` в `web/eslint.config.js`:** заменить
|
||||||
|
`src/routes/preview/**` на `src/routes/**` (после переезда все маршруты —
|
||||||
|
новый дизайн), компонентный glob `src/lib/components/kit/**` пока оставить.
|
||||||
- [ ] **[C18]** Правки импортов/редиректов после переноса, проверка билда.
|
- [ ] **[C18]** Правки импортов/редиректов после переноса, проверка билда.
|
||||||
|
|
||||||
### Шаг 6. Удаление старого
|
### Шаг 6. Удаление старого
|
||||||
|
|
||||||
- [ ] **[C19]** Удалить старое `ui/`, прочие старые компоненты, старый `app.css`,
|
- [ ] **[C19]** Удалить старое `ui/`, прочие старые компоненты, старый `app.css`,
|
||||||
неиспользуемые классы. Переименовать `kit/` → `ui/` (если решено на C1).
|
неиспользуемые классы. Переименовать `kit/` → `ui/` (если решено на C1 —
|
||||||
|
тогда в `eslint.config.js` обновить и компонентный glob).
|
||||||
|
**И раскрыть ESLint `recommended` на весь код** — убрать scoped-блок
|
||||||
|
`newCode` в `web/eslint.config.js` (см. AGENTS.md), прогнать `lint` и
|
||||||
|
починить всплывшие ошибки в оставшемся коде.
|
||||||
|
_Примечание: доменные модули (`lib/core`, `lib/registry`, тесты и т.п.) —
|
||||||
|
это не «старый дизайн», у них свой линт-долг; расширение `recommended` на
|
||||||
|
них может выдать много ошибок. Решить на C19: либо чиним сразу, либо
|
||||||
|
расширяем scoped только на `src/routes/**` + `src/lib/components/**`._
|
||||||
- [ ] **[C20]** Финальный проход по брейкпоинтам 1200/1100/800/480; build без
|
- [ ] **[C20]** Финальный проход по брейкпоинтам 1200/1100/800/480; build без
|
||||||
предупреждений; `grep` по старым токенам/классам пуст.
|
предупреждений; `grep` по старым токенам/классам пуст.
|
||||||
- [ ] **[C21]** _(опционально)_ Удалить `refs/` и `refs-html/` из репо — они
|
- [ ] **[C21]** _(опционально)_ Удалить `refs/` и `refs-html/` из репо — они
|
||||||
@@ -357,7 +367,7 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
> Если какой-то коммит тянет за собой > 500 строк (например, C6/C7),
|
> Если какой-то коммит тянет за собой > 500 строк (например, C6/C7),
|
||||||
> разбивать на под-коммиты по 2–4 компонента. Тесты/билд прогонять после
|
> разбивать на под-коммиты по 2–4 компонента. Тесты/билд прогонять после
|
||||||
> каждого коммита, чтобы регрессия локализовалась одним шагом назад.
|
> каждого коммита, чтобы регрессия локализовалась одним шагом назад.
|
||||||
|
>
|
||||||
> **Атомарность и точки «поломки» между коммитами.**
|
> **Атомарность и точки «поломки» между коммитами.**
|
||||||
>
|
>
|
||||||
> - C1+C2 уже сделаны вместе (связаны: нейтральный root без `(old)`-layout
|
> - C1+C2 уже сделаны вместе (связаны: нейтральный root без `(old)`-layout
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "pnpm --dir web dev",
|
"dev": "pnpm --dir web dev",
|
||||||
"build": "pnpm --dir web build"
|
"build": "pnpm --dir web build",
|
||||||
|
"format": "pnpm --dir web format"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Ku6epXBOCTuK",
|
"author": "Ku6epXBOCTuK",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
.svelte-kit
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
static
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 80,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"plugins": ["prettier-plugin-svelte"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.md",
|
||||||
|
"options": {
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import prettier from "eslint-config-prettier";
|
||||||
|
import svelte from "eslint-plugin-svelte";
|
||||||
|
import globals from "globals";
|
||||||
|
import svelteParser from "svelte-eslint-parser";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
// Новый код редизайна: к нему применяем полные recommended-наборы уже сейчас.
|
||||||
|
// Когда старый дизайн удалим (C19), этот scoped-блок убирается и recommended
|
||||||
|
// включается на весь код (см. план-redesign §10, шаг 6).
|
||||||
|
const newCode = ["**/src/lib/components/kit/**", "**/src/routes/preview/**"];
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"**/node_modules/**",
|
||||||
|
"**/build/**",
|
||||||
|
"**/.svelte-kit/**",
|
||||||
|
"**/dist/**",
|
||||||
|
"**/static/**",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Минимально на весь код: парсинг TS/Svelte + запрет инлайн-тип-импортов.
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.svelte.ts", "**/*.svelte.js", "**/*.svelte"],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
projectService: {
|
||||||
|
allowDefaultProject: [
|
||||||
|
"vitest.config.ts",
|
||||||
|
"eslint.config.js",
|
||||||
|
"svelte.config.js",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
extraFileExtensions: [".svelte"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": tseslint.plugin,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Инлайн-тип-импорты (import('x').Y) запрещены — только
|
||||||
|
// `import type { Y } from 'x'` наверху файла.
|
||||||
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
"error",
|
||||||
|
{ prefer: "type-imports", fixStyle: "separate-type-imports" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.ts", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.svelte"],
|
||||||
|
languageOptions: {
|
||||||
|
parser: svelteParser,
|
||||||
|
parserOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Полные recommended-наборы — только на новый код.
|
||||||
|
...[
|
||||||
|
...(Array.isArray(js.configs.recommended)
|
||||||
|
? js.configs.recommended
|
||||||
|
: [js.configs.recommended]),
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...svelte.configs["flat/recommended"],
|
||||||
|
].map((cfg) => ({
|
||||||
|
...cfg,
|
||||||
|
files: newCode,
|
||||||
|
})),
|
||||||
|
|
||||||
|
// В Svelte 5 пропсы деструктурируются через `let` (конвенция документации и
|
||||||
|
// наш AGENTS.md), поэтому prefer-const на них — ложноположительный.
|
||||||
|
{
|
||||||
|
files: ["**/*.svelte"],
|
||||||
|
rules: {
|
||||||
|
"prefer-const": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// prettier — последним, чтобы гасить форматирующие правила из recommended.
|
||||||
|
prettier,
|
||||||
|
);
|
||||||
+12
-1
@@ -10,15 +10,26 @@
|
|||||||
"prepare": "svelte-kit sync || echo ''",
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"test": "vitest run"
|
"test": "vitest run",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.63.0",
|
"@sveltejs/kit": "^2.63.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||||
|
"eslint": "^10.9.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-svelte": "^3.23.0",
|
||||||
|
"globals": "^17.11.0",
|
||||||
|
"prettier": "^3.9.6",
|
||||||
|
"prettier-plugin-svelte": "^4.1.1",
|
||||||
"svelte": "^5.56.1",
|
"svelte": "^5.56.1",
|
||||||
"svelte-check": "^4.6.0",
|
"svelte-check": "^4.6.0",
|
||||||
|
"svelte-eslint-parser": "^1.8.1",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
|
"typescript-eslint": "^8.67.0",
|
||||||
"vite": "^8.0.16",
|
"vite": "^8.0.16",
|
||||||
"vitest": "^4.1.11"
|
"vitest": "^4.1.11"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+969
-20
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BlueprintGrid from './BlueprintGrid.svelte';
|
import BlueprintGrid from "./BlueprintGrid.svelte";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,8 +6,16 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(to right, color-mix(in srgb, var(--line) 45%, transparent) 1px, transparent 1px),
|
linear-gradient(
|
||||||
linear-gradient(to bottom, color-mix(in srgb, var(--line) 45%, transparent) 1px, transparent 1px);
|
to right,
|
||||||
|
color-mix(in srgb, var(--line) 45%, transparent) 1px,
|
||||||
|
transparent 1px
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
color-mix(in srgb, var(--line) 45%, transparent) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
background-size: 32px 32px;
|
background-size: 32px 32px;
|
||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,44 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: import('svelte').Snippet;
|
children?: Snippet;
|
||||||
size?: 'sm' | 'large';
|
size?: "sm" | "large";
|
||||||
}
|
}
|
||||||
let { children, size = 'large' }: Props = $props();
|
let { children, size = "large" }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="checker-canvas checker-canvas--{size}">
|
<div class="checker-canvas checker-canvas--{size}">
|
||||||
{#if children}
|
{#if children}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.checker-canvas {
|
.checker-canvas {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #0b0f14;
|
background-color: #0b0f14;
|
||||||
background-image: linear-gradient(45deg, #1a2129 25%, transparent 25%),
|
background-image:
|
||||||
linear-gradient(-45deg, #1a2129 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #1a2129 75%),
|
linear-gradient(45deg, #1a2129 25%, transparent 25%),
|
||||||
linear-gradient(-45deg, transparent 75%, #1a2129 75%);
|
linear-gradient(-45deg, #1a2129 25%, transparent 25%),
|
||||||
background-size: 20px 20px;
|
linear-gradient(45deg, transparent 75%, #1a2129 75%),
|
||||||
background-position:
|
linear-gradient(-45deg, transparent 75%, #1a2129 75%);
|
||||||
0 0,
|
background-size: 20px 20px;
|
||||||
0 10px,
|
background-position:
|
||||||
10px -10px,
|
0 0,
|
||||||
-10px 0;
|
0 10px,
|
||||||
}
|
10px -10px,
|
||||||
|
-10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.checker-canvas--sm {
|
.checker-canvas--sm {
|
||||||
min-height: 180px;
|
min-height: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checker-canvas--large {
|
.checker-canvas--large {
|
||||||
min-height: 360px;
|
min-height: 360px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
accent?: boolean;
|
accent?: boolean;
|
||||||
children?: import('svelte').Snippet;
|
children?: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { accent = false, children }: Props = $props();
|
let { accent = false, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="mono-label" class:accent={accent}>
|
<span class="mono-label" class:accent>
|
||||||
{#if children}
|
{#if children}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import StatusDot from './StatusDot.svelte';
|
import StatusDot from "./StatusDot.svelte";
|
||||||
import MonoLabel from './MonoLabel.svelte';
|
import MonoLabel from "./MonoLabel.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
@import '@fontsource/ibm-plex-sans/400.css';
|
@import "@fontsource/ibm-plex-sans/400.css";
|
||||||
@import '@fontsource/ibm-plex-sans/500.css';
|
@import "@fontsource/ibm-plex-sans/500.css";
|
||||||
@import '@fontsource/ibm-plex-sans/600.css';
|
@import "@fontsource/ibm-plex-sans/600.css";
|
||||||
@import '@fontsource/ibm-plex-sans/700.css';
|
@import "@fontsource/ibm-plex-sans/700.css";
|
||||||
@import '@fontsource/ibm-plex-mono/400.css';
|
@import "@fontsource/ibm-plex-mono/400.css";
|
||||||
@import '@fontsource/ibm-plex-mono/500.css';
|
@import "@fontsource/ibm-plex-mono/500.css";
|
||||||
|
|
||||||
/* Новый дизайн: токены (§2 плана redesign).
|
/* Новый дизайн: токены (§2 плана redesign).
|
||||||
Файл грузится только layout'ом новой ветки (preview/+layout), поэтому
|
Файл грузится только layout'ом новой ветки (preview/+layout), поэтому
|
||||||
глобально не конфликтует со старым app.css. */
|
глобально не конфликтует со старым app.css. */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
|
--font-sans: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
|
||||||
--font-mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', monospace;
|
--font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", monospace;
|
||||||
|
|
||||||
--background: #eef1f4;
|
--background: #eef1f4;
|
||||||
--panel: #f8fafb;
|
--panel: #f8fafb;
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
--radius: 4px;
|
--radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] {
|
[data-theme="dark"] {
|
||||||
--background: #11171d;
|
--background: #11171d;
|
||||||
--panel: #182129;
|
--panel: #182129;
|
||||||
--foreground: #e8eef2;
|
--foreground: #e8eef2;
|
||||||
|
|||||||
Reference in New Issue
Block a user