diff --git a/packages/editor/dist/extensions/taskitem/component.d.ts b/packages/editor/dist/extensions/taskitem/component.d.ts
new file mode 100644
index 000000000..0d615e09b
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/component.d.ts
@@ -0,0 +1,4 @@
+///
+import { ImageProps } from "rebass";
+import { NodeViewProps } from "@tiptap/react";
+export declare function TaskItemComponent(props: ImageProps & NodeViewProps): JSX.Element;
diff --git a/packages/editor/dist/extensions/taskitem/component.js b/packages/editor/dist/extensions/taskitem/component.js
new file mode 100644
index 000000000..d2a4b04b5
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/component.js
@@ -0,0 +1,130 @@
+var __assign = (this && this.__assign) || function () {
+ __assign = Object.assign || function(t) {
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
+ s = arguments[i];
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
+ t[p] = s[p];
+ }
+ return t;
+ };
+ return __assign.apply(this, arguments);
+};
+import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+import { Flex } from "rebass";
+import { NodeViewWrapper, NodeViewContent, } from "@tiptap/react";
+import { ThemeProvider } from "emotion-theming";
+import { Icon } from "../../toolbar/components/icon";
+import { Icons } from "../../toolbar/icons";
+import { findChildren, } from "@tiptap/core";
+import { useCallback } from "react";
+export function TaskItemComponent(props) {
+ var checked = props.node.attrs.checked;
+ var editor = props.editor, updateAttributes = props.updateAttributes, node = props.node, getPos = props.getPos;
+ // const [isOpen, setIsOpen] = useState(true);
+ // const elementRef = useRef();
+ // const isActive = editor.isActive("attachment", { hash });
+ // const [isToolbarVisible, setIsToolbarVisible] = useState();
+ var theme = editor.storage.theme;
+ // useEffect(() => {
+ // setIsToolbarVisible(isActive);
+ // }, [isActive]);
+ var toggle = useCallback(function () {
+ if (!editor.isEditable)
+ return false;
+ updateAttributes({ checked: !checked });
+ var tr = editor.state.tr;
+ var parentPos = getPos();
+ toggleChildren(node, tr, !checked, parentPos);
+ editor.view.dispatch(tr);
+ return true;
+ }, [editor, getPos, node]);
+ var nestedTaskList = getChildren(node, getPos()).find(function (_a) {
+ var node = _a.node;
+ return node.type.name === "taskList";
+ });
+ var isNested = !!nestedTaskList;
+ return (_jsx(NodeViewWrapper, { children: _jsx(ThemeProvider, __assign({ theme: theme }, { children: _jsxs(Flex, __assign({ sx: {
+ mb: 2,
+ ":hover > .dragHandle, :hover > .toggleSublist": {
+ opacity: 1,
+ },
+ } }, { children: [_jsxs(Flex, __assign({ sx: { flex: 1 } }, { children: [_jsx(Icon, { className: "dragHandle", draggable: "true", contentEditable: false, "data-drag-handle": true, path: Icons.dragHandle, sx: {
+ opacity: 0,
+ alignSelf: "start",
+ mr: 2,
+ cursor: "grab",
+ ".icon:hover path": {
+ fill: "var(--disabled) !important",
+ },
+ }, size: 20 }), _jsx(Icon, { path: checked ? Icons.check : "", sx: {
+ border: "2px solid",
+ borderColor: checked ? "disabled" : "icon",
+ borderRadius: "default",
+ alignSelf: "start",
+ mr: 2,
+ p: "1px",
+ cursor: "pointer",
+ ":hover": {
+ borderColor: "disabled",
+ },
+ ":hover .icon path": {
+ fill: "var(--disabled) !important",
+ },
+ }, onMouseEnter: function (e) {
+ if (e.buttons > 0) {
+ toggle();
+ }
+ }, onMouseDown: function (e) {
+ if (toggle())
+ e.preventDefault();
+ }, color: checked ? "disabled" : "icon", size: 13 }), _jsx(NodeViewContent, { as: "li", style: {
+ listStyleType: "none",
+ textDecorationLine: checked ? "line-through" : "none",
+ color: checked ? "var(--disabled)" : "var(--text)",
+ flex: 1,
+ } })] })), isNested && (_jsx(Icon, { className: "toggleSublist", path: nestedTaskList.node.attrs.collapsed
+ ? Icons.chevronDown
+ : Icons.chevronUp, sx: {
+ opacity: 0,
+ position: "absolute",
+ right: 0,
+ alignSelf: "start",
+ mr: 2,
+ cursor: "pointer",
+ ".icon:hover path": {
+ fill: "var(--disabled) !important",
+ },
+ }, size: 20, onClick: function () {
+ editor
+ .chain()
+ .setNodeSelection(getPos())
+ .command(function (_a) {
+ var tr = _a.tr;
+ var pos = nestedTaskList.pos, node = nestedTaskList.node;
+ tr.setNodeMarkup(pos, undefined, {
+ collapsed: !node.attrs.collapsed,
+ });
+ return true;
+ })
+ .run();
+ } }))] })) })) }));
+}
+function toggleChildren(node, tr, toggleState, parentPos) {
+ var children = findChildren(node, function (node) { return node.type.name === "taskItem"; });
+ for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
+ var pos = children_1[_i].pos;
+ // need to add 1 to get inside the node
+ var actualPos = pos + parentPos + 1;
+ tr.setNodeMarkup(actualPos, undefined, {
+ checked: toggleState,
+ });
+ }
+ return tr;
+}
+function getChildren(node, parentPos) {
+ var children = [];
+ node.forEach(function (node, offset) {
+ children.push({ node: node, pos: parentPos + offset + 1 });
+ });
+ return children;
+}
diff --git a/packages/editor/dist/extensions/taskitem/index.d.ts b/packages/editor/dist/extensions/taskitem/index.d.ts
new file mode 100644
index 000000000..3518ff3b2
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/index.d.ts
@@ -0,0 +1 @@
+export * from "./task-item";
diff --git a/packages/editor/dist/extensions/taskitem/index.js b/packages/editor/dist/extensions/taskitem/index.js
new file mode 100644
index 000000000..3518ff3b2
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/index.js
@@ -0,0 +1 @@
+export * from "./task-item";
diff --git a/packages/editor/dist/extensions/taskitem/taskitem.d.ts b/packages/editor/dist/extensions/taskitem/taskitem.d.ts
new file mode 100644
index 000000000..ed24800a1
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/taskitem.d.ts
@@ -0,0 +1,3 @@
+export interface AttachmentOptions {
+}
+export declare const TaskItemNode: import("@tiptap/core").Node;
diff --git a/packages/editor/dist/extensions/taskitem/taskitem.js b/packages/editor/dist/extensions/taskitem/taskitem.js
new file mode 100644
index 000000000..b0adabe6c
--- /dev/null
+++ b/packages/editor/dist/extensions/taskitem/taskitem.js
@@ -0,0 +1,50 @@
+import { mergeAttributes } from "@tiptap/core";
+import { TaskItem } from "@tiptap/extension-task-item";
+import { ReactNodeViewRenderer } from "@tiptap/react";
+import { TaskItemComponent } from "./component";
+export var TaskItemNode = TaskItem.extend({
+ draggable: true,
+ addAttributes: function () {
+ return {
+ checked: {
+ default: false,
+ keepOnSplit: false,
+ parseHTML: function (element) { return element.classList.contains("checked"); },
+ renderHTML: function (attributes) { return ({
+ class: attributes.checked ? "checked" : "",
+ }); },
+ },
+ };
+ },
+ renderHTML: function (_a) {
+ var node = _a.node, HTMLAttributes = _a.HTMLAttributes;
+ return [
+ "li",
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
+ "data-type": this.name,
+ }),
+ 0,
+ ];
+ },
+ // addAttributes() {
+ // return {
+ // hash: getDataAttribute("hash"),
+ // filename: getDataAttribute("filename"),
+ // type: getDataAttribute("type"),
+ // size: getDataAttribute("size"),
+ // };
+ // },
+ // parseHTML() {
+ // return [
+ // {
+ // tag: "span[data-hash]",
+ // },
+ // ];
+ // },
+ // renderHTML({ HTMLAttributes }) {
+ // return ["span", mergeAttributes(HTMLAttributes)];
+ // },
+ addNodeView: function () {
+ return ReactNodeViewRenderer(TaskItemComponent);
+ },
+});
diff --git a/packages/editor/dist/extensions/tasklist/component.d.ts b/packages/editor/dist/extensions/tasklist/component.d.ts
new file mode 100644
index 000000000..058874187
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/component.d.ts
@@ -0,0 +1,3 @@
+///
+import { NodeViewProps } from "@tiptap/react";
+export declare function TaskListComponent(props: NodeViewProps): JSX.Element;
diff --git a/packages/editor/dist/extensions/tasklist/component.js b/packages/editor/dist/extensions/tasklist/component.js
new file mode 100644
index 000000000..49761bc3e
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/component.js
@@ -0,0 +1,74 @@
+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, Text } from "rebass";
+import { NodeViewWrapper, NodeViewContent, } from "@tiptap/react";
+import { findParentNodeClosestToPos, findChildren } from "@tiptap/core";
+import { ThemeProvider } from "emotion-theming";
+import { Icon } from "../../toolbar/components/icon";
+import { Icons } from "../../toolbar/icons";
+import { useEffect, useState } from "react";
+export function TaskListComponent(props) {
+ var editor = props.editor, getPos = props.getPos, node = props.node;
+ var collapsed = node.attrs.collapsed;
+ var _a = useState({ checked: 0, total: 0, percentage: 0 }), stats = _a[0], setStats = _a[1];
+ var theme = editor.storage.theme;
+ var resolvedPos = editor.state.doc.resolve(getPos());
+ var parentTaskItem = findParentNodeClosestToPos(resolvedPos, function (node) { return node.type.name === "taskItem"; });
+ var nested = !!parentTaskItem;
+ useEffect(function () {
+ if (!parentTaskItem)
+ return;
+ var node = parentTaskItem.node, pos = parentTaskItem.pos;
+ var allChecked = areAllChecked(node);
+ var tr = editor.state.tr;
+ tr.setNodeMarkup(pos, node.type, { checked: allChecked });
+ editor.view.dispatch(tr);
+ }, [parentTaskItem]);
+ useEffect(function () {
+ if (nested)
+ return;
+ var children = findChildren(node, function (node) { return node.type.name === "taskItem"; });
+ var checked = children.filter(function (node) { return node.node.attrs.checked; }).length;
+ var total = children.length;
+ var percentage = Math.round((checked / total) * 100);
+ setStats({ checked: checked, total: total, percentage: percentage });
+ }, [nested, node]);
+ console.log(collapsed);
+ return (_jsx(NodeViewWrapper, __assign({ style: { display: collapsed ? "none" : "block" } }, { children: _jsx(ThemeProvider, __assign({ theme: theme }, { children: _jsxs(Flex, __assign({ sx: { flexDirection: "column" } }, { children: [nested ? null : (_jsxs(Flex, __assign({ sx: {
+ position: "relative",
+ bg: "bgSecondary",
+ py: 1,
+ borderRadius: "default",
+ mb: 2,
+ alignItems: "center",
+ justifyContent: "end",
+ overflow: "hidden",
+ px: 2,
+ } }, { children: [_jsx(Box, { sx: {
+ height: "100%",
+ width: "".concat(stats.percentage, "%"),
+ position: "absolute",
+ bg: "border",
+ zIndex: 0,
+ left: 0,
+ transition: "width 250ms ease-out",
+ } }), _jsxs(Flex, __assign({ sx: { zIndex: 1 } }, { children: [_jsx(Icon, { path: Icons.checkbox, size: 15 }), _jsxs(Text, __assign({ variant: "body", sx: { ml: 1 } }, { children: [stats.checked, "/", stats.total] }))] }))] }))), _jsx(NodeViewContent, { as: "ul", style: {
+ paddingInlineStart: 0,
+ marginBlockStart: nested ? 15 : 0,
+ marginBlockEnd: 0,
+ } })] })) })) })));
+}
+function areAllChecked(node) {
+ var children = findChildren(node, function (node) { return node.type.name === "taskItem"; });
+ return children.every(function (node) { return node.node.attrs.checked; });
+}
diff --git a/packages/editor/dist/extensions/tasklist/index.d.ts b/packages/editor/dist/extensions/tasklist/index.d.ts
new file mode 100644
index 000000000..291e035cb
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/index.d.ts
@@ -0,0 +1 @@
+export * from "./task-list";
diff --git a/packages/editor/dist/extensions/tasklist/index.js b/packages/editor/dist/extensions/tasklist/index.js
new file mode 100644
index 000000000..291e035cb
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/index.js
@@ -0,0 +1 @@
+export * from "./task-list";
diff --git a/packages/editor/dist/extensions/tasklist/tasklist.d.ts b/packages/editor/dist/extensions/tasklist/tasklist.d.ts
new file mode 100644
index 000000000..1520f3f44
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/tasklist.d.ts
@@ -0,0 +1 @@
+export declare const TaskListNode: import("@tiptap/core").Node;
diff --git a/packages/editor/dist/extensions/tasklist/tasklist.js b/packages/editor/dist/extensions/tasklist/tasklist.js
new file mode 100644
index 000000000..34d533bc8
--- /dev/null
+++ b/packages/editor/dist/extensions/tasklist/tasklist.js
@@ -0,0 +1,47 @@
+import { TaskList } from "@tiptap/extension-task-list";
+import { ReactNodeViewRenderer } from "@tiptap/react";
+import { TaskListComponent } from "./component";
+export var TaskListNode = TaskList.extend({
+ addAttributes: function () {
+ return {
+ collapsed: {
+ default: false,
+ keepOnSplit: false,
+ parseHTML: function (element) { return element.dataset.collapsed === "true"; },
+ renderHTML: function (attributes) { return ({
+ "data-collapsed": attributes.collapsed === true,
+ }); },
+ },
+ };
+ },
+ // renderHTML({ node, HTMLAttributes }) {
+ // return [
+ // "li",
+ // mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
+ // "data-type": this.name,
+ // }),
+ // 0,
+ // ];
+ // },
+ // addAttributes() {
+ // return {
+ // hash: getDataAttribute("hash"),
+ // filename: getDataAttribute("filename"),
+ // type: getDataAttribute("type"),
+ // size: getDataAttribute("size"),
+ // };
+ // },
+ // parseHTML() {
+ // return [
+ // {
+ // tag: "span[data-hash]",
+ // },
+ // ];
+ // },
+ // renderHTML({ HTMLAttributes }) {
+ // return ["span", mergeAttributes(HTMLAttributes)];
+ // },
+ addNodeView: function () {
+ return ReactNodeViewRenderer(TaskListComponent);
+ },
+});
diff --git a/packages/editor/dist/index.js b/packages/editor/dist/index.js
index cc020f8d3..3631263a3 100644
--- a/packages/editor/dist/index.js
+++ b/packages/editor/dist/index.js
@@ -47,6 +47,8 @@ import TableHeader from "@tiptap/extension-table-header";
import { ImageNode } from "./extensions/image";
import { useTheme } from "@notesnook/theme";
import { AttachmentNode } from "./extensions/attachment";
+import { TaskListNode } from "./extensions/task-list";
+import { TaskItemNode } from "./extensions/task-item";
EditorView.prototype.updateState = function updateState(state) {
if (!this.docView)
return; // This prevents the matchesNode error on hot reloads
@@ -58,7 +60,11 @@ var useTiptap = function (options, deps) {
var defaultOptions = useMemo(function () { return ({
extensions: [
TextStyle,
- StarterKit,
+ StarterKit.configure({
+ dropcursor: {
+ class: "drop-cursor",
+ },
+ }),
CharacterCount,
Underline,
Subscript,
@@ -68,8 +74,9 @@ var useTiptap = function (options, deps) {
FontFamily,
BulletList,
OrderedList,
+ TaskItemNode.configure({ nested: true }),
+ TaskListNode,
Link,
- ImageNode,
Table.configure({
resizable: true,
allowTableNodeSelection: true,
@@ -89,6 +96,7 @@ var useTiptap = function (options, deps) {
Placeholder.configure({
placeholder: "Start writing your note...",
}),
+ ImageNode,
AttachmentNode.configure({
onDownloadAttachment: onDownloadAttachment,
}),
diff --git a/packages/editor/dist/toolbar/components/icon.js b/packages/editor/dist/toolbar/components/icon.js
index c9a8f6af4..f51d1ea20 100644
--- a/packages/editor/dist/toolbar/components/icon.js
+++ b/packages/editor/dist/toolbar/components/icon.js
@@ -30,7 +30,7 @@ function MDIIconWrapper(_a) {
var themedColor = theme.colors
? theme.colors[color]
: color;
- return (_jsx(MDIIcon, { title: title, path: path, size: size + "px", style: {
+ return (_jsx(MDIIcon, { className: "icon", title: title, path: path, size: size + "px", style: {
strokeWidth: stroke || "0px",
stroke: themedColor,
}, color: themedColor, spin: rotate }));
diff --git a/packages/editor/dist/toolbar/icons.d.ts b/packages/editor/dist/toolbar/icons.d.ts
index b44dc584b..9bdad04bc 100644
--- a/packages/editor/dist/toolbar/icons.d.ts
+++ b/packages/editor/dist/toolbar/icons.d.ts
@@ -22,9 +22,9 @@ export declare const Icons: {
textColor: string;
link: string;
image: string;
- chevronDown: string;
colorClear: string;
check: string;
+ checkbox: string;
loading: string;
more: string;
upload: string;
@@ -48,9 +48,13 @@ export declare const Icons: {
deleteTable: string;
mergeCells: string;
splitCells: string;
+ checklist: string;
+ dragHandle: string;
plus: string;
close: string;
delete: string;
download: string;
+ chevronDown: string;
+ chevronUp: string;
};
export declare type IconNames = keyof typeof Icons;
diff --git a/packages/editor/dist/toolbar/icons.js b/packages/editor/dist/toolbar/icons.js
index e91f836f2..cc242fd49 100644
--- a/packages/editor/dist/toolbar/icons.js
+++ b/packages/editor/dist/toolbar/icons.js
@@ -1,4 +1,4 @@
-import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, } from "@mdi/js";
+import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, } from "@mdi/js";
export var Icons = {
bold: mdiFormatBold,
italic: mdiFormatItalic,
@@ -23,9 +23,9 @@ export var Icons = {
textColor: mdiFormatColorText,
link: mdiLinkPlus,
image: mdiImage,
- chevronDown: mdiChevronDown,
colorClear: mdiInvertColorsOff,
check: mdiCheck,
+ checkbox: mdiCheckboxMarkedOutline,
loading: mdiLoading,
more: mdiDotsVertical,
upload: mdiUploadOutline,
@@ -49,8 +49,12 @@ export var Icons = {
deleteTable: mdiTrashCanOutline,
mergeCells: mdiTableMergeCells,
splitCells: mdiTableSplitCell,
+ checklist: mdiFormatListCheckbox,
+ dragHandle: mdiDrag,
plus: mdiPlus,
close: mdiClose,
delete: mdiDeleteOutline,
download: mdiDownloadOutline,
+ chevronDown: mdiChevronDown,
+ chevronUp: mdiChevronUp,
};
diff --git a/packages/editor/dist/toolbar/toolbar.js b/packages/editor/dist/toolbar/toolbar.js
index ba9e1508a..95c1fb83b 100644
--- a/packages/editor/dist/toolbar/toolbar.js
+++ b/packages/editor/dist/toolbar/toolbar.js
@@ -25,7 +25,7 @@ export function Toolbar(props) {
["subscript", "superscript", "horizontalRule"],
["codeblock", "blockquote"],
["formatClear", "ltr", "rtl"],
- ["numberedList", "bulletList"],
+ ["numberedList", "bulletList", "checklist"],
["link", "image", "attachment", "table"],
["textColor", "highlight"],
];
diff --git a/packages/editor/dist/toolbar/tools/index.d.ts b/packages/editor/dist/toolbar/tools/index.d.ts
index 31f49b7e2..9f6b7983b 100644
--- a/packages/editor/dist/toolbar/tools/index.d.ts
+++ b/packages/editor/dist/toolbar/tools/index.d.ts
@@ -4,7 +4,7 @@ import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
import { Blockquote, CodeBlock, HorizontalRule, Image, Table } from "./block";
import { Headings } from "./headings";
-import { NumberedList, BulletList } from "./lists";
+import { NumberedList, BulletList, Checklist } from "./lists";
import { LeftToRight, RightToLeft } from "./text-direction";
import { Highlight, TextColor } from "./colors";
declare const tools: {
@@ -30,6 +30,7 @@ declare const tools: {
rtl: RightToLeft;
numberedList: NumberedList;
bulletList: BulletList;
+ checklist: Checklist;
textColor: TextColor;
highlight: Highlight;
link: Link;
diff --git a/packages/editor/dist/toolbar/tools/index.js b/packages/editor/dist/toolbar/tools/index.js
index 52ce892bd..399523476 100644
--- a/packages/editor/dist/toolbar/tools/index.js
+++ b/packages/editor/dist/toolbar/tools/index.js
@@ -3,7 +3,7 @@ import { FontSize, FontFamily } from "./font";
import { AlignCenter, AlignLeft, AlignRight, AlignJustify } from "./alignment";
import { Blockquote, CodeBlock, HorizontalRule, Image, Table } from "./block";
import { Headings } from "./headings";
-import { NumberedList, BulletList } from "./lists";
+import { NumberedList, BulletList, Checklist } from "./lists";
import { LeftToRight, RightToLeft } from "./text-direction";
import { Highlight, TextColor } from "./colors";
var tools = {
@@ -29,6 +29,7 @@ var tools = {
rtl: new RightToLeft(),
numberedList: new NumberedList(),
bulletList: new BulletList(),
+ checklist: new Checklist(),
textColor: new TextColor(),
highlight: new Highlight(),
link: new Link(),
diff --git a/packages/editor/dist/toolbar/tools/inline.d.ts b/packages/editor/dist/toolbar/tools/inline.d.ts
index 118f86bc1..90c680b7b 100644
--- a/packages/editor/dist/toolbar/tools/inline.d.ts
+++ b/packages/editor/dist/toolbar/tools/inline.d.ts
@@ -1,3 +1,4 @@
+///
import { ITool, ToolProps } from "../types";
import { ToolId } from ".";
import { IconNames } from "../icons";
diff --git a/packages/editor/dist/toolbar/tools/lists.d.ts b/packages/editor/dist/toolbar/tools/lists.d.ts
index 05e58e754..fe8d31586 100644
--- a/packages/editor/dist/toolbar/tools/lists.d.ts
+++ b/packages/editor/dist/toolbar/tools/lists.d.ts
@@ -29,4 +29,9 @@ declare type BulletListStyleTypes = "circle" | "square" | "disc";
export declare class BulletList extends ListTool {
constructor();
}
+export declare class Checklist implements ITool {
+ id: ToolId;
+ title: string;
+ render: (props: ToolProps) => JSX.Element;
+}
export {};
diff --git a/packages/editor/dist/toolbar/tools/lists.js b/packages/editor/dist/toolbar/tools/lists.js
index 4fbabeb1d..303774692 100644
--- a/packages/editor/dist/toolbar/tools/lists.js
+++ b/packages/editor/dist/toolbar/tools/lists.js
@@ -127,6 +127,19 @@ var BulletList = /** @class */ (function (_super) {
return BulletList;
}(ListTool));
export { BulletList };
+var Checklist = /** @class */ (function () {
+ function Checklist() {
+ var _this = this;
+ this.id = "checklist";
+ this.title = "Checklist";
+ this.render = function (props) {
+ var editor = props.editor;
+ return (_jsx(ToolButton, { title: _this.title, id: _this.id, icon: "checklist", onClick: function () { return editor.chain().focus().toggleTaskList().run(); }, toggled: false }));
+ };
+ }
+ return Checklist;
+}());
+export { Checklist };
function ListThumbnail(props) {
var listStyleType = props.listStyleType;
return (_jsx(Flex, __assign({ as: "ul", sx: {
diff --git a/packages/editor/package-lock.json b/packages/editor/package-lock.json
index f6019d7fe..3fc2cbb4c 100644
--- a/packages/editor/package-lock.json
+++ b/packages/editor/package-lock.json
@@ -26,6 +26,8 @@
"@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-task-item": "^2.0.0-beta.31",
+ "@tiptap/extension-task-list": "^2.0.0-beta.26",
"@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",
@@ -4141,6 +4143,30 @@
"@tiptap/core": "^2.0.0-beta.1"
}
},
+ "node_modules/@tiptap/extension-task-item": {
+ "version": "2.0.0-beta.31",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.31.tgz",
+ "integrity": "sha512-9MCInLAf/l/wDD1N3GgOImemloFARi1l9AJ5acfo+sDjN52yfvaLb//lvLJ6IGz4xGepeAyCME8Qns8UGqG4RQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.0.0-beta.1"
+ }
+ },
+ "node_modules/@tiptap/extension-task-list": {
+ "version": "2.0.0-beta.26",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.26.tgz",
+ "integrity": "sha512-7zPpz9eOUCnFyWNDFYPCUJ39gjID+mCI5BuXyXrjJjDfm8wxg/xTgg9+KC6xakczos7DypnhzlRKSs4EFczeUg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.0.0-beta.1"
+ }
+ },
"node_modules/@tiptap/extension-text": {
"version": "2.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.15.tgz",
@@ -27708,6 +27734,18 @@
"integrity": "sha512-ldEVDpIUX7ZqbViTy4c/RfyNGRv++O/r3A/Ivuon1PykaDDTbPlp5JM89FunAD39cLAbo2HKtweqdmzCMlZsqA==",
"requires": {}
},
+ "@tiptap/extension-task-item": {
+ "version": "2.0.0-beta.31",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.31.tgz",
+ "integrity": "sha512-9MCInLAf/l/wDD1N3GgOImemloFARi1l9AJ5acfo+sDjN52yfvaLb//lvLJ6IGz4xGepeAyCME8Qns8UGqG4RQ==",
+ "requires": {}
+ },
+ "@tiptap/extension-task-list": {
+ "version": "2.0.0-beta.26",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.26.tgz",
+ "integrity": "sha512-7zPpz9eOUCnFyWNDFYPCUJ39gjID+mCI5BuXyXrjJjDfm8wxg/xTgg9+KC6xakczos7DypnhzlRKSs4EFczeUg==",
+ "requires": {}
+ },
"@tiptap/extension-text": {
"version": "2.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.0-beta.15.tgz",
diff --git a/packages/editor/package.json b/packages/editor/package.json
index a43993456..19d7eeb44 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -22,6 +22,8 @@
"@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-task-item": "^2.0.0-beta.31",
+ "@tiptap/extension-task-list": "^2.0.0-beta.26",
"@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",
diff --git a/packages/editor/src/extensions/task-item/component.tsx b/packages/editor/src/extensions/task-item/component.tsx
new file mode 100644
index 000000000..a07e6e9cf
--- /dev/null
+++ b/packages/editor/src/extensions/task-item/component.tsx
@@ -0,0 +1,191 @@
+import { Box, Flex, Image, ImageProps, Text } from "rebass";
+import {
+ NodeViewWrapper,
+ NodeViewProps,
+ NodeViewContent,
+ FloatingMenu,
+} from "@tiptap/react";
+import { ThemeProvider } from "emotion-theming";
+import { Theme } from "@notesnook/theme";
+import { Icon } from "../../toolbar/components/icon";
+import { Icons } from "../../toolbar/icons";
+import { Node } from "prosemirror-model";
+import { Transaction, Selection } from "prosemirror-state";
+import {
+ findParentNodeClosestToPos,
+ findChildren,
+ NodeWithPos,
+} from "@tiptap/core";
+import { useCallback, useState } from "react";
+
+export function TaskItemComponent(props: ImageProps & NodeViewProps) {
+ const { checked } = props.node.attrs;
+
+ const { editor, updateAttributes, node, getPos } = props;
+ // const [isOpen, setIsOpen] = useState(true);
+ // const elementRef = useRef();
+ // const isActive = editor.isActive("attachment", { hash });
+ // const [isToolbarVisible, setIsToolbarVisible] = useState();
+ const theme = editor.storage.theme as Theme;
+
+ // useEffect(() => {
+ // setIsToolbarVisible(isActive);
+ // }, [isActive]);
+
+ const toggle = useCallback(() => {
+ if (!editor.isEditable) return false;
+ updateAttributes({ checked: !checked });
+
+ const tr = editor.state.tr;
+ const parentPos = getPos();
+
+ toggleChildren(node, tr, !checked, parentPos);
+
+ editor.view.dispatch(tr);
+ return true;
+ }, [editor, getPos, node]);
+ const nestedTaskList = getChildren(node, getPos()).find(
+ ({ node }) => node.type.name === "taskList"
+ );
+ const isNested = !!nestedTaskList;
+
+ return (
+
+
+ .dragHandle, :hover > .toggleSublist": {
+ opacity: 1,
+ },
+ }}
+ >
+
+
+ {
+ if (e.buttons > 0) {
+ toggle();
+ }
+ }}
+ onMouseDown={(e) => {
+ if (toggle()) e.preventDefault();
+ }}
+ color={checked ? "disabled" : "icon"}
+ size={13}
+ />
+
+
+
+ {isNested && (
+ {
+ editor
+ .chain()
+ .setNodeSelection(getPos())
+ .command(({ tr }) => {
+ const { pos, node } = nestedTaskList;
+ tr.setNodeMarkup(pos, undefined, {
+ collapsed: !node.attrs.collapsed,
+ });
+ return true;
+ })
+ .run();
+ }}
+ />
+ )}
+
+
+ {/*
+
+
+
+
+
+ */}
+
+ );
+}
+
+function toggleChildren(
+ node: Node,
+ tr: Transaction,
+ toggleState: boolean,
+ parentPos: number
+): Transaction {
+ const children = findChildren(node, (node) => node.type.name === "taskItem");
+ for (const { pos } of children) {
+ // need to add 1 to get inside the node
+ const actualPos = pos + parentPos + 1;
+ tr.setNodeMarkup(actualPos, undefined, {
+ checked: toggleState,
+ });
+ }
+ return tr;
+}
+
+function getChildren(node: Node, parentPos: number) {
+ const children: NodeWithPos[] = [];
+ node.forEach((node, offset) => {
+ children.push({ node, pos: parentPos + offset + 1 });
+ });
+ return children;
+}
diff --git a/packages/editor/src/extensions/task-item/index.ts b/packages/editor/src/extensions/task-item/index.ts
new file mode 100644
index 000000000..3518ff3b2
--- /dev/null
+++ b/packages/editor/src/extensions/task-item/index.ts
@@ -0,0 +1 @@
+export * from "./task-item";
diff --git a/packages/editor/src/extensions/task-item/task-item.ts b/packages/editor/src/extensions/task-item/task-item.ts
new file mode 100644
index 000000000..77a69aafe
--- /dev/null
+++ b/packages/editor/src/extensions/task-item/task-item.ts
@@ -0,0 +1,62 @@
+import { nodeInputRule, mergeAttributes } from "@tiptap/core";
+import { TaskItem } from "@tiptap/extension-task-item";
+import { ReactNodeViewRenderer } from "@tiptap/react";
+import { Attribute } from "@tiptap/core";
+import { TaskItemComponent } from "./component";
+
+export interface AttachmentOptions {
+ // HTMLAttributes: Record;
+ // onDownloadAttachment: (attachment: Attachment) => boolean;
+}
+
+export const TaskItemNode = TaskItem.extend({
+ draggable: true,
+
+ addAttributes() {
+ return {
+ checked: {
+ default: false,
+ keepOnSplit: false,
+ parseHTML: (element) => element.classList.contains("checked"),
+ renderHTML: (attributes) => ({
+ class: attributes.checked ? "checked" : "",
+ }),
+ },
+ };
+ },
+
+ renderHTML({ node, HTMLAttributes }) {
+ return [
+ "li",
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
+ "data-type": this.name,
+ }),
+ 0,
+ ];
+ },
+
+ // addAttributes() {
+ // return {
+ // hash: getDataAttribute("hash"),
+ // filename: getDataAttribute("filename"),
+ // type: getDataAttribute("type"),
+ // size: getDataAttribute("size"),
+ // };
+ // },
+
+ // parseHTML() {
+ // return [
+ // {
+ // tag: "span[data-hash]",
+ // },
+ // ];
+ // },
+
+ // renderHTML({ HTMLAttributes }) {
+ // return ["span", mergeAttributes(HTMLAttributes)];
+ // },
+
+ addNodeView() {
+ return ReactNodeViewRenderer(TaskItemComponent);
+ },
+});
diff --git a/packages/editor/src/extensions/task-list/component.tsx b/packages/editor/src/extensions/task-list/component.tsx
new file mode 100644
index 000000000..4da1b1ccd
--- /dev/null
+++ b/packages/editor/src/extensions/task-list/component.tsx
@@ -0,0 +1,109 @@
+import { Box, Flex, Image, ImageProps, Text } from "rebass";
+import {
+ NodeViewWrapper,
+ NodeViewProps,
+ NodeViewContent,
+ FloatingMenu,
+} from "@tiptap/react";
+import { Node } from "prosemirror-model";
+import { Transaction, Selection } from "prosemirror-state";
+import { findParentNodeClosestToPos, findChildren } from "@tiptap/core";
+import { ThemeProvider } from "emotion-theming";
+import { Theme } from "@notesnook/theme";
+import { Icon } from "../../toolbar/components/icon";
+import { Icons } from "../../toolbar/icons";
+import { useEffect, useState } from "react";
+
+export function TaskListComponent(props: NodeViewProps) {
+ const { editor, getPos, node } = props;
+ const { collapsed } = node.attrs;
+ const [stats, setStats] = useState({ checked: 0, total: 0, percentage: 0 });
+
+ const theme = editor.storage.theme as Theme;
+ const resolvedPos = editor.state.doc.resolve(getPos());
+ const parentTaskItem = findParentNodeClosestToPos(
+ resolvedPos,
+ (node) => node.type.name === "taskItem"
+ );
+ const nested = !!parentTaskItem;
+
+ useEffect(() => {
+ if (!parentTaskItem) return;
+ const { node, pos } = parentTaskItem;
+ const allChecked = areAllChecked(node);
+ const tr = editor.state.tr;
+ tr.setNodeMarkup(pos, node.type, { checked: allChecked });
+ editor.view.dispatch(tr);
+ }, [parentTaskItem]);
+
+ useEffect(() => {
+ if (nested) return;
+ const children = findChildren(
+ node,
+ (node) => node.type.name === "taskItem"
+ );
+ const checked = children.filter((node) => node.node.attrs.checked).length;
+ const total = children.length;
+ const percentage = Math.round((checked / total) * 100);
+ setStats({ checked, total, percentage });
+ }, [nested, node]);
+ console.log(collapsed);
+ return (
+
+
+
+ {nested ? null : (
+
+
+
+
+
+ {stats.checked}/{stats.total}
+
+
+ {/*
+ {stats.percentage}% done
+ */}
+
+ )}
+
+
+
+
+ );
+}
+
+function areAllChecked(node: Node) {
+ const children = findChildren(node, (node) => node.type.name === "taskItem");
+ return children.every((node) => node.node.attrs.checked);
+}
diff --git a/packages/editor/src/extensions/task-list/index.ts b/packages/editor/src/extensions/task-list/index.ts
new file mode 100644
index 000000000..291e035cb
--- /dev/null
+++ b/packages/editor/src/extensions/task-list/index.ts
@@ -0,0 +1 @@
+export * from "./task-list";
diff --git a/packages/editor/src/extensions/task-list/task-list.ts b/packages/editor/src/extensions/task-list/task-list.ts
new file mode 100644
index 000000000..796b7ef7d
--- /dev/null
+++ b/packages/editor/src/extensions/task-list/task-list.ts
@@ -0,0 +1,55 @@
+import { nodeInputRule, mergeAttributes } from "@tiptap/core";
+import { TaskList } from "@tiptap/extension-task-list";
+import { ReactNodeViewRenderer } from "@tiptap/react";
+import { Attribute } from "@tiptap/core";
+import { TaskListComponent } from "./component";
+
+export const TaskListNode = TaskList.extend({
+ addAttributes() {
+ return {
+ collapsed: {
+ default: false,
+ keepOnSplit: false,
+ parseHTML: (element) => element.dataset.collapsed === "true",
+ renderHTML: (attributes) => ({
+ "data-collapsed": attributes.collapsed === true,
+ }),
+ },
+ };
+ },
+
+ // renderHTML({ node, HTMLAttributes }) {
+ // return [
+ // "li",
+ // mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
+ // "data-type": this.name,
+ // }),
+ // 0,
+ // ];
+ // },
+
+ // addAttributes() {
+ // return {
+ // hash: getDataAttribute("hash"),
+ // filename: getDataAttribute("filename"),
+ // type: getDataAttribute("type"),
+ // size: getDataAttribute("size"),
+ // };
+ // },
+
+ // parseHTML() {
+ // return [
+ // {
+ // tag: "span[data-hash]",
+ // },
+ // ];
+ // },
+
+ // renderHTML({ HTMLAttributes }) {
+ // return ["span", mergeAttributes(HTMLAttributes)];
+ // },
+
+ addNodeView() {
+ return ReactNodeViewRenderer(TaskListComponent);
+ },
+});
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index 842a62737..7ee0903f4 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -26,6 +26,8 @@ import { ImageNode } from "./extensions/image";
import { ThemeConfig } from "@notesnook/theme/dist/theme/types";
import { useTheme } from "@notesnook/theme";
import { AttachmentNode, AttachmentOptions } from "./extensions/attachment";
+import { TaskListNode } from "./extensions/task-list";
+import { TaskItemNode } from "./extensions/task-item";
EditorView.prototype.updateState = function updateState(state) {
if (!(this as any).docView) return; // This prevents the matchesNode error on hot reloads
@@ -49,7 +51,11 @@ const useTiptap = (
() => ({
extensions: [
TextStyle,
- StarterKit,
+ StarterKit.configure({
+ dropcursor: {
+ class: "drop-cursor",
+ },
+ }),
CharacterCount,
Underline,
Subscript,
@@ -59,8 +65,9 @@ const useTiptap = (
FontFamily,
BulletList,
OrderedList,
+ TaskItemNode.configure({ nested: true }),
+ TaskListNode,
Link,
- ImageNode,
Table.configure({
resizable: true,
allowTableNodeSelection: true,
@@ -80,6 +87,8 @@ const useTiptap = (
Placeholder.configure({
placeholder: "Start writing your note...",
}),
+
+ ImageNode,
AttachmentNode.configure({
onDownloadAttachment,
}),
diff --git a/packages/editor/src/toolbar/components/icon.tsx b/packages/editor/src/toolbar/components/icon.tsx
index b1b64f23a..6df86b219 100644
--- a/packages/editor/src/toolbar/components/icon.tsx
+++ b/packages/editor/src/toolbar/components/icon.tsx
@@ -28,6 +28,7 @@ function MDIIconWrapper({
return (
{
}
}
+export class Checklist implements ITool {
+ id: ToolId = "checklist";
+ title = "Checklist";
+
+ render = (props: ToolProps) => {
+ const { editor } = props;
+
+ return (
+ editor.chain().focus().toggleTaskList().run()}
+ toggled={false}
+ />
+ );
+ };
+}
+
type ListThumbnailProps = { listStyleType: string };
function ListThumbnail(props: ListThumbnailProps) {
const { listStyleType } = props;