chore: Refactors towards shared menu component (#4445)
This commit is contained in:
@@ -70,7 +70,7 @@ export function actionToKBar(
|
||||
return [];
|
||||
}
|
||||
|
||||
const resolvedIcon = resolve<React.ReactElement<any>>(action.icon, context);
|
||||
const resolvedIcon = resolve<React.ReactElement>(action.icon, context);
|
||||
const resolvedChildren = resolve<Action[]>(action.children, context);
|
||||
const resolvedSection = resolve<string>(action.section, context);
|
||||
const resolvedName = resolve<string>(action.name, context);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LocationDescriptor } from "history";
|
||||
import { CheckmarkIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { mergeRefs } from "react-merge-refs";
|
||||
import { MenuItem as BaseMenuItem } from "reakit/Menu";
|
||||
import styled, { css } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
@@ -8,6 +9,7 @@ import MenuIconWrapper from "../MenuIconWrapper";
|
||||
|
||||
type Props = {
|
||||
onClick?: (event: React.SyntheticEvent) => void | Promise<void>;
|
||||
active?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
dangerous?: boolean;
|
||||
@@ -18,18 +20,23 @@ type Props = {
|
||||
hide?: () => void;
|
||||
level?: number;
|
||||
icon?: React.ReactElement;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const MenuItem: React.FC<Props> = ({
|
||||
onClick,
|
||||
children,
|
||||
selected,
|
||||
disabled,
|
||||
as,
|
||||
hide,
|
||||
icon,
|
||||
...rest
|
||||
}) => {
|
||||
const MenuItem = (
|
||||
{
|
||||
onClick,
|
||||
children,
|
||||
active,
|
||||
selected,
|
||||
disabled,
|
||||
as,
|
||||
hide,
|
||||
icon,
|
||||
...rest
|
||||
}: Props,
|
||||
ref: React.Ref<HTMLAnchorElement>
|
||||
) => {
|
||||
const handleClick = React.useCallback(
|
||||
(ev) => {
|
||||
if (onClick) {
|
||||
@@ -63,10 +70,14 @@ const MenuItem: React.FC<Props> = ({
|
||||
{(props) => (
|
||||
<MenuAnchor
|
||||
{...props}
|
||||
$toggleable={selected !== undefined}
|
||||
$active={active}
|
||||
as={onClick ? "button" : as}
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
ref={mergeRefs([
|
||||
ref,
|
||||
props.ref as React.RefObject<HTMLAnchorElement>,
|
||||
])}
|
||||
>
|
||||
{selected !== undefined && (
|
||||
<>
|
||||
@@ -97,6 +108,7 @@ type MenuAnchorProps = {
|
||||
disabled?: boolean;
|
||||
dangerous?: boolean;
|
||||
disclosure?: boolean;
|
||||
$active?: boolean;
|
||||
};
|
||||
|
||||
export const MenuAnchorCSS = css<MenuAnchorProps>`
|
||||
@@ -104,6 +116,7 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
padding-left: ${(props) => 12 + (props.level || 0) * 10}px;
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
@@ -146,6 +159,20 @@ export const MenuAnchorCSS = css<MenuAnchorProps>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${
|
||||
props.$active &&
|
||||
`
|
||||
color: ${props.theme.white};
|
||||
background: ${props.dangerous ? props.theme.danger : props.theme.primary};
|
||||
box-shadow: none;
|
||||
cursor: var(--pointer);
|
||||
|
||||
svg {
|
||||
fill: ${props.theme.white};
|
||||
}
|
||||
`
|
||||
}
|
||||
`};
|
||||
|
||||
${breakpoint("tablet")`
|
||||
@@ -160,4 +187,4 @@ export const MenuAnchor = styled.a`
|
||||
${MenuAnchorCSS}
|
||||
`;
|
||||
|
||||
export default MenuItem;
|
||||
export default React.forwardRef<HTMLAnchorElement, Props>(MenuItem);
|
||||
|
||||
@@ -11,5 +11,5 @@ export default function Separator(rest: React.HTMLAttributes<HTMLHRElement>) {
|
||||
}
|
||||
|
||||
const HorizontalRule = styled.hr`
|
||||
margin: 0.5em 12px;
|
||||
margin: 6px 0;
|
||||
`;
|
||||
|
||||
@@ -205,7 +205,7 @@ export const Background = styled(Scrollable)<BackgroundProps>`
|
||||
max-width: 100%;
|
||||
background: ${(props) => props.theme.menuBackground};
|
||||
border-radius: 6px;
|
||||
padding: 6px 0;
|
||||
padding: 6px;
|
||||
min-width: 180px;
|
||||
min-height: 44px;
|
||||
max-height: 75vh;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { findParentNode } from "prosemirror-utils";
|
||||
import React from "react";
|
||||
import getMenuItems from "../menus/block";
|
||||
import BlockMenuItem from "./BlockMenuItem";
|
||||
import CommandMenu, { Props } from "./CommandMenu";
|
||||
import CommandMenuItem from "./CommandMenuItem";
|
||||
|
||||
type BlockMenuProps = Omit<
|
||||
Props,
|
||||
@@ -26,7 +26,7 @@ function BlockMenu(props: BlockMenuProps) {
|
||||
filterable={true}
|
||||
onClearSearch={clearSearch}
|
||||
renderMenuItem={(item, _index, options) => (
|
||||
<BlockMenuItem
|
||||
<CommandMenuItem
|
||||
onClick={options.onClick}
|
||||
selected={options.selected}
|
||||
icon={item.icon}
|
||||
|
||||
@@ -62,7 +62,7 @@ type State = {
|
||||
selectedIndex: number;
|
||||
};
|
||||
|
||||
class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
|
||||
class CommandMenu<T extends MenuItem> extends React.Component<Props<T>, State> {
|
||||
menuRef = React.createRef<HTMLDivElement>();
|
||||
inputRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
@@ -79,7 +79,7 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
|
||||
window.addEventListener("keydown", this.handleKeyDown);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||
shouldComponentUpdate(nextProps: Props<T>, nextState: State) {
|
||||
return (
|
||||
nextProps.search !== this.props.search ||
|
||||
nextProps.isActive !== this.props.isActive ||
|
||||
@@ -87,7 +87,7 @@ class CommandMenu<T = MenuItem> extends React.Component<Props<T>, State> {
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: Props<T>) {
|
||||
if (!prevProps.isActive && this.props.isActive) {
|
||||
// reset scroll position to top when opening menu as the contents are
|
||||
// hidden, not unrendered
|
||||
@@ -575,7 +575,7 @@ const LinkInputWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const LinkInput = styled(Input)`
|
||||
height: 36px;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
`;
|
||||
@@ -584,7 +584,7 @@ const List = styled.ol`
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
height: 100%;
|
||||
padding: 8px 0;
|
||||
padding: 6px;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
@@ -599,7 +599,7 @@ const Empty = styled.div`
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
`;
|
||||
|
||||
@@ -630,7 +630,7 @@ export const Wrapper = styled(Scrollable)<{
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
width: 300px;
|
||||
width: 280px;
|
||||
height: auto;
|
||||
max-height: 324px;
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import * as React from "react";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import styled from "styled-components";
|
||||
import MenuItem from "~/components/ContextMenu/MenuItem";
|
||||
|
||||
export type Props = {
|
||||
selected: boolean;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
icon?: typeof React.Component | React.FC<any>;
|
||||
icon?: React.ReactElement;
|
||||
title: React.ReactNode;
|
||||
shortcut?: string;
|
||||
containerId?: string;
|
||||
};
|
||||
|
||||
function BlockMenuItem({
|
||||
function CommandMenuItem({
|
||||
selected,
|
||||
disabled,
|
||||
onClick,
|
||||
@@ -21,8 +22,6 @@ function BlockMenuItem({
|
||||
icon,
|
||||
containerId = "block-menu-container",
|
||||
}: Props) {
|
||||
const Icon = icon;
|
||||
|
||||
const ref = React.useCallback(
|
||||
(node) => {
|
||||
if (selected && node) {
|
||||
@@ -43,56 +42,22 @@ function BlockMenuItem({
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
selected={selected}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
ref={ref}
|
||||
active={selected}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
icon={icon}
|
||||
>
|
||||
{Icon && (
|
||||
<>
|
||||
<Icon color="currentColor" />
|
||||
|
||||
</>
|
||||
)}
|
||||
{title}
|
||||
{shortcut && <Shortcut>{shortcut}</Shortcut>}
|
||||
{shortcut && <Shortcut $active={selected}>{shortcut}</Shortcut>}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const Shortcut = styled.span`
|
||||
color: ${(props) => props.theme.textTertiary};
|
||||
const Shortcut = styled.span<{ $active?: boolean }>`
|
||||
color: ${(props) =>
|
||||
props.$active ? props.theme.white50 : props.theme.textTertiary};
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
const MenuItem = styled.button<{
|
||||
selected: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
cursor: var(--pointer);
|
||||
border: none;
|
||||
opacity: ${(props) => (props.disabled ? ".5" : "1")};
|
||||
color: ${(props) =>
|
||||
props.selected ? props.theme.white : props.theme.textSecondary};
|
||||
background: ${(props) => (props.selected ? props.theme.primary : "none")};
|
||||
padding: 0 16px;
|
||||
outline: none;
|
||||
|
||||
&:active {
|
||||
color: ${(props) => props.theme.white};
|
||||
background: ${(props) => (props.selected ? props.theme.primary : "none")};
|
||||
|
||||
${Shortcut} {
|
||||
color: ${(props) => props.theme.textSecondary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default BlockMenuItem;
|
||||
export default CommandMenuItem;
|
||||
@@ -1,35 +1,23 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import BlockMenuItem, { Props as BlockMenuItemProps } from "./BlockMenuItem";
|
||||
import CommandMenuItem, {
|
||||
Props as CommandMenuItemProps,
|
||||
} from "./CommandMenuItem";
|
||||
|
||||
const Emoji = styled.span`
|
||||
font-size: 16px;
|
||||
line-height: 1.6em;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
emoji: React.ReactNode;
|
||||
title: React.ReactNode;
|
||||
};
|
||||
|
||||
const EmojiTitle = ({ emoji, title }: Props) => {
|
||||
return (
|
||||
<p>
|
||||
<Emoji className="emoji">{emoji}</Emoji>
|
||||
|
||||
{title}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
type EmojiMenuItemProps = Omit<BlockMenuItemProps, "shortcut" | "theme"> & {
|
||||
type EmojiMenuItemProps = Omit<CommandMenuItemProps, "shortcut" | "theme"> & {
|
||||
emoji: string;
|
||||
};
|
||||
|
||||
export default function EmojiMenuItem(props: EmojiMenuItemProps) {
|
||||
export default function EmojiMenuItem({ emoji, ...rest }: EmojiMenuItemProps) {
|
||||
return (
|
||||
<BlockMenuItem
|
||||
{...props}
|
||||
title={<EmojiTitle emoji={props.emoji} title={props.title} />}
|
||||
<CommandMenuItem
|
||||
{...rest}
|
||||
icon={<Emoji className="emoji">{emoji}</Emoji>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ function ToolbarMenu(props: Props) {
|
||||
if (item.visible === false || !item.icon) {
|
||||
return null;
|
||||
}
|
||||
const Icon = item.icon;
|
||||
const isActive = item.active ? item.active(state) : false;
|
||||
|
||||
return (
|
||||
@@ -39,7 +38,7 @@ function ToolbarMenu(props: Props) {
|
||||
onClick={() => item.name && commands[item.name](item.attrs)}
|
||||
active={isActive}
|
||||
>
|
||||
<Icon color="currentColor" />
|
||||
{React.cloneElement(item.icon, { color: "currentColor" })}
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "heading",
|
||||
title: dictionary.h1,
|
||||
keywords: "h1 heading1 title",
|
||||
icon: Heading1Icon,
|
||||
icon: <Heading1Icon />,
|
||||
shortcut: "^ ⇧ 1",
|
||||
attrs: { level: 1 },
|
||||
},
|
||||
@@ -49,7 +49,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "heading",
|
||||
title: dictionary.h2,
|
||||
keywords: "h2 heading2",
|
||||
icon: Heading2Icon,
|
||||
icon: <Heading2Icon />,
|
||||
shortcut: "^ ⇧ 2",
|
||||
attrs: { level: 2 },
|
||||
},
|
||||
@@ -57,7 +57,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "heading",
|
||||
title: dictionary.h3,
|
||||
keywords: "h3 heading3",
|
||||
icon: Heading3Icon,
|
||||
icon: <Heading3Icon />,
|
||||
shortcut: "^ ⇧ 3",
|
||||
attrs: { level: 3 },
|
||||
},
|
||||
@@ -67,20 +67,20 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
{
|
||||
name: "checkbox_list",
|
||||
title: dictionary.checkboxList,
|
||||
icon: TodoListIcon,
|
||||
icon: <TodoListIcon />,
|
||||
keywords: "checklist checkbox task",
|
||||
shortcut: "^ ⇧ 7",
|
||||
},
|
||||
{
|
||||
name: "bullet_list",
|
||||
title: dictionary.bulletList,
|
||||
icon: BulletedListIcon,
|
||||
icon: <BulletedListIcon />,
|
||||
shortcut: "^ ⇧ 8",
|
||||
},
|
||||
{
|
||||
name: "ordered_list",
|
||||
title: dictionary.orderedList,
|
||||
icon: OrderedListIcon,
|
||||
icon: <OrderedListIcon />,
|
||||
shortcut: "^ ⇧ 9",
|
||||
},
|
||||
{
|
||||
@@ -89,52 +89,52 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
{
|
||||
name: "image",
|
||||
title: dictionary.image,
|
||||
icon: ImageIcon,
|
||||
icon: <ImageIcon />,
|
||||
keywords: "picture photo",
|
||||
},
|
||||
{
|
||||
name: "link",
|
||||
title: dictionary.link,
|
||||
icon: LinkIcon,
|
||||
icon: <LinkIcon />,
|
||||
shortcut: `${metaDisplay} k`,
|
||||
keywords: "link url uri href",
|
||||
},
|
||||
{
|
||||
name: "attachment",
|
||||
title: dictionary.file,
|
||||
icon: AttachmentIcon,
|
||||
icon: <AttachmentIcon />,
|
||||
keywords: "file upload attach",
|
||||
},
|
||||
{
|
||||
name: "table",
|
||||
title: dictionary.table,
|
||||
icon: TableIcon,
|
||||
icon: <TableIcon />,
|
||||
attrs: { rowsCount: 3, colsCount: 3 },
|
||||
},
|
||||
{
|
||||
name: "blockquote",
|
||||
title: dictionary.quote,
|
||||
icon: BlockQuoteIcon,
|
||||
icon: <BlockQuoteIcon />,
|
||||
shortcut: `${metaDisplay} ]`,
|
||||
},
|
||||
{
|
||||
name: "code_block",
|
||||
title: dictionary.codeBlock,
|
||||
icon: CodeIcon,
|
||||
icon: <CodeIcon />,
|
||||
shortcut: "^ ⇧ \\",
|
||||
keywords: "script",
|
||||
},
|
||||
{
|
||||
name: "hr",
|
||||
title: dictionary.hr,
|
||||
icon: HorizontalRuleIcon,
|
||||
icon: <HorizontalRuleIcon />,
|
||||
shortcut: `${metaDisplay} _`,
|
||||
keywords: "horizontal rule break line",
|
||||
},
|
||||
{
|
||||
name: "hr",
|
||||
title: dictionary.pageBreak,
|
||||
icon: PageBreakIcon,
|
||||
icon: <PageBreakIcon />,
|
||||
keywords: "page print break line",
|
||||
attrs: { markup: "***" },
|
||||
},
|
||||
@@ -142,19 +142,19 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
name: "date",
|
||||
title: dictionary.insertDate,
|
||||
keywords: "clock",
|
||||
icon: CalendarIcon,
|
||||
icon: <CalendarIcon />,
|
||||
},
|
||||
{
|
||||
name: "time",
|
||||
title: dictionary.insertTime,
|
||||
keywords: "clock",
|
||||
icon: ClockIcon,
|
||||
icon: <ClockIcon />,
|
||||
},
|
||||
{
|
||||
name: "datetime",
|
||||
title: dictionary.insertDateTime,
|
||||
keywords: "clock",
|
||||
icon: CalendarIcon,
|
||||
icon: <CalendarIcon />,
|
||||
},
|
||||
{
|
||||
name: "separator",
|
||||
@@ -162,21 +162,21 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
{
|
||||
name: "container_notice",
|
||||
title: dictionary.infoNotice,
|
||||
icon: InfoIcon,
|
||||
icon: <InfoIcon />,
|
||||
keywords: "notice card information",
|
||||
attrs: { style: "info" },
|
||||
},
|
||||
{
|
||||
name: "container_notice",
|
||||
title: dictionary.warningNotice,
|
||||
icon: WarningIcon,
|
||||
icon: <WarningIcon />,
|
||||
keywords: "notice card error",
|
||||
attrs: { style: "warning" },
|
||||
},
|
||||
{
|
||||
name: "container_notice",
|
||||
title: dictionary.tipNotice,
|
||||
icon: StarredIcon,
|
||||
icon: <StarredIcon />,
|
||||
keywords: "notice card suggestion",
|
||||
attrs: { style: "tip" },
|
||||
},
|
||||
@@ -186,7 +186,7 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
{
|
||||
name: "code_block",
|
||||
title: "Mermaid Diagram",
|
||||
icon: () => <Img src="/images/mermaidjs.png" alt="Mermaid Diagram" />,
|
||||
icon: <Img src="/images/mermaidjs.png" alt="Mermaid Diagram" />,
|
||||
keywords: "diagram flowchart",
|
||||
attrs: { language: "mermaidjs" },
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PageBreakIcon, HorizontalRuleIcon } from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import isNodeActive from "@shared/editor/queries/isNodeActive";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
@@ -16,14 +17,14 @@ export default function dividerMenuItems(
|
||||
tooltip: dictionary.pageBreak,
|
||||
attrs: { markup: "***" },
|
||||
active: isNodeActive(schema.nodes.hr, { markup: "***" }),
|
||||
icon: PageBreakIcon,
|
||||
icon: <PageBreakIcon />,
|
||||
},
|
||||
{
|
||||
name: "hr",
|
||||
tooltip: dictionary.hr,
|
||||
attrs: { markup: "---" },
|
||||
active: isNodeActive(schema.nodes.hr, { markup: "---" }),
|
||||
icon: HorizontalRuleIcon,
|
||||
icon: <HorizontalRuleIcon />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import {
|
||||
TodoListIcon,
|
||||
InputIcon,
|
||||
HighlightIcon,
|
||||
ItalicIcon,
|
||||
} from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import { isInTable } from "prosemirror-tables";
|
||||
import * as React from "react";
|
||||
import isInCode from "@shared/editor/queries/isInCode";
|
||||
import isInList from "@shared/editor/queries/isInList";
|
||||
import isMarkActive from "@shared/editor/queries/isMarkActive";
|
||||
@@ -36,7 +38,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "placeholder",
|
||||
tooltip: dictionary.placeholder,
|
||||
icon: InputIcon,
|
||||
icon: <InputIcon />,
|
||||
active: isMarkActive(schema.marks.placeholder),
|
||||
visible: isTemplate,
|
||||
},
|
||||
@@ -47,28 +49,35 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "strong",
|
||||
tooltip: dictionary.strong,
|
||||
icon: BoldIcon,
|
||||
icon: <BoldIcon />,
|
||||
active: isMarkActive(schema.marks.strong),
|
||||
visible: !isCode,
|
||||
},
|
||||
{
|
||||
name: "em",
|
||||
tooltip: dictionary.em,
|
||||
icon: <ItalicIcon />,
|
||||
active: isMarkActive(schema.marks.em),
|
||||
visible: !isCode,
|
||||
},
|
||||
{
|
||||
name: "strikethrough",
|
||||
tooltip: dictionary.strikethrough,
|
||||
icon: StrikethroughIcon,
|
||||
icon: <StrikethroughIcon />,
|
||||
active: isMarkActive(schema.marks.strikethrough),
|
||||
visible: !isCode,
|
||||
},
|
||||
{
|
||||
name: "highlight",
|
||||
tooltip: dictionary.mark,
|
||||
icon: HighlightIcon,
|
||||
icon: <HighlightIcon />,
|
||||
active: isMarkActive(schema.marks.highlight),
|
||||
visible: !isTemplate && !isCode,
|
||||
},
|
||||
{
|
||||
name: "code_inline",
|
||||
tooltip: dictionary.codeInline,
|
||||
icon: CodeIcon,
|
||||
icon: <CodeIcon />,
|
||||
active: isMarkActive(schema.marks.code_inline),
|
||||
},
|
||||
{
|
||||
@@ -78,7 +87,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "heading",
|
||||
tooltip: dictionary.heading,
|
||||
icon: Heading1Icon,
|
||||
icon: <Heading1Icon />,
|
||||
active: isNodeActive(schema.nodes.heading, { level: 1 }),
|
||||
attrs: { level: 1 },
|
||||
visible: allowBlocks && !isCode,
|
||||
@@ -86,7 +95,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "heading",
|
||||
tooltip: dictionary.subheading,
|
||||
icon: Heading2Icon,
|
||||
icon: <Heading2Icon />,
|
||||
active: isNodeActive(schema.nodes.heading, { level: 2 }),
|
||||
attrs: { level: 2 },
|
||||
visible: allowBlocks && !isCode,
|
||||
@@ -94,7 +103,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "blockquote",
|
||||
tooltip: dictionary.quote,
|
||||
icon: BlockQuoteIcon,
|
||||
icon: <BlockQuoteIcon />,
|
||||
active: isNodeActive(schema.nodes.blockquote),
|
||||
attrs: { level: 2 },
|
||||
visible: allowBlocks && !isCode,
|
||||
@@ -106,7 +115,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "checkbox_list",
|
||||
tooltip: dictionary.checkboxList,
|
||||
icon: TodoListIcon,
|
||||
icon: <TodoListIcon />,
|
||||
keywords: "checklist checkbox task",
|
||||
active: isNodeActive(schema.nodes.checkbox_list),
|
||||
visible: (allowBlocks || isList) && !isCode,
|
||||
@@ -114,14 +123,14 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "bullet_list",
|
||||
tooltip: dictionary.bulletList,
|
||||
icon: BulletedListIcon,
|
||||
icon: <BulletedListIcon />,
|
||||
active: isNodeActive(schema.nodes.bullet_list),
|
||||
visible: (allowBlocks || isList) && !isCode,
|
||||
},
|
||||
{
|
||||
name: "ordered_list",
|
||||
tooltip: dictionary.orderedList,
|
||||
icon: OrderedListIcon,
|
||||
icon: <OrderedListIcon />,
|
||||
active: isNodeActive(schema.nodes.ordered_list),
|
||||
visible: (allowBlocks || isList) && !isCode,
|
||||
},
|
||||
@@ -132,7 +141,7 @@ export default function formattingMenuItems(
|
||||
{
|
||||
name: "link",
|
||||
tooltip: dictionary.createLink,
|
||||
icon: LinkIcon,
|
||||
icon: <LinkIcon />,
|
||||
active: isMarkActive(schema.marks.link),
|
||||
attrs: { href: "" },
|
||||
visible: !isCode,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AlignImageCenterIcon,
|
||||
} from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import isNodeActive from "@shared/editor/queries/isNodeActive";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
@@ -27,14 +28,14 @@ export default function imageMenuItems(
|
||||
{
|
||||
name: "alignLeft",
|
||||
tooltip: dictionary.alignLeft,
|
||||
icon: AlignImageLeftIcon,
|
||||
icon: <AlignImageLeftIcon />,
|
||||
visible: true,
|
||||
active: isLeftAligned,
|
||||
},
|
||||
{
|
||||
name: "alignCenter",
|
||||
tooltip: dictionary.alignCenter,
|
||||
icon: AlignImageCenterIcon,
|
||||
icon: <AlignImageCenterIcon />,
|
||||
visible: true,
|
||||
active: (state) =>
|
||||
isNodeActive(schema.nodes.image)(state) &&
|
||||
@@ -44,7 +45,7 @@ export default function imageMenuItems(
|
||||
{
|
||||
name: "alignRight",
|
||||
tooltip: dictionary.alignRight,
|
||||
icon: AlignImageRightIcon,
|
||||
icon: <AlignImageRightIcon />,
|
||||
visible: true,
|
||||
active: isRightAligned,
|
||||
},
|
||||
@@ -55,21 +56,21 @@ export default function imageMenuItems(
|
||||
{
|
||||
name: "downloadImage",
|
||||
tooltip: dictionary.downloadImage,
|
||||
icon: DownloadIcon,
|
||||
icon: <DownloadIcon />,
|
||||
visible: !!fetch,
|
||||
active: () => false,
|
||||
},
|
||||
{
|
||||
name: "replaceImage",
|
||||
tooltip: dictionary.replaceImage,
|
||||
icon: ReplaceIcon,
|
||||
icon: <ReplaceIcon />,
|
||||
visible: true,
|
||||
active: () => false,
|
||||
},
|
||||
{
|
||||
name: "deleteImage",
|
||||
tooltip: dictionary.deleteImage,
|
||||
icon: TrashIcon,
|
||||
icon: <TrashIcon />,
|
||||
visible: true,
|
||||
active: () => false,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TrashIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
|
||||
@@ -7,7 +8,7 @@ export default function tableMenuItems(dictionary: Dictionary): MenuItem[] {
|
||||
{
|
||||
name: "deleteTable",
|
||||
tooltip: dictionary.deleteTable,
|
||||
icon: TrashIcon,
|
||||
icon: <TrashIcon />,
|
||||
active: () => false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
InsertRightIcon,
|
||||
} from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import isNodeActive from "@shared/editor/queries/isNodeActive";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
@@ -23,7 +24,7 @@ export default function tableColMenuItems(
|
||||
{
|
||||
name: "setColumnAttr",
|
||||
tooltip: dictionary.alignLeft,
|
||||
icon: AlignLeftIcon,
|
||||
icon: <AlignLeftIcon />,
|
||||
attrs: { index, alignment: "left" },
|
||||
active: isNodeActive(schema.nodes.th, {
|
||||
colspan: 1,
|
||||
@@ -34,7 +35,7 @@ export default function tableColMenuItems(
|
||||
{
|
||||
name: "setColumnAttr",
|
||||
tooltip: dictionary.alignCenter,
|
||||
icon: AlignCenterIcon,
|
||||
icon: <AlignCenterIcon />,
|
||||
attrs: { index, alignment: "center" },
|
||||
active: isNodeActive(schema.nodes.th, {
|
||||
colspan: 1,
|
||||
@@ -45,7 +46,7 @@ export default function tableColMenuItems(
|
||||
{
|
||||
name: "setColumnAttr",
|
||||
tooltip: dictionary.alignRight,
|
||||
icon: AlignRightIcon,
|
||||
icon: <AlignRightIcon />,
|
||||
attrs: { index, alignment: "right" },
|
||||
active: isNodeActive(schema.nodes.th, {
|
||||
colspan: 1,
|
||||
@@ -59,13 +60,13 @@ export default function tableColMenuItems(
|
||||
{
|
||||
name: rtl ? "addColumnAfter" : "addColumnBefore",
|
||||
tooltip: rtl ? dictionary.addColumnAfter : dictionary.addColumnBefore,
|
||||
icon: InsertLeftIcon,
|
||||
icon: <InsertLeftIcon />,
|
||||
active: () => false,
|
||||
},
|
||||
{
|
||||
name: rtl ? "addColumnBefore" : "addColumnAfter",
|
||||
tooltip: rtl ? dictionary.addColumnBefore : dictionary.addColumnAfter,
|
||||
icon: InsertRightIcon,
|
||||
icon: <InsertRightIcon />,
|
||||
active: () => false,
|
||||
},
|
||||
{
|
||||
@@ -74,7 +75,7 @@ export default function tableColMenuItems(
|
||||
{
|
||||
name: "deleteColumn",
|
||||
tooltip: dictionary.deleteColumn,
|
||||
icon: TrashIcon,
|
||||
icon: <TrashIcon />,
|
||||
active: () => false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TrashIcon, InsertAboveIcon, InsertBelowIcon } from "outline-icons";
|
||||
import { EditorState } from "prosemirror-state";
|
||||
import * as React from "react";
|
||||
import { MenuItem } from "@shared/editor/types";
|
||||
import { Dictionary } from "~/hooks/useDictionary";
|
||||
|
||||
@@ -12,7 +13,7 @@ export default function tableRowMenuItems(
|
||||
{
|
||||
name: "addRowAfter",
|
||||
tooltip: dictionary.addRowBefore,
|
||||
icon: InsertAboveIcon,
|
||||
icon: <InsertAboveIcon />,
|
||||
attrs: { index: index - 1 },
|
||||
active: () => false,
|
||||
visible: index !== 0,
|
||||
@@ -20,7 +21,7 @@ export default function tableRowMenuItems(
|
||||
{
|
||||
name: "addRowAfter",
|
||||
tooltip: dictionary.addRowAfter,
|
||||
icon: InsertBelowIcon,
|
||||
icon: <InsertBelowIcon />,
|
||||
attrs: { index },
|
||||
active: () => false,
|
||||
},
|
||||
@@ -30,7 +31,7 @@ export default function tableRowMenuItems(
|
||||
{
|
||||
name: "deleteRow",
|
||||
tooltip: dictionary.deleteRow,
|
||||
icon: TrashIcon,
|
||||
icon: <TrashIcon />,
|
||||
active: () => false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -677,7 +677,6 @@ const MaxWidth = styled(Flex)<MaxWidthProps>`
|
||||
padding-bottom: 16px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
padding: 0 44px;
|
||||
margin: 4px auto 12px;
|
||||
max-width: ${(props: MaxWidthProps) =>
|
||||
props.isFullWidth
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function DisabledEmbed(props: Props & ThemeProps<DefaultTheme>) {
|
||||
<Widget
|
||||
title={props.embed.title}
|
||||
href={props.attrs.href}
|
||||
icon={props.embed.icon(undefined)}
|
||||
icon={props.embed.icon}
|
||||
context={props.attrs.href.replace(/^https?:\/\//, "")}
|
||||
isSelected={props.isSelected}
|
||||
theme={props.theme}
|
||||
|
||||
@@ -68,7 +68,7 @@ const Img = styled(Image)`
|
||||
`;
|
||||
|
||||
export class EmbedDescriptor {
|
||||
icon: React.FC<any>;
|
||||
icon?: React.ReactNode;
|
||||
name?: string;
|
||||
title?: string;
|
||||
shortcut?: string;
|
||||
@@ -127,264 +127,262 @@ const embeds: EmbedDescriptor[] = [
|
||||
title: "Abstract",
|
||||
keywords: "design",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/abstract.png" alt="Abstract" />,
|
||||
icon: <Img src="/images/abstract.png" alt="Abstract" />,
|
||||
component: Abstract,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Airtable",
|
||||
keywords: "spreadsheet",
|
||||
icon: () => <Img src="/images/airtable.png" alt="Airtable" />,
|
||||
icon: <Img src="/images/airtable.png" alt="Airtable" />,
|
||||
component: Airtable,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Berrycast",
|
||||
keywords: "video",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/berrycast.png" alt="Berrycast" />,
|
||||
icon: <Img src="/images/berrycast.png" alt="Berrycast" />,
|
||||
component: Berrycast,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Bilibili",
|
||||
keywords: "video",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/bilibili.png" alt="Bilibili" />,
|
||||
icon: <Img src="/images/bilibili.png" alt="Bilibili" />,
|
||||
component: Bilibili,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Cawemo",
|
||||
keywords: "bpmn process",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/cawemo.png" alt="Cawemo" />,
|
||||
icon: <Img src="/images/cawemo.png" alt="Cawemo" />,
|
||||
component: Cawemo,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "ClickUp",
|
||||
keywords: "project",
|
||||
icon: () => <Img src="/images/clickup.png" alt="ClickUp" />,
|
||||
icon: <Img src="/images/clickup.png" alt="ClickUp" />,
|
||||
component: ClickUp,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Codepen",
|
||||
keywords: "code editor",
|
||||
icon: () => <Img src="/images/codepen.png" alt="Codepen" />,
|
||||
icon: <Img src="/images/codepen.png" alt="Codepen" />,
|
||||
component: Codepen,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "DBDiagram",
|
||||
keywords: "diagrams database",
|
||||
icon: () => <Img src="/images/dbdiagram.png" alt="DBDiagram" />,
|
||||
icon: <Img src="/images/dbdiagram.png" alt="DBDiagram" />,
|
||||
component: DBDiagram,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Descript",
|
||||
keywords: "audio",
|
||||
icon: () => <Img src="/images/descript.png" alt="Descript" />,
|
||||
icon: <Img src="/images/descript.png" alt="Descript" />,
|
||||
component: Descript,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Figma",
|
||||
keywords: "design svg vector",
|
||||
icon: () => <Img src="/images/figma.png" alt="Figma" />,
|
||||
icon: <Img src="/images/figma.png" alt="Figma" />,
|
||||
component: Figma,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Framer",
|
||||
keywords: "design prototyping",
|
||||
icon: () => <Img src="/images/framer.png" alt="Framer" />,
|
||||
icon: <Img src="/images/framer.png" alt="Framer" />,
|
||||
component: Framer,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "GitHub Gist",
|
||||
keywords: "code",
|
||||
icon: () => <Img src="/images/github-gist.png" alt="GitHub" />,
|
||||
icon: <Img src="/images/github-gist.png" alt="GitHub" />,
|
||||
component: Gist,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Gliffy",
|
||||
keywords: "diagram",
|
||||
icon: () => <Img src="/images/gliffy.png" alt="Gliffy" />,
|
||||
icon: <Img src="/images/gliffy.png" alt="Gliffy" />,
|
||||
component: Gliffy,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Diagrams.net",
|
||||
keywords: "diagrams drawio",
|
||||
icon: () => <Img src="/images/diagrams.png" alt="Diagrams.net" />,
|
||||
icon: <Img src="/images/diagrams.png" alt="Diagrams.net" />,
|
||||
component: Diagrams,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Drawings",
|
||||
keywords: "drawings",
|
||||
icon: () => <Img src="/images/google-drawings.png" alt="Google Drawings" />,
|
||||
icon: <Img src="/images/google-drawings.png" alt="Google Drawings" />,
|
||||
component: GoogleDrawings,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Drive",
|
||||
keywords: "drive",
|
||||
icon: () => <Img src="/images/google-drive.png" alt="Google Drive" />,
|
||||
icon: <Img src="/images/google-drive.png" alt="Google Drive" />,
|
||||
component: GoogleDrive,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Docs",
|
||||
keywords: "documents word",
|
||||
icon: () => <Img src="/images/google-docs.png" alt="Google Docs" />,
|
||||
icon: <Img src="/images/google-docs.png" alt="Google Docs" />,
|
||||
component: GoogleDocs,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Sheets",
|
||||
keywords: "excel spreadsheet",
|
||||
icon: () => <Img src="/images/google-sheets.png" alt="Google Sheets" />,
|
||||
icon: <Img src="/images/google-sheets.png" alt="Google Sheets" />,
|
||||
component: GoogleSheets,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Slides",
|
||||
keywords: "presentation slideshow",
|
||||
icon: () => <Img src="/images/google-slides.png" alt="Google Slides" />,
|
||||
icon: <Img src="/images/google-slides.png" alt="Google Slides" />,
|
||||
component: GoogleSlides,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Calendar",
|
||||
keywords: "calendar",
|
||||
icon: () => <Img src="/images/google-calendar.png" alt="Google Calendar" />,
|
||||
icon: <Img src="/images/google-calendar.png" alt="Google Calendar" />,
|
||||
component: GoogleCalendar,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Data Studio",
|
||||
keywords: "bi business intelligence",
|
||||
icon: () => (
|
||||
<Img src="/images/google-datastudio.png" alt="Google Data Studio" />
|
||||
),
|
||||
icon: <Img src="/images/google-datastudio.png" alt="Google Data Studio" />,
|
||||
component: GoogleDataStudio,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Google Forms",
|
||||
keywords: "form survey",
|
||||
icon: () => <Img src="/images/google-forms.png" alt="Google Forms" />,
|
||||
icon: <Img src="/images/google-forms.png" alt="Google Forms" />,
|
||||
component: GoogleForms,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Grist",
|
||||
keywords: "spreadsheet",
|
||||
icon: () => <Img src="/images/grist.png" alt="Grist" />,
|
||||
icon: <Img src="/images/grist.png" alt="Grist" />,
|
||||
component: Grist,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "InVision",
|
||||
keywords: "design prototype",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/invision.png" alt="InVision" />,
|
||||
icon: <Img src="/images/invision.png" alt="InVision" />,
|
||||
component: InVision,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "JSFiddle",
|
||||
keywords: "code",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/jsfiddle.png" alt="JSFiddle" />,
|
||||
icon: <Img src="/images/jsfiddle.png" alt="JSFiddle" />,
|
||||
component: JSFiddle,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Loom",
|
||||
keywords: "video screencast",
|
||||
icon: () => <Img src="/images/loom.png" alt="Loom" />,
|
||||
icon: <Img src="/images/loom.png" alt="Loom" />,
|
||||
component: Loom,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Lucidchart",
|
||||
keywords: "chart",
|
||||
icon: () => <Img src="/images/lucidchart.png" alt="Lucidchart" />,
|
||||
icon: <Img src="/images/lucidchart.png" alt="Lucidchart" />,
|
||||
component: Lucidchart,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Marvel",
|
||||
keywords: "design prototype",
|
||||
icon: () => <Img src="/images/marvel.png" alt="Marvel" />,
|
||||
icon: <Img src="/images/marvel.png" alt="Marvel" />,
|
||||
component: Marvel,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Mindmeister",
|
||||
keywords: "mindmap",
|
||||
icon: () => <Img src="/images/mindmeister.png" alt="Mindmeister" />,
|
||||
icon: <Img src="/images/mindmeister.png" alt="Mindmeister" />,
|
||||
component: Mindmeister,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Miro",
|
||||
keywords: "whiteboard",
|
||||
icon: () => <Img src="/images/miro.png" alt="Miro" />,
|
||||
icon: <Img src="/images/miro.png" alt="Miro" />,
|
||||
component: Miro,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Mode",
|
||||
keywords: "analytics",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/mode-analytics.png" alt="Mode" />,
|
||||
icon: <Img src="/images/mode-analytics.png" alt="Mode" />,
|
||||
component: ModeAnalytics,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Otter.ai",
|
||||
keywords: "audio transcription meeting notes",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/otter.png" alt="Otter.ai" />,
|
||||
icon: <Img src="/images/otter.png" alt="Otter.ai" />,
|
||||
component: Otter,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Pitch",
|
||||
keywords: "presentation",
|
||||
defaultHidden: true,
|
||||
icon: () => <Img src="/images/pitch.png" alt="Pitch" />,
|
||||
icon: <Img src="/images/pitch.png" alt="Pitch" />,
|
||||
component: Pitch,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Prezi",
|
||||
keywords: "presentation",
|
||||
icon: () => <Img src="/images/prezi.png" alt="Prezi" />,
|
||||
icon: <Img src="/images/prezi.png" alt="Prezi" />,
|
||||
component: Prezi,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Scribe",
|
||||
keywords: "screencast",
|
||||
icon: () => <Img src="/images/scribe.png" alt="Scribe" />,
|
||||
icon: <Img src="/images/scribe.png" alt="Scribe" />,
|
||||
component: Scribe,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Spotify",
|
||||
keywords: "music",
|
||||
icon: () => <Img src="/images/spotify.png" alt="Spotify" />,
|
||||
icon: <Img src="/images/spotify.png" alt="Spotify" />,
|
||||
component: Spotify,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Tldraw",
|
||||
keywords: "draw schematics diagrams",
|
||||
visible: false,
|
||||
icon: () => <Img src="/images/tldraw.png" alt="Tldraw" />,
|
||||
icon: <Img src="/images/tldraw.png" alt="Tldraw" />,
|
||||
component: Tldraw,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Trello",
|
||||
keywords: "kanban",
|
||||
icon: () => <Img src="/images/trello.png" alt="Trello" />,
|
||||
icon: <Img src="/images/trello.png" alt="Trello" />,
|
||||
component: Trello,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Typeform",
|
||||
keywords: "form survey",
|
||||
icon: () => <Img src="/images/typeform.png" alt="Typeform" />,
|
||||
icon: <Img src="/images/typeform.png" alt="Typeform" />,
|
||||
component: Typeform,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Vimeo",
|
||||
keywords: "video",
|
||||
icon: () => <Img src="/images/vimeo.png" alt="Vimeo" />,
|
||||
icon: <Img src="/images/vimeo.png" alt="Vimeo" />,
|
||||
component: Vimeo,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "Whimsical",
|
||||
keywords: "whiteboard",
|
||||
icon: () => <Img src="/images/whimsical.png" alt="Whimsical" />,
|
||||
icon: <Img src="/images/whimsical.png" alt="Whimsical" />,
|
||||
component: Whimsical,
|
||||
}),
|
||||
new EmbedDescriptor({
|
||||
title: "YouTube",
|
||||
keywords: "google video",
|
||||
icon: () => <Img src="/images/youtube.png" alt="YouTube" />,
|
||||
icon: <Img src="/images/youtube.png" alt="YouTube" />,
|
||||
component: YouTube,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -3,7 +3,9 @@ import { MenuItem } from "../types";
|
||||
|
||||
type Item = MenuItem | EmbedDescriptor;
|
||||
|
||||
export default function filterExcessSeparators(items: Item[]): Item[] {
|
||||
export default function filterExcessSeparators<T extends Item>(
|
||||
items: T[]
|
||||
): T[] {
|
||||
return items
|
||||
.reduce((acc, item) => {
|
||||
// trim separator if the previous item was a separator
|
||||
@@ -14,7 +16,7 @@ export default function filterExcessSeparators(items: Item[]): Item[] {
|
||||
return acc;
|
||||
}
|
||||
return [...acc, item];
|
||||
}, [] as Item[])
|
||||
}, [] as T[])
|
||||
.filter((item, index, arr) => {
|
||||
if (
|
||||
item.name === "separator" &&
|
||||
|
||||
@@ -15,7 +15,7 @@ export enum EventType {
|
||||
}
|
||||
|
||||
export type MenuItem = {
|
||||
icon?: typeof React.Component | React.FC<any>;
|
||||
icon?: React.ReactElement;
|
||||
name?: string;
|
||||
title?: string;
|
||||
shortcut?: string;
|
||||
|
||||
Reference in New Issue
Block a user