Fix workspace scope section mapping
This commit is contained in:
parent
1f140c1130
commit
08894288bc
|
|
@ -931,16 +931,112 @@ function getSelectedPagesCount(scan: ProjectScanSummary) {
|
||||||
return scan.pages.filter((page) => page.selected).length;
|
return scan.pages.filter((page) => page.selected).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedCoveragePages(scan: ProjectScanSummary) {
|
function getPageScopeId(page: ProjectScanSummary["pages"][number]) {
|
||||||
return scan.pages
|
return `page:${page.pageId}`;
|
||||||
.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) {
|
function getSectionScopeId(section: ProjectScanSummary["siteSections"][number]) {
|
||||||
return `${projectId}:${pageId}`;
|
return `section:${section.sectionId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkspaceScopeItem = {
|
||||||
|
scopeId: string;
|
||||||
|
kind: "page" | "section";
|
||||||
|
pageId: string;
|
||||||
|
sectionId?: string;
|
||||||
|
title: string;
|
||||||
|
pathLabel: string;
|
||||||
|
sourcePath: string;
|
||||||
|
previewSourcePath: string | null;
|
||||||
|
previewTargetSelector: string | null;
|
||||||
|
previewTargetIndex: number | null;
|
||||||
|
previewKind: ProjectScanSummary["pages"][number]["previewKind"];
|
||||||
|
order: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPageWorkspaceScopeItem(page: ProjectScanSummary["pages"][number], index: number): WorkspaceScopeItem {
|
||||||
|
return {
|
||||||
|
kind: "page",
|
||||||
|
order: getWorkspaceScopeOrder(page, index),
|
||||||
|
pageId: page.pageId,
|
||||||
|
pathLabel: getPagePathLabel(page),
|
||||||
|
previewKind: page.previewKind,
|
||||||
|
previewSourcePath: page.previewSourcePath ?? page.sourcePath,
|
||||||
|
previewTargetIndex: null,
|
||||||
|
previewTargetSelector: page.previewTargetSelector,
|
||||||
|
scopeId: getPageScopeId(page),
|
||||||
|
sourcePath: page.sourcePath,
|
||||||
|
title: getPageTitle(page)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSectionWorkspaceScopeItem(
|
||||||
|
section: ProjectScanSummary["siteSections"][number],
|
||||||
|
fallbackOrder: number
|
||||||
|
): WorkspaceScopeItem | null {
|
||||||
|
if (!section.pageId || !section.enabled || !section.selected) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section.previewKind === "none" && !section.previewSourcePath && !section.previewTargetSelector) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexSuffix = section.previewTargetIndex != null ? ` #${section.previewTargetIndex + 1}` : "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "section",
|
||||||
|
order: section.order ?? fallbackOrder,
|
||||||
|
pageId: section.pageId,
|
||||||
|
pathLabel: `${section.sourcePath} · секция главной${indexSuffix}`,
|
||||||
|
previewKind: section.previewKind,
|
||||||
|
previewSourcePath: section.previewSourcePath,
|
||||||
|
previewTargetIndex: section.previewTargetIndex,
|
||||||
|
previewTargetSelector: section.previewTargetSelector,
|
||||||
|
scopeId: getSectionScopeId(section),
|
||||||
|
sectionId: section.sectionId,
|
||||||
|
sourcePath: section.sourcePath,
|
||||||
|
title: section.title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedWorkspaceScopeItems(scan: ProjectScanSummary): WorkspaceScopeItem[] {
|
||||||
|
const sectionPageIds = new Set(
|
||||||
|
scan.siteSections
|
||||||
|
.filter((section) => section.selected && section.enabled && section.pageId)
|
||||||
|
.map((section) => section.pageId!)
|
||||||
|
);
|
||||||
|
const pageItems = scan.pages
|
||||||
|
.map((page, index) => ({ index, page }))
|
||||||
|
.filter(({ page }) => isWorkspaceScopePage(page))
|
||||||
|
.filter(({ page }) => page.scope !== "template_block" || !sectionPageIds.has(page.pageId))
|
||||||
|
.map(({ page, index }) => getPageWorkspaceScopeItem(page, index));
|
||||||
|
const sectionItems = scan.siteSections
|
||||||
|
.map((section, index) => getSectionWorkspaceScopeItem(section, index))
|
||||||
|
.filter((item): item is WorkspaceScopeItem => Boolean(item));
|
||||||
|
|
||||||
|
return [...pageItems, ...sectionItems].sort((left, right) => {
|
||||||
|
if (left.kind !== right.kind) {
|
||||||
|
if (left.kind === "page" && left.order === -1) return -1;
|
||||||
|
if (right.kind === "page" && right.order === -1) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.order - right.order || left.title.localeCompare(right.title, "ru");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedWorkspaceIssueCount(scan: ProjectScanSummary | null | undefined, scopeItems: WorkspaceScopeItem[]) {
|
||||||
|
if (!scan?.audit) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageIds = new Set(scopeItems.map((item) => item.pageId));
|
||||||
|
|
||||||
|
return scan.audit.issues.filter((issue) => issue.pageId && pageIds.has(issue.pageId)).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWorkspaceKey(projectId: string, scopeId: string) {
|
||||||
|
return `${projectId}:${scopeId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceSection = PageWorkspace["sections"][number];
|
type WorkspaceSection = PageWorkspace["sections"][number];
|
||||||
|
|
@ -1399,13 +1495,13 @@ export function App() {
|
||||||
const [auditIssueLimits, setAuditIssueLimits] = useState<Record<string, number>>({});
|
const [auditIssueLimits, setAuditIssueLimits] = useState<Record<string, number>>({});
|
||||||
const [auditSeverityFilters, setAuditSeverityFilters] = useState<Record<string, AuditSeverityFilter>>({});
|
const [auditSeverityFilters, setAuditSeverityFilters] = useState<Record<string, AuditSeverityFilter>>({});
|
||||||
const [openPageAuditDetails, setOpenPageAuditDetails] = useState<Record<string, boolean>>({});
|
const [openPageAuditDetails, setOpenPageAuditDetails] = useState<Record<string, boolean>>({});
|
||||||
const [activeWorkspacePageIds, setActiveWorkspacePageIds] = useState<Record<string, string>>({});
|
const [activeWorkspaceScopeIds, setActiveWorkspaceScopeIds] = useState<Record<string, string>>({});
|
||||||
const [activeWorkspaceSectionIds, setActiveWorkspaceSectionIds] = useState<Record<string, string>>({});
|
const [activeWorkspaceSectionIds, setActiveWorkspaceSectionIds] = useState<Record<string, string>>({});
|
||||||
const [activeWorkspaceTargetIds, setActiveWorkspaceTargetIds] = useState<Record<string, string>>({});
|
const [activeWorkspaceTargetIds, setActiveWorkspaceTargetIds] = useState<Record<string, string>>({});
|
||||||
const [workspacePreviewMarkers, setWorkspacePreviewMarkers] = useState<Record<string, PreviewMarker[]>>({});
|
const [workspacePreviewMarkers, setWorkspacePreviewMarkers] = useState<Record<string, PreviewMarker[]>>({});
|
||||||
const [workspaceDrawers, setWorkspaceDrawers] = useState<Record<string, WorkspaceDrawerPanel[]>>({});
|
const [workspaceDrawers, setWorkspaceDrawers] = useState<Record<string, WorkspaceDrawerPanel[]>>({});
|
||||||
const [workspacePageMenus, setWorkspacePageMenus] = useState<Record<string, boolean>>({});
|
const [workspacePageMenus, setWorkspacePageMenus] = useState<Record<string, boolean>>({});
|
||||||
const [workspacePreviewPageIds, setWorkspacePreviewPageIds] = useState<Record<string, string>>({});
|
const [workspacePreviewScopeIds, setWorkspacePreviewScopeIds] = useState<Record<string, string>>({});
|
||||||
const [pageWorkspaces, setPageWorkspaces] = useState<Record<string, PageWorkspace>>({});
|
const [pageWorkspaces, setPageWorkspaces] = useState<Record<string, PageWorkspace>>({});
|
||||||
const [workspaceDraftTexts, setWorkspaceDraftTexts] = useState<Record<string, string>>({});
|
const [workspaceDraftTexts, setWorkspaceDraftTexts] = useState<Record<string, string>>({});
|
||||||
const [workspaceSectionTexts, setWorkspaceSectionTexts] = useState<Record<string, WorkspaceSectionTextMap>>({});
|
const [workspaceSectionTexts, setWorkspaceSectionTexts] = useState<Record<string, WorkspaceSectionTextMap>>({});
|
||||||
|
|
@ -1619,26 +1715,27 @@ export function App() {
|
||||||
return () => undefined;
|
return () => undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopePages = getSelectedCoveragePages(scanSummary);
|
const scopeItems = getSelectedWorkspaceScopeItems(scanSummary);
|
||||||
const activePageId = activeWorkspacePageIds[projectId] ?? scopePages[0]?.pageId ?? "";
|
const activeScopeId = activeWorkspaceScopeIds[projectId] ?? scopeItems[0]?.scopeId ?? "";
|
||||||
const workspaceKey = activePageId ? getWorkspaceKey(projectId, activePageId) : "";
|
const activeScopeItem = scopeItems.find((item) => item.scopeId === activeScopeId) ?? scopeItems[0] ?? null;
|
||||||
|
const workspaceKey = activeScopeItem ? getWorkspaceKey(projectId, activeScopeItem.scopeId) : "";
|
||||||
const workspace = workspaceKey ? pageWorkspaces[workspaceKey] : null;
|
const workspace = workspaceKey ? pageWorkspaces[workspaceKey] : null;
|
||||||
|
|
||||||
if (!workspaceKey || !workspace) {
|
if (!workspaceKey || !workspace) {
|
||||||
return () => undefined;
|
return () => undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const issues = getPageIssues(scanSummary, activePageId);
|
const issues = getPageIssues(scanSummary, activeScopeItem?.pageId ?? null);
|
||||||
const targets = buildWorkspaceTargets(workspace, issues);
|
const targets = buildWorkspaceTargets(workspace, issues);
|
||||||
|
|
||||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 80));
|
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 80));
|
||||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 360));
|
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 360));
|
||||||
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 1000));
|
timers.push(window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 1000));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
timers.forEach((timer) => window.clearTimeout(timer));
|
timers.forEach((timer) => window.clearTimeout(timer));
|
||||||
};
|
};
|
||||||
}, [activeProjectId, activeStageIndex, activeWorkspacePageIds, pageWorkspaces, scanSummaries, workspacePreviewModes]);
|
}, [activeProjectId, activeStageIndex, activeWorkspaceScopeIds, pageWorkspaces, scanSummaries, workspacePreviewModes]);
|
||||||
|
|
||||||
function scrollWorkspaceFieldIntoView(workspaceKey: string, targetId: string, attempt = 0) {
|
function scrollWorkspaceFieldIntoView(workspaceKey: string, targetId: string, attempt = 0) {
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
|
|
@ -1663,21 +1760,27 @@ export function App() {
|
||||||
if (data?.type === "seo-mode:active-scope-page") {
|
if (data?.type === "seo-mode:active-scope-page") {
|
||||||
const sourceWorkspaceKey = typeof data.workspaceKey === "string" ? data.workspaceKey : "";
|
const sourceWorkspaceKey = typeof data.workspaceKey === "string" ? data.workspaceKey : "";
|
||||||
const pageId = typeof data.pageId === "string" ? data.pageId : "";
|
const pageId = typeof data.pageId === "string" ? data.pageId : "";
|
||||||
|
const scopeId =
|
||||||
|
typeof data.scopeId === "string" && data.scopeId.length > 0
|
||||||
|
? data.scopeId
|
||||||
|
: pageId
|
||||||
|
? `page:${pageId}`
|
||||||
|
: "";
|
||||||
const [projectId = ""] = sourceWorkspaceKey.split(":");
|
const [projectId = ""] = sourceWorkspaceKey.split(":");
|
||||||
|
|
||||||
if (!projectId || !pageId) {
|
if (!projectId || !scopeId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextWorkspaceKey = getWorkspaceKey(projectId, pageId);
|
const nextWorkspaceKey = getWorkspaceKey(projectId, scopeId);
|
||||||
|
|
||||||
if (!pageWorkspaces[nextWorkspaceKey]) {
|
if (!pageWorkspaces[nextWorkspaceKey]) {
|
||||||
const project = projects.find((item) => item.id === projectId);
|
const project = projects.find((item) => item.id === projectId);
|
||||||
const scanSummary = scanSummaries[projectId];
|
const scanSummary = scanSummaries[projectId];
|
||||||
const page = scanSummary ? getSelectedCoveragePages(scanSummary).find((item) => item.pageId === pageId) : null;
|
const scopeItem = scanSummary ? getSelectedWorkspaceScopeItems(scanSummary).find((item) => item.scopeId === scopeId) : null;
|
||||||
|
|
||||||
if (project && page && busyWorkspaceKey !== nextWorkspaceKey) {
|
if (project && scopeItem && busyWorkspaceKey !== nextWorkspaceKey) {
|
||||||
void handleOpenPageWorkspace(project, page, {
|
void handleOpenPageWorkspace(project, scopeItem, {
|
||||||
deferActiveUntilLoaded: true,
|
deferActiveUntilLoaded: true,
|
||||||
navigatePreview: false,
|
navigatePreview: false,
|
||||||
silent: true
|
silent: true
|
||||||
|
|
@ -1687,14 +1790,14 @@ export function App() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveWorkspacePageIds((currentPageIds) => {
|
setActiveWorkspaceScopeIds((currentScopeIds) => {
|
||||||
if (currentPageIds[projectId] === pageId) {
|
if (currentScopeIds[projectId] === scopeId) {
|
||||||
return currentPageIds;
|
return currentScopeIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentPageIds,
|
...currentScopeIds,
|
||||||
[projectId]: pageId
|
[projectId]: scopeId
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
@ -2267,10 +2370,14 @@ export function App() {
|
||||||
|
|
||||||
async function handleOpenPageWorkspace(
|
async function handleOpenPageWorkspace(
|
||||||
project: ProjectSummary,
|
project: ProjectSummary,
|
||||||
page: ProjectScanSummary["pages"][number],
|
target: ProjectScanSummary["pages"][number] | WorkspaceScopeItem,
|
||||||
options: { deferActiveUntilLoaded?: boolean; navigatePreview?: boolean; silent?: boolean } = {}
|
options: { deferActiveUntilLoaded?: boolean; navigatePreview?: boolean; silent?: boolean } = {}
|
||||||
) {
|
) {
|
||||||
const workspaceKey = getWorkspaceKey(project.id, page.pageId);
|
const isScopeItem = "scopeId" in target;
|
||||||
|
const pageId = target.pageId;
|
||||||
|
const scopeId = isScopeItem ? target.scopeId : getPageScopeId(target);
|
||||||
|
const workspaceTitle = isScopeItem ? target.title : getPageTitle(target);
|
||||||
|
const workspaceKey = getWorkspaceKey(project.id, scopeId);
|
||||||
|
|
||||||
setBusyWorkspaceKey(workspaceKey);
|
setBusyWorkspaceKey(workspaceKey);
|
||||||
|
|
||||||
|
|
@ -2281,21 +2388,21 @@ export function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.navigatePreview !== false) {
|
if (options.navigatePreview !== false) {
|
||||||
setWorkspacePreviewPageIds((currentPageIds) => ({
|
setWorkspacePreviewScopeIds((currentScopeIds) => ({
|
||||||
...currentPageIds,
|
...currentScopeIds,
|
||||||
[project.id]: page.pageId
|
[project.id]: scopeId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.deferActiveUntilLoaded) {
|
if (!options.deferActiveUntilLoaded) {
|
||||||
setActiveWorkspacePageIds((currentPageIds) => ({
|
setActiveWorkspaceScopeIds((currentScopeIds) => ({
|
||||||
...currentPageIds,
|
...currentScopeIds,
|
||||||
[project.id]: page.pageId
|
[project.id]: scopeId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await openPageWorkspace(project.id, page.pageId);
|
const result = await openPageWorkspace(project.id, pageId);
|
||||||
|
|
||||||
setPageWorkspaces((currentWorkspaces) => ({
|
setPageWorkspaces((currentWorkspaces) => ({
|
||||||
...currentWorkspaces,
|
...currentWorkspaces,
|
||||||
|
|
@ -2318,14 +2425,14 @@ export function App() {
|
||||||
[workspaceKey]: currentTargetIds[workspaceKey] ?? result.workspace.sections[0]?.id ?? ""
|
[workspaceKey]: currentTargetIds[workspaceKey] ?? result.workspace.sections[0]?.id ?? ""
|
||||||
}));
|
}));
|
||||||
if (options.deferActiveUntilLoaded) {
|
if (options.deferActiveUntilLoaded) {
|
||||||
setActiveWorkspacePageIds((currentPageIds) => ({
|
setActiveWorkspaceScopeIds((currentScopeIds) => ({
|
||||||
...currentPageIds,
|
...currentScopeIds,
|
||||||
[project.id]: page.pageId
|
[project.id]: scopeId
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.silent) {
|
if (!options.silent) {
|
||||||
setProjectActionMessage(`Рабочая зона открыта: ${getPageTitle(page)}.`);
|
setProjectActionMessage(`Рабочая зона открыта: ${workspaceTitle}.`);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setProjectActionError(getErrorMessage(error, "Не удалось открыть рабочую зону страницы."));
|
setProjectActionError(getErrorMessage(error, "Не удалось открыть рабочую зону страницы."));
|
||||||
|
|
@ -2345,8 +2452,7 @@ export function App() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSavePageWorkspace(projectId: string, pageId: string) {
|
async function handleSavePageWorkspace(projectId: string, pageId: string, workspaceKey = getWorkspaceKey(projectId, `page:${pageId}`)) {
|
||||||
const workspaceKey = getWorkspaceKey(projectId, pageId);
|
|
||||||
const workspace = pageWorkspaces[workspaceKey];
|
const workspace = pageWorkspaces[workspaceKey];
|
||||||
const workingText = workspace
|
const workingText = workspace
|
||||||
? buildWorkingTextFromSections(workspace, workspaceSectionTexts[workspaceKey])
|
? buildWorkingTextFromSections(workspace, workspaceSectionTexts[workspaceKey])
|
||||||
|
|
@ -2383,15 +2489,13 @@ export function App() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleResetPageWorkspace(projectId: string, pageId: string) {
|
async function handleResetPageWorkspace(projectId: string, pageId: string, workspaceKey = getWorkspaceKey(projectId, `page:${pageId}`)) {
|
||||||
const confirmed = window.confirm("Сбросить working draft к original snapshot? Исходный проект не изменится.");
|
const confirmed = window.confirm("Сбросить working draft к original snapshot? Исходный проект не изменится.");
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceKey = getWorkspaceKey(projectId, pageId);
|
|
||||||
|
|
||||||
setBusyWorkspaceKey(workspaceKey);
|
setBusyWorkspaceKey(workspaceKey);
|
||||||
setProjectActionError(null);
|
setProjectActionError(null);
|
||||||
setProjectActionMessage(null);
|
setProjectActionMessage(null);
|
||||||
|
|
@ -2496,7 +2600,8 @@ export function App() {
|
||||||
function updateWorkspacePreviewMarkers(
|
function updateWorkspacePreviewMarkers(
|
||||||
workspaceKey: string,
|
workspaceKey: string,
|
||||||
targets: WorkspaceTarget[],
|
targets: WorkspaceTarget[],
|
||||||
scopePages: ProjectScanSummary["pages"] = []
|
scopeItems: WorkspaceScopeItem[] = [],
|
||||||
|
activeScopeItem: WorkspaceScopeItem | null = null
|
||||||
) {
|
) {
|
||||||
const frameWindow = previewFrameRefs.current[workspaceKey]?.contentWindow;
|
const frameWindow = previewFrameRefs.current[workspaceKey]?.contentWindow;
|
||||||
|
|
||||||
|
|
@ -2513,27 +2618,41 @@ export function App() {
|
||||||
{
|
{
|
||||||
type: "seo-mode:measure-targets",
|
type: "seo-mode:measure-targets",
|
||||||
workspaceKey,
|
workspaceKey,
|
||||||
activePageId: workspaceKey.split(":")[1] ?? "",
|
activePageId: activeScopeItem?.pageId ?? "",
|
||||||
scopePages: scopePages
|
activeScopeId: activeScopeItem?.scopeId ?? "",
|
||||||
.filter((page) => page.previewTargetSelector)
|
scopePages: scopeItems
|
||||||
.map((page) => ({
|
.filter((item) => item.previewTargetSelector)
|
||||||
pageId: page.pageId,
|
.map((item) => ({
|
||||||
label: getPageTitle(page),
|
label: item.title,
|
||||||
selector: page.previewTargetSelector
|
pageId: item.pageId,
|
||||||
|
scopeId: item.scopeId,
|
||||||
|
selector: item.previewTargetSelector,
|
||||||
|
selectorIndex: item.previewTargetIndex
|
||||||
})),
|
})),
|
||||||
targets: targets
|
targets: targets
|
||||||
.filter((target) => (target.markerSelectors.length > 0 || target.markerFallbackSelector) && target.issues.length > 0)
|
.filter((target) => (target.markerSelectors.length > 0 || target.markerFallbackSelector) && target.issues.length > 0)
|
||||||
.map((target) => ({
|
.map((target) => {
|
||||||
id: target.id,
|
const shouldAnchorToRenderedSection = activeScopeItem?.kind === "section" && Boolean(activeScopeItem.previewTargetSelector);
|
||||||
label: target.title,
|
const sectionFallbackSelector = shouldAnchorToRenderedSection
|
||||||
number: target.number,
|
? activeScopeItem?.previewTargetSelector ?? null
|
||||||
selector: target.selector,
|
: target.markerFallbackSelector;
|
||||||
selectors: target.markerSelectors,
|
|
||||||
fallbackSelector: target.markerFallbackSelector,
|
return {
|
||||||
pageStartFallback: target.kind !== "media",
|
fallbackSelector: sectionFallbackSelector,
|
||||||
preferPageStart: target.sectionId === "seo-title" || target.sectionId === "seo-description" || target.sectionId === "seo-h1",
|
fallbackSelectorIndex: shouldAnchorToRenderedSection ? activeScopeItem?.previewTargetIndex ?? 0 : null,
|
||||||
sectionId: target.sectionId
|
id: target.id,
|
||||||
}))
|
label: target.title,
|
||||||
|
number: target.number,
|
||||||
|
pageStartFallback: !shouldAnchorToRenderedSection && target.kind !== "media",
|
||||||
|
preferPageStart:
|
||||||
|
!shouldAnchorToRenderedSection &&
|
||||||
|
(target.sectionId === "seo-title" || target.sectionId === "seo-description" || target.sectionId === "seo-h1"),
|
||||||
|
sectionId: target.sectionId,
|
||||||
|
selector: target.selector,
|
||||||
|
selectorIndex: null,
|
||||||
|
selectors: shouldAnchorToRenderedSection ? [] : target.markerSelectors
|
||||||
|
};
|
||||||
|
})
|
||||||
},
|
},
|
||||||
"*"
|
"*"
|
||||||
);
|
);
|
||||||
|
|
@ -2543,25 +2662,27 @@ export function App() {
|
||||||
workspaceKey: string,
|
workspaceKey: string,
|
||||||
mode: WorkspacePreviewMode,
|
mode: WorkspacePreviewMode,
|
||||||
targets: WorkspaceTarget[],
|
targets: WorkspaceTarget[],
|
||||||
scopePages: ProjectScanSummary["pages"] = []
|
scopeItems: WorkspaceScopeItem[] = [],
|
||||||
|
activeScopeItem: WorkspaceScopeItem | null = null
|
||||||
) {
|
) {
|
||||||
setWorkspacePreviewModes((currentModes) => ({
|
setWorkspacePreviewModes((currentModes) => ({
|
||||||
...currentModes,
|
...currentModes,
|
||||||
[workspaceKey]: mode
|
[workspaceKey]: mode
|
||||||
}));
|
}));
|
||||||
|
|
||||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 120);
|
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 120);
|
||||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 450);
|
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 450);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleWorkspacePreviewLoad(
|
function handleWorkspacePreviewLoad(
|
||||||
workspaceKey: string,
|
workspaceKey: string,
|
||||||
targets: WorkspaceTarget[],
|
targets: WorkspaceTarget[],
|
||||||
scopePages: ProjectScanSummary["pages"] = []
|
scopeItems: WorkspaceScopeItem[] = [],
|
||||||
|
activeScopeItem: WorkspaceScopeItem | null = null
|
||||||
) {
|
) {
|
||||||
updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages);
|
updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem);
|
||||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 350);
|
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 350);
|
||||||
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopePages), 1000);
|
window.setTimeout(() => updateWorkspacePreviewMarkers(workspaceKey, targets, scopeItems, activeScopeItem), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAuditIssuesVisibleCount(scanId: string, visibleCount: number) {
|
function setAuditIssuesVisibleCount(scanId: string, visibleCount: number) {
|
||||||
|
|
@ -2614,15 +2735,15 @@ export function App() {
|
||||||
delete nextIds[project.id];
|
delete nextIds[project.id];
|
||||||
return nextIds;
|
return nextIds;
|
||||||
});
|
});
|
||||||
setActiveWorkspacePageIds((currentPageIds) => {
|
setActiveWorkspaceScopeIds((currentScopeIds) => {
|
||||||
const nextPageIds = { ...currentPageIds };
|
const nextScopeIds = { ...currentScopeIds };
|
||||||
delete nextPageIds[project.id];
|
delete nextScopeIds[project.id];
|
||||||
return nextPageIds;
|
return nextScopeIds;
|
||||||
});
|
});
|
||||||
setWorkspacePreviewPageIds((currentPageIds) => {
|
setWorkspacePreviewScopeIds((currentScopeIds) => {
|
||||||
const nextPageIds = { ...currentPageIds };
|
const nextScopeIds = { ...currentScopeIds };
|
||||||
delete nextPageIds[project.id];
|
delete nextScopeIds[project.id];
|
||||||
return nextPageIds;
|
return nextScopeIds;
|
||||||
});
|
});
|
||||||
setPageWorkspaces((currentWorkspaces) =>
|
setPageWorkspaces((currentWorkspaces) =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
|
|
@ -2719,30 +2840,30 @@ export function App() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedWorkspacePages = getSelectedCoveragePages(scanSummary);
|
const selectedWorkspaceScopeItems = getSelectedWorkspaceScopeItems(scanSummary);
|
||||||
const selectedPage = selectedWorkspacePages[0];
|
const selectedScopeItem = selectedWorkspaceScopeItems[0];
|
||||||
|
|
||||||
if (!selectedPage) {
|
if (!selectedScopeItem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activePageId = activeWorkspacePageIds[project.id] ?? selectedPage.pageId;
|
const activeScopeId = activeWorkspaceScopeIds[project.id] ?? selectedScopeItem.scopeId;
|
||||||
const pageToOpen =
|
const scopeItemToOpen =
|
||||||
selectedWorkspacePages.find((page) => page.pageId === activePageId) ??
|
selectedWorkspaceScopeItems.find((item) => item.scopeId === activeScopeId) ??
|
||||||
selectedPage;
|
selectedScopeItem;
|
||||||
const workspaceKey = getWorkspaceKey(project.id, pageToOpen.pageId);
|
const workspaceKey = getWorkspaceKey(project.id, scopeItemToOpen.scopeId);
|
||||||
|
|
||||||
if (pageWorkspaces[workspaceKey] || busyWorkspaceKey === workspaceKey) {
|
if (pageWorkspaces[workspaceKey] || busyWorkspaceKey === workspaceKey) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleOpenPageWorkspace(project, pageToOpen, {
|
void handleOpenPageWorkspace(project, scopeItemToOpen, {
|
||||||
navigatePreview: !workspacePreviewPageIds[project.id],
|
navigatePreview: !workspacePreviewScopeIds[project.id],
|
||||||
silent: true
|
silent: true
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [activeWorkspacePageIds, busyWorkspaceKey, pageWorkspaces, projects, scanSummaries, workspacePreviewPageIds]);
|
}, [activeWorkspaceScopeIds, busyWorkspaceKey, pageWorkspaces, projects, scanSummaries, workspacePreviewScopeIds]);
|
||||||
|
|
||||||
const createNameConflict = pickedFolder ? hasProjectNameConflict(pickedFolder.rootName) : false;
|
const createNameConflict = pickedFolder ? hasProjectNameConflict(pickedFolder.rootName) : false;
|
||||||
const canCreateProject = Boolean(pickedFolder) && !isSubmitting && !createNameConflict;
|
const canCreateProject = Boolean(pickedFolder) && !isSubmitting && !createNameConflict;
|
||||||
|
|
@ -3325,23 +3446,26 @@ export function App() {
|
||||||
const isSelectingPages = selectingPagesProjectId === project.id;
|
const isSelectingPages = selectingPagesProjectId === project.id;
|
||||||
const selectedPagesCount = scanSummary ? getSelectedPagesCount(scanSummary) : 0;
|
const selectedPagesCount = scanSummary ? getSelectedPagesCount(scanSummary) : 0;
|
||||||
const selectedPages = scanSummary ? scanSummary.pages.filter((page) => page.selected) : [];
|
const selectedPages = scanSummary ? scanSummary.pages.filter((page) => page.selected) : [];
|
||||||
const selectedCoveragePages = scanSummary ? getSelectedCoveragePages(scanSummary) : [];
|
const selectedWorkspaceScopeItems = scanSummary ? getSelectedWorkspaceScopeItems(scanSummary) : [];
|
||||||
const scanScopeCounts = scanSummary ? getScanScopeCounts(scanSummary) : null;
|
const scanScopeCounts = scanSummary ? getScanScopeCounts(scanSummary) : null;
|
||||||
const projectSiteTitle = getProjectSiteTitle(scanSummary);
|
const projectSiteTitle = getProjectSiteTitle(scanSummary);
|
||||||
const pageTreeGroups = scanSummary ? getPageTreeGroups(scanSummary.pages) : [];
|
const pageTreeGroups = scanSummary ? getPageTreeGroups(scanSummary.pages) : [];
|
||||||
const activeWorkspacePageId = activeWorkspacePageIds[project.id] ?? selectedCoveragePages[0]?.pageId ?? null;
|
const activeWorkspaceScopeId =
|
||||||
const activeWorkspaceKey = activeWorkspacePageId ? getWorkspaceKey(project.id, activeWorkspacePageId) : null;
|
activeWorkspaceScopeIds[project.id] ?? selectedWorkspaceScopeItems[0]?.scopeId ?? null;
|
||||||
const activeWorkspace = activeWorkspaceKey ? pageWorkspaces[activeWorkspaceKey] : null;
|
const activeWorkspaceScopeItem =
|
||||||
const activeWorkspacePage =
|
selectedWorkspaceScopeItems.find((item) => item.scopeId === activeWorkspaceScopeId) ??
|
||||||
selectedCoveragePages.find((page) => page.pageId === activeWorkspacePageId) ??
|
selectedWorkspaceScopeItems[0] ??
|
||||||
scanSummary?.pages.find((page) => page.pageId === activeWorkspacePageId) ??
|
|
||||||
null;
|
null;
|
||||||
|
const activeWorkspaceKey = activeWorkspaceScopeItem
|
||||||
|
? getWorkspaceKey(project.id, activeWorkspaceScopeItem.scopeId)
|
||||||
|
: null;
|
||||||
|
const activeWorkspace = activeWorkspaceKey ? pageWorkspaces[activeWorkspaceKey] : null;
|
||||||
const activeWorkspaceText =
|
const activeWorkspaceText =
|
||||||
activeWorkspaceKey && activeWorkspace
|
activeWorkspaceKey && activeWorkspace
|
||||||
? (workspaceDraftTexts[activeWorkspaceKey] ?? activeWorkspace.item.workingText)
|
? (workspaceDraftTexts[activeWorkspaceKey] ?? activeWorkspace.item.workingText)
|
||||||
: "";
|
: "";
|
||||||
const isWorkspaceBusy = activeWorkspaceKey ? busyWorkspaceKey === activeWorkspaceKey : false;
|
const isWorkspaceBusy = activeWorkspaceKey ? busyWorkspaceKey === activeWorkspaceKey : false;
|
||||||
const activeWorkspaceIssues = getPageIssues(scanSummary, activeWorkspacePageId);
|
const activeWorkspaceIssues = getPageIssues(scanSummary, activeWorkspaceScopeItem?.pageId ?? null);
|
||||||
const activeWorkspaceTargets = activeWorkspace ? buildWorkspaceTargets(activeWorkspace, activeWorkspaceIssues) : [];
|
const activeWorkspaceTargets = activeWorkspace ? buildWorkspaceTargets(activeWorkspace, activeWorkspaceIssues) : [];
|
||||||
const activeWorkspaceTargetId =
|
const activeWorkspaceTargetId =
|
||||||
activeWorkspaceKey && activeWorkspaceTargets.length > 0
|
activeWorkspaceKey && activeWorkspaceTargets.length > 0
|
||||||
|
|
@ -3355,24 +3479,22 @@ export function App() {
|
||||||
const activeWorkspacePreviewMode = activeWorkspaceKey ? (workspacePreviewModes[activeWorkspaceKey] ?? "desktop") : "desktop";
|
const activeWorkspacePreviewMode = activeWorkspaceKey ? (workspacePreviewModes[activeWorkspaceKey] ?? "desktop") : "desktop";
|
||||||
const activeWorkspaceDrawers = activeWorkspaceKey ? (workspaceDrawers[activeWorkspaceKey] ?? []) : [];
|
const activeWorkspaceDrawers = activeWorkspaceKey ? (workspaceDrawers[activeWorkspaceKey] ?? []) : [];
|
||||||
const isWorkspacePageMenuOpen = activeWorkspaceKey ? Boolean(workspacePageMenus[activeWorkspaceKey]) : false;
|
const isWorkspacePageMenuOpen = activeWorkspaceKey ? Boolean(workspacePageMenus[activeWorkspaceKey]) : false;
|
||||||
const previewWorkspacePageId = workspacePreviewPageIds[project.id] ?? activeWorkspacePageId;
|
const previewWorkspaceScopeId = workspacePreviewScopeIds[project.id] ?? activeWorkspaceScopeItem?.scopeId ?? null;
|
||||||
const previewWorkspacePage =
|
const previewWorkspaceScopeItem =
|
||||||
selectedCoveragePages.find((page) => page.pageId === previewWorkspacePageId) ??
|
selectedWorkspaceScopeItems.find((item) => item.scopeId === previewWorkspaceScopeId) ??
|
||||||
activeWorkspacePage;
|
activeWorkspaceScopeItem;
|
||||||
const activeWorkspacePageIndex = Math.max(
|
const activeWorkspacePageIndex = Math.max(
|
||||||
0,
|
0,
|
||||||
selectedCoveragePages.findIndex((page) => page.pageId === activeWorkspacePageId)
|
selectedWorkspaceScopeItems.findIndex((item) => item.scopeId === activeWorkspaceScopeItem?.scopeId)
|
||||||
);
|
);
|
||||||
const activeWorkspacePreviewSourcePath =
|
const activeWorkspacePreviewSourcePath =
|
||||||
activeWorkspace && previewWorkspacePage
|
activeWorkspace && previewWorkspaceScopeItem
|
||||||
? (previewWorkspacePage.previewSourcePath ?? previewWorkspacePage.sourcePath)
|
? (previewWorkspaceScopeItem.previewSourcePath ?? previewWorkspaceScopeItem.sourcePath)
|
||||||
: "";
|
: "";
|
||||||
const activeWorkspacePreviewFocusSelector = previewWorkspacePage?.previewTargetSelector ?? null;
|
const activeWorkspacePreviewFocusSelector = previewWorkspaceScopeItem?.previewTargetSelector ?? null;
|
||||||
|
const activeWorkspacePreviewFocusIndex = previewWorkspaceScopeItem?.previewTargetIndex ?? null;
|
||||||
const activeWorkspaceIssueCounts = getAuditIssueCounts(activeWorkspaceIssues);
|
const activeWorkspaceIssueCounts = getAuditIssueCounts(activeWorkspaceIssues);
|
||||||
const selectedCoverageIssueCount = selectedCoveragePages.reduce(
|
const selectedCoverageIssueCount = getSelectedWorkspaceIssueCount(scanSummary, selectedWorkspaceScopeItems);
|
||||||
(total, page) => total + getPageIssues(scanSummary, page.pageId).length,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const selectedAuditIssuesAll = scanSummary?.audit
|
const selectedAuditIssuesAll = scanSummary?.audit
|
||||||
? getSelectedAuditIssues(scanSummary, scanSummary.audit.issues)
|
? getSelectedAuditIssues(scanSummary, scanSummary.audit.issues)
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -3534,7 +3656,7 @@ export function App() {
|
||||||
<summary>Инженерный список аудита</summary>
|
<summary>Инженерный список аудита</summary>
|
||||||
<div className="page-audit-list">
|
<div className="page-audit-list">
|
||||||
{scanSummary.pages.map((page) => {
|
{scanSummary.pages.map((page) => {
|
||||||
const workspaceKey = getWorkspaceKey(project.id, page.pageId);
|
const workspaceKey = getWorkspaceKey(project.id, getPageScopeId(page));
|
||||||
const isOpeningPage = busyWorkspaceKey === workspaceKey;
|
const isOpeningPage = busyWorkspaceKey === workspaceKey;
|
||||||
const auditDetailsKey = `${scanSummary.id}:${page.pageId}`;
|
const auditDetailsKey = `${scanSummary.id}:${page.pageId}`;
|
||||||
const isAuditDetailsOpen = Boolean(openPageAuditDetails[auditDetailsKey]);
|
const isAuditDetailsOpen = Boolean(openPageAuditDetails[auditDetailsKey]);
|
||||||
|
|
@ -4584,34 +4706,34 @@ export function App() {
|
||||||
<span>Apply пока не трогаем</span>
|
<span>Apply пока не трогаем</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="workspace-page-tabs">
|
<div className="workspace-page-tabs">
|
||||||
{selectedCoveragePages.map((page) => {
|
{selectedWorkspaceScopeItems.map((scopeItem) => {
|
||||||
const workspaceKey = getWorkspaceKey(project.id, page.pageId);
|
const workspaceKey = getWorkspaceKey(project.id, scopeItem.scopeId);
|
||||||
const isActivePage = activeWorkspacePageId === page.pageId;
|
const isActivePage = activeWorkspaceScopeItem?.scopeId === scopeItem.scopeId;
|
||||||
const isOpening = busyWorkspaceKey === workspaceKey;
|
const isOpening = busyWorkspaceKey === workspaceKey;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={isActivePage ? "active" : undefined}
|
className={isActivePage ? "active" : undefined}
|
||||||
disabled={isOpening}
|
disabled={isOpening}
|
||||||
key={`workspace-tab-${page.pageId}`}
|
key={`workspace-tab-${scopeItem.scopeId}`}
|
||||||
onClick={() => void handleOpenPageWorkspace(project, page)}
|
onClick={() => void handleOpenPageWorkspace(project, scopeItem)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{isOpening ? <Loader2 className="spin" size={14} /> : <FileText size={14} />}
|
{isOpening ? <Loader2 className="spin" size={14} /> : <FileText size={14} />}
|
||||||
<span>{getPageTitle(page)}</span>
|
<span>{scopeItem.title}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{activeWorkspace && activeWorkspacePage && activeWorkspaceKey ? (
|
{activeWorkspace && activeWorkspaceScopeItem && activeWorkspaceKey ? (
|
||||||
<div className="workspace-editor-stack workspace-cockpit">
|
<div className="workspace-editor-stack workspace-cockpit">
|
||||||
<div className="workspace-current-page">
|
<div className="workspace-current-page">
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
Активная страница {activeWorkspacePageIndex + 1}/{selectedCoveragePages.length} · выбранный scope
|
Активная зона {activeWorkspacePageIndex + 1}/{selectedWorkspaceScopeItems.length} · выбранный scope
|
||||||
</span>
|
</span>
|
||||||
<strong>{getPageTitle(activeWorkspacePage)}</strong>
|
<strong>{activeWorkspaceScopeItem.title}</strong>
|
||||||
<small>{getPagePathLabel(activeWorkspacePage)}</small>
|
<small>{activeWorkspaceScopeItem.pathLabel}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section className={`workspace-preview-panel workspace-cockpit-panel ${activeWorkspaceDrawers.length > 0 ? "drawer-open" : ""}`}>
|
<section className={`workspace-preview-panel workspace-cockpit-panel ${activeWorkspaceDrawers.length > 0 ? "drawer-open" : ""}`}>
|
||||||
|
|
@ -4633,7 +4755,7 @@ export function App() {
|
||||||
<FileText size={14} />
|
<FileText size={14} />
|
||||||
Страница
|
Страница
|
||||||
<em>
|
<em>
|
||||||
{activeWorkspacePageIndex + 1}/{selectedCoveragePages.length}
|
{activeWorkspacePageIndex + 1}/{selectedWorkspaceScopeItems.length}
|
||||||
</em>
|
</em>
|
||||||
<ChevronDown size={14} />
|
<ChevronDown size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -4643,32 +4765,32 @@ export function App() {
|
||||||
<strong>Страницы и секции scope</strong>
|
<strong>Страницы и секции scope</strong>
|
||||||
<small>Переключает preview, ошибки и поля рабочей зоны.</small>
|
<small>Переключает preview, ошибки и поля рабочей зоны.</small>
|
||||||
</div>
|
</div>
|
||||||
{selectedCoveragePages.length > 0 ? (
|
{selectedWorkspaceScopeItems.length > 0 ? (
|
||||||
<div className="workspace-page-menu-list">
|
<div className="workspace-page-menu-list">
|
||||||
{selectedCoveragePages.map((page, pageIndex) => {
|
{selectedWorkspaceScopeItems.map((scopeItem, pageIndex) => {
|
||||||
const pageWorkspaceKey = getWorkspaceKey(project.id, page.pageId);
|
const pageWorkspaceKey = getWorkspaceKey(project.id, scopeItem.scopeId);
|
||||||
const isActivePage = activeWorkspacePageId === page.pageId;
|
const isActivePage = activeWorkspaceScopeItem?.scopeId === scopeItem.scopeId;
|
||||||
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
||||||
const pageIssues = getPageIssues(scanSummary, page.pageId);
|
const pageIssues = getPageIssues(scanSummary, scopeItem.pageId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={isActivePage ? "active" : undefined}
|
className={isActivePage ? "active" : undefined}
|
||||||
disabled={isOpening}
|
disabled={isOpening}
|
||||||
key={`workspace-page-menu-${page.pageId}`}
|
key={`workspace-page-menu-${scopeItem.scopeId}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setWorkspacePageMenus((currentMenus) => ({
|
setWorkspacePageMenus((currentMenus) => ({
|
||||||
...currentMenus,
|
...currentMenus,
|
||||||
[activeWorkspaceKey]: false
|
[activeWorkspaceKey]: false
|
||||||
}));
|
}));
|
||||||
void handleOpenPageWorkspace(project, page, { silent: true });
|
void handleOpenPageWorkspace(project, scopeItem, { silent: true });
|
||||||
}}
|
}}
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span>{pageIndex + 1}</span>
|
<span>{pageIndex + 1}</span>
|
||||||
<strong>{getPageTitle(page)}</strong>
|
<strong>{scopeItem.title}</strong>
|
||||||
<small>{getPagePathLabel(page)}</small>
|
<small>{scopeItem.pathLabel}</small>
|
||||||
<em>{pageIssues.length} замечаний</em>
|
<em>{pageIssues.length} замечаний</em>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -4689,7 +4811,7 @@ export function App() {
|
||||||
>
|
>
|
||||||
<FileText size={14} />
|
<FileText size={14} />
|
||||||
Scope
|
Scope
|
||||||
<em>{selectedCoveragePages.length}</em>
|
<em>{selectedWorkspaceScopeItems.length}</em>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={activeWorkspaceDrawers.includes("issues") ? "active" : undefined}
|
className={activeWorkspaceDrawers.includes("issues") ? "active" : undefined}
|
||||||
|
|
@ -4719,7 +4841,8 @@ export function App() {
|
||||||
activeWorkspaceKey,
|
activeWorkspaceKey,
|
||||||
mode.id,
|
mode.id,
|
||||||
activeWorkspaceTargets,
|
activeWorkspaceTargets,
|
||||||
selectedCoveragePages
|
selectedWorkspaceScopeItems,
|
||||||
|
activeWorkspaceScopeItem
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -4737,7 +4860,8 @@ export function App() {
|
||||||
handleWorkspacePreviewLoad(
|
handleWorkspacePreviewLoad(
|
||||||
activeWorkspaceKey,
|
activeWorkspaceKey,
|
||||||
activeWorkspaceTargets,
|
activeWorkspaceTargets,
|
||||||
selectedCoveragePages
|
selectedWorkspaceScopeItems,
|
||||||
|
activeWorkspaceScopeItem
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ref={(element) => {
|
ref={(element) => {
|
||||||
|
|
@ -4753,9 +4877,10 @@ export function App() {
|
||||||
src={getPreviewPageUrl(
|
src={getPreviewPageUrl(
|
||||||
project.id,
|
project.id,
|
||||||
activeWorkspacePreviewSourcePath,
|
activeWorkspacePreviewSourcePath,
|
||||||
activeWorkspacePreviewFocusSelector
|
activeWorkspacePreviewFocusSelector,
|
||||||
|
activeWorkspacePreviewFocusIndex
|
||||||
)}
|
)}
|
||||||
title={`Предпросмотр ${getPageTitle(activeWorkspacePage)}`}
|
title={`Предпросмотр ${activeWorkspaceScopeItem.title}`}
|
||||||
/>
|
/>
|
||||||
<div className="workspace-preview-markers">
|
<div className="workspace-preview-markers">
|
||||||
{activeWorkspaceMarkers.map((marker) => {
|
{activeWorkspaceMarkers.map((marker) => {
|
||||||
|
|
@ -4796,7 +4921,7 @@ export function App() {
|
||||||
</span>
|
</span>
|
||||||
<strong>
|
<strong>
|
||||||
{activeWorkspaceDrawer === "scope"
|
{activeWorkspaceDrawer === "scope"
|
||||||
? `${selectedCoveragePages.length} в scope`
|
? `${selectedWorkspaceScopeItems.length} в scope`
|
||||||
: activeWorkspaceDrawer === "fields"
|
: activeWorkspaceDrawer === "fields"
|
||||||
? `${activeWorkspaceTargets.length} полей`
|
? `${activeWorkspaceTargets.length} полей`
|
||||||
: `${activeWorkspaceIssues.length} замечаний`}
|
: `${activeWorkspaceIssues.length} замечаний`}
|
||||||
|
|
@ -4820,23 +4945,23 @@ export function App() {
|
||||||
</div>
|
</div>
|
||||||
{activeWorkspaceDrawer === "scope" ? (
|
{activeWorkspaceDrawer === "scope" ? (
|
||||||
<div className="workspace-scope-list">
|
<div className="workspace-scope-list">
|
||||||
{selectedCoveragePages.map((page, pageIndex) => {
|
{selectedWorkspaceScopeItems.map((scopeItem, pageIndex) => {
|
||||||
const pageWorkspaceKey = getWorkspaceKey(project.id, page.pageId);
|
const pageWorkspaceKey = getWorkspaceKey(project.id, scopeItem.scopeId);
|
||||||
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
const isOpening = busyWorkspaceKey === pageWorkspaceKey;
|
||||||
const isActivePage = activeWorkspacePageId === page.pageId;
|
const isActivePage = activeWorkspaceScopeItem?.scopeId === scopeItem.scopeId;
|
||||||
const pageIssues = getPageIssues(scanSummary, page.pageId);
|
const pageIssues = getPageIssues(scanSummary, scopeItem.pageId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={isActivePage ? "active" : undefined}
|
className={isActivePage ? "active" : undefined}
|
||||||
disabled={isOpening}
|
disabled={isOpening}
|
||||||
key={`workspace-scope-${page.pageId}`}
|
key={`workspace-scope-${scopeItem.scopeId}`}
|
||||||
onClick={() => void handleOpenPageWorkspace(project, page)}
|
onClick={() => void handleOpenPageWorkspace(project, scopeItem)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span>{pageIndex + 1}</span>
|
<span>{pageIndex + 1}</span>
|
||||||
<strong>{getPageTitle(page)}</strong>
|
<strong>{scopeItem.title}</strong>
|
||||||
<small>{getPagePathLabel(page)}</small>
|
<small>{scopeItem.pathLabel}</small>
|
||||||
<em>{pageIssues.length} замечаний</em>
|
<em>{pageIssues.length} замечаний</em>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
@ -4902,7 +5027,11 @@ export function App() {
|
||||||
className="tiny-action primary"
|
className="tiny-action primary"
|
||||||
disabled={isWorkspaceBusy}
|
disabled={isWorkspaceBusy}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
void handleSavePageWorkspace(project.id, activeWorkspace.pageId)
|
void handleSavePageWorkspace(
|
||||||
|
project.id,
|
||||||
|
activeWorkspace.pageId,
|
||||||
|
activeWorkspaceKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1039,16 +1039,38 @@ function buildScopeFlow(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScopeStats(scan: ProjectScanSummary) {
|
function getScopeStats(scan: ProjectScanSummary) {
|
||||||
return scan.pages.reduce(
|
const pageStats = scan.pages.reduce(
|
||||||
(stats, page) => {
|
(stats, page) => {
|
||||||
stats.total += 1;
|
stats.total += 1;
|
||||||
if (page.scope === "indexable_page") stats.indexable += 1;
|
if (page.scope === "indexable_page") stats.indexable += 1;
|
||||||
|
if (page.scope === "admin_page") stats.admin += 1;
|
||||||
|
if (page.scope === "technical_page") stats.technical += 1;
|
||||||
if (page.selected && page.scope === "indexable_page") stats.selectedIndexable += 1;
|
if (page.selected && page.scope === "indexable_page") stats.selectedIndexable += 1;
|
||||||
|
if (page.selected && page.scope === "admin_page") stats.selectedAdmin += 1;
|
||||||
|
if (page.selected && page.scope === "technical_page") stats.selectedTechnical += 1;
|
||||||
if (page.selected && page.scope !== "indexable_page") stats.selectedExcluded += 1;
|
if (page.selected && page.scope !== "indexable_page") stats.selectedExcluded += 1;
|
||||||
return stats;
|
return stats;
|
||||||
},
|
},
|
||||||
{ indexable: 0, selectedExcluded: 0, selectedIndexable: 0, total: 0 }
|
{
|
||||||
|
admin: 0,
|
||||||
|
indexable: 0,
|
||||||
|
selectedAdmin: 0,
|
||||||
|
selectedExcluded: 0,
|
||||||
|
selectedIndexable: 0,
|
||||||
|
selectedTechnical: 0,
|
||||||
|
technical: 0,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
const selectedSections = scan.siteSections.filter((section) => section.selected && section.enabled && section.pageId).length;
|
||||||
|
const enabledSections = scan.siteSections.filter((section) => section.enabled && section.pageId).length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pageStats,
|
||||||
|
enabledSections,
|
||||||
|
selectedSections,
|
||||||
|
selectedWorkspaceItems: pageStats.selectedIndexable + selectedSections + pageStats.selectedAdmin + pageStats.selectedTechnical
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldAutosaveNodeChanges(changes: NodeChange<ScopeFlowNode>[]) {
|
function shouldAutosaveNodeChanges(changes: NodeChange<ScopeFlowNode>[]) {
|
||||||
|
|
@ -1268,6 +1290,10 @@ export default function ProjectScopeFlow({
|
||||||
const groups = scopePriority
|
const groups = scopePriority
|
||||||
.map((scope) => ({
|
.map((scope) => ({
|
||||||
count: scope === "template_block" ? scan.siteSections.length : scan.pages.filter((page) => page.scope === scope).length,
|
count: scope === "template_block" ? scan.siteSections.length : scan.pages.filter((page) => page.scope === scope).length,
|
||||||
|
selected:
|
||||||
|
scope === "template_block"
|
||||||
|
? scan.siteSections.filter((section) => section.selected && section.enabled && section.pageId).length
|
||||||
|
: scan.pages.filter((page) => page.scope === scope && page.selected).length,
|
||||||
scope
|
scope
|
||||||
}))
|
}))
|
||||||
.filter((group) => group.count > 0);
|
.filter((group) => group.count > 0);
|
||||||
|
|
@ -1278,15 +1304,16 @@ export default function ProjectScopeFlow({
|
||||||
<div>
|
<div>
|
||||||
<strong>Конфигурация проекта</strong>
|
<strong>Конфигурация проекта</strong>
|
||||||
<small>
|
<small>
|
||||||
Выбрано посадочных {stats.selectedIndexable}/{stats.indexable}; технических выбранных{" "}
|
Выбрано рабочих зон {stats.selectedWorkspaceItems}: посадочных {stats.selectedIndexable}/{stats.indexable},
|
||||||
{stats.selectedExcluded}.
|
секций {stats.selectedSections}/{stats.enabledSections}, админка {stats.selectedAdmin}/{stats.admin},
|
||||||
|
техслой {stats.selectedTechnical}/{stats.technical}.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div className="project-scope-flow-actions">
|
<div className="project-scope-flow-actions">
|
||||||
<button disabled={isBusy} onClick={onApplyRecommendedScope} type="button">
|
<button disabled={isBusy} onClick={onApplyRecommendedScope} type="button">
|
||||||
SEO scope
|
SEO scope
|
||||||
</button>
|
</button>
|
||||||
<button disabled={isBusy || stats.selectedIndexable + stats.selectedExcluded === 0} onClick={onClearSelection} type="button">
|
<button disabled={isBusy || stats.selectedWorkspaceItems === 0} onClick={onClearSelection} type="button">
|
||||||
Снять все
|
Снять все
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1295,7 +1322,7 @@ export default function ProjectScopeFlow({
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<span className={`scope-legend-item ${group.scope}`} key={group.scope}>
|
<span className={`scope-legend-item ${group.scope}`} key={group.scope}>
|
||||||
<FileText size={13} />
|
<FileText size={13} />
|
||||||
{scopeGroupNames[group.scope]} · {group.count}
|
{scopeGroupNames[group.scope]} · {group.selected}/{group.count}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ function buildPreviewBridgeScript() {
|
||||||
return `
|
return `
|
||||||
<script>
|
<script>
|
||||||
(() => {
|
(() => {
|
||||||
const state = { workspaceKey: "", targets: [], scopePages: [], activeScopePageId: "", lastScopeReportAt: 0 };
|
const state = { workspaceKey: "", targets: [], scopePages: [], activeScopeId: "", activeScopePageId: "", lastScopeReportAt: 0 };
|
||||||
const layerId = "seo-mode-preview-marker-layer";
|
const layerId = "seo-mode-preview-marker-layer";
|
||||||
let frame = 0;
|
let frame = 0;
|
||||||
let lastActivation = { id: "", at: 0 };
|
let lastActivation = { id: "", at: 0 };
|
||||||
|
|
@ -428,9 +428,21 @@ function buildPreviewBridgeScript() {
|
||||||
return visibleRect;
|
return visibleRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUsableRect(selector) {
|
function getElementForSelector(selector, selectorIndex) {
|
||||||
if (!selector) return null;
|
if (!selector) return null;
|
||||||
const element = document.querySelector(selector);
|
|
||||||
|
try {
|
||||||
|
const index = Number.isInteger(selectorIndex) && selectorIndex >= 0 ? selectorIndex : 0;
|
||||||
|
const matches = Array.from(document.querySelectorAll(selector));
|
||||||
|
|
||||||
|
return matches[Math.max(0, Math.min(index, matches.length - 1))] || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUsableRect(selector, selectorIndex) {
|
||||||
|
const element = getElementForSelector(selector, selectorIndex);
|
||||||
if (!element) return null;
|
if (!element) return null;
|
||||||
const style = window.getComputedStyle(element);
|
const style = window.getComputedStyle(element);
|
||||||
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return null;
|
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return null;
|
||||||
|
|
@ -525,16 +537,6 @@ function buildPreviewBridgeScript() {
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElementForSelector(selector) {
|
|
||||||
if (!selector) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
return document.querySelector(selector);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportActiveScopePage() {
|
function reportActiveScopePage() {
|
||||||
if (!state.workspaceKey || !Array.isArray(state.scopePages) || state.scopePages.length === 0) {
|
if (!state.workspaceKey || !Array.isArray(state.scopePages) || state.scopePages.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -546,7 +548,7 @@ function buildPreviewBridgeScript() {
|
||||||
const probeLine = Math.min(Math.max(window.innerHeight * 0.38, 180), Math.max(180, window.innerHeight - 120));
|
const probeLine = Math.min(Math.max(window.innerHeight * 0.38, 180), Math.max(180, window.innerHeight - 120));
|
||||||
|
|
||||||
for (const page of state.scopePages) {
|
for (const page of state.scopePages) {
|
||||||
const element = getElementForSelector(page.selector);
|
const element = getElementForSelector(page.selector, page.selectorIndex);
|
||||||
if (!element) continue;
|
if (!element) continue;
|
||||||
|
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
|
|
@ -555,26 +557,26 @@ function buildPreviewBridgeScript() {
|
||||||
|
|
||||||
if (rect.top <= probeLine && rect.bottom > probeLine) {
|
if (rect.top <= probeLine && rect.bottom > probeLine) {
|
||||||
if (!activeAtProbe || rect.top > activeAtProbe.top) {
|
if (!activeAtProbe || rect.top > activeAtProbe.top) {
|
||||||
activeAtProbe = { pageId: page.pageId, top: rect.top };
|
activeAtProbe = { pageId: page.pageId, scopeId: page.scopeId || page.pageId, top: rect.top };
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rect.top > probeLine) {
|
if (rect.top > probeLine) {
|
||||||
if (!nearestBelow || rect.top < nearestBelow.top) {
|
if (!nearestBelow || rect.top < nearestBelow.top) {
|
||||||
nearestBelow = { pageId: page.pageId, top: rect.top };
|
nearestBelow = { pageId: page.pageId, scopeId: page.scopeId || page.pageId, top: rect.top };
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastAbove || rect.bottom > lastAbove.bottom) {
|
if (!lastAbove || rect.bottom > lastAbove.bottom) {
|
||||||
lastAbove = { pageId: page.pageId, bottom: rect.bottom };
|
lastAbove = { pageId: page.pageId, scopeId: page.scopeId || page.pageId, bottom: rect.bottom };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bestPage = activeAtProbe || nearestBelow || lastAbove;
|
const bestPage = activeAtProbe || nearestBelow || lastAbove;
|
||||||
|
|
||||||
if (!bestPage || bestPage.pageId === state.activeScopePageId) {
|
if (!bestPage || bestPage.scopeId === state.activeScopeId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -583,11 +585,13 @@ function buildPreviewBridgeScript() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.activeScopeId = bestPage.scopeId;
|
||||||
state.activeScopePageId = bestPage.pageId;
|
state.activeScopePageId = bestPage.pageId;
|
||||||
state.lastScopeReportAt = now;
|
state.lastScopeReportAt = now;
|
||||||
window.parent?.postMessage({
|
window.parent?.postMessage({
|
||||||
type: "seo-mode:active-scope-page",
|
type: "seo-mode:active-scope-page",
|
||||||
workspaceKey: state.workspaceKey,
|
workspaceKey: state.workspaceKey,
|
||||||
|
scopeId: bestPage.scopeId,
|
||||||
pageId: bestPage.pageId
|
pageId: bestPage.pageId
|
||||||
}, "*");
|
}, "*");
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +615,7 @@ function buildPreviewBridgeScript() {
|
||||||
let placedFallbackMarker = false;
|
let placedFallbackMarker = false;
|
||||||
|
|
||||||
for (const selector of selectors) {
|
for (const selector of selectors) {
|
||||||
const rect = getUsableRect(selector);
|
const rect = getUsableRect(selector, target.selectorIndex);
|
||||||
if (!rect) continue;
|
if (!rect) continue;
|
||||||
placedExactMarker = true;
|
placedExactMarker = true;
|
||||||
const baseLeft = Math.max(12, rect.left + window.scrollX + 10);
|
const baseLeft = Math.max(12, rect.left + window.scrollX + 10);
|
||||||
|
|
@ -623,7 +627,7 @@ function buildPreviewBridgeScript() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prefersPageStart && !placedExactMarker && target.fallbackSelector) {
|
if (!prefersPageStart && !placedExactMarker && target.fallbackSelector) {
|
||||||
const rect = getUsableRect(target.fallbackSelector);
|
const rect = getUsableRect(target.fallbackSelector, target.fallbackSelectorIndex);
|
||||||
if (rect) {
|
if (rect) {
|
||||||
placedFallbackMarker = true;
|
placedFallbackMarker = true;
|
||||||
const baseLeft = Math.max(12, rect.left + window.scrollX + 10);
|
const baseLeft = Math.max(12, rect.left + window.scrollX + 10);
|
||||||
|
|
@ -690,6 +694,7 @@ function buildPreviewBridgeScript() {
|
||||||
state.workspaceKey = data.workspaceKey || "";
|
state.workspaceKey = data.workspaceKey || "";
|
||||||
state.targets = Array.isArray(data.targets) ? data.targets : [];
|
state.targets = Array.isArray(data.targets) ? data.targets : [];
|
||||||
state.scopePages = Array.isArray(data.scopePages) ? data.scopePages : [];
|
state.scopePages = Array.isArray(data.scopePages) ? data.scopePages : [];
|
||||||
|
state.activeScopeId = data.activeScopeId || state.activeScopeId || "";
|
||||||
state.activeScopePageId = data.activePageId || state.activeScopePageId || "";
|
state.activeScopePageId = data.activePageId || state.activeScopePageId || "";
|
||||||
scheduleMeasure();
|
scheduleMeasure();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue