mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
ea665b80ee
* wip * Mobile support * Address review feedback on inline menu - Mark selection-restore transaction as not added to history - Only open desktop inline menu when an anchor is available Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
205 lines
5.4 KiB
TypeScript
205 lines
5.4 KiB
TypeScript
import {
|
|
TrashIcon,
|
|
InsertAboveIcon,
|
|
InsertBelowIcon,
|
|
PaletteIcon,
|
|
TableHeaderRowIcon,
|
|
TableSplitCellsIcon,
|
|
TableMergeCellsIcon,
|
|
} from "outline-icons";
|
|
import type { EditorState } from "prosemirror-state";
|
|
import { CellSelection, selectedRect } from "prosemirror-tables";
|
|
import {
|
|
getCellsInRow,
|
|
isMergedCellSelection,
|
|
isMultipleCellSelection,
|
|
} from "@shared/editor/queries/table";
|
|
import { t } from "i18next";
|
|
import type {
|
|
MenuItem,
|
|
NodeAttrMark,
|
|
SelectionContext,
|
|
} from "@shared/editor/types";
|
|
import { ArrowDownIcon, ArrowUpIcon } from "~/components/Icons/ArrowIcon";
|
|
import CircleIcon from "~/components/Icons/CircleIcon";
|
|
import CellBackgroundColorPicker from "../components/CellBackgroundColorPicker";
|
|
import TableCell from "@shared/editor/nodes/TableCell";
|
|
import { DottedCircleIcon } from "~/components/Icons/DottedCircleIcon";
|
|
|
|
/**
|
|
* Get the set of background colors used in a row.
|
|
*
|
|
* @param state - the current editor state.
|
|
* @param rowIndex - the row index.
|
|
* @returns a set of hex color strings.
|
|
*/
|
|
function getRowColors(state: EditorState, rowIndex: number): Set<string> {
|
|
const colors = new Set<string>();
|
|
const cells = getCellsInRow(rowIndex)(state) || [];
|
|
|
|
cells.forEach((pos) => {
|
|
const node = state.doc.nodeAt(pos);
|
|
if (!node) {
|
|
return;
|
|
}
|
|
const backgroundMark = (node.attrs.marks ?? []).find(
|
|
(mark: NodeAttrMark) => mark.type === "background"
|
|
);
|
|
if (backgroundMark && backgroundMark.attrs.color) {
|
|
colors.add(backgroundMark.attrs.color);
|
|
}
|
|
});
|
|
|
|
return colors;
|
|
}
|
|
|
|
/**
|
|
* Returns menu items for the table row selection toolbar.
|
|
*
|
|
* @param ctx - the current selection context.
|
|
* @returns an array of menu items.
|
|
*/
|
|
export default function tableRowMenuItems(ctx: SelectionContext): MenuItem[] {
|
|
if (ctx.readOnly) {
|
|
return [];
|
|
}
|
|
|
|
const index = ctx.rowIndex!;
|
|
const { state } = ctx;
|
|
const { selection } = state;
|
|
|
|
if (!(selection instanceof CellSelection)) {
|
|
return [];
|
|
}
|
|
|
|
const tableMap = selectedRect(state);
|
|
const rowColors = getRowColors(state, index);
|
|
const hasBackground = rowColors.size > 0;
|
|
const activeColor =
|
|
rowColors.size === 1 ? rowColors.values().next().value : null;
|
|
const customColor =
|
|
rowColors.size === 1
|
|
? [...rowColors].find((c) => !TableCell.isPresetColor(c))
|
|
: undefined;
|
|
|
|
return [
|
|
{
|
|
name: "toggleHeaderRow",
|
|
label: t("Toggle header"),
|
|
icon: <TableHeaderRowIcon />,
|
|
visible: index === 0,
|
|
},
|
|
{
|
|
name: "addRowBefore",
|
|
label: t("Insert before"),
|
|
icon: <InsertAboveIcon />,
|
|
attrs: { index },
|
|
},
|
|
{
|
|
name: "addRowAfter",
|
|
label: t("Insert after"),
|
|
icon: <InsertBelowIcon />,
|
|
attrs: { index },
|
|
},
|
|
{
|
|
name: "moveTableRow",
|
|
label: t("Move up"),
|
|
icon: <ArrowUpIcon />,
|
|
attrs: { from: index, to: index - 1 },
|
|
visible: index > 0,
|
|
},
|
|
{
|
|
name: "moveTableRow",
|
|
label: t("Move down"),
|
|
icon: <ArrowDownIcon />,
|
|
attrs: { from: index, to: index + 1 },
|
|
visible: index < tableMap.map.height - 1,
|
|
},
|
|
{
|
|
name: "separator",
|
|
},
|
|
{
|
|
label: t("Background"),
|
|
icon:
|
|
rowColors.size > 1 ? (
|
|
<CircleIcon color="rainbow" />
|
|
) : rowColors.size === 1 ? (
|
|
<CircleIcon color={rowColors.values().next().value} />
|
|
) : (
|
|
<PaletteIcon />
|
|
),
|
|
children: [
|
|
...[
|
|
{
|
|
name: "toggleRowBackgroundAndCollapseSelection",
|
|
label: t("None"),
|
|
icon: <DottedCircleIcon color="transparent" />,
|
|
active: () => (hasBackground ? false : true),
|
|
attrs: { color: null },
|
|
},
|
|
],
|
|
...TableCell.presetColors.map((preset) => ({
|
|
name: "toggleRowBackgroundAndCollapseSelection",
|
|
label: preset.name,
|
|
icon: <CircleIcon retainColor color={preset.hex} />,
|
|
active: () => rowColors.size === 1 && rowColors.has(preset.hex),
|
|
attrs: { color: preset.hex },
|
|
})),
|
|
...(customColor
|
|
? [
|
|
{
|
|
name: "toggleRowBackgroundAndCollapseSelection",
|
|
label: customColor,
|
|
icon: <CircleIcon retainColor color={customColor} />,
|
|
active: () => true,
|
|
attrs: { color: customColor },
|
|
},
|
|
]
|
|
: []),
|
|
{
|
|
icon: <CircleIcon retainColor color="rainbow" />,
|
|
label: "Custom",
|
|
children: [
|
|
{
|
|
content: (
|
|
<CellBackgroundColorPicker
|
|
activeColor={activeColor}
|
|
command="toggleRowBackground"
|
|
/>
|
|
),
|
|
preventCloseCondition: () =>
|
|
!!document.activeElement?.matches(
|
|
".ProseMirror.ProseMirror-focused"
|
|
),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "separator",
|
|
},
|
|
{
|
|
name: "mergeCells",
|
|
label: t("Merge cells"),
|
|
icon: <TableMergeCellsIcon />,
|
|
visible: isMultipleCellSelection(state),
|
|
},
|
|
{
|
|
name: "splitCell",
|
|
label: t("Split cell"),
|
|
icon: <TableSplitCellsIcon />,
|
|
visible: isMergedCellSelection(state),
|
|
},
|
|
{
|
|
name: "separator",
|
|
},
|
|
{
|
|
name: "deleteRow",
|
|
label: t("Delete"),
|
|
dangerous: true,
|
|
icon: <TrashIcon />,
|
|
},
|
|
];
|
|
}
|