Merge branch 'main' into feat/bases

This commit is contained in:
Philipinho
2026-06-20 02:35:39 +01:00
21 changed files with 120 additions and 44 deletions
@@ -787,18 +787,34 @@ export const getSuggestionItems = ({
for (const [group, items] of Object.entries(CommandGroups)) {
const filteredItems = items.filter((item) => {
if (excludeItems?.has(item.title)) return false;
const translatedTitle = i18n.t(item.title);
const translatedDescription = i18n.t(item.description);
return (
fuzzyMatch(search, item.title) ||
fuzzyMatch(search, translatedTitle) ||
item.description.toLowerCase().includes(search) ||
translatedDescription.toLowerCase().includes(search) ||
(item.searchTerms &&
item.searchTerms.some((term: string) => term.includes(search)))
item.searchTerms.some(
(term: string) =>
term.includes(search) ||
i18n.t(term).toLowerCase().includes(search),
))
);
});
if (filteredItems.length) {
filteredGroups[group] = filteredItems.sort((a, b) => {
const aTitle = a.title.toLowerCase().includes(search) ? 0 : 1;
const bTitle = b.title.toLowerCase().includes(search) ? 0 : 1;
const aTitle =
a.title.toLowerCase().includes(search) ||
i18n.t(a.title).toLowerCase().includes(search)
? 0
: 1;
const bTitle =
b.title.toLowerCase().includes(search) ||
i18n.t(b.title).toLowerCase().includes(search)
? 0
: 1;
return aTitle - bTitle;
});
}
@@ -15,6 +15,7 @@ interface PageEditorProps {
title: string;
content: any;
pageId?: string;
printMode?: boolean;
/**
* When rendering inside a public share, pass the share's id (or key). Lookups
* for transclusion content then resolve against the share graph instead of
@@ -28,6 +29,7 @@ export default function ReadonlyPageEditor({
title,
content,
pageId,
printMode = false,
shareId,
}: PageEditorProps) {
const [, setReadOnlyEditor] = useAtom(readOnlyEditorAtom);
@@ -48,8 +50,12 @@ export default function ReadonlyPageEditor({
}, []);
const extensions = useMemo(() => {
const excludedExtensions = new Set([
"uniqueID",
...(printMode ? ["tableHeaderPin", "tableReadonlySort"] : []),
]);
const filteredExtensions = mainExtensions.filter(
(ext) => ext.name !== "uniqueID",
(ext) => !excludedExtensions.has(ext.name),
);
return [
@@ -59,7 +65,7 @@ export default function ReadonlyPageEditor({
updateDocument: false,
}),
];
}, []);
}, [printMode]);
const titleExtensions = [
Document.extend({
@@ -163,8 +163,13 @@
@media print {
.tableWrapper.tableHeaderPinned table tr:first-child {
position: static;
transform: none;
position: static !important;
top: auto !important;
transform: none !important;
}
.tableReadonlySortChevron {
display: none !important;
}
}
@@ -268,4 +273,4 @@
.prosemirror-dropcursor-inline {
display: none;
}
}
}
@@ -91,7 +91,9 @@ export default function GroupMembersList() {
<ActionIcon
variant="subtle"
c="gray"
aria-label={t("Member actions")}
aria-label={t("Member actions for {{name}}", {
name: user.name,
})}
>
<IconDots size={20} stroke={2} />
</ActionIcon>
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useId, useState } from "react";
import {
ActionIcon,
Group,
@@ -31,6 +31,7 @@ import classes from "../notification.module.css";
export function NotificationPopover() {
const { t } = useTranslation();
const titleId = useId();
const [opened, setOpened] = useState(false);
const [tab, setTab] = useState<NotificationTab>("direct");
const [filter, setFilter] = useState<NotificationFilter>("all");
@@ -83,10 +84,11 @@ export function NotificationPopover() {
<Popover.Dropdown
p={0}
aria-labelledby={titleId}
style={{ width: "min(420px, calc(100vw - 24px))" }}
>
<Group justify="space-between" px="md" py="sm">
<Title order={2} fz="sm" fw={600}>
<Title id={titleId} order={2} fz="sm" fw={600}>
{t("Notifications")}
</Title>
<Group gap={4}>
@@ -35,6 +35,7 @@ import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
import { treeModel } from "@/features/page/tree/model/tree-model";
import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts";
import type { SpaceTreeNode } from "@/features/page/tree/types.ts";
import classes from "@/features/page/tree/styles/tree.module.css";
export interface NodeMenuProps {
node: SpaceTreeNode;
@@ -124,8 +125,9 @@ export function NodeMenu({ node, canEdit }: NodeMenuProps) {
<Menu shadow="md" width={200}>
<Menu.Target>
<ActionIcon
variant="transparent"
c="gray"
variant="subtle"
color="gray"
className={classes.actionIcon}
aria-label={t("Page menu for {{name}}", { name: getPageTitle(node.name, node.isBase, t) })}
tabIndex={-1}
onClick={(e) => {
@@ -209,13 +209,13 @@ function PageArrow({ isOpen, hasChildren, onToggle }: PageArrowProps) {
return (
<span
aria-hidden
className={classes.actionIcon}
style={{
width: 20,
height: 20,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
color: "var(--mantine-color-gray-6)",
flexShrink: 0,
}}
>
@@ -228,7 +228,8 @@ function PageArrow({ isOpen, hasChildren, onToggle }: PageArrowProps) {
<ActionIcon
size={20}
variant="subtle"
c="gray"
color="gray"
className={classes.actionIcon}
aria-label={isOpen ? t("Collapse") : t("Expand")}
aria-expanded={isOpen}
tabIndex={-1}
@@ -280,8 +281,9 @@ function CreateNode({
return (
<ActionIcon
variant="transparent"
c="gray"
variant="subtle"
color="gray"
className={classes.actionIcon}
aria-label={t("Create subpage of {{name}}", { name: node.name || t("untitled") })}
tabIndex={-1}
onClick={(e) => {
@@ -57,6 +57,10 @@
flex-shrink: 0;
}
.actionIcon {
color: light-dark(var(--mantine-color-dark-3), var(--mantine-color-gray-4));
}
.text {
flex: 1;
/* min-width: 0 lets a flex child shrink below its content size — required
@@ -17,6 +17,7 @@ import {
import { useTranslation } from "react-i18next";
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
import { SpaceFilterMenu } from "@/features/space/components/space-filter-menu";
import { RadioMenuItem } from "@/components/ui/radio-menu-item";
import { useHasFeature } from "@/ee/hooks/use-feature";
import { Feature } from "@/ee/features";
import classes from "./search-spotlight-filters.module.css";
@@ -175,7 +176,7 @@ export function SearchSpotlightFilters({
{contentTypeOptions.map((option) => (
<Menu.Item
key={option.value}
role="menuitemradio"
component={RadioMenuItem}
aria-checked={contentType === option.value}
onClick={() =>
!option.disabled &&
@@ -13,6 +13,7 @@ import { useDebouncedValue } from "@mantine/hooks";
import { IconCheck, IconSearch } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
import { RadioMenuItem } from "@/components/ui/radio-menu-item";
type SpaceFilterMenuProps = {
value: string | null;
@@ -75,7 +76,7 @@ export function SpaceFilterMenu({
<ScrollArea.Autosize mah={280}>
<Menu.Item
role="menuitemradio"
component={RadioMenuItem}
aria-checked={!value}
onClick={() => onChange(null)}
>
@@ -103,7 +104,7 @@ export function SpaceFilterMenu({
{orderedSpaces.map((space) => (
<Menu.Item
key={space.id}
role="menuitemradio"
component={RadioMenuItem}
aria-checked={value === space.id}
onClick={() => onChange(space.id)}
>
@@ -210,7 +210,9 @@ export default function SpaceMembersList({
<ActionIcon
variant="subtle"
c="gray"
aria-label={t("Member actions")}
aria-label={t("Member actions for {{name}}", {
name: member.name,
})}
>
<IconDots size={20} stroke={2} />
</ActionIcon>
@@ -12,9 +12,14 @@ import useUserRole from "@/hooks/use-user-role.tsx";
interface Props {
userId: string;
name: string;
deactivatedAt: Date | null;
}
export default function MemberActionMenu({ userId, deactivatedAt }: Props) {
export default function MemberActionMenu({
userId,
name,
deactivatedAt,
}: Props) {
const { t } = useTranslation();
const deleteWorkspaceMemberMutation = useDeleteWorkspaceMemberMutation();
const deactivateMutation = useDeactivateWorkspaceMemberMutation();
@@ -86,7 +91,7 @@ export default function MemberActionMenu({ userId, deactivatedAt }: Props) {
<ActionIcon
variant="subtle"
c="gray"
aria-label={t("Member actions")}
aria-label={t("Member actions for {{name}}", { name })}
>
<IconDots size={20} stroke={2} />
</ActionIcon>
@@ -111,6 +111,7 @@ export default function WorkspaceMembersTable() {
{isAdmin && (
<MemberActionMenu
userId={user.id}
name={user.name}
deactivatedAt={user.deactivatedAt}
/>
)}