feat: govern task-local quick actions
This commit is contained in:
+14
-7
@@ -37,10 +37,17 @@ owner module.
|
|||||||
|
|
||||||
Quick Access is not an authorization boundary. Every contribution keeps its
|
Quick Access is not an authorization boundary. Every contribution keeps its
|
||||||
own permission requirements and View surface. Full-page routes remain the
|
own permission requirements and View surface. Full-page routes remain the
|
||||||
canonical fallback. Launch context version 1 contains only the tenant/account
|
canonical fallback. Launch-context version 2 contains only the tenant/account
|
||||||
identity, safe active-object reference, acting-assignment identifiers,
|
identity, a reference-contract-version-1 active object, acting-assignment
|
||||||
temporal selection, exact View identity, and return route. Cross-tenant object
|
identifiers, temporal selection, exact View identity, View recommendations or
|
||||||
references are discarded and unknown context versions are ignored. The
|
focus, and a safe return route. Cross-tenant object references are discarded
|
||||||
destination reauthorizes every read and effect. Disabling this module removes
|
and unknown versions fail closed. View focus applies only when at least one
|
||||||
the rail without making any domain state unavailable through its owning
|
focused tool is currently enabled, context-compatible, and authorized; a
|
||||||
module.
|
recommendation only changes ordering and emphasis.
|
||||||
|
|
||||||
|
Each renderer performs owner-side reads and effects and explicitly reports
|
||||||
|
either a result-contract-version-1 completion with a typed owner reference or
|
||||||
|
a cancellation reason. Closing the drawer is not completion. The host may
|
||||||
|
listen for that correlated result while its unsaved page state remains mounted.
|
||||||
|
Disabling this module removes the rail without making any domain state
|
||||||
|
unavailable through its owning module.
|
||||||
|
|||||||
@@ -121,9 +121,11 @@ DOCUMENTATION = (
|
|||||||
"Messages combines enabled Mail, Postbox, and future chat contributions in one overlay. "
|
"Messages combines enabled Mail, Postbox, and future chat contributions in one overlay. "
|
||||||
"Personal settings can reorder or hide items that remain available under system, tenant, "
|
"Personal settings can reorder or hide items that remain available under system, tenant, "
|
||||||
"permission, and View policy. Every item retains a link to its complete owning page. "
|
"permission, and View policy. Every item retains a link to its complete owning page. "
|
||||||
"That launch carries a versioned, bounded reference to the current object, acting assignment, "
|
"Launch-context version 2 carries only versioned, bounded references to the current object, acting assignment, "
|
||||||
"temporal selection, View revision, and return location. The destination rechecks access, and "
|
"temporal selection, exact View revision, and return location. Owner modules recheck access when a tool opens and "
|
||||||
"the breadcrumb return action restores the originating route without copying protected content."
|
"before each effect. A tool returns either an explicit version-1 completion with a typed owner reference or an "
|
||||||
|
"explicit cancellation; closing the drawer does not imply success. The overlay preserves unsaved host-page work, "
|
||||||
|
"and the complete owning page remains the fallback for work that exceeds the compact surface."
|
||||||
),
|
),
|
||||||
layer="configured",
|
layer="configured",
|
||||||
documentation_types=("user",),
|
documentation_types=("user",),
|
||||||
@@ -143,8 +145,11 @@ DOCUMENTATION = (
|
|||||||
"Eine Kategorie in der rechten Leiste oeffnet kompakte Werkzeuge, ohne die aktuelle Aufgabe zu verlassen. "
|
"Eine Kategorie in der rechten Leiste oeffnet kompakte Werkzeuge, ohne die aktuelle Aufgabe zu verlassen. "
|
||||||
"Nachrichten fuehrt Beitraege aus Mail, Postfach und kuenftigen Chat-Modulen in einer Einblendung zusammen. "
|
"Nachrichten fuehrt Beitraege aus Mail, Postfach und kuenftigen Chat-Modulen in einer Einblendung zusammen. "
|
||||||
"Persoenliche Einstellungen koennen alle durch System, Mandant, Berechtigungen und Ansicht zugelassenen Eintraege ordnen oder ausblenden. "
|
"Persoenliche Einstellungen koennen alle durch System, Mandant, Berechtigungen und Ansicht zugelassenen Eintraege ordnen oder ausblenden. "
|
||||||
"Beim Oeffnen der vollstaendigen Seite werden nur versionierte, begrenzte Verweise auf Objekt, handelnde Zuordnung, "
|
"Startkontext Version 2 uebergibt nur versionierte, begrenzte Verweise auf Objekt, handelnde Zuordnung, "
|
||||||
"Zeitbezug, Ansichtsversion und Ruecksprungort uebergeben. Das Ziel prueft den Zugriff erneut."
|
"Zeitbezug, genaue Ansichtsversion und Ruecksprungort. Das besitzende Modul prueft den Zugriff beim Oeffnen "
|
||||||
|
"und vor jeder Wirkung erneut. Ein Werkzeug meldet entweder einen ausdruecklichen Abschluss mit typisiertem "
|
||||||
|
"Besitzerverweis oder einen ausdruecklichen Abbruch; das Schliessen gilt nicht als Erfolg. Die Einblendung "
|
||||||
|
"erhaelt ungespeicherte Arbeit auf der Ausgangsseite, die vollstaendige Besitzerseite bleibt das Ausweichziel."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -164,7 +169,10 @@ DOCUMENTATION = (
|
|||||||
"The catalogue follows installed module registrations. System settings constrain tenants; "
|
"The catalogue follows installed module registrations. System settings constrain tenants; "
|
||||||
"tenant settings constrain users. An item may remain available, be blocked, or be forced. "
|
"tenant settings constrain users. An item may remain available, be blocked, or be forced. "
|
||||||
"Views and permissions form additional ceilings and Quick Access never grants access to domain data. "
|
"Views and permissions form additional ceilings and Quick Access never grants access to domain data. "
|
||||||
"Launch context version 1 rejects cross-tenant active-object references and unknown context versions fail closed."
|
"A View may recommend tools or focus the rail to a subset, but only currently enabled, context-compatible, authorized "
|
||||||
|
"tools participate. Workflow receives that same presentation from the exact resolved View revision. Launch-context "
|
||||||
|
"version 2, reference contract version 1, and result contract version 1 fail closed on unknown versions; cross-tenant "
|
||||||
|
"active-object and result references are rejected."
|
||||||
),
|
),
|
||||||
layer="configured",
|
layer="configured",
|
||||||
documentation_types=("admin",),
|
documentation_types=("admin",),
|
||||||
@@ -177,7 +185,10 @@ DOCUMENTATION = (
|
|||||||
"Der Katalog folgt den Registrierungen installierter Module. Systemeinstellungen begrenzen Mandanten, "
|
"Der Katalog folgt den Registrierungen installierter Module. Systemeinstellungen begrenzen Mandanten, "
|
||||||
"Mandanteneinstellungen begrenzen Benutzer. Ein Eintrag kann verfuegbar, gesperrt oder erzwungen sein. "
|
"Mandanteneinstellungen begrenzen Benutzer. Ein Eintrag kann verfuegbar, gesperrt oder erzwungen sein. "
|
||||||
"Ansichten und Berechtigungen bilden weitere Grenzen; Schnellzugriff erteilt selbst keinen Datenzugriff. "
|
"Ansichten und Berechtigungen bilden weitere Grenzen; Schnellzugriff erteilt selbst keinen Datenzugriff. "
|
||||||
"Startkontext Version 1 verwirft mandantenfremde Objektverweise; unbekannte Versionen werden abgelehnt."
|
"Eine Ansicht darf Werkzeuge empfehlen oder die Leiste auf eine Teilmenge fokussieren, jedoch nur innerhalb "
|
||||||
|
"der aktivierten, kontextgeeigneten und berechtigten Werkzeuge. Workflow verwendet dieselbe Darstellung aus "
|
||||||
|
"der genau aufgeloesten Ansichtsversion. Startkontext Version 2 sowie Verweis- und Ergebniskontrakt Version 1 "
|
||||||
|
"lehnen unbekannte Versionen ab; mandantenfremde Objekt- und Ergebnisverweise werden verworfen."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class CatalogueCategoryResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class CatalogueToolResponse(BaseModel):
|
class CatalogueToolResponse(BaseModel):
|
||||||
|
contract_version: str
|
||||||
id: str
|
id: str
|
||||||
module_id: str
|
module_id: str
|
||||||
category_id: str
|
category_id: str
|
||||||
@@ -55,6 +56,10 @@ class CatalogueToolResponse(BaseModel):
|
|||||||
order: int
|
order: int
|
||||||
default_enabled: bool
|
default_enabled: bool
|
||||||
modes: list[str]
|
modes: list[str]
|
||||||
|
availability: str
|
||||||
|
accepted_reference_kinds: list[str]
|
||||||
|
returned_reference_kinds: list[str]
|
||||||
|
help_context_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class CatalogueResponse(BaseModel):
|
class CatalogueResponse(BaseModel):
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ def build_catalogue(
|
|||||||
continue
|
continue
|
||||||
tools.append(
|
tools.append(
|
||||||
CatalogueToolResponse(
|
CatalogueToolResponse(
|
||||||
|
contract_version=tool.contract_version,
|
||||||
id=tool.id,
|
id=tool.id,
|
||||||
module_id=tool.module_id,
|
module_id=tool.module_id,
|
||||||
category_id=tool.category_id,
|
category_id=tool.category_id,
|
||||||
@@ -104,6 +105,10 @@ def build_catalogue(
|
|||||||
order=tool.order,
|
order=tool.order,
|
||||||
default_enabled=tool.default_enabled,
|
default_enabled=tool.default_enabled,
|
||||||
modes=list(tool.modes),
|
modes=list(tool.modes),
|
||||||
|
availability=tool.availability,
|
||||||
|
accepted_reference_kinds=list(tool.accepted_reference_kinds),
|
||||||
|
returned_reference_kinds=list(tool.returned_reference_kinds),
|
||||||
|
help_context_id=tool.help_context_id,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
tools.sort(key=lambda item: (item.category_id, item.order, item.id))
|
tools.sort(key=lambda item: (item.category_id, item.order, item.id))
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export type QuickAccessCategory = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type QuickAccessTool = {
|
export type QuickAccessTool = {
|
||||||
|
contract_version: "1";
|
||||||
id: string;
|
id: string;
|
||||||
module_id: string;
|
module_id: string;
|
||||||
category_id: string;
|
category_id: string;
|
||||||
@@ -40,6 +41,10 @@ export type QuickAccessTool = {
|
|||||||
order: number;
|
order: number;
|
||||||
default_enabled: boolean;
|
default_enabled: boolean;
|
||||||
modes: string[];
|
modes: string[];
|
||||||
|
availability: "global" | "active_object";
|
||||||
|
accepted_reference_kinds: string[];
|
||||||
|
returned_reference_kinds: string[];
|
||||||
|
help_context_id?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuickAccessCatalogue = {
|
export type QuickAccessCatalogue = {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useEffect, useMemo, useRef, useState, type MouseEvent } from "react";
|
|||||||
import { Link, useLocation } from "react-router";
|
import { Link, useLocation } from "react-router";
|
||||||
import {
|
import {
|
||||||
DismissibleAlert,
|
DismissibleAlert,
|
||||||
|
DocumentationHelpLink,
|
||||||
IconButton,
|
IconButton,
|
||||||
LoadingFrame,
|
LoadingFrame,
|
||||||
dispatchQuickAccessResult,
|
dispatchQuickAccessResult,
|
||||||
@@ -51,12 +52,46 @@ export default function QuickAccessRail({ settings, auth, tools, launchContext }
|
|||||||
[contributions]
|
[contributions]
|
||||||
);
|
);
|
||||||
const availableToolIds = useMemo(() => new Set(tools.map((tool) => tool.id)), [tools]);
|
const availableToolIds = useMemo(() => new Set(tools.map((tool) => tool.id)), [tools]);
|
||||||
const categories = useMemo(
|
const metadataById = useMemo(() => new Map(tools.map((tool) => [tool.id, tool])), [tools]);
|
||||||
() => (effective?.categories ?? []).map((category) => ({
|
const focusedToolIds = useMemo(
|
||||||
|
() => new Set(launchContext.viewContext?.focusedToolIds ?? []),
|
||||||
|
[launchContext.viewContext?.focusedToolIds]
|
||||||
|
);
|
||||||
|
const recommendedToolIds = useMemo(
|
||||||
|
() => new Set(launchContext.viewContext?.recommendedToolIds ?? []),
|
||||||
|
[launchContext.viewContext?.recommendedToolIds]
|
||||||
|
);
|
||||||
|
const categories = useMemo(() => {
|
||||||
|
const eligibleCategories = (effective?.categories ?? [])
|
||||||
|
.filter((category) => category.enabled)
|
||||||
|
.map((category) => ({
|
||||||
|
...category,
|
||||||
|
tools: category.tools.filter((tool) => {
|
||||||
|
if (!tool.enabled || !availableToolIds.has(tool.id)) return false;
|
||||||
|
const metadata = metadataById.get(tool.id);
|
||||||
|
if (metadata?.availability === "active_object" && !launchContext.activeObject) return false;
|
||||||
|
if (metadata?.acceptedReferenceKinds.length && launchContext.activeObject) {
|
||||||
|
const activeKind = `${launchContext.activeObject.ownerModule}.${launchContext.activeObject.kind}`;
|
||||||
|
if (!metadata.acceptedReferenceKinds.includes(activeKind)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
const hasEligibleFocus = eligibleCategories.some((category) =>
|
||||||
|
category.tools.some((tool) => focusedToolIds.has(tool.id))
|
||||||
|
);
|
||||||
|
return eligibleCategories.map((category) => ({
|
||||||
...category,
|
...category,
|
||||||
tools: category.tools.filter((tool) => tool.enabled && availableToolIds.has(tool.id))
|
tools: category.tools.filter((tool) =>
|
||||||
})).filter((category) => category.enabled && category.tools.length > 0),
|
!hasEligibleFocus || focusedToolIds.has(tool.id)
|
||||||
[availableToolIds, effective]
|
).sort((left, right) =>
|
||||||
|
Number(recommendedToolIds.has(right.id)) - Number(recommendedToolIds.has(left.id))
|
||||||
|
|| left.order - right.order
|
||||||
|
|| left.id.localeCompare(right.id)
|
||||||
|
)
|
||||||
|
})).filter((category) => category.enabled && category.tools.length > 0);
|
||||||
|
},
|
||||||
|
[availableToolIds, effective, focusedToolIds, launchContext.activeObject, metadataById, recommendedToolIds]
|
||||||
);
|
);
|
||||||
const activeCategory = categories.find((category) => category.id === activeCategoryId) ?? null;
|
const activeCategory = categories.find((category) => category.id === activeCategoryId) ?? null;
|
||||||
|
|
||||||
@@ -189,13 +224,27 @@ export default function QuickAccessRail({ settings, auth, tools, launchContext }
|
|||||||
<LoadingFrame loading={loading} label="i18n:govoplan-quick-access.loading">
|
<LoadingFrame loading={loading} label="i18n:govoplan-quick-access.loading">
|
||||||
{activeCategory.tools.map((tool) => {
|
{activeCategory.tools.map((tool) => {
|
||||||
const renderer = renderers.get(tool.id);
|
const renderer = renderers.get(tool.id);
|
||||||
|
const metadata = metadataById.get(tool.id);
|
||||||
return (
|
return (
|
||||||
<section className="quick-access-tool" key={tool.id} data-tool-id={tool.id}>
|
<section
|
||||||
|
className={`quick-access-tool${recommendedToolIds.has(tool.id) ? " is-recommended" : ""}`}
|
||||||
|
key={tool.id}
|
||||||
|
data-tool-id={tool.id}
|
||||||
|
data-tool-contract-version={metadata?.contractVersion}
|
||||||
|
>
|
||||||
<div className="quick-access-tool-heading">
|
<div className="quick-access-tool-heading">
|
||||||
<div>
|
<div>
|
||||||
<strong>{translateText(tool.label)}</strong>
|
<strong>{translateText(tool.label)}</strong>
|
||||||
|
{recommendedToolIds.has(tool.id) ? (
|
||||||
|
<span className="quick-access-recommended">
|
||||||
|
{translateText("i18n:govoplan-quick-access.recommended_for_view")}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
{tool.description ? <small>{translateText(tool.description)}</small> : null}
|
{tool.description ? <small>{translateText(tool.description)}</small> : null}
|
||||||
</div>
|
</div>
|
||||||
|
{metadata?.helpContextId ? (
|
||||||
|
<DocumentationHelpLink reference={{ contextId: metadata.helpContextId, moduleId: tool.module_id }} />
|
||||||
|
) : null}
|
||||||
{tool.full_page_path ? (
|
{tool.full_page_path ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -214,7 +263,21 @@ export default function QuickAccessRail({ settings, auth, tools, launchContext }
|
|||||||
active: true,
|
active: true,
|
||||||
launchContext,
|
launchContext,
|
||||||
complete: (result) => {
|
complete: (result) => {
|
||||||
dispatchQuickAccessResult(tool.id, launchContext, result);
|
if (!dispatchQuickAccessResult(tool.id, launchContext, result, metadata?.returnedReferenceKinds ?? [])) {
|
||||||
|
setError("i18n:govoplan-quick-access.invalid_result");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closeDrawer();
|
||||||
|
},
|
||||||
|
cancel: (reason = "user") => {
|
||||||
|
if (!dispatchQuickAccessResult(tool.id, launchContext, {
|
||||||
|
contractVersion: "1",
|
||||||
|
outcome: "cancelled",
|
||||||
|
reason
|
||||||
|
}, metadata?.returnedReferenceKinds ?? [])) {
|
||||||
|
setError("i18n:govoplan-quick-access.invalid_result");
|
||||||
|
return;
|
||||||
|
}
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -196,8 +196,8 @@ export default function QuickAccessSettingsPanel({
|
|||||||
<p>{isPersonal ? "i18n:govoplan-quick-access.personal_help" : "i18n:govoplan-quick-access.admin_help"}</p>
|
<p>{isPersonal ? "i18n:govoplan-quick-access.personal_help" : "i18n:govoplan-quick-access.admin_help"}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="quick-access-settings-actions">
|
<div className="quick-access-settings-actions">
|
||||||
<Button variant="secondary" icon={<RotateCcw size={16} />} disabled={!dirty || saving} onClick={reset}>i18n:govoplan-quick-access.discard</Button>
|
<Button variant="secondary" disabled={!dirty || saving} onClick={reset}><RotateCcw size={16} aria-hidden="true" />i18n:govoplan-quick-access.discard</Button>
|
||||||
<Button variant="primary" icon={<Save size={16} />} disabled={!dirty || saving || !canWrite} onClick={() => void save()}>i18n:govoplan-quick-access.save</Button>
|
<Button variant="primary" disabled={!dirty || saving || !canWrite} onClick={() => void save()}><Save size={16} aria-hidden="true" />i18n:govoplan-quick-access.save</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-quick-access.loading": "Schnellzugriff wird geladen",
|
"i18n:govoplan-quick-access.loading": "Schnellzugriff wird geladen",
|
||||||
"i18n:govoplan-quick-access.open_full_page": "Vollständige Seite öffnen",
|
"i18n:govoplan-quick-access.open_full_page": "Vollständige Seite öffnen",
|
||||||
"i18n:govoplan-quick-access.compact_view_unavailable": "Die kompakte Ansicht ist nicht verfügbar. Verwenden Sie die vollständige Seite.",
|
"i18n:govoplan-quick-access.compact_view_unavailable": "Die kompakte Ansicht ist nicht verfügbar. Verwenden Sie die vollständige Seite.",
|
||||||
|
"i18n:govoplan-quick-access.recommended_for_view": "Für diese Ansicht empfohlen",
|
||||||
|
"i18n:govoplan-quick-access.invalid_result": "Das Werkzeug hat ein ungültiges oder nicht zugelassenes Ergebnis zurückgegeben.",
|
||||||
"i18n:govoplan-quick-access.settings_title": "Schnellzugriff",
|
"i18n:govoplan-quick-access.settings_title": "Schnellzugriff",
|
||||||
"i18n:govoplan-quick-access.personal_help": "Werkzeuge auswählen und ordnen, die neben der aktuellen Arbeit verfügbar bleiben.",
|
"i18n:govoplan-quick-access.personal_help": "Werkzeuge auswählen und ordnen, die neben der aktuellen Arbeit verfügbar bleiben.",
|
||||||
"i18n:govoplan-quick-access.admin_help": "Verfügbarkeit, erzwungene Einträge und Standardreihenfolge für nachgeordnete Ebenen festlegen.",
|
"i18n:govoplan-quick-access.admin_help": "Verfügbarkeit, erzwungene Einträge und Standardreihenfolge für nachgeordnete Ebenen festlegen.",
|
||||||
@@ -45,6 +47,8 @@ export const generatedTranslations: PlatformTranslations = {
|
|||||||
"i18n:govoplan-quick-access.loading": "Loading Quick Access",
|
"i18n:govoplan-quick-access.loading": "Loading Quick Access",
|
||||||
"i18n:govoplan-quick-access.open_full_page": "Open full page",
|
"i18n:govoplan-quick-access.open_full_page": "Open full page",
|
||||||
"i18n:govoplan-quick-access.compact_view_unavailable": "The compact view is unavailable. Use the full page.",
|
"i18n:govoplan-quick-access.compact_view_unavailable": "The compact view is unavailable. Use the full page.",
|
||||||
|
"i18n:govoplan-quick-access.recommended_for_view": "Recommended for this View",
|
||||||
|
"i18n:govoplan-quick-access.invalid_result": "The tool returned an invalid or undeclared result.",
|
||||||
"i18n:govoplan-quick-access.settings_title": "Quick Access",
|
"i18n:govoplan-quick-access.settings_title": "Quick Access",
|
||||||
"i18n:govoplan-quick-access.personal_help": "Choose and order the tools kept beside your current work.",
|
"i18n:govoplan-quick-access.personal_help": "Choose and order the tools kept beside your current work.",
|
||||||
"i18n:govoplan-quick-access.admin_help": "Set availability, forced items, and default ordering for lower scopes.",
|
"i18n:govoplan-quick-access.admin_help": "Set availability, forced items, and default ordering for lower scopes.",
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default, quickAccessModule } from "./module";
|
export { default, quickAccessModule } from "./module";
|
||||||
|
export { default as QuickAccessRail } from "./components/QuickAccessRail";
|
||||||
export * from "./api/quickAccess";
|
export * from "./api/quickAccess";
|
||||||
|
|||||||
@@ -124,6 +124,21 @@
|
|||||||
padding: 14px 16px 16px;
|
padding: 14px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-access-tool.is-recommended {
|
||||||
|
border-inline-start: 3px solid var(--accent);
|
||||||
|
padding-inline-start: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-access-recommended {
|
||||||
|
width: fit-content;
|
||||||
|
border-radius: var(--radius-round);
|
||||||
|
padding: 1px 7px;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
.quick-access-tool-heading,
|
.quick-access-tool-heading,
|
||||||
.quick-access-settings-heading,
|
.quick-access-settings-heading,
|
||||||
.quick-access-preference-row,
|
.quick-access-preference-row,
|
||||||
|
|||||||
Reference in New Issue
Block a user