From 101119911a6619fe4cb1f9a1b4645d75ab195619 Mon Sep 17 00:00:00 2001
From: 3252a8 <3252a8@proton.me>
Date: Wed, 3 Jun 2026 10:55:52 +0300
Subject: [PATCH] refactor(webapp): remove emoji web app logo option
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Drop the emoji-logo feature (and its font picker) from the Web App. Only
an uploaded/linked image logo and favicon remain; when no logo is set,
the default project logo is shown. Existing emoji-logo overrides are
ignored — the keys are gone from the manifest, so the override service
skips them and the app falls back to the default logo.
- Remove WEBAPP_LOGO_USE_EMOJI / WEBAPP_LOGO_EMOJI / WEBAPP_LOGO_EMOJI_FONT
settings, validators, manifest entries and override/runtime plumbing.
- Strip the animated-emoji fetch/cache subsystem, the /webapp-emoji route
and emoji branches from logo/favicon resolution; leftover emoji cache
files are now purged on appearance save.
- Simplify BrandMark to an image-only component and drop the emoji UI
from the admin Appearance section.
- Regenerate the demo settings manifest and clean docs, locales, nginx
and demo data of emoji-logo references.
---
README.md | 4 +-
backend/bot/app/web/admin_api_impl/themes.py | 19 +-
.../app/web/admin_api_impl/webapp_runtime.py | 3 -
.../bot/app/web/admin_settings_manifest.py | 20 -
backend/bot/app/web/webapp/_runtime.py | 2 -
backend/bot/app/web/webapp/application.py | 1 -
backend/bot/app/web/webapp/assets.py | 167 ------
backend/bot/app/web/webapp/routes.py | 4 -
backend/bot/services/email_templates.py | 2 -
.../bot/services/settings_override_service.py | 9 -
backend/config/settings.py | 30 --
deploy/docker/frontend/nginx.conf | 2 +-
docs-site/scripts/generate-email-previews.py | 1 -
docs/configuration/env-vars.md | 3 -
docs/features/admin-panel.md | 6 +-
docs/features/web-app.md | 2 +-
docs/features/webapp-themes.md | 3 +-
docs/getting-started/configuration.md | 2 +-
docs/getting-started/deployment.md | 2 +-
frontend/src/App.svelte | 9 +-
frontend/src/PreviewBoard.svelte | 12 +-
.../admin/sections/AppearanceSection.svelte | 124 +----
.../webapp/TicketMessageBubble.svelte | 2 +-
frontend/src/lib/webapp/BrandMark.svelte | 195 +------
frontend/src/lib/webapp/browser.js | 37 +-
frontend/src/lib/webapp/demoDataset.js | 480 +-----------------
frontend/src/lib/webapp/mockApi.js | 36 --
frontend/src/lib/webapp/previewMock.js | 3 -
.../webapp/settingsManifest.generated.json | 104 ----
locales/en.json | 17 +-
locales/ru.json | 17 +-
tests/test_email_localization.py | 1 -
tests/test_locale_overrides.py | 1 -
tests/test_security.py | 1 -
tests/test_settings.py | 7 -
...st_subscription_lifecycle_notifications.py | 1 -
tests/test_user_email_notifications.py | 1 -
tests/test_webapp_assets.py | 75 +--
tests/test_webapp_route_contract.py | 1 -
39 files changed, 50 insertions(+), 1356 deletions(-)
diff --git a/README.md b/README.md
index 96c9ad9..9444857 100644
--- a/README.md
+++ b/README.md
@@ -98,10 +98,10 @@ docker compose logs -f backend worker frontend
Для каталога тарифов используется `TARIFFS_CONFIG_PATH` со значением по умолчанию `data/tariffs.json`. Пример формата лежит в [data/tariffs.example.json](data/tariffs.example.json), подробности - в [docs/features/tariffs.md](docs/features/tariffs.md).
-В compose-примерах `/app/data` монтируется из папки `./data` рядом с `docker-compose.yml`. Заранее создайте каталог и отдайте его пользователю контейнера. Это нужно для сохранения `data/tariffs.json`, каталога тем `data/themes`, кеша логотипа Web App и animated emoji:
+В compose-примерах `/app/data` монтируется из папки `./data` рядом с `docker-compose.yml`. Заранее создайте каталог и отдайте его пользователю контейнера. Это нужно для сохранения `data/tariffs.json`, каталога тем `data/themes` и кеша логотипа Web App:
```bash
-mkdir -p data/themes data/webapp-logo data/webapp-emoji
+mkdir -p data/themes data/webapp-logo
touch data/locales-overrides.json
chown -R 10001:10001 data
chmod -R u+rwX data
diff --git a/backend/bot/app/web/admin_api_impl/themes.py b/backend/bot/app/web/admin_api_impl/themes.py
index 21efbe4..b68ed08 100644
--- a/backend/bot/app/web/admin_api_impl/themes.py
+++ b/backend/bot/app/web/admin_api_impl/themes.py
@@ -136,10 +136,6 @@ def _favicon_digest(url: str) -> Optional[str]:
return match.group(1) if match else None
-def _emoji_to_codepoints(value: str) -> str:
- return "_".join(f"{ord(char):x}" for char in str(value or "").strip())
-
-
def prune_unused_appearance_assets(settings: Settings) -> None:
keep_logos = {
filename
@@ -156,15 +152,6 @@ def prune_unused_appearance_assets(settings: Settings) -> None:
]
if digest
}
- keep_emoji_prefixes = set()
- if (
- getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False)
- and str(getattr(settings, "WEBAPP_LOGO_EMOJI_FONT", "") or "").strip()
- == "noto-color-animated"
- ):
- codepoints = _emoji_to_codepoints(getattr(settings, "WEBAPP_LOGO_EMOJI", ""))
- if codepoints:
- keep_emoji_prefixes.add(f"{codepoints}.512.")
for path in WEBAPP_UPLOADED_LOGO_DIR.glob("logo-*"):
if path.is_file() and path.name not in keep_logos:
@@ -184,10 +171,9 @@ def prune_unused_appearance_assets(settings: Settings) -> None:
except OSError:
logger.warning("Failed to remove unused webapp favicon set %s", path, exc_info=True)
+ # Emoji logos were removed; purge any leftover animated-emoji cache files.
for path in WEBAPP_EMOJI_CACHE_DIR.glob("*.512.*"):
- if path.is_file() and not any(
- path.name.startswith(prefix) for prefix in keep_emoji_prefixes
- ):
+ if path.is_file():
try:
path.unlink()
except OSError:
@@ -389,7 +375,6 @@ async def admin_appearance_logo_upload_route(request: web.Request) -> web.Respon
request,
{
"WEBAPP_LOGO_URL": logo_url,
- "WEBAPP_LOGO_USE_EMOJI": False,
**(
{"WEBAPP_LOGO_FAVICON_URL": favicon_payload["favicon_url"]}
if favicon_payload.get("favicon_url")
diff --git a/backend/bot/app/web/admin_api_impl/webapp_runtime.py b/backend/bot/app/web/admin_api_impl/webapp_runtime.py
index 83a8f57..00d98e3 100644
--- a/backend/bot/app/web/admin_api_impl/webapp_runtime.py
+++ b/backend/bot/app/web/admin_api_impl/webapp_runtime.py
@@ -13,9 +13,6 @@ WEBAPP_APPEARANCE_SETTING_KEYS = frozenset(
{
"WEBAPP_TITLE",
"WEBAPP_LOGO_URL",
- "WEBAPP_LOGO_USE_EMOJI",
- "WEBAPP_LOGO_EMOJI",
- "WEBAPP_LOGO_EMOJI_FONT",
"WEBAPP_FAVICON_URL",
"WEBAPP_FAVICON_USE_CUSTOM",
"WEBAPP_LOGO_FAVICON_URL",
diff --git a/backend/bot/app/web/admin_settings_manifest.py b/backend/bot/app/web/admin_settings_manifest.py
index 7545b32..8c06835 100644
--- a/backend/bot/app/web/admin_settings_manifest.py
+++ b/backend/bot/app/web/admin_settings_manifest.py
@@ -181,27 +181,7 @@ SETTINGS_MANIFEST: List[SettingField] = [
SettingField(
"WEBAPP_PRIMARY_COLOR", "color", "appearance", "Основной цвет", placeholder="#00fe7a"
),
- SettingField("WEBAPP_LOGO_USE_EMOJI", "bool", "appearance", "Использовать эмоджи-логотип"),
SettingField("WEBAPP_LOGO_URL", "url", "appearance", "URL логотипа"),
- SettingField("WEBAPP_LOGO_EMOJI", "string", "appearance", "Эмоджи-логотип", placeholder="🫥"),
- SettingField(
- "WEBAPP_LOGO_EMOJI_FONT",
- "string",
- "appearance",
- "Шрифт эмоджи-логотипа",
- "Выберите шрифт для отображения эмодзи-логотипа",
- choices=(
- ("system", "Системный (по умолчанию)"),
- ("noto-color", "Noto Color Emoji"),
- ("noto-color-animated", "Noto Color Emoji Animated"),
- ("noto-emoji", "Noto Emoji"),
- ("twemoji", "Twitter Emoji"),
- ("openmoji", "OpenMoji"),
- ("apple", "Apple Color Emoji (local)"),
- ("segoe", "Segoe UI Emoji (local)"),
- ("noto-local", "Noto Emoji (local)"),
- ),
- ),
SettingField(
"WEBAPP_FAVICON_USE_CUSTOM",
"bool",
diff --git a/backend/bot/app/web/webapp/_runtime.py b/backend/bot/app/web/webapp/_runtime.py
index 4da0199..2a578f1 100644
--- a/backend/bot/app/web/webapp/_runtime.py
+++ b/backend/bot/app/web/webapp/_runtime.py
@@ -82,7 +82,6 @@ WEBAPP_DEFAULT_LOGO_PATH = "/webapp-default-logo.webp"
WEBAPP_DEFAULT_FAVICON_DIGEST = "19b2a242e5b7bc2d"
WEBAPP_DEFAULT_FAVICON_DIR = WEBAPP_DEFAULT_BRAND_DIR / "favicons" / WEBAPP_DEFAULT_FAVICON_DIGEST
WEBAPP_DEFAULT_FAVICON_URL = f"{WEBAPP_FAVICON_PATH}/{WEBAPP_DEFAULT_FAVICON_DIGEST}/icon-180.png"
-WEBAPP_EMOJI_CACHE_DIR = APP_ROOT / "data" / "webapp-emoji"
WEBAPP_CONFIG_PLACEHOLDER = ""
WEBAPP_I18N_PLACEHOLDER = ""
WEBAPP_JS_PLACEHOLDER = ""
@@ -92,7 +91,6 @@ DEV_MOCK_END_MARKER = ""
WEBAPP_RATE_LIMIT_WINDOW_SECONDS = 60
WEBAPP_RATE_LIMIT_MAX_REQUESTS = 30
WEBAPP_LOGO_MAX_BYTES = 2 * 1024 * 1024
-WEBAPP_EMOJI_MAX_BYTES = 4 * 1024 * 1024
WEBAPP_THEME_CSS_MAX_BYTES = 512 * 1024
WEBAPP_THEME_ASSET_MAX_BYTES = 1024 * 1024
WEBAPP_THEME_ASSET_CONTENT_TYPES = {
diff --git a/backend/bot/app/web/webapp/application.py b/backend/bot/app/web/webapp/application.py
index 3dce8e2..a5ead66 100644
--- a/backend/bot/app/web/webapp/application.py
+++ b/backend/bot/app/web/webapp/application.py
@@ -33,7 +33,6 @@ def create_subscription_webapp_application(
async def _startup(app_obj: web.Application) -> None:
await _ensure_shared_http_session()
await _warm_webapp_logo_cache(app_obj)
- await _warm_webapp_animated_emoji_cache(app_obj)
await warm_subscription_guides_config(app_obj)
async def _shutdown(app_obj: web.Application) -> None:
diff --git a/backend/bot/app/web/webapp/assets.py b/backend/bot/app/web/webapp/assets.py
index cd53216..7b2aba8 100644
--- a/backend/bot/app/web/webapp/assets.py
+++ b/backend/bot/app/web/webapp/assets.py
@@ -206,9 +206,6 @@ async def theme_asset_route(request: web.Request) -> web.Response:
def _resolve_webapp_logo_url(settings: Settings) -> str:
- if getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- return ""
-
raw_logo_url = (getattr(settings, "WEBAPP_LOGO_URL", None) or "").strip()
if not raw_logo_url:
return WEBAPP_DEFAULT_LOGO_PATH
@@ -303,29 +300,8 @@ def _uploaded_webapp_logo_response(filename: str) -> web.Response:
return response
-def _emoji_to_codepoints(value: str) -> str:
- return "_".join(f"{ord(char):x}" for char in str(value or "").strip())
-
-
-def _webapp_emoji_disk_path(codepoints: str, ext: str) -> Path:
- return WEBAPP_EMOJI_CACHE_DIR / f"{codepoints}.512.{ext}"
-
-
-def _webapp_animated_emoji_source_url(codepoints: str, ext: str) -> str:
- return f"https://fonts.gstatic.com/s/e/notoemoji/latest/{codepoints}/512.{ext}"
-
-
-def _webapp_animated_emoji_asset_path(emoji: str, ext: str = "gif") -> str:
- codepoints = _emoji_to_codepoints(emoji)
- if not codepoints or ext not in {"gif", "webp"}:
- return ""
- return f"/webapp-emoji/{codepoints}/512.{ext}"
-
-
async def webapp_logo_route(request: web.Request) -> web.Response:
settings: Settings = request.app["settings"]
- if getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- raise web.HTTPNotFound(text="webapp_logo_disabled")
raw_logo_url = (settings.WEBAPP_LOGO_URL or "").strip()
if not raw_logo_url:
raise web.HTTPNotFound(text="webapp_logo_not_configured")
@@ -521,37 +497,8 @@ def _webapp_default_brand_file_response(path: Path, content_type: str) -> web.Re
return web.Response(body=body, content_type=content_type)
-async def webapp_animated_emoji_route(request: web.Request) -> web.Response:
- codepoints = str(request.match_info.get("codepoints") or "").strip().lower()
- ext = str(request.match_info.get("ext") or "").strip().lower()
- if not re.fullmatch(r"[0-9a-f]+(?:_[0-9a-f]+)*", codepoints) or ext not in {"gif", "webp"}:
- raise web.HTTPNotFound(text="webapp_emoji_not_found")
-
- emoji_cache_key = f"{codepoints}:{ext}"
- emoji_caches: Dict[str, Tuple[bytes, str]] = request.app.setdefault("webapp_emoji_cache", {})
- emoji_cache = emoji_caches.get(emoji_cache_key)
- if emoji_cache is None:
- cache_lock: asyncio.Lock = request.app.setdefault("webapp_emoji_cache_lock", asyncio.Lock())
- async with cache_lock:
- emoji_cache = emoji_caches.get(emoji_cache_key)
- if emoji_cache is None:
- emoji_cache = await _load_or_fetch_webapp_animated_emoji(codepoints, ext)
- if emoji_cache:
- emoji_caches[emoji_cache_key] = emoji_cache
-
- if not emoji_cache:
- raise web.HTTPNotFound(text="webapp_emoji_unavailable")
-
- body, content_type = emoji_cache
- response = web.Response(body=body, content_type=content_type)
- response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
- return response
-
-
async def _warm_webapp_logo_cache(app: web.Application) -> None:
settings: Settings = app["settings"]
- if getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- return
raw_logo_url = (settings.WEBAPP_LOGO_URL or "").strip()
if not raw_logo_url or not _is_proxyable_webapp_logo_url(raw_logo_url):
return
@@ -573,111 +520,6 @@ async def _warm_webapp_logo_cache(app: web.Application) -> None:
)
-async def _warm_webapp_animated_emoji_cache(app: web.Application) -> None:
- settings: Settings = app["settings"]
- if not getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- return
- if str(settings.WEBAPP_LOGO_EMOJI_FONT or "").strip() != "noto-color-animated":
- return
-
- codepoints = _emoji_to_codepoints(settings.WEBAPP_LOGO_EMOJI)
- if not codepoints:
- return
-
- app.setdefault("webapp_emoji_cache", {})
- app.setdefault("webapp_emoji_cache_lock", asyncio.Lock())
- emoji_caches: Dict[str, Tuple[bytes, str]] = app["webapp_emoji_cache"]
-
- for ext in ("gif", "webp"):
- emoji_cache_key = f"{codepoints}:{ext}"
- if emoji_cache_key in emoji_caches:
- continue
- loaded_emoji = await _load_or_fetch_webapp_animated_emoji(codepoints, ext)
- if loaded_emoji:
- emoji_caches[emoji_cache_key] = loaded_emoji
- if ext == "gif":
- return
-
-
-async def _load_or_fetch_webapp_animated_emoji(
- codepoints: str, ext: str
-) -> Optional[Tuple[bytes, str]]:
- disk_emoji = await asyncio.to_thread(_read_webapp_animated_emoji_from_disk, codepoints, ext)
- if disk_emoji:
- return disk_emoji
-
- fetched_emoji = await _fetch_webapp_animated_emoji(codepoints, ext)
- if fetched_emoji:
- await asyncio.to_thread(
- _write_webapp_animated_emoji_to_disk, codepoints, ext, fetched_emoji
- )
- return fetched_emoji
-
-
-def _read_webapp_animated_emoji_from_disk(codepoints: str, ext: str) -> Optional[Tuple[bytes, str]]:
- path = _webapp_emoji_disk_path(codepoints, ext)
- try:
- body = path.read_bytes()
- except OSError:
- return None
-
- if not body or len(body) > WEBAPP_EMOJI_MAX_BYTES:
- return None
- return body, "image/gif" if ext == "gif" else "image/webp"
-
-
-def _write_webapp_animated_emoji_to_disk(
- codepoints: str, ext: str, emoji: Tuple[bytes, str]
-) -> None:
- body, _content_type = emoji
- if not body or len(body) > WEBAPP_EMOJI_MAX_BYTES:
- return
-
- path = _webapp_emoji_disk_path(codepoints, ext)
- try:
- WEBAPP_EMOJI_CACHE_DIR.mkdir(parents=True, exist_ok=True)
- path.write_bytes(body)
- except OSError as exc:
- logger.warning("Failed to write WEBAPP animated emoji cache: %s", exc)
-
-
-async def _fetch_webapp_animated_emoji(codepoints: str, ext: str) -> Optional[Tuple[bytes, str]]:
- try:
- session = await _get_shared_http_session()
- timeout = ClientTimeout(total=4)
- source_url = _webapp_animated_emoji_source_url(codepoints, ext)
- async with session.get(
- source_url,
- allow_redirects=False,
- headers={"Accept": "image/gif,image/webp,image/*,*/*;q=0.8"},
- timeout=timeout,
- ) as response:
- if response.status != 200:
- return None
-
- content_type = (
- (response.headers.get("Content-Type") or "").split(";", 1)[0].strip().lower()
- )
- expected_content_type = "image/gif" if ext == "gif" else "image/webp"
- if content_type and content_type != expected_content_type:
- return None
-
- body = bytearray()
- async for chunk in response.content.iter_chunked(64 * 1024):
- body.extend(chunk)
- if len(body) > WEBAPP_EMOJI_MAX_BYTES:
- logger.warning("WEBAPP animated emoji exceeded the 4 MiB limit.")
- return None
-
- if not body:
- return None
-
- return bytes(body), expected_content_type
- except Exception as exc:
- logger.warning("Failed to fetch WEBAPP animated emoji: %s", exc)
- return None
-
-
async def _load_or_fetch_webapp_logo(logo_url: str) -> Optional[Tuple[bytes, str]]:
disk_logo = await asyncio.to_thread(_read_webapp_logo_from_disk, logo_url)
if disk_logo:
@@ -1144,9 +986,6 @@ def _build_webapp_bootstrap_payload(request: web.Request) -> Dict[str, Any]:
"themesDir": settings.WEBAPP_THEMES_DIR,
"themePreviewKey": preview_key,
"logoUrl": cached["logo_url"],
- "logoUseEmoji": bool(settings.WEBAPP_LOGO_USE_EMOJI),
- "logoEmoji": settings.WEBAPP_LOGO_EMOJI,
- "logoEmojiFont": settings.WEBAPP_LOGO_EMOJI_FONT,
"faviconUrl": cached["favicon_url"],
"faviconUseCustom": bool(settings.WEBAPP_FAVICON_USE_CUSTOM),
"apiBase": "/api",
@@ -1308,12 +1147,6 @@ async def index_route(request: web.Request) -> web.Response:
f'',
)
brand_asset_url = cached["logo_url"]
- if (
- not brand_asset_url
- and settings.WEBAPP_LOGO_USE_EMOJI
- and settings.WEBAPP_LOGO_EMOJI_FONT == "noto-color-animated"
- ):
- brand_asset_url = _webapp_animated_emoji_asset_path(settings.WEBAPP_LOGO_EMOJI)
if brand_asset_url:
html = html.replace(
"",
diff --git a/backend/bot/app/web/webapp/routes.py b/backend/bot/app/web/webapp/routes.py
index c9b7c13..e6535b4 100644
--- a/backend/bot/app/web/webapp/routes.py
+++ b/backend/bot/app/web/webapp/routes.py
@@ -46,10 +46,6 @@ def setup_subscription_webapp_routes(app: web.Application) -> None:
rf"{WEBAPP_FAVICON_PATH}/{{digest:[0-9a-f]{{16}}}}/{{filename:[A-Za-z0-9_.-]+}}",
webapp_favicon_route,
)
- app.router.add_get(
- r"/webapp-emoji/{codepoints:[0-9a-f_]+}/512.{ext:gif|webp}",
- webapp_animated_emoji_route,
- )
app.router.add_get("/subscription_webapp.{asset_hash:[0-9a-f]{8}}.css", css_asset_route)
app.router.add_get("/subscription_webapp.css", css_asset_route)
app.router.add_get(
diff --git a/backend/bot/services/email_templates.py b/backend/bot/services/email_templates.py
index f8ed96a..0b09c1f 100644
--- a/backend/bot/services/email_templates.py
+++ b/backend/bot/services/email_templates.py
@@ -48,8 +48,6 @@ def _safe_color(value: Optional[str]) -> str:
def _public_logo_url(settings: Settings) -> Optional[str]:
"""Email recipients can't reach the in-app /webapp-logo proxy, so only a
stored public https URL can be used directly. Anything else is dropped."""
- if getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- return None
raw = (settings.WEBAPP_LOGO_URL or "").strip()
if not raw:
return None
diff --git a/backend/bot/services/settings_override_service.py b/backend/bot/services/settings_override_service.py
index 822b0ec..7702a32 100644
--- a/backend/bot/services/settings_override_service.py
+++ b/backend/bot/services/settings_override_service.py
@@ -28,10 +28,7 @@ from db.dal import app_settings_dal
logger = logging.getLogger(__name__)
APPEARANCE_OVERRIDE_KEYS = {
- "WEBAPP_LOGO_USE_EMOJI",
"WEBAPP_LOGO_URL",
- "WEBAPP_LOGO_EMOJI",
- "WEBAPP_LOGO_EMOJI_FONT",
"WEBAPP_FAVICON_USE_CUSTOM",
"WEBAPP_FAVICON_URL",
"WEBAPP_LOGO_FAVICON_URL",
@@ -167,12 +164,6 @@ def _appearance_snapshot(settings: Settings) -> Dict[str, Any]:
snapshot["WEBAPP_FAVICON_URL"] = favicon_url
if getattr(settings, "WEBAPP_FAVICON_USE_CUSTOM", False):
snapshot["WEBAPP_FAVICON_USE_CUSTOM"] = True
- if getattr(settings, "WEBAPP_LOGO_USE_EMOJI", False):
- snapshot["WEBAPP_LOGO_USE_EMOJI"] = True
- snapshot["WEBAPP_LOGO_EMOJI"] = getattr(settings, "WEBAPP_LOGO_EMOJI", "")
- emoji_font = getattr(settings, "WEBAPP_LOGO_EMOJI_FONT", "")
- if emoji_font and emoji_font != "system":
- snapshot["WEBAPP_LOGO_EMOJI_FONT"] = emoji_font
primary_color = getattr(settings, "WEBAPP_PRIMARY_COLOR", None)
if primary_color and primary_color != "#00fe7a":
snapshot["WEBAPP_PRIMARY_COLOR"] = primary_color
diff --git a/backend/config/settings.py b/backend/config/settings.py
index 4b16f1a..6d8f5ae 100644
--- a/backend/config/settings.py
+++ b/backend/config/settings.py
@@ -59,9 +59,6 @@ class WebAppSettings(BaseModel):
title: str
primary_color: str
logo_url: Optional[str]
- logo_use_emoji: bool
- logo_emoji: str
- logo_emoji_font: str
favicon_use_custom: bool
favicon_url: Optional[str]
logo_favicon_url: Optional[str]
@@ -395,15 +392,6 @@ class Settings(BaseSettings):
),
)
WEBAPP_LOGO_URL: Optional[str] = Field(default=None)
- WEBAPP_LOGO_USE_EMOJI: bool = Field(default=False)
- WEBAPP_LOGO_EMOJI: str = Field(default="🫥")
- WEBAPP_LOGO_EMOJI_FONT: str = Field(
- default="system",
- description=(
- "Emoji font for logo fallback: system, noto-color, noto-color-animated, "
- "noto-emoji, twemoji, openmoji, apple, segoe, noto-local"
- ),
- )
WEBAPP_FAVICON_USE_CUSTOM: bool = Field(default=False)
WEBAPP_FAVICON_URL: Optional[str] = Field(default=None)
WEBAPP_LOGO_FAVICON_URL: Optional[str] = Field(default=None)
@@ -566,9 +554,6 @@ class Settings(BaseSettings):
title=self.WEBAPP_TITLE,
primary_color=self.WEBAPP_PRIMARY_COLOR,
logo_url=self.WEBAPP_LOGO_URL,
- logo_use_emoji=self.WEBAPP_LOGO_USE_EMOJI,
- logo_emoji=self.WEBAPP_LOGO_EMOJI,
- logo_emoji_font=self.WEBAPP_LOGO_EMOJI_FONT,
favicon_use_custom=self.WEBAPP_FAVICON_USE_CUSTOM,
favicon_url=self.WEBAPP_FAVICON_URL,
logo_favicon_url=self.WEBAPP_LOGO_FAVICON_URL,
@@ -810,21 +795,6 @@ class Settings(BaseSettings):
def ignore_deprecated_webapp_logo_url_env(cls, _value):
return None
- @field_validator("WEBAPP_LOGO_USE_EMOJI", mode="before")
- @classmethod
- def ignore_deprecated_webapp_logo_use_emoji_env(cls, _value):
- return False
-
- @field_validator("WEBAPP_LOGO_EMOJI", mode="before")
- @classmethod
- def ignore_deprecated_webapp_logo_emoji_env(cls, _value):
- return "🫥"
-
- @field_validator("WEBAPP_LOGO_EMOJI_FONT", mode="before")
- @classmethod
- def ignore_deprecated_webapp_logo_emoji_font_env(cls, _value):
- return "system"
-
@field_validator("WEBAPP_FAVICON_USE_CUSTOM", mode="before")
@classmethod
def ignore_deprecated_webapp_favicon_use_custom_env(cls, _value):
diff --git a/deploy/docker/frontend/nginx.conf b/deploy/docker/frontend/nginx.conf
index 00bcfab..fa71122 100644
--- a/deploy/docker/frontend/nginx.conf
+++ b/deploy/docker/frontend/nginx.conf
@@ -84,7 +84,7 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
}
- location ~ ^/(webapp-logo|webapp-uploaded-logo|webapp-favicon|webapp-emoji|webapp-theme-css|webapp-theme-assets)/ {
+ location ~ ^/(webapp-logo|webapp-uploaded-logo|webapp-favicon|webapp-theme-css|webapp-theme-assets)/ {
proxy_pass http://backend:8081;
proxy_http_version 1.1;
proxy_set_header Host $host;
diff --git a/docs-site/scripts/generate-email-previews.py b/docs-site/scripts/generate-email-previews.py
index 84d54a4..ac36473 100644
--- a/docs-site/scripts/generate-email-previews.py
+++ b/docs-site/scripts/generate-email-previews.py
@@ -64,7 +64,6 @@ def settings():
DEFAULT_LANGUAGE=LANGUAGE,
EMAIL_CODE_TTL_SECONDS=600,
WEBAPP_LOGO_URL="",
- WEBAPP_LOGO_USE_EMOJI=False,
WEBAPP_PRIMARY_COLOR="#00fe7a",
WEBAPP_TITLE="remnawave-minishop",
)
diff --git a/docs/configuration/env-vars.md b/docs/configuration/env-vars.md
index e110aed..ca5b671 100644
--- a/docs/configuration/env-vars.md
+++ b/docs/configuration/env-vars.md
@@ -161,9 +161,6 @@
| `TELEGRAM_OAUTH_REQUEST_ACCESS` | `.env` | Дополнительные разрешения, например `write`. |
| `WEBAPP_PRIMARY_COLOR` | Админка | Устаревшее env-поле, игнорируется. |
| `WEBAPP_LOGO_URL` | Админка | Устаревшее env-поле, игнорируется. |
-| `WEBAPP_LOGO_USE_EMOJI` | Админка | Устаревшее env-поле, игнорируется. |
-| `WEBAPP_LOGO_EMOJI` | Админка | Устаревшее env-поле, игнорируется. |
-| `WEBAPP_LOGO_EMOJI_FONT` | Админка | Устаревшее env-поле, игнорируется. |
| `WEBAPP_FAVICON_USE_CUSTOM` | Админка | Устаревшее env-поле, игнорируется. |
| `WEBAPP_FAVICON_URL` | Админка | Устаревшее env-поле, игнорируется. |
| `WEBAPP_LOGO_FAVICON_URL` | Админка | Устаревшее env-поле, игнорируется. |
diff --git a/docs/features/admin-panel.md b/docs/features/admin-panel.md
index 1847075..afe87d4 100644
--- a/docs/features/admin-panel.md
+++ b/docs/features/admin-panel.md
@@ -9,7 +9,7 @@
- блокировка пользователей, входящий список тикетов поддержки, рассылки, промокоды и просмотр логов;
- ручная синхронизация с Remnawave;
- редактор разрешенных настроек приложения из manifest-файла;
-- раздел **Внешний вид** для логотипа, emoji-логотипа, выбора темы, accent-цвета, масштаба логотипа и предпросмотра тем;
+- раздел **Внешний вид** для логотипа, выбора темы, accent-цвета, масштаба логотипа и предпросмотра тем;
- раздел **Инструкции подключения** для встроенной страницы установки, поведения кнопок бота и Remnawave Subscription Page config;
- раздел **Бэкапы** для просмотра локальных ZIP-архивов, загрузки архива и восстановления БД/compose-папки;
- редактор JSON-каталога тарифов;
@@ -49,7 +49,7 @@
В manifest сейчас входят:
- общие параметры: язык, валюта, ссылки поддержки, документы, обязательный канал, Remnawave-доступы и поведение `/start`;
-- внешний вид и доступность Web App: название, цвет, логотип, emoji-логотип и `WEBAPP_ENABLED`;
+- внешний вид и доступность Web App: название, цвет, логотип и `WEBAPP_ENABLED`;
- инструкции подключения: `SUBSCRIPTION_GUIDES_ENABLED`, `SUBSCRIPTION_GUIDES_BOT_MENU_ENABLED`, чтение конфига из Remnawave Panel, JSON-переопределение и резервный путь к файлу;
- legacy-тарифы без JSON-каталога: периоды подписки, RUB/Stars цены, реферальные бонусы и пакеты трафика;
- платежные провайдеры: включение методов, порядок кнопок, публичные параметры и секреты YooKassa, FreeKassa, Platega, SeverPay, Wata, CryptoPay, Heleket и Stars, а также текст и иконки кнопок оплаты;
@@ -102,7 +102,7 @@
## Внешний вид
-Раздел **Внешний вид** объединяет настройки бренда и темы Web App. Логотип можно загрузить файлом или по HTTPS-ссылке; backend сохраняет файл в `data/webapp-logo/uploads` и подставляет локальный URL. Если включен emoji-логотип, картинка скрывается, а для emoji можно выбрать системный, Twemoji, Noto Color, animated Noto и другие варианты отрисовки.
+Раздел **Внешний вид** объединяет настройки бренда и темы Web App. Логотип можно загрузить файлом или по HTTPS-ссылке; backend сохраняет файл в `data/webapp-logo/uploads` и подставляет локальный URL. Если логотип не задан, показывается логотип проекта по умолчанию. Favicon генерируется из логотипа или загружается отдельно.
В блоке тем админка читает каталог из `WEBAPP_THEMES_DIR`, показывает встроенные и кастомные темы, позволяет выбрать текущую тему, изменить accent, включить или выключить тему для админки и настроить масштаб логотипа на главной и экране входа. Кнопка предпросмотра открывает `/home?theme_preview=` и не меняет глобальную тему до сохранения.
diff --git a/docs/features/web-app.md b/docs/features/web-app.md
index a92408e..7e2a869 100644
--- a/docs/features/web-app.md
+++ b/docs/features/web-app.md
@@ -67,7 +67,7 @@ SUPPORT_TICKET_RATE_LIMIT_PER_HOUR=5
Если `WEBAPP_ENABLED=False`, пользовательское веб-приложение и админ-панель не регистрируются. Чтобы снова попасть в админку, включите `WEBAPP_ENABLED=True` в `.env` и перезапустите backend/frontend контейнеры.
-Внешний вид настраивается в админке: раздел **Внешний вид** управляет логотипом, emoji-логотипом, accent-цветом, выбранной темой и масштабом логотипа. Кастомные темы читаются из `WEBAPP_THEMES_DIR`, а `WEBAPP_DEFAULT_THEME` может принудительно выбрать тему по ключу. Подробный контракт `theme.json`, CSS/asset-роуты и пайплайн создания темы описаны в [webapp-themes.md](webapp-themes.md).
+Внешний вид настраивается в админке: раздел **Внешний вид** управляет логотипом, accent-цветом, выбранной темой и масштабом логотипа. Кастомные темы читаются из `WEBAPP_THEMES_DIR`, а `WEBAPP_DEFAULT_THEME` может принудительно выбрать тему по ключу. Подробный контракт `theme.json`, CSS/asset-роуты и пайплайн создания темы описаны в [webapp-themes.md](webapp-themes.md).
## Авторизация
diff --git a/docs/features/webapp-themes.md b/docs/features/webapp-themes.md
index 4ee81c9..0757939 100644
--- a/docs/features/webapp-themes.md
+++ b/docs/features/webapp-themes.md
@@ -13,7 +13,6 @@ Web App поддерживает файловые темы, предпросмо
- включить или выключить применение темы в админ-панели;
- настроить масштаб логотипа на главной и экране входа;
- загрузить логотип файлом или по HTTPS-ссылке;
-- включить emoji-логотип и выбрать способ его отрисовки;
- открыть предпросмотр темы через `/home?theme_preview=`.
Через файлы темы можно менять намного больше:
@@ -51,7 +50,7 @@ WEBAPP_DEFAULT_THEME=
В compose-примерах `data/themes` - это локальная папка рядом с выбранным `docker-compose.yml`; она монтируется в контейнер как `/app/data/themes`. Правки в `backend/bot/app/web/themes` попадают в прод только при сборке собственного образа; опубликованный образ их не видит.
-Важно: `WEBAPP_PRIMARY_COLOR`, `WEBAPP_LOGO_URL`, `WEBAPP_LOGO_USE_EMOJI`, `WEBAPP_LOGO_EMOJI` и `WEBAPP_LOGO_EMOJI_FONT` больше не являются рабочим способом первичной настройки через `.env`. Эти значения редактируются в админке и сохраняются как overrides в базе. Тема при этом может использовать сохраненный primary color как fallback accent.
+Важно: `WEBAPP_PRIMARY_COLOR` и `WEBAPP_LOGO_URL` больше не являются рабочим способом первичной настройки через `.env`. Эти значения редактируются в админке и сохраняются как overrides в базе. Тема при этом может использовать сохраненный primary color как fallback accent.
## Контракт `theme.json`
diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md
index 7941128..93230c7 100644
--- a/docs/getting-started/configuration.md
+++ b/docs/getting-started/configuration.md
@@ -87,7 +87,7 @@ openssl rand -hex 32
Перед первым запуском создайте каталоги и отдайте их пользователю контейнера:
```bash
-mkdir -p data/themes data/webapp-logo data/webapp-emoji data/tariffs
+mkdir -p data/themes data/webapp-logo data/tariffs
touch data/locales-overrides.json
chown -R 10001:10001 data
chmod -R u+rwX data
diff --git a/docs/getting-started/deployment.md b/docs/getting-started/deployment.md
index e5ff0bb..dadfc08 100644
--- a/docs/getting-started/deployment.md
+++ b/docs/getting-started/deployment.md
@@ -377,7 +377,7 @@ distributed lock; код подготовлен к нескольким репл
Перед первым запуском на сервере заранее дайте права пользователю контейнера `10001`:
```bash
-mkdir -p data/themes data/webapp-logo data/webapp-emoji data/tariffs
+mkdir -p data/themes data/webapp-logo data/tariffs
touch data/locales-overrides.json
chown -R 10001:10001 data
chmod -R u+rwX data
diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte
index c50d228..13aaa80 100644
--- a/frontend/src/App.svelte
+++ b/frontend/src/App.svelte
@@ -358,13 +358,9 @@
} = $accountStore);
$: brandTitle = CFG.title || "/minishop";
- $: brandEmoji = CFG.logoEmoji || "🫥";
- $: brandEmojiFont = CFG.logoEmojiFont || "system";
$: brand = normalizeBrand({
title: brandTitle,
- logoUrl: CFG.logoUseEmoji ? "" : CFG.logoUrl,
- emoji: brandEmoji,
- emojiFont: brandEmojiFont,
+ logoUrl: CFG.logoUrl,
});
$: faviconBrand = normalizeBrand({
...brand,
@@ -2077,9 +2073,6 @@
]);
return [
"WEBAPP_LOGO_URL",
- "WEBAPP_LOGO_USE_EMOJI",
- "WEBAPP_LOGO_EMOJI",
- "WEBAPP_LOGO_EMOJI_FONT",
"WEBAPP_FAVICON_URL",
"WEBAPP_FAVICON_USE_CUSTOM",
"WEBAPP_LOGO_FAVICON_URL",
diff --git a/frontend/src/PreviewBoard.svelte b/frontend/src/PreviewBoard.svelte
index fd64391..f43375a 100644
--- a/frontend/src/PreviewBoard.svelte
+++ b/frontend/src/PreviewBoard.svelte
@@ -31,7 +31,7 @@
export let mockData = {};
const title = config.title || "/minishop";
- const logoEmoji = config.logoEmoji || "🫥";
+ const logoUrl = config.logoUrl || "/webapp-default-logo.webp";
const plans = mockData.plans || [];
const sub = mockData.subscription || {};
const methods = mockData.payment_methods || [];
@@ -78,7 +78,7 @@
@@ -113,7 +113,7 @@
@@ -198,7 +198,7 @@
@@ -221,7 +221,7 @@
@@ -252,7 +252,7 @@
-
{logoEmoji}
+
{title}
Войдите в свой аккаунт
diff --git a/frontend/src/admin/sections/AppearanceSection.svelte b/frontend/src/admin/sections/AppearanceSection.svelte
index 21aef22..13d0fdc 100644
--- a/frontend/src/admin/sections/AppearanceSection.svelte
+++ b/frontend/src/admin/sections/AppearanceSection.svelte
@@ -1,16 +1,10 @@