Polish SEO workspace controls

This commit is contained in:
DCCONSTRUCTIONS 2026-06-28 17:11:22 +03:00
parent 08894288bc
commit 5ec8e7e691
4 changed files with 155 additions and 66 deletions

View File

@ -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[],

View File

@ -1301,23 +1301,9 @@ export default function ProjectScopeFlow({
return (
<section className="project-scope-flow-panel">
<div className="project-scope-flow-head">
<div>
<strong>Конфигурация проекта</strong>
<small>
Выбрано рабочих зон {stats.selectedWorkspaceItems}: посадочных {stats.selectedIndexable}/{stats.indexable},
секций {stats.selectedSections}/{stats.enabledSections}, админка {stats.selectedAdmin}/{stats.admin},
техслой {stats.selectedTechnical}/{stats.technical}.
</small>
</div>
<div className="project-scope-flow-actions">
<button disabled={isBusy} onClick={onApplyRecommendedScope} type="button">
SEO scope
</button>
<button disabled={isBusy || stats.selectedWorkspaceItems === 0} onClick={onClearSelection} type="button">
Снять все
</button>
</div>
</div>
<div className="project-scope-flow-controls">
<div className="project-scope-flow-legend">
{groups.map((group) => (
<span className={`scope-legend-item ${group.scope}`} key={group.scope}>
@ -1326,6 +1312,15 @@ export default function ProjectScopeFlow({
</span>
))}
</div>
<div className="project-scope-flow-actions">
<button className="active" disabled={isBusy} onClick={onApplyRecommendedScope} type="button">
SEO scope
</button>
<button disabled={isBusy || stats.selectedWorkspaceItems === 0} onClick={onClearSelection} type="button">
Снять все
</button>
</div>
</div>
<div className="project-scope-flow-canvas">
<ReactFlowProvider>
<ReactFlow

View File

@ -4543,16 +4543,8 @@ input[type="checkbox"] {
}
.project-scope-flow-head {
display: flex;
display: block;
align-items: center;
justify-content: space-between;
gap: 14px;
min-width: 0;
}
.project-scope-flow-head > 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;

View File

@ -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);