Fix SEO workspace scope preview
This commit is contained in:
parent
d27e6ee43d
commit
1f140c1130
|
|
@ -2,6 +2,7 @@ import {
|
|||
Activity,
|
||||
Archive,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
CircleAlert,
|
||||
Copy,
|
||||
Database,
|
||||
|
|
@ -220,6 +221,10 @@ function getScanStatusLabel(status: string) {
|
|||
}
|
||||
|
||||
function getPageTitle(page: ProjectScanSummary["pages"][number]) {
|
||||
if (page.scope === "template_block" && page.structureLabel) {
|
||||
return page.structureLabel;
|
||||
}
|
||||
|
||||
if (isHomePage(page)) {
|
||||
return page.title || "Главная страница";
|
||||
}
|
||||
|
|
@ -263,6 +268,30 @@ function isIndexableScanPage(page: ProjectScanSummary["pages"][number]) {
|
|||
return page.scope === "indexable_page";
|
||||
}
|
||||
|
||||
function isWorkspaceScopePage(page: ProjectScanSummary["pages"][number]) {
|
||||
if (!page.selected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (page.previewKind === "none" && !page.previewSourcePath && !page.previewTargetSelector) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getWorkspaceScopeOrder(page: ProjectScanSummary["pages"][number], originalIndex: number) {
|
||||
if (isHomePage(page)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (page.structureOrder != null) {
|
||||
return page.structureOrder;
|
||||
}
|
||||
|
||||
return 1000 + originalIndex;
|
||||
}
|
||||
|
||||
function getScanPageScopeLabel(scope: ProjectScanSummary["pages"][number]["scope"]) {
|
||||
const labels = {
|
||||
admin_page: "админка",
|
||||
|
|
@ -903,7 +932,11 @@ function getSelectedPagesCount(scan: ProjectScanSummary) {
|
|||
}
|
||||
|
||||
function getSelectedCoveragePages(scan: ProjectScanSummary) {
|
||||
return scan.pages.filter((page) => page.selected && isIndexableScanPage(page));
|
||||
return scan.pages
|
||||
.map((page, index) => ({ index, page }))
|
||||
.filter(({ page }) => isWorkspaceScopePage(page))
|
||||
.sort((left, right) => getWorkspaceScopeOrder(left.page, left.index) - getWorkspaceScopeOrder(right.page, right.index))
|
||||
.map(({ page }) => page);
|
||||
}
|
||||
|
||||
function getWorkspaceKey(projectId: string, pageId: string) {
|
||||
|
|
@ -930,6 +963,7 @@ type WorkspaceTarget = {
|
|||
};
|
||||
|
||||
type WorkspacePreviewMode = "desktop" | "mobile";
|
||||
type WorkspaceDrawerPanel = "fields" | "issues" | "scope";
|
||||
|
||||
const WORKSPACE_PREVIEW_MODES: Array<{ id: WorkspacePreviewMode; label: string }> = [
|
||||
{ id: "desktop", label: "Desktop" },
|
||||
|
|
@ -1369,6 +1403,9 @@ export function App() {
|
|||
const [activeWorkspaceSectionIds, setActiveWorkspaceSectionIds] = useState<Record<string, string>>({});
|
||||
const [activeWorkspaceTargetIds, setActiveWorkspaceTargetIds] = useState<Record<string, string>>({});
|
||||
const [workspacePreviewMarkers, setWorkspacePreviewMarkers] = useState<Record<string, PreviewMarker[]>>({});
|
||||
const [workspaceDrawers, setWorkspaceDrawers] = useState<Record<string, WorkspaceDrawerPanel[]>>({});
|
||||
const [workspacePageMenus, setWorkspacePageMenus] = useState<Record<string, boolean>>({});
|
||||
const [workspacePreviewPageIds, setWorkspacePreviewPageIds] = useState<Record<string, string>>({});
|
||||
const [pageWorkspaces, setPageWorkspaces] = useState<Record<string, PageWorkspace>>({});
|
||||
const [workspaceDraftTexts, setWorkspaceDraftTexts] = useState<Record<string, string>>({});
|
||||
const [workspaceSectionTexts, setWorkspaceSectionTexts] = useState<Record<string, WorkspaceSectionTextMap>>({});
|
||||
|
|
@ -1557,27 +1594,51 @@ export function App() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timers: number[] = [];
|
||||
|
||||
for (const [workspaceKey, workspace] of Object.entries(pageWorkspaces)) {
|
||||
const [projectId = "", pageId = ""] = workspaceKey.split(":");
|
||||
const scanSummary = scanSummaries[projectId];
|
||||
const issues = getPageIssues(scanSummary, pageId);
|
||||
const targets = buildWorkspaceTargets(workspace, issues);
|
||||
|
||||
if (targets.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 180));
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 700));
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 1600));
|
||||
if (activeStageIndex !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollToWorkspace = () => {
|
||||
document.querySelector(".page-workspace-panel")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
};
|
||||
const firstTimer = window.setTimeout(scrollToWorkspace, 120);
|
||||
const secondTimer = window.setTimeout(scrollToWorkspace, 650);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(firstTimer);
|
||||
window.clearTimeout(secondTimer);
|
||||
};
|
||||
}, [activeProjectId, activeStageIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
const timers: number[] = [];
|
||||
const projectId = activeProjectId ?? "";
|
||||
const scanSummary = projectId ? scanSummaries[projectId] : null;
|
||||
|
||||
if (activeStageIndex !== 2 || !projectId || !scanSummary) {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
const scopePages = getSelectedCoveragePages(scanSummary);
|
||||
const activePageId = activeWorkspacePageIds[projectId] ?? scopePages[0]?.pageId ?? "";
|
||||
const workspaceKey = activePageId ? getWorkspaceKey(projectId, activePageId) : "";
|
||||
const workspace = workspaceKey ? pageWorkspaces[workspaceKey] : null;
|
||||
|
||||
if (!workspaceKey || !workspace) {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
const issues = getPageIssues(scanSummary, activePageId);
|
||||
const targets = buildWorkspaceTargets(workspace, issues);
|
||||
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 80));
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 360));
|
||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 1000));
|
||||
|
||||
return () => {
|
||||
timers.forEach((timer) => window.clearTimeout(timer));
|
||||
};
|
||||
}, [activeStageIndex, activeWorkspacePageIds, pageWorkspaces, scanSummaries, workspacePreviewModes]);
|
||||
}, [activeProjectId, activeStageIndex, activeWorkspacePageIds, pageWorkspaces, scanSummaries, workspacePreviewModes]);
|
||||
|
||||
function scrollWorkspaceFieldIntoView(workspaceKey: string, targetId: string, attempt = 0) {
|
||||
window.requestAnimationFrame(() => {
|
||||
|
|
@ -1599,6 +1660,46 @@ export function App() {
|
|||
const data = event.data;
|
||||
|
||||
if (!data || typeof data !== "object" || 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 : "";
|
||||
const [projectId = ""] = sourceWorkspaceKey.split(":");
|
||||
|
||||
if (!projectId || !pageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextWorkspaceKey = getWorkspaceKey(projectId, pageId);
|
||||
|
||||
if (!pageWorkspaces[nextWorkspaceKey]) {
|
||||
const project = projects.find((item) => item.id === projectId);
|
||||
const scanSummary = scanSummaries[projectId];
|
||||
const page = scanSummary ? getSelectedCoveragePages(scanSummary).find((item) => item.pageId === pageId) : null;
|
||||
|
||||
if (project && page && busyWorkspaceKey !== nextWorkspaceKey) {
|
||||
void handleOpenPageWorkspace(project, page, {
|
||||
deferActiveUntilLoaded: true,
|
||||
navigatePreview: false,
|
||||
silent: true
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveWorkspacePageIds((currentPageIds) => {
|
||||
if (currentPageIds[projectId] === pageId) {
|
||||
return currentPageIds;
|
||||
}
|
||||
|
||||
return {
|
||||
...currentPageIds,
|
||||
[projectId]: pageId
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?.type !== "seo-mode:activate-target") {
|
||||
return;
|
||||
}
|
||||
|
|
@ -1619,6 +1720,7 @@ export function App() {
|
|||
...currentSectionIds,
|
||||
[workspaceKey]: sectionId || targetId
|
||||
}));
|
||||
openWorkspaceDrawer(workspaceKey, "fields");
|
||||
|
||||
scrollWorkspaceFieldIntoView(workspaceKey, targetId);
|
||||
return;
|
||||
|
|
@ -1669,7 +1771,7 @@ export function App() {
|
|||
return () => {
|
||||
window.removeEventListener("message", handlePreviewMessage);
|
||||
};
|
||||
}, []);
|
||||
}, [busyWorkspaceKey, pageWorkspaces, projects, scanSummaries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (projects.length === 0) {
|
||||
|
|
@ -2166,7 +2268,7 @@ export function App() {
|
|||
async function handleOpenPageWorkspace(
|
||||
project: ProjectSummary,
|
||||
page: ProjectScanSummary["pages"][number],
|
||||
options: { silent?: boolean } = {}
|
||||
options: { deferActiveUntilLoaded?: boolean; navigatePreview?: boolean; silent?: boolean } = {}
|
||||
) {
|
||||
const workspaceKey = getWorkspaceKey(project.id, page.pageId);
|
||||
|
||||
|
|
@ -2178,10 +2280,19 @@ export function App() {
|
|||
setProjectActionProjectId(project.id);
|
||||
}
|
||||
|
||||
setActiveWorkspacePageIds((currentPageIds) => ({
|
||||
...currentPageIds,
|
||||
[project.id]: page.pageId
|
||||
}));
|
||||
if (options.navigatePreview !== false) {
|
||||
setWorkspacePreviewPageIds((currentPageIds) => ({
|
||||
...currentPageIds,
|
||||
[project.id]: page.pageId
|
||||
}));
|
||||
}
|
||||
|
||||
if (!options.deferActiveUntilLoaded) {
|
||||
setActiveWorkspacePageIds((currentPageIds) => ({
|
||||
...currentPageIds,
|
||||
[project.id]: page.pageId
|
||||
}));
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await openPageWorkspace(project.id, page.pageId);
|
||||
|
|
@ -2206,6 +2317,12 @@ export function App() {
|
|||
...currentTargetIds,
|
||||
[workspaceKey]: currentTargetIds[workspaceKey] ?? result.workspace.sections[0]?.id ?? ""
|
||||
}));
|
||||
if (options.deferActiveUntilLoaded) {
|
||||
setActiveWorkspacePageIds((currentPageIds) => ({
|
||||
...currentPageIds,
|
||||
[project.id]: page.pageId
|
||||
}));
|
||||
}
|
||||
|
||||
if (!options.silent) {
|
||||
setProjectActionMessage(`Рабочая зона открыта: ${getPageTitle(page)}.`);
|
||||
|
|
@ -2320,11 +2437,67 @@ export function App() {
|
|||
...currentSectionIds,
|
||||
[workspaceKey]: target.sectionId
|
||||
}));
|
||||
openWorkspaceDrawer(workspaceKey, "fields");
|
||||
|
||||
scrollWorkspaceFieldIntoView(workspaceKey, target.id);
|
||||
}
|
||||
|
||||
function updateWorkspacePreviewMarkers(workspaceKey: string, targets: WorkspaceTarget[]) {
|
||||
function openWorkspaceDrawer(workspaceKey: string, drawer: WorkspaceDrawerPanel) {
|
||||
setWorkspaceDrawers((currentDrawers) => {
|
||||
const currentWorkspaceDrawers = currentDrawers[workspaceKey] ?? [];
|
||||
|
||||
if (currentWorkspaceDrawers.includes(drawer)) {
|
||||
return currentDrawers;
|
||||
}
|
||||
|
||||
return {
|
||||
...currentDrawers,
|
||||
[workspaceKey]: [...currentWorkspaceDrawers, drawer]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function toggleWorkspaceDrawer(workspaceKey: string, drawer: WorkspaceDrawerPanel) {
|
||||
setWorkspaceDrawers((currentDrawers) => {
|
||||
const currentWorkspaceDrawers = currentDrawers[workspaceKey] ?? [];
|
||||
const nextWorkspaceDrawers = currentWorkspaceDrawers.includes(drawer)
|
||||
? currentWorkspaceDrawers.filter((currentDrawer) => currentDrawer !== drawer)
|
||||
: [...currentWorkspaceDrawers, drawer];
|
||||
|
||||
return {
|
||||
...currentDrawers,
|
||||
[workspaceKey]: nextWorkspaceDrawers
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function closeWorkspaceDrawer(workspaceKey: string, drawer: WorkspaceDrawerPanel) {
|
||||
setWorkspaceDrawers((currentDrawers) => {
|
||||
const currentWorkspaceDrawers = currentDrawers[workspaceKey] ?? [];
|
||||
|
||||
if (!currentWorkspaceDrawers.includes(drawer)) {
|
||||
return currentDrawers;
|
||||
}
|
||||
|
||||
return {
|
||||
...currentDrawers,
|
||||
[workspaceKey]: currentWorkspaceDrawers.filter((currentDrawer) => currentDrawer !== drawer)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function toggleWorkspacePageMenu(workspaceKey: string) {
|
||||
setWorkspacePageMenus((currentMenus) => ({
|
||||
...currentMenus,
|
||||
[workspaceKey]: !currentMenus[workspaceKey]
|
||||
}));
|
||||
}
|
||||
|
||||
function updateWorkspacePreviewMarkers(
|
||||
workspaceKey: string,
|
||||
targets: WorkspaceTarget[],
|
||||
scopePages: ProjectScanSummary["pages"] = []
|
||||
) {
|
||||
const frameWindow = previewFrameRefs.current[workspaceKey]?.contentWindow;
|
||||
|
||||
if (!frameWindow) {
|
||||
|
|
@ -2340,6 +2513,14 @@ export function App() {
|
|||
{
|
||||
type: "seo-mode:measure-targets",
|
||||
workspaceKey,
|
||||
activePageId: workspaceKey.split(":")[1] ?? "",
|
||||
scopePages: scopePages
|
||||
.filter((page) => page.previewTargetSelector)
|
||||
.map((page) => ({
|
||||
pageId: page.pageId,
|
||||
label: getPageTitle(page),
|
||||
selector: page.previewTargetSelector
|
||||
})),
|
||||
targets: targets
|
||||
.filter((target) => (target.markerSelectors.length > 0 || target.markerFallbackSelector) && target.issues.length > 0)
|
||||
.map((target) => ({
|
||||
|
|
@ -2358,20 +2539,29 @@ export function App() {
|
|||
);
|
||||
}
|
||||
|
||||
function changeWorkspacePreviewMode(workspaceKey: string, mode: WorkspacePreviewMode, targets: WorkspaceTarget[]) {
|
||||
function changeWorkspacePreviewMode(
|
||||
workspaceKey: string,
|
||||
mode: WorkspacePreviewMode,
|
||||
targets: WorkspaceTarget[],
|
||||
scopePages: ProjectScanSummary["pages"] = []
|
||||
) {
|
||||
setWorkspacePreviewModes((currentModes) => ({
|
||||
...currentModes,
|
||||
[workspaceKey]: mode
|
||||
}));
|
||||
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 120);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 450);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 120);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 450);
|
||||
}
|
||||
|
||||
function handleWorkspacePreviewLoad(workspaceKey: string, targets: WorkspaceTarget[]) {
|
||||
updateWorkspacePreviewMarkers(workspaceKey, targets);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 350);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets), 1000);
|
||||
function handleWorkspacePreviewLoad(
|
||||
workspaceKey: string,
|
||||
targets: WorkspaceTarget[],
|
||||
scopePages: ProjectScanSummary["pages"] = []
|
||||
) {
|
||||
updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 350);
|
||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 1000);
|
||||
}
|
||||
|
||||
function setAuditIssuesVisibleCount(scanId: string, visibleCount: number) {
|
||||
|
|
@ -2429,6 +2619,11 @@ export function App() {
|
|||
delete nextPageIds[project.id];
|
||||
return nextPageIds;
|
||||
});
|
||||
setWorkspacePreviewPageIds((currentPageIds) => {
|
||||
const nextPageIds = { ...currentPageIds };
|
||||
delete nextPageIds[project.id];
|
||||
return nextPageIds;
|
||||
});
|
||||
setPageWorkspaces((currentWorkspaces) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(currentWorkspaces).filter(([workspaceKey]) => !workspaceKey.startsWith(`${project.id}:`))
|
||||
|
|
@ -2440,6 +2635,14 @@ export function App() {
|
|||
setWorkspaceSectionTexts((currentTexts) =>
|
||||
Object.fromEntries(Object.entries(currentTexts).filter(([workspaceKey]) => !workspaceKey.startsWith(`${project.id}:`)))
|
||||
);
|
||||
setWorkspaceDrawers((currentDrawers) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(currentDrawers).filter(([workspaceKey]) => !workspaceKey.startsWith(`${project.id}:`))
|
||||
)
|
||||
);
|
||||
setWorkspacePageMenus((currentMenus) =>
|
||||
Object.fromEntries(Object.entries(currentMenus).filter(([workspaceKey]) => !workspaceKey.startsWith(`${project.id}:`)))
|
||||
);
|
||||
setActiveWorkspaceSectionIds((currentSectionIds) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(currentSectionIds).filter(([workspaceKey]) => !workspaceKey.startsWith(`${project.id}:`))
|
||||
|
|
@ -2516,24 +2719,30 @@ export function App() {
|
|||
continue;
|
||||
}
|
||||
|
||||
const selectedPage = scanSummary.pages.find((page) => page.selected);
|
||||
const selectedWorkspacePages = getSelectedCoveragePages(scanSummary);
|
||||
const selectedPage = selectedWorkspacePages[0];
|
||||
|
||||
if (!selectedPage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const activePageId = activeWorkspacePageIds[project.id] ?? selectedPage.pageId;
|
||||
const pageToOpen = scanSummary.pages.find((page) => page.pageId === activePageId) ?? selectedPage;
|
||||
const pageToOpen =
|
||||
selectedWorkspacePages.find((page) => page.pageId === activePageId) ??
|
||||
selectedPage;
|
||||
const workspaceKey = getWorkspaceKey(project.id, pageToOpen.pageId);
|
||||
|
||||
if (pageWorkspaces[workspaceKey] || busyWorkspaceKey === workspaceKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void handleOpenPageWorkspace(project, pageToOpen, { silent: true });
|
||||
void handleOpenPageWorkspace(project, pageToOpen, {
|
||||
navigatePreview: !workspacePreviewPageIds[project.id],
|
||||
silent: true
|
||||
});
|
||||
break;
|
||||
}
|
||||
}, [activeWorkspacePageIds, busyWorkspaceKey, pageWorkspaces, projects, scanSummaries]);
|
||||
}, [activeWorkspacePageIds, busyWorkspaceKey, pageWorkspaces, projects, scanSummaries, workspacePreviewPageIds]);
|
||||
|
||||
const createNameConflict = pickedFolder ? hasProjectNameConflict(pickedFolder.rootName) : false;
|
||||
const canCreateProject = Boolean(pickedFolder) && !isSubmitting && !createNameConflict;
|
||||
|
|
@ -3144,19 +3353,26 @@ export function App() {
|
|||
null;
|
||||
const activeWorkspaceMarkers = activeWorkspaceKey ? (workspacePreviewMarkers[activeWorkspaceKey] ?? []) : [];
|
||||
const activeWorkspacePreviewMode = activeWorkspaceKey ? (workspacePreviewModes[activeWorkspaceKey] ?? "desktop") : "desktop";
|
||||
const activeWorkspaceSectionId =
|
||||
activeWorkspaceKey && activeWorkspace
|
||||
? (activeWorkspaceTarget?.sectionId ?? activeWorkspaceSectionIds[activeWorkspaceKey] ?? activeWorkspace.sections[0]?.id ?? "")
|
||||
: "";
|
||||
const activeWorkspaceSection =
|
||||
activeWorkspace?.sections.find((section) => section.id === activeWorkspaceSectionId) ??
|
||||
activeWorkspace?.sections[0] ??
|
||||
null;
|
||||
const activeWorkspaceSectionText =
|
||||
activeWorkspaceKey && activeWorkspaceSection
|
||||
? (workspaceSectionTexts[activeWorkspaceKey]?.[activeWorkspaceSection.id] ??
|
||||
activeWorkspaceSection.workingText)
|
||||
const activeWorkspaceDrawers = activeWorkspaceKey ? (workspaceDrawers[activeWorkspaceKey] ?? []) : [];
|
||||
const isWorkspacePageMenuOpen = activeWorkspaceKey ? Boolean(workspacePageMenus[activeWorkspaceKey]) : false;
|
||||
const previewWorkspacePageId = workspacePreviewPageIds[project.id] ?? activeWorkspacePageId;
|
||||
const previewWorkspacePage =
|
||||
selectedCoveragePages.find((page) => page.pageId === previewWorkspacePageId) ??
|
||||
activeWorkspacePage;
|
||||
const activeWorkspacePageIndex = Math.max(
|
||||
0,
|
||||
selectedCoveragePages.findIndex((page) => page.pageId === activeWorkspacePageId)
|
||||
);
|
||||
const activeWorkspacePreviewSourcePath =
|
||||
activeWorkspace && previewWorkspacePage
|
||||
? (previewWorkspacePage.previewSourcePath ?? previewWorkspacePage.sourcePath)
|
||||
: "";
|
||||
const activeWorkspacePreviewFocusSelector = previewWorkspacePage?.previewTargetSelector ?? null;
|
||||
const activeWorkspaceIssueCounts = getAuditIssueCounts(activeWorkspaceIssues);
|
||||
const selectedCoverageIssueCount = selectedCoveragePages.reduce(
|
||||
(total, page) => total + getPageIssues(scanSummary, page.pageId).length,
|
||||
0
|
||||
);
|
||||
const selectedAuditIssuesAll = scanSummary?.audit
|
||||
? getSelectedAuditIssues(scanSummary, scanSummary.audit.issues)
|
||||
: [];
|
||||
|
|
@ -4362,7 +4578,7 @@ export function App() {
|
|||
<section className="page-workspace-panel">
|
||||
<div className="page-workspace-heading">
|
||||
<div>
|
||||
<strong>Рабочая зона выбранных посадочных</strong>
|
||||
<strong>Рабочая зона выбранного scope</strong>
|
||||
<small>Original read-only, working draft сохраняется в Postgres + MinIO.</small>
|
||||
</div>
|
||||
<span>Apply пока не трогаем</span>
|
||||
|
|
@ -4388,41 +4604,157 @@ export function App() {
|
|||
})}
|
||||
</div>
|
||||
{activeWorkspace && activeWorkspacePage && activeWorkspaceKey ? (
|
||||
<div className="workspace-editor-stack">
|
||||
<div className="workspace-editor-stack workspace-cockpit">
|
||||
<div className="workspace-current-page">
|
||||
<div>
|
||||
<span>Выбранная страница</span>
|
||||
<span>
|
||||
Активная страница {activeWorkspacePageIndex + 1}/{selectedCoveragePages.length} · выбранный scope
|
||||
</span>
|
||||
<strong>{getPageTitle(activeWorkspacePage)}</strong>
|
||||
<small>{getPagePathLabel(activeWorkspacePage)}</small>
|
||||
</div>
|
||||
<em>{activeWorkspaceIssues.length} замечаний</em>
|
||||
</div>
|
||||
<div className="workspace-preview-layout">
|
||||
<section className="workspace-preview-panel">
|
||||
<div className="workspace-subheading">
|
||||
<strong>Зеркало страницы</strong>
|
||||
<small>Оранжевые точки ведут к полям правки ниже.</small>
|
||||
<section className={`workspace-preview-panel workspace-cockpit-panel ${activeWorkspaceDrawers.length > 0 ? "drawer-open" : ""}`}>
|
||||
<div className="workspace-cockpit-toolbar">
|
||||
<div className="workspace-preview-status">
|
||||
<span>Замечаний на активной странице: {activeWorkspaceIssues.length}</span>
|
||||
<span>В выбранном scope: {selectedCoverageIssueCount}</span>
|
||||
<span>Warning: {activeWorkspaceIssueCounts.warnings} · Info: {activeWorkspaceIssueCounts.info}</span>
|
||||
</div>
|
||||
<div className="workspace-preview-mode-switch" aria-label="Режим зеркала">
|
||||
{WORKSPACE_PREVIEW_MODES.map((mode) => (
|
||||
<div className="workspace-current-page-actions">
|
||||
<div className={`workspace-page-control ${isWorkspacePageMenuOpen ? "open" : ""}`}>
|
||||
<button
|
||||
className={activeWorkspacePreviewMode === mode.id ? "active" : undefined}
|
||||
key={mode.id}
|
||||
onClick={() => changeWorkspacePreviewMode(activeWorkspaceKey, mode.id, activeWorkspaceTargets)}
|
||||
aria-expanded={isWorkspacePageMenuOpen}
|
||||
aria-haspopup="menu"
|
||||
className="workspace-page-control-trigger"
|
||||
onClick={() => toggleWorkspacePageMenu(activeWorkspaceKey)}
|
||||
type="button"
|
||||
>
|
||||
{mode.label}
|
||||
<FileText size={14} />
|
||||
Страница
|
||||
<em>
|
||||
{activeWorkspacePageIndex + 1}/{selectedCoveragePages.length}
|
||||
</em>
|
||||
<ChevronDown size={14} />
|
||||
</button>
|
||||
))}
|
||||
{isWorkspacePageMenuOpen ? (
|
||||
<div className="workspace-page-menu" role="menu">
|
||||
<div className="workspace-page-menu-head">
|
||||
<strong>Страницы и секции scope</strong>
|
||||
<small>Переключает preview, ошибки и поля рабочей зоны.</small>
|
||||
</div>
|
||||
{selectedCoveragePages.length > 0 ? (
|
||||
<div className="workspace-page-menu-list">
|
||||
{selectedCoveragePages.map((page, pageIndex) => {
|
||||
const pageWorkspaceKey = getWorkspaceKey(project.id, page.pageId);
|
||||
const isActivePage = activeWorkspacePageId === page.pageId;
|
||||
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
||||
const pageIssues = getPageIssues(scanSummary, page.pageId);
|
||||
|
||||
return (
|
||||
<button
|
||||
className={isActivePage ? "active" : undefined}
|
||||
disabled={isOpening}
|
||||
key={`workspace-page-menu-${page.pageId}`}
|
||||
onClick={() => {
|
||||
setWorkspacePageMenus((currentMenus) => ({
|
||||
...currentMenus,
|
||||
[activeWorkspaceKey]: false
|
||||
}));
|
||||
void handleOpenPageWorkspace(project, page, { silent: true });
|
||||
}}
|
||||
role="menuitem"
|
||||
type="button"
|
||||
>
|
||||
<span>{pageIndex + 1}</span>
|
||||
<strong>{getPageTitle(page)}</strong>
|
||||
<small>{getPagePathLabel(page)}</small>
|
||||
<em>{pageIssues.length} замечаний</em>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<small className="workspace-page-menu-empty">
|
||||
В выбранном scope пока нет рабочих страниц или секций.
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<button
|
||||
className={activeWorkspaceDrawers.includes("scope") ? "active" : undefined}
|
||||
onClick={() => toggleWorkspaceDrawer(activeWorkspaceKey, "scope")}
|
||||
type="button"
|
||||
>
|
||||
<FileText size={14} />
|
||||
Scope
|
||||
<em>{selectedCoveragePages.length}</em>
|
||||
</button>
|
||||
<button
|
||||
className={activeWorkspaceDrawers.includes("issues") ? "active" : undefined}
|
||||
onClick={() => toggleWorkspaceDrawer(activeWorkspaceKey, "issues")}
|
||||
type="button"
|
||||
>
|
||||
<CircleAlert size={14} />
|
||||
Ошибки
|
||||
<em>{activeWorkspaceIssues.length}</em>
|
||||
</button>
|
||||
<button
|
||||
className={activeWorkspaceDrawers.includes("fields") ? "active" : undefined}
|
||||
onClick={() => toggleWorkspaceDrawer(activeWorkspaceKey, "fields")}
|
||||
type="button"
|
||||
>
|
||||
<FileText size={14} />
|
||||
Поля
|
||||
<em>{activeWorkspaceTargets.length}</em>
|
||||
</button>
|
||||
<div className="workspace-preview-mode-switch" aria-label="Режим зеркала">
|
||||
{WORKSPACE_PREVIEW_MODES.map((mode) => (
|
||||
<button
|
||||
className={activeWorkspacePreviewMode === mode.id ? "active" : undefined}
|
||||
key={mode.id}
|
||||
onClick={() =>
|
||||
changeWorkspacePreviewMode(
|
||||
activeWorkspaceKey,
|
||||
mode.id,
|
||||
activeWorkspaceTargets,
|
||||
selectedCoveragePages
|
||||
)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
{mode.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="workspace-preview-shell">
|
||||
<div className={`workspace-preview-frame ${activeWorkspacePreviewMode === "mobile" ? "mobile-preview" : "desktop-preview"}`}>
|
||||
<iframe
|
||||
onLoad={() => handleWorkspacePreviewLoad(activeWorkspaceKey, activeWorkspaceTargets)}
|
||||
onLoad={() =>
|
||||
handleWorkspacePreviewLoad(
|
||||
activeWorkspaceKey,
|
||||
activeWorkspaceTargets,
|
||||
selectedCoveragePages
|
||||
)
|
||||
}
|
||||
ref={(element) => {
|
||||
previewFrameRefs.current[activeWorkspaceKey] = element;
|
||||
if (element) {
|
||||
previewFrameRefs.current = {
|
||||
[activeWorkspaceKey]: element
|
||||
};
|
||||
} else {
|
||||
delete previewFrameRefs.current[activeWorkspaceKey];
|
||||
}
|
||||
}}
|
||||
sandbox="allow-scripts allow-forms allow-popups"
|
||||
src={getPreviewPageUrl(project.id, activeWorkspace.page.sourcePath)}
|
||||
src={getPreviewPageUrl(
|
||||
project.id,
|
||||
activeWorkspacePreviewSourcePath,
|
||||
activeWorkspacePreviewFocusSelector
|
||||
)}
|
||||
title={`Предпросмотр ${getPageTitle(activeWorkspacePage)}`}
|
||||
/>
|
||||
<div className="workspace-preview-markers">
|
||||
|
|
@ -4449,142 +4781,192 @@ export function App() {
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="workspace-page-issues">
|
||||
<div className="workspace-subheading">
|
||||
<strong>Ошибки этой страницы</strong>
|
||||
<small>{activeWorkspaceIssues.length} шт. из базового аудита</small>
|
||||
</div>
|
||||
{activeWorkspaceIssues.length > 0 ? (
|
||||
<div className="workspace-issue-list">
|
||||
{activeWorkspaceIssues.map((issue) => {
|
||||
const target = activeWorkspaceTargets.find((item) => item.issues.some((targetIssue) => targetIssue.id === issue.id));
|
||||
{activeWorkspaceDrawers.length > 0 ? (
|
||||
<div className="workspace-drawer-stack">
|
||||
{activeWorkspaceDrawers.map((activeWorkspaceDrawer) => (
|
||||
<aside className="workspace-drawer open" key={`workspace-drawer-${activeWorkspaceDrawer}`}>
|
||||
<div className="workspace-drawer-header">
|
||||
<div>
|
||||
<span>
|
||||
{activeWorkspaceDrawer === "scope"
|
||||
? "Выбранный scope"
|
||||
: activeWorkspaceDrawer === "fields"
|
||||
? "Поля для правки"
|
||||
: "Аудит страницы"}
|
||||
</span>
|
||||
<strong>
|
||||
{activeWorkspaceDrawer === "scope"
|
||||
? `${selectedCoveragePages.length} в scope`
|
||||
: activeWorkspaceDrawer === "fields"
|
||||
? `${activeWorkspaceTargets.length} полей`
|
||||
: `${activeWorkspaceIssues.length} замечаний`}
|
||||
</strong>
|
||||
<small>
|
||||
{activeWorkspaceDrawer === "scope"
|
||||
? "Дальше анализируются только выбранные страницы и секции."
|
||||
: activeWorkspaceDrawer === "fields"
|
||||
? "Клик по точке или карточке открывает связанное поле."
|
||||
: "Список относится к активной странице, не ко всему сайту."}
|
||||
</small>
|
||||
</div>
|
||||
<button
|
||||
aria-label="Закрыть панель"
|
||||
className="workspace-drawer-close"
|
||||
onClick={() => closeWorkspaceDrawer(activeWorkspaceKey, activeWorkspaceDrawer)}
|
||||
type="button"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
{activeWorkspaceDrawer === "scope" ? (
|
||||
<div className="workspace-scope-list">
|
||||
{selectedCoveragePages.map((page, pageIndex) => {
|
||||
const pageWorkspaceKey = getWorkspaceKey(project.id, page.pageId);
|
||||
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
||||
const isActivePage = activeWorkspacePageId === page.pageId;
|
||||
const pageIssues = getPageIssues(scanSummary, page.pageId);
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={!target}
|
||||
key={`workspace-issue-${issue.id}`}
|
||||
onClick={() => {
|
||||
if (!target) return;
|
||||
activateWorkspaceTarget(activeWorkspaceKey, target);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span>{getAuditSeverityLabel(issue.severity)}</span>
|
||||
<strong>{issue.message}</strong>
|
||||
<small>{issue.field ? `Поле: ${issue.field}` : "Медиа/страница"}</small>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<small className="workspace-empty">По этой странице ошибок аудита нет.</small>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<section className="workspace-section-workbench">
|
||||
<div className="workspace-subheading">
|
||||
<strong>Поля для правки</strong>
|
||||
<small>Title, meta, заголовки, текстовые блоки и media-поля редактируются здесь.</small>
|
||||
</div>
|
||||
<div className="section-workbench-grid">
|
||||
<div className="section-map-list">
|
||||
{activeWorkspaceTargets.map((target) => (
|
||||
<button
|
||||
className={
|
||||
activeWorkspaceTarget?.id === target.id ? "section-map-card workspace-target-card active" : "section-map-card workspace-target-card"
|
||||
}
|
||||
key={target.id}
|
||||
onClick={() => activateWorkspaceTarget(activeWorkspaceKey, target)}
|
||||
type="button"
|
||||
>
|
||||
<div>
|
||||
<span>{target.number}</span>
|
||||
<small>{getSectionKindLabel(target.kind)}</small>
|
||||
</div>
|
||||
<strong>{target.title}</strong>
|
||||
<p>{target.issues[0]?.message ?? target.subtitle}</p>
|
||||
<em>{target.workingText.length} симв.</em>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{activeWorkspaceTarget ? (
|
||||
<div className="section-editor-card" id={getWorkspaceFieldDomId(activeWorkspaceKey, activeWorkspaceTarget.id)}>
|
||||
<div className="workspace-subheading">
|
||||
<strong>
|
||||
{activeWorkspaceTarget.number}. {activeWorkspaceTarget.title}
|
||||
</strong>
|
||||
<small>{activeWorkspaceTarget.selector ?? "selector пока не задан"}</small>
|
||||
</div>
|
||||
{activeWorkspaceTarget.issues.length > 0 ? (
|
||||
<div className="workspace-target-issues">
|
||||
{activeWorkspaceTarget.issues.map((issue) => (
|
||||
<small key={`target-issue-${issue.id}`}>
|
||||
{getAuditSeverityLabel(issue.severity)} · {issue.message}
|
||||
</small>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<label>
|
||||
<span>Original · только чтение</span>
|
||||
<pre>{activeWorkspaceTarget.originalText || "Пусто"}</pre>
|
||||
</label>
|
||||
<label>
|
||||
<span>Working · редактируется</span>
|
||||
<textarea
|
||||
onChange={(event) =>
|
||||
setWorkspaceSectionTexts((currentTexts) => ({
|
||||
...currentTexts,
|
||||
[activeWorkspaceKey]: {
|
||||
...(currentTexts[activeWorkspaceKey] ?? {}),
|
||||
[activeWorkspaceTarget.sectionId]: event.target.value
|
||||
}
|
||||
}))
|
||||
}
|
||||
value={activeWorkspaceSectionText}
|
||||
/>
|
||||
</label>
|
||||
<div className="workspace-actions">
|
||||
<button
|
||||
className="tiny-action primary"
|
||||
disabled={isWorkspaceBusy}
|
||||
onClick={() =>
|
||||
void handleSavePageWorkspace(project.id, activeWorkspace.pageId)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
{isWorkspaceBusy ? <Loader2 className="spin" size={14} /> : <Save size={14} />}
|
||||
Сохранить draft
|
||||
</button>
|
||||
<button
|
||||
className="tiny-action"
|
||||
disabled={isWorkspaceBusy}
|
||||
onClick={() =>
|
||||
setWorkspaceSectionTexts((currentTexts) => ({
|
||||
...currentTexts,
|
||||
[activeWorkspaceKey]: {
|
||||
...(currentTexts[activeWorkspaceKey] ?? {}),
|
||||
[activeWorkspaceTarget.sectionId]: activeWorkspaceTarget.originalText
|
||||
}
|
||||
}))
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
Сбросить секцию
|
||||
</button>
|
||||
<button
|
||||
className="tiny-action"
|
||||
disabled={isWorkspaceBusy}
|
||||
onClick={() =>
|
||||
void handleResetPageWorkspace(project.id, activeWorkspace.pageId)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
Сбросить весь draft
|
||||
</button>
|
||||
</div>
|
||||
return (
|
||||
<button
|
||||
className={isActivePage ? "active" : undefined}
|
||||
disabled={isOpening}
|
||||
key={`workspace-scope-${page.pageId}`}
|
||||
onClick={() => void handleOpenPageWorkspace(project, page)}
|
||||
type="button"
|
||||
>
|
||||
<span>{pageIndex + 1}</span>
|
||||
<strong>{getPageTitle(page)}</strong>
|
||||
<small>{getPagePathLabel(page)}</small>
|
||||
<em>{pageIssues.length} замечаний</em>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : activeWorkspaceDrawer === "fields" ? (
|
||||
<div className="workspace-fields-drawer">
|
||||
{activeWorkspaceTargets.length > 0 ? (
|
||||
activeWorkspaceTargets.map((target) => {
|
||||
const targetSectionText =
|
||||
workspaceSectionTexts[activeWorkspaceKey]?.[target.sectionId] ?? target.workingText;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
activeWorkspaceTarget?.id === target.id
|
||||
? "section-editor-card workspace-field-card active"
|
||||
: "section-editor-card workspace-field-card"
|
||||
}
|
||||
id={getWorkspaceFieldDomId(activeWorkspaceKey, target.id)}
|
||||
key={`workspace-field-${target.id}`}
|
||||
>
|
||||
<div className="workspace-subheading">
|
||||
<strong>
|
||||
{target.number}. {target.title}
|
||||
</strong>
|
||||
<small>{target.selector ?? "selector пока не задан"}</small>
|
||||
</div>
|
||||
<div className="workspace-field-meta">
|
||||
<span>{getSectionKindLabel(target.kind)}</span>
|
||||
<em>{target.workingText.length} симв.</em>
|
||||
</div>
|
||||
{target.issues.length > 0 ? (
|
||||
<div className="workspace-target-issues">
|
||||
{target.issues.map((issue) => (
|
||||
<small key={`target-issue-${target.id}-${issue.id}`}>
|
||||
{getAuditSeverityLabel(issue.severity)} · {issue.message}
|
||||
</small>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<label>
|
||||
<span>Original · только чтение</span>
|
||||
<pre>{target.originalText || "Пусто"}</pre>
|
||||
</label>
|
||||
<label>
|
||||
<span>Working · редактируется</span>
|
||||
<textarea
|
||||
onChange={(event) =>
|
||||
setWorkspaceSectionTexts((currentTexts) => ({
|
||||
...currentTexts,
|
||||
[activeWorkspaceKey]: {
|
||||
...(currentTexts[activeWorkspaceKey] ?? {}),
|
||||
[target.sectionId]: event.target.value
|
||||
}
|
||||
}))
|
||||
}
|
||||
value={targetSectionText}
|
||||
/>
|
||||
</label>
|
||||
<div className="workspace-actions">
|
||||
<button
|
||||
className="tiny-action primary"
|
||||
disabled={isWorkspaceBusy}
|
||||
onClick={() =>
|
||||
void handleSavePageWorkspace(project.id, activeWorkspace.pageId)
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
{isWorkspaceBusy ? <Loader2 className="spin" size={14} /> : <Save size={14} />}
|
||||
Сохранить draft
|
||||
</button>
|
||||
<button
|
||||
className="tiny-action"
|
||||
disabled={isWorkspaceBusy}
|
||||
onClick={() =>
|
||||
setWorkspaceSectionTexts((currentTexts) => ({
|
||||
...currentTexts,
|
||||
[activeWorkspaceKey]: {
|
||||
...(currentTexts[activeWorkspaceKey] ?? {}),
|
||||
[target.sectionId]: target.originalText
|
||||
}
|
||||
}))
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
Сбросить секцию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<small className="workspace-empty">Для этой страницы пока нет полей, связанных с замечаниями.</small>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{activeWorkspaceIssues.length > 0 ? (
|
||||
<div className="workspace-issue-list">
|
||||
{activeWorkspaceIssues.map((issue) => {
|
||||
const target = activeWorkspaceTargets.find((item) => item.issues.some((targetIssue) => targetIssue.id === issue.id));
|
||||
const explanation = getAuditIssueExplanation(issue.code);
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={!target}
|
||||
key={`workspace-issue-${issue.id}`}
|
||||
onClick={() => {
|
||||
if (!target) return;
|
||||
activateWorkspaceTarget(activeWorkspaceKey, target);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span>{getAuditSeverityLabel(issue.severity)}</span>
|
||||
<strong>{issue.message}</strong>
|
||||
<small>{issue.field ? `Поле: ${issue.field}` : "Медиа/страница"}</small>
|
||||
<p>{explanation.action}</p>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<small className="workspace-empty">По активной странице ошибок базового аудита нет.</small>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</aside>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6317,7 +6317,7 @@ input[type="checkbox"] {
|
|||
}
|
||||
}
|
||||
|
||||
/* Stage 3: keep the page editor clean. The selected page is the object now,
|
||||
/* Stage 3: page workspace cockpit. The selected page is the object now,
|
||||
so project/list scaffolding stays out of the visual hierarchy. */
|
||||
.stage-3 .project-row {
|
||||
padding: 0 !important;
|
||||
|
|
@ -6327,6 +6327,26 @@ input[type="checkbox"] {
|
|||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.stage-3 .project-setup {
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.stage-3 .project-setup > .section-heading {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.stage-3 .project-list-card {
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.stage-3 .project-list {
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.stage-3 .project-row > div > .project-title-row,
|
||||
.stage-3 .project-row > div > .project-source,
|
||||
.stage-3 .page-workspace-heading,
|
||||
|
|
@ -6344,18 +6364,16 @@ input[type="checkbox"] {
|
|||
}
|
||||
|
||||
.stage-3 .workspace-editor-stack {
|
||||
gap: 18px !important;
|
||||
gap: 14px !important;
|
||||
}
|
||||
|
||||
.workspace-current-page {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.workspace-current-page div {
|
||||
.workspace-current-page > div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
|
|
@ -6371,9 +6389,11 @@ input[type="checkbox"] {
|
|||
color: var(--ink);
|
||||
font-size: clamp(24px, 2.2vw, 34px);
|
||||
font-weight: 760;
|
||||
letter-spacing: -0.04em;
|
||||
letter-spacing: 0;
|
||||
line-height: 1.05;
|
||||
overflow-wrap: anywhere;
|
||||
max-width: 100%;
|
||||
overflow-wrap: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.workspace-current-page small {
|
||||
|
|
@ -6383,16 +6403,211 @@ input[type="checkbox"] {
|
|||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.workspace-current-page em {
|
||||
flex: 0 0 auto;
|
||||
padding: 8px 12px;
|
||||
.workspace-cockpit-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions > button,
|
||||
.workspace-page-control-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-height: 38px;
|
||||
padding: 0 13px;
|
||||
color: var(--ink);
|
||||
background: #f2f2f4;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions > button:hover,
|
||||
.workspace-page-control-trigger:hover {
|
||||
border-color: rgba(24, 32, 29, 0.2);
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions > button.active,
|
||||
.workspace-page-control.open .workspace-page-control-trigger {
|
||||
color: #fff;
|
||||
background: var(--ink);
|
||||
border-color: var(--ink);
|
||||
}
|
||||
|
||||
.workspace-current-page-actions > button em,
|
||||
.workspace-page-control-trigger em {
|
||||
flex: 0 0 auto;
|
||||
min-width: 23px;
|
||||
height: 23px;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
padding: 0 7px;
|
||||
color: currentColor;
|
||||
background: rgba(24, 32, 29, 0.06);
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 560;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions > button.active em,
|
||||
.workspace-page-control.open .workspace-page-control-trigger em {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.workspace-page-control {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.workspace-page-control-trigger svg:last-child {
|
||||
transition: transform 160ms ease;
|
||||
}
|
||||
|
||||
.workspace-page-control.open .workspace-page-control-trigger svg:last-child {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.workspace-page-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
z-index: 30;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
width: min(380px, calc(100vw - 48px));
|
||||
max-height: min(420px, calc(100vh - 240px));
|
||||
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);
|
||||
border: 1px solid rgba(24, 32, 29, 0.1);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 22px 70px rgba(24, 32, 29, 0.16);
|
||||
backdrop-filter: blur(30px) saturate(1.08);
|
||||
-webkit-backdrop-filter: blur(30px) saturate(1.08);
|
||||
}
|
||||
|
||||
.workspace-page-menu-head {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
padding: 2px 2px 0;
|
||||
}
|
||||
|
||||
.workspace-page-menu-head strong {
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
font-weight: 780;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.workspace-page-menu-head small,
|
||||
.workspace-page-menu-empty {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
gap: 3px 9px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button:hover,
|
||||
.workspace-page-menu-list button.active {
|
||||
background: #fffaf5;
|
||||
border-color: rgba(242, 122, 26, 0.32);
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button span {
|
||||
grid-row: span 2;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button strong,
|
||||
.workspace-page-menu-list button small {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button strong {
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 760;
|
||||
line-height: 1.15;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button small {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-page-menu-list button em {
|
||||
grid-row: span 2;
|
||||
align-self: center;
|
||||
padding: 4px 8px;
|
||||
color: var(--muted);
|
||||
background: #f6f6f7;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 760;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions .workspace-preview-mode-switch {
|
||||
min-height: 38px;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-preview-panel,
|
||||
|
|
@ -6406,6 +6621,44 @@ input[type="checkbox"] {
|
|||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-cockpit-panel {
|
||||
position: relative;
|
||||
gap: 10px !important;
|
||||
padding: 12px !important;
|
||||
overflow: hidden;
|
||||
border-radius: 22px !important;
|
||||
}
|
||||
|
||||
.workspace-preview-status {
|
||||
display: flex;
|
||||
flex: 1 1 360px;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
padding: 0 2px 2px;
|
||||
}
|
||||
|
||||
.workspace-preview-status span {
|
||||
min-height: 26px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 9px;
|
||||
color: var(--muted);
|
||||
background: #f6f6f7;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.workspace-preview-shell {
|
||||
position: relative;
|
||||
min-height: clamp(620px, 68vh, 840px);
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-subheading {
|
||||
align-items: baseline !important;
|
||||
padding: 0 2px;
|
||||
|
|
@ -6430,11 +6683,18 @@ input[type="checkbox"] {
|
|||
border-radius: 22px !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-cockpit-panel .workspace-preview-frame {
|
||||
position: absolute !important;
|
||||
inset: 0;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-preview-panel iframe {
|
||||
width: max(1600px, 100%) !important;
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
height: clamp(620px, 72vh, 860px) !important;
|
||||
min-height: 620px !important;
|
||||
height: 100% !important;
|
||||
min-height: clamp(620px, 68vh, 840px) !important;
|
||||
background: #fff !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
|
|
@ -6459,7 +6719,238 @@ input[type="checkbox"] {
|
|||
}
|
||||
|
||||
.stage-3 .workspace-preview-frame.desktop-preview iframe {
|
||||
width: max(1600px, 100%) !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.workspace-drawer-stack {
|
||||
position: absolute;
|
||||
inset: 12px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.workspace-drawer {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
flex: 0 0 calc((100% - 24px) / 3);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
border: 1px solid rgba(24, 32, 29, 0.12);
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 22px 68px rgba(24, 32, 29, 0.14);
|
||||
pointer-events: auto;
|
||||
animation: workspaceDrawerIn 180ms cubic-bezier(0.2, 0.82, 0.2, 1);
|
||||
}
|
||||
|
||||
.workspace-drawer.open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
@keyframes workspaceDrawerIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(18px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-drawer-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 16px 16px 12px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.workspace-drawer-header > div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-drawer-header span {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.workspace-drawer-header strong {
|
||||
color: var(--ink);
|
||||
font-size: 22px;
|
||||
font-weight: 760;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workspace-drawer-header small {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.workspace-drawer-close {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
display: grid;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
color: var(--ink);
|
||||
background: #f7f7f8;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workspace-drawer-close:hover {
|
||||
background: #efeff1;
|
||||
}
|
||||
|
||||
.workspace-scope-list,
|
||||
.workspace-fields-drawer,
|
||||
.workspace-drawer > .workspace-issue-list {
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.workspace-scope-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.workspace-scope-list button {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
gap: 4px 9px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workspace-scope-list button:hover,
|
||||
.workspace-scope-list button.active {
|
||||
background: #fffaf5;
|
||||
border-color: rgba(242, 122, 26, 0.34);
|
||||
}
|
||||
|
||||
.workspace-scope-list button span {
|
||||
grid-row: span 2;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--ink);
|
||||
background: #f3f3f4;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.workspace-scope-list button.active span {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.workspace-scope-list strong,
|
||||
.workspace-scope-list small {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.workspace-scope-list strong {
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
font-weight: 760;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-scope-list small {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-scope-list em {
|
||||
grid-row: span 2;
|
||||
align-self: center;
|
||||
padding: 4px 8px;
|
||||
color: var(--muted);
|
||||
background: #f6f6f7;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 760;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workspace-fields-drawer {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.workspace-fields-drawer .section-editor-card {
|
||||
padding: 14px !important;
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
|
||||
.workspace-fields-drawer .workspace-field-card.active {
|
||||
background: #fffaf5 !important;
|
||||
border-color: rgba(242, 122, 26, 0.36) !important;
|
||||
}
|
||||
|
||||
.workspace-field-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.workspace-field-meta span,
|
||||
.workspace-field-meta em {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 0 8px;
|
||||
color: var(--muted);
|
||||
background: #f5f5f6;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 760;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workspace-fields-drawer .section-editor-card pre {
|
||||
max-height: 140px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.workspace-fields-drawer .section-editor-card textarea {
|
||||
min-height: 170px;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-page-issues {
|
||||
|
|
@ -6485,6 +6976,21 @@ input[type="checkbox"] {
|
|||
transform: none !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-issue-list button {
|
||||
grid-template-columns: auto minmax(0, 1fr) !important;
|
||||
padding: 11px !important;
|
||||
border-radius: 14px !important;
|
||||
}
|
||||
|
||||
.stage-3 .workspace-issue-list button p {
|
||||
grid-column: 2;
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.stage-3 .section-map-card.active,
|
||||
.stage-3 .section-map-card.active:hover {
|
||||
color: var(--ink) !important;
|
||||
|
|
@ -6524,9 +7030,33 @@ input[type="checkbox"] {
|
|||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.workspace-current-page {
|
||||
.workspace-cockpit-toolbar,
|
||||
.workspace-current-page-actions {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.workspace-current-page-actions,
|
||||
.workspace-current-page-actions > button,
|
||||
.workspace-page-control,
|
||||
.workspace-page-control-trigger,
|
||||
.workspace-preview-mode-switch {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workspace-preview-shell {
|
||||
min-height: 720px;
|
||||
}
|
||||
|
||||
.workspace-drawer-stack {
|
||||
inset: 8px;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.workspace-drawer {
|
||||
flex: 0 0 min(76%, 620px);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ function buildPreviewBridgeScript() {
|
|||
return `
|
||||
<script>
|
||||
(() => {
|
||||
const state = { workspaceKey: "", targets: [] };
|
||||
const state = { workspaceKey: "", targets: [], scopePages: [], activeScopePageId: "", lastScopeReportAt: 0 };
|
||||
const layerId = "seo-mode-preview-marker-layer";
|
||||
let frame = 0;
|
||||
let lastActivation = { id: "", at: 0 };
|
||||
|
|
@ -525,6 +525,73 @@ function buildPreviewBridgeScript() {
|
|||
return marker;
|
||||
}
|
||||
|
||||
function getElementForSelector(selector) {
|
||||
if (!selector) return null;
|
||||
|
||||
try {
|
||||
return document.querySelector(selector);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function reportActiveScopePage() {
|
||||
if (!state.workspaceKey || !Array.isArray(state.scopePages) || state.scopePages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeAtProbe = null;
|
||||
let nearestBelow = null;
|
||||
let lastAbove = null;
|
||||
const probeLine = Math.min(Math.max(window.innerHeight * 0.38, 180), Math.max(180, window.innerHeight - 120));
|
||||
|
||||
for (const page of state.scopePages) {
|
||||
const element = getElementForSelector(page.selector);
|
||||
if (!element) continue;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
if (rect.height <= 0 || rect.width <= 0) continue;
|
||||
|
||||
if (rect.top <= probeLine && rect.bottom > probeLine) {
|
||||
if (!activeAtProbe || rect.top > activeAtProbe.top) {
|
||||
activeAtProbe = { pageId: page.pageId, top: rect.top };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rect.top > probeLine) {
|
||||
if (!nearestBelow || rect.top < nearestBelow.top) {
|
||||
nearestBelow = { pageId: page.pageId, top: rect.top };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lastAbove || rect.bottom > lastAbove.bottom) {
|
||||
lastAbove = { pageId: page.pageId, bottom: rect.bottom };
|
||||
}
|
||||
}
|
||||
|
||||
const bestPage = activeAtProbe || nearestBelow || lastAbove;
|
||||
|
||||
if (!bestPage || bestPage.pageId === state.activeScopePageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (now - state.lastScopeReportAt < 120) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.activeScopePageId = bestPage.pageId;
|
||||
state.lastScopeReportAt = now;
|
||||
window.parent?.postMessage({
|
||||
type: "seo-mode:active-scope-page",
|
||||
workspaceKey: state.workspaceKey,
|
||||
pageId: bestPage.pageId
|
||||
}, "*");
|
||||
}
|
||||
|
||||
function measure() {
|
||||
const layer = ensureLayer();
|
||||
const root = document.documentElement;
|
||||
|
|
@ -609,6 +676,8 @@ function buildPreviewBridgeScript() {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
reportActiveScopePage();
|
||||
}
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
|
|
@ -620,6 +689,8 @@ function buildPreviewBridgeScript() {
|
|||
|
||||
state.workspaceKey = data.workspaceKey || "";
|
||||
state.targets = Array.isArray(data.targets) ? data.targets : [];
|
||||
state.scopePages = Array.isArray(data.scopePages) ? data.scopePages : [];
|
||||
state.activeScopePageId = data.activePageId || state.activeScopePageId || "";
|
||||
scheduleMeasure();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue