This commit is contained in:
thecodrr
2022-04-17 07:22:15 +05:00
parent 1db666e6d0
commit 7ae6590350
132 changed files with 10214 additions and 206 deletions

View File

@@ -0,0 +1 @@
export { MenuPresenter } from "./menu";

View File

@@ -0,0 +1 @@
export { MenuPresenter } from "./menu";

View File

@@ -0,0 +1,21 @@
import { PropsWithChildren } from "react";
import { FlexProps } from "rebass";
import { MenuOptions } from "./useMenu";
import { MenuItem as MenuItemType } from "./types";
declare type MenuProps = MenuContainerProps & {
items: MenuItemType[];
closeMenu: () => void;
};
export declare function Menu(props: MenuProps): JSX.Element;
declare type MenuContainerProps = FlexProps & {
title?: string;
};
declare type MenuPresenterProps = MenuContainerProps & {
items: MenuItemType[];
options: MenuOptions;
isOpen: boolean;
onClose: () => void;
className?: string;
};
export declare function MenuPresenter(props: PropsWithChildren<MenuPresenterProps>): JSX.Element;
export {};

View File

@@ -0,0 +1,234 @@
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 __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;
};
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useRef, useEffect, useState, } from "react";
import { Box, Flex, Text } from "rebass";
import { getPosition } from "./useMenu";
// import { FlexScrollContainer } from "../scrollcontainer";
import MenuItem from "./menuitem";
// import { useMenuTrigger, useMenu, getPosition } from "../../hooks/useMenu";
import Modal from "react-modal";
// import { store as selectionStore } from "../../stores/selectionstore";
function useMenuFocus(items, onAction, onClose) {
var _a = useState(-1), focusIndex = _a[0], setFocusIndex = _a[1];
var _b = useState(false), isSubmenuOpen = _b[0], setIsSubmenuOpen = _b[1];
var moveItemIntoView = useCallback(function (index) {
var item = items[index];
if (!item)
return;
var element = document.getElementById(item.key);
if (!element)
return;
element.scrollIntoView({
behavior: "auto",
});
}, [items]);
var onKeyDown = useCallback(function (e) {
var isSeperator = function (i) { var _a, _b; return items && (((_a = items[i]) === null || _a === void 0 ? void 0 : _a.type) === "seperator" || ((_b = items[i]) === null || _b === void 0 ? void 0 : _b.isDisabled)); };
var moveDown = function (i) { return (i < items.length - 1 ? ++i : 0); };
var moveUp = function (i) { return (i > 0 ? --i : items.length - 1); };
var hasSubmenu = function (i) { var _a; return items && ((_a = items[i]) === null || _a === void 0 ? void 0 : _a.hasSubmenu); };
var openSubmenu = function (index) {
if (!hasSubmenu(index))
return;
setIsSubmenuOpen(true);
};
var closeSubmenu = function (index) {
if (!hasSubmenu(index))
return;
setIsSubmenuOpen(false);
};
setFocusIndex(function (i) {
var nextIndex = i;
switch (e.key) {
case "ArrowUp":
if (isSubmenuOpen)
break;
nextIndex = moveUp(i);
while (isSeperator(nextIndex)) {
nextIndex = moveUp(nextIndex);
}
break;
case "ArrowDown":
if (isSubmenuOpen)
break;
nextIndex = moveDown(i);
while (isSeperator(nextIndex)) {
nextIndex = moveDown(nextIndex);
}
break;
case "ArrowRight":
openSubmenu(i);
break;
case "ArrowLeft":
closeSubmenu(i);
break;
case "Enter":
onAction && onAction(e);
break;
case "Escape":
onClose && onClose(e);
break;
default:
break;
}
if (nextIndex !== i)
moveItemIntoView(nextIndex);
return nextIndex;
});
}, [items, isSubmenuOpen, moveItemIntoView, onAction]);
useEffect(function () {
window.addEventListener("keydown", onKeyDown);
return function () {
window.removeEventListener("keydown", onKeyDown);
};
}, [onKeyDown]);
return { focusIndex: focusIndex, setFocusIndex: setFocusIndex, isSubmenuOpen: isSubmenuOpen, setIsSubmenuOpen: setIsSubmenuOpen };
}
export function Menu(props) {
var _a;
var items = props.items, title = props.title, closeMenu = props.closeMenu, containerProps = __rest(props, ["items", "title", "closeMenu"]);
var hoverTimeout = useRef();
var onAction = useCallback(function (e, item) {
e.stopPropagation();
if (closeMenu)
closeMenu();
if (item.onClick) {
item.onClick();
}
}, [closeMenu]);
var _b = useMenuFocus(items, function (e) {
var item = items[focusIndex];
if (item)
onAction(e, item);
}, function () { return closeMenu(); }), focusIndex = _b.focusIndex, setFocusIndex = _b.setFocusIndex, isSubmenuOpen = _b.isSubmenuOpen, setIsSubmenuOpen = _b.setIsSubmenuOpen;
var subMenuRef = useRef(null);
useEffect(function () {
var item = items[focusIndex];
if (!item || !subMenuRef.current)
return;
var menuItemElement = document.getElementById(item.key);
if (!menuItemElement)
return;
if (!isSubmenuOpen) {
subMenuRef.current.style.visibility = "hidden";
return;
}
var _a = getPosition(subMenuRef.current, {
yOffset: menuItemElement.offsetHeight,
target: menuItemElement,
location: "right",
}), top = _a.top, left = _a.left;
subMenuRef.current.style.visibility = "visible";
subMenuRef.current.style.top = "".concat(top, "px");
subMenuRef.current.style.left = "".concat(left, "px");
}, [isSubmenuOpen, focusIndex, items]);
return (_jsxs(_Fragment, { children: [_jsx(MenuContainer, __assign({}, containerProps, { children: items.map(function (item, index) { return (_jsx(MenuItem, { item: item, onClick: function (e) {
var _a;
if ((_a = item.items) === null || _a === void 0 ? void 0 : _a.length) {
setFocusIndex(index);
setIsSubmenuOpen(true);
}
else
onAction(e, item);
}, isFocused: focusIndex === index, onMouseEnter: function () {
var _a;
if (item.isDisabled) {
setFocusIndex(-1);
return;
}
if (hoverTimeout.current)
clearTimeout(hoverTimeout.current);
setFocusIndex(index);
setIsSubmenuOpen(false);
if ((_a = item.items) === null || _a === void 0 ? void 0 : _a.length) {
hoverTimeout.current = setTimeout(function () {
setIsSubmenuOpen(true);
}, 500);
}
}, onMouseLeave: function () {
if (hoverTimeout.current)
clearTimeout(hoverTimeout.current);
} }, item.key)); }) })), isSubmenuOpen && (_jsx(Flex, __assign({ ref: subMenuRef, style: { visibility: "hidden" }, sx: {
position: "absolute",
} }, { children: _jsx(Menu, { items: ((_a = items[focusIndex]) === null || _a === void 0 ? void 0 : _a.items) || [], closeMenu: closeMenu }) })))] }));
}
function MenuContainer(props) {
var children = props.children, title = props.title, sx = props.sx, flexProps = __rest(props, ["children", "title", "sx"]);
return (_jsxs(Box, __assign({ className: "menuContainer", as: "ul", tabIndex: -1, sx: __assign({ bg: "background", py: 1, display: "flex", flexDirection: "column", position: "relative", listStyle: "none", padding: 0, margin: 0, borderRadius: "default", boxShadow: "menu", border: "1px solid var(--border)", width: 220 }, sx) }, flexProps, { children: [title && (_jsx(Text, __assign({ sx: {
fontFamily: "body",
fontSize: "subtitle",
color: "primary",
py: "8px",
px: 3,
borderBottom: "1px solid",
borderBottomColor: "border",
wordWrap: "break-word",
} }, { children: title }))), children] })));
}
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"]);
// const { isOpen, closeMenu } = useMenuTrigger();
// const { items, } = useMenu();
var position = options.position, type = options.type;
var isAutocomplete = type === "autocomplete";
var contentRef = useRef();
useEffect(function () {
if (!contentRef.current || !position)
return;
var menu = contentRef.current;
var menuPosition = getPosition(menu, position);
menu.style.top = menuPosition.top + "px";
menu.style.left = menuPosition.left + "px";
}, [position]);
return (_jsx(Modal, __assign({ contentRef: function (ref) { return (contentRef.current = ref); }, className: className || "menuContainer", role: "menu", isOpen: isOpen, appElement: document.body, shouldCloseOnEsc: true, shouldReturnFocusAfterClose: true, shouldCloseOnOverlayClick: true, shouldFocusAfterRender: !isAutocomplete, ariaHideApp: !isAutocomplete, preventScroll: !isAutocomplete, onRequestClose: onClose, portalClassName: className || "menuPresenter", onAfterOpen: function (obj) {
if (!obj || !position)
return;
var menu = obj.contentEl;
var menuPosition = getPosition(menu, position);
menu.style.top = menuPosition.top + "px";
menu.style.left = menuPosition.left + "px";
}, overlayElement: function (props, contentEl) {
return (_jsx(Box, __assign({}, props, { style: __assign(__assign({}, props.style), { position: isAutocomplete ? "initial" : "fixed", zIndex: 1000, backgroundColor: isAutocomplete ? "transparent" : "unset" }) }, { children: contentEl })));
}, contentElement: function (props, children) { return (_jsx(Box, __assign({}, props, { style: {}, sx: {
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
width: "fit-content",
height: "fit-content",
position: "absolute",
backgroundColor: undefined,
padding: 0,
zIndex: 0,
outline: 0,
isolation: "isolate",
} }, { children: children }))); }, style: {
content: {},
overlay: {
zIndex: 999,
background: "transparent",
},
} }, { children: props.children ? (props.children) : (_jsx(Menu, __assign({ items: items, closeMenu: onClose }, containerProps))) })));
}

View File

@@ -0,0 +1,11 @@
/// <reference types="react" />
import { MenuItem } from "./types";
declare type MenuItemProps = {
item: MenuItem;
isFocused: boolean;
onMouseEnter: () => void;
onMouseLeave: () => void;
onClick: React.MouseEventHandler<HTMLButtonElement>;
};
declare function MenuItem(props: MenuItemProps): JSX.Element;
export default MenuItem;

View File

@@ -0,0 +1,49 @@
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, Fragment as _Fragment } from "react/jsx-runtime";
// import { Check, ChevronRight, Pro } from "../icons";
import { useRef } from "react";
import { Flex, Box, Text, Button } from "rebass";
import { Icon } from "../../toolbar/components/icon";
import { Icons } from "../../toolbar/icons";
function MenuItem(props) {
var item = props.item, isFocused = props.isFocused, onMouseEnter = props.onMouseEnter, onMouseLeave = props.onMouseLeave, onClick = props.onClick;
var title = item.title, key = item.key,
// color,
icon = item.icon,
// iconColor,
type = item.type, tooltip = item.tooltip, isDisabled = item.isDisabled, isChecked = item.isChecked, hasSubmenu = item.hasSubmenu, Component = item.component, modifier = item.modifier;
var itemRef = useRef(null);
if (type === "seperator")
return (_jsx(Box, { as: "li", sx: {
width: "95%",
height: "0.5px",
bg: "border",
my: 2,
alignSelf: "center",
} }, key));
return (_jsx(Flex, __assign({ as: "li", sx: { flex: 1, flexDirection: "column" }, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave }, { children: _jsx(Button, __assign({ id: key, "data-test-id": "menuitem-".concat(key), ref: itemRef, tabIndex: -1, variant: "menuitem", title: tooltip, disabled: isDisabled, onClick: onClick, sx: {
bg: isFocused ? "hover" : "transparent",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
} }, { children: Component ? (_jsx(Component, {})) : (_jsxs(_Fragment, { children: [_jsxs(Flex, { children: [icon && (_jsx(Icon, { path: Icons[icon], color: "text", size: 15, sx: { mr: 2 } })), _jsx(Text, __assign({ as: "span", sx: {
fontFamily: "body",
fontSize: "menu",
color: "text",
} }, { children: title }))] }), _jsxs(Flex, { children: [isChecked && _jsx(Icon, { path: Icons.check, size: 14 }), modifier && (_jsx(Text, __assign({ as: "span", sx: {
fontFamily: "body",
fontSize: "menu",
color: "fontTertiary",
} }, { children: modifier })))] })] })) }), key) })));
}
export default MenuItem;

View File

@@ -0,0 +1,17 @@
/// <reference types="react" />
import { IconNames } from "../../toolbar/icons";
export declare type MenuItem = {
type: "menuitem" | "seperator";
key: string;
component?: (props: any) => JSX.Element;
onClick?: () => void;
title?: string;
icon?: IconNames;
tooltip?: string;
isDisabled?: boolean;
isHidden?: boolean;
isChecked?: boolean;
hasSubmenu?: boolean;
modifier?: string;
items?: MenuItem[];
};

View File

@@ -0,0 +1,20 @@
// export type ResolverFunction<T, TData> = (
// data: any,
// item: MenuItem<TData>
// ) => T;
// export type Resolvable<T, TData> = T | ResolverFunction<T, TData>;
// export type MenuItem<TData> = {
// type: "menuitem" | "seperator";
// key: string;
// component?: Resolvable<(props: any) => JSX.Element, TData>;
// onClick?: (data: TData, item: MenuItem<TData>) => void;
// title?: Resolvable<string, TData>;
// icon?: Resolvable<string, TData>;
// tooltip?: Resolvable<string, TData>;
// disabled?: Resolvable<string, TData>;
// hidden?: Resolvable<boolean, TData>;
// checked?: Resolvable<boolean, TData>;
// modifier?: Resolvable<string[], TData>;
// items?: Resolvable<MenuItem<TData>[], TData>;
// };
export {};

View File

@@ -0,0 +1,28 @@
declare type PositionData = {
x: number;
y: number;
actualX: number;
actualY: number;
width?: number;
height?: number;
};
export declare type MenuOptions = {
type: "autocomplete" | "menu";
position?: PositionOptions;
};
declare type PositionOptions = {
target?: HTMLElement | "mouse";
isTargetAbsolute?: boolean;
location?: "right" | "left" | "below" | "top";
align?: "center" | "start" | "end";
yOffset?: number;
xOffset?: number;
yAnchor?: HTMLElement;
parent?: HTMLElement | Element;
};
export declare function getPosition(element: HTMLElement, options: PositionOptions): {
top: number;
left: number;
};
export declare function getElementPosition(element: HTMLElement, absolute: boolean): PositionData;
export {};

View File

@@ -0,0 +1,161 @@
var mousePosition = { x: 0, y: 0, actualX: 0, actualY: 0 };
window.addEventListener("mousemove", function (e) {
var _a = getMousePosition(e), x = _a.x, y = _a.y, actualX = _a.actualX, actualY = _a.actualY;
mousePosition.x = x;
mousePosition.y = y;
mousePosition.actualX = actualX;
mousePosition.actualY = actualY;
});
export function getPosition(element, options) {
var _a = options || {}, _b = _a.target, target = _b === void 0 ? "mouse" : _b, _c = _a.isTargetAbsolute, isTargetAbsolute = _c === void 0 ? false : _c, _d = _a.location, location = _d === void 0 ? undefined : _d, _e = _a.yOffset, yOffset = _e === void 0 ? 0 : _e, _f = _a.xOffset, xOffset = _f === void 0 ? 0 : _f, _g = _a.align, align = _g === void 0 ? "start" : _g, _h = _a.parent, parent = _h === void 0 ? document.body : _h, yAnchor = _a.yAnchor;
var _j = target === "mouse"
? mousePosition
: getElementPosition(target, isTargetAbsolute), x = _j.x, y = _j.y, width = _j.width, height = _j.height, actualX = _j.actualX, actualY = _j.actualY;
var elementWidth = element.offsetWidth;
var elementHeight = element.offsetHeight;
var windowWidth = parent.clientWidth;
var windowHeight = parent.clientHeight - 20;
var position = { top: 0, left: 0 };
if (windowWidth - actualX < elementWidth) {
var xDiff = actualX - x;
position.left = windowWidth - elementWidth;
position.left -= xDiff;
}
else {
position.left = x;
}
if (width) {
if (location === "right")
position.left += width;
else if (location === "left")
position.left -= elementWidth;
}
if (windowHeight - actualY < elementHeight) {
var yDiff = actualY - y;
position.top = windowHeight - elementHeight;
position.top -= yDiff;
}
else {
position.top = y;
}
if (height) {
if (location === "below")
position.top += height;
else if (location === "top")
position.top -= height;
}
if (target !== "mouse" && align === "center" && elementWidth > 0) {
position.left -= elementWidth / 2 - target.clientWidth / 2;
}
// Adjust menu height
if (elementHeight > windowHeight - position.top) {
element.style.maxHeight = "".concat(windowHeight - 20, "px");
}
if (yAnchor) {
var anchorY = getElementPosition(yAnchor, isTargetAbsolute);
position.top = anchorY.actualY - elementHeight;
}
position.top = position.top < 0 ? 0 : position.top;
position.left = position.left < 0 ? 0 : position.left;
position.top += yOffset;
position.left += xOffset;
return position;
}
function getMousePosition(e) {
var posx = 0;
var posy = 0;
if (!e && window.event)
e = window.event;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
posy =
e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return {
x: posx,
y: posy,
actualY: posy,
actualX: posx,
};
}
export function getElementPosition(element, absolute) {
var rect = element.getBoundingClientRect();
var position = {
x: element.offsetLeft,
y: element.offsetTop,
width: rect.width,
height: rect.height,
actualY: rect.y,
actualX: rect.x,
};
if (absolute) {
position.x = position.actualX;
position.y = position.actualY;
}
return position;
}
// function mapMenuItems<TData>(
// items: MenuItem<TData>[],
// data: TData
// ): ResolvedMenuItem<TData>[] {
// return items.reduce((prev, item) => {
// const { key, onClick: _onClick, disabled, hidden, checked, type } = item;
// const isHidden = resolveProp(hidden, data, item);
// if (isHidden) return prev;
// const isSeperator = type === "seperator";
// if (isSeperator) {
// prev.push({ isSeperator: true });
// return prev;
// }
// const title = resolveProp(item.title, data, item);
// const icon = resolveProp(item.icon, data, item);
// const isChecked = resolveProp(checked, data, item);
// const isDisabled = resolveProp(disabled, data, item);
// const items = resolveProp(item.items, data, item);
// const modifier = resolveProp(item.modifier, data, item);
// const onClick =
// typeof _onClick === "function" && _onClick.bind(this, data, item);
// const tooltip =
// isDisabled || resolveProp(item.tooltip, data, item) || title;
// const hasSubmenu = items?.length > 0;
// const menuItem: ResolvedMenuItem<TData> = {
// type,
// title,
// key,
// onClick,
// tooltip,
// isChecked,
// isDisabled: !!isDisabled,
// isHidden,
// hasSubmenu,
// icon,
// modifier: modifier?.join("+"),
// };
// if (hasSubmenu)
// menuItem.items = mapMenuItems(items, { ...data, parent: menuItem });
// prev.push(menuItem);
// return prev;
// }, []);
// }
// function resolveProp<T, TData>(
// prop: Resolvable<T, TData>,
// data: any,
// item: MenuItem<TData>
// ): T {
// if (typeof prop === "function" && (prop as any).isReactComponent) {
// return prop as T;
// }
// return isResolverFunction<T, TData>(prop) ? prop(data, item) : prop;
// }
// function isResolverFunction<T, TData>(
// prop: any
// ): prop is ResolverFunction<T, TData> {
// return typeof prop === "function";
// }

View File

@@ -0,0 +1,4 @@
/// <reference types="react" />
import { ToggleProps } from "react-toggle";
import "react-toggle/style.css";
export declare function Toggle(props: ToggleProps): JSX.Element;

View File

@@ -0,0 +1,18 @@
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import ReactToggle from "react-toggle";
import "react-toggle/style.css";
var css = ".react-toggle {\n display: flex;\n align-items: center;\n }\n \n .react-toggle-thumb {\n box-shadow: none;\n }\n \n .react-toggle-track {\n width: 30px;\n height: 18px;\n }\n \n .react-toggle-thumb {\n width: 16px;\n height: 16px;\n top: 0px;\n left: 1px;\n margin-top: 1px;\n }\n \n .react-toggle--checked .react-toggle-thumb {\n left: 13px;\n border-color: var(--primary);\n }\n \n .react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {\n box-shadow: none;\n }\n \n .react-toggle--focus .react-toggle-thumb {\n box-shadow: none;\n }\n ";
export function Toggle(props) {
return (_jsxs(_Fragment, { children: [_jsx("style", { children: css }), _jsx(ReactToggle, __assign({ size: 20, onChange: function () { }, icons: false }, props))] }));
}

View File

@@ -0,0 +1 @@
export declare const BulletList: import("@tiptap/core").Node<import("@tiptap/extension-bullet-list").BulletListOptions, any>;

View File

@@ -0,0 +1,19 @@
import TiptapBulletList from "@tiptap/extension-bullet-list";
export var BulletList = TiptapBulletList.extend({
addAttributes: function () {
return {
listType: {
default: null,
parseHTML: function (element) { return element.style.listStyleType; },
renderHTML: function (attributes) {
if (!attributes.listType) {
return {};
}
return {
style: "list-style-type: ".concat(attributes.listType),
};
},
},
};
},
});

View File

@@ -0,0 +1,3 @@
import { BulletList } from "./bullet-list";
export * from "./bullet-list";
export default BulletList;

View File

@@ -0,0 +1,3 @@
import { BulletList } from "./bullet-list";
export * from "./bullet-list";
export default BulletList;

View File

@@ -0,0 +1,22 @@
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";
declare type FontSizeOptions = {
types: string[];
defaultFontSize: number;
};
declare module "@tiptap/core" {
interface Commands<ReturnType> {
fontSize: {
/**
* Set the font family
*/
setFontSize: (fontSize: string) => ReturnType;
/**
* Unset the font family
*/
unsetFontSize: () => ReturnType;
};
}
}
export declare const FontSize: Extension<FontSizeOptions, any>;
export {};

View File

@@ -0,0 +1,49 @@
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";
export var FontSize = Extension.create({
name: "fontSize",
defaultOptions: {
types: ["textStyle"],
defaultFontSize: 16,
},
addGlobalAttributes: function () {
return [
{
types: this.options.types,
attributes: {
fontSize: {
default: "".concat(this.options.defaultFontSize, "px"),
parseHTML: function (element) { return element.style.fontSize; },
renderHTML: function (attributes) {
if (!attributes.fontSize) {
return {};
}
return {
style: "font-size: ".concat(attributes.fontSize),
};
},
},
},
},
];
},
addCommands: function () {
return {
setFontSize: function (fontSize) {
return function (_a) {
var chain = _a.chain;
return chain().setMark("textStyle", { fontSize: fontSize }).run();
};
},
unsetFontSize: function () {
return function (_a) {
var chain = _a.chain;
return chain()
.setMark("textStyle", { fontSize: null })
.removeEmptyTextStyle()
.run();
};
},
};
},
});

View File

@@ -0,0 +1,3 @@
import { FontSize } from "./font-size";
export * from "./font-size";
export default FontSize;

View File

@@ -0,0 +1,3 @@
import { FontSize } from "./font-size";
export * from "./font-size";
export default FontSize;

View File

@@ -0,0 +1,4 @@
/// <reference types="react" />
import { ImageProps } from "rebass";
import { NodeViewProps } from "@tiptap/react";
export declare function ImageComponent(props: ImageProps & NodeViewProps): JSX.Element;

View File

@@ -0,0 +1,129 @@
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 { Box, Flex, Image, Text } 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 { Popup } from "../../toolbar/components/popup";
import { Toggle } from "../../components/toggle";
import { Input } from "@rebass/forms";
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;
var editor = props.editor, updateAttributes = props.updateAttributes;
var imageRef = useRef();
var isActive = editor.isActive("image", { src: src });
var _b = useState(), isToolbarVisible = _b[0], setIsToolbarVisible = _b[1];
var theme = editor.storage.theme;
useEffect(function () {
setIsToolbarVisible(isActive);
}, [isActive]);
return (_jsx(NodeViewWrapper, { children: _jsx(ThemeProvider, __assign({ theme: theme }, { children: _jsx(Box, __assign({ sx: {
display: float ? "block" : "flex",
justifyContent: float
? "stretch"
: align === "center"
? "center"
: align === "left"
? "start"
: "end",
} }, { children: _jsxs(Resizable, __assign({ style: {
float: float ? (align === "left" ? "left" : "right") : "none",
}, size: {
height: height || "auto",
width: width || "auto",
}, maxWidth: "100%", onResizeStop: function (e, direction, ref, d) {
updateAttributes({
width: ref.clientWidth,
height: ref.clientHeight,
});
}, lockAspectRatio: true }, { children: [_jsx(Flex, __assign({ sx: { position: "relative", justifyContent: "end" } }, { children: isToolbarVisible && (_jsx(ImageToolbar, { editor: editor, float: float, align: align, height: height || 0, width: width || 0 })) })), _jsx(Image, __assign({ ref: imageRef, src: src, alt: alt, title: title, width: "100%", height: "100%", sx: {
border: isActive
? "2px solid var(--primary)"
: "2px solid transparent",
borderRadius: "default",
} }, props))] })) })) })) }));
}
function ImageToolbar(props) {
var editor = props.editor, float = props.float, height = props.height, width = props.width;
var _a = useState(false), 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",
top: -40,
mb: 2,
zIndex: 9999,
alignItems: "end",
} }, { children: [_jsxs(Flex, __assign({ sx: {
bg: "background",
boxShadow: "menu",
flexWrap: "nowrap",
borderRadius: "default",
mb: 2,
} }, { children: [_jsxs(Flex, __assign({ className: "toolbar-group", sx: {
pr: 1,
mr: 1,
borderRight: "1px solid var(--border)",
":last-of-type": { mr: 0, pr: 0, borderRight: "none" },
} }, { children: [_jsx(ToolButton, { toggled: false, title: "Align left", id: "alignLeft", icon: "alignLeft", onClick: function () {
return editor.chain().focus().setImageAlignment({ align: "left" }).run();
} }), float ? null : (_jsx(ToolButton, { toggled: false, title: "Align center", id: "alignCenter", icon: "alignCenter", onClick: function () {
return editor
.chain()
.focus()
.setImageAlignment({ align: "center" })
.run();
} })), _jsx(ToolButton, { toggled: false, title: "Align right", id: "alignRight", icon: "alignRight", onClick: function () {
return editor.chain().focus().setImageAlignment({ align: "right" }).run();
} })] })), _jsx(Flex, __assign({ className: "toolbar-group", sx: {
pr: 1,
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);
} })] }))] })) })))] })));
}

View File

@@ -0,0 +1,33 @@
import { Node } from "@tiptap/core";
export interface ImageOptions {
inline: boolean;
allowBase64: boolean;
HTMLAttributes: Record<string, any>;
}
export declare type ImageAttributes = Partial<ImageSizeOptions> & {
src: string;
alt?: string;
title?: string;
};
export declare type ImageAlignmentOptions = {
float?: boolean;
align?: "center" | "left" | "right";
};
export declare type ImageSizeOptions = {
width: number;
height: number;
};
declare module "@tiptap/core" {
interface Commands<ReturnType> {
image: {
/**
* Add an image
*/
setImage: (options: ImageAttributes) => ReturnType;
setImageAlignment: (options: ImageAlignmentOptions) => ReturnType;
setImageSize: (options: ImageSizeOptions) => ReturnType;
};
}
}
export declare const inputRegex: RegExp;
export declare const ImageNode: Node<ImageOptions, any>;

View File

@@ -0,0 +1,108 @@
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 { Node, nodeInputRule, mergeAttributes } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react";
import { ImageComponent } from "./component";
export var inputRegex = /(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/;
export var ImageNode = Node.create({
name: "image",
addOptions: function () {
return {
inline: false,
allowBase64: false,
HTMLAttributes: {},
};
},
inline: function () {
return this.options.inline;
},
group: function () {
return this.options.inline ? "inline" : "block";
},
draggable: true,
addAttributes: function () {
return {
src: {
default: null,
},
alt: {
default: null,
},
title: {
default: null,
},
width: { default: null },
height: { default: null },
float: {
default: false,
},
align: { default: "left" },
};
},
parseHTML: function () {
return [
{
tag: this.options.allowBase64
? "img[src]"
: 'img[src]:not([src^="data:"])',
},
];
},
renderHTML: function (_a) {
var HTMLAttributes = _a.HTMLAttributes;
return [
"img",
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
];
},
addNodeView: function () {
return ReactNodeViewRenderer(ImageComponent);
},
addCommands: function () {
var _this = this;
return {
setImage: function (options) {
return function (_a) {
var commands = _a.commands;
return commands.insertContent({
type: _this.name,
attrs: options,
});
};
},
setImageAlignment: function (options) {
return function (_a) {
var commands = _a.commands;
return commands.updateAttributes(_this.name, __assign({}, options));
};
},
setImageSize: function (options) {
return function (_a) {
var commands = _a.commands;
return commands.updateAttributes(_this.name, __assign({}, options));
};
},
};
},
addInputRules: function () {
return [
nodeInputRule({
find: inputRegex,
type: this.type,
getAttributes: function (match) {
var alt = match[2], src = match[3], title = match[4];
return { src: src, alt: alt, title: title };
},
}),
];
},
});

View File

@@ -0,0 +1 @@
export * from "./image";

View File

@@ -0,0 +1 @@
export * from "./image";

View File

@@ -0,0 +1,3 @@
import { OrderedList } from "./ordered-list";
export * from "./ordered-list";
export default OrderedList;

View File

@@ -0,0 +1,3 @@
import { OrderedList } from "./ordered-list";
export * from "./ordered-list";
export default OrderedList;

View File

@@ -0,0 +1 @@
export declare const OrderedList: import("@tiptap/core").Node<import("@tiptap/extension-ordered-list").OrderedListOptions, any>;

View File

@@ -0,0 +1,19 @@
import TiptapOrderedList from "@tiptap/extension-ordered-list";
export var OrderedList = TiptapOrderedList.extend({
addAttributes: function () {
return {
listType: {
default: null,
parseHTML: function (element) { return element.style.listStyleType; },
renderHTML: function (attributes) {
if (!attributes.listType) {
return {};
}
return {
style: "list-style-type: ".concat(attributes.listType),
};
},
},
};
},
});

View File

@@ -0,0 +1,3 @@
import { TableCell } from "./table-cell";
export * from "./table-cell";
export default TableCell;

View File

@@ -0,0 +1,3 @@
import { TableCell } from "./table-cell";
export * from "./table-cell";
export default TableCell;

View File

@@ -0,0 +1 @@
export declare const TableCell: import("@tiptap/core").Node<import("@tiptap/extension-table-cell").TableCellOptions, any>;

View File

@@ -0,0 +1,37 @@
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 TiptapTableCell from "@tiptap/extension-table-cell";
export var TableCell = TiptapTableCell.extend({
addAttributes: function () {
var _a;
return __assign(__assign({}, (_a = this.parent) === null || _a === void 0 ? void 0 : _a.call(this)), { backgroundColor: addStyleAttribute("backgroundColor", "background-color"), color: addStyleAttribute("color", "color"), borderWidth: addStyleAttribute("borderWidth", "border-width", "px"), borderStyle: addStyleAttribute("borderStyle", "border-style"), borderColor: addStyleAttribute("borderColor", "border-color") });
},
});
function addStyleAttribute(name, cssName, unit) {
return {
default: null,
parseHTML: function (element) {
var _a;
return unit
? (_a = element.style[name]) === null || _a === void 0 ? void 0 : _a.toString().replace(unit, "")
: element.style[name];
},
renderHTML: function (attributes) {
if (!attributes[name]) {
return {};
}
return {
style: "".concat(cssName, ": ").concat(attributes[name]).concat(unit || ""),
};
},
};
}

View File

@@ -0,0 +1,3 @@
import { TextDirection } from "./text-direction";
export * from "./text-direction";
export default TextDirection;

View File

@@ -0,0 +1,3 @@
import { TextDirection } from "./text-direction";
export * from "./text-direction";
export default TextDirection;

View File

@@ -0,0 +1,19 @@
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";
declare type TextDirectionOptions = {
types: string[];
defaultDirection: TextDirections;
};
declare type TextDirections = "ltr" | "rtl";
declare module "@tiptap/core" {
interface Commands<ReturnType> {
textDirection: {
/**
* Set the font family
*/
setTextDirection: (direction: TextDirections) => ReturnType;
};
}
}
export declare const TextDirection: Extension<TextDirectionOptions, any>;
export {};

View File

@@ -0,0 +1,43 @@
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";
export var TextDirection = Extension.create({
name: "textDirection",
defaultOptions: {
types: ["paragraph", "heading"],
defaultDirection: "ltr",
},
addGlobalAttributes: function () {
return [
{
types: this.options.types,
attributes: {
textDirection: {
default: this.options.defaultDirection,
parseHTML: function (element) { return element.dir; },
renderHTML: function (attributes) {
if (!attributes.textDirection) {
return {};
}
return {
dir: attributes.textDirection,
};
},
},
},
},
];
},
addCommands: function () {
var _this = this;
return {
setTextDirection: function (direction) {
return function (_a) {
var commands = _a.commands;
return _this.options.types.every(function (type) {
return commands.updateAttributes(type, { textDirection: direction });
});
};
},
};
},
});

6
packages/editor/dist/index.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="react" />
import { EditorOptions } from "@tiptap/react";
import Toolbar from "./toolbar";
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
declare const useTiptap: (options?: Partial<EditorOptions & ThemeConfig>, deps?: import("react").DependencyList | undefined) => import("@tiptap/react").Editor | null;
export { useTiptap, Toolbar };

108
packages/editor/dist/index.js vendored Normal file
View File

@@ -0,0 +1,108 @@
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 __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;
};
import CharacterCount from "@tiptap/extension-character-count";
import Placeholder from "@tiptap/extension-placeholder";
import Underline from "@tiptap/extension-underline";
import { useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { useMemo } from "react";
import { EditorView } from "prosemirror-view";
import Toolbar from "./toolbar";
import TextAlign from "@tiptap/extension-text-align";
import Subscript from "@tiptap/extension-subscript";
import Superscript from "@tiptap/extension-superscript";
import FontSize from "./extensions/font-size";
import TextDirection from "./extensions/text-direction";
import TextStyle from "@tiptap/extension-text-style";
import FontFamily from "@tiptap/extension-font-family";
import BulletList from "./extensions/bullet-list";
import OrderedList from "./extensions/ordered-list";
import Highlight from "@tiptap/extension-highlight";
import Color from "@tiptap/extension-color";
import Link from "@tiptap/extension-link";
import Table from "@tiptap/extension-table";
import TableRow from "@tiptap/extension-table-row";
import TableCell from "./extensions/table-cell";
import TableHeader from "@tiptap/extension-table-header";
import { ImageNode } from "./extensions/image";
import { useTheme } from "@notesnook/theme";
EditorView.prototype.updateState = function updateState(state) {
if (!this.docView)
return; // This prevents the matchesNode error on hot reloads
this.updateStateInner(state, this.state.plugins != state.plugins);
};
var useTiptap = function (options, deps) {
if (options === void 0) { options = {}; }
var theme = options.theme, accent = options.accent, scale = options.scale, onCreate = options.onCreate, restOptions = __rest(options, ["theme", "accent", "scale", "onCreate"]);
var defaultOptions = useMemo(function () { return ({
extensions: [
TextStyle,
StarterKit,
CharacterCount,
Underline,
Subscript,
Superscript,
FontSize,
TextDirection,
FontFamily,
BulletList,
OrderedList,
Link,
ImageNode,
Table.configure({
resizable: true,
allowTableNodeSelection: true,
}),
TableRow,
TableCell,
TableHeader,
Highlight.configure({
multicolor: true,
}),
Color,
TextAlign.configure({
types: ["heading", "paragraph"],
alignments: ["left", "right", "center", "justify"],
defaultAlignment: "left",
}),
Placeholder.configure({
placeholder: "Start writing your note...",
}),
],
onCreate: function (_a) {
var editor = _a.editor;
if (theme && accent && scale) {
editor.storage.theme = useTheme({ theme: theme, accent: accent, scale: scale });
}
if (onCreate)
onCreate({ editor: editor });
},
}); }, [theme, accent, scale, onCreate]);
var editor = useEditor(__assign(__assign({}, defaultOptions), restOptions), deps);
/**
* Add editor to global for use in React Native.
*/
global.editor = editor;
return editor;
};
export { useTiptap, Toolbar };

View File

@@ -0,0 +1,8 @@
/// <reference types="react" />
import { MenuItem } from "../../components/menu/types";
declare type DropdownProps = {
selectedItem: string | JSX.Element;
items: MenuItem[];
};
export declare function Dropdown(props: DropdownProps): JSX.Element;
export {};

View File

@@ -0,0 +1,42 @@
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, Fragment as _Fragment } from "react/jsx-runtime";
import { useRef, useState } from "react";
import { Button, Text } from "rebass";
import { Icon } from "./icon";
import { Icons } from "../icons";
import { MenuPresenter } from "../../components/menu/menu";
export function Dropdown(props) {
var items = props.items, selectedItem = props.selectedItem;
var buttonRef = useRef(null);
var _a = useState(false), isOpen = _a[0], setIsOpen = _a[1];
return (_jsxs(_Fragment, { children: [_jsxs(Button, __assign({ ref: buttonRef, sx: {
p: 1,
m: 0,
bg: isOpen ? "hover" : "transparent",
mr: 1,
display: "flex",
alignItems: "center",
":hover": { bg: "hover" },
":last-of-type": {
mr: 0,
},
}, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: [typeof selectedItem === "string" ? (_jsx(Text, __assign({ sx: { fontSize: 12, mr: 1, color: "text" } }, { children: selectedItem }))) : (selectedItem), _jsx(Icon, { path: Icons.chevronDown, size: 16, color: "text" })] })), _jsx(MenuPresenter, { options: {
type: "menu",
position: {
target: buttonRef.current || undefined,
isTargetAbsolute: true,
location: "below",
yOffset: 5,
},
}, isOpen: isOpen, items: items, onClose: function () { return setIsOpen(false); } })] }));
}

View File

@@ -0,0 +1,14 @@
/// <reference types="react" />
import { SchemeColors } from "@notesnook/theme/dist/theme/colorscheme";
import { FlexProps } from "rebass";
declare type IconProps = {
title?: string;
path: string;
size?: number;
color?: keyof SchemeColors;
stroke?: string;
rotate?: boolean;
};
export declare type NNIconProps = FlexProps & IconProps;
export declare function Icon(props: NNIconProps): JSX.Element;
export {};

View File

@@ -0,0 +1,41 @@
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 __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;
};
import { jsx as _jsx } from "react/jsx-runtime";
import MDIIcon from "@mdi/react";
import { useTheme } from "emotion-theming";
import { Flex } from "rebass";
function MDIIconWrapper(_a) {
var title = _a.title, path = _a.path, _b = _a.size, size = _b === void 0 ? 24 : _b, _c = _a.color, color = _c === void 0 ? "icon" : _c, stroke = _a.stroke, rotate = _a.rotate;
var theme = useTheme();
var themedColor = theme.colors
? theme.colors[color]
: color;
return (_jsx(MDIIcon, { title: title, path: path, size: size + "px", style: {
strokeWidth: stroke || "0px",
stroke: themedColor,
}, color: themedColor, spin: rotate }));
}
export function Icon(props) {
var sx = props.sx, title = props.title, color = props.color, size = props.size, stroke = props.stroke, rotate = props.rotate, path = props.path, restProps = __rest(props, ["sx", "title", "color", "size", "stroke", "rotate", "path"]);
return (_jsx(Flex, __assign({ sx: __assign({ flexShrink: 0, justifyContent: "center", alignItems: "center" }, sx) }, restProps, { children: _jsx(MDIIconWrapper, { title: title, path: path, rotate: rotate, color: color, stroke: stroke, size: size }) })));
}

View File

@@ -0,0 +1,17 @@
import { ButtonProps } from "rebass";
import { IconNames } from "../icons";
import { PropsWithChildren } from "react";
import { SchemeColors } from "@notesnook/theme/dist/theme/colorscheme";
declare type PopupProps = {
title?: string;
action?: PopupButtonProps;
};
export declare function Popup(props: PropsWithChildren<PopupProps>): JSX.Element;
declare type PopupButtonProps = ButtonProps & {
text?: string;
loading?: boolean;
icon?: IconNames;
iconSize?: number;
iconColor?: keyof SchemeColors;
};
export {};

View File

@@ -0,0 +1,45 @@
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 __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;
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { Button, Flex, Text } from "rebass";
import { Icon } from "./icon";
import { Icons } from "../icons";
export function Popup(props) {
var title = props.title, action = props.action, children = props.children;
return (_jsxs(Flex, __assign({ sx: {
bg: "background",
flexDirection: "column",
borderRadius: "default",
border: "1px solid var(--border)",
boxShadow: "menu",
} }, { children: [title && (_jsxs(Flex, __assign({ sx: {
justifyContent: "space-between",
alignItems: "center",
mx: 1,
mt: 1,
} }, { children: [_jsx(Text, __assign({ variant: "subtitle" }, { children: title })), action && (_jsx(PopupButton, __assign({ "data-test-id": "popup-no", color: "text" }, action)))] }))), children] })));
}
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) })));
}

View File

@@ -0,0 +1,13 @@
import { SchemeColors } from "@notesnook/theme/dist/theme/colorscheme";
import React from "react";
import { ButtonProps } from "rebass";
import { IconNames } from "../icons";
declare type ToolButtonProps = ButtonProps & {
icon: IconNames;
iconColor?: keyof SchemeColors;
iconSize?: number;
toggled: boolean;
buttonRef?: React.Ref<HTMLButtonElement>;
};
export declare function ToolButton(props: ToolButtonProps): JSX.Element;
export {};

View File

@@ -0,0 +1,32 @@
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 __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;
};
import { jsx as _jsx } from "react/jsx-runtime";
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": {
mr: 0,
} }, sx) }, buttonProps, { children: _jsx(Icon, { path: Icons[icon], color: iconColor || "text", size: iconSize || 18 }) })));
}

View File

@@ -0,0 +1,3 @@
/// <reference types="react" />
import { FloatingMenuProps } from "./types";
export declare function EditorFloatingMenus(props: FloatingMenuProps): JSX.Element;

View File

@@ -0,0 +1,16 @@
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { TableRowFloatingMenu, TableColumnFloatingMenu } from "./table";
export function EditorFloatingMenus(props) {
return (_jsxs(_Fragment, { children: [_jsx(TableRowFloatingMenu, __assign({}, props)), _jsx(TableColumnFloatingMenu, __assign({}, props))] }));
}

View File

@@ -0,0 +1,4 @@
/// <reference types="react" />
import { FloatingMenuProps } from "./types";
export declare function TableRowFloatingMenu(props: FloatingMenuProps): JSX.Element | null;
export declare function TableColumnFloatingMenu(props: FloatingMenuProps): JSX.Element | null;

View File

@@ -0,0 +1,427 @@
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 };
}
};
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Slider } from "@rebass/forms";
import { useEffect, 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 { ColorPicker, DEFAULT_COLORS } from "../tools/colors";
import { selectedRect } from "prosemirror-tables";
export function TableRowFloatingMenu(props) {
var editor = props.editor;
var theme = editor.storage.theme;
var _a = useState(null), position = _a[0], setPosition = _a[1];
var _b = useState(false), isMenuOpen = _b[0], setIsMenuOpen = _b[1];
useEffect(function () {
var _a;
if (!editor.isActive("tableRow"))
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 (_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 ? 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 () {
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 TableColumnFloatingMenu(props) {
var _this = this;
var editor = props.editor;
var _a = useState(null), position = _a[0], setPosition = _a[1];
var _b = useState(false), isMenuOpen = _b[0], setIsMenuOpen = _b[1];
var _c = useState(false), showCellProps = _c[0], setShowCellProps = _c[1];
var _d = useState(null), menuPosition = _d[0], setMenuPosition = _d[1];
useEffect(function () {
var _a;
if (!editor.isActive("tableRow"))
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 (_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: "insertColumnRight", onClick: function () { return editor.chain().focus().addColumnAfter().run(); }, sx: { mr: 0, p: "3px", borderRadius: "small" }, iconSize: 16 })] })), _jsx(MenuPresenter, { isOpen: isMenuOpen, onClose: function () {
setIsMenuOpen(false);
editor.commands.focus();
}, options: {
type: "menu",
position: {},
}, items: [
{
key: "addColumnAbove",
type: "menuitem",
title: "Add column left",
onClick: function () { return editor.chain().focus().addColumnBefore().run(); },
icon: "insertColumnLeft",
},
{
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",
},
{
key: "sortDesc",
type: "menuitem",
title: "Sort descending",
onClick: function () { },
icon: "sortDesc",
},
{
key: "cellProperties",
type: "menuitem",
title: "Cell properties",
onClick: function () {
setShowCellProps(true);
setMenuPosition({
target: position.target || undefined,
isTargetAbsolute: true,
yOffset: 10,
location: "below",
});
},
icon: "cellProperties",
},
] }), _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); } }) }))] })));
}
function CellProperties(props) {
var editor = props.editor, onClose = props.onClose;
var attributes = editor.getAttributes("tableCell");
console.log(attributes);
return (_jsx(Popup, __assign({ title: "Cell properties", action: {
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) {
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 = useState(false), 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, __assign({ sx: {
flexDirection: "column",
bg: "background",
boxShadow: "menu",
border: "1px solid var(--border)",
borderRadius: "default",
p: 1,
width: 160,
} }, { children: _jsx(ColorPicker, { colors: DEFAULT_COLORS, color: color, onClear: function () { return onColorChange(); }, onChange: function (color) { return onColorChange(color); } }) })) }))] }));
}
/**
* Done:
* insertTable
*
* addRowBefore
* addRowAfter
* deleteRow
*
* addColumnBefore
* addColumnAfter
* deleteColumn
*
* setCellAttribute
*
* deleteTable
*
* mergeCells
* splitCell
* mergeOrSplit
*
* toggleHeaderColumn
* toggleHeaderRow
* toggleHeaderCell
* fixTables
* goToNextCell
* goToPreviousCell
*/
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;
}

View File

@@ -0,0 +1,4 @@
import { Editor } from "@tiptap/core";
export declare type FloatingMenuProps = {
editor: Editor;
};

View File

@@ -0,0 +1 @@
export {};

50
packages/editor/dist/toolbar/icons.d.ts vendored Normal file
View File

@@ -0,0 +1,50 @@
export declare const Icons: {
bold: string;
italic: string;
underline: string;
strikethrough: string;
code: string;
alignCenter: string;
alignLeft: string;
alignRight: string;
alignJustify: string;
subscript: string;
superscript: string;
horizontalRule: string;
codeblock: string;
blockquote: string;
formatClear: string;
ltr: string;
rtl: string;
numberedList: string;
bulletList: string;
highlight: string;
textColor: string;
link: string;
image: string;
chevronDown: string;
colorClear: string;
check: string;
loading: string;
more: string;
upload: string;
attachment: string;
table: string;
insertRowBelow: string;
insertRowAbove: string;
moveRowDown: string;
moveRowUp: string;
deleteRow: string;
toggleHeaderRow: string;
insertColumnRight: string;
insertColumnLeft: string;
moveColumnRight: string;
moveColumnLeft: string;
deleteColumn: string;
cellProperties: string;
backgroundColor: string;
borderColor: string;
close: string;
sortDesc: string;
};
export declare type IconNames = keyof typeof Icons;

50
packages/editor/dist/toolbar/icons.js vendored Normal file
View File

@@ -0,0 +1,50 @@
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, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, } from "@mdi/js";
export var Icons = {
bold: mdiFormatBold,
italic: mdiFormatItalic,
underline: mdiFormatUnderline,
strikethrough: mdiFormatStrikethrough,
code: mdiCodeTags,
alignCenter: mdiFormatAlignCenter,
alignLeft: mdiFormatAlignLeft,
alignRight: mdiFormatAlignRight,
alignJustify: mdiFormatAlignJustify,
subscript: mdiFormatSubscript,
superscript: mdiFormatSuperscript,
horizontalRule: mdiBorderHorizontal,
codeblock: mdiCodeBraces,
blockquote: mdiFormatQuoteClose,
formatClear: mdiFormatClear,
ltr: mdiFormatTextdirectionLToR,
rtl: mdiFormatTextdirectionRToL,
numberedList: mdiFormatListNumbered,
bulletList: mdiFormatListBulleted,
highlight: mdiFormatColorHighlight,
textColor: mdiFormatColorText,
link: mdiLinkPlus,
image: mdiImage,
chevronDown: mdiChevronDown,
colorClear: mdiInvertColorsOff,
check: mdiCheck,
loading: mdiLoading,
more: mdiDotsVertical,
upload: mdiUploadOutline,
attachment: mdiAttachment,
table: mdiTable,
insertRowBelow: mdiPlus,
insertRowAbove: mdiTableRowPlusBefore,
moveRowDown: mdiArrowExpandDown,
moveRowUp: mdiArrowExpandUp,
deleteRow: mdiTableRowRemove,
toggleHeaderRow: mdiTableBorder,
insertColumnRight: mdiPlus,
insertColumnLeft: mdiTableColumnPlusBefore,
moveColumnRight: mdiArrowExpandRight,
moveColumnLeft: mdiArrowExpandLeft,
deleteColumn: mdiTableColumnRemove,
cellProperties: mdiSquareRoundedBadgeOutline,
backgroundColor: mdiFormatColorFill,
borderColor: mdiBorderAllVariant,
close: mdiClose,
sortDesc: mdiSortDescending,
};

View File

@@ -0,0 +1,2 @@
import { Toolbar } from "./toolbar";
export default Toolbar;

2
packages/editor/dist/toolbar/index.js vendored Normal file
View File

@@ -0,0 +1,2 @@
import { Toolbar } from "./toolbar";
export default Toolbar;

View File

@@ -0,0 +1,3 @@
/// <reference types="react" />
import { FloatingMenuProps } from "./types";
export declare function EditorFloatingMenus(props: FloatingMenuProps): JSX.Element;

View File

@@ -0,0 +1,16 @@
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, Fragment as _Fragment } from "react/jsx-runtime";
import { TableRowFloatingMenu } from "./table";
export function EditorFloatingMenus(props) {
return (_jsx(_Fragment, { children: _jsx(TableRowFloatingMenu, __assign({}, props)) }));
}

View File

@@ -0,0 +1,3 @@
/// <reference types="react" />
import { FloatingMenuProps } from "./types";
export declare function TableRowFloatingMenu(props: FloatingMenuProps): JSX.Element;

View File

@@ -0,0 +1,21 @@
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 } from "react/jsx-runtime";
import { FloatingMenu } from "@tiptap/react";
import { Flex, Text } from "rebass";
export function TableRowFloatingMenu(props) {
var editor = props.editor;
return (_jsx(FloatingMenu, __assign({ editor: editor, shouldShow: function (_a) {
var editor = _a.editor, state = _a.state;
return editor.isActive("tableRow") && state.selection.empty;
} }, { children: _jsx(Flex, __assign({ sx: { bg: "background" } }, { children: _jsx(Text, { children: "Hello" }) })) })));
}

View File

@@ -0,0 +1,4 @@
import { Editor } from "@tiptap/core";
export declare type FloatingMenuProps = {
editor: Editor;
};

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,8 @@
/// <reference types="react" />
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
import { Editor } from "@tiptap/core";
declare type ToolbarProps = ThemeConfig & {
editor: Editor | null;
};
export declare function Toolbar(props: ToolbarProps): JSX.Element | null;
export {};

45
packages/editor/dist/toolbar/toolbar.js vendored Normal file
View File

@@ -0,0 +1,45 @@
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 { useTheme } from "@notesnook/theme";
import { Flex } from "rebass";
import { findToolById } from "./tools";
import { ThemeProvider } from "emotion-theming";
import { EditorFloatingMenus } from "./floating-menus";
export function Toolbar(props) {
var editor = props.editor, theme = props.theme, accent = props.accent, scale = props.scale;
var themeProperties = useTheme({ accent: accent, theme: theme, scale: scale });
var tools = [
["bold", "italic", "underline", "strikethrough", "code"],
["fontSize", "fontFamily", "headings"],
["alignLeft", "alignCenter", "alignRight", "alignJustify"],
["subscript", "superscript", "horizontalRule"],
["codeblock", "blockquote"],
["formatClear", "ltr", "rtl"],
["numberedList", "bulletList"],
["link", "image", "attachment", "table"],
["textColor", "highlight"],
];
if (!editor)
return null;
return (_jsxs(ThemeProvider, __assign({ theme: themeProperties }, { children: [_jsx(Flex, __assign({ sx: { flexWrap: "wrap" } }, { children: tools.map(function (tools) {
return (_jsx(Flex, __assign({ className: "toolbar-group", sx: {
pr: 2,
mr: 2,
borderRight: "1px solid var(--border)",
":last-of-type": { mr: 0, pr: 0, borderRight: "none" },
} }, { children: tools.map(function (toolId) {
var Component = findToolById(toolId).render;
return _jsx(Component, { editor: editor });
}) })));
}) })), _jsx(EditorFloatingMenus, { editor: editor })] })));
}

View File

@@ -0,0 +1,25 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
import { IconNames } from "../icons";
declare class AlignmentTool<TId extends ToolId, TTitle extends string> implements ITool {
readonly id: TId;
readonly title: TTitle;
private readonly alignment;
private readonly icon;
constructor(id: TId, title: TTitle, alignment: "left" | "right" | "center" | "justify", icon: IconNames);
render: (props: ToolProps) => JSX.Element;
}
export declare class AlignCenter extends AlignmentTool<ToolId, string> {
constructor();
}
export declare class AlignRight extends AlignmentTool<ToolId, string> {
constructor();
}
export declare class AlignLeft extends AlignmentTool<ToolId, string> {
constructor();
}
export declare class AlignJustify extends AlignmentTool<ToolId, string> {
constructor();
}
export {};

View File

@@ -0,0 +1,65 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { jsx as _jsx } from "react/jsx-runtime";
import { ToolButton } from "../components/tool-button";
var AlignmentTool = /** @class */ (function () {
function AlignmentTool(id, title, alignment, icon) {
var _this = this;
this.id = id;
this.title = title;
this.alignment = alignment;
this.icon = icon;
this.render = function (props) {
var editor = props.editor;
return (_jsx(ToolButton, { title: _this.title, id: _this.id, icon: _this.icon, onClick: function () {
return editor.chain().focus().setTextAlign(_this.alignment).run();
}, toggled: editor.isActive({ textAlign: _this.alignment }) }));
};
}
return AlignmentTool;
}());
var AlignCenter = /** @class */ (function (_super) {
__extends(AlignCenter, _super);
function AlignCenter() {
return _super.call(this, "alignCenter", "Align center", "center", "alignCenter") || this;
}
return AlignCenter;
}(AlignmentTool));
export { AlignCenter };
var AlignRight = /** @class */ (function (_super) {
__extends(AlignRight, _super);
function AlignRight() {
return _super.call(this, "alignRight", "Align right", "right", "alignRight") || this;
}
return AlignRight;
}(AlignmentTool));
export { AlignRight };
var AlignLeft = /** @class */ (function (_super) {
__extends(AlignLeft, _super);
function AlignLeft() {
return _super.call(this, "alignLeft", "Align left", "left", "alignLeft") || this;
}
return AlignLeft;
}(AlignmentTool));
export { AlignLeft };
var AlignJustify = /** @class */ (function (_super) {
__extends(AlignJustify, _super);
function AlignJustify() {
return _super.call(this, "alignJustify", "Justify", "justify", "alignJustify") || this;
}
return AlignJustify;
}(AlignmentTool));
export { AlignJustify };

View File

@@ -0,0 +1,42 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { Editor } from "@tiptap/core";
import { ToolId } from ".";
import { IconNames } from "../icons";
declare class BlockTool<TId extends ToolId> implements ITool {
readonly id: TId;
readonly title: string;
private readonly icon;
private readonly command;
constructor(id: TId, title: string, icon: IconNames, command: (editor: Editor) => boolean);
render: (props: ToolProps) => JSX.Element;
}
export declare class HorizontalRule extends BlockTool<ToolId> {
constructor();
}
export declare class CodeBlock extends BlockTool<ToolId> {
constructor();
}
export declare class Blockquote extends BlockTool<ToolId> {
constructor();
}
export declare class Image implements ITool {
id: ToolId;
title: string;
render: (props: ToolProps) => JSX.Element;
}
export declare class Attachment extends BlockTool<ToolId> {
constructor();
}
export declare class Table implements ITool {
id: ToolId;
title: string;
private MAX_COLUMNS;
private MAX_ROWS;
private MIN_COLUMNS;
private MIN_ROWS;
render: (props: ToolProps) => JSX.Element;
private getCellLocation;
private isCellHighlighted;
}
export {};

View File

@@ -0,0 +1,220 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { ToolButton } from "../components/tool-button";
import { Icons } from "../icons";
import { MenuPresenter } from "../../components/menu/menu";
import { useEffect, useRef, useState } from "react";
import { Dropdown } from "../components/dropdown";
import { Icon } from "../components/icon";
import { Box, Button, Flex, Text } from "rebass";
import { Popup } from "../components/popup";
var BlockTool = /** @class */ (function () {
function BlockTool(id, title, icon, command) {
var _this = this;
this.id = id;
this.title = title;
this.icon = icon;
this.command = command;
this.render = function (props) {
var editor = props.editor;
return (_jsx(ToolButton, { id: _this.id, title: _this.title, icon: _this.icon, onClick: function () { return _this.command(editor); }, toggled: editor.isActive(_this.id) }));
};
}
return BlockTool;
}());
var HorizontalRule = /** @class */ (function (_super) {
__extends(HorizontalRule, _super);
function HorizontalRule() {
return _super.call(this, "horizontalRule", "Horizontal rule", "horizontalRule", function (editor) {
return editor.chain().focus().setHorizontalRule().run();
}) || this;
}
return HorizontalRule;
}(BlockTool));
export { HorizontalRule };
var CodeBlock = /** @class */ (function (_super) {
__extends(CodeBlock, _super);
function CodeBlock() {
return _super.call(this, "codeblock", "Codeblock", "codeblock", function (editor) {
return editor.chain().focus().toggleCodeBlock().run();
}) || this;
}
return CodeBlock;
}(BlockTool));
export { CodeBlock };
var Blockquote = /** @class */ (function (_super) {
__extends(Blockquote, _super);
function Blockquote() {
return _super.call(this, "blockquote", "Blockquote", "blockquote", function (editor) {
return editor.chain().focus().toggleBlockquote().run();
}) || this;
}
return Blockquote;
}(BlockTool));
export { Blockquote };
var Image = /** @class */ (function () {
function Image() {
this.id = "image";
this.title = "Image";
this.render = function (props) {
var editor = props.editor;
return (_jsx(_Fragment, { children: _jsx(Dropdown, { selectedItem: _jsx(Icon, { path: Icons.image, size: 16 }), 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 () { },
},
] }) }));
};
}
return Image;
}());
export { Image };
var Attachment = /** @class */ (function (_super) {
__extends(Attachment, _super);
function Attachment() {
return _super.call(this, "attachment", "Attachment", "attachment", function (editor) {
return false;
}) || this;
}
return Attachment;
}(BlockTool));
export { Attachment };
var Table = /** @class */ (function () {
function Table() {
var _this = this;
this.id = "table";
this.title = "Table";
this.MAX_COLUMNS = 20;
this.MAX_ROWS = 20;
this.MIN_COLUMNS = 4;
this.MIN_ROWS = 4;
this.render = function (props) {
var editor = props.editor;
var _a = useState(false), isOpen = _a[0], setIsOpen = _a[1];
var ref = useRef(null);
var _b = useState({
column: 0,
row: 0,
}), cellLocation = _b[0], setCellLocation = _b[1];
var _c = useState({
columns: _this.MIN_COLUMNS,
rows: _this.MIN_ROWS,
}), tableSize = _c[0], setTableSize = _c[1];
useEffect(function () {
setTableSize(function (old) {
var columns = old.columns, rows = old.rows;
var column = cellLocation.column, row = cellLocation.row;
var isDecrease = row === rows - 2 || column === columns - 2;
var rowFactor = Number(row === rows || row === rows - 2);
var columnFactor = Number(column === columns || column === columns - 2);
return {
columns: isDecrease
? Math.max(column + columnFactor, _this.MIN_COLUMNS)
: Math.min(old.columns + columnFactor, _this.MAX_COLUMNS),
rows: isDecrease
? Math.max(row + rowFactor, _this.MIN_ROWS)
: Math.min(old.rows + rowFactor, _this.MAX_ROWS),
};
});
}, [cellLocation]);
return (_jsxs(Flex, __assign({ ref: ref }, { children: [_jsxs(Button, __assign({ sx: {
p: 1,
m: 0,
bg: isOpen ? "hover" : "transparent",
mr: 1,
display: "flex",
alignItems: "center",
":hover": { bg: "hover" },
":last-of-type": {
mr: 0,
},
}, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: [_jsx(Icon, { path: Icons.table, color: "text", size: 18 }), _jsx(Icon, { path: Icons.chevronDown, color: "text", size: 18 })] })), _jsx(MenuPresenter, __assign({ isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [], options: {
type: "menu",
position: {
target: ref.current || undefined,
isTargetAbsolute: true,
location: "below",
yOffset: 5,
},
} }, { children: _jsx(Popup, { children: _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: _this.isCellHighlighted(index, cellLocation, tableSize)
? "disabled"
: "transparent",
":hover": {
bg: "disabled",
},
}, onMouseEnter: function () {
setCellLocation(_this.getCellLocation(index, tableSize));
}, onClick: function () {
editor
.chain()
.focus()
.insertTable({
cols: cellLocation.column,
rows: cellLocation.row,
})
.run();
setIsOpen(false);
} })); }) })), _jsxs(Text, __assign({ variant: "body", sx: { mt: 1 } }, { children: [cellLocation.column, "x", cellLocation.row] }))] })) }) }))] })));
};
}
Table.prototype.getCellLocation = function (index, tableSize) {
var cellIndex = index + 1;
var column = cellIndex % tableSize.columns;
var row = cellIndex / tableSize.columns;
var flooredRow = Math.floor(row);
row = row === flooredRow ? row : flooredRow + 1;
return { column: column ? column : tableSize.columns, row: row };
};
Table.prototype.isCellHighlighted = function (index, currentCellLocation, tableSize) {
var cellLocation = this.getCellLocation(index, tableSize);
return (cellLocation.row <= currentCellLocation.row &&
cellLocation.column <= currentCellLocation.column);
};
return Table;
}());
export { Table };

View File

@@ -0,0 +1,28 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { Editor } from "@tiptap/core";
import { ToolId } from ".";
import { IconNames } from "../icons";
export declare const DEFAULT_COLORS: string[];
declare class ColorTool implements ITool {
readonly id: ToolId;
readonly title: string;
private readonly icon;
private readonly onColorChange;
constructor(id: ToolId, title: string, icon: IconNames, onColorChange: (editor: Editor, color?: string) => void);
render: (props: ToolProps) => JSX.Element;
}
export declare class Highlight extends ColorTool {
constructor();
}
export declare class TextColor extends ColorTool {
constructor();
}
declare type ColorPickerProps = {
colors: string[];
color: string;
onClear: () => void;
onChange: (color: string) => void;
};
export declare function ColorPicker(props: ColorPickerProps): JSX.Element;
export {};

View File

@@ -0,0 +1,182 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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, Fragment as _Fragment } from "react/jsx-runtime";
import { Box, Button, Flex, Text } from "rebass";
import { Input } from "@rebass/forms";
import { Icon } from "../components/icon";
import { Icons } from "../icons";
import { ToolButton } from "../components/tool-button";
import { MenuPresenter } from "../../components/menu/menu";
import { useRef, useState } from "react";
import tinycolor from "tinycolor2";
export var DEFAULT_COLORS = [
"#f44336",
"#e91e63",
"#9c27b0",
"#673ab7",
"#3f51b5",
"#2196f3",
"#03a9f4",
"#00bcd4",
"#009688",
"#4caf50",
"#8bc34a",
"#cddc39",
"#ffeb3b",
"#ffc107",
];
var ColorTool = /** @class */ (function () {
function ColorTool(id, title, icon, onColorChange) {
var _this = this;
this.id = id;
this.title = title;
this.icon = icon;
this.onColorChange = onColorChange;
this.render = function (props) {
var editor = props.editor;
var _a = useState(false), isOpen = _a[0], setIsOpen = _a[1];
var attrs = editor.getAttributes(_this.id === "highlight" ? "highlight" : "textStyle");
var ref = useRef(null);
var isActive = editor.isActive(_this.id === "highlight" ? "highlight" : "textStyle", { color: /\W+/gm });
return (_jsxs(Flex, __assign({ ref: ref }, { children: [_jsx(ToolButton, { title: _this.title, id: _this.id, icon: _this.icon, onClick: function () { }, toggled: false, sx: { mr: 0, bg: isActive ? attrs.color : "transparent" } }), _jsx(Button, __assign({ sx: {
p: 0,
m: 0,
bg: "transparent",
":hover": { bg: "hover" },
":last-of-type": {
mr: 0,
},
}, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: _jsx(Icon, { path: Icons.chevronDown, color: "text", size: 18 }) })), _jsx(MenuPresenter, __assign({ isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: [],
// sx={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", p: 1 }}
options: {
type: "menu",
position: {
target: ref.current || undefined,
isTargetAbsolute: true,
location: "below",
align: "center",
yOffset: 5,
},
} }, { children: _jsx(Flex, __assign({ sx: {
flexDirection: "column",
bg: "background",
boxShadow: "menu",
border: "1px solid var(--border)",
borderRadius: "default",
p: 1,
width: 160,
} }, { children: _jsx(ColorPicker, { colors: DEFAULT_COLORS, color: attrs.color, onClear: function () { return _this.onColorChange(editor); }, onChange: function (color) { return _this.onColorChange(editor, color); } }) })) }))] })));
};
}
return ColorTool;
}());
var Highlight = /** @class */ (function (_super) {
__extends(Highlight, _super);
function Highlight() {
return _super.call(this, "highlight", "Highlight", "highlight", function (editor, color) {
return color
? editor.chain().focus().toggleHighlight({ color: color }).run()
: editor.chain().focus().unsetHighlight().run();
}) || this;
}
return Highlight;
}(ColorTool));
export { Highlight };
var TextColor = /** @class */ (function (_super) {
__extends(TextColor, _super);
function TextColor() {
return _super.call(this, "textColor", "Text color", "textColor", function (editor, color) {
return color
? editor.chain().focus().setColor(color).run()
: editor.chain().focus().unsetColor().run();
}) || this;
}
return TextColor;
}(ColorTool));
export { TextColor };
export function ColorPicker(props) {
var colors = props.colors, color = props.color, onClear = props.onClear, onChange = props.onChange;
var _a = useState(tinycolor(color || colors[0]).toHexString()), currentColor = _a[0], setCurrentColor = _a[1];
return (_jsxs(_Fragment, { children: [_jsx(Flex, __assign({ sx: {
width: "100%",
height: 50,
bg: currentColor,
mb: 1,
borderRadius: "default",
alignItems: "center",
justifyContent: "center",
} }, { children: _jsx(Text, __assign({ sx: {
fontSize: "subheading",
color: tinycolor(currentColor).isDark() ? "white" : "black",
} }, { children: currentColor })) })), _jsxs(Box, __assign({ sx: {
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr",
} }, { children: [colors.map(function (color) { return (_jsx(Box, { sx: {
bg: color,
width: 25,
height: 25,
m: "2px",
borderRadius: "default",
cursor: "pointer",
":hover": {
filter: "brightness(85%)",
},
}, onClick: function () {
setCurrentColor(color);
onChange(color);
} })); }), _jsx(Flex, __assign({ sx: {
width: 25,
height: 25,
m: "2px",
borderRadius: "default",
cursor: "pointer",
alignItems: "center",
justifyContent: "center",
":hover": {
filter: "brightness(85%)",
},
}, onClick: onClear }, { children: _jsx(Icon, { path: Icons.colorClear, size: 18 }) }))] })), _jsxs(Flex, __assign({ sx: {
mt: 1,
borderRadius: "default",
} }, { children: [_jsx(Input, { placeholder: "#000000", sx: {
p: 1,
m: 0,
fontSize: "body",
border: "none",
borderWidth: 0,
}, value: currentColor, maxLength: 7, onChange: function (e) {
var value = e.target.value;
if (!value)
return;
setCurrentColor(value);
} }), _jsx(Button, __assign({ sx: {
bg: "transparent",
p: 1,
":hover": { bg: "hover" },
cursor: "pointer",
}, onClick: function () { return onChange(currentColor); } }, { children: _jsx(Icon, { path: Icons.check, color: "text", size: 18 }) }))] }))] }));
}

View File

@@ -0,0 +1,16 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
export declare class FontSize implements ITool {
title: string;
id: ToolId;
private defaultFontSizes;
render: (props: ToolProps) => JSX.Element;
}
export declare class FontFamily implements ITool {
title: string;
id: ToolId;
private fontFamilies;
render: (props: ToolProps) => JSX.Element;
private toMenuItems;
}

View File

@@ -0,0 +1,69 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { Dropdown } from "../components/dropdown";
var FontSize = /** @class */ (function () {
function FontSize() {
var _this = this;
this.title = "Font size";
this.id = "fontSize";
this.defaultFontSizes = [
12, 14, 16, 18, 20, 24, 28, 32, 36, 42, 48, 60, 72, 100,
];
this.render = function (props) {
var editor = props.editor;
var currentFontSize = _this.defaultFontSizes.find(function (size) {
return editor.isActive("textStyle", { fontSize: "".concat(size, "px") });
}) || 16;
return (_jsx(Dropdown, { selectedItem: "".concat(currentFontSize, "px"), items: _this.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(); },
}); }) }));
};
}
return FontSize;
}());
export { FontSize };
var FontFamily = /** @class */ (function () {
function FontFamily() {
var _this = this;
this.title = "Font family";
this.id = "fontFamily";
this.fontFamilies = {
System: "Open Sans",
Serif: "serif",
Monospace: "monospace",
};
this.render = function (props) {
var _a, _b;
var editor = props.editor;
var currentFontFamily = ((_b = (_a = Object.entries(_this.fontFamilies)
.find(function (_a) {
var key = _a[0], value = _a[1];
return editor.isActive("textStyle", { fontFamily: value });
})) === null || _a === void 0 ? void 0 : _a.map(function (a) { return a; })) === null || _b === void 0 ? void 0 : _b.at(0)) || "System";
return (_jsx(Dropdown, { selectedItem: currentFontFamily, items: _this.toMenuItems(editor, currentFontFamily) }));
};
}
FontFamily.prototype.toMenuItems = function (editor, currentFontFamily) {
var menuItems = [];
var _loop_1 = function (key) {
var value = this_1.fontFamilies[key];
menuItems.push({
key: key,
type: "menuitem",
title: key,
isChecked: key === currentFontFamily,
onClick: function () { return editor.chain().focus().setFontFamily(value).run(); },
});
};
var this_1 = this;
for (var key in this.fontFamilies) {
_loop_1(key);
}
return menuItems;
};
return FontFamily;
}());
export { FontFamily };

View File

@@ -0,0 +1,10 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
export declare class Headings implements ITool {
title: string;
id: ToolId;
private defaultLevels;
render: (props: ToolProps) => JSX.Element;
private toMenuItems;
}

View File

@@ -0,0 +1,51 @@
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 } from "react/jsx-runtime";
import { Dropdown } from "../components/dropdown";
var Headings = /** @class */ (function () {
function Headings() {
var _this = this;
this.title = "Headings";
this.id = "headings";
this.defaultLevels = [1, 2, 3, 4, 5, 6];
this.render = function (props) {
var editor = props.editor;
var currentHeadingLevel = _this.defaultLevels.find(function (level) {
return editor.isActive("heading", { level: level });
});
return (_jsx(Dropdown, { selectedItem: currentHeadingLevel ? "Heading ".concat(currentHeadingLevel) : "Paragraph", items: _this.toMenuItems(editor, currentHeadingLevel) }));
};
}
Headings.prototype.toMenuItems = function (editor, currentHeadingLevel) {
var menuItems = this.defaultLevels.map(function (level) { return ({
type: "menuitem",
key: "heading-".concat(level),
title: "Heading ".concat(level),
isChecked: level === currentHeadingLevel,
onClick: function () {
return editor
.chain()
.focus()
.setHeading({ level: level })
.run();
},
}); });
var paragraph = {
key: "paragraph",
type: "menuitem",
title: "Paragraph",
isChecked: !currentHeadingLevel,
onClick: function () { return editor.chain().focus().setParagraph().run(); },
};
return __spreadArray([paragraph], menuItems, true);
};
return Headings;
}());
export { Headings };

View File

@@ -0,0 +1,42 @@
import { ITool } from "../types";
import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, Link } from "./inline";
import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
import { Blockquote, CodeBlock, HorizontalRule, Image, Attachment, Table } from "./block";
import { Headings } from "./headings";
import { NumberedList, BulletList } from "./lists";
import { LeftToRight, RightToLeft } from "./text-direction";
import { Highlight, TextColor } from "./colors";
declare const tools: {
bold: Bold;
italic: Italic;
underline: Underline;
strikethrough: Strikethrough;
code: Code;
formatClear: ClearFormatting;
alignCenter: AlignCenter;
alignRight: AlignRight;
alignLeft: AlignLeft;
alignJustify: AlignJustify;
subscript: Subscript;
superscript: Superscript;
fontSize: FontSize;
fontFamily: FontFamily;
horizontalRule: HorizontalRule;
codeblock: CodeBlock;
blockquote: Blockquote;
headings: Headings;
ltr: LeftToRight;
rtl: RightToLeft;
numberedList: NumberedList;
bulletList: BulletList;
textColor: TextColor;
highlight: Highlight;
link: Link;
image: Image;
attachment: Attachment;
table: Table;
};
export declare type ToolId = keyof typeof tools;
export declare function findToolById(id: ToolId): ITool;
export {};

View File

@@ -0,0 +1,41 @@
import { Bold, Italic, Underline, Strikethrough, Code, Subscript, Superscript, ClearFormatting, Link, } from "./inline";
import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
import { Blockquote, CodeBlock, HorizontalRule, Image, Attachment, Table, } from "./block";
import { Headings } from "./headings";
import { NumberedList, BulletList } from "./lists";
import { LeftToRight, RightToLeft } from "./text-direction";
import { Highlight, TextColor } from "./colors";
var tools = {
bold: new Bold(),
italic: new Italic(),
underline: new Underline(),
strikethrough: new Strikethrough(),
code: new Code(),
formatClear: new ClearFormatting(),
alignCenter: new AlignCenter(),
alignRight: new AlignRight(),
alignLeft: new AlignLeft(),
alignJustify: new AlignJustify(),
subscript: new Subscript(),
superscript: new Superscript(),
fontSize: new FontSize(),
fontFamily: new FontFamily(),
horizontalRule: new HorizontalRule(),
codeblock: new CodeBlock(),
blockquote: new Blockquote(),
headings: new Headings(),
ltr: new LeftToRight(),
rtl: new RightToLeft(),
numberedList: new NumberedList(),
bulletList: new BulletList(),
textColor: new TextColor(),
highlight: new Highlight(),
link: new Link(),
image: new Image(),
attachment: new Attachment(),
table: new Table(),
};
export function findToolById(id) {
return tools[id];
}

View File

@@ -0,0 +1,43 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
import { IconNames } from "../icons";
declare class InlineTool<TId extends ToolId> implements ITool {
readonly id: TId;
readonly title: string;
private readonly icon;
constructor(id: TId, title: string, icon: IconNames);
render: (props: ToolProps) => JSX.Element;
}
export declare class Italic extends InlineTool<ToolId> {
constructor();
}
export declare class Strikethrough extends InlineTool<ToolId> {
constructor();
}
export declare class Underline extends InlineTool<ToolId> {
constructor();
}
export declare class Code extends InlineTool<ToolId> {
constructor();
}
export declare class Bold extends InlineTool<ToolId> {
constructor();
}
export declare class Subscript extends InlineTool<ToolId> {
constructor();
}
export declare class Superscript extends InlineTool<ToolId> {
constructor();
}
export declare class ClearFormatting implements ITool {
id: ToolId;
title: string;
render: (props: ToolProps) => JSX.Element;
}
export declare class Link implements ITool {
id: ToolId;
title: string;
render: (props: ToolProps) => JSX.Element;
}
export {};

View File

@@ -0,0 +1,176 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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, Fragment as _Fragment } from "react/jsx-runtime";
import { ToolButton } from "../components/tool-button";
import { MenuPresenter } from "../../components/menu/menu";
import { useRef, useState } from "react";
import { Flex } from "rebass";
import { Input } from "@rebass/forms";
import { Popup } from "../components/popup";
var InlineTool = /** @class */ (function () {
function InlineTool(id, title, icon) {
var _this = this;
this.id = id;
this.title = title;
this.icon = icon;
this.render = function (props) {
var editor = props.editor;
return (_jsx(ToolButton, { title: _this.title, id: _this.id, icon: _this.icon, onClick: function () { return editor.chain().focus().toggleMark(_this.id).run(); }, toggled: editor.isActive(_this.id) }));
};
}
return InlineTool;
}());
var Italic = /** @class */ (function (_super) {
__extends(Italic, _super);
function Italic() {
return _super.call(this, "italic", "Italic", "italic") || this;
}
return Italic;
}(InlineTool));
export { Italic };
var Strikethrough = /** @class */ (function (_super) {
__extends(Strikethrough, _super);
function Strikethrough() {
return _super.call(this, "strikethrough", "Strikethrough", "strikethrough") || this;
}
return Strikethrough;
}(InlineTool));
export { Strikethrough };
var Underline = /** @class */ (function (_super) {
__extends(Underline, _super);
function Underline() {
return _super.call(this, "underline", "Underline", "underline") || this;
}
return Underline;
}(InlineTool));
export { Underline };
var Code = /** @class */ (function (_super) {
__extends(Code, _super);
function Code() {
return _super.call(this, "code", "Code", "code") || this;
}
return Code;
}(InlineTool));
export { Code };
var Bold = /** @class */ (function (_super) {
__extends(Bold, _super);
function Bold() {
return _super.call(this, "bold", "Bold", "bold") || this;
}
return Bold;
}(InlineTool));
export { Bold };
var Subscript = /** @class */ (function (_super) {
__extends(Subscript, _super);
function Subscript() {
return _super.call(this, "subscript", "Subscript", "subscript") || this;
}
return Subscript;
}(InlineTool));
export { Subscript };
var Superscript = /** @class */ (function (_super) {
__extends(Superscript, _super);
function Superscript() {
return _super.call(this, "superscript", "Superscript", "superscript") || this;
}
return Superscript;
}(InlineTool));
export { Superscript };
var ClearFormatting = /** @class */ (function () {
function ClearFormatting() {
var _this = this;
this.id = "formatClear";
this.title = "Clear all formatting";
this.render = function (props) {
var editor = props.editor;
return (_jsx(ToolButton, { title: _this.title, id: _this.id, icon: "formatClear", onClick: function () {
return editor.chain().focus().clearNodes().unsetAllMarks().run();
}, toggled: false }));
};
}
return ClearFormatting;
}());
export { ClearFormatting };
var Link = /** @class */ (function () {
function Link() {
var _this = this;
this.id = "link";
this.title = "Link";
this.render = function (props) {
var editor = props.editor;
var buttonRef = useRef(null);
var targetRef = useRef();
var _a = useState(false), isOpen = _a[0], setIsOpen = _a[1];
var _b = useState(), href = _b[0], setHref = _b[1];
var _c = useState(), text = _c[0], setText = _c[1];
var currentUrl = editor.getAttributes("link").href;
var isEditing = !!currentUrl;
return (_jsxs(_Fragment, { children: [_jsx(ToolButton, { ref: buttonRef, title: _this.title, id: _this.id, icon: "link", onClick: function () {
if (isEditing)
setHref(currentUrl);
var _a = editor.state.selection, from = _a.from, to = _a.to, $from = _a.$from;
var selectedNode = $from.node();
var pos = selectedNode.isTextblock ? $from.before() : $from.pos;
var domNode = editor.view.nodeDOM(pos);
targetRef.current = domNode;
var selectedText = isEditing
? selectedNode.textContent
: editor.state.doc.textBetween(from, to);
setText(selectedText);
setIsOpen(true);
}, toggled: isOpen || !!isEditing }), _jsx(MenuPresenter, __assign({ options: {
type: "menu",
position: {
target: targetRef.current || buttonRef.current || undefined,
isTargetAbsolute: true,
location: "below",
yOffset: 5,
},
}, isOpen: isOpen, items: [], onClose: function () {
editor.commands.focus();
setIsOpen(false);
} }, { children: _jsx(Popup, __assign({ title: isEditing ? "Edit link" : "Insert link", action: {
text: isEditing ? "Edit" : "Insert",
onClick: function () {
if (!href)
return;
var commandChain = editor
.chain()
.focus()
.extendMarkRange("link")
.setLink({ href: href, target: "_blank" });
if (text)
commandChain = commandChain.insertContent(text).focus();
commandChain.run();
setIsOpen(false);
},
} }, { children: _jsxs(Flex, __assign({ sx: { p: 1, width: 300, flexDirection: "column" } }, { children: [_jsx(Input, { type: "text", placeholder: "Link text", value: text, onChange: function (e) { return setText(e.target.value); } }), _jsx(Input, { type: "url", sx: { mt: 1 }, autoFocus: true, placeholder: "https://example.com/", value: href, onChange: function (e) { return setHref(e.target.value); } })] })) })) }))] }));
};
}
return Link;
}());
export { Link };

View File

@@ -0,0 +1,32 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { Editor } from "@tiptap/core";
import { ToolId } from ".";
import { IconNames } from "../icons";
declare type ListSubType<TListStyleTypes> = {
items: string[];
title: string;
type: TListStyleTypes;
};
declare type ListOptions<TListStyleTypes> = {
icon: IconNames;
type: "bulletList" | "orderedList";
onClick: (editor: Editor) => void;
subTypes: ListSubType<TListStyleTypes>[];
};
declare class ListTool<TListStyleTypes extends string> implements ITool {
readonly id: ToolId;
readonly title: string;
private readonly options;
constructor(id: ToolId, title: string, options: ListOptions<TListStyleTypes>);
render: (props: ToolProps) => JSX.Element;
}
declare type NumberedListStyleTypes = "lower-roman" | "upper-roman" | "lower-greek" | "lower-alpha" | "upper-alpha" | "decimal";
export declare class NumberedList extends ListTool<NumberedListStyleTypes> {
constructor();
}
declare type BulletListStyleTypes = "circle" | "square" | "disc";
export declare class BulletList extends ListTool<BulletListStyleTypes> {
constructor();
}
export {};

View File

@@ -0,0 +1,151 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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 { Box, Button, Flex } from "rebass";
import { Icon } from "../components/icon";
import { Icons } from "../icons";
import { ToolButton } from "../components/tool-button";
import { MenuPresenter } from "../../components/menu/menu";
import { useRef, useState } from "react";
var ListTool = /** @class */ (function () {
function ListTool(id, title, options) {
var _this = this;
this.id = id;
this.title = title;
this.options = options;
this.render = function (props) {
var editor = props.editor;
var _a = useState(false), isOpen = _a[0], setIsOpen = _a[1];
var ref = useRef(null);
var isActive = editor.isActive(_this.options.type);
return (_jsxs(Flex, __assign({ ref: ref }, { children: [_jsx(ToolButton, { title: _this.title, id: _this.id, icon: _this.options.icon, onClick: function () { return _this.options.onClick(editor); }, toggled: isActive, sx: { mr: 0 } }), _jsx(Button, __assign({ sx: {
p: 0,
m: 0,
bg: "transparent",
":hover": { bg: "hover" },
":last-of-type": {
mr: 0,
},
}, onClick: function () { return setIsOpen(function (s) { return !s; }); } }, { children: _jsx(Icon, { path: Icons.chevronDown, color: "text", size: 18 }) })), _jsx(MenuPresenter, { isOpen: isOpen, onClose: function () { return setIsOpen(false); }, items: _this.options.subTypes.map(function (item) { return ({
key: item.type,
tooltip: item.title,
type: "menuitem",
component: function () { return _jsx(ListThumbnail, { listStyleType: item.type }); },
onClick: function () {
var chain = editor.chain().focus();
if (!isActive) {
if (_this.options.type === "bulletList")
chain = chain.toggleBulletList();
else
chain = chain.toggleOrderedList();
}
return chain
.updateAttributes(_this.options.type, { listType: item.type })
.run();
},
}); }), sx: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", p: 1 }, options: {
type: "menu",
position: {
target: ref.current || undefined,
isTargetAbsolute: true,
location: "below",
yOffset: 5,
},
} })] })));
};
}
return ListTool;
}());
var NumberedList = /** @class */ (function (_super) {
__extends(NumberedList, _super);
function NumberedList() {
var options = {
type: "orderedList",
icon: "numberedList",
onClick: function (editor) { return editor.chain().focus().toggleOrderedList().run(); },
subTypes: [
{ type: "decimal", title: "Decimal", items: ["1", "2", "3"] },
{ type: "upper-alpha", title: "Upper alpha", items: ["A", "B", "C"] },
{ type: "lower-alpha", title: "Lower alpha", items: ["a", "b", "c"] },
{
type: "upper-roman",
title: "Upper Roman",
items: ["I", "II", "III"],
},
{
type: "lower-roman",
title: "Lower Roman",
items: ["i", "ii", "iii"],
},
{ type: "lower-greek", title: "Lower Greek", items: ["α", "β", "γ"] },
],
};
return _super.call(this, "numberedList", "Numbered list", options) || this;
}
return NumberedList;
}(ListTool));
export { NumberedList };
var BulletList = /** @class */ (function (_super) {
__extends(BulletList, _super);
function BulletList() {
var options = {
type: "bulletList",
icon: "bulletList",
onClick: function (editor) { return editor.chain().focus().toggleOrderedList().run(); },
subTypes: [
{ type: "disc", title: "Decimal", items: ["1", "2", "3"] },
{ type: "circle", title: "Upper alpha", items: ["A", "B", "C"] },
{ type: "square", title: "Lower alpha", items: ["a", "b", "c"] },
],
};
return _super.call(this, "bulletList", "Bullet list", options) || this;
}
return BulletList;
}(ListTool));
export { BulletList };
function ListThumbnail(props) {
var listStyleType = props.listStyleType;
return (_jsx(Flex, __assign({ as: "ul", sx: {
flexDirection: "column",
flex: 1,
p: 0,
listStyleType: listStyleType,
} }, { children: [0, 0, 0].map(function () { return (_jsx(Box, __assign({ as: "li", sx: {
display: "list-item",
color: "text",
fontSize: 8,
mb: "1px",
} }, { children: _jsx(Flex, __assign({ sx: {
alignItems: "center",
} }, { children: _jsx(Box, { sx: {
width: "100%",
flexShrink: 0,
height: 4,
bg: "#cbcbcb",
borderRadius: "2px",
} }) })) }))); }) })));
}

View File

@@ -0,0 +1,19 @@
/// <reference types="react" />
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
import { IconNames } from "../icons";
declare class TextDirectionTool<TId extends ToolId, TTitle extends string> implements ITool {
readonly id: TId;
readonly title: TTitle;
private readonly icon;
private readonly direction;
constructor(id: TId, title: TTitle, icon: IconNames, direction: "ltr" | "rtl");
render: (props: ToolProps) => JSX.Element;
}
export declare class LeftToRight extends TextDirectionTool<ToolId, string> {
constructor();
}
export declare class RightToLeft extends TextDirectionTool<ToolId, string> {
constructor();
}
export {};

View File

@@ -0,0 +1,49 @@
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { jsx as _jsx } from "react/jsx-runtime";
import { ToolButton } from "../components/tool-button";
var TextDirectionTool = /** @class */ (function () {
function TextDirectionTool(id, title, icon, direction) {
var _this = this;
this.id = id;
this.title = title;
this.icon = icon;
this.direction = direction;
this.render = function (props) {
var editor = props.editor;
return (_jsx(ToolButton, { title: _this.title, id: _this.id, icon: _this.icon, onClick: function () {
return editor.chain().focus().setTextDirection(_this.direction).run();
}, toggled: editor.isActive({ textDirection: _this.direction }) }));
};
}
return TextDirectionTool;
}());
var LeftToRight = /** @class */ (function (_super) {
__extends(LeftToRight, _super);
function LeftToRight() {
return _super.call(this, "ltr", "Left-to-right", "ltr", "ltr") || this;
}
return LeftToRight;
}(TextDirectionTool));
export { LeftToRight };
var RightToLeft = /** @class */ (function (_super) {
__extends(RightToLeft, _super);
function RightToLeft() {
return _super.call(this, "rtl", "Right-to-left", "rtl", "rtl") || this;
}
return RightToLeft;
}(TextDirectionTool));
export { RightToLeft };

12
packages/editor/dist/toolbar/types.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
/// <reference types="react" />
import { Editor } from "@tiptap/core";
import { ToolId } from "./tools";
export declare type ToolProps = {
editor: Editor;
};
export interface ITool {
id: ToolId;
title: string;
description?: string;
render(props: ToolProps): JSX.Element;
}

4
packages/editor/dist/toolbar/types.js vendored Normal file
View File

@@ -0,0 +1,4 @@
export {};
// export interface ToolConstructor {
// new (editor: Editor): ITool;
// }

View File

@@ -1,28 +0,0 @@
import CharacterCount from "@tiptap/extension-character-count";
import Placeholder from "@tiptap/extension-placeholder";
import { EditorOptions, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { useMemo } from "react";
export const useTiptap = (options: Partial<EditorOptions> = {}, deps?: any) => {
const defaultOptions = useMemo<Partial<EditorOptions>>(
() => ({
extensions: [
StarterKit,
CharacterCount,
Placeholder.configure({
placeholder: "Start writing your note...",
}),
],
}),
[]
);
const editor = useEditor({ ...defaultOptions, ...options }, deps);
/**
* Add editor to global for use in React Native.
*/
global.editor = editor;
return editor;
};

File diff suppressed because it is too large Load Diff

View File

@@ -2,25 +2,61 @@
"name": "notesnook-editor",
"version": "0.0.1",
"private": true,
"main": "./",
"main": "./dist/index.js",
"dependencies": {
"@tiptap/extension-history": "^2.0.0-beta.21",
"@mdi/js": "^6.6.96",
"@mdi/react": "^1.5.0",
"@notesnook/theme": "file:../themeprovider",
"@rebass/forms": "^4.0.6",
"@tiptap/extension-character-count": "^2.0.0-beta.24",
"@tiptap/extension-color": "^2.0.0-beta.9",
"@tiptap/extension-font-family": "^2.0.0-beta.21",
"@tiptap/extension-highlight": "^2.0.0-beta.33",
"@tiptap/extension-history": "^2.0.0-beta.21",
"@tiptap/extension-horizontal-rule": "^2.0.0-beta.31",
"@tiptap/extension-link": "^2.0.0-beta.36",
"@tiptap/extension-placeholder": "^2.0.0-beta.45",
"@tiptap/extension-subscript": "^2.0.0-beta.10",
"@tiptap/extension-superscript": "^2.0.0-beta.10",
"@tiptap/extension-table": "^2.0.0-beta.48",
"@tiptap/extension-table-cell": "^2.0.0-beta.20",
"@tiptap/extension-table-header": "^2.0.0-beta.22",
"@tiptap/extension-table-row": "^2.0.0-beta.19",
"@tiptap/extension-text-align": "^2.0.0-beta.29",
"@tiptap/extension-text-style": "^2.0.0-beta.23",
"@tiptap/extension-underline": "^2.0.0-beta.23",
"@tiptap/react": "^2.0.0-beta.98",
"@tiptap/starter-kit": "^2.0.0-beta.150"
"@tiptap/starter-kit": "^2.0.0-beta.150",
"@types/rebass": "^4.0.10",
"@types/rebass__forms": "^4.0.6",
"emotion-theming": "^10.0.19",
"prosemirror-tables": "^1.1.1",
"re-resizable": "^6.9.5",
"react-color": "^2.19.3",
"react-modal": "^3.14.4",
"react-toggle": "^4.1.2",
"reactjs-popup": "^2.0.5",
"rebass": "^4.0.7",
"tinycolor2": "^1.4.2",
"tippy.js": "^6.3.7",
"zustand": "^3.7.2"
},
"devDependencies": {
"@types/node": "^16.11.11",
"@types/react": "^17.0.37",
"@types/react-color": "^3.0.6",
"@types/react-dom": "^17.0.11",
"@types/react-modal": "^3.13.1",
"@types/react-toggle": "^4.0.3",
"@types/tinycolor2": "^1.4.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"typescript": "^4.5.2",
"typescript-plugin-css-modules": "^3.4.0",
"web-vitals": "^1.1.2"
},
"scripts": {
"build": "react-scripts build"
"build": "tsc"
}
}

View File

@@ -0,0 +1 @@
export { MenuPresenter } from "./menu";

View File

@@ -0,0 +1,377 @@
import React, {
useCallback,
useRef,
useEffect,
useState,
PropsWithChildren,
} from "react";
import ReactDOM from "react-dom";
import { Box, Flex, FlexProps, Text } from "rebass";
import { getPosition, MenuOptions } from "./useMenu";
// import { FlexScrollContainer } from "../scrollcontainer";
import MenuItem from "./menuitem";
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 { store as selectionStore } from "../../stores/selectionstore";
function useMenuFocus(
items: MenuItemType[],
onAction: (event: KeyboardEvent) => void,
onClose: (event: KeyboardEvent) => void
) {
const [focusIndex, setFocusIndex] = useState(-1);
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
const moveItemIntoView = useCallback(
(index) => {
const item = items[index];
if (!item) return;
const element = document.getElementById(item.key);
if (!element) return;
element.scrollIntoView({
behavior: "auto",
});
},
[items]
);
const onKeyDown = useCallback(
(e: KeyboardEvent) => {
const isSeperator = (i: number) =>
items && (items[i]?.type === "seperator" || items[i]?.isDisabled);
const moveDown = (i: number) => (i < items.length - 1 ? ++i : 0);
const moveUp = (i: number) => (i > 0 ? --i : items.length - 1);
const hasSubmenu = (i: number) => items && items[i]?.hasSubmenu;
const openSubmenu = (index: number) => {
if (!hasSubmenu(index)) return;
setIsSubmenuOpen(true);
};
const closeSubmenu = (index: number) => {
if (!hasSubmenu(index)) return;
setIsSubmenuOpen(false);
};
setFocusIndex((i) => {
let nextIndex = i;
switch (e.key) {
case "ArrowUp":
if (isSubmenuOpen) break;
nextIndex = moveUp(i);
while (isSeperator(nextIndex)) {
nextIndex = moveUp(nextIndex);
}
break;
case "ArrowDown":
if (isSubmenuOpen) break;
nextIndex = moveDown(i);
while (isSeperator(nextIndex)) {
nextIndex = moveDown(nextIndex);
}
break;
case "ArrowRight":
openSubmenu(i);
break;
case "ArrowLeft":
closeSubmenu(i);
break;
case "Enter":
onAction && onAction(e);
break;
case "Escape":
onClose && onClose(e);
break;
default:
break;
}
if (nextIndex !== i) moveItemIntoView(nextIndex);
return nextIndex;
});
},
[items, isSubmenuOpen, moveItemIntoView, onAction]
);
useEffect(() => {
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
};
}, [onKeyDown]);
return { focusIndex, setFocusIndex, isSubmenuOpen, setIsSubmenuOpen };
}
type MenuProps = MenuContainerProps & {
items: MenuItemType[];
closeMenu: () => void;
};
export function Menu(props: MenuProps) {
const { items, title, closeMenu, ...containerProps } = props;
const hoverTimeout = useRef<NodeJS.Timeout>();
const onAction = useCallback(
(e, item) => {
e.stopPropagation();
if (closeMenu) closeMenu();
if (item.onClick) {
item.onClick();
}
},
[closeMenu]
);
const { focusIndex, setFocusIndex, isSubmenuOpen, setIsSubmenuOpen } =
useMenuFocus(
items,
(e) => {
const item = items[focusIndex];
if (item) onAction(e, item);
},
() => closeMenu()
);
const subMenuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const item = items[focusIndex];
if (!item || !subMenuRef.current) return;
const menuItemElement = document.getElementById(item.key);
if (!menuItemElement) return;
if (!isSubmenuOpen) {
subMenuRef.current.style.visibility = "hidden";
return;
}
const { top, left } = getPosition(subMenuRef.current, {
yOffset: menuItemElement.offsetHeight,
target: menuItemElement,
location: "right",
});
subMenuRef.current.style.visibility = "visible";
subMenuRef.current.style.top = `${top}px`;
subMenuRef.current.style.left = `${left}px`;
}, [isSubmenuOpen, focusIndex, items]);
return (
<>
<MenuContainer {...containerProps}>
{items.map((item, index) => (
<MenuItem
key={item.key}
item={item}
onClick={(e) => {
if (item.items?.length) {
setFocusIndex(index);
setIsSubmenuOpen(true);
} else onAction(e, item);
}}
isFocused={focusIndex === index}
onMouseEnter={() => {
if (item.isDisabled) {
setFocusIndex(-1);
return;
}
if (hoverTimeout.current) clearTimeout(hoverTimeout.current);
setFocusIndex(index);
setIsSubmenuOpen(false);
if (item.items?.length) {
hoverTimeout.current = setTimeout(() => {
setIsSubmenuOpen(true);
}, 500);
}
}}
onMouseLeave={() => {
if (hoverTimeout.current) clearTimeout(hoverTimeout.current);
}}
/>
))}
</MenuContainer>
{isSubmenuOpen && (
<Flex
ref={subMenuRef}
style={{ visibility: "hidden" }}
sx={{
position: "absolute",
}}
>
<Menu items={items[focusIndex]?.items || []} closeMenu={closeMenu} />
</Flex>
)}
</>
);
}
type MenuContainerProps = FlexProps & {
title?: string;
};
function MenuContainer(props: PropsWithChildren<MenuContainerProps>) {
const { children, title, sx, ...flexProps } = props;
return (
<Box
className="menuContainer"
as="ul"
tabIndex={-1}
sx={{
bg: "background",
py: 1,
display: "flex",
flexDirection: "column",
position: "relative",
listStyle: "none",
padding: 0,
margin: 0,
borderRadius: "default",
boxShadow: "menu",
border: "1px solid var(--border)",
width: 220,
...sx,
}}
{...flexProps}
>
{title && (
<Text
sx={{
fontFamily: "body",
fontSize: "subtitle",
color: "primary",
py: "8px",
px: 3,
borderBottom: "1px solid",
borderBottomColor: "border",
wordWrap: "break-word",
}}
>
{title}
</Text>
)}
{children}
{/* <FlexScrollContainer>{children}</FlexScrollContainer> */}
</Box>
);
}
type MenuPresenterProps = MenuContainerProps & {
items: MenuItemType[];
options: MenuOptions;
isOpen: boolean;
onClose: () => void;
className?: string;
};
export function MenuPresenter(props: PropsWithChildren<MenuPresenterProps>) {
const {
className,
options,
items,
isOpen,
onClose,
children,
...containerProps
} = props;
// const { isOpen, closeMenu } = useMenuTrigger();
// const { items, } = useMenu();
const { position, type } = options;
const isAutocomplete = type === "autocomplete";
const contentRef = useRef<HTMLDivElement>();
useEffect(() => {
if (!contentRef.current || !position) return;
const menu = contentRef.current;
const menuPosition = getPosition(menu, position);
menu.style.top = menuPosition.top + "px";
menu.style.left = menuPosition.left + "px";
}, [position]);
return (
<Modal
contentRef={(ref) => (contentRef.current = ref)}
className={className || "menuContainer"}
role="menu"
isOpen={isOpen}
appElement={document.body}
shouldCloseOnEsc
shouldReturnFocusAfterClose
shouldCloseOnOverlayClick
shouldFocusAfterRender={!isAutocomplete}
ariaHideApp={!isAutocomplete}
preventScroll={!isAutocomplete}
onRequestClose={onClose}
portalClassName={className || "menuPresenter"}
onAfterOpen={(obj) => {
if (!obj || !position) return;
const { contentEl: menu } = obj;
const menuPosition = getPosition(menu, position);
menu.style.top = menuPosition.top + "px";
menu.style.left = menuPosition.left + "px";
}}
overlayElement={(props, contentEl) => {
return (
<Box
{...props}
style={{
...props.style,
position: isAutocomplete ? "initial" : "fixed",
zIndex: 1000,
backgroundColor: isAutocomplete ? "transparent" : "unset",
}}
// onClick={(e) => {
// if (!(e.target instanceof HTMLElement)) return;
// console.log(e.target.closest(".ReactModal__Content"));
// if (e.target.closest(".ReactModal__Content")) return;
// onClose();
// }}
// onContextMenu={(e) => {
// e.preventDefault();
// onClose();
// }}
>
{contentEl}
</Box>
);
}}
contentElement={(props, children) => (
<Box
{...props}
style={{}}
sx={{
top: 0,
left: 0,
right: 0,
bottom: 0,
display: "flex",
width: "fit-content",
height: "fit-content",
position: "absolute",
backgroundColor: undefined,
padding: 0,
zIndex: 0,
outline: 0,
isolation: "isolate",
}}
>
{children}
</Box>
)}
style={{
content: {},
overlay: {
zIndex: 999,
background: "transparent",
},
}}
>
{props.children ? (
props.children
) : (
<Menu items={items} closeMenu={onClose} {...containerProps} />
)}
</Modal>
);
}

View File

@@ -0,0 +1,121 @@
// import { Check, ChevronRight, Pro } from "../icons";
import { useRef } from "react";
import { Flex, Box, Text, Button } from "rebass";
import { Icon } from "../../toolbar/components/icon";
import { Icons } from "../../toolbar/icons";
import { MenuItem /*ResolvedMenuItem*/ } from "./types";
type MenuItemProps = {
// item: ResolvedMenuItem<any>;
item: MenuItem;
isFocused: boolean;
onMouseEnter: () => void;
onMouseLeave: () => void;
onClick: React.MouseEventHandler<HTMLButtonElement>;
};
function MenuItem(props: MenuItemProps) {
const { item, isFocused, onMouseEnter, onMouseLeave, onClick } = props;
const {
title,
key,
// color,
icon,
// iconColor,
type,
tooltip,
isDisabled,
isChecked,
hasSubmenu,
component: Component,
modifier,
} = item;
const itemRef = useRef<HTMLButtonElement>(null);
if (type === "seperator")
return (
<Box
as="li"
key={key}
sx={{
width: "95%",
height: "0.5px",
bg: "border",
my: 2,
alignSelf: "center",
}}
/>
);
return (
<Flex
as="li"
sx={{ flex: 1, flexDirection: "column" }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
<Button
id={key}
data-test-id={`menuitem-${key}`}
key={key}
ref={itemRef}
tabIndex={-1}
variant="menuitem"
title={tooltip}
disabled={isDisabled}
onClick={onClick}
sx={{
bg: isFocused ? "hover" : "transparent",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
{Component ? (
<Component />
) : (
<>
<Flex>
{icon && (
<Icon
path={Icons[icon]}
color={"text"}
size={15}
sx={{ mr: 2 }}
/>
)}
<Text
as="span"
sx={{
fontFamily: "body",
fontSize: "menu",
color: "text",
}}
>
{title}
</Text>
</Flex>
<Flex>
{isChecked && <Icon path={Icons.check} size={14} />}
{/*
{hasSubmenu && <ChevronRight size={14} />} */}
{modifier && (
<Text
as="span"
sx={{
fontFamily: "body",
fontSize: "menu",
color: "fontTertiary",
}}
>
{modifier}
</Text>
)}
</Flex>
</>
)}
</Button>
</Flex>
);
}
export default MenuItem;

View File

@@ -0,0 +1,37 @@
// export type ResolverFunction<T, TData> = (
// data: any,
// item: MenuItem<TData>
// ) => T;
// export type Resolvable<T, TData> = T | ResolverFunction<T, TData>;
// export type MenuItem<TData> = {
// type: "menuitem" | "seperator";
// key: string;
// component?: Resolvable<(props: any) => JSX.Element, TData>;
// onClick?: (data: TData, item: MenuItem<TData>) => void;
// title?: Resolvable<string, TData>;
// icon?: Resolvable<string, TData>;
// tooltip?: Resolvable<string, TData>;
// disabled?: Resolvable<string, TData>;
// hidden?: Resolvable<boolean, TData>;
// checked?: Resolvable<boolean, TData>;
// modifier?: Resolvable<string[], TData>;
// items?: Resolvable<MenuItem<TData>[], TData>;
// };
import { IconNames } from "../../toolbar/icons";
export type MenuItem = {
type: "menuitem" | "seperator";
key: string;
component?: (props: any) => JSX.Element;
onClick?: () => void;
title?: string;
icon?: IconNames;
tooltip?: string;
isDisabled?: boolean;
isHidden?: boolean;
isChecked?: boolean;
hasSubmenu?: boolean;
modifier?: string;
items?: MenuItem[];
};

View File

@@ -0,0 +1,296 @@
import create from "zustand";
import shallow from "zustand/shallow";
import {
MenuItem,
// Resolvable,
// ResolvedMenuItem,
// ResolverFunction,
} from "./types";
type PositionData = {
x: number;
y: number;
actualX: number;
actualY: number;
width?: number;
height?: number;
};
const mousePosition: PositionData = { x: 0, y: 0, actualX: 0, actualY: 0 };
window.addEventListener("mousemove", (e) => {
const { x, y, actualX, actualY } = getMousePosition(e);
mousePosition.x = x;
mousePosition.y = y;
mousePosition.actualX = actualX;
mousePosition.actualY = actualY;
});
export type MenuOptions = {
type: "autocomplete" | "menu";
position?: PositionOptions;
};
// interface IMenuStore {
// isOpen?: boolean;
// // items?: ResolvedMenuItem<any>[];
// items?: MenuItem[];
// title?: string;
// options?: MenuOptions;
// //data?: any;
// // open: <TData>(items: MenuItem<TData>[], data: TData) => void;
// open: (items: MenuItem[]) => void;
// close: () => void;
// }
// const useMenuStore = create<IMenuStore>((set) => ({
// isOpen: false,
// items: [],
// title: undefined,
// options: undefined,
// // data: undefined,
// // open: <TData>(items: MenuItem<TData>[], data: TData) =>
// // set((state) => {
// // state.isOpen = true;
// // state.items = mapMenuItems(items, data);
// // // state.data = data;
// // }),
// open: (items: MenuItem[], options?: MenuOptions) =>
// set((state) => {
// state.isOpen = true;
// state.items = items.filter((item) => !item.isHidden);
// state.options = options;
// // state.data = data;
// }),
// close: () =>
// set((state) => {
// state.isOpen = false;
// state.items = [];
// state.options = undefined;
// // state.data = undefined;
// state.title = undefined;
// }),
// }));
// export function useMenuTrigger() {
// const isOpen = useMenuStore((store) => store.isOpen);
// const [open, close] = useMenuStore(
// (store) => [store.open, store.close],
// shallow
// );
// return {
// openMenu: open,
// closeMenu: close,
// isOpen,
// };
// }
// export function useMenu() {
// const [items, options] = useMenuStore((store) => [
// store.items,
// store.options,
// ]);
// return { items, options };
// }
type PositionOptions = {
target?: HTMLElement | "mouse";
isTargetAbsolute?: boolean;
location?: "right" | "left" | "below" | "top";
align?: "center" | "start" | "end";
yOffset?: number;
xOffset?: number;
yAnchor?: HTMLElement;
parent?: HTMLElement | Element;
};
export function getPosition(
element: HTMLElement,
options: PositionOptions
): { top: number; left: number } {
const {
target = "mouse",
isTargetAbsolute = false,
location = undefined,
yOffset = 0,
xOffset = 0,
align = "start",
parent = document.body,
yAnchor,
} = options || {};
const { x, y, width, height, actualX, actualY } =
target === "mouse"
? mousePosition
: getElementPosition(target, isTargetAbsolute);
const elementWidth = element.offsetWidth;
const elementHeight = element.offsetHeight;
const windowWidth = parent.clientWidth;
const windowHeight = parent.clientHeight - 20;
let position = { top: 0, left: 0 };
if (windowWidth - actualX < elementWidth) {
const xDiff = actualX - x;
position.left = windowWidth - elementWidth;
position.left -= xDiff;
} else {
position.left = x;
}
if (width) {
if (location === "right") position.left += width;
else if (location === "left") position.left -= elementWidth;
}
if (windowHeight - actualY < elementHeight) {
const yDiff = actualY - y;
position.top = windowHeight - elementHeight;
position.top -= yDiff;
} else {
position.top = y;
}
if (height) {
if (location === "below") position.top += height;
else if (location === "top") position.top -= height;
}
if (target !== "mouse" && align === "center" && elementWidth > 0) {
position.left -= elementWidth / 2 - target.clientWidth / 2;
}
// Adjust menu height
if (elementHeight > windowHeight - position.top) {
element.style.maxHeight = `${windowHeight - 20}px`;
}
if (yAnchor) {
const anchorY = getElementPosition(yAnchor, isTargetAbsolute);
position.top = anchorY.actualY - elementHeight;
}
position.top = position.top < 0 ? 0 : position.top;
position.left = position.left < 0 ? 0 : position.left;
position.top += yOffset;
position.left += xOffset;
return position;
}
function getMousePosition(e: MouseEvent) {
var posx = 0;
var posy = 0;
if (!e && window.event) e = window.event as MouseEvent;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
} else if (e.clientX || e.clientY) {
posx =
e.clientX +
document.body.scrollLeft +
document.documentElement.scrollLeft;
posy =
e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return {
x: posx,
y: posy,
actualY: posy,
actualX: posx,
};
}
export function getElementPosition(
element: HTMLElement,
absolute: boolean
): PositionData {
const rect = element.getBoundingClientRect();
const position: PositionData = {
x: element.offsetLeft,
y: element.offsetTop,
width: rect.width,
height: rect.height,
actualY: rect.y,
actualX: rect.x,
};
if (absolute) {
position.x = position.actualX;
position.y = position.actualY;
}
return position;
}
// function mapMenuItems<TData>(
// items: MenuItem<TData>[],
// data: TData
// ): ResolvedMenuItem<TData>[] {
// return items.reduce((prev, item) => {
// const { key, onClick: _onClick, disabled, hidden, checked, type } = item;
// const isHidden = resolveProp(hidden, data, item);
// if (isHidden) return prev;
// const isSeperator = type === "seperator";
// if (isSeperator) {
// prev.push({ isSeperator: true });
// return prev;
// }
// const title = resolveProp(item.title, data, item);
// const icon = resolveProp(item.icon, data, item);
// const isChecked = resolveProp(checked, data, item);
// const isDisabled = resolveProp(disabled, data, item);
// const items = resolveProp(item.items, data, item);
// const modifier = resolveProp(item.modifier, data, item);
// const onClick =
// typeof _onClick === "function" && _onClick.bind(this, data, item);
// const tooltip =
// isDisabled || resolveProp(item.tooltip, data, item) || title;
// const hasSubmenu = items?.length > 0;
// const menuItem: ResolvedMenuItem<TData> = {
// type,
// title,
// key,
// onClick,
// tooltip,
// isChecked,
// isDisabled: !!isDisabled,
// isHidden,
// hasSubmenu,
// icon,
// modifier: modifier?.join("+"),
// };
// if (hasSubmenu)
// menuItem.items = mapMenuItems(items, { ...data, parent: menuItem });
// prev.push(menuItem);
// return prev;
// }, []);
// }
// function resolveProp<T, TData>(
// prop: Resolvable<T, TData>,
// data: any,
// item: MenuItem<TData>
// ): T {
// if (typeof prop === "function" && (prop as any).isReactComponent) {
// return prop as T;
// }
// return isResolverFunction<T, TData>(prop) ? prop(data, item) : prop;
// }
// function isResolverFunction<T, TData>(
// prop: any
// ): prop is ResolverFunction<T, TData> {
// return typeof prop === "function";
// }

View File

@@ -0,0 +1,46 @@
import ReactToggle, { ToggleProps } from "react-toggle";
import "react-toggle/style.css";
const css = `.react-toggle {
display: flex;
align-items: center;
}
.react-toggle-thumb {
box-shadow: none;
}
.react-toggle-track {
width: 30px;
height: 18px;
}
.react-toggle-thumb {
width: 16px;
height: 16px;
top: 0px;
left: 1px;
margin-top: 1px;
}
.react-toggle--checked .react-toggle-thumb {
left: 13px;
border-color: var(--primary);
}
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
box-shadow: none;
}
.react-toggle--focus .react-toggle-thumb {
box-shadow: none;
}
`;
export function Toggle(props: ToggleProps) {
return (
<>
<style>{css}</style>
<ReactToggle size={20} onChange={() => {}} icons={false} {...props} />
</>
);
}

View File

@@ -0,0 +1,21 @@
import TiptapBulletList from "@tiptap/extension-bullet-list";
export const BulletList = TiptapBulletList.extend({
addAttributes() {
return {
listType: {
default: null,
parseHTML: (element) => element.style.listStyleType,
renderHTML: (attributes) => {
if (!attributes.listType) {
return {};
}
return {
style: `list-style-type: ${attributes.listType}`,
};
},
},
};
},
});

View File

@@ -0,0 +1,5 @@
import { BulletList } from "./bullet-list";
export * from "./bullet-list";
export default BulletList;

View File

@@ -0,0 +1,72 @@
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";
type FontSizeOptions = {
types: string[];
defaultFontSize: number;
};
declare module "@tiptap/core" {
interface Commands<ReturnType> {
fontSize: {
/**
* Set the font family
*/
setFontSize: (fontSize: string) => ReturnType;
/**
* Unset the font family
*/
unsetFontSize: () => ReturnType;
};
}
}
export const FontSize = Extension.create<FontSizeOptions>({
name: "fontSize",
defaultOptions: {
types: ["textStyle"],
defaultFontSize: 16,
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
fontSize: {
default: `${this.options.defaultFontSize}px`,
parseHTML: (element) => element.style.fontSize,
renderHTML: (attributes) => {
if (!attributes.fontSize) {
return {};
}
return {
style: `font-size: ${attributes.fontSize}`,
};
},
},
},
},
];
},
addCommands() {
return {
setFontSize:
(fontSize) =>
({ chain }) => {
return chain().setMark("textStyle", { fontSize }).run();
},
unsetFontSize:
() =>
({ chain }) => {
return chain()
.setMark("textStyle", { fontSize: null })
.removeEmptyTextStyle()
.run();
},
};
},
});

View File

@@ -0,0 +1,5 @@
import { FontSize } from "./font-size";
export * from "./font-size";
export default FontSize;

View File

@@ -0,0 +1,265 @@
import { Box, Flex, Image, ImageProps, Text } from "rebass";
import { NodeViewWrapper, NodeViewProps, FloatingMenu } from "@tiptap/react";
import {
ImageAlignmentOptions,
ImageAttributes,
ImageSizeOptions,
} from "./image";
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
import { ThemeProvider } from "emotion-theming";
import { Theme, useTheme } from "@notesnook/theme";
import { Resizable } from "re-resizable";
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 { Popup } from "../../toolbar/components/popup";
import { Toggle } from "../../components/toggle";
import { Input } from "@rebass/forms";
export function ImageComponent(props: ImageProps & NodeViewProps) {
const { src, alt, title, width, height, align, float } = props.node
.attrs as ImageAttributes & ImageAlignmentOptions;
const { editor, updateAttributes } = props;
const imageRef = useRef<HTMLImageElement>();
const isActive = editor.isActive("image", { src });
const [isToolbarVisible, setIsToolbarVisible] = useState<boolean>();
const theme = editor.storage.theme as Theme;
useEffect(() => {
setIsToolbarVisible(isActive);
}, [isActive]);
return (
<NodeViewWrapper>
<ThemeProvider theme={theme}>
<Box
sx={{
display: float ? "block" : "flex",
justifyContent: float
? "stretch"
: align === "center"
? "center"
: align === "left"
? "start"
: "end",
}}
>
<Resizable
style={{
float: float ? (align === "left" ? "left" : "right") : "none",
}}
size={{
height: height || "auto",
width: width || "auto",
}}
maxWidth="100%"
onResizeStop={(e, direction, ref, d) => {
updateAttributes({
width: ref.clientWidth,
height: ref.clientHeight,
});
}}
lockAspectRatio={true}
>
<Flex sx={{ position: "relative", justifyContent: "end" }}>
{isToolbarVisible && (
<ImageToolbar
editor={editor}
float={float}
align={align}
height={height || 0}
width={width || 0}
/>
)}
</Flex>
<Image
ref={imageRef}
src={src}
alt={alt}
title={title}
width={"100%"}
height={"100%"}
sx={{
border: isActive
? "2px solid var(--primary)"
: "2px solid transparent",
borderRadius: "default",
}}
{...props}
/>
</Resizable>
</Box>
</ThemeProvider>
</NodeViewWrapper>
);
}
type ImageToolbarProps = ImageAlignmentOptions &
Required<ImageSizeOptions> & {
editor: Editor;
};
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 (
<Flex
sx={{
flexDirection: "column",
position: "absolute",
top: -40,
mb: 2,
zIndex: 9999,
alignItems: "end",
}}
>
<Flex
sx={{
bg: "background",
boxShadow: "menu",
flexWrap: "nowrap",
borderRadius: "default",
mb: 2,
}}
>
<Flex
className="toolbar-group"
sx={{
pr: 1,
mr: 1,
borderRight: "1px solid var(--border)",
":last-of-type": { mr: 0, pr: 0, borderRight: "none" },
}}
>
<ToolButton
toggled={false}
title="Align left"
id="alignLeft"
icon="alignLeft"
onClick={() =>
editor.chain().focus().setImageAlignment({ align: "left" }).run()
}
/>
{float ? null : (
<ToolButton
toggled={false}
title="Align center"
id="alignCenter"
icon="alignCenter"
onClick={() =>
editor
.chain()
.focus()
.setImageAlignment({ align: "center" })
.run()
}
/>
)}
<ToolButton
toggled={false}
title="Align right"
id="alignRight"
icon="alignRight"
onClick={() =>
editor.chain().focus().setImageAlignment({ align: "right" }).run()
}
/>
</Flex>
<Flex
className="toolbar-group"
sx={{
pr: 1,
mr: 1,
borderRight: "1px solid var(--border)",
":last-of-type": { mr: 0, pr: 0, borderRight: "none" },
}}
>
<ToolButton
toggled={isOpen}
title="Image properties"
id="imageProperties"
icon="more"
onClick={() => setIsOpen((s) => !s)}
/>
</Flex>
</Flex>
{isOpen && (
<Popup
title="Image properties"
action={{
icon: "close",
onClick: () => {
setIsOpen(false);
},
}}
>
<Flex sx={{ width: 200, flexDirection: "column", p: 1 }}>
<Flex
sx={{ justifyContent: "space-between", alignItems: "center" }}
>
<Text variant={"body"}>Floating?</Text>
<Toggle
checked={float}
onClick={() =>
editor
.chain()
.setImageAlignment({ float: !float, align: "left" })
.run()
}
/>
</Flex>
<Flex sx={{ alignItems: "center", mt: 2 }}>
<Input
type="number"
placeholder="Width"
value={width}
sx={{
mr: 2,
p: 1,
fontSize: "body",
}}
onChange={(e) => onSizeChange(e.target.valueAsNumber)}
/>
<Input
type="number"
placeholder="Height"
value={height}
sx={{ p: 1, fontSize: "body" }}
onChange={(e) =>
onSizeChange(undefined, e.target.valueAsNumber)
}
/>
</Flex>
</Flex>
</Popup>
)}
</Flex>
);
}

Some files were not shown because too many files have changed in this diff Show More