feat: allow selecting audience export fields

This commit is contained in:
2026-05-19 16:26:04 +08:00
parent db95a1f565
commit 50933af0a6
11 changed files with 1273 additions and 110 deletions
+90 -5
View File
@@ -1,5 +1,10 @@
import { escapeCsvCell } from "../../shared/csv";
import { buildMarketCsvColumns, type CsvColumn } from "./csv-exporter";
import {
buildMarketCsvColumns,
listBackendMetricCsvHeaders,
listRateCsvHeaders,
type CsvColumn
} from "./csv-exporter";
import type {
AudienceProfileDistributionItem,
AudienceProfileExportRow,
@@ -16,6 +21,15 @@ type AudienceProfileCsvColumn = {
readValue: (row: AudienceProfileExportRow) => string;
};
export interface AudienceProfileCsvOptions {
selectedHeaders?: string[];
}
export type AudienceProfileCsvFieldGroup = {
headers: string[];
label: string;
};
const PROFILE_LAYOUTS: Array<{
includeGender: boolean;
kind: AudienceProfileKind;
@@ -91,14 +105,15 @@ const BUSINESS_ESTIMATE_METRIC_LAYOUTS: Array<{
];
export function buildAudienceProfileCsv(
rows: AudienceProfileExportRow[]
rows: AudienceProfileExportRow[],
options: AudienceProfileCsvOptions = {}
): string {
const marketColumns = buildMarketCsvColumns(rows.map((row) => row.record));
const csvColumns = [
const csvColumns = filterAudienceProfileCsvColumns([
...marketColumns.map(toMarketColumn),
...buildBusinessAbilityColumns(),
...PROFILE_LAYOUTS.flatMap((layout) => buildProfileColumns(layout))
];
], options.selectedHeaders);
const headerLine = csvColumns.map((column) => column.header).join(",");
const rowLines = rows.map((row) =>
csvColumns.map((column) => escapeCsvCell(column.readValue(row))).join(",")
@@ -107,7 +122,72 @@ export function buildAudienceProfileCsv(
return [headerLine, ...rowLines].join("\n");
}
export function listAudienceProfileCsvHeaders(
rows: AudienceProfileExportRow[] = []
): string[] {
const marketColumns = buildMarketCsvColumns(rows.map((row) => row.record));
return [
...marketColumns.map((column) => column.header),
...buildBusinessAbilityColumns().map((column) => column.header),
...PROFILE_LAYOUTS.flatMap((layout) => buildProfileColumns(layout)).map(
(column) => column.header
)
];
}
export function listAudienceProfileSelectableFieldGroups(): AudienceProfileCsvFieldGroup[] {
return [
{
headers: listRateCsvHeaders(),
label: "看后搜率"
},
{
headers: listBackendMetricCsvHeaders(),
label: "秒思api数据"
},
{
headers: buildBusinessVideoColumns().map((column) => column.header),
label: "内容数据"
},
{
headers: buildBusinessEstimateColumns().map((column) => column.header),
label: "效果预估"
},
...PROFILE_LAYOUTS.map((layout) => ({
headers: buildProfileColumns(layout).map((column) => column.header),
label: layout.label
}))
];
}
function filterAudienceProfileCsvColumns(
columns: AudienceProfileCsvColumn[],
selectedHeaders: string[] | undefined
): AudienceProfileCsvColumn[] {
if (!selectedHeaders) {
return columns;
}
const selectableHeaderSet = new Set(listAudienceProfileSelectableHeaders());
const selectedHeaderSet = new Set(selectedHeaders);
return columns.filter(
(column) =>
!selectableHeaderSet.has(column.header) ||
selectedHeaderSet.has(column.header)
);
}
function listAudienceProfileSelectableHeaders(): string[] {
return listAudienceProfileSelectableFieldGroups().flatMap(
(group) => group.headers
);
}
function buildBusinessAbilityColumns(): AudienceProfileCsvColumn[] {
return [...buildBusinessVideoColumns(), ...buildBusinessEstimateColumns()];
}
function buildBusinessVideoColumns(): AudienceProfileCsvColumn[] {
return [
...BUSINESS_VIDEO_LAYOUTS.flatMap((videoLayout) =>
BUSINESS_VIDEO_METRIC_LAYOUTS.map((metricLayout) => ({
@@ -115,7 +195,12 @@ function buildBusinessAbilityColumns(): AudienceProfileCsvColumn[] {
readValue: (row: AudienceProfileExportRow) =>
readBusinessVideoValue(row, videoLayout.key, metricLayout.key)
}))
),
)
];
}
function buildBusinessEstimateColumns(): AudienceProfileCsvColumn[] {
return [
...BUSINESS_ESTIMATE_LAYOUTS.flatMap((durationLayout) =>
BUSINESS_ESTIMATE_METRIC_LAYOUTS.map((metricLayout) => ({
header: `${BUSINESS_ESTIMATE_SECTION_LABEL}-${durationLayout.label}-${metricLayout.label}`,
@@ -0,0 +1,295 @@
import type { AudienceProfileCsvFieldGroup } from "./audience-profile-csv";
export function promptForAudienceProfileFields(
document: Document,
groups: AudienceProfileCsvFieldGroup[],
selectedHeaders: string[]
): Promise<string[] | null> {
return new Promise((resolve) => {
const selectableHeaders = groups.flatMap((group) => group.headers);
const selectedHeaderSet = new Set(
selectedHeaders.filter((header) => selectableHeaders.includes(header))
);
if (selectedHeaderSet.size === 0) {
selectableHeaders.forEach((header) => selectedHeaderSet.add(header));
}
const overlay = document.createElement("div");
overlay.dataset.audienceProfileFieldDialog = "overlay";
applyOverlayStyles(overlay);
const dialog = document.createElement("section");
applyDialogStyles(dialog);
const title = document.createElement("h2");
applyTitleStyles(title);
const hint = document.createElement("p");
hint.textContent = "基础字段会固定保留。取消勾选后,本次及后续画像CSV将不包含对应列。";
applyHintStyles(hint);
const toolbar = document.createElement("div");
applyToolbarStyles(toolbar);
const selectAllButton = document.createElement("button");
selectAllButton.type = "button";
selectAllButton.textContent = "全选";
applySecondaryButtonStyles(selectAllButton);
const resetButton = document.createElement("button");
resetButton.type = "button";
resetButton.textContent = "恢复默认";
applySecondaryButtonStyles(resetButton);
toolbar.append(selectAllButton, resetButton);
const groupContainer = document.createElement("div");
applyGroupContainerStyles(groupContainer);
const fieldInputs: HTMLInputElement[] = [];
groups.forEach((group) => {
const groupSection = document.createElement("section");
groupSection.dataset.audienceProfileFieldDialogGroup = "section";
applyGroupSectionStyles(groupSection);
const groupHeader = document.createElement("label");
applyGroupHeaderStyles(groupHeader);
const groupInput = document.createElement("input");
groupInput.type = "checkbox";
const groupTitle = document.createElement("span");
groupTitle.textContent = group.label;
groupHeader.append(groupInput, groupTitle);
const fieldList = document.createElement("div");
applyFieldListStyles(fieldList);
const groupFieldInputs = group.headers.map((header) => {
const fieldLabel = document.createElement("label");
applyFieldLabelStyles(fieldLabel);
const input = document.createElement("input");
input.type = "checkbox";
input.value = header;
input.dataset.audienceProfileFieldDialogField = "checkbox";
input.checked = selectedHeaderSet.has(header);
const text = document.createElement("span");
text.textContent = header;
fieldLabel.append(input, text);
fieldList.append(fieldLabel);
fieldInputs.push(input);
return input;
});
const syncGroupInput = () => {
const checkedCount = groupFieldInputs.filter((input) => input.checked).length;
groupInput.checked = checkedCount === groupFieldInputs.length;
groupInput.indeterminate = checkedCount > 0 && checkedCount < groupFieldInputs.length;
};
groupInput.addEventListener("change", () => {
groupFieldInputs.forEach((input) => {
input.checked = groupInput.checked;
});
syncTitle();
});
groupFieldInputs.forEach((input) => {
input.addEventListener("change", () => {
syncGroupInput();
syncTitle();
});
});
syncGroupInput();
groupSection.append(groupHeader, fieldList);
groupContainer.append(groupSection);
});
const actions = document.createElement("div");
applyActionsStyles(actions);
const cancelButton = document.createElement("button");
cancelButton.type = "button";
cancelButton.textContent = "取消";
applySecondaryButtonStyles(cancelButton);
const confirmButton = document.createElement("button");
confirmButton.type = "button";
confirmButton.dataset.audienceProfileFieldDialogSave = "button";
confirmButton.textContent = "保存";
applyPrimaryButtonStyles(confirmButton);
actions.append(cancelButton, confirmButton);
dialog.append(title, hint, toolbar, groupContainer, actions);
overlay.append(dialog);
document.body.appendChild(overlay);
function syncTitle() {
const checkedCount = fieldInputs.filter((input) => input.checked).length;
title.textContent = `可选字段(已选 ${checkedCount}/${fieldInputs.length} 个字段)`;
}
function close(value: string[] | null) {
overlay.remove();
resolve(value);
}
selectAllButton.addEventListener("click", () => {
fieldInputs.forEach((input) => {
input.checked = true;
});
syncTitle();
syncAllGroupInputs(dialog);
});
resetButton.addEventListener("click", () => {
fieldInputs.forEach((input) => {
input.checked = true;
});
syncTitle();
syncAllGroupInputs(dialog);
});
cancelButton.addEventListener("click", () => close(null));
confirmButton.addEventListener("click", () => {
const nextHeaders = fieldInputs
.filter((input) => input.checked)
.map((input) => input.value);
close(nextHeaders);
});
overlay.addEventListener("click", (event) => {
if (event.target === overlay) {
close(null);
}
});
syncTitle();
});
}
function syncAllGroupInputs(dialog: HTMLElement): void {
dialog
.querySelectorAll('[data-audience-profile-field-dialog-group="section"]')
.forEach((section) => {
const groupInput = section.querySelector(":scope > label > input");
const fieldInputs = Array.from(
section.querySelectorAll(":scope > div input")
) as HTMLInputElement[];
if (!(groupInput instanceof HTMLInputElement) || fieldInputs.length === 0) {
return;
}
const checkedCount = fieldInputs.filter((input) => input.checked).length;
groupInput.checked = checkedCount === fieldInputs.length;
groupInput.indeterminate = checkedCount > 0 && checkedCount < fieldInputs.length;
});
}
function applyOverlayStyles(overlay: HTMLElement): void {
overlay.style.position = "fixed";
overlay.style.inset = "0";
overlay.style.zIndex = "2147483647";
overlay.style.display = "flex";
overlay.style.alignItems = "center";
overlay.style.justifyContent = "center";
overlay.style.background = "rgba(15, 23, 42, 0.38)";
}
function applyDialogStyles(dialog: HTMLElement): void {
dialog.style.width = "680px";
dialog.style.maxWidth = "calc(100vw - 32px)";
dialog.style.maxHeight = "calc(100vh - 48px)";
dialog.style.display = "flex";
dialog.style.flexDirection = "column";
dialog.style.background = "#ffffff";
dialog.style.borderRadius = "8px";
dialog.style.boxShadow = "0 18px 45px rgba(15, 23, 42, 0.22)";
dialog.style.padding = "20px";
dialog.style.boxSizing = "border-box";
}
function applyTitleStyles(title: HTMLElement): void {
title.style.margin = "0 0 8px";
title.style.fontSize = "18px";
title.style.fontWeight = "700";
title.style.color = "#1f2329";
}
function applyHintStyles(hint: HTMLElement): void {
hint.style.margin = "0 0 12px";
hint.style.fontSize = "13px";
hint.style.lineHeight = "20px";
hint.style.color = "#64748b";
}
function applyToolbarStyles(toolbar: HTMLElement): void {
toolbar.style.display = "flex";
toolbar.style.gap = "8px";
toolbar.style.marginBottom = "12px";
}
function applyGroupContainerStyles(container: HTMLElement): void {
container.style.display = "flex";
container.style.flexDirection = "column";
container.style.gap = "10px";
container.style.overflow = "auto";
container.style.paddingRight = "4px";
}
function applyGroupSectionStyles(section: HTMLElement): void {
section.style.border = "1px solid #e5e7eb";
section.style.borderRadius = "8px";
section.style.padding = "10px";
}
function applyGroupHeaderStyles(label: HTMLElement): void {
label.style.display = "flex";
label.style.alignItems = "center";
label.style.gap = "8px";
label.style.fontWeight = "700";
label.style.color = "#1f2329";
label.style.marginBottom = "8px";
}
function applyFieldListStyles(list: HTMLElement): void {
list.style.display = "grid";
list.style.gridTemplateColumns = "repeat(auto-fit, minmax(220px, 1fr))";
list.style.gap = "8px";
}
function applyFieldLabelStyles(label: HTMLElement): void {
label.style.display = "flex";
label.style.alignItems = "center";
label.style.gap = "6px";
label.style.fontSize = "13px";
label.style.lineHeight = "18px";
label.style.color = "#374151";
}
function applyActionsStyles(actions: HTMLElement): void {
actions.style.display = "flex";
actions.style.justifyContent = "flex-end";
actions.style.columnGap = "8px";
actions.style.marginTop = "14px";
}
function applyPrimaryButtonStyles(button: HTMLButtonElement): void {
button.style.height = "32px";
button.style.padding = "0 15px";
button.style.border = "1px solid #7f1d2d";
button.style.borderRadius = "8px";
button.style.background = "#7f1d2d";
button.style.color = "#ffffff";
button.style.fontWeight = "600";
}
function applySecondaryButtonStyles(button: HTMLButtonElement): void {
button.style.height = "32px";
button.style.padding = "0 15px";
button.style.border = "1px solid #d0d7de";
button.style.borderRadius = "8px";
button.style.background = "#ffffff";
button.style.color = "#1f2329";
button.style.fontWeight = "600";
}
+8
View File
@@ -74,6 +74,14 @@ const BACKEND_METRIC_COLUMNS: CsvColumn[] = [
}
];
export function listRateCsvHeaders(): string[] {
return RATE_COLUMNS.map((column) => column.header);
}
export function listBackendMetricCsvHeaders(): string[] {
return BACKEND_METRIC_COLUMNS.map((column) => column.header);
}
export function buildMarketCsv(records: MarketRecord[]): string {
const csvColumns = buildMarketCsvColumns(records);
const headerLine = csvColumns.map((column) => column.header).join(",");
+89 -4
View File
@@ -1,5 +1,9 @@
import { buildMarketCsv } from "./csv-exporter";
import { buildAudienceProfileCsv } from "./audience-profile-csv";
import {
buildAudienceProfileCsv,
listAudienceProfileSelectableFieldGroups,
type AudienceProfileCsvOptions
} from "./audience-profile-csv";
import {
AUDIENCE_PROFILE_TARGETS,
createAudienceProfileClient,
@@ -8,6 +12,7 @@ import {
import { createAuthorBaseClient } from "./author-base-client";
import { parseAuthorIds } from "./author-id-input";
import { createBusinessAbilityClient } from "./business-ability-client";
import { promptForAudienceProfileFields } from "./audience-profile-field-dialog";
import { promptForAuthorIds } from "./author-id-dialog";
import { promptForBatchName } from "./batch-name-dialog";
import { createBatchPayload, type BatchPayload } from "./batch-payload";
@@ -58,7 +63,10 @@ interface MutationObserverLike {
}
export interface CreateMarketControllerOptions {
buildAudienceProfileCsv?: (rows: AudienceProfileExportRow[]) => string;
buildAudienceProfileCsv?: (
rows: AudienceProfileExportRow[],
options?: AudienceProfileCsvOptions
) => string;
buildCsv?: (records: MarketRecord[]) => string;
document: Document;
getAuthState?: () => Promise<AuthStateValue>;
@@ -85,6 +93,9 @@ export interface CreateMarketControllerOptions {
window: Window;
}
const AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY =
"sces:audience-profile:selectedHeaders";
export function createMarketController(options: CreateMarketControllerOptions) {
const marketApiClient = createMarketApiClient();
const audienceProfileClient = createAudienceProfileClient();
@@ -266,7 +277,12 @@ export function createMarketController(options: CreateMarketControllerOptions) {
return;
}
options.onCsvReady?.(buildAudienceCsv(rows), buildAudienceProfileFilename());
options.onCsvReady?.(
buildAudienceCsv(rows, {
selectedHeaders: readAudienceProfileSelectedHeaders()
}),
buildAudienceProfileFilename()
);
setToolbarExportStatus(toolbar, "");
} catch (error) {
setToolbarExportStatus(
@@ -313,7 +329,9 @@ export function createMarketController(options: CreateMarketControllerOptions) {
}
options.onCsvReady?.(
buildAudienceCsv(rows),
buildAudienceCsv(rows, {
selectedHeaders: readAudienceProfileSelectedHeaders()
}),
buildAudienceProfileFilename(new Date(), "按ID导出")
);
setToolbarExportStatus(toolbar, "");
@@ -326,6 +344,24 @@ export function createMarketController(options: CreateMarketControllerOptions) {
setToolbarBusyState(toolbar, false);
}
},
onConfigureAudienceProfileFields: async () => {
const groups = listAudienceProfileSelectableFieldGroups();
const selectedHeaders = readAudienceProfileSelectedHeaders();
const nextHeaders = await promptForAudienceProfileFields(
options.document,
groups,
selectedHeaders
);
if (nextHeaders === null) {
return;
}
saveAudienceProfileSelectedHeaders(nextHeaders);
setToolbarExportStatus(
toolbar,
`画像字段已保存(已选 ${nextHeaders.length}/${readAudienceProfileSelectableHeaders().length} 个字段)`
);
},
onSubmitBatch: async () => {
syncSelectionStateFromDom();
const exportTarget = readToolbarExportTarget(toolbar);
@@ -909,6 +945,55 @@ export function createMarketController(options: CreateMarketControllerOptions) {
return "铁粉画像";
}
function readAudienceProfileSelectableHeaders(): string[] {
return listAudienceProfileSelectableFieldGroups().flatMap(
(group) => group.headers
);
}
function readAudienceProfileSelectedHeaders(): string[] {
const selectableHeaders = readAudienceProfileSelectableHeaders();
const selectableHeaderSet = new Set(selectableHeaders);
try {
const rawValue = options.window.localStorage?.getItem(
AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY
);
if (!rawValue) {
return selectableHeaders;
}
const parsedValue = JSON.parse(rawValue) as unknown;
if (!Array.isArray(parsedValue)) {
return selectableHeaders;
}
const selectedHeaders = parsedValue.filter(
(header): header is string =>
typeof header === "string" && selectableHeaderSet.has(header)
);
return selectedHeaders.length > 0 ? selectedHeaders : selectableHeaders;
} catch {
return selectableHeaders;
}
}
function saveAudienceProfileSelectedHeaders(headers: string[]): void {
const selectableHeaderSet = new Set(readAudienceProfileSelectableHeaders());
const selectedHeaders = headers.filter((header) =>
selectableHeaderSet.has(header)
);
try {
options.window.localStorage?.setItem(
AUDIENCE_PROFILE_FIELD_SELECTION_STORAGE_KEY,
JSON.stringify(selectedHeaders)
);
} catch {
// localStorage may be unavailable in hardened browser contexts.
}
}
async function prepareCurrentPageForExport(): Promise<void> {
await runSyncCycle();
await harvestCurrentPageForExport();
+25
View File
@@ -7,12 +7,14 @@ export interface PluginToolbarHandlers {
onExport(): Promise<void> | void;
onExportAudienceProfile(): Promise<void> | void;
onExportAudienceProfileByIds(): Promise<void> | void;
onConfigureAudienceProfileFields(): Promise<void> | void;
onSubmitBatch(): Promise<void> | void;
}
export interface PluginToolbarDom {
audienceProfileByIdExportButton: HTMLButtonElement;
audienceProfileExportButton: HTMLButtonElement;
audienceProfileFieldButton: HTMLButtonElement;
batchSubmitButton: HTMLButtonElement;
exportButton: HTMLButtonElement;
exportCustomPagesInput: HTMLInputElement;
@@ -89,6 +91,11 @@ export function ensurePluginToolbar(
audienceProfileByIdExportButton.dataset.pluginExportAudienceProfileById = "button";
audienceProfileByIdExportButton.textContent = "按ID导出画像CSV";
const audienceProfileFieldButton = document.createElement("button");
audienceProfileFieldButton.type = "button";
audienceProfileFieldButton.dataset.pluginAudienceProfileFields = "button";
audienceProfileFieldButton.textContent = "画像字段";
const batchSubmitButton = document.createElement("button");
batchSubmitButton.type = "button";
batchSubmitButton.dataset.pluginBatchSubmit = "button";
@@ -104,6 +111,7 @@ export function ensurePluginToolbar(
exportButton,
audienceProfileExportButton,
audienceProfileByIdExportButton,
audienceProfileFieldButton,
batchSubmitButton,
exportStatusText
);
@@ -112,6 +120,7 @@ export function ensurePluginToolbar(
applyNativeControlStyles(document, {
audienceProfileExportButton,
audienceProfileByIdExportButton,
audienceProfileFieldButton,
batchSubmitButton,
exportButton,
exportCustomPagesInput,
@@ -128,12 +137,16 @@ export function ensurePluginToolbar(
audienceProfileByIdExportButton.addEventListener("click", () => {
void handlers.onExportAudienceProfileByIds();
});
audienceProfileFieldButton.addEventListener("click", () => {
void handlers.onConfigureAudienceProfileFields();
});
batchSubmitButton.addEventListener("click", () => {
void handlers.onSubmitBatch();
});
exportRangeSelect.addEventListener("change", () => {
syncCustomPagesInputVisibility({
batchSubmitButton,
audienceProfileFieldButton,
audienceProfileByIdExportButton,
audienceProfileExportButton,
exportButton,
@@ -147,6 +160,7 @@ export function ensurePluginToolbar(
const toolbarDom = {
audienceProfileExportButton,
audienceProfileByIdExportButton,
audienceProfileFieldButton,
batchSubmitButton,
exportButton,
exportCustomPagesInput,
@@ -178,6 +192,9 @@ function readToolbarDom(root: HTMLElement): PluginToolbarDom {
audienceProfileExportButton: root.querySelector(
'[data-plugin-export-audience-profile="button"]'
) as HTMLButtonElement,
audienceProfileFieldButton: root.querySelector(
'[data-plugin-audience-profile-fields="button"]'
) as HTMLButtonElement,
batchSubmitButton: root.querySelector(
'[data-plugin-batch-submit="button"]'
) as HTMLButtonElement,
@@ -260,6 +277,7 @@ export function setToolbarBusyState(
): void {
[
toolbar.batchSubmitButton,
toolbar.audienceProfileFieldButton,
toolbar.audienceProfileByIdExportButton,
toolbar.audienceProfileExportButton,
toolbar.exportButton,
@@ -460,6 +478,7 @@ function applyNativeControlStyles(
controls: {
audienceProfileExportButton: HTMLButtonElement;
audienceProfileByIdExportButton: HTMLButtonElement;
audienceProfileFieldButton: HTMLButtonElement;
batchSubmitButton: HTMLButtonElement;
exportButton: HTMLButtonElement;
exportCustomPagesInput: HTMLInputElement;
@@ -478,6 +497,7 @@ function applyNativeControlStyles(
controls.exportButton.className = nativeButton.className;
controls.audienceProfileExportButton.className = nativeButton.className;
controls.audienceProfileByIdExportButton.className = nativeButton.className;
controls.audienceProfileFieldButton.className = nativeButton.className;
controls.batchSubmitButton.className = nativeButton.className;
}
@@ -485,6 +505,7 @@ function applyNativeControlStyles(
controls.exportButton,
controls.audienceProfileExportButton,
controls.audienceProfileByIdExportButton,
controls.audienceProfileFieldButton,
controls.batchSubmitButton
].forEach((button) => {
applyPrimaryButtonStyles(button);
@@ -539,6 +560,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
[data-plugin-export="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:hover:not(:disabled),
[data-plugin-audience-profile-fields="button"]:hover:not(:disabled),
[data-plugin-batch-submit="button"]:hover:not(:disabled) {
background-color: #6d1627 !important;
border-color: #6d1627 !important;
@@ -547,6 +569,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
[data-plugin-export="button"]:active:not(:disabled),
[data-plugin-export-audience-profile="button"]:active:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:active:not(:disabled),
[data-plugin-audience-profile-fields="button"]:active:not(:disabled),
[data-plugin-batch-submit="button"]:active:not(:disabled) {
background-color: #58111f !important;
border-color: #58111f !important;
@@ -556,6 +579,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
[data-plugin-export="button"]:focus-visible,
[data-plugin-export-audience-profile="button"]:focus-visible,
[data-plugin-export-audience-profile-by-id="button"]:focus-visible,
[data-plugin-audience-profile-fields="button"]:focus-visible,
[data-plugin-batch-submit="button"]:focus-visible {
outline: none !important;
box-shadow: 0 0 0 3px rgba(127, 29, 45, 0.2) !important;
@@ -564,6 +588,7 @@ function ensurePluginActionButtonTheme(document: Document): void {
[data-plugin-export="button"]:disabled,
[data-plugin-export-audience-profile="button"]:disabled,
[data-plugin-export-audience-profile-by-id="button"]:disabled,
[data-plugin-audience-profile-fields="button"]:disabled,
[data-plugin-batch-submit="button"]:disabled {
background-color: #c89ca4 !important;
border-color: #c89ca4 !important;