diff --git a/packages/editor/dist/components/menu/menu.d.ts b/packages/editor/dist/components/menu/menu.d.ts index 5ec21b401..efd7acfdd 100644 --- a/packages/editor/dist/components/menu/menu.d.ts +++ b/packages/editor/dist/components/menu/menu.d.ts @@ -2,6 +2,7 @@ import { PropsWithChildren } from "react"; import { FlexProps } from "rebass"; import { MenuOptions } from "./useMenu"; import { MenuItem as MenuItemType } from "./types"; +import "react-spring-bottom-sheet/dist/style.css"; declare type MenuProps = MenuContainerProps & { items: MenuItemType[]; closeMenu: () => void; @@ -10,12 +11,24 @@ export declare function Menu(props: MenuProps): JSX.Element; declare type MenuContainerProps = FlexProps & { title?: string; }; +export declare type PopupType = "sheet" | "menu" | "none"; +export declare type PopupPresenterProps = MenuPresenterProps & ActionSheetPresenterProps & { + mobile?: PopupType; + desktop?: PopupType; +}; +export declare function PopupPresenter(props: PropsWithChildren): JSX.Element | null; export declare type MenuPresenterProps = MenuContainerProps & { - items: MenuItemType[]; - options: MenuOptions; + items?: MenuItemType[]; + onClose?: () => void; isOpen: boolean; - onClose: () => void; - className?: string; + options?: MenuOptions; }; export declare function MenuPresenter(props: PropsWithChildren): JSX.Element; +export declare type ActionSheetPresenterProps = MenuContainerProps & { + items?: MenuItemType[]; + isOpen: boolean; + onClose?: () => void; + blocking?: boolean; +}; +export declare function ActionSheetPresenter(props: PropsWithChildren): JSX.Element; export {}; diff --git a/packages/editor/dist/components/menu/menu.js b/packages/editor/dist/components/menu/menu.js index b472a1533..6ab7db9a8 100644 --- a/packages/editor/dist/components/menu/menu.js +++ b/packages/editor/dist/components/menu/menu.js @@ -44,6 +44,9 @@ import { getPosition } from "./useMenu"; import MenuItem from "./menuitem"; // import { useMenuTrigger, useMenu, getPosition } from "../../hooks/useMenu"; import Modal from "react-modal"; +import { BottomSheet } from "react-spring-bottom-sheet"; +import "react-spring-bottom-sheet/dist/style.css"; +import { useIsMobile } from "../../toolbar/stores/toolbar-store"; // import { store as selectionStore } from "../../stores/selectionstore"; function useMenuFocus(items, onAction, onClose) { var _a = __read(useState(-1), 2), focusIndex = _a[0], setFocusIndex = _a[1]; @@ -202,8 +205,18 @@ function MenuContainer(props) { wordWrap: "break-word", } }, { children: title }))), children] }))); } +export function PopupPresenter(props) { + var _a = props.mobile, mobile = _a === void 0 ? "menu" : _a, _b = props.desktop, desktop = _b === void 0 ? "menu" : _b, restProps = __rest(props, ["mobile", "desktop"]); + var isMobile = useIsMobile(); + if (isMobile && mobile === "sheet") + return _jsx(ActionSheetPresenter, __assign({}, restProps)); + else if (mobile === "menu" || desktop === "menu") + return _jsx(MenuPresenter, __assign({}, restProps)); + else + return props.isOpen ? _jsx(_Fragment, { children: props.children }) : null; +} export function MenuPresenter(props) { - var className = props.className, options = props.options, items = props.items, isOpen = props.isOpen, onClose = props.onClose, children = props.children, containerProps = __rest(props, ["className", "options", "items", "isOpen", "onClose", "children"]); + var className = props.className, _a = props.options, options = _a === void 0 ? { type: "menu", position: {} } : _a, _b = props.items, items = _b === void 0 ? [] : _b, isOpen = props.isOpen, _c = props.onClose, onClose = _c === void 0 ? function () { } : _c, children = props.children, containerProps = __rest(props, ["className", "options", "items", "isOpen", "onClose", "children"]); var position = options.position, type = options.type; var isAutocomplete = type === "autocomplete"; var contentRef = useRef(); @@ -255,3 +268,7 @@ export function MenuPresenter(props) { }, } }, { children: props.children ? (props.children) : (_jsx(Menu, __assign({ items: items, closeMenu: onClose }, containerProps))) }))); } +export function ActionSheetPresenter(props) { + var _a = props.items, items = _a === void 0 ? [] : _a, isOpen = props.isOpen, _b = props.onClose, onClose = _b === void 0 ? function () { } : _b, children = props.children, sx = props.sx, _c = props.blocking, blocking = _c === void 0 ? true : _c, containerProps = __rest(props, ["items", "isOpen", "onClose", "children", "sx", "blocking"]); + return (_jsx(BottomSheet, __assign({ open: isOpen, onDismiss: onClose, blocking: blocking }, { children: props.children ? (props.children) : (_jsx(Menu, __assign({ items: items, closeMenu: onClose, sx: __assign({ flex: 1, boxShadow: "none", border: "none" }, sx) }, containerProps))) }))); +} diff --git a/packages/editor/dist/components/responsive/index.d.ts b/packages/editor/dist/components/responsive/index.d.ts new file mode 100644 index 000000000..309abd87f --- /dev/null +++ b/packages/editor/dist/components/responsive/index.d.ts @@ -0,0 +1,9 @@ +import { PropsWithChildren } from "react"; +declare type ResponsiveContainerProps = { + mobile?: JSX.Element; + desktop?: JSX.Element; +}; +export declare function ResponsiveContainer(props: ResponsiveContainerProps): JSX.Element | null; +export declare function DesktopOnly(props: PropsWithChildren<{}>): JSX.Element; +export declare function MobileOnly(props: PropsWithChildren<{}>): JSX.Element; +export {}; diff --git a/packages/editor/dist/components/responsive/index.js b/packages/editor/dist/components/responsive/index.js new file mode 100644 index 000000000..7087e0f0d --- /dev/null +++ b/packages/editor/dist/components/responsive/index.js @@ -0,0 +1,15 @@ +import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; +import { useToolbarStore } from "../../toolbar/stores/toolbar-store"; +export function ResponsiveContainer(props) { + var isMobile = useToolbarStore(function (store) { return store.isMobile; }); + if (isMobile) + return props.mobile || null; + else + return props.desktop || null; +} +export function DesktopOnly(props) { + return _jsx(ResponsiveContainer, { desktop: _jsx(_Fragment, { children: props.children }) }); +} +export function MobileOnly(props) { + return _jsx(ResponsiveContainer, { mobile: _jsx(_Fragment, { children: props.children }) }); +} diff --git a/packages/editor/dist/extensions/embed/component.js b/packages/editor/dist/extensions/embed/component.js index 80a14436b..966a01580 100644 --- a/packages/editor/dist/extensions/embed/component.js +++ b/packages/editor/dist/extensions/embed/component.js @@ -32,6 +32,7 @@ import { ThemeProvider } from "emotion-theming"; import { Resizable } from "re-resizable"; import { ToolButton } from "../../toolbar/components/tool-button"; import { useEffect, useRef, useState } from "react"; +import { PopupPresenter, } from "../../components/menu/menu"; import { EmbedPopup } from "../../toolbar/popups/embed-popup"; export function EmbedComponent(props) { var _a = props.node.attrs, src = _a.src, width = _a.width, height = _a.height, align = _a.align; @@ -58,14 +59,27 @@ export function EmbedComponent(props) { width: ref.clientWidth, height: ref.clientHeight, }); - }, lockAspectRatio: true }, { children: [_jsx(Flex, __assign({ sx: { position: "relative", justifyContent: "end" } }, { children: isToolbarVisible && (_jsx(ImageToolbar, { editor: editor, align: align, height: height || 0, width: width || 0, src: src })) })), _jsx(Box, __assign({ as: "iframe", ref: embedRef, src: src, width: "100%", height: "100%", sx: { - border: isActive - ? "2px solid var(--primary)" - : "2px solid transparent", - borderRadius: "default", + }, lockAspectRatio: true }, { children: [_jsx(Flex, __assign({ width: "100%", sx: { + position: "relative", + justifyContent: "end", + borderTop: "20px solid var(--bgSecondary)", + // borderLeft: "20px solid var(--bgSecondary)", + borderTopLeftRadius: "default", + borderTopRightRadius: "default", + borderColor: isActive ? "border" : "bgSecondary", + cursor: "pointer", + ":hover": { + borderColor: "border", + }, + } }, { children: isToolbarVisible && (_jsx(EmbedToolbar, { editor: editor, align: align, height: height || 0, width: width || 0, src: src })) })), _jsx(Box, __assign({ as: "iframe", ref: embedRef, src: src, width: "100%", height: "100%", sx: { + border: "none", + // border: isActive + // ? "2px solid var(--primary)" + // : "2px solid transparent", + // borderRadius: "default", } }, props))] })) })) })) })); } -function ImageToolbar(props) { +function EmbedToolbar(props) { var editor = props.editor, height = props.height, width = props.width, src = props.src; var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; return (_jsxs(Flex, __assign({ sx: { @@ -101,5 +115,5 @@ function ImageToolbar(props) { mr: 1, borderRight: "1px solid var(--border)", ":last-of-type": { mr: 0, pr: 0, borderRight: "none" }, - } }, { children: _jsx(ToolButton, { toggled: isOpen, title: "Embed properties", id: "embedProperties", icon: "more", onClick: function () { return setIsOpen(function (s) { return !s; }); } }) }))] })), isOpen && (_jsx(EmbedPopup, { title: "Embed properties", icon: "close", onClose: function () { return setIsOpen(false); }, embed: props, onSourceChanged: function (src) { }, onSizeChanged: function (size) { return editor.commands.setEmbedSize(size); } }))] }))); + } }, { children: _jsx(ToolButton, { toggled: isOpen, title: "Embed properties", id: "embedProperties", icon: "more", onClick: function () { return setIsOpen(function (s) { return !s; }); } }) }))] })), _jsx(PopupPresenter, __assign({ isOpen: isOpen, desktop: "none", mobile: "sheet", onClose: function () { return setIsOpen(false); }, blocking: true }, { children: _jsx(EmbedPopup, { title: "Embed properties", icon: "close", onClose: function () { return setIsOpen(false); }, embed: props, onSourceChanged: function (src) { }, onSizeChanged: function (size) { return editor.commands.setEmbedSize(size); } }) }))] }))); } diff --git a/packages/editor/dist/extensions/image/component.js b/packages/editor/dist/extensions/image/component.js index ead2e82d9..cce48dcc1 100644 --- a/packages/editor/dist/extensions/image/component.js +++ b/packages/editor/dist/extensions/image/component.js @@ -26,15 +26,15 @@ var __read = (this && this.__read) || function (o, n) { return ar; }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { Box, Flex, Image, Text } from "rebass"; +import { Box, Flex, Image } from "rebass"; import { NodeViewWrapper } from "@tiptap/react"; import { ThemeProvider } from "emotion-theming"; import { Resizable } from "re-resizable"; import { ToolButton } from "../../toolbar/components/tool-button"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; +import { PopupPresenter, } from "../../components/menu/menu"; import { Popup } from "../../toolbar/components/popup"; -import { Toggle } from "../../components/toggle"; -import { Input } from "@rebass/forms"; +import { ImageProperties } from "../../toolbar/popups/image-properties"; export function ImageComponent(props) { var _a = props.node .attrs, src = _a.src, alt = _a.alt, title = _a.title, width = _a.width, height = _a.height, align = _a.align, float = _a.float; @@ -75,23 +75,6 @@ export function ImageComponent(props) { function ImageToolbar(props) { var editor = props.editor, float = props.float, height = props.height, width = props.width; var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; - var onSizeChange = useCallback(function (newWidth, newHeight) { - var size = newWidth - ? { - width: newWidth, - height: newWidth * (height / width), - } - : newHeight - ? { - width: newHeight * (width / height), - height: newHeight, - } - : { - width: 0, - height: 0, - }; - editor.chain().setImageSize(size).run(); - }, [width, height]); return (_jsxs(Flex, __assign({ sx: { flexDirection: "column", position: "absolute", @@ -125,21 +108,10 @@ function ImageToolbar(props) { mr: 1, borderRight: "1px solid var(--border)", ":last-of-type": { mr: 0, pr: 0, borderRight: "none" }, - } }, { children: _jsx(ToolButton, { toggled: isOpen, title: "Image properties", id: "imageProperties", icon: "more", onClick: function () { return setIsOpen(function (s) { return !s; }); } }) }))] })), isOpen && (_jsx(Popup, __assign({ title: "Image properties", action: { - icon: "close", - onClick: function () { - setIsOpen(false); - }, - } }, { children: _jsxs(Flex, __assign({ sx: { width: 200, flexDirection: "column", p: 1 } }, { children: [_jsxs(Flex, __assign({ sx: { justifyContent: "space-between", alignItems: "center" } }, { children: [_jsx(Text, __assign({ variant: "body" }, { children: "Floating?" })), _jsx(Toggle, { checked: float, onClick: function () { - return editor - .chain() - .setImageAlignment({ float: !float, align: "left" }) - .run(); - } })] })), _jsxs(Flex, __assign({ sx: { alignItems: "center", mt: 2 } }, { children: [_jsx(Input, { type: "number", placeholder: "Width", value: width, sx: { - mr: 2, - p: 1, - fontSize: "body", - }, onChange: function (e) { return onSizeChange(e.target.valueAsNumber); } }), _jsx(Input, { type: "number", placeholder: "Height", value: height, sx: { p: 1, fontSize: "body" }, onChange: function (e) { - return onSizeChange(undefined, e.target.valueAsNumber); - } })] }))] })) })))] }))); + } }, { children: _jsx(ToolButton, { toggled: isOpen, title: "Image properties", id: "imageProperties", icon: "more", onClick: function () { return setIsOpen(function (s) { return !s; }); } }) }))] })), _jsx(PopupPresenter, __assign({ mobile: "sheet", desktop: "none", isOpen: isOpen, onClose: function () { return setIsOpen(false); }, blocking: false }, { children: _jsx(Popup, __assign({ title: "Image properties", action: { + icon: "close", + onClick: function () { + setIsOpen(false); + }, + } }, { children: _jsx(ImageProperties, __assign({}, props)) })) }))] }))); } diff --git a/packages/editor/dist/toolbar/components/dropdown.js b/packages/editor/dist/toolbar/components/dropdown.js index 9540437d7..0e1bc9564 100644 --- a/packages/editor/dist/toolbar/components/dropdown.js +++ b/packages/editor/dist/toolbar/components/dropdown.js @@ -31,12 +31,12 @@ import { Button, Text } from "rebass"; import { Icon } from "./icon"; import { Icons } from "../icons"; import { MenuPresenter } from "../../components/menu/menu"; -import { useToolbarContext } from "../hooks/useToolbarContext"; +import { useToolbarLocation } from "../stores/toolbar-store"; export function Dropdown(props) { var items = props.items, selectedItem = props.selectedItem, buttonRef = props.buttonRef, menuWidth = props.menuWidth; var internalRef = useRef(); var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; - var toolbarLocation = useToolbarContext().toolbarLocation; + var toolbarLocation = useToolbarLocation(); return (_jsxs(_Fragment, { children: [_jsxs(Button, __assign({ ref: function (ref) { internalRef.current = ref; if (buttonRef) diff --git a/packages/editor/dist/toolbar/components/popup.js b/packages/editor/dist/toolbar/components/popup.js index 292dfbe9b..608217705 100644 --- a/packages/editor/dist/toolbar/components/popup.js +++ b/packages/editor/dist/toolbar/components/popup.js @@ -29,9 +29,9 @@ export function Popup(props) { return (_jsxs(Flex, __assign({ sx: { bg: "background", flexDirection: "column", - borderRadius: "default", - border: "1px solid var(--border)", - boxShadow: "menu", + // borderRadius: "default", + // border: "1px solid var(--border)", + // boxShadow: "menu", } }, { children: [title && (_jsxs(Flex, __assign({ sx: { justifyContent: "space-between", alignItems: "center", @@ -41,5 +41,5 @@ export function Popup(props) { } function PopupButton(props) { var text = props.text, loading = props.loading, icon = props.icon, iconColor = props.iconColor, iconSize = props.iconSize, restProps = __rest(props, ["text", "loading", "icon", "iconColor", "iconSize"]); - return (_jsx(Button, __assign({ variant: "dialog", sx: { p: 1, px: 2 } }, restProps, { children: loading ? (_jsx(Icon, { path: Icons.loading, size: 16, rotate: true, color: "primary" })) : icon ? (_jsx(Icon, { path: Icons[icon], size: iconSize || 18, color: iconColor || "icon" })) : (text) }))); + return (_jsx(Button, __assign({ variant: "icon", sx: { p: 1, px: 2 } }, restProps, { children: loading ? (_jsx(Icon, { path: Icons.loading, size: 16, rotate: true, color: "primary" })) : icon ? (_jsx(Icon, { path: Icons[icon], size: iconSize || 18, color: iconColor || "icon" })) : (text) }))); } diff --git a/packages/editor/dist/toolbar/components/splitbutton.js b/packages/editor/dist/toolbar/components/splitbutton.js index f5e204173..a86e0d92e 100644 --- a/packages/editor/dist/toolbar/components/splitbutton.js +++ b/packages/editor/dist/toolbar/components/splitbutton.js @@ -43,12 +43,12 @@ import { Icons } from "../icons"; import { Icon } from "./icon"; import { ToolButton } from "./tool-button"; import { MenuPresenter } from "../../components/menu/menu"; -import { useToolbarContext } from "../hooks/useToolbarContext"; +import { useToolbarLocation } from "../stores/toolbar-store"; export function SplitButton(props) { var menuPresenterProps = props.menuPresenterProps, children = props.children, toolButtonProps = __rest(props, ["menuPresenterProps", "children"]); var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; var ref = useRef(null); - var toolbarLocation = useToolbarContext().toolbarLocation; + var toolbarLocation = useToolbarLocation(); return (_jsxs(_Fragment, { children: [_jsxs(Flex, __assign({ ref: ref, sx: { borderRadius: "default", bg: isOpen ? "hover" : "transparent", diff --git a/packages/editor/dist/toolbar/components/toolbutton.d.ts b/packages/editor/dist/toolbar/components/toolbutton.d.ts index 824e90ced..883a3f460 100644 --- a/packages/editor/dist/toolbar/components/toolbutton.d.ts +++ b/packages/editor/dist/toolbar/components/toolbutton.d.ts @@ -8,5 +8,6 @@ export declare type ToolButtonProps = ButtonProps & { iconSize?: number; toggled: boolean; buttonRef?: React.MutableRefObject; + variant?: "small" | "normal"; }; export declare function ToolButton(props: ToolButtonProps): JSX.Element; diff --git a/packages/editor/dist/toolbar/components/toolbutton.js b/packages/editor/dist/toolbar/components/toolbutton.js index 6df824fe5..29fde82e0 100644 --- a/packages/editor/dist/toolbar/components/toolbutton.js +++ b/packages/editor/dist/toolbar/components/toolbutton.js @@ -25,8 +25,8 @@ import { Button } from "rebass"; import { Icons } from "../icons"; import { Icon } from "./icon"; export function ToolButton(props) { - var id = props.id, icon = props.icon, iconSize = props.iconSize, iconColor = props.iconColor, toggled = props.toggled, sx = props.sx, buttonRef = props.buttonRef, buttonProps = __rest(props, ["id", "icon", "iconSize", "iconColor", "toggled", "sx", "buttonRef"]); - return (_jsx(Button, __assign({ ref: buttonRef, tabIndex: -1, id: "tool-".concat(id), sx: __assign({ p: 1, m: 0, bg: toggled ? "hover" : "transparent", mr: 1, ":hover": { bg: "hover" }, ":last-of-type": { + var id = props.id, icon = props.icon, iconSize = props.iconSize, iconColor = props.iconColor, toggled = props.toggled, sx = props.sx, buttonRef = props.buttonRef, _a = props.variant, variant = _a === void 0 ? "normal" : _a, buttonProps = __rest(props, ["id", "icon", "iconSize", "iconColor", "toggled", "sx", "buttonRef", "variant"]); + return (_jsx(Button, __assign({ ref: buttonRef, tabIndex: -1, id: "tool-".concat(id), sx: __assign({ p: variant === "small" ? "3px" : 1, borderRadius: variant === "small" ? "small" : "default", m: 0, bg: toggled ? "hover" : "transparent", mr: variant === "small" ? 0 : 1, ":hover": { bg: "hover" }, ":last-of-type": { mr: 0, - } }, sx) }, buttonProps, { children: _jsx(Icon, { path: Icons[icon], color: iconColor || "text", size: iconSize || 18 }) }))); + } }, sx) }, buttonProps, { children: _jsx(Icon, { path: Icons[icon], color: iconColor || "text", size: iconSize || variant === "small" ? 16 : 18 }) }))); } diff --git a/packages/editor/dist/toolbar/floatingmenus/index.js b/packages/editor/dist/toolbar/floatingmenus/index.js index 58c6bf446..ef6fb823c 100644 --- a/packages/editor/dist/toolbar/floatingmenus/index.js +++ b/packages/editor/dist/toolbar/floatingmenus/index.js @@ -9,9 +9,10 @@ var __assign = (this && this.__assign) || function () { }; return __assign.apply(this, arguments); }; -import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; -import { TableRowFloatingMenu, TableColumnFloatingMenu } from "./table"; +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; +import { TableRowFloatingMenu, TableColumnFloatingMenu, TableFloatingMenu, } from "./table/table"; import { SearchReplaceFloatingMenu } from "./search-replace"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function EditorFloatingMenus(props) { - return (_jsxs(_Fragment, { children: [_jsx(TableRowFloatingMenu, __assign({}, props)), _jsx(TableColumnFloatingMenu, __assign({}, props)), _jsx(SearchReplaceFloatingMenu, __assign({}, props))] })); + return (_jsxs(_Fragment, { children: [_jsxs(DesktopOnly, { children: [_jsx(TableRowFloatingMenu, __assign({}, props)), _jsx(TableColumnFloatingMenu, __assign({}, props))] }), _jsx(MobileOnly, { children: _jsx(TableFloatingMenu, __assign({}, props)) }), _jsx(SearchReplaceFloatingMenu, __assign({}, props))] })); } diff --git a/packages/editor/dist/toolbar/floatingmenus/searchreplace.js b/packages/editor/dist/toolbar/floatingmenus/searchreplace.js index 0f6f03e65..87ab7dfe7 100644 --- a/packages/editor/dist/toolbar/floatingmenus/searchreplace.js +++ b/packages/editor/dist/toolbar/floatingmenus/searchreplace.js @@ -9,88 +9,21 @@ var __assign = (this && this.__assign) || function () { }; return __assign.apply(this, arguments); }; -var __read = (this && this.__read) || function (o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -}; -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { Input } from "@rebass/forms"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { Flex } from "rebass"; -import { MenuPresenter } from "../../components/menu/menu"; -import { Popup } from "../components/popup"; -import { ToolButton } from "../components/tool-button"; +import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; +import { PopupPresenter, } from "../../components/menu/menu"; +import { SearchReplacePopup } from "../popups/search-replace"; export function SearchReplaceFloatingMenu(props) { var editor = props.editor; - var _a = editor.storage - .searchreplace, isSearching = _a.isSearching, selectedText = _a.selectedText; - var _b = __read(useState(false), 2), matchCase = _b[0], setMatchCase = _b[1]; - var _c = __read(useState(false), 2), matchWholeWord = _c[0], setMatchWholeWord = _c[1]; - var _d = __read(useState(false), 2), enableRegex = _d[0], setEnableRegex = _d[1]; - var replaceText = useRef(""); - var searchInputRef = useRef(); - var search = useCallback(function (term) { - editor.commands.search(term, { - matchCase: matchCase, - enableRegex: enableRegex, - matchWholeWord: matchWholeWord, - }); - }, [matchCase, enableRegex, matchWholeWord]); - useEffect(function () { - if (!searchInputRef.current) - return; - search(searchInputRef.current.value); - }, [search, matchCase, matchWholeWord, enableRegex]); - useEffect(function () { - if (isSearching && selectedText) { - if (searchInputRef.current) { - var input_1 = searchInputRef.current; - setTimeout(function () { - input_1.value = selectedText; - input_1.focus(); - }, 0); - } - search(selectedText); - } - }, [isSearching, selectedText, search]); + var isSearching = editor.storage.searchreplace.isSearching; if (!isSearching) return null; - return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { - type: "autocomplete", - position: { - target: document.querySelector(".editor-toolbar") || "mouse", - isTargetAbsolute: true, - location: "below", - align: "end", - }, - } }, { children: _jsx(Popup, { children: _jsxs(Flex, __assign({ sx: { p: 1, flexDirection: "column" } }, { children: [_jsxs(Flex, __assign({ sx: { alignItems: "start", flexShrink: 0 } }, { children: [_jsxs(Flex, __assign({ sx: { - position: "relative", - mr: 1, - width: 200, - alignItems: "center", - } }, { children: [_jsx(Input, { defaultValue: selectedText, ref: searchInputRef, autoFocus: true, sx: { p: 1 }, placeholder: "Find", onChange: function (e) { - search(e.target.value); - } }), _jsxs(Flex, __assign({ sx: { - position: "absolute", - right: 0, - mr: 0, - } }, { children: [_jsx(ToolButton, { sx: { - mr: 0, - }, toggled: matchCase, title: "Match case", id: "matchCase", icon: "caseSensitive", onClick: function () { return setMatchCase(function (s) { return !s; }); }, iconSize: 14 }), _jsx(ToolButton, { sx: { - mr: 0, - }, toggled: matchWholeWord, title: "Match whole word", id: "matchWholeWord", icon: "wholeWord", onClick: function () { return setMatchWholeWord(function (s) { return !s; }); }, iconSize: 14 }), _jsx(ToolButton, { sx: { - mr: 0, - }, toggled: enableRegex, title: "Enable regex", id: "enableRegex", icon: "regex", onClick: function () { return setEnableRegex(function (s) { return !s; }); }, iconSize: 14 })] }))] })), _jsx(ToolButton, { toggled: false, title: "Previous match", id: "previousMatch", icon: "previousMatch", onClick: function () { return editor.commands.moveToPreviousResult(); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Next match", id: "nextMatch", icon: "nextMatch", onClick: function () { return editor.commands.moveToNextResult(); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Close", id: "close", icon: "close", onClick: function () { return editor.chain().focus().endSearch().run(); }, iconSize: 16, sx: { mr: 0 } })] })), _jsxs(Flex, __assign({ sx: { alignItems: "start", flexShrink: 0, mt: 1 } }, { children: [_jsx(Input, { sx: { p: 1, width: 200, mr: 1 }, placeholder: "Replace", onChange: function (e) { return (replaceText.current = e.target.value); } }), _jsx(ToolButton, { toggled: false, title: "Replace", id: "replace", icon: "replaceOne", onClick: function () { return editor.commands.replace(replaceText.current); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Replace all", id: "replaceAll", icon: "replaceAll", onClick: function () { return editor.commands.replaceAll(replaceText.current); }, sx: { mr: 0 }, iconSize: 16 })] }))] })) }) }))); + return (_jsx(_Fragment, { children: _jsx(PopupPresenter, __assign({ mobile: "sheet", desktop: "menu", isOpen: true, onClose: function () { return editor.commands.endSearch(); }, options: { + type: "autocomplete", + position: { + target: document.querySelector(".editor-toolbar") || "mouse", + isTargetAbsolute: true, + location: "below", + align: "end", + }, + } }, { children: _jsx(SearchReplacePopup, { editor: editor }) })) })); } diff --git a/packages/editor/dist/toolbar/floatingmenus/table.js b/packages/editor/dist/toolbar/floatingmenus/table.js index c8d886f5a..f6f4fd0a2 100644 --- a/packages/editor/dist/toolbar/floatingmenus/table.js +++ b/packages/editor/dist/toolbar/floatingmenus/table.js @@ -45,6 +45,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) { if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; @@ -74,15 +85,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run import { Slider } from "@rebass/forms"; import { useEffect, useRef, useState } from "react"; import { Flex, Text } from "rebass"; -import { MenuPresenter } from "../../components/menu/menu"; +import { ActionSheetPresenter, MenuPresenter, } from "../../components/menu/menu"; import { Popup } from "../components/popup"; import { ToolButton } from "../components/tool-button"; import { selectedRect } from "prosemirror-tables"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function TableRowFloatingMenu(props) { var editor = props.editor; - var theme = editor.storage.theme; + // const theme = editor.storage.theme as Theme; var _a = __read(useState(null), 2), position = _a[0], setPosition = _a[1]; - var _b = __read(useState(false), 2), isMenuOpen = _b[0], setIsMenuOpen = _b[1]; useEffect(function () { var _a; if (!editor.isActive("tableCell") && @@ -111,18 +122,23 @@ export function TableRowFloatingMenu(props) { }, [editor.state.selection]); if (!position) return null; - return (_jsxs(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { + return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { type: "autocomplete", position: position, - } }, { children: [_jsxs(Flex, __assign({ sx: { - bg: "background", - flexWrap: "nowrap", - borderRadius: "default", - opacity: isMenuOpen ? 1 : 0.3, - ":hover": { - opacity: 1, - }, - } }, { children: [_jsx(ToolButton, { toggled: isMenuOpen, title: "Row properties", id: "properties", icon: "more", onClick: function () { return setIsMenuOpen(true); }, iconSize: 16, sx: { mr: 0, p: "3px", borderRadius: "small" } }), _jsx(ToolButton, { toggled: false, title: "Insert row below", id: "insertRowBelow", icon: "insertRowBelow", onClick: function () { return editor.chain().focus().addRowAfter().run(); }, sx: { mr: 0, p: "3px", borderRadius: "small" }, iconSize: 16 })] })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () { + } }, { children: _jsxs(Flex, __assign({ sx: { + bg: "background", + flexWrap: "nowrap", + borderRadius: "default", + // opacity: isMenuOpen ? 1 : 0.3, + ":hover": { + opacity: 1, + }, + } }, { children: [_jsx(RowProperties, { title: "Row properties", editor: editor, variant: "small", icon: "more" }), _jsx(InsertRowBelow, { title: "Insert row below", icon: "insertRowBelow", editor: editor, variant: "small" })] })) }))); +} +function RowProperties(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + var _a = __read(useState(false), 2), isMenuOpen = _a[0], setIsMenuOpen = _a[1]; + return (_jsxs(_Fragment, { children: [_jsx(ToolButton, __assign({ toggled: isMenuOpen }, toolProps, { onClick: function () { return setIsMenuOpen(true); } })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () { setIsMenuOpen(false); editor.commands.focus(); }, options: { @@ -157,47 +173,20 @@ export function TableRowFloatingMenu(props) { onClick: function () { return editor.chain().focus().deleteRow().run(); }, icon: "deleteRow", }, - ] })] }))); + ] })] })); } -export function TableColumnFloatingMenu(props) { +function InsertRowBelow(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + return (_jsx(ToolButton, __assign({ toggled: false }, toolProps, { onClick: function () { return editor.chain().focus().addRowAfter().run(); } }))); +} +function ColumnProperties(props) { var _this = this; - var editor = props.editor; - var _a = __read(useState(null), 2), position = _a[0], setPosition = _a[1]; + var editor = props.editor, currentCell = props.currentCell, toolProps = __rest(props, ["editor", "currentCell"]); + var _a = __read(useState(false), 2), isMenuOpen = _a[0], setIsMenuOpen = _a[1]; var isInsideCellSelection = !editor.state.selection.empty && editor.state.selection.$anchor.node().type.name === "tableCell"; - var _b = __read(useState(false), 2), isMenuOpen = _b[0], setIsMenuOpen = _b[1]; - var _c = __read(useState(false), 2), showCellProps = _c[0], setShowCellProps = _c[1]; - var _d = __read(useState(null), 2), menuPosition = _d[0], setMenuPosition = _d[1]; - useEffect(function () { - var _a; - if (!editor.isActive("tableCell") && - !editor.isActive("tableRow") && - !editor.isActive("tableHeader")) { - setPosition(null); - return; - } - var $from = editor.state.selection.$from; - var selectedNode = $from.node(); - var pos = selectedNode.isTextblock ? $from.before() : $from.pos; - var currentCell = (_a = editor.view.nodeDOM(pos)) === null || _a === void 0 ? void 0 : _a.closest("td,th"); - var currentTable = currentCell === null || currentCell === void 0 ? void 0 : currentCell.closest("table"); - if (!currentCell || !currentTable) - return; - setPosition(function (old) { - if ((old === null || old === void 0 ? void 0 : old.target) === currentCell) - return old; - return { - isTargetAbsolute: true, - location: "top", - align: "center", - yAnchor: currentTable, - yOffset: -2, - target: currentCell, - }; - }); - }, [editor.state.selection]); - if (!position) - return null; + var _b = __read(useState(false), 2), showCellProps = _b[0], setShowCellProps = _b[1]; + var _c = __read(useState(null), 2), menuPosition = _c[0], setMenuPosition = _c[1]; var columnProperties = [ { key: "addColumnLeft", @@ -259,7 +248,7 @@ export function TableColumnFloatingMenu(props) { onClick: function () { setShowCellProps(true); setMenuPosition({ - target: position.target || undefined, + target: currentCell || undefined, isTargetAbsolute: true, yOffset: 10, location: "below", @@ -277,20 +266,9 @@ export function TableColumnFloatingMenu(props) { onClick: function () { return editor.chain().focus().deleteTable().run(); }, }, ]; - return (_jsxs(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { - type: "autocomplete", - position: position, - } }, { children: [_jsxs(Flex, __assign({ sx: { - bg: "background", - flexWrap: "nowrap", - borderRadius: "default", - opacity: isMenuOpen || showCellProps ? 1 : 0.3, - ":hover": { - opacity: 1, - }, - } }, { children: [_jsx(ToolButton, { toggled: isMenuOpen, title: "Column properties", id: "properties", icon: "more", onClick: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { - return [2 /*return*/, setIsMenuOpen(true)]; - }); }); }, iconSize: 16, sx: { mr: 0, p: "3px", borderRadius: "small" } }), _jsx(ToolButton, { toggled: false, title: "Insert column right", id: "insertColumnRight", icon: "plus", onClick: function () { return editor.chain().focus().addColumnAfter().run(); }, sx: { mr: 0, p: "3px", borderRadius: "small" }, iconSize: 16 })] })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () { + return (_jsxs(_Fragment, { children: [_jsx(ToolButton, __assign({ toggled: isMenuOpen }, toolProps, { onClick: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { + return [2 /*return*/, setIsMenuOpen(true)]; + }); }); } })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () { setIsMenuOpen(false); editor.commands.focus(); }, options: { @@ -301,13 +279,67 @@ export function TableColumnFloatingMenu(props) { { type: "seperator", key: "cellSeperator" } ], false), __read(cellProperties), false), [ { type: "seperator", key: "tableSeperator" } - ], false), __read(tableProperties), false) }), _jsx(MenuPresenter, __assign({ isOpen: showCellProps, onClose: function () { - setShowCellProps(false); - editor.commands.focus(); - }, options: { - type: "menu", - position: menuPosition || {}, - }, items: [] }, { children: _jsx(CellProperties, { editor: editor, onClose: function () { return setShowCellProps(false); } }) }))] }))); + ], false), __read(tableProperties), false) }), _jsx(DesktopOnly, { children: _jsx(MenuPresenter, __assign({ isOpen: showCellProps, onClose: function () { + setShowCellProps(false); + editor.commands.focus(); + }, options: { + type: "menu", + position: menuPosition || {}, + }, items: [] }, { children: _jsx(CellProperties, { editor: editor, onClose: function () { return setShowCellProps(false); } }) })) }), _jsx(MobileOnly, { children: _jsx(ActionSheetPresenter, __assign({ isOpen: showCellProps, onClose: function () { + setShowCellProps(false); + editor.commands.focus(); + }, items: [] }, { children: _jsx(CellProperties, { editor: editor, onClose: function () { return setShowCellProps(false); } }) })) })] })); +} +function InsertColumnRight(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + return (_jsx(ToolButton, __assign({}, toolProps, { toggled: false, onClick: function () { return editor.chain().focus().addColumnAfter().run(); } }))); +} +export function TableColumnFloatingMenu(props) { + var editor = props.editor; + var _a = __read(useState(null), 2), position = _a[0], setPosition = _a[1]; + useEffect(function () { + var _a; + if (!editor.isActive("tableCell") && + !editor.isActive("tableRow") && + !editor.isActive("tableHeader")) { + setPosition(null); + return; + } + var $from = editor.state.selection.$from; + var selectedNode = $from.node(); + var pos = selectedNode.isTextblock ? $from.before() : $from.pos; + var currentCell = (_a = editor.view.nodeDOM(pos)) === null || _a === void 0 ? void 0 : _a.closest("td,th"); + var currentTable = currentCell === null || currentCell === void 0 ? void 0 : currentCell.closest("table"); + if (!currentCell || !currentTable) + return; + setPosition(function (old) { + if ((old === null || old === void 0 ? void 0 : old.target) === currentCell) + return old; + return { + isTargetAbsolute: true, + location: "top", + align: "center", + yAnchor: currentTable, + yOffset: 2, + target: currentCell, + }; + }); + }, [editor.state.selection]); + if (!position) + return null; + return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { + type: "autocomplete", + position: position, + } }, { children: _jsxs(Flex, __assign({ sx: { + bg: "background", + flexWrap: "nowrap", + borderRadius: "default", + // opacity: 0.3, + // opacity: isMenuOpen || showCellProps ? 1 : 0.3, + ":hover": { + opacity: 1, + }, + } }, { children: [_jsx(ColumnProperties, { currentCell: position.target, title: "Column properties", editor: editor, icon: "more", variant: "small" }), _jsx(InsertColumnRight, { editor: editor, title: "Insert column right", variant: "small", icon: "plus" })] })) }))); } function CellProperties(props) { var editor = props.editor, onClose = props.onClose; @@ -317,7 +349,7 @@ function CellProperties(props) { icon: "close", iconColor: "error", onClick: onClose, - } }, { children: _jsxs(Flex, __assign({ sx: { flexDirection: "column", width: 200, px: 1, mb: 2 } }, { children: [_jsx(ColorPickerTool, { color: attributes.backgroundColor, title: "Background color", icon: "backgroundColor", onColorChange: function (color) { + } }, { children: _jsxs(Flex, __assign({ sx: { flexDirection: "column", px: 1, mb: 2 } }, { children: [_jsx(ColorPickerTool, { color: attributes.backgroundColor, title: "Background color", icon: "backgroundColor", onColorChange: function (color) { return editor.commands.setCellAttribute("backgroundColor", color); } }), _jsx(ColorPickerTool, { color: attributes.color, title: "Text color", icon: "textColor", onColorChange: function (color) { return editor.commands.setCellAttribute("color", color); diff --git a/packages/editor/dist/toolbar/floatingmenus/table/actions.d.ts b/packages/editor/dist/toolbar/floatingmenus/table/actions.d.ts new file mode 100644 index 000000000..c4a829775 --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/actions.d.ts @@ -0,0 +1,6 @@ +import { Editor } from "@tiptap/core"; +declare function moveColumnRight(editor: Editor): void; +declare function moveColumnLeft(editor: Editor): void; +declare function moveRowDown(editor: Editor): void; +declare function moveRowUp(editor: Editor): void; +export { moveColumnLeft, moveColumnRight, moveRowDown, moveRowUp }; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/actions.js b/packages/editor/dist/toolbar/floatingmenus/table/actions.js new file mode 100644 index 000000000..2774ab1fe --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/actions.js @@ -0,0 +1,108 @@ +import { selectedRect } from "prosemirror-tables"; +function moveColumnRight(editor) { + var tr = editor.state.tr; + var rect = selectedRect(editor.state); + if (rect.right === rect.map.width) + return; + var transaction = moveColumn(tr, rect, rect.left, rect.left + 1); + if (!transaction) + return; + editor.view.dispatch(transaction); +} +function moveColumnLeft(editor) { + var tr = editor.state.tr; + var rect = selectedRect(editor.state); + if (rect.left === 0) + return; + var transaction = moveColumn(tr, rect, rect.left, rect.left - 1); + if (!transaction) + return; + editor.view.dispatch(transaction); +} +function moveRowDown(editor) { + var tr = editor.state.tr; + var rect = selectedRect(editor.state); + if (rect.top + 1 === rect.map.height) + return; + var transaction = moveRow(tr, rect, rect.top, rect.top + 1); + if (!transaction) + return; + editor.view.dispatch(transaction); +} +function moveRowUp(editor) { + var tr = editor.state.tr; + var rect = selectedRect(editor.state); + if (rect.top === 0) + return; + var transaction = moveRow(tr, rect, rect.top, rect.top - 1); + if (!transaction) + return; + editor.view.dispatch(transaction); +} +function moveColumn(tr, rect, from, to) { + var fromCells = getColumnCells(rect, from); + var toCells = getColumnCells(rect, to); + return moveCells(tr, rect, fromCells, toCells); +} +function getColumnCells(_a, col) { + var map = _a.map, table = _a.table; + var cells = []; + for (var row = 0; row < map.height;) { + var index = row * map.width + col; + if (index >= map.map.length) + break; + var pos = map.map[index]; + var cell = table.nodeAt(pos); + if (!cell) + continue; + cells.push({ cell: cell, pos: pos }); + row += cell.attrs.rowspan; + console.log(cell.textContent); + } + return cells; +} +function moveRow(tr, rect, from, to) { + var fromCells = getRowCells(rect, from); + var toCells = getRowCells(rect, to); + return moveCells(tr, rect, fromCells, toCells); +} +function getRowCells(_a, row) { + var map = _a.map, table = _a.table; + var cells = []; + for (var col = 0, index = row * map.width; col < map.width; col++, index++) { + if (index >= map.map.length) + break; + var pos = map.map[index]; + var cell = table.nodeAt(pos); + if (!cell) + continue; + cells.push({ cell: cell, pos: pos }); + col += cell.attrs.colspan - 1; + } + return cells; +} +function moveCells(tr, rect, fromCells, toCells) { + if (fromCells.length !== toCells.length) + return; + var mapStart = tr.mapping.maps.length; + for (var i = 0; i < toCells.length; ++i) { + var fromCell = fromCells[i]; + var toCell = toCells[i]; + var fromStart = tr.mapping + .slice(mapStart) + .map(rect.tableStart + fromCell.pos); + var fromEnd = fromStart + fromCell.cell.nodeSize; + var fromSlice = tr.doc.slice(fromStart, fromEnd); + var toStart = tr.mapping + .slice(mapStart) + .map(rect.tableStart + toCell.pos); + var toEnd = toStart + toCell.cell.nodeSize; + var toSlice = tr.doc.slice(toStart, toEnd); + tr.replace(toStart, toEnd, fromSlice); + fromStart = tr.mapping.slice(mapStart).map(rect.tableStart + fromCell.pos); + fromEnd = fromStart + fromCell.cell.nodeSize; + tr.replace(fromStart, fromEnd, toSlice); + } + return tr; +} +export { moveColumnLeft, moveColumnRight, moveRowDown, moveRowUp }; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/index.d.ts b/packages/editor/dist/toolbar/floatingmenus/table/index.d.ts new file mode 100644 index 000000000..1d5440135 --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/index.d.ts @@ -0,0 +1 @@ +export { TableColumnFloatingMenu, TableFloatingMenu, TableRowFloatingMenu, } from "./table"; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/index.js b/packages/editor/dist/toolbar/floatingmenus/table/index.js new file mode 100644 index 000000000..1d5440135 --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/index.js @@ -0,0 +1 @@ +export { TableColumnFloatingMenu, TableFloatingMenu, TableRowFloatingMenu, } from "./table"; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/table.d.ts b/packages/editor/dist/toolbar/floatingmenus/table/table.d.ts new file mode 100644 index 000000000..2ac8450ec --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/table.d.ts @@ -0,0 +1,5 @@ +/// +import { FloatingMenuProps } from "../types"; +export declare function TableRowFloatingMenu(props: FloatingMenuProps): JSX.Element | null; +export declare function TableColumnFloatingMenu(props: FloatingMenuProps): JSX.Element | null; +export declare function TableFloatingMenu(props: FloatingMenuProps): JSX.Element | null; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/table.js b/packages/editor/dist/toolbar/floatingmenus/table/table.js new file mode 100644 index 000000000..03695e410 --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/table.js @@ -0,0 +1,149 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __read = (this && this.__read) || function (o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +}; +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { useEffect, useState } from "react"; +import { Flex } from "rebass"; +import { MenuPresenter } from "../../../components/menu/menu"; +import { ColumnProperties, InsertColumnRight, InsertRowBelow, RowProperties, } from "./tools"; +import { getToolbarElement } from "../../utils/dom"; +import { useToolbarLocation } from "../../stores/toolbar-store"; +export function TableRowFloatingMenu(props) { + var editor = props.editor; + // const theme = editor.storage.theme as Theme; + var _a = __read(useState(null), 2), position = _a[0], setPosition = _a[1]; + useEffect(function () { + var _a; + if (!editor.isActive("tableCell") && + !editor.isActive("tableRow") && + !editor.isActive("tableHeader")) { + setPosition(null); + return; + } + var $from = editor.state.selection.$from; + var selectedNode = $from.node(); + var pos = selectedNode.isTextblock ? $from.before() : $from.pos; + var currentRow = (_a = editor.view.nodeDOM(pos)) === null || _a === void 0 ? void 0 : _a.closest("tr"); + if (!currentRow) + return; + setPosition(function (old) { + if ((old === null || old === void 0 ? void 0 : old.target) === currentRow) + return old; + return { + isTargetAbsolute: true, + location: "left", + xOffset: -5, + target: currentRow, + // parent: editor.view.dom as HTMLElement, + }; + }); + }, [editor.state.selection]); + if (!position) + return null; + return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { + type: "autocomplete", + position: position, + } }, { children: _jsxs(Flex, __assign({ sx: { + bg: "background", + flexWrap: "nowrap", + borderRadius: "default", + // opacity: isMenuOpen ? 1 : 0.3, + ":hover": { + opacity: 1, + }, + } }, { children: [_jsx(RowProperties, { title: "Row properties", editor: editor, variant: "small", icon: "more" }), _jsx(InsertRowBelow, { title: "Insert row below", icon: "insertRowBelow", editor: editor, variant: "small" })] })) }))); +} +export function TableColumnFloatingMenu(props) { + var editor = props.editor; + var _a = __read(useState(null), 2), position = _a[0], setPosition = _a[1]; + useEffect(function () { + var _a; + if (!editor.isActive("tableCell") && + !editor.isActive("tableRow") && + !editor.isActive("tableHeader")) { + setPosition(null); + return; + } + var $from = editor.state.selection.$from; + var selectedNode = $from.node(); + var pos = selectedNode.isTextblock ? $from.before() : $from.pos; + var currentCell = (_a = editor.view.nodeDOM(pos)) === null || _a === void 0 ? void 0 : _a.closest("td,th"); + var currentTable = currentCell === null || currentCell === void 0 ? void 0 : currentCell.closest("table"); + if (!currentCell || !currentTable) + return; + setPosition(function (old) { + if ((old === null || old === void 0 ? void 0 : old.target) === currentCell) + return old; + return { + isTargetAbsolute: true, + location: "top", + align: "center", + yAnchor: currentTable, + yOffset: 2, + target: currentCell, + }; + }); + }, [editor.state.selection]); + if (!position) + return null; + return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { + type: "autocomplete", + position: position, + } }, { children: _jsxs(Flex, __assign({ sx: { + bg: "background", + flexWrap: "nowrap", + borderRadius: "default", + // opacity: 0.3, + // opacity: isMenuOpen || showCellProps ? 1 : 0.3, + ":hover": { + opacity: 1, + }, + } }, { children: [_jsx(ColumnProperties, { currentCell: position.target, title: "Column properties", editor: editor, icon: "more", variant: "small" }), _jsx(InsertColumnRight, { editor: editor, title: "Insert column right", variant: "small", icon: "plus" })] })) }))); +} +export function TableFloatingMenu(props) { + var editor = props.editor; + var toolbarLocation = useToolbarLocation(); + if (!editor.isActive("table")) + return null; + return (_jsx(MenuPresenter, __assign({ isOpen: true, items: [], onClose: function () { }, options: { + type: "autocomplete", + position: { + isTargetAbsolute: true, + target: getToolbarElement(), + location: toolbarLocation === "bottom" ? "top" : "below", + }, + } }, { children: _jsxs(Flex, __assign({ sx: { + bg: "background", + flexWrap: "nowrap", + borderRadius: "default", + // opacity: 0.3, + // opacity: isMenuOpen || showCellProps ? 1 : 0.3, + ":hover": { + opacity: 1, + }, + } }, { children: [_jsx(RowProperties, { title: "Row properties", editor: editor, variant: "normal", icon: "rowProperties" }), _jsx(InsertRowBelow, { title: "Insert row below", icon: "insertRowBelow", editor: editor, variant: "normal" }), _jsx(ColumnProperties, { title: "Column properties", editor: editor, icon: "columnProperties", variant: "normal" }), _jsx(InsertColumnRight, { editor: editor, title: "Insert column right", variant: "normal", icon: "insertColumnRight" })] })) }))); +} diff --git a/packages/editor/dist/toolbar/floatingmenus/table/tools.d.ts b/packages/editor/dist/toolbar/floatingmenus/table/tools.d.ts new file mode 100644 index 000000000..ff7bd737e --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/tools.d.ts @@ -0,0 +1,14 @@ +/// +import { ToolButtonProps } from "../../components/tool-button"; +import { ToolProps } from "../../types"; +declare type TableToolProps = ToolProps & { + variant: ToolButtonProps["variant"]; +}; +export declare function RowProperties(props: TableToolProps): JSX.Element; +export declare function InsertRowBelow(props: TableToolProps): JSX.Element; +declare type ColumnPropertiesProps = TableToolProps & { + currentCell?: HTMLElement; +}; +export declare function ColumnProperties(props: ColumnPropertiesProps): JSX.Element; +export declare function InsertColumnRight(props: TableToolProps): JSX.Element; +export {}; diff --git a/packages/editor/dist/toolbar/floatingmenus/table/tools.js b/packages/editor/dist/toolbar/floatingmenus/table/tools.js new file mode 100644 index 000000000..5510ac2e0 --- /dev/null +++ b/packages/editor/dist/toolbar/floatingmenus/table/tools.js @@ -0,0 +1,242 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __read = (this && this.__read) || function (o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState } from "react"; +import { MenuPresenter, PopupPresenter, } from "../../../components/menu/menu"; +import { ToolButton } from "../../components/tool-button"; +import { CellProperties } from "../../popups/cell-properties"; +import { moveColumnLeft, moveColumnRight, moveRowDown, moveRowUp, } from "./actions"; +export function RowProperties(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + var _a = __read(useState(false), 2), isMenuOpen = _a[0], setIsMenuOpen = _a[1]; + return (_jsxs(_Fragment, { children: [_jsx(ToolButton, __assign({ toggled: isMenuOpen }, toolProps, { onClick: function () { return setIsMenuOpen(true); } })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () { + setIsMenuOpen(false); + editor.commands.focus(); + }, options: { + type: "menu", + position: {}, + }, items: [ + { + key: "addRowAbove", + type: "menuitem", + title: "Add row above", + onClick: function () { return editor.chain().focus().addRowBefore().run(); }, + icon: "insertRowAbove", + }, + { + key: "moveRowUp", + type: "menuitem", + title: "Move row up", + onClick: function () { return moveRowUp(editor); }, + icon: "moveRowUp", + }, + { + key: "moveRowDown", + type: "menuitem", + title: "Move row down", + onClick: function () { return moveRowDown(editor); }, + icon: "moveRowDown", + }, + { + key: "deleteRow", + type: "menuitem", + title: "Delete row", + onClick: function () { return editor.chain().focus().deleteRow().run(); }, + icon: "deleteRow", + }, + ] })] })); +} +export function InsertRowBelow(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + return (_jsx(ToolButton, __assign({ toggled: false }, toolProps, { onClick: function () { return editor.chain().focus().addRowAfter().run(); } }))); +} +export function ColumnProperties(props) { + var _this = this; + var editor = props.editor, currentCell = props.currentCell, toolProps = __rest(props, ["editor", "currentCell"]); + var _a = __read(useState(false), 2), isMenuOpen = _a[0], setIsMenuOpen = _a[1]; + var isInsideCellSelection = !editor.state.selection.empty && + editor.state.selection.$anchor.node().type.name === "tableCell"; + var _b = __read(useState(false), 2), showCellProps = _b[0], setShowCellProps = _b[1]; + var _c = __read(useState(null), 2), menuPosition = _c[0], setMenuPosition = _c[1]; + var columnProperties = [ + { + key: "addColumnLeft", + type: "menuitem", + title: "Add column left", + onClick: function () { return editor.chain().focus().addColumnBefore().run(); }, + icon: "insertColumnLeft", + }, + { + key: "addColumnRight", + type: "menuitem", + title: "Add column right", + onClick: function () { return editor.chain().focus().addColumnAfter().run(); }, + icon: "insertColumnRight", + }, + { + key: "moveColumnLeft", + type: "menuitem", + title: "Move column left", + onClick: function () { return moveColumnLeft(editor); }, + icon: "moveColumnLeft", + }, + { + key: "moveColumnRight", + type: "menuitem", + title: "Move column right", + onClick: function () { return moveColumnRight(editor); }, + icon: "moveColumnRight", + }, + { + key: "deleteColumn", + type: "menuitem", + title: "Delete column", + onClick: function () { return editor.chain().focus().deleteColumn().run(); }, + icon: "deleteColumn", + }, + ]; + var mergeSplitProperties = [ + { + key: "splitCells", + type: "menuitem", + title: "Split cells", + onClick: function () { return editor.chain().focus().splitCell().run(); }, + icon: "splitCells", + }, + { + key: "mergeCells", + type: "menuitem", + title: "Merge cells", + onClick: function () { return editor.chain().focus().mergeCells().run(); }, + icon: "mergeCells", + }, + ]; + var cellProperties = [ + { + key: "cellProperties", + type: "menuitem", + title: "Cell properties", + onClick: function () { + setShowCellProps(true); + setMenuPosition({ + target: currentCell || undefined, + isTargetAbsolute: true, + yOffset: 10, + location: "below", + }); + }, + icon: "cellProperties", + }, + ]; + var tableProperties = [ + { + key: "deleteTable", + type: "menuitem", + title: "Delete table", + icon: "deleteTable", + onClick: function () { return editor.chain().focus().deleteTable().run(); }, + }, + ]; + return (_jsxs(_Fragment, { children: [_jsx(ToolButton, __assign({ toggled: isMenuOpen }, toolProps, { onClick: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { + return [2 /*return*/, setIsMenuOpen(true)]; + }); }); } })), _jsx(PopupPresenter, { isOpen: isMenuOpen, onClose: function () { + setIsMenuOpen(false); + editor.commands.focus(); + }, mobile: "sheet", items: isInsideCellSelection + ? __spreadArray(__spreadArray([], __read(mergeSplitProperties), false), __read(cellProperties), false) : __spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], __read(columnProperties), false), [ + { type: "seperator", key: "cellSeperator" } + ], false), __read(cellProperties), false), [ + { type: "seperator", key: "tableSeperator" } + ], false), __read(tableProperties), false) }), _jsx(PopupPresenter, __assign({ isOpen: showCellProps, onClose: function () { + setShowCellProps(false); + editor.commands.focus(); + }, options: { + type: "menu", + position: menuPosition || {}, + }, mobile: "sheet" }, { children: _jsx(CellProperties, { editor: editor, onClose: function () { return setShowCellProps(false); } }) }))] })); +} +export function InsertColumnRight(props) { + var editor = props.editor, toolProps = __rest(props, ["editor"]); + return (_jsx(ToolButton, __assign({}, toolProps, { toggled: false, onClick: function () { return editor.chain().focus().addColumnAfter().run(); } }))); +} diff --git a/packages/editor/dist/toolbar/hooks/useToolbarContext.d.ts b/packages/editor/dist/toolbar/hooks/useToolbarContext.d.ts index 1900e527e..3ab7ab463 100644 --- a/packages/editor/dist/toolbar/hooks/useToolbarContext.d.ts +++ b/packages/editor/dist/toolbar/hooks/useToolbarContext.d.ts @@ -1,12 +1,9 @@ import React from "react"; -export declare type ToolbarLocation = "top" | "bottom"; export declare const ToolbarContext: React.Context<{ currentPopup?: string | undefined; setCurrentPopup?: React.Dispatch> | undefined; - toolbarLocation?: ToolbarLocation | undefined; }>; export declare function useToolbarContext(): { currentPopup?: string | undefined; setCurrentPopup?: React.Dispatch> | undefined; - toolbarLocation?: ToolbarLocation | undefined; }; diff --git a/packages/editor/dist/toolbar/icons.d.ts b/packages/editor/dist/toolbar/icons.d.ts index d22fd89bc..ccaed4da3 100644 --- a/packages/editor/dist/toolbar/icons.d.ts +++ b/packages/editor/dist/toolbar/icons.d.ts @@ -31,12 +31,14 @@ export declare const Icons: { upload: string; attachment: string; table: string; + rowProperties: string; insertRowBelow: string; insertRowAbove: string; moveRowDown: string; moveRowUp: string; deleteRow: string; toggleHeaderRow: string; + columnProperties: string; insertColumnRight: string; insertColumnLeft: string; moveColumnRight: string; diff --git a/packages/editor/dist/toolbar/icons.js b/packages/editor/dist/toolbar/icons.js index 7bd0a850d..eeb1b52e1 100644 --- a/packages/editor/dist/toolbar/icons.js +++ b/packages/editor/dist/toolbar/icons.js @@ -1,4 +1,4 @@ -import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, } from "@mdi/js"; +import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusAfter, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, mdiTableColumnWidth, mdiTableRowHeight, } from "@mdi/js"; export var Icons = { bold: mdiFormatBold, italic: mdiFormatItalic, @@ -32,12 +32,14 @@ export var Icons = { upload: mdiUploadOutline, attachment: mdiAttachment, table: mdiTable, - insertRowBelow: mdiPlus, + rowProperties: mdiTableRowHeight, + insertRowBelow: mdiTableRowPlusAfter, insertRowAbove: mdiTableRowPlusBefore, moveRowDown: mdiArrowExpandDown, moveRowUp: mdiArrowExpandUp, deleteRow: mdiTableRowRemove, toggleHeaderRow: mdiTableBorder, + columnProperties: mdiTableColumnWidth, insertColumnRight: mdiTableColumnPlusAfter, insertColumnLeft: mdiTableColumnPlusBefore, moveColumnRight: mdiArrowExpandRight, diff --git a/packages/editor/dist/toolbar/popups/cellproperties.d.ts b/packages/editor/dist/toolbar/popups/cellproperties.d.ts new file mode 100644 index 000000000..d116c136d --- /dev/null +++ b/packages/editor/dist/toolbar/popups/cellproperties.d.ts @@ -0,0 +1,8 @@ +/// +import { Editor } from "@tiptap/core"; +declare type CellPropertiesProps = { + editor: Editor; + onClose: () => void; +}; +export declare function CellProperties(props: CellPropertiesProps): JSX.Element; +export {}; diff --git a/packages/editor/dist/toolbar/popups/cellproperties.js b/packages/editor/dist/toolbar/popups/cellproperties.js new file mode 100644 index 000000000..ee117455f --- /dev/null +++ b/packages/editor/dist/toolbar/popups/cellproperties.js @@ -0,0 +1,83 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __read = (this && this.__read) || function (o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +}; +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; +import { Slider } from "@rebass/forms"; +import { useRef, useState } from "react"; +import { Flex, Text } from "rebass"; +import { MenuPresenter } from "../../components/menu/menu"; +import { Popup } from "../components/popup"; +import { ToolButton } from "../components/tool-button"; +export function CellProperties(props) { + var editor = props.editor, onClose = props.onClose; + var attributes = editor.getAttributes("tableCell"); + return (_jsx(Popup, __assign({ title: "Cell properties", action: { + icon: "close", + iconColor: "error", + onClick: onClose, + } }, { children: _jsxs(Flex, __assign({ sx: { flexDirection: "column", px: 1, mb: 2 } }, { children: [_jsx(ColorPickerTool, { color: attributes.backgroundColor, title: "Background color", icon: "backgroundColor", onColorChange: function (color) { + return editor.commands.setCellAttribute("backgroundColor", color); + } }), _jsx(ColorPickerTool, { color: attributes.color, title: "Text color", icon: "textColor", onColorChange: function (color) { + return editor.commands.setCellAttribute("color", color); + } }), _jsx(ColorPickerTool, { color: attributes.borderColor, title: "Border color", icon: "borderColor", onColorChange: function (color) { + return editor.commands.setCellAttribute("borderColor", color); + } }), _jsxs(Flex, __assign({ sx: { flexDirection: "column" } }, { children: [_jsxs(Flex, __assign({ sx: { + justifyContent: "space-between", + alignItems: "center", + mt: 1, + } }, { children: [_jsx(Text, __assign({ variant: "body" }, { children: "Border width" })), _jsxs(Text, __assign({ variant: "body" }, { children: [attributes.borderWidth || 1, "px"] }))] })), _jsx(Slider, { min: 1, max: 5, value: attributes.borderWidth || 1, onChange: function (e) { + editor.commands.setCellAttribute("borderWidth", e.target.valueAsNumber); + } })] }))] })) }))); +} +function ColorPickerTool(props) { + var color = props.color, title = props.title, icon = props.icon, onColorChange = props.onColorChange; + var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; + var buttonRef = useRef(null); + return (_jsxs(_Fragment, { children: [_jsxs(Flex, __assign({ sx: { justifyContent: "space-between", alignItems: "center", mt: 1 } }, { children: [_jsx(Text, __assign({ variant: "body" }, { children: title })), _jsx(ToolButton, { buttonRef: buttonRef, toggled: isOpen, title: title, id: icon, icon: icon, iconSize: 16, sx: { + p: "2.5px", + borderRadius: "small", + backgroundColor: color || "transparent", + ":hover": { bg: color, filter: "brightness(90%)" }, + }, onClick: function () { return setIsOpen(true); } })] })), _jsx(MenuPresenter, __assign({ isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [], options: { + type: "menu", + position: { + target: buttonRef.current || undefined, + location: "below", + align: "center", + isTargetAbsolute: true, + yOffset: 5, + }, + } }, { children: _jsx(Flex, { sx: { + flexDirection: "column", + bg: "background", + boxShadow: "menu", + border: "1px solid var(--border)", + borderRadius: "default", + p: 1, + width: 160, + } }) }))] })); +} diff --git a/packages/editor/dist/toolbar/popups/embedpopup.js b/packages/editor/dist/toolbar/popups/embedpopup.js index 4da217713..b317fe550 100644 --- a/packages/editor/dist/toolbar/popups/embedpopup.js +++ b/packages/editor/dist/toolbar/popups/embedpopup.js @@ -92,23 +92,28 @@ export function EmbedPopup(props) { src: _src, }); }, - } }, { children: _jsxs(Flex, __assign({ sx: { width: 300, flexDirection: "column", p: 1 } }, { children: [error && (_jsxs(Text, __assign({ variant: "error", sx: { + } }, { children: _jsxs(Flex, __assign({ sx: { flexDirection: "column", p: 1 } }, { children: [error && (_jsxs(Text, __assign({ variant: "error", sx: { bg: "errorBg", color: "error", p: 1, borderRadius: "default", - } }, { children: ["Error: ", error] }))), _jsxs(Flex, { children: [_jsx(Button, __assign({ variant: "dialog", sx: { - p: 1, + } }, { children: ["Error: ", error] }))), _jsxs(Flex, __assign({ sx: { mb: 1 } }, { children: [_jsx(Button, __assign({ variant: "dialog", sx: { + pb: 1, mr: 1, + borderRadius: 0, color: embedSource === "url" ? "primary" : "text", - }, onClick: function () { return setEmbedSource("url"); } }, { children: "From link" })), _jsx(Button, __assign({ variant: "dialog", sx: { - p: 1, + borderBottom: "2px solid", + borderBottomColor: embedSource === "url" ? "primary" : "transparent", + }, onClick: function () { return setEmbedSource("url"); } }, { children: "From URL" })), _jsx(Button, __assign({ variant: "dialog", sx: { + pb: 1, + borderRadius: 0, color: embedSource === "code" ? "primary" : "text", - }, onClick: function () { return setEmbedSource("code"); } }, { children: "From code" }))] }), embedSource === "url" ? (_jsx(Input, { placeholder: "Embed source URL", value: src, autoFocus: true, onChange: function (e) { return setSrc(e.target.value); }, sx: { p: 1, mt: 1, fontSize: "body" } })) : (_jsx(Textarea, { autoFocus: true, variant: "forms.input", sx: { fontSize: "subBody", fontFamily: "monospace", mt: 1 }, minHeight: 100, onChange: function (e) { return setSrc(e.target.value); }, placeholder: "Paste embed code here. Only iframes are supported." })), embedSource === "url" ? (_jsxs(Flex, __assign({ sx: { alignItems: "center", mt: 2 } }, { children: [_jsx(Input, { type: "number", placeholder: "Width", value: width, sx: { - mr: 2, - p: 1, + borderBottom: "2px solid", + borderBottomColor: embedSource === "code" ? "primary" : "transparent", + }, onClick: function () { return setEmbedSource("code"); } }, { children: "From code" }))] })), embedSource === "url" ? (_jsx(Input, { placeholder: "Enter embed source URL", value: src, autoFocus: true, onChange: function (e) { return setSrc(e.target.value); }, sx: { mt: 1, fontSize: "body" } })) : (_jsx(Textarea, { autoFocus: true, variant: "forms.input", sx: { fontSize: "subBody", fontFamily: "monospace", mt: 1 }, minHeight: [200, 100], onChange: function (e) { return setSrc(e.target.value); }, placeholder: "Paste embed code here. Only iframes are supported." })), embedSource === "url" ? (_jsxs(Flex, __assign({ sx: { alignItems: "center", mt: 1 } }, { children: [_jsx(Input, { type: "number", placeholder: "Width", value: width, sx: { + mr: 1, fontSize: "body", - }, onChange: function (e) { return onSizeChange(e.target.valueAsNumber); } }), _jsx(Input, { type: "number", placeholder: "Height", value: height, sx: { p: 1, fontSize: "body" }, onChange: function (e) { return onSizeChange(undefined, e.target.valueAsNumber); } })] }))) : null] })) }))); + }, onChange: function (e) { return onSizeChange(e.target.valueAsNumber); } }), _jsx(Input, { type: "number", placeholder: "Height", value: height, sx: { fontSize: "body" }, onChange: function (e) { return onSizeChange(undefined, e.target.valueAsNumber); } })] }))) : null] })) }))); } function getAttribute(document, id) { var element = document.querySelector("[".concat(id, "]")); diff --git a/packages/editor/dist/toolbar/popups/imageproperties.d.ts b/packages/editor/dist/toolbar/popups/imageproperties.d.ts new file mode 100644 index 000000000..608647e46 --- /dev/null +++ b/packages/editor/dist/toolbar/popups/imageproperties.d.ts @@ -0,0 +1,7 @@ +/// +import { ImageAlignmentOptions, ImageSizeOptions } from "../../extensions/image"; +import { Editor } from "@tiptap/core"; +export declare type ImagePropertiesProps = ImageSizeOptions & ImageAlignmentOptions & { + editor: Editor; +}; +export declare function ImageProperties(props: ImagePropertiesProps): JSX.Element; diff --git a/packages/editor/dist/toolbar/popups/imageproperties.js b/packages/editor/dist/toolbar/popups/imageproperties.js new file mode 100644 index 000000000..aeeebc4a8 --- /dev/null +++ b/packages/editor/dist/toolbar/popups/imageproperties.js @@ -0,0 +1,46 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { Flex, Text } from "rebass"; +import { useCallback } from "react"; +import { Toggle } from "../../components/toggle"; +import { Input } from "@rebass/forms"; +export function ImageProperties(props) { + var height = props.height, width = props.width, float = props.float, editor = props.editor; + var onSizeChange = useCallback(function (newWidth, newHeight) { + var size = newWidth + ? { + width: newWidth, + height: newWidth * (height / width), + } + : newHeight + ? { + width: newHeight * (width / height), + height: newHeight, + } + : { + width: 0, + height: 0, + }; + editor.chain().setImageSize(size).run(); + }, [width, height]); + return (_jsxs(Flex, __assign({ sx: { width: 200, flexDirection: "column", p: 1 } }, { children: [_jsxs(Flex, __assign({ sx: { justifyContent: "space-between", alignItems: "center" } }, { children: [_jsx(Text, __assign({ variant: "body" }, { children: "Floating?" })), _jsx(Toggle, { checked: float, onClick: function () { + return editor + .chain() + .setImageAlignment({ float: !float, align: "left" }) + .run(); + } })] })), _jsxs(Flex, __assign({ sx: { alignItems: "center", mt: 2 } }, { children: [_jsx(Input, { type: "number", placeholder: "Width", value: width, sx: { + mr: 2, + p: 1, + fontSize: "body", + }, onChange: function (e) { return onSizeChange(e.target.valueAsNumber); } }), _jsx(Input, { type: "number", placeholder: "Height", value: height, sx: { p: 1, fontSize: "body" }, onChange: function (e) { return onSizeChange(undefined, e.target.valueAsNumber); } })] }))] }))); +} diff --git a/packages/editor/dist/toolbar/popups/searchreplace.d.ts b/packages/editor/dist/toolbar/popups/searchreplace.d.ts new file mode 100644 index 000000000..74cbb2e62 --- /dev/null +++ b/packages/editor/dist/toolbar/popups/searchreplace.d.ts @@ -0,0 +1,6 @@ +/// +import { Editor } from "@tiptap/core"; +export declare type SearchReplacePopupProps = { + editor: Editor; +}; +export declare function SearchReplacePopup(props: SearchReplacePopupProps): JSX.Element; diff --git a/packages/editor/dist/toolbar/popups/searchreplace.js b/packages/editor/dist/toolbar/popups/searchreplace.js new file mode 100644 index 000000000..35128de4d --- /dev/null +++ b/packages/editor/dist/toolbar/popups/searchreplace.js @@ -0,0 +1,103 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __read = (this && this.__read) || function (o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +}; +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { Input } from "@rebass/forms"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Flex } from "rebass"; +import { ToolButton } from "../components/tool-button"; +export function SearchReplacePopup(props) { + var editor = props.editor; + var selectedText = editor.storage.searchreplace.selectedText; + var _a = __read(useState(false), 2), matchCase = _a[0], setMatchCase = _a[1]; + var _b = __read(useState(false), 2), matchWholeWord = _b[0], setMatchWholeWord = _b[1]; + var _c = __read(useState(false), 2), enableRegex = _c[0], setEnableRegex = _c[1]; + var replaceText = useRef(""); + var searchInputRef = useRef(); + var search = useCallback(function (term) { + editor.commands.search(term, { + matchCase: matchCase, + enableRegex: enableRegex, + matchWholeWord: matchWholeWord, + }); + }, [matchCase, enableRegex, matchWholeWord]); + useEffect(function () { + if (!searchInputRef.current) + return; + search(searchInputRef.current.value); + }, [search, matchCase, matchWholeWord, enableRegex]); + useEffect(function () { + if (selectedText) { + if (searchInputRef.current) { + var input_1 = searchInputRef.current; + setTimeout(function () { + input_1.value = selectedText; + input_1.focus(); + }, 0); + } + search(selectedText); + } + }, [selectedText, search]); + return ( + // {}} + // options={{ + // type: "autocomplete", + // position: { + // target: + // document.querySelector(".editor-toolbar") || "mouse", + // isTargetAbsolute: true, + // location: "below", + // align: "end", + // }, + // }} + // > + // + _jsxs(Flex, __assign({ sx: { p: 1, flexDirection: "column" } }, { children: [_jsxs(Flex, __assign({ sx: { alignItems: "start", flexShrink: 0 } }, { children: [_jsxs(Flex, __assign({ sx: { + position: "relative", + mr: 1, + width: 200, + alignItems: "center", + } }, { children: [_jsx(Input, { defaultValue: selectedText, ref: searchInputRef, autoFocus: true, sx: { p: 1 }, placeholder: "Find", onChange: function (e) { + search(e.target.value); + } }), _jsxs(Flex, __assign({ sx: { + position: "absolute", + right: 0, + mr: 0, + } }, { children: [_jsx(ToolButton, { sx: { + mr: 0, + }, toggled: matchCase, title: "Match case", id: "matchCase", icon: "caseSensitive", onClick: function () { return setMatchCase(function (s) { return !s; }); }, iconSize: 14 }), _jsx(ToolButton, { sx: { + mr: 0, + }, toggled: matchWholeWord, title: "Match whole word", id: "matchWholeWord", icon: "wholeWord", onClick: function () { return setMatchWholeWord(function (s) { return !s; }); }, iconSize: 14 }), _jsx(ToolButton, { sx: { + mr: 0, + }, toggled: enableRegex, title: "Enable regex", id: "enableRegex", icon: "regex", onClick: function () { return setEnableRegex(function (s) { return !s; }); }, iconSize: 14 })] }))] })), _jsx(ToolButton, { toggled: false, title: "Previous match", id: "previousMatch", icon: "previousMatch", onClick: function () { return editor.commands.moveToPreviousResult(); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Next match", id: "nextMatch", icon: "nextMatch", onClick: function () { return editor.commands.moveToNextResult(); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Close", id: "close", icon: "close", onClick: function () { return editor.chain().focus().endSearch().run(); }, iconSize: 16, sx: { mr: 0 } })] })), _jsxs(Flex, __assign({ sx: { alignItems: "start", flexShrink: 0, mt: 1 } }, { children: [_jsx(Input, { sx: { p: 1, width: 200, mr: 1 }, placeholder: "Replace", onChange: function (e) { return (replaceText.current = e.target.value); } }), _jsx(ToolButton, { toggled: false, title: "Replace", id: "replace", icon: "replaceOne", onClick: function () { return editor.commands.replace(replaceText.current); }, sx: { mr: 0 }, iconSize: 16 }), _jsx(ToolButton, { toggled: false, title: "Replace all", id: "replaceAll", icon: "replaceAll", onClick: function () { return editor.commands.replaceAll(replaceText.current); }, sx: { mr: 0 }, iconSize: 16 })] }))] })) + // + // + ); +} diff --git a/packages/editor/dist/toolbar/popups/tablepopup.d.ts b/packages/editor/dist/toolbar/popups/tablepopup.d.ts index d69028602..44a07a5ea 100644 --- a/packages/editor/dist/toolbar/popups/tablepopup.d.ts +++ b/packages/editor/dist/toolbar/popups/tablepopup.d.ts @@ -4,7 +4,9 @@ declare type TableSize = { rows: number; }; export declare type TablePopupProps = { - onClose: (size: TableSize) => void; + onInsertTable: (size: TableSize) => void; + cellSize?: number; + autoExpand?: boolean; }; export declare function TablePopup(props: TablePopupProps): JSX.Element; export {}; diff --git a/packages/editor/dist/toolbar/popups/tablepopup.js b/packages/editor/dist/toolbar/popups/tablepopup.js index bf78dc086..944f846c7 100644 --- a/packages/editor/dist/toolbar/popups/tablepopup.js +++ b/packages/editor/dist/toolbar/popups/tablepopup.js @@ -28,12 +28,14 @@ var __read = (this && this.__read) || function (o, n) { import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { Box, Flex, Text } from "rebass"; import { useEffect, useState } from "react"; +import { Popup } from "../components/popup"; +import { Input } from "@rebass/forms"; var MAX_COLUMNS = 20; var MAX_ROWS = 20; var MIN_COLUMNS = 12; var MIN_ROWS = 6; export function TablePopup(props) { - var onClose = props.onClose; + var onInsertTable = props.onInsertTable, cellSize = props.cellSize, autoExpand = props.autoExpand; var _a = __read(useState({ column: 0, row: 0, @@ -43,6 +45,8 @@ export function TablePopup(props) { rows: MIN_ROWS, }), 2), tableSize = _b[0], setTableSize = _b[1]; useEffect(function () { + if (!autoExpand) + return; setTableSize(function (old) { var columns = old.columns, rows = old.rows; var column = cellLocation.column, row = cellLocation.row; @@ -58,31 +62,57 @@ export function TablePopup(props) { : Math.min(old.rows + rowFactor, MAX_ROWS), }; }); - }, [cellLocation]); - return (_jsxs(Flex, __assign({ sx: { p: 1, flexDirection: "column", alignItems: "center" } }, { children: [_jsx(Box, __assign({ sx: { - display: "grid", - gridTemplateColumns: "1fr ".repeat(tableSize.columns), - gap: "3px", - bg: "background", - } }, { children: Array(tableSize.columns * tableSize.rows) - .fill(0) - .map(function (_, index) { return (_jsx(Box, { width: 15, height: 15, sx: { - border: "1px solid var(--disabled)", - borderRadius: "2px", - bg: isCellHighlighted(index, cellLocation, tableSize) - ? "disabled" - : "transparent", - ":hover": { - bg: "disabled", - }, - }, onMouseEnter: function () { - setCellLocation(getCellLocation(index, tableSize)); - }, onClick: function () { - onClose({ - columns: cellLocation.column, - rows: cellLocation.row, - }); - } })); }) })), _jsxs(Text, __assign({ variant: "body", sx: { mt: 1 } }, { children: [cellLocation.column, "x", cellLocation.row] }))] }))); + }, [cellLocation, autoExpand]); + return (_jsx(Popup, __assign({ title: "Insert table", action: { + icon: "check", + onClick: function () { + onInsertTable({ + columns: cellLocation.column, + rows: cellLocation.row, + }); + }, + } }, { children: _jsxs(Flex, __assign({ sx: { p: 1, flexDirection: "column", alignItems: "center" } }, { children: [_jsx(Box, __assign({ sx: { + display: "grid", + gridTemplateColumns: "repeat(".concat(tableSize.columns, ", minmax(").concat(cellSize || 15, "px, 1fr))"), + gap: "3px", + bg: "background", + width: "100%", + }, onTouchMove: function (e) { + var touch = e.touches.item(0); + var element = document.elementFromPoint(touch.pageX, touch.pageY); + if (!element) + return; + var index = element.dataset.index; + if (!index) + return; + setCellLocation(getCellLocation(parseInt(index), tableSize)); + } }, { children: Array(tableSize.columns * tableSize.rows) + .fill(0) + .map(function (_, index) { return (_jsx(Box, { "data-index": index, height: cellSize || 15, sx: { + border: "1px solid var(--disabled)", + borderRadius: "2px", + bg: isCellHighlighted(index, cellLocation, tableSize) + ? "disabled" + : "transparent", + }, onTouchStart: function () { + setCellLocation(getCellLocation(index, tableSize)); + }, onMouseEnter: function () { + setCellLocation(getCellLocation(index, tableSize)); + }, onClick: function () { + onInsertTable({ + columns: cellLocation.column, + rows: cellLocation.row, + }); + } })); }) })), _jsxs(Flex, __assign({ sx: { + display: ["flex", "none"], + my: 1, + alignItems: "center", + justifyContent: "center", + } }, { children: [_jsx(Input, { placeholder: "".concat(cellLocation.column, " columns"), sx: { mr: 1 }, type: "number", value: cellLocation.column, onChange: function (e) { + setCellLocation(function (l) { return (__assign(__assign({}, l), { column: e.target.valueAsNumber || 0 })); }); + } }), _jsx(Input, { placeholder: "".concat(cellLocation.row, " rows"), type: "number", value: cellLocation.row, onChange: function (e) { + setCellLocation(function (l) { return (__assign(__assign({}, l), { row: e.target.valueAsNumber || 0 })); }); + } })] })), _jsxs(Text, __assign({ variant: "body", sx: { mt: 1, display: ["none", "block"] } }, { children: [cellLocation.column, "x", cellLocation.row] }))] })) }))); } function getCellLocation(index, tableSize) { var cellIndex = index + 1; diff --git a/packages/editor/dist/toolbar/stores/toolbarstore.d.ts b/packages/editor/dist/toolbar/stores/toolbarstore.d.ts new file mode 100644 index 000000000..58587008a --- /dev/null +++ b/packages/editor/dist/toolbar/stores/toolbarstore.d.ts @@ -0,0 +1,11 @@ +export declare type ToolbarLocation = "top" | "bottom"; +interface ToolbarState { + isMobile: boolean; + setIsMobile: (isMobile: boolean) => void; + toolbarLocation: ToolbarLocation; + setToolbarLocation: (location: ToolbarLocation) => void; +} +export declare const useToolbarStore: import("zustand").UseBoundStore>; +export declare function useToolbarLocation(): ToolbarLocation; +export declare function useIsMobile(): boolean; +export {}; diff --git a/packages/editor/dist/toolbar/stores/toolbarstore.js b/packages/editor/dist/toolbar/stores/toolbarstore.js new file mode 100644 index 000000000..92897309b --- /dev/null +++ b/packages/editor/dist/toolbar/stores/toolbarstore.js @@ -0,0 +1,21 @@ +import create from "zustand"; +export var useToolbarStore = create(function (set) { return ({ + isMobile: false, + setIsMobile: function (isMobile) { + return set(function (state) { + state.isMobile = isMobile; + }); + }, + toolbarLocation: "top", + setToolbarLocation: function (location) { + return set(function (state) { + state.toolbarLocation = location; + }); + }, +}); }); +export function useToolbarLocation() { + return useToolbarStore(function (store) { return store.toolbarLocation; }); +} +export function useIsMobile() { + return useToolbarStore(function (store) { return store.isMobile; }); +} diff --git a/packages/editor/dist/toolbar/toolbar.d.ts b/packages/editor/dist/toolbar/toolbar.d.ts index 507c2b74e..e871a4cea 100644 --- a/packages/editor/dist/toolbar/toolbar.d.ts +++ b/packages/editor/dist/toolbar/toolbar.d.ts @@ -1,10 +1,11 @@ /// import { ThemeConfig } from "@notesnook/theme/dist/theme/types"; import { Editor } from "@tiptap/core"; -import { ToolbarLocation } from "./hooks/useToolbarContext"; +import { ToolbarLocation } from "./stores/toolbar-store"; declare type ToolbarProps = ThemeConfig & { editor: Editor | null; location: ToolbarLocation; + isMobile?: boolean; }; export declare function Toolbar(props: ToolbarProps): JSX.Element | null; export {}; diff --git a/packages/editor/dist/toolbar/toolbar.js b/packages/editor/dist/toolbar/toolbar.js index 7e5e0f045..450a01f9a 100644 --- a/packages/editor/dist/toolbar/toolbar.js +++ b/packages/editor/dist/toolbar/toolbar.js @@ -44,14 +44,20 @@ import { ThemeProvider } from "emotion-theming"; import { EditorFloatingMenus } from "./floating-menus"; import { getToolDefinition } from "./tool-definitions"; import { ToolButton } from "./components/tool-button"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { MenuPresenter } from "../components/menu"; import { Popup } from "./components/popup"; -import { ToolbarContext, useToolbarContext, } from "./hooks/useToolbarContext"; +import { ToolbarContext, useToolbarContext } from "./hooks/useToolbarContext"; +import { useToolbarLocation, useToolbarStore, } from "./stores/toolbar-store"; export function Toolbar(props) { - var editor = props.editor, theme = props.theme, accent = props.accent, scale = props.scale, location = props.location; + var editor = props.editor, theme = props.theme, accent = props.accent, scale = props.scale, location = props.location, isMobile = props.isMobile; var themeProperties = useTheme({ accent: accent, theme: theme, scale: scale }); var _a = __read(useState(), 2), currentPopup = _a[0], setCurrentPopup = _a[1]; + var _b = useToolbarStore(), setIsMobile = _b.setIsMobile, setToolbarLocation = _b.setToolbarLocation; + useEffect(function () { + setIsMobile(isMobile || false); + setToolbarLocation(location); + }, [isMobile, location]); var tools = [ ["insertBlock"], [ @@ -75,7 +81,10 @@ export function Toolbar(props) { ]; if (!editor) return null; - return (_jsxs(ThemeProvider, __assign({ theme: themeProperties }, { children: [_jsx(ToolbarContext.Provider, __assign({ value: { setCurrentPopup: setCurrentPopup, currentPopup: currentPopup, toolbarLocation: location } }, { children: _jsx(Flex, __assign({ className: "editor-toolbar", sx: { flexWrap: ["nowrap", "wrap"], overflowX: ["auto", "hidden"] } }, { children: tools.map(function (tools) { + return (_jsxs(ThemeProvider, __assign({ theme: themeProperties }, { children: [_jsx(ToolbarContext.Provider, __assign({ value: { + setCurrentPopup: setCurrentPopup, + currentPopup: currentPopup, + } }, { children: _jsx(Flex, __assign({ className: "editor-toolbar", sx: { flexWrap: ["nowrap", "wrap"], overflowX: ["auto", "hidden"] } }, { children: tools.map(function (tools) { return (_jsx(ToolbarGroup, { tools: tools, editor: editor, sx: { flexShrink: 0, pr: 2, @@ -94,13 +103,14 @@ function ToolbarGroup(props) { else { var Component = findToolById(toolId); var toolDefinition = getToolDefinition(toolId); - return _jsx(Component, __assign({ editor: editor, id: toolId }, toolDefinition)); + return _jsx(Component, __assign({ editor: editor }, toolDefinition)); } }) }))); } function MoreTools(props) { var popupId = props.popupId; - var _a = useToolbarContext(), currentPopup = _a.currentPopup, setCurrentPopup = _a.setCurrentPopup, toolbarLocation = _a.toolbarLocation; + var _a = useToolbarContext(), currentPopup = _a.currentPopup, setCurrentPopup = _a.setCurrentPopup; + var toolbarLocation = useToolbarLocation(); var buttonRef = useRef(); var show = popupId === currentPopup; var setShow = function (state) { diff --git a/packages/editor/dist/toolbar/tools/block.js b/packages/editor/dist/toolbar/tools/block.js index 79bc1a664..f93e4dc05 100644 --- a/packages/editor/dist/toolbar/tools/block.js +++ b/packages/editor/dist/toolbar/tools/block.js @@ -27,16 +27,17 @@ var __read = (this && this.__read) || function (o, n) { }; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { Icons } from "../icons"; -import { MenuPresenter } from "../../components/menu/menu"; +import { ActionSheetPresenter, } from "../../components/menu/menu"; import { useRef, useState } from "react"; import { Icon } from "../components/icon"; import { Button } from "rebass"; +import { EmbedPopup } from "../popups/embed-popup"; import { TablePopup } from "../popups/table-popup"; -import { useToolbarContext } from "../hooks/useToolbarContext"; +import { useToolbarLocation } from "../stores/toolbar-store"; export function InsertBlock(props) { var buttonRef = useRef(); var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; - var toolbarLocation = useToolbarContext().toolbarLocation; + var toolbarLocation = useToolbarLocation(); return (_jsxs(_Fragment, { children: [_jsx(Button, __assign({ ref: buttonRef, sx: { p: 1, m: 0, @@ -48,23 +49,15 @@ export function InsertBlock(props) { ":last-of-type": { mr: 0, }, - }, onMouseDown: function (e) { return e.preventDefault(); }, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: _jsx(Icon, { path: Icons.plus, size: 18, color: "primary" }) })), _jsx(MenuPresenter, { options: { - type: "menu", - position: { - target: buttonRef.current || undefined, - isTargetAbsolute: true, - location: toolbarLocation === "bottom" ? "top" : "below", - yOffset: 5, - }, - }, isOpen: isOpen, items: [ + }, onMouseDown: function (e) { return e.preventDefault(); }, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: _jsx(Icon, { path: Icons.plus, size: 18, color: "primary" }) })), _jsx(ActionSheetPresenter, { title: "Choose a block to insert", isOpen: isOpen, items: [ tasklist(editor), horizontalRule(editor), codeblock(editor), blockquote(editor), - image(editor), + imageActionSheet(editor), attachment(editor), - embed(editor), - table(editor), + embedActionSheet(editor), + tableActionSheet(editor), ], onClose: function () { return setIsOpen(false); } })] })); } var horizontalRule = function (editor) { return ({ @@ -113,6 +106,38 @@ var image = function (editor) { return ({ }, ], }); }; +var imageActionSheet = function (editor) { return ({ + key: "image", + type: "menuitem", + title: "Image", + icon: "image", + items: [ + { + key: "imageOptions", + type: "menuitem", + component: function (_a) { + var onClick = _a.onClick; + var _b = __read(useState(true), 2), isOpen = _b[0], setIsOpen = _b[1]; + return (_jsx(ActionSheetPresenter, { isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [ + { + key: "upload-from-disk", + type: "menuitem", + title: "Upload from disk", + icon: "upload", + onClick: function () { }, + }, + { + key: "upload-from-url", + type: "menuitem", + title: "Attach from URL", + icon: "link", + onClick: function () { }, + }, + ] })); + }, + }, + ], +}); }; var embed = function (editor) { return ({ key: "embed", type: "menuitem", @@ -128,7 +153,7 @@ var table = function (editor) { return ({ { key: "table-size-selector", type: "menuitem", - component: function (props) { return (_jsx(TablePopup, { onClose: function (size) { + component: function (props) { return (_jsx(TablePopup, { onInsertTable: function (size) { var _a; editor === null || editor === void 0 ? void 0 : editor.chain().focus().insertTable({ rows: size.rows, @@ -139,6 +164,51 @@ var table = function (editor) { return ({ }, ], }); }; +var embedActionSheet = function (editor) { return ({ + key: "embed", + type: "menuitem", + title: "Embed", + icon: "embed", + items: [ + { + key: "table-size-selector", + type: "menuitem", + component: function (_a) { + var onClick = _a.onClick; + var _b = __read(useState(true), 2), isOpen = _b[0], setIsOpen = _b[1]; + return (_jsx(ActionSheetPresenter, __assign({ isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [] }, { children: _jsx(EmbedPopup, { title: "Insert embed", icon: "check", onClose: function (embed) { + editor === null || editor === void 0 ? void 0 : editor.chain().insertEmbed(embed).run(); + setIsOpen(false); + onClick === null || onClick === void 0 ? void 0 : onClick(); + } }) }))); + }, + }, + ], +}); }; +var tableActionSheet = function (editor) { return ({ + key: "table", + type: "menuitem", + title: "Table", + icon: "table", + items: [ + { + key: "table-size-selector", + type: "menuitem", + component: function (_a) { + var onClick = _a.onClick; + var _b = __read(useState(true), 2), isOpen = _b[0], setIsOpen = _b[1]; + return (_jsx(ActionSheetPresenter, __assign({ isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [] }, { children: _jsx(TablePopup, { cellSize: 30, autoExpand: false, onInsertTable: function (size) { + editor === null || editor === void 0 ? void 0 : editor.chain().focus().insertTable({ + rows: size.rows, + cols: size.columns, + }).run(); + setIsOpen(false); + onClick === null || onClick === void 0 ? void 0 : onClick(); + } }) }))); + }, + }, + ], +}); }; var attachment = function (editor) { return ({ key: "attachment", type: "menuitem", diff --git a/packages/editor/dist/toolbar/tools/font.js b/packages/editor/dist/toolbar/tools/font.js index 61e5a0a82..e191ace48 100644 --- a/packages/editor/dist/toolbar/tools/font.js +++ b/packages/editor/dist/toolbar/tools/font.js @@ -1,3 +1,14 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; @@ -14,23 +25,32 @@ var __read = (this && this.__read) || function (o, n) { } return ar; }; -import { jsx as _jsx } from "react/jsx-runtime"; +import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; import { Dropdown } from "../components/dropdown"; +import { Box, Button } from "rebass"; var defaultFontSizes = [ - 12, 14, 16, 18, 20, 24, 28, 32, 36, 42, 48, 60, 72, 100, + 8, 12, 14, 16, 18, 20, 24, 28, 32, 36, 42, 48, 60, 72, 100, ]; export function FontSize(props) { var editor = props.editor; var currentFontSize = defaultFontSizes.find(function (size) { return editor.isActive("textStyle", { fontSize: "".concat(size, "px") }); }) || 16; - return (_jsx(Dropdown, { selectedItem: "".concat(currentFontSize, "px"), items: defaultFontSizes.map(function (size) { return ({ - key: "".concat(size, "px"), - type: "menuitem", - title: "".concat(size, "px"), - isChecked: size === currentFontSize, - onClick: function () { return editor.chain().focus().setFontSize("".concat(size, "px")).run(); }, - }); }), menuWidth: 100 })); + return (_jsx(Dropdown, { selectedItem: "".concat(currentFontSize, "px"), items: [ + { + key: "font-sizes", + type: "menuitem", + component: function () { return (_jsx(Box, __assign({ sx: { display: "grid", gridTemplateColumns: "repeat(5, 1fr)" } }, { children: defaultFontSizes.map(function (size) { return (_jsxs(Button, __assign({ variant: "menuitem" }, { children: [size, "px"] }))); }) }))); }, + }, + ], + // items={defaultFontSizes.map((size) => ({ + // key: `${size}px`, + // type: "menuitem", + // title: `${size}px`, + // isChecked: size === currentFontSize, + // onClick: () => editor.chain().focus().setFontSize(`${size}px`).run(), + // }))} + menuWidth: 100 })); } var fontFamilies = { System: "Open Sans", diff --git a/packages/editor/dist/toolbar/tools/inline.js b/packages/editor/dist/toolbar/tools/inline.js index 6f1ac8f6e..09d068db6 100644 --- a/packages/editor/dist/toolbar/tools/inline.js +++ b/packages/editor/dist/toolbar/tools/inline.js @@ -33,8 +33,8 @@ import { Flex } from "rebass"; import { Input } from "@rebass/forms"; import { Popup } from "../components/popup"; function InlineTool(props) { - var editor = props.editor, title = props.title, id = props.id, icon = props.icon, isToggled = props.isToggled, onClick = props.onClick; - return (_jsx(ToolButton, { title: title, id: id, icon: icon, onClick: function () { return onClick(editor); }, toggled: isToggled(editor) })); + var editor = props.editor, title = props.title, icon = props.icon, isToggled = props.isToggled, onClick = props.onClick; + return (_jsx(ToolButton, { title: title, id: icon, icon: icon, onClick: function () { return onClick(editor); }, toggled: isToggled(editor) })); } export function Italic(props) { return (_jsx(InlineTool, __assign({}, props, { isToggled: function (editor) { return editor.isActive("italic"); }, onClick: function (editor) { return editor.chain().focus().toggleItalic().run(); } }))); @@ -63,7 +63,7 @@ export function ClearFormatting(props) { } }))); } export function Link(props) { - var editor = props.editor, id = props.id, title = props.title, icon = props.icon; + var editor = props.editor, title = props.title, icon = props.icon; var buttonRef = useRef(null); var targetRef = useRef(); var _a = __read(useState(false), 2), isOpen = _a[0], setIsOpen = _a[1]; @@ -71,7 +71,7 @@ export function Link(props) { var _c = __read(useState(), 2), text = _c[0], setText = _c[1]; var currentUrl = editor.getAttributes("link").href; var isEditing = !!currentUrl; - return (_jsxs(_Fragment, { children: [_jsx(ToolButton, { ref: buttonRef, title: title, id: id, icon: icon, onClick: function () { + return (_jsxs(_Fragment, { children: [_jsx(ToolButton, { id: icon, ref: buttonRef, title: title, icon: icon, onClick: function () { if (isEditing) setHref(currentUrl); var _a = editor.state.selection, from = _a.from, to = _a.to, $from = _a.$from; diff --git a/packages/editor/dist/toolbar/types.d.ts b/packages/editor/dist/toolbar/types.d.ts index 02b6fd643..19ce4f691 100644 --- a/packages/editor/dist/toolbar/types.d.ts +++ b/packages/editor/dist/toolbar/types.d.ts @@ -1,9 +1,7 @@ import { Editor } from "@tiptap/core"; import { IconNames } from "./icons"; -import { ToolId } from "./tools"; export declare type ToolProps = ToolDefinition & { editor: Editor; - id: ToolId; }; export declare type ToolDefinition = { icon: IconNames; diff --git a/packages/editor/dist/toolbar/utils/dom.d.ts b/packages/editor/dist/toolbar/utils/dom.d.ts new file mode 100644 index 000000000..ad25b37e2 --- /dev/null +++ b/packages/editor/dist/toolbar/utils/dom.d.ts @@ -0,0 +1 @@ +export declare function getToolbarElement(): HTMLElement; diff --git a/packages/editor/dist/toolbar/utils/dom.js b/packages/editor/dist/toolbar/utils/dom.js new file mode 100644 index 000000000..aab1dc4d5 --- /dev/null +++ b/packages/editor/dist/toolbar/utils/dom.js @@ -0,0 +1,3 @@ +export function getToolbarElement() { + return (document.querySelector(".editor-toolbar") || undefined); +} diff --git a/packages/editor/package-lock.json b/packages/editor/package-lock.json index f3a61a68c..2abca7c7d 100644 --- a/packages/editor/package-lock.json +++ b/packages/editor/package-lock.json @@ -41,6 +41,7 @@ "re-resizable": "^6.9.5", "react-color": "^2.19.3", "react-modal": "^3.14.4", + "react-spring-bottom-sheet": "^3.4.0", "react-toggle": "^4.1.2", "reactjs-popup": "^2.0.5", "rebass": "^4.0.7", @@ -3081,6 +3082,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, "node_modules/@mdi/js": { "version": "6.6.96", "resolved": "https://registry.npmjs.org/@mdi/js/-/js-6.6.96.tgz", @@ -3245,6 +3251,33 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reach/portal": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.13.2.tgz", + "integrity": "sha512-g74BnCdtuTGthzzHn2cWW+bcyIYb0iIE/yRsm89i8oNzNgpopbkh9UY8TPbhNlys52h7U60s4kpRTmcq+JqsTA==", + "dependencies": { + "@reach/utils": "0.13.2", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, + "node_modules/@reach/utils": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", + "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", + "dependencies": { + "@types/warning": "^3.0.0", + "tslib": "^2.1.0", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": "^16.8.0 || 17.x", + "react-dom": "^16.8.0 || 17.x" + } + }, "node_modules/@rebass/forms": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@rebass/forms/-/forms-4.0.6.tgz", @@ -4703,6 +4736,11 @@ "node": ">=0.10.0" } }, + "node_modules/@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, "node_modules/@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -5123,6 +5161,28 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xstate/react": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@xstate/react/-/react-1.6.3.tgz", + "integrity": "sha512-NCUReRHPGvvCvj2yLZUTfR0qVp6+apc8G83oXSjN4rl89ZjyujiKrTff55bze/HrsvCsP/sUJASf2n0nzMF1KQ==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.0.0", + "use-subscription": "^1.3.0" + }, + "peerDependencies": { + "@xstate/fsm": "^1.0.0", + "react": "^16.8.0 || ^17.0.0", + "xstate": "^4.11.0" + }, + "peerDependenciesMeta": { + "@xstate/fsm": { + "optional": true + }, + "xstate": { + "optional": true + } + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -6333,6 +6393,11 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/body-scroll-lock": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", + "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==" + }, "node_modules/bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -10562,6 +10627,14 @@ "readable-stream": "^2.3.6" } }, + "node_modules/focus-trap": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.2.tgz", + "integrity": "sha512-gBEuXOPNOKPrLdZpMFUSTyIo1eT2NSZRrwZ9r/0Jqw5tmT3Yvxfmu8KBHw8xW2XQkw6E/JoG+OlEq7UDtSUNgw==", + "dependencies": { + "tabbable": "^5.3.2" + } + }, "node_modules/follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -18891,6 +18964,37 @@ "semver": "bin/semver" } }, + "node_modules/react-spring": { + "version": "8.0.27", + "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz", + "integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/react-spring-bottom-sheet": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-spring-bottom-sheet/-/react-spring-bottom-sheet-3.4.0.tgz", + "integrity": "sha512-zKwTymxrTRMHPjfBiMw8reQlWoVqlCGMTefmMYkAlBvR7n3hBe5sntuQJAEjmrAnA+cLSGp44mtmgBtT2ksL5Q==", + "dependencies": { + "@juggle/resize-observer": "^3.2.0", + "@reach/portal": "^0.13.0", + "@xstate/react": "^1.2.0", + "body-scroll-lock": "^3.1.5", + "focus-trap": "^6.2.2", + "react-spring": "^8.0.27", + "react-use-gesture": "^8.0.1", + "xstate": "^4.15.1" + }, + "peerDependencies": { + "react": "^16.14.0 || 17" + } + }, "node_modules/react-toggle": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.2.tgz", @@ -18904,6 +19008,15 @@ "react-dom": ">= 15.3.0 < 18" } }, + "node_modules/react-use-gesture": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-8.0.1.tgz", + "integrity": "sha512-CXzUNkulUdgouaAlvAsC5ZVo0fi9KGSBSk81WrE4kOIcJccpANe9zZkAYr5YZZhqpicIFxitsrGVS4wmoMun9A==", + "deprecated": "This package is no longer maintained. Please use @use-gesture/react instead", + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -21553,6 +21666,11 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.2.tgz", + "integrity": "sha512-6G/8EWRFx8CiSe2++/xHhXkmCRq2rHtDtZbQFHx34cvDfZzIBfvwG9zGUNTWMXWLCYvDj3aQqOzdl3oCxKuBkQ==" + }, "node_modules/table": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", @@ -22158,8 +22276,7 @@ "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -22675,6 +22792,38 @@ "node": ">=0.10.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-subscription": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.7.0.tgz", + "integrity": "sha512-87x6MjiIVE/BWqtxfiRvM6jfvGudN+UeVOnWi7qKYp2c0YJn5+Z5Jt0kZw6Tt+8hs7kw/BWo2WBhizJSAZsQJA==", + "dependencies": { + "use-sync-external-store": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz", + "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -24723,6 +24872,15 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xstate": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.32.0.tgz", + "integrity": "sha512-62gETqwnw4pBRe+tVWMt8hLgWEU8lq2qO8VN5PWmTELceRVt3I1bu1cwdraVRHUn4Bb2lnhNzn1A73oShuC+8g==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/xstate" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -27036,6 +27194,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@juggle/resize-observer": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.3.1.tgz", + "integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==" + }, "@mdi/js": { "version": "6.6.96", "resolved": "https://registry.npmjs.org/@mdi/js/-/js-6.6.96.tgz", @@ -27160,6 +27323,25 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" }, + "@reach/portal": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/portal/-/portal-0.13.2.tgz", + "integrity": "sha512-g74BnCdtuTGthzzHn2cWW+bcyIYb0iIE/yRsm89i8oNzNgpopbkh9UY8TPbhNlys52h7U60s4kpRTmcq+JqsTA==", + "requires": { + "@reach/utils": "0.13.2", + "tslib": "^2.1.0" + } + }, + "@reach/utils": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@reach/utils/-/utils-0.13.2.tgz", + "integrity": "sha512-3ir6cN60zvUrwjOJu7C6jec/samqAeyAB12ZADK+qjnmQPdzSYldrFWwDVV5H0WkhbYXR3uh+eImu13hCetNPQ==", + "requires": { + "@types/warning": "^3.0.0", + "tslib": "^2.1.0", + "warning": "^4.0.3" + } + }, "@rebass/forms": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@rebass/forms/-/forms-4.0.6.tgz", @@ -28246,6 +28428,11 @@ } } }, + "@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, "@types/webpack": { "version": "4.41.32", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", @@ -28582,6 +28769,15 @@ "@xtuc/long": "4.2.2" } }, + "@xstate/react": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@xstate/react/-/react-1.6.3.tgz", + "integrity": "sha512-NCUReRHPGvvCvj2yLZUTfR0qVp6+apc8G83oXSjN4rl89ZjyujiKrTff55bze/HrsvCsP/sUJASf2n0nzMF1KQ==", + "requires": { + "use-isomorphic-layout-effect": "^1.0.0", + "use-subscription": "^1.3.0" + } + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -29556,6 +29752,11 @@ } } }, + "body-scroll-lock": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", + "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==" + }, "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -32882,6 +33083,14 @@ "readable-stream": "^2.3.6" } }, + "focus-trap": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.2.tgz", + "integrity": "sha512-gBEuXOPNOKPrLdZpMFUSTyIo1eT2NSZRrwZ9r/0Jqw5tmT3Yvxfmu8KBHw8xW2XQkw6E/JoG+OlEq7UDtSUNgw==", + "requires": { + "tabbable": "^5.3.2" + } + }, "follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -39463,6 +39672,30 @@ } } }, + "react-spring": { + "version": "8.0.27", + "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz", + "integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==", + "requires": { + "@babel/runtime": "^7.3.1", + "prop-types": "^15.5.8" + } + }, + "react-spring-bottom-sheet": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-spring-bottom-sheet/-/react-spring-bottom-sheet-3.4.0.tgz", + "integrity": "sha512-zKwTymxrTRMHPjfBiMw8reQlWoVqlCGMTefmMYkAlBvR7n3hBe5sntuQJAEjmrAnA+cLSGp44mtmgBtT2ksL5Q==", + "requires": { + "@juggle/resize-observer": "^3.2.0", + "@reach/portal": "^0.13.0", + "@xstate/react": "^1.2.0", + "body-scroll-lock": "^3.1.5", + "focus-trap": "^6.2.2", + "react-spring": "^8.0.27", + "react-use-gesture": "^8.0.1", + "xstate": "^4.15.1" + } + }, "react-toggle": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.2.tgz", @@ -39471,6 +39704,12 @@ "classnames": "^2.2.5" } }, + "react-use-gesture": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-8.0.1.tgz", + "integrity": "sha512-CXzUNkulUdgouaAlvAsC5ZVo0fi9KGSBSk81WrE4kOIcJccpANe9zZkAYr5YZZhqpicIFxitsrGVS4wmoMun9A==", + "requires": {} + }, "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", @@ -41631,6 +41870,11 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tabbable": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.2.tgz", + "integrity": "sha512-6G/8EWRFx8CiSe2++/xHhXkmCRq2rHtDtZbQFHx34cvDfZzIBfvwG9zGUNTWMXWLCYvDj3aQqOzdl3oCxKuBkQ==" + }, "table": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", @@ -42093,8 +42337,7 @@ "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsutils": { "version": "3.21.0", @@ -42476,6 +42719,26 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "requires": {} + }, + "use-subscription": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.7.0.tgz", + "integrity": "sha512-87x6MjiIVE/BWqtxfiRvM6jfvGudN+UeVOnWi7qKYp2c0YJn5+Z5Jt0kZw6Tt+8hs7kw/BWo2WBhizJSAZsQJA==", + "requires": { + "use-sync-external-store": "^1.1.0" + } + }, + "use-sync-external-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz", + "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==", + "requires": {} + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -44176,6 +44439,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "xstate": { + "version": "4.32.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-4.32.0.tgz", + "integrity": "sha512-62gETqwnw4pBRe+tVWMt8hLgWEU8lq2qO8VN5PWmTELceRVt3I1bu1cwdraVRHUn4Bb2lnhNzn1A73oShuC+8g==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/packages/editor/package.json b/packages/editor/package.json index eb82283d9..f1f2eb2b5 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -37,6 +37,7 @@ "re-resizable": "^6.9.5", "react-color": "^2.19.3", "react-modal": "^3.14.4", + "react-spring-bottom-sheet": "^3.4.0", "react-toggle": "^4.1.2", "reactjs-popup": "^2.0.5", "rebass": "^4.0.7", diff --git a/packages/editor/src/components/menu/menu.tsx b/packages/editor/src/components/menu/menu.tsx index dad833bc6..e52c32527 100644 --- a/packages/editor/src/components/menu/menu.tsx +++ b/packages/editor/src/components/menu/menu.tsx @@ -14,6 +14,9 @@ import { MenuItem as MenuItemType /*ResolvedMenuItem*/ } from "./types"; // import { useMenuTrigger, useMenu, getPosition } from "../../hooks/useMenu"; import Modal from "react-modal"; import { ThemeProvider } from "emotion-theming"; +import { BottomSheet } from "react-spring-bottom-sheet"; +import "react-spring-bottom-sheet/dist/style.css"; +import { useIsMobile } from "../../toolbar/stores/toolbar-store"; // import { store as selectionStore } from "../../stores/selectionstore"; function useMenuFocus( @@ -258,20 +261,35 @@ function MenuContainer(props: PropsWithChildren) { ); } +export type PopupType = "sheet" | "menu" | "none"; +export type PopupPresenterProps = MenuPresenterProps & + ActionSheetPresenterProps & { + mobile?: PopupType; + desktop?: PopupType; + }; +export function PopupPresenter(props: PropsWithChildren) { + const { mobile = "menu", desktop = "menu", ...restProps } = props; + const isMobile = useIsMobile(); + if (isMobile && mobile === "sheet") + return ; + else if (mobile === "menu" || desktop === "menu") + return ; + else return props.isOpen ? <>{props.children} : null; +} + export type MenuPresenterProps = MenuContainerProps & { - items: MenuItemType[]; - options: MenuOptions; + items?: MenuItemType[]; + onClose?: () => void; isOpen: boolean; - onClose: () => void; - className?: string; + options?: MenuOptions; }; export function MenuPresenter(props: PropsWithChildren) { const { className, - options, - items, + options = { type: "menu", position: {} }, + items = [], isOpen, - onClose, + onClose = () => {}, children, ...containerProps } = props; @@ -384,3 +402,43 @@ export function MenuPresenter(props: PropsWithChildren) { ); } + +export type ActionSheetPresenterProps = MenuContainerProps & { + items?: MenuItemType[]; + isOpen: boolean; + onClose?: () => void; + blocking?: boolean; +}; +export function ActionSheetPresenter( + props: PropsWithChildren +) { + const { + items = [], + isOpen, + onClose = () => {}, + children, + sx, + blocking = true, + ...containerProps + } = props; + + return ( + + {props.children ? ( + props.children + ) : ( + + )} + + ); +} diff --git a/packages/editor/src/components/responsive/index.tsx b/packages/editor/src/components/responsive/index.tsx new file mode 100644 index 000000000..e2c454aa9 --- /dev/null +++ b/packages/editor/src/components/responsive/index.tsx @@ -0,0 +1,21 @@ +import React, { PropsWithChildren } from "react"; +import { useToolbarStore } from "../../toolbar/stores/toolbar-store"; + +type ResponsiveContainerProps = { + mobile?: JSX.Element; + desktop?: JSX.Element; +}; + +export function ResponsiveContainer(props: ResponsiveContainerProps) { + const isMobile = useToolbarStore((store) => store.isMobile); + if (isMobile) return props.mobile || null; + else return props.desktop || null; +} + +export function DesktopOnly(props: PropsWithChildren<{}>) { + return {props.children}} />; +} + +export function MobileOnly(props: PropsWithChildren<{}>) { + return {props.children}} />; +} diff --git a/packages/editor/src/extensions/embed/component.tsx b/packages/editor/src/extensions/embed/component.tsx index 105291f7c..e15f3e165 100644 --- a/packages/editor/src/extensions/embed/component.tsx +++ b/packages/editor/src/extensions/embed/component.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Image, ImageProps, Text } from "rebass"; +import { Box, Button, Flex, Image, ImageProps, Text } from "rebass"; import { NodeViewWrapper, NodeViewProps, FloatingMenu } from "@tiptap/react"; import { ThemeConfig } from "@notesnook/theme/dist/theme/types"; import { ThemeProvider } from "emotion-theming"; @@ -8,7 +8,11 @@ import { ToolButton } from "../../toolbar/components/tool-button"; import { findToolById, ToolId } from "../../toolbar/tools"; import { Editor } from "@tiptap/core"; import { useCallback, useEffect, useRef, useState } from "react"; -import { MenuPresenter } from "../../components/menu/menu"; +import { + ActionSheetPresenter, + MenuPresenter, + PopupPresenter, +} from "../../components/menu/menu"; import { Popup } from "../../toolbar/components/popup"; import { Toggle } from "../../components/toggle"; import { Input } from "@rebass/forms"; @@ -18,6 +22,9 @@ import { EmbedSizeOptions, } from "./embed"; import { EmbedPopup } from "../../toolbar/popups/embed-popup"; +import { Icon } from "../../toolbar/components/icon"; +import { Icons } from "../../toolbar/icons"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function EmbedComponent(props: NodeViewProps) { const { src, width, height, align } = props.node.attrs as EmbedAttributes & @@ -61,9 +68,27 @@ export function EmbedComponent(props: NodeViewProps) { }} lockAspectRatio={true} > - + {/* + + */} + {isToolbarVisible && ( - @@ -98,7 +124,7 @@ type ImageToolbarProps = Required & editor: Editor; }; -function ImageToolbar(props: ImageToolbarProps) { +function EmbedToolbar(props: ImageToolbarProps) { const { editor, height, width, src } = props; const [isOpen, setIsOpen] = useState(false); @@ -184,7 +210,13 @@ function ImageToolbar(props: ImageToolbarProps) { - {isOpen && ( + setIsOpen(false)} + blocking={true} + > {}} onSizeChanged={(size) => editor.commands.setEmbedSize(size)} /> - )} + ); } diff --git a/packages/editor/src/extensions/image/component.tsx b/packages/editor/src/extensions/image/component.tsx index 69c8a3c05..554768dad 100644 --- a/packages/editor/src/extensions/image/component.tsx +++ b/packages/editor/src/extensions/image/component.tsx @@ -13,10 +13,16 @@ import { ToolButton } from "../../toolbar/components/tool-button"; import { findToolById, ToolId } from "../../toolbar/tools"; import { Editor } from "@tiptap/core"; import { useCallback, useEffect, useRef, useState } from "react"; -import { MenuPresenter } from "../../components/menu/menu"; +import { + ActionSheetPresenter, + MenuPresenter, + PopupPresenter, +} from "../../components/menu/menu"; import { Popup } from "../../toolbar/components/popup"; import { Toggle } from "../../components/toggle"; import { Input } from "@rebass/forms"; +import { ImageProperties } from "../../toolbar/popups/image-properties"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function ImageComponent(props: ImageProps & NodeViewProps) { const { src, alt, title, width, height, align, float } = props.node @@ -106,28 +112,6 @@ function ImageToolbar(props: ImageToolbarProps) { const { editor, float, height, width } = props; const [isOpen, setIsOpen] = useState(false); - const onSizeChange = useCallback( - (newWidth?: number, newHeight?: number) => { - const size: ImageSizeOptions = newWidth - ? { - width: newWidth, - height: newWidth * (height / width), - } - : newHeight - ? { - width: newHeight * (width / height), - height: newHeight, - } - : { - width: 0, - height: 0, - }; - - editor.chain().setImageSize(size).run(); - }, - [width, height] - ); - return ( - {isOpen && ( + setIsOpen(false)} + blocking={false} + > - - - Floating? - - editor - .chain() - .setImageAlignment({ float: !float, align: "left" }) - .run() - } - /> - - - onSizeChange(e.target.valueAsNumber)} - /> - - onSizeChange(undefined, e.target.valueAsNumber) - } - /> - - + - )} + ); } diff --git a/packages/editor/src/toolbar/components/dropdown.tsx b/packages/editor/src/toolbar/components/dropdown.tsx index 0dc728cf4..6f9d6d162 100644 --- a/packages/editor/src/toolbar/components/dropdown.tsx +++ b/packages/editor/src/toolbar/components/dropdown.tsx @@ -5,6 +5,7 @@ import { Icons } from "../icons"; import { MenuPresenter, MenuPresenterProps } from "../../components/menu/menu"; import { MenuItem } from "../../components/menu/types"; import { useToolbarContext } from "../hooks/useToolbarContext"; +import { useToolbarLocation } from "../stores/toolbar-store"; type DropdownProps = { selectedItem: string | JSX.Element; @@ -16,7 +17,7 @@ export function Dropdown(props: DropdownProps) { const { items, selectedItem, buttonRef, menuWidth } = props; const internalRef = useRef(); const [isOpen, setIsOpen] = useState(false); - const { toolbarLocation } = useToolbarContext(); + const toolbarLocation = useToolbarLocation(); return ( <> diff --git a/packages/editor/src/toolbar/components/popup.tsx b/packages/editor/src/toolbar/components/popup.tsx index 02e7e00f2..896041e0e 100644 --- a/packages/editor/src/toolbar/components/popup.tsx +++ b/packages/editor/src/toolbar/components/popup.tsx @@ -17,9 +17,9 @@ export function Popup(props: PropsWithChildren) { sx={{ bg: "background", flexDirection: "column", - borderRadius: "default", - border: "1px solid var(--border)", - boxShadow: "menu", + // borderRadius: "default", + // border: "1px solid var(--border)", + // boxShadow: "menu", }} > {title && ( @@ -52,7 +52,7 @@ type PopupButtonProps = ButtonProps & { function PopupButton(props: PopupButtonProps) { const { text, loading, icon, iconColor, iconSize, ...restProps } = props; return ( - ); diff --git a/packages/editor/src/toolbar/floating-menus/index.tsx b/packages/editor/src/toolbar/floating-menus/index.tsx index bab012c96..4fa414b80 100644 --- a/packages/editor/src/toolbar/floating-menus/index.tsx +++ b/packages/editor/src/toolbar/floating-menus/index.tsx @@ -1,12 +1,22 @@ -import { TableRowFloatingMenu, TableColumnFloatingMenu } from "./table"; +import { + TableRowFloatingMenu, + TableColumnFloatingMenu, + TableFloatingMenu, +} from "./table/table"; import { SearchReplaceFloatingMenu } from "./search-replace"; import { FloatingMenuProps } from "./types"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function EditorFloatingMenus(props: FloatingMenuProps) { return ( <> - - + + + + + + + ); diff --git a/packages/editor/src/toolbar/floating-menus/search-replace.tsx b/packages/editor/src/toolbar/floating-menus/search-replace.tsx index fdf0b2a77..26ff90af1 100644 --- a/packages/editor/src/toolbar/floating-menus/search-replace.tsx +++ b/packages/editor/src/toolbar/floating-menus/search-replace.tsx @@ -1,187 +1,38 @@ -import { Input } from "@rebass/forms"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { Flex } from "rebass"; -import { MenuPresenter } from "../../components/menu/menu"; +import { + ActionSheetPresenter, + MenuPresenter, + PopupPresenter, +} from "../../components/menu/menu"; import { SearchStorage } from "../../extensions/search-replace"; -import { Popup } from "../components/popup"; -import { ToolButton } from "../components/tool-button"; import { FloatingMenuProps } from "./types"; +import { SearchReplacePopup } from "../popups/search-replace"; +import { DesktopOnly, MobileOnly } from "../../components/responsive"; export function SearchReplaceFloatingMenu(props: FloatingMenuProps) { const { editor } = props; - const { isSearching, selectedText } = editor.storage - .searchreplace as SearchStorage; - - const [matchCase, setMatchCase] = useState(false); - const [matchWholeWord, setMatchWholeWord] = useState(false); - const [enableRegex, setEnableRegex] = useState(false); - const replaceText = useRef(""); - const searchInputRef = useRef(); - - const search = useCallback( - (term: string) => { - editor.commands.search(term, { - matchCase, - enableRegex, - matchWholeWord, - }); - }, - [matchCase, enableRegex, matchWholeWord] - ); - - useEffect(() => { - if (!searchInputRef.current) return; - search(searchInputRef.current.value); - }, [search, matchCase, matchWholeWord, enableRegex]); - - useEffect(() => { - if (isSearching && selectedText) { - if (searchInputRef.current) { - const input = searchInputRef.current; - setTimeout(() => { - input.value = selectedText; - input.focus(); - }, 0); - } - search(selectedText); - } - }, [isSearching, selectedText, search]); + const { isSearching } = editor.storage.searchreplace as SearchStorage; if (!isSearching) return null; return ( - {}} - options={{ - type: "autocomplete", - position: { - target: - document.querySelector(".editor-toolbar") || "mouse", - isTargetAbsolute: true, - location: "below", - align: "end", - }, - }} - > - - - - - { - search(e.target.value); - }} - /> - - setMatchCase((s) => !s)} - iconSize={14} - /> - setMatchWholeWord((s) => !s)} - iconSize={14} - /> - setEnableRegex((s) => !s)} - iconSize={14} - /> - - - editor.commands.moveToPreviousResult()} - sx={{ mr: 0 }} - iconSize={16} - /> - editor.commands.moveToNextResult()} - sx={{ mr: 0 }} - iconSize={16} - /> - editor.chain().focus().endSearch().run()} - iconSize={16} - sx={{ mr: 0 }} - /> - - - (replaceText.current = e.target.value)} - /> - editor.commands.replace(replaceText.current)} - sx={{ mr: 0 }} - iconSize={16} - /> - editor.commands.replaceAll(replaceText.current)} - sx={{ mr: 0 }} - iconSize={16} - /> - - - - + <> + editor.commands.endSearch()} + options={{ + type: "autocomplete", + position: { + target: + document.querySelector(".editor-toolbar") || "mouse", + isTargetAbsolute: true, + location: "below", + align: "end", + }, + }} + > + + + ); } diff --git a/packages/editor/src/toolbar/floating-menus/table.tsx b/packages/editor/src/toolbar/floating-menus/table.tsx deleted file mode 100644 index 1107a58b9..000000000 --- a/packages/editor/src/toolbar/floating-menus/table.tsx +++ /dev/null @@ -1,665 +0,0 @@ -import { Theme } from "@notesnook/theme"; -import { Slider } from "@rebass/forms"; -import { Editor, findParentNodeClosestToPos } from "@tiptap/core"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { Flex, Text } from "rebass"; -import { MenuPresenter } from "../../components/menu/menu"; -import { getElementPosition, MenuOptions } from "../../components/menu/useMenu"; -import { Popup } from "../components/popup"; -import { ToolButton } from "../components/tool-button"; -import { IconNames } from "../icons"; -// import { ColorPicker, DEFAULT_COLORS } from "../tools/colors"; -import { FloatingMenuProps } from "./types"; -import { selectedRect, TableMap, TableRect } from "prosemirror-tables"; -import { Transaction } from "prosemirror-state"; -import { MenuItem } from "../../components/menu/types"; - -export function TableRowFloatingMenu(props: FloatingMenuProps) { - const { editor } = props; - const theme = editor.storage.theme as Theme; - const [position, setPosition] = useState( - null - ); - const [isMenuOpen, setIsMenuOpen] = useState(false); - - useEffect(() => { - if ( - !editor.isActive("tableCell") && - !editor.isActive("tableRow") && - !editor.isActive("tableHeader") - ) { - setPosition(null); - return; - } - - let { $from } = editor.state.selection; - - const selectedNode = $from.node(); - const pos = selectedNode.isTextblock ? $from.before() : $from.pos; - - const currentRow = (editor.view.nodeDOM(pos) as HTMLElement)?.closest("tr"); - if (!currentRow) return; - setPosition((old) => { - if (old?.target === currentRow) return old; - - return { - isTargetAbsolute: true, - location: "left", - xOffset: -5, - target: currentRow, - // parent: editor.view.dom as HTMLElement, - }; - }); - }, [editor.state.selection]); - - if (!position) return null; - - return ( - {}} - options={{ - type: "autocomplete", - position, - }} - > - - setIsMenuOpen(true)} - iconSize={16} - sx={{ mr: 0, p: "3px", borderRadius: "small" }} - /> - editor.chain().focus().addRowAfter().run()} - sx={{ mr: 0, p: "3px", borderRadius: "small" }} - iconSize={16} - /> - - - { - setIsMenuOpen(false); - editor.commands.focus(); - }} - options={{ - type: "menu", - position: {}, - }} - items={[ - { - key: "addRowAbove", - type: "menuitem", - title: "Add row above", - onClick: () => editor.chain().focus().addRowBefore().run(), - icon: "insertRowAbove", - }, - { - key: "moveRowUp", - type: "menuitem", - title: "Move row up", - onClick: () => moveRowUp(editor), - icon: "moveRowUp", - }, - { - key: "moveRowDown", - type: "menuitem", - title: "Move row down", - onClick: () => moveRowDown(editor), - icon: "moveRowDown", - }, - { - key: "deleteRow", - type: "menuitem", - title: "Delete row", - onClick: () => editor.chain().focus().deleteRow().run(), - icon: "deleteRow", - }, - ]} - /> - - ); -} - -export function TableColumnFloatingMenu(props: FloatingMenuProps) { - const { editor } = props; - const [position, setPosition] = useState( - null - ); - const isInsideCellSelection = - !editor.state.selection.empty && - editor.state.selection.$anchor.node().type.name === "tableCell"; - - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [showCellProps, setShowCellProps] = useState(false); - const [menuPosition, setMenuPosition] = useState< - MenuOptions["position"] | null - >(null); - - useEffect(() => { - if ( - !editor.isActive("tableCell") && - !editor.isActive("tableRow") && - !editor.isActive("tableHeader") - ) { - setPosition(null); - return; - } - let { $from } = editor.state.selection; - - const selectedNode = $from.node(); - const pos = selectedNode.isTextblock ? $from.before() : $from.pos; - - const currentCell = (editor.view.nodeDOM(pos) as HTMLElement)?.closest( - "td,th" - ); - const currentTable = currentCell?.closest("table"); - - if (!currentCell || !currentTable) return; - - setPosition((old) => { - if (old?.target === currentCell) return old; - - return { - isTargetAbsolute: true, - location: "top", - align: "center", - yAnchor: currentTable, - yOffset: -2, - target: currentCell as HTMLElement, - }; - }); - }, [editor.state.selection]); - - if (!position) return null; - - const columnProperties: MenuItem[] = [ - { - key: "addColumnLeft", - type: "menuitem", - title: "Add column left", - onClick: () => editor.chain().focus().addColumnBefore().run(), - icon: "insertColumnLeft", - }, - { - key: "addColumnRight", - type: "menuitem", - title: "Add column right", - onClick: () => editor.chain().focus().addColumnAfter().run(), - icon: "insertColumnRight", - }, - { - key: "moveColumnLeft", - type: "menuitem", - title: "Move column left", - onClick: () => moveColumnLeft(editor), - icon: "moveColumnLeft", - }, - { - key: "moveColumnRight", - type: "menuitem", - title: "Move column right", - onClick: () => moveColumnRight(editor), - icon: "moveColumnRight", - }, - { - key: "deleteColumn", - type: "menuitem", - title: "Delete column", - onClick: () => editor.chain().focus().deleteColumn().run(), - icon: "deleteColumn", - }, - ]; - - const mergeSplitProperties: MenuItem[] = [ - { - key: "splitCells", - type: "menuitem", - title: "Split cells", - onClick: () => editor.chain().focus().splitCell().run(), - icon: "splitCells", - }, - { - key: "mergeCells", - type: "menuitem", - title: "Merge cells", - onClick: () => editor.chain().focus().mergeCells().run(), - icon: "mergeCells", - }, - ]; - - const cellProperties: MenuItem[] = [ - { - key: "cellProperties", - type: "menuitem", - title: "Cell properties", - onClick: () => { - setShowCellProps(true); - setMenuPosition({ - target: position.target || undefined, - isTargetAbsolute: true, - yOffset: 10, - location: "below", - }); - }, - icon: "cellProperties", - }, - ]; - - const tableProperties: MenuItem[] = [ - { - key: "deleteTable", - type: "menuitem", - title: "Delete table", - icon: "deleteTable", - onClick: () => editor.chain().focus().deleteTable().run(), - }, - ]; - - return ( - {}} - options={{ - type: "autocomplete", - position, - }} - > - - setIsMenuOpen(true)} - iconSize={16} - sx={{ mr: 0, p: "3px", borderRadius: "small" }} - /> - editor.chain().focus().addColumnAfter().run()} - sx={{ mr: 0, p: "3px", borderRadius: "small" }} - iconSize={16} - /> - - - { - setIsMenuOpen(false); - editor.commands.focus(); - }} - options={{ - type: "menu", - position: {}, - }} - items={ - isInsideCellSelection - ? [...mergeSplitProperties, ...cellProperties] - : [ - ...columnProperties, - { type: "seperator", key: "cellSeperator" }, - ...cellProperties, - { type: "seperator", key: "tableSeperator" }, - ...tableProperties, - ] - } - /> - - { - setShowCellProps(false); - editor.commands.focus(); - }} - options={{ - type: "menu", - position: menuPosition || {}, - }} - items={[]} - > - setShowCellProps(false)} - /> - - - ); -} - -type CellPropertiesProps = { editor: Editor; onClose: () => void }; -function CellProperties(props: CellPropertiesProps) { - const { editor, onClose } = props; - const attributes = editor.getAttributes("tableCell"); - console.log(attributes); - return ( - - - - editor.commands.setCellAttribute("backgroundColor", color) - } - /> - - editor.commands.setCellAttribute("color", color) - } - /> - - editor.commands.setCellAttribute("borderColor", color) - } - /> - - - Border width - {attributes.borderWidth || 1}px - - { - editor.commands.setCellAttribute( - "borderWidth", - e.target.valueAsNumber - ); - }} - /> - - - - ); -} -type ColorPickerToolProps = { - color: string; - title: string; - icon: IconNames; - onColorChange: (color?: string) => void; -}; -function ColorPickerTool(props: ColorPickerToolProps) { - const { color, title, icon, onColorChange } = props; - const [isOpen, setIsOpen] = useState(false); - const buttonRef = useRef(null); - - return ( - <> - - {title} - setIsOpen(true)} - /> - - - setIsOpen(false)} - items={[]} - options={{ - type: "menu", - position: { - target: buttonRef.current || undefined, - location: "below", - align: "center", - isTargetAbsolute: true, - yOffset: 5, - }, - }} - > - - {/* onColorChange()} - onChange={(color) => onColorChange(color)} - /> */} - - - - ); -} - -/** - * Done: - * insertTable - * - * addRowBefore - * addRowAfter - * deleteRow - * - * addColumnBefore - * addColumnAfter - * deleteColumn - * - * setCellAttribute - * - * deleteTable - * - * mergeCells - * splitCell - * mergeOrSplit - * - * toggleHeaderColumn - * toggleHeaderRow - * toggleHeaderCell - * fixTables - * goToNextCell - * goToPreviousCell - */ - -function moveColumnRight(editor: Editor) { - const { tr } = editor.state; - const rect = selectedRect(editor.state); - if (rect.right === rect.map.width) return; - - const transaction = moveColumn(tr, rect, rect.left, rect.left + 1); - if (!transaction) return; - - editor.view.dispatch(transaction); -} - -function moveColumnLeft(editor: Editor) { - const { tr } = editor.state; - const rect = selectedRect(editor.state); - if (rect.left === 0) return; - - const transaction = moveColumn(tr, rect, rect.left, rect.left - 1); - if (!transaction) return; - - editor.view.dispatch(transaction); -} - -function moveRowDown(editor: Editor) { - const { tr } = editor.state; - const rect = selectedRect(editor.state); - if (rect.top + 1 === rect.map.height) return; - - const transaction = moveRow(tr, rect, rect.top, rect.top + 1); - if (!transaction) return; - - editor.view.dispatch(transaction); -} - -function moveRowUp(editor: Editor) { - const { tr } = editor.state; - const rect = selectedRect(editor.state); - if (rect.top === 0) return; - - const transaction = moveRow(tr, rect, rect.top, rect.top - 1); - if (!transaction) return; - - editor.view.dispatch(transaction); -} - -function moveColumn( - tr: Transaction, - rect: TableRect, - from: number, - to: number -) { - let fromCells = getColumnCells(rect, from); - let toCells = getColumnCells(rect, to); - - return moveCells(tr, rect, fromCells, toCells); -} - -function getColumnCells({ map, table }: TableRect, col: number) { - let cells = []; - for (let row = 0; row < map.height; ) { - let index = row * map.width + col; - if (index >= map.map.length) break; - - let pos = map.map[index]; - - let cell = table.nodeAt(pos); - if (!cell) continue; - cells.push({ cell, pos }); - - row += cell.attrs.rowspan; - console.log(cell.textContent); - } - - return cells; -} - -function moveRow( - tr: Transaction, - rect: TableRect, - from: number, - to: number -) { - let fromCells = getRowCells(rect, from); - let toCells = getRowCells(rect, to); - return moveCells(tr, rect, fromCells, toCells); -} - -function getRowCells({ map, table }: TableRect, row: number) { - let cells = []; - for (let col = 0, index = row * map.width; col < map.width; col++, index++) { - if (index >= map.map.length) break; - - let pos = map.map[index]; - let cell = table.nodeAt(pos); - - if (!cell) continue; - cells.push({ cell, pos }); - - col += cell.attrs.colspan - 1; - } - - return cells; -} - -function moveCells( - tr: Transaction, - rect: TableRect, - fromCells: any[], - toCells: any[] -) { - if (fromCells.length !== toCells.length) return; - let mapStart = tr.mapping.maps.length; - - for (let i = 0; i < toCells.length; ++i) { - const fromCell = fromCells[i]; - const toCell = toCells[i]; - - let fromStart = tr.mapping - .slice(mapStart) - .map(rect.tableStart + fromCell.pos); - let fromEnd = fromStart + fromCell.cell.nodeSize; - const fromSlice = tr.doc.slice(fromStart, fromEnd); - - const toStart = tr.mapping - .slice(mapStart) - .map(rect.tableStart + toCell.pos); - const toEnd = toStart + toCell.cell.nodeSize; - const toSlice = tr.doc.slice(toStart, toEnd); - - tr.replace(toStart, toEnd, fromSlice); - - fromStart = tr.mapping.slice(mapStart).map(rect.tableStart + fromCell.pos); - fromEnd = fromStart + fromCell.cell.nodeSize; - tr.replace(fromStart, fromEnd, toSlice); - } - - return tr; -} diff --git a/packages/editor/src/toolbar/floating-menus/table/actions.ts b/packages/editor/src/toolbar/floating-menus/table/actions.ts new file mode 100644 index 000000000..15c256208 --- /dev/null +++ b/packages/editor/src/toolbar/floating-menus/table/actions.ts @@ -0,0 +1,163 @@ +import { Theme } from "@notesnook/theme"; +import { Slider } from "@rebass/forms"; +import { Editor, findParentNodeClosestToPos } from "@tiptap/core"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Flex, Text } from "rebass"; +import { + ActionSheetPresenter, + MenuPresenter, +} from "../../../components/menu/menu"; +import { + getElementPosition, + MenuOptions, +} from "../../../components/menu/useMenu"; +import { Popup } from "../../components/popup"; +import { ToolButton, ToolButtonProps } from "../../components/tool-button"; +import { IconNames } from "../../icons"; +// import { ColorPicker, DEFAULT_COLORS } from "../tools/colors"; +import { FloatingMenuProps } from "../types"; +import { selectedRect, TableMap, TableRect } from "prosemirror-tables"; +import { Transaction } from "prosemirror-state"; +import { MenuItem } from "../../../components/menu/types"; +import { DesktopOnly, MobileOnly } from "../../../components/responsive"; +import { ToolProps } from "../../types"; + +function moveColumnRight(editor: Editor) { + const { tr } = editor.state; + const rect = selectedRect(editor.state); + if (rect.right === rect.map.width) return; + + const transaction = moveColumn(tr, rect, rect.left, rect.left + 1); + if (!transaction) return; + + editor.view.dispatch(transaction); +} + +function moveColumnLeft(editor: Editor) { + const { tr } = editor.state; + const rect = selectedRect(editor.state); + if (rect.left === 0) return; + + const transaction = moveColumn(tr, rect, rect.left, rect.left - 1); + if (!transaction) return; + + editor.view.dispatch(transaction); +} + +function moveRowDown(editor: Editor) { + const { tr } = editor.state; + const rect = selectedRect(editor.state); + if (rect.top + 1 === rect.map.height) return; + + const transaction = moveRow(tr, rect, rect.top, rect.top + 1); + if (!transaction) return; + + editor.view.dispatch(transaction); +} + +function moveRowUp(editor: Editor) { + const { tr } = editor.state; + const rect = selectedRect(editor.state); + if (rect.top === 0) return; + + const transaction = moveRow(tr, rect, rect.top, rect.top - 1); + if (!transaction) return; + + editor.view.dispatch(transaction); +} + +function moveColumn( + tr: Transaction, + rect: TableRect, + from: number, + to: number +) { + let fromCells = getColumnCells(rect, from); + let toCells = getColumnCells(rect, to); + + return moveCells(tr, rect, fromCells, toCells); +} + +function getColumnCells({ map, table }: TableRect, col: number) { + let cells = []; + for (let row = 0; row < map.height; ) { + let index = row * map.width + col; + if (index >= map.map.length) break; + + let pos = map.map[index]; + + let cell = table.nodeAt(pos); + if (!cell) continue; + cells.push({ cell, pos }); + + row += cell.attrs.rowspan; + console.log(cell.textContent); + } + + return cells; +} + +function moveRow( + tr: Transaction, + rect: TableRect, + from: number, + to: number +) { + let fromCells = getRowCells(rect, from); + let toCells = getRowCells(rect, to); + return moveCells(tr, rect, fromCells, toCells); +} + +function getRowCells({ map, table }: TableRect, row: number) { + let cells = []; + for (let col = 0, index = row * map.width; col < map.width; col++, index++) { + if (index >= map.map.length) break; + + let pos = map.map[index]; + let cell = table.nodeAt(pos); + + if (!cell) continue; + cells.push({ cell, pos }); + + col += cell.attrs.colspan - 1; + } + + return cells; +} + +function moveCells( + tr: Transaction, + rect: TableRect, + fromCells: any[], + toCells: any[] +) { + if (fromCells.length !== toCells.length) return; + let mapStart = tr.mapping.maps.length; + + for (let i = 0; i < toCells.length; ++i) { + const fromCell = fromCells[i]; + const toCell = toCells[i]; + + let fromStart = tr.mapping + .slice(mapStart) + .map(rect.tableStart + fromCell.pos); + let fromEnd = fromStart + fromCell.cell.nodeSize; + const fromSlice = tr.doc.slice(fromStart, fromEnd); + + const toStart = tr.mapping + .slice(mapStart) + .map(rect.tableStart + toCell.pos); + const toEnd = toStart + toCell.cell.nodeSize; + const toSlice = tr.doc.slice(toStart, toEnd); + + tr.replace(toStart, toEnd, fromSlice); + + fromStart = tr.mapping.slice(mapStart).map(rect.tableStart + fromCell.pos); + fromEnd = fromStart + fromCell.cell.nodeSize; + tr.replace(fromStart, fromEnd, toSlice); + } + + return tr; +} + +export { moveColumnLeft, moveColumnRight, moveRowDown, moveRowUp }; diff --git a/packages/editor/src/toolbar/floating-menus/table/index.ts b/packages/editor/src/toolbar/floating-menus/table/index.ts new file mode 100644 index 000000000..50a03f9a0 --- /dev/null +++ b/packages/editor/src/toolbar/floating-menus/table/index.ts @@ -0,0 +1,5 @@ +export { + TableColumnFloatingMenu, + TableFloatingMenu, + TableRowFloatingMenu, +} from "./table"; diff --git a/packages/editor/src/toolbar/floating-menus/table/table.tsx b/packages/editor/src/toolbar/floating-menus/table/table.tsx new file mode 100644 index 000000000..e9ddf912c --- /dev/null +++ b/packages/editor/src/toolbar/floating-menus/table/table.tsx @@ -0,0 +1,234 @@ +import { useEffect, useState } from "react"; +import { Flex } from "rebass"; +import { MenuPresenter } from "../../../components/menu/menu"; +import { MenuOptions } from "../../../components/menu/useMenu"; +// import { ColorPicker, DEFAULT_COLORS } from "../tools/colors"; +import { FloatingMenuProps } from "../types"; +import { + ColumnProperties, + InsertColumnRight, + InsertRowBelow, + RowProperties, +} from "./tools"; +import { getToolbarElement } from "../../utils/dom"; +import { useToolbarContext } from "../../hooks/useToolbarContext"; +import { useToolbarLocation } from "../../stores/toolbar-store"; + +export function TableRowFloatingMenu(props: FloatingMenuProps) { + const { editor } = props; + // const theme = editor.storage.theme as Theme; + const [position, setPosition] = useState( + null + ); + + useEffect(() => { + if ( + !editor.isActive("tableCell") && + !editor.isActive("tableRow") && + !editor.isActive("tableHeader") + ) { + setPosition(null); + return; + } + + let { $from } = editor.state.selection; + + const selectedNode = $from.node(); + const pos = selectedNode.isTextblock ? $from.before() : $from.pos; + + const currentRow = (editor.view.nodeDOM(pos) as HTMLElement)?.closest("tr"); + if (!currentRow) return; + setPosition((old) => { + if (old?.target === currentRow) return old; + + return { + isTargetAbsolute: true, + location: "left", + xOffset: -5, + target: currentRow, + // parent: editor.view.dom as HTMLElement, + }; + }); + }, [editor.state.selection]); + + if (!position) return null; + + return ( + {}} + options={{ + type: "autocomplete", + position, + }} + > + + + + + + ); +} + +export function TableColumnFloatingMenu(props: FloatingMenuProps) { + const { editor } = props; + const [position, setPosition] = useState( + null + ); + + useEffect(() => { + if ( + !editor.isActive("tableCell") && + !editor.isActive("tableRow") && + !editor.isActive("tableHeader") + ) { + setPosition(null); + return; + } + let { $from } = editor.state.selection; + + const selectedNode = $from.node(); + const pos = selectedNode.isTextblock ? $from.before() : $from.pos; + + const currentCell = (editor.view.nodeDOM(pos) as HTMLElement)?.closest( + "td,th" + ); + const currentTable = currentCell?.closest("table"); + + if (!currentCell || !currentTable) return; + + setPosition((old) => { + if (old?.target === currentCell) return old; + + return { + isTargetAbsolute: true, + location: "top", + align: "center", + yAnchor: currentTable, + yOffset: 2, + target: currentCell as HTMLElement, + }; + }); + }, [editor.state.selection]); + + if (!position) return null; + + return ( + {}} + options={{ + type: "autocomplete", + position, + }} + > + + + + + + ); +} + +export function TableFloatingMenu(props: FloatingMenuProps) { + const { editor } = props; + const toolbarLocation = useToolbarLocation(); + if (!editor.isActive("table")) return null; + return ( + {}} + options={{ + type: "autocomplete", + position: { + isTargetAbsolute: true, + target: getToolbarElement(), + location: toolbarLocation === "bottom" ? "top" : "below", + }, + }} + > + + + + + + + + ); +} diff --git a/packages/editor/src/toolbar/floating-menus/table/tools.tsx b/packages/editor/src/toolbar/floating-menus/table/tools.tsx new file mode 100644 index 000000000..8ab555f03 --- /dev/null +++ b/packages/editor/src/toolbar/floating-menus/table/tools.tsx @@ -0,0 +1,262 @@ +import { Theme } from "@notesnook/theme"; +import { Slider } from "@rebass/forms"; +import { Editor, findParentNodeClosestToPos } from "@tiptap/core"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Flex, Text } from "rebass"; +import { + ActionSheetPresenter, + MenuPresenter, + PopupPresenter, +} from "../../../components/menu/menu"; +import { + getElementPosition, + MenuOptions, +} from "../../../components/menu/useMenu"; +import { Popup } from "../../components/popup"; +import { ToolButton, ToolButtonProps } from "../../components/tool-button"; +import { IconNames } from "../../icons"; +// import { ColorPicker, DEFAULT_COLORS } from "../tools/colors"; +import { FloatingMenuProps } from "../types"; +import { selectedRect, TableMap, TableRect } from "prosemirror-tables"; +import { Transaction } from "prosemirror-state"; +import { MenuItem } from "../../../components/menu/types"; +import { DesktopOnly, MobileOnly } from "../../../components/responsive"; +import { ToolProps } from "../../types"; +import { CellProperties } from "../../popups/cell-properties"; +import { + moveColumnLeft, + moveColumnRight, + moveRowDown, + moveRowUp, +} from "./actions"; + +type TableToolProps = ToolProps & { variant: ToolButtonProps["variant"] }; + +export function RowProperties(props: TableToolProps) { + const { editor, ...toolProps } = props; + const [isMenuOpen, setIsMenuOpen] = useState(false); + return ( + <> + setIsMenuOpen(true)} + // iconSize={16} + // sx={{ mr: 0, p: "3px", borderRadius: "small" }} + /> + { + setIsMenuOpen(false); + editor.commands.focus(); + }} + options={{ + type: "menu", + position: {}, + }} + items={[ + { + key: "addRowAbove", + type: "menuitem", + title: "Add row above", + onClick: () => editor.chain().focus().addRowBefore().run(), + icon: "insertRowAbove", + }, + { + key: "moveRowUp", + type: "menuitem", + title: "Move row up", + onClick: () => moveRowUp(editor), + icon: "moveRowUp", + }, + { + key: "moveRowDown", + type: "menuitem", + title: "Move row down", + onClick: () => moveRowDown(editor), + icon: "moveRowDown", + }, + { + key: "deleteRow", + type: "menuitem", + title: "Delete row", + onClick: () => editor.chain().focus().deleteRow().run(), + icon: "deleteRow", + }, + ]} + /> + + ); +} + +export function InsertRowBelow(props: TableToolProps) { + const { editor, ...toolProps } = props; + return ( + editor.chain().focus().addRowAfter().run()} + // sx={{ mr: 0, p: "3px", borderRadius: "small" }} + // iconSize={16} + /> + ); +} + +type ColumnPropertiesProps = TableToolProps & { + currentCell?: HTMLElement; +}; +export function ColumnProperties(props: ColumnPropertiesProps) { + const { editor, currentCell, ...toolProps } = props; + const [isMenuOpen, setIsMenuOpen] = useState(false); + const isInsideCellSelection = + !editor.state.selection.empty && + editor.state.selection.$anchor.node().type.name === "tableCell"; + + const [showCellProps, setShowCellProps] = useState(false); + const [menuPosition, setMenuPosition] = useState< + MenuOptions["position"] | null + >(null); + + const columnProperties: MenuItem[] = [ + { + key: "addColumnLeft", + type: "menuitem", + title: "Add column left", + onClick: () => editor.chain().focus().addColumnBefore().run(), + icon: "insertColumnLeft", + }, + { + key: "addColumnRight", + type: "menuitem", + title: "Add column right", + onClick: () => editor.chain().focus().addColumnAfter().run(), + icon: "insertColumnRight", + }, + { + key: "moveColumnLeft", + type: "menuitem", + title: "Move column left", + onClick: () => moveColumnLeft(editor), + icon: "moveColumnLeft", + }, + { + key: "moveColumnRight", + type: "menuitem", + title: "Move column right", + onClick: () => moveColumnRight(editor), + icon: "moveColumnRight", + }, + { + key: "deleteColumn", + type: "menuitem", + title: "Delete column", + onClick: () => editor.chain().focus().deleteColumn().run(), + icon: "deleteColumn", + }, + ]; + + const mergeSplitProperties: MenuItem[] = [ + { + key: "splitCells", + type: "menuitem", + title: "Split cells", + onClick: () => editor.chain().focus().splitCell().run(), + icon: "splitCells", + }, + { + key: "mergeCells", + type: "menuitem", + title: "Merge cells", + onClick: () => editor.chain().focus().mergeCells().run(), + icon: "mergeCells", + }, + ]; + + const cellProperties: MenuItem[] = [ + { + key: "cellProperties", + type: "menuitem", + title: "Cell properties", + onClick: () => { + setShowCellProps(true); + setMenuPosition({ + target: currentCell || undefined, + isTargetAbsolute: true, + yOffset: 10, + location: "below", + }); + }, + icon: "cellProperties", + }, + ]; + + const tableProperties: MenuItem[] = [ + { + key: "deleteTable", + type: "menuitem", + title: "Delete table", + icon: "deleteTable", + onClick: () => editor.chain().focus().deleteTable().run(), + }, + ]; + + return ( + <> + setIsMenuOpen(true)} + /> + { + setIsMenuOpen(false); + editor.commands.focus(); + }} + mobile="sheet" + items={ + isInsideCellSelection + ? [...mergeSplitProperties, ...cellProperties] + : [ + ...columnProperties, + { type: "seperator", key: "cellSeperator" }, + ...cellProperties, + { type: "seperator", key: "tableSeperator" }, + ...tableProperties, + ] + } + /> + + { + setShowCellProps(false); + editor.commands.focus(); + }} + options={{ + type: "menu", + position: menuPosition || {}, + }} + mobile="sheet" + > + setShowCellProps(false)} + /> + + + ); +} + +export function InsertColumnRight(props: TableToolProps) { + const { editor, ...toolProps } = props; + return ( + editor.chain().focus().addColumnAfter().run()} + // sx={{ mr: 0, p: "3px", borderRadius: "small" }} + // iconSize={16} + /> + ); +} diff --git a/packages/editor/src/toolbar/hooks/useToolbarContext.ts b/packages/editor/src/toolbar/hooks/useToolbarContext.ts index 68b7642e8..e3d80629f 100644 --- a/packages/editor/src/toolbar/hooks/useToolbarContext.ts +++ b/packages/editor/src/toolbar/hooks/useToolbarContext.ts @@ -1,11 +1,8 @@ import React, { useContext } from "react"; -export type ToolbarLocation = "top" | "bottom"; - export const ToolbarContext = React.createContext<{ currentPopup?: string; setCurrentPopup?: React.Dispatch>; - toolbarLocation?: ToolbarLocation; }>({}); export function useToolbarContext() { diff --git a/packages/editor/src/toolbar/icons.ts b/packages/editor/src/toolbar/icons.ts index 66cb62255..bf3ff3b2f 100644 --- a/packages/editor/src/toolbar/icons.ts +++ b/packages/editor/src/toolbar/icons.ts @@ -78,6 +78,10 @@ import { mdiMoviePlusOutline, mdiLink, mdiChevronRight, + mdiTableRow, + mdiTableColumn, + mdiTableColumnWidth, + mdiTableRowHeight, } from "@mdi/js"; export const Icons = { @@ -113,12 +117,14 @@ export const Icons = { upload: mdiUploadOutline, attachment: mdiAttachment, table: mdiTable, - insertRowBelow: mdiPlus, + rowProperties: mdiTableRowHeight, + insertRowBelow: mdiTableRowPlusAfter, insertRowAbove: mdiTableRowPlusBefore, moveRowDown: mdiArrowExpandDown, moveRowUp: mdiArrowExpandUp, deleteRow: mdiTableRowRemove, toggleHeaderRow: mdiTableBorder, + columnProperties: mdiTableColumnWidth, insertColumnRight: mdiTableColumnPlusAfter, insertColumnLeft: mdiTableColumnPlusBefore, moveColumnRight: mdiArrowExpandRight, diff --git a/packages/editor/src/toolbar/popups/cell-properties.tsx b/packages/editor/src/toolbar/popups/cell-properties.tsx new file mode 100644 index 000000000..8b137c74c --- /dev/null +++ b/packages/editor/src/toolbar/popups/cell-properties.tsx @@ -0,0 +1,145 @@ +import { Slider } from "@rebass/forms"; +import { Editor } from "@tiptap/core"; +import { useRef, useState } from "react"; +import { Flex, Text } from "rebass"; +import { MenuPresenter } from "../../components/menu/menu"; +import { Popup } from "../components/popup"; +import { ToolButton } from "../components/tool-button"; +import { IconNames } from "../icons"; + +type CellPropertiesProps = { editor: Editor; onClose: () => void }; +export function CellProperties(props: CellPropertiesProps) { + const { editor, onClose } = props; + const attributes = editor.getAttributes("tableCell"); + return ( + + + + editor.commands.setCellAttribute("backgroundColor", color) + } + /> + + editor.commands.setCellAttribute("color", color) + } + /> + + editor.commands.setCellAttribute("borderColor", color) + } + /> + + + Border width + {attributes.borderWidth || 1}px + + { + editor.commands.setCellAttribute( + "borderWidth", + e.target.valueAsNumber + ); + }} + /> + + + + ); +} +type ColorPickerToolProps = { + color: string; + title: string; + icon: IconNames; + onColorChange: (color?: string) => void; +}; +function ColorPickerTool(props: ColorPickerToolProps) { + const { color, title, icon, onColorChange } = props; + const [isOpen, setIsOpen] = useState(false); + const buttonRef = useRef(null); + + return ( + <> + + {title} + setIsOpen(true)} + /> + + + setIsOpen(false)} + items={[]} + options={{ + type: "menu", + position: { + target: buttonRef.current || undefined, + location: "below", + align: "center", + isTargetAbsolute: true, + yOffset: 5, + }, + }} + > + + {/* onColorChange()} + onChange={(color) => onColorChange(color)} + /> */} + + + + ); +} diff --git a/packages/editor/src/toolbar/popups/embed-popup.tsx b/packages/editor/src/toolbar/popups/embed-popup.tsx index bde63c2d1..dfcfe7ed1 100644 --- a/packages/editor/src/toolbar/popups/embed-popup.tsx +++ b/packages/editor/src/toolbar/popups/embed-popup.tsx @@ -100,7 +100,7 @@ export function EmbedPopup(props: EmbedPopupProps) { }, }} > - + {error && ( )} {/* */} - +