Тема и палитра CRM: crm:theme → <html data-theme data-pal>, запоминание до следующего crm:theme

This commit is contained in:
EugeneTes
2026-10-15 10:04:11 +00:00
parent 25fb88f48e
commit 6f73f90fa8
2 changed files with 25 additions and 2 deletions

View File

@@ -26,6 +26,7 @@
- **CRM → вики `crm:context`:** `{ type: "crm:context", version: 1, link_template: "https://<crm>/#/module/<id>?path={path}&ws={ws}" }`. Принимается только с origin из `Crm__Origins`. Формат терпимый: `link_template` (или `linkTemplate`) ищется в самом сообщении `crm:context` или `crm:auth`, а также во вложенных `context` / `crm_context` / `crm`. Шаблон без `{path}` или не http(s) игнорируется.
- **Кнопка «Скопіювати посилання»** (страница и папка): ссылка по `link_template`, `{path}` = `/p/<id>` или `/f/<id>`, `{ws}` = `workspace.id` из CRM (оба через `encodeURIComponent`). Пока шаблона нет — прямая ссылка `https://wiki-crm.tes.gd/p/<id>`. Копирование с запасным путём (textarea + `execCommand`).
- **CRM → вики `crm:theme`:** `{ type: "crm:theme", version: 1, theme: "light"|"dark", palette: "<имя>" }` — в ответ на `crm:ready`/`crm:refresh` и при каждой смене темы или палитры. Только с origin из `Crm__Origins` и от `window.parent`. Вики ставит `<html data-theme data-pal>` (`palette` по умолчанию `classic`), остальное делают стили `tvorymo-ui.css`. Последняя тема запоминается в `localStorage`, чтобы при загрузке не мигала системная.
- **Вики → CRM `crm:navigate`:** `{ type: "crm:navigate", version: 1, path }` при каждом переходе (страница, папка, главная, назад/вперёд) и один раз после первого `crm:auth`. Только в iframe и только на origin CRM.
- **Вне iframe:** для `/p/<id>` и `/f/<id>` кнопка «Відкрити в CRM» по env `CRM_LINK_TEMPLATE` (`{ws}` пустой). `CRM_AUTO_REDIRECT=true` — сразу `location.replace` туда. Без шаблона всё как раньше. Оба значения отдаёт `GET /api/config` (`crmLinkTemplate`, `crmAutoRedirect`).

View File

@@ -1,4 +1,4 @@
// Мост с CRM: postMessage (crm:ready → crm:auth, crm:refresh, crm:logout).
// Мост с CRM: postMessage (crm:ready → crm:auth, crm:theme, crm:refresh, crm:logout).
// Токен живёт только в памяти, никуда не логируется и не выводится.
import { useSyncExternalStore } from "react";
import { directLink, fillTemplate, templateFrom } from "./links";
@@ -120,6 +120,26 @@ function onContext(d: Record<string, unknown>) {
if (t && t !== state.linkTemplate) set({ linkTemplate: t });
}
const THEME_KEY = "wiki.crmTheme";
/** Тема и палитра CRM на <html>: дальше всё делает tvorymo-ui.css ([data-theme][data-pal]). */
function applyTheme(theme: unknown, palette: unknown) {
if (theme !== "dark" && theme !== "light") return;
const pal = typeof palette === "string" && /^[a-z0-9-]{1,32}$/.test(palette) ? palette : "classic";
const el = document.documentElement;
el.dataset.theme = theme;
el.dataset.pal = pal;
try { localStorage.setItem(THEME_KEY, JSON.stringify({ theme, pal })); } catch { /* хранилище недоступно */ }
}
/** Последняя тема CRM до прихода crm:theme — чтобы не мигать системной темой при загрузке. */
function restoreTheme() {
try {
const v = JSON.parse(localStorage.getItem(THEME_KEY) || "null");
if (v) applyTheme(v.theme, v.pal);
} catch { /* нет или битое значение */ }
}
/** Сообщить CRM текущий путь, чтобы её адрес указывал на эту страницу. CRM без поддержки просто игнорирует. */
function sendNavigate(path: string) {
if (window.parent === window) return;
@@ -174,6 +194,7 @@ function sendReady() {
export async function initCrm() {
if (window.parent === window) { set({ status: "standalone" }); void initOutside(); return; }
restoreTheme();
const parent = detectParent();
setDiag({ parentOrigin: parent.origin, parentSource: parent.source, startedAt: Date.now() });
@@ -212,7 +233,8 @@ function handle(ev: MessageEvent) {
onContext(d);
void onAuth(d);
if (first) sendNavigate(currentPath()); // первый раз: CRM узнаёт, где сейчас вики
} else if (d.type === "crm:context") onContext(d);
} else if (d.type === "crm:theme") applyTheme(d.theme, d.palette);
else if (d.type === "crm:context") onContext(d);
else if (d.type === "crm:workspace") {
// Будущее сообщение о смене workspace (задача Олега); формат: { workspace: { id, kind, title } }.
lastWs = d.workspace ?? null;