diff --git a/seo_mode/seo_mode/app/src/App.tsx b/seo_mode/seo_mode/app/src/App.tsx index d2e3698..649f6d8 100644 --- a/seo_mode/seo_mode/app/src/App.tsx +++ b/seo_mode/seo_mode/app/src/App.tsx @@ -1756,7 +1756,16 @@ export function App() { function handlePreviewMessage(event: MessageEvent) { const data = event.data; - if (!data || typeof data !== "object" || data.type !== "seo-mode:preview-markers") { + if (!data || typeof data !== "object") { + return; + } + + if (data.type === "seo-mode:preview-pointerdown") { + closeWorkspacePageMenus(); + return; + } + + if (data.type !== "seo-mode:preview-markers") { if (data?.type === "seo-mode:active-scope-page") { const sourceWorkspaceKey = typeof data.workspaceKey === "string" ? data.workspaceKey : ""; const pageId = typeof data.pageId === "string" ? data.pageId : ""; @@ -2597,6 +2606,42 @@ export function App() { })); } + function closeWorkspacePageMenus() { + setWorkspacePageMenus((currentMenus) => { + let hasOpenMenu = false; + const nextMenus = { ...currentMenus }; + + Object.keys(nextMenus).forEach((workspaceKey) => { + if (nextMenus[workspaceKey]) { + nextMenus[workspaceKey] = false; + hasOpenMenu = true; + } + }); + + return hasOpenMenu ? nextMenus : currentMenus; + }); + } + + useEffect(() => { + if (!Object.values(workspacePageMenus).some(Boolean)) { + return; + } + + function handleDocumentPointerDown(event: PointerEvent) { + if (event.target instanceof Element && event.target.closest(".workspace-page-control")) { + return; + } + + closeWorkspacePageMenus(); + } + + document.addEventListener("pointerdown", handleDocumentPointerDown, true); + + return () => { + document.removeEventListener("pointerdown", handleDocumentPointerDown, true); + }; + }, [workspacePageMenus]); + function updateWorkspacePreviewMarkers( workspaceKey: string, targets: WorkspaceTarget[], diff --git a/seo_mode/seo_mode/app/src/ProjectScopeFlow.tsx b/seo_mode/seo_mode/app/src/ProjectScopeFlow.tsx index 2ec1a98..5c3d9a5 100644 --- a/seo_mode/seo_mode/app/src/ProjectScopeFlow.tsx +++ b/seo_mode/seo_mode/app/src/ProjectScopeFlow.tsx @@ -1301,16 +1301,19 @@ export default function ProjectScopeFlow({ return (
-
- Конфигурация проекта - - Выбрано рабочих зон {stats.selectedWorkspaceItems}: посадочных {stats.selectedIndexable}/{stats.indexable}, - секций {stats.selectedSections}/{stats.enabledSections}, админка {stats.selectedAdmin}/{stats.admin}, - техслой {stats.selectedTechnical}/{stats.technical}. - + Конфигурация проекта +
+
+
+ {groups.map((group) => ( + + + {scopeGroupNames[group.scope]} · {group.selected}/{group.count} + + ))}
-
-
- {groups.map((group) => ( - - - {scopeGroupNames[group.scope]} · {group.selected}/{group.count} - - ))} -
div:first-child { - display: grid; - gap: 4px; min-width: 0; } @@ -4562,10 +4554,12 @@ input[type="checkbox"] { font-weight: 780; } -.project-scope-flow-head small { - color: var(--muted); - font-size: 13px; - line-height: 1.35; +.project-scope-flow-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 8px; } .project-scope-flow-actions { @@ -4576,26 +4570,39 @@ input[type="checkbox"] { } .project-scope-flow-actions button { - min-height: 32px; - padding: 0 12px; - color: #202522; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 38px; + padding: 0 13px; + color: var(--ink); background: #ffffff; - border: 1px solid rgba(0, 0, 0, 0.14); - border-radius: 8px; + border: 1px solid var(--line); + border-radius: 999px; cursor: pointer; font-size: 13px; - font-weight: 780; + font-weight: 700; + line-height: 1; + white-space: nowrap; } -.project-scope-flow-actions button:first-child { +.project-scope-flow-actions button:hover:not(:disabled) { + border-color: rgba(24, 32, 29, 0.2); + background: #fafafa; +} + +.project-scope-flow-actions button.active { color: #ffffff; - background: #202522; - border-color: #202522; + background: var(--ink); + border-color: var(--ink); } .project-scope-flow-actions button:disabled { cursor: not-allowed; - opacity: 0.48; + color: var(--muted); + background: #f6f6f7; + opacity: 1; } .project-scope-flow-legend { @@ -4607,20 +4614,23 @@ input[type="checkbox"] { .scope-legend-item { display: inline-flex; align-items: center; - gap: 6px; - min-height: 28px; - padding: 0 9px; - color: #4d5753; - background: #ffffff; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 8px; - font-size: 12px; - font-weight: 720; + justify-content: center; + gap: 8px; + min-height: 38px; + padding: 0 13px; + color: var(--muted); + background: #f6f6f7; + border: 1px solid var(--line); + border-radius: 999px; + font-size: 13px; + font-weight: 700; + line-height: 1; + white-space: nowrap; } .scope-legend-item.indexable_page { - color: #63300d; - border-color: rgba(242, 122, 26, 0.28); + color: var(--muted); + border-color: var(--line); } .project-scope-flow-canvas { @@ -5268,15 +5278,18 @@ input[type="checkbox"] { .page-audit-summary span { display: inline-flex; align-items: center; - gap: 6px; - min-height: 30px; - padding: 0 10px; - color: var(--ink); - background: rgba(255, 255, 255, 0.72); + justify-content: center; + gap: 8px; + min-height: 38px; + padding: 0 13px; + color: var(--muted); + background: #f6f6f7; border: 1px solid var(--line); border-radius: 999px; font-size: 13px; - font-weight: 600; + font-weight: 700; + line-height: 1; + white-space: nowrap; } .page-audit-list { @@ -6493,14 +6506,13 @@ input[type="checkbox"] { right: 0; z-index: 30; display: grid; + grid-template-rows: auto minmax(0, 1fr); gap: 10px; width: min(380px, calc(100vw - 48px)); - max-height: min(420px, calc(100vh - 240px)); + max-height: min(480px, calc(100vh - 180px)); padding: 12px; overflow: hidden; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.72)), - rgba(255, 255, 255, 0.94); + background: rgba(255, 255, 255, 0.97); border: 1px solid rgba(24, 32, 29, 0.1); border-radius: 20px; box-shadow: 0 22px 70px rgba(24, 32, 29, 0.16); @@ -6533,8 +6545,12 @@ input[type="checkbox"] { display: grid; gap: 7px; min-height: 0; - overflow: auto; + max-height: min(360px, calc(100vh - 260px)); + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; padding-right: 2px; + scrollbar-gutter: stable; } .workspace-page-menu-list button { @@ -6552,8 +6568,14 @@ input[type="checkbox"] { .workspace-page-menu-list button:hover, .workspace-page-menu-list button.active { - background: #fffaf5; - border-color: rgba(242, 122, 26, 0.32); + background: #f6f6f7; + border-color: rgba(24, 32, 29, 0.16); +} + +.workspace-page-menu-list button.active { + color: #ffffff; + background: var(--ink); + border-color: var(--ink); } .workspace-page-menu-list button span { @@ -6562,8 +6584,8 @@ input[type="checkbox"] { height: 26px; display: grid; place-items: center; - color: #fff; - background: var(--accent); + color: #ffffff; + background: var(--ink); border-radius: 999px; font-size: 12px; font-weight: 850; @@ -6605,6 +6627,21 @@ input[type="checkbox"] { white-space: nowrap; } +.workspace-page-menu-list button.active span { + color: var(--ink); + background: #ffffff; +} + +.workspace-page-menu-list button.active strong, +.workspace-page-menu-list button.active small { + color: #ffffff; +} + +.workspace-page-menu-list button.active em { + color: #ffffff; + background: rgba(255, 255, 255, 0.16); +} + .workspace-current-page-actions .workspace-preview-mode-switch { min-height: 38px; background: #fff !important; diff --git a/seo_mode/seo_mode/server/src/preview/routes.ts b/seo_mode/seo_mode/server/src/preview/routes.ts index b87dde7..0ffb5d0 100644 --- a/seo_mode/seo_mode/server/src/preview/routes.ts +++ b/seo_mode/seo_mode/server/src/preview/routes.ts @@ -596,6 +596,17 @@ function buildPreviewBridgeScript() { }, "*"); } + function reportPreviewPointerDown() { + if (!state.workspaceKey) { + return; + } + + window.parent?.postMessage({ + type: "seo-mode:preview-pointerdown", + workspaceKey: state.workspaceKey + }, "*"); + } + function measure() { const layer = ensureLayer(); const root = document.documentElement; @@ -702,6 +713,7 @@ function buildPreviewBridgeScript() { window.addEventListener("resize", scheduleMeasure); window.addEventListener("scroll", scheduleMeasure, { passive: true }); document.addEventListener("scroll", scheduleMeasure, { capture: true, passive: true }); + document.addEventListener("pointerdown", reportPreviewPointerDown, { capture: true, passive: true }); window.addEventListener("load", () => { window.setTimeout(scheduleMeasure, 50); window.setTimeout(scheduleMeasure, 500);