mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-28 16:06:47 +01:00
feat: add outline list
This commit is contained in:
4
packages/editor/dist/extensions/outlinelist/component.d.ts
vendored
Normal file
4
packages/editor/dist/extensions/outlinelist/component.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/// <reference types="react" />
|
||||
import { ReactNodeViewProps } from "../react";
|
||||
import { OutlineListAttributes } from "./outline-list";
|
||||
export declare function OutlineListComponent(props: ReactNodeViewProps<OutlineListAttributes>): JSX.Element;
|
||||
22
packages/editor/dist/extensions/outlinelist/component.js
vendored
Normal file
22
packages/editor/dist/extensions/outlinelist/component.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
import { Text } from "rebass";
|
||||
import { useMemo } from "react";
|
||||
import { OutlineListItem } from "../outline-list-item";
|
||||
export function OutlineListComponent(props) {
|
||||
var editor = props.editor, getPos = props.getPos, node = props.node, updateAttributes = props.updateAttributes, forwardRef = props.forwardRef;
|
||||
var collapsed = node.attrs.collapsed;
|
||||
var isNested = useMemo(function () {
|
||||
var _a;
|
||||
var pos = editor.state.doc.resolve(getPos());
|
||||
return ((_a = pos.parent) === null || _a === void 0 ? void 0 : _a.type.name) === OutlineListItem.name;
|
||||
}, []);
|
||||
return (_jsx(_Fragment, { children: _jsx(Text, { className: "outline-list", as: "div", ref: forwardRef, sx: {
|
||||
ul: {
|
||||
display: collapsed ? "none" : "block",
|
||||
paddingInlineStart: 0,
|
||||
paddingLeft: isNested ? 1 : 0,
|
||||
marginBlockStart: isNested ? 5 : 0,
|
||||
marginBlockEnd: 0,
|
||||
},
|
||||
} }) }));
|
||||
}
|
||||
1
packages/editor/dist/extensions/outlinelist/index.d.ts
vendored
Normal file
1
packages/editor/dist/extensions/outlinelist/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./outline-list";
|
||||
1
packages/editor/dist/extensions/outlinelist/index.js
vendored
Normal file
1
packages/editor/dist/extensions/outlinelist/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./outline-list";
|
||||
19
packages/editor/dist/extensions/outlinelist/outlinelist.d.ts
vendored
Normal file
19
packages/editor/dist/extensions/outlinelist/outlinelist.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Node } from "@tiptap/core";
|
||||
export declare type OutlineListAttributes = {
|
||||
collapsed: boolean;
|
||||
};
|
||||
export interface OutlineListOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
outlineList: {
|
||||
/**
|
||||
* Toggle a bullet list
|
||||
*/
|
||||
toggleOutlineList: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
export declare const inputRegex: RegExp;
|
||||
export declare const OutlineList: Node<OutlineListOptions, any>;
|
||||
81
packages/editor/dist/extensions/outlinelist/outlinelist.js
vendored
Normal file
81
packages/editor/dist/extensions/outlinelist/outlinelist.js
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Node, mergeAttributes, wrappingInputRule } from "@tiptap/core";
|
||||
import { createNodeView } from "../react";
|
||||
import { OutlineListComponent } from "./component";
|
||||
export var inputRegex = /^\s*(-o)\s$/;
|
||||
var outlineListItemName = "outlineListItem";
|
||||
export var OutlineList = Node.create({
|
||||
name: "outlineList",
|
||||
addOptions: function () {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
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,
|
||||
}); },
|
||||
},
|
||||
};
|
||||
},
|
||||
group: "block list",
|
||||
content: "".concat(outlineListItemName, "+"),
|
||||
parseHTML: function () {
|
||||
return [
|
||||
{
|
||||
tag: "ul[data-type=\"".concat(this.name, "\"]"),
|
||||
priority: 52,
|
||||
},
|
||||
];
|
||||
},
|
||||
renderHTML: function (_a) {
|
||||
var HTMLAttributes = _a.HTMLAttributes;
|
||||
return [
|
||||
"ul",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
addCommands: function () {
|
||||
var _this = this;
|
||||
return {
|
||||
toggleOutlineList: function () {
|
||||
return function (_a) {
|
||||
var commands = _a.commands;
|
||||
return commands.toggleList(_this.name, outlineListItemName);
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
addKeyboardShortcuts: function () {
|
||||
var _this = this;
|
||||
return {
|
||||
"Mod-Shift-O": function () { return _this.editor.commands.toggleOutlineList(); },
|
||||
};
|
||||
},
|
||||
addInputRules: function () {
|
||||
return [
|
||||
wrappingInputRule({
|
||||
find: inputRegex,
|
||||
type: this.type,
|
||||
}),
|
||||
];
|
||||
},
|
||||
addNodeView: function () {
|
||||
var _this = this;
|
||||
return createNodeView(OutlineListComponent, {
|
||||
contentDOMFactory: function () {
|
||||
var content = document.createElement("ul");
|
||||
content.classList.add("".concat(_this.name.toLowerCase(), "-content-wrapper"));
|
||||
content.style.whiteSpace = "inherit";
|
||||
return { dom: content };
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
5
packages/editor/dist/extensions/outlinelist/outlinelistitem.d.ts
vendored
Normal file
5
packages/editor/dist/extensions/outlinelist/outlinelistitem.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Node } from "@tiptap/core";
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
export declare const OutlineListItem: Node<ListItemOptions, any>;
|
||||
39
packages/editor/dist/extensions/outlinelist/outlinelistitem.js
vendored
Normal file
39
packages/editor/dist/extensions/outlinelist/outlinelistitem.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import { onBackspacePressed } from "../list-item/commands";
|
||||
export var OutlineListItem = Node.create({
|
||||
name: "outlineListItem",
|
||||
addOptions: function () {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
content: "paragraph block*",
|
||||
defining: true,
|
||||
parseHTML: function () {
|
||||
return [
|
||||
{
|
||||
tag: "ul[data-list-type=\"outline\"] > li",
|
||||
},
|
||||
];
|
||||
},
|
||||
renderHTML: function (_a) {
|
||||
var HTMLAttributes = _a.HTMLAttributes;
|
||||
return [
|
||||
"li",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
0,
|
||||
];
|
||||
},
|
||||
addKeyboardShortcuts: function () {
|
||||
var _this = this;
|
||||
return {
|
||||
Enter: function () { return _this.editor.commands.splitListItem(_this.name); },
|
||||
Tab: function () { return _this.editor.commands.sinkListItem(_this.name); },
|
||||
"Shift-Tab": function () { return _this.editor.commands.liftListItem(_this.name); },
|
||||
Backspace: function (_a) {
|
||||
var editor = _a.editor;
|
||||
return onBackspacePressed(editor, _this.name, _this.type);
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
3
packages/editor/dist/extensions/outlinelistitem/component.d.ts
vendored
Normal file
3
packages/editor/dist/extensions/outlinelistitem/component.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/// <reference types="react" />
|
||||
import { ReactNodeViewProps } from "../react";
|
||||
export declare function OutlineListItemComponent(props: ReactNodeViewProps): JSX.Element;
|
||||
78
packages/editor/dist/extensions/outlinelistitem/component.js
vendored
Normal file
78
packages/editor/dist/extensions/outlinelistitem/component.js
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
var __read = (this && this.__read) || function (o, n) {
|
||||
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
||||
if (!m) return o;
|
||||
var i = m.call(o), r, ar = [], e;
|
||||
try {
|
||||
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
||||
}
|
||||
catch (error) { e = { error: error }; }
|
||||
finally {
|
||||
try {
|
||||
if (r && !r.done && (m = i["return"])) m.call(i);
|
||||
}
|
||||
finally { if (e) throw e.error; }
|
||||
}
|
||||
return ar;
|
||||
};
|
||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
import { Box, Flex, Text } from "rebass";
|
||||
import { Icon } from "../../toolbar/components/icon";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { findChildren, } from "@tiptap/core";
|
||||
import { OutlineList } from "../outline-list/outline-list";
|
||||
export function OutlineListItemComponent(props) {
|
||||
var _a, _b;
|
||||
var editor = props.editor, updateAttributes = props.updateAttributes, node = props.node, getPos = props.getPos, forwardRef = props.forwardRef;
|
||||
var isNested = ((_a = node.lastChild) === null || _a === void 0 ? void 0 : _a.type.name) === OutlineList.name;
|
||||
var isCollapsed = isNested && ((_b = node.lastChild) === null || _b === void 0 ? void 0 : _b.attrs.collapsed);
|
||||
return (_jsxs(Flex, { children: [_jsxs(Flex, __assign({ className: "outline", sx: {
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
mt: "3px",
|
||||
} }, { children: [isNested ? (_jsx(Icon, { path: isCollapsed ? Icons.chevronRight : Icons.chevronDown, title: isCollapsed
|
||||
? "Click to uncollapse list"
|
||||
: "Click to collapse list", sx: {
|
||||
cursor: "pointer",
|
||||
transition: "all .2s ease-in-out",
|
||||
":hover": {
|
||||
transform: "scale(1.3)",
|
||||
},
|
||||
".icon:hover path": {
|
||||
fill: "var(--checked) !important",
|
||||
},
|
||||
}, size: 18, onMouseDown: function (e) { return e.preventDefault(); }, onClick: function () {
|
||||
var _a = __read(findChildren(node, function (node) { return node.type.name === OutlineList.name; }), 1), subList = _a[0];
|
||||
if (!subList)
|
||||
return;
|
||||
var pos = subList.pos;
|
||||
editor.commands.toggleOutlineCollapse(pos + getPos() + 1, !isCollapsed);
|
||||
} })) : (_jsx(Icon, { path: Icons.circle, size: 18, sx: { transform: "scale(0.4)" } })), isNested && !isCollapsed && (_jsx(Box, { sx: {
|
||||
flex: 1,
|
||||
width: 1,
|
||||
mt: 2,
|
||||
backgroundColor: "border",
|
||||
borderRadius: 50,
|
||||
flexShrink: 0,
|
||||
cursor: "pointer",
|
||||
transition: "all .2s ease-in-out",
|
||||
":hover": {
|
||||
backgroundColor: "fontTertiary",
|
||||
width: 4,
|
||||
},
|
||||
}, contentEditable: false }))] })), _jsx(Text, { as: "li", ref: forwardRef, sx: {
|
||||
pl: 2,
|
||||
listStyleType: "none",
|
||||
flex: 1,
|
||||
} })] }));
|
||||
}
|
||||
1
packages/editor/dist/extensions/outlinelistitem/index.d.ts
vendored
Normal file
1
packages/editor/dist/extensions/outlinelistitem/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./outline-list-item";
|
||||
1
packages/editor/dist/extensions/outlinelistitem/index.js
vendored
Normal file
1
packages/editor/dist/extensions/outlinelistitem/index.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./outline-list-item";
|
||||
12
packages/editor/dist/extensions/outlinelistitem/outlinelistitem.d.ts
vendored
Normal file
12
packages/editor/dist/extensions/outlinelistitem/outlinelistitem.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Node } from "@tiptap/core";
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
outlineListItem: {
|
||||
toggleOutlineCollapse: (subListPos: number, state: boolean) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
export declare const OutlineListItem: Node<ListItemOptions, any>;
|
||||
126
packages/editor/dist/extensions/outlinelistitem/outlinelistitem.js
vendored
Normal file
126
packages/editor/dist/extensions/outlinelistitem/outlinelistitem.js
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
var __read = (this && this.__read) || function (o, n) {
|
||||
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
||||
if (!m) return o;
|
||||
var i = m.call(o), r, ar = [], e;
|
||||
try {
|
||||
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
||||
}
|
||||
catch (error) { e = { error: error }; }
|
||||
finally {
|
||||
try {
|
||||
if (r && !r.done && (m = i["return"])) m.call(i);
|
||||
}
|
||||
finally { if (e) throw e.error; }
|
||||
}
|
||||
return ar;
|
||||
};
|
||||
import { Node, mergeAttributes, findChildren } from "@tiptap/core";
|
||||
import { findParentNodeOfTypeClosestToPos } from "prosemirror-utils";
|
||||
import { onBackspacePressed } from "../list-item/commands";
|
||||
import { OutlineList } from "../outline-list/outline-list";
|
||||
import { createNodeView } from "../react";
|
||||
import { OutlineListItemComponent } from "./component";
|
||||
export var OutlineListItem = Node.create({
|
||||
name: "outlineListItem",
|
||||
addOptions: function () {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
content: "heading* block*",
|
||||
defining: true,
|
||||
parseHTML: function () {
|
||||
return [
|
||||
{
|
||||
tag: "li[data-type=\"".concat(this.name, "\"]"),
|
||||
},
|
||||
];
|
||||
},
|
||||
renderHTML: function (_a) {
|
||||
var HTMLAttributes = _a.HTMLAttributes;
|
||||
return [
|
||||
"li",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
addKeyboardShortcuts: function () {
|
||||
var _this = this;
|
||||
return {
|
||||
"Mod-Space": function (_a) {
|
||||
var editor = _a.editor;
|
||||
var subList = findSublist(editor, _this.type);
|
||||
if (!subList)
|
||||
return false;
|
||||
var isCollapsed = subList.isCollapsed, subListPos = subList.subListPos;
|
||||
return _this.editor.commands.toggleOutlineCollapse(subListPos, !isCollapsed);
|
||||
},
|
||||
Enter: function (_a) {
|
||||
var editor = _a.editor;
|
||||
var subList = findSublist(editor, _this.type);
|
||||
if (!subList)
|
||||
return false;
|
||||
var isCollapsed = subList.isCollapsed, subListPos = subList.subListPos;
|
||||
if (isCollapsed) {
|
||||
return _this.editor.commands.toggleOutlineCollapse(subListPos, false);
|
||||
}
|
||||
return _this.editor.commands.splitListItem(_this.name);
|
||||
},
|
||||
Tab: function () { return _this.editor.commands.sinkListItem(_this.name); },
|
||||
"Shift-Tab": function () { return _this.editor.commands.liftListItem(_this.name); },
|
||||
Backspace: function (_a) {
|
||||
var editor = _a.editor;
|
||||
return onBackspacePressed(editor, _this.name, _this.type);
|
||||
},
|
||||
};
|
||||
},
|
||||
addCommands: function () {
|
||||
return {
|
||||
toggleOutlineCollapse: function (pos, state) {
|
||||
return function (_a) {
|
||||
var tr = _a.tr;
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
collapsed: state,
|
||||
});
|
||||
return true;
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
addNodeView: function () {
|
||||
return createNodeView(OutlineListItemComponent, {
|
||||
contentDOMFactory: true,
|
||||
// wrapperFactory: () => document.createElement("li"),
|
||||
});
|
||||
},
|
||||
});
|
||||
function findSublist(editor, type) {
|
||||
var _a, _b;
|
||||
var selection = editor.state.selection;
|
||||
var $from = selection.$from;
|
||||
var listItem = findParentNodeOfTypeClosestToPos($from, type);
|
||||
if (!listItem)
|
||||
return false;
|
||||
var _c = __read(findChildren(listItem.node, function (node) { return node.type.name === OutlineList.name; }), 1), subList = _c[0];
|
||||
if (!subList)
|
||||
return false;
|
||||
var isNested = ((_a = subList === null || subList === void 0 ? void 0 : subList.node) === null || _a === void 0 ? void 0 : _a.type.name) === OutlineList.name;
|
||||
var isCollapsed = (_b = subList === null || subList === void 0 ? void 0 : subList.node) === null || _b === void 0 ? void 0 : _b.attrs.collapsed;
|
||||
var subListPos = listItem.pos + subList.pos + 1;
|
||||
return { isCollapsed: isCollapsed, isNested: isNested, subListPos: subListPos };
|
||||
// return (
|
||||
// this.editor
|
||||
// .chain()
|
||||
// .command(({ tr }) => {
|
||||
// tr.setNodeMarkup(listItem.pos + subList.pos + 1, undefined, {
|
||||
// collapsed: !isCollapsed,
|
||||
// });
|
||||
// return true;
|
||||
// })
|
||||
// //.setTextSelection(listItem.pos + subList.pos + 1)
|
||||
// //.splitListItem(this.name)
|
||||
// .run()
|
||||
// );
|
||||
}
|
||||
@@ -23,7 +23,7 @@ export declare class PortalProvider extends React.Component<BasePortalProviderPr
|
||||
static displayName: string;
|
||||
portalProviderAPI: PortalProviderAPI;
|
||||
constructor(props: BasePortalProviderProps);
|
||||
render(): JSX.Element | React.ReactChild | null;
|
||||
render(): React.ReactChild | JSX.Element | null;
|
||||
componentDidUpdate(): void;
|
||||
}
|
||||
export declare class PortalRenderer extends React.Component<{
|
||||
|
||||
4
packages/editor/dist/index.js
vendored
4
packages/editor/dist/index.js
vendored
@@ -54,6 +54,8 @@ import { CodeBlock } from "./extensions/code-block";
|
||||
import { ListItem } from "./extensions/list-item";
|
||||
import { Link } from "./extensions/link";
|
||||
import { EventDispatcher } from "./extensions/react";
|
||||
import { OutlineList } from "./extensions/outline-list";
|
||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||
EditorView.prototype.updateState = function updateState(state) {
|
||||
if (!this.docView)
|
||||
return; // This prevents the matchesNode error on hot reloads
|
||||
@@ -115,6 +117,8 @@ var useTiptap = function (options, deps) {
|
||||
onDownloadAttachment: onDownloadAttachment,
|
||||
onOpenAttachmentPicker: onOpenAttachmentPicker,
|
||||
}),
|
||||
OutlineListItem,
|
||||
OutlineList,
|
||||
ListItem,
|
||||
],
|
||||
onCreate: function (_a) {
|
||||
|
||||
1
packages/editor/dist/toolbar/icons.d.ts
vendored
1
packages/editor/dist/toolbar/icons.d.ts
vendored
@@ -70,6 +70,7 @@ export declare const Icons: {
|
||||
chevronDown: string;
|
||||
chevronUp: string;
|
||||
chevronRight: string;
|
||||
circle: string;
|
||||
none: string;
|
||||
};
|
||||
export declare type IconNames = keyof typeof Icons;
|
||||
|
||||
3
packages/editor/dist/toolbar/icons.js
vendored
3
packages/editor/dist/toolbar/icons.js
vendored
@@ -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, mdiTableRowPlusAfter, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, mdiTableColumnWidth, mdiTableRowHeight, mdiMinus, mdiPaletteOutline, } from "@mdi/js";
|
||||
import { mdiAttachment, mdiBorderHorizontal, mdiCheck, mdiChevronDown, mdiCodeBraces, mdiCodeTags, mdiDotsVertical, mdiFormatAlignCenter, mdiFormatAlignJustify, mdiFormatAlignLeft, mdiFormatAlignRight, mdiFormatBold, mdiFormatClear, mdiFormatColorHighlight, mdiFormatColorText, mdiFormatItalic, mdiFormatListBulleted, mdiFormatListNumbered, mdiFormatQuoteClose, mdiFormatStrikethrough, mdiFormatSubscript, mdiFormatSuperscript, mdiFormatTextdirectionLToR, mdiFormatTextdirectionRToL, mdiFormatUnderline, mdiImage, mdiInvertColorsOff, mdiLinkPlus, mdiLoading, mdiTable, mdiTableBorder, mdiTableRowPlusAfter, mdiTableRowPlusBefore, mdiTableRowRemove, mdiTableColumnPlusAfter, mdiTableColumnPlusBefore, mdiTableColumnRemove, mdiUploadOutline, mdiPlus, mdiSquareRoundedBadgeOutline, mdiFormatColorFill, mdiBorderAllVariant, mdiClose, mdiSortDescending, mdiArrowExpandRight, mdiArrowExpandLeft, mdiArrowExpandDown, mdiArrowExpandUp, mdiTrashCanOutline, mdiTableMergeCells, mdiTableSplitCell, mdiDeleteOutline, mdiDownloadOutline, mdiFormatListCheckbox, mdiDrag, mdiCheckboxMarkedOutline, mdiChevronUp, mdiArrowUp, mdiArrowDown, mdiRegex, mdiFormatLetterCase, mdiFormatLetterMatches, mdiMoviePlusOutline, mdiLink, mdiChevronRight, mdiTableColumnWidth, mdiTableRowHeight, mdiMinus, mdiPaletteOutline, mdiCircle, } from "@mdi/js";
|
||||
export var Icons = {
|
||||
bold: mdiFormatBold,
|
||||
italic: mdiFormatItalic,
|
||||
@@ -71,5 +71,6 @@ export var Icons = {
|
||||
chevronDown: mdiChevronDown,
|
||||
chevronUp: mdiChevronUp,
|
||||
chevronRight: mdiChevronRight,
|
||||
circle: mdiCircle,
|
||||
none: "",
|
||||
};
|
||||
|
||||
105
packages/editor/src/extensions/outline-list-item/component.tsx
Normal file
105
packages/editor/src/extensions/outline-list-item/component.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Box, Flex, Text } from "rebass";
|
||||
import { ReactNodeViewProps } from "../react";
|
||||
import { Icon } from "../../toolbar/components/icon";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { Node } from "prosemirror-model";
|
||||
import { Transaction } from "prosemirror-state";
|
||||
import {
|
||||
findChildren,
|
||||
findParentNode,
|
||||
getNodeType,
|
||||
NodeWithPos,
|
||||
} from "@tiptap/core";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { OutlineList } from "../outline-list/outline-list";
|
||||
|
||||
export function OutlineListItemComponent(props: ReactNodeViewProps) {
|
||||
const { editor, updateAttributes, node, getPos, forwardRef } = props;
|
||||
|
||||
const isNested = node.lastChild?.type.name === OutlineList.name;
|
||||
const isCollapsed = isNested && node.lastChild?.attrs.collapsed;
|
||||
|
||||
return (
|
||||
<Flex>
|
||||
<Flex
|
||||
className="outline"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
mt: "3px",
|
||||
}}
|
||||
>
|
||||
{isNested ? (
|
||||
<Icon
|
||||
path={isCollapsed ? Icons.chevronRight : Icons.chevronDown}
|
||||
title={
|
||||
isCollapsed
|
||||
? "Click to uncollapse list"
|
||||
: "Click to collapse list"
|
||||
}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
transition: `all .2s ease-in-out`,
|
||||
":hover": {
|
||||
transform: "scale(1.3)",
|
||||
},
|
||||
".icon:hover path": {
|
||||
fill: "var(--checked) !important",
|
||||
},
|
||||
}}
|
||||
size={18}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => {
|
||||
const [subList] = findChildren(
|
||||
node,
|
||||
(node) => node.type.name === OutlineList.name
|
||||
);
|
||||
if (!subList) return;
|
||||
const { pos } = subList;
|
||||
|
||||
editor.commands.toggleOutlineCollapse(
|
||||
pos + getPos() + 1,
|
||||
!isCollapsed
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
path={Icons.circle}
|
||||
size={18}
|
||||
sx={{ transform: "scale(0.4)" }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isNested && !isCollapsed && (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
width: 1,
|
||||
mt: 2,
|
||||
backgroundColor: "border",
|
||||
borderRadius: 50,
|
||||
flexShrink: 0,
|
||||
cursor: "pointer",
|
||||
transition: `all .2s ease-in-out`,
|
||||
":hover": {
|
||||
backgroundColor: "fontTertiary",
|
||||
width: 4,
|
||||
},
|
||||
}}
|
||||
contentEditable={false}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Text
|
||||
as="li"
|
||||
ref={forwardRef}
|
||||
sx={{
|
||||
pl: 2,
|
||||
listStyleType: "none",
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./outline-list-item";
|
||||
@@ -0,0 +1,134 @@
|
||||
import { Node, mergeAttributes, findChildren, Editor } from "@tiptap/core";
|
||||
import { NodeType } from "prosemirror-model";
|
||||
import { findParentNodeOfTypeClosestToPos } from "prosemirror-utils";
|
||||
import { onBackspacePressed } from "../list-item/commands";
|
||||
import { OutlineList } from "../outline-list/outline-list";
|
||||
import { createNodeView } from "../react";
|
||||
import { OutlineListItemComponent } from "./component";
|
||||
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
outlineListItem: {
|
||||
toggleOutlineCollapse: (subListPos: number, state: boolean) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const OutlineListItem = Node.create<ListItemOptions>({
|
||||
name: "outlineListItem",
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
|
||||
content: "heading* block*",
|
||||
|
||||
defining: true,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `li[data-type="${this.name}"]`,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"li",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"Mod-Space": ({ editor }) => {
|
||||
const subList = findSublist(editor, this.type);
|
||||
if (!subList) return false;
|
||||
const { isCollapsed, subListPos } = subList;
|
||||
|
||||
return this.editor.commands.toggleOutlineCollapse(
|
||||
subListPos,
|
||||
!isCollapsed
|
||||
);
|
||||
},
|
||||
Enter: ({ editor }) => {
|
||||
const subList = findSublist(editor, this.type);
|
||||
if (!subList) return false;
|
||||
const { isCollapsed, subListPos } = subList;
|
||||
|
||||
if (isCollapsed) {
|
||||
return this.editor.commands.toggleOutlineCollapse(subListPos, false);
|
||||
}
|
||||
|
||||
return this.editor.commands.splitListItem(this.name);
|
||||
},
|
||||
Tab: () => this.editor.commands.sinkListItem(this.name),
|
||||
"Shift-Tab": () => this.editor.commands.liftListItem(this.name),
|
||||
Backspace: ({ editor }) =>
|
||||
onBackspacePressed(editor, this.name, this.type),
|
||||
};
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
toggleOutlineCollapse:
|
||||
(pos, state) =>
|
||||
({ tr }) => {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
collapsed: state,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return createNodeView(OutlineListItemComponent, {
|
||||
contentDOMFactory: true,
|
||||
// wrapperFactory: () => document.createElement("li"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function findSublist(editor: Editor, type: NodeType) {
|
||||
const { selection } = editor.state;
|
||||
const { $from } = selection;
|
||||
|
||||
const listItem = findParentNodeOfTypeClosestToPos($from, type);
|
||||
if (!listItem) return false;
|
||||
|
||||
const [subList] = findChildren(
|
||||
listItem.node,
|
||||
(node) => node.type.name === OutlineList.name
|
||||
);
|
||||
if (!subList) return false;
|
||||
|
||||
const isNested = subList?.node?.type.name === OutlineList.name;
|
||||
const isCollapsed = subList?.node?.attrs.collapsed;
|
||||
const subListPos = listItem.pos + subList.pos + 1;
|
||||
|
||||
return { isCollapsed, isNested, subListPos };
|
||||
// return (
|
||||
// this.editor
|
||||
// .chain()
|
||||
// .command(({ tr }) => {
|
||||
// tr.setNodeMarkup(listItem.pos + subList.pos + 1, undefined, {
|
||||
// collapsed: !isCollapsed,
|
||||
// });
|
||||
// return true;
|
||||
// })
|
||||
// //.setTextSelection(listItem.pos + subList.pos + 1)
|
||||
// //.splitListItem(this.name)
|
||||
// .run()
|
||||
// );
|
||||
}
|
||||
47
packages/editor/src/extensions/outline-list/component.tsx
Normal file
47
packages/editor/src/extensions/outline-list/component.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Box, Flex, Text } from "rebass";
|
||||
import { ReactNodeViewProps } from "../react";
|
||||
import { Node } from "prosemirror-model";
|
||||
import {
|
||||
findParentNodeClosestToPos,
|
||||
findChildren,
|
||||
getNodeType,
|
||||
} from "@tiptap/core";
|
||||
import { Icon } from "../../toolbar/components/icon";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Input } from "@rebass/forms";
|
||||
import { TaskItemNode } from "../task-item";
|
||||
import { OutlineListAttributes } from "./outline-list";
|
||||
import { findParentNodeOfTypeClosestToPos } from "prosemirror-utils";
|
||||
import { OutlineListItem } from "../outline-list-item";
|
||||
|
||||
export function OutlineListComponent(
|
||||
props: ReactNodeViewProps<OutlineListAttributes>
|
||||
) {
|
||||
const { editor, getPos, node, updateAttributes, forwardRef } = props;
|
||||
const { collapsed } = node.attrs;
|
||||
|
||||
const isNested = useMemo(() => {
|
||||
const pos = editor.state.doc.resolve(getPos());
|
||||
return pos.parent?.type.name === OutlineListItem.name;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
className="outline-list"
|
||||
as={"div"}
|
||||
ref={forwardRef}
|
||||
sx={{
|
||||
ul: {
|
||||
display: collapsed ? "none" : "block",
|
||||
paddingInlineStart: 0,
|
||||
paddingLeft: isNested ? 1 : 0,
|
||||
marginBlockStart: isNested ? 5 : 0,
|
||||
marginBlockEnd: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
packages/editor/src/extensions/outline-list/index.ts
Normal file
1
packages/editor/src/extensions/outline-list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./outline-list";
|
||||
106
packages/editor/src/extensions/outline-list/outline-list.ts
Normal file
106
packages/editor/src/extensions/outline-list/outline-list.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Node, mergeAttributes, wrappingInputRule } from "@tiptap/core";
|
||||
import { createNodeView } from "../react";
|
||||
import { OutlineListComponent } from "./component";
|
||||
|
||||
export type OutlineListAttributes = {
|
||||
collapsed: boolean;
|
||||
};
|
||||
|
||||
export interface OutlineListOptions {
|
||||
HTMLAttributes: Record<string, any>;
|
||||
}
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
outlineList: {
|
||||
/**
|
||||
* Toggle a bullet list
|
||||
*/
|
||||
toggleOutlineList: () => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const inputRegex = /^\s*(-o)\s$/;
|
||||
const outlineListItemName = `outlineListItem`;
|
||||
export const OutlineList = Node.create<OutlineListOptions>({
|
||||
name: "outlineList",
|
||||
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
};
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
collapsed: {
|
||||
default: false,
|
||||
keepOnSplit: false,
|
||||
parseHTML: (element) => element.dataset.collapsed === "true",
|
||||
renderHTML: (attributes) => ({
|
||||
"data-collapsed": attributes.collapsed === true,
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
group: "block list",
|
||||
|
||||
content: `${outlineListItemName}+`,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: `ul[data-type="${this.name}"]`,
|
||||
priority: 52,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return [
|
||||
"ul",
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
"data-type": this.name,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
return {
|
||||
toggleOutlineList:
|
||||
() =>
|
||||
({ commands }) => {
|
||||
return commands.toggleList(this.name, outlineListItemName);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"Mod-Shift-O": () => this.editor.commands.toggleOutlineList(),
|
||||
};
|
||||
},
|
||||
|
||||
addInputRules() {
|
||||
return [
|
||||
wrappingInputRule({
|
||||
find: inputRegex,
|
||||
type: this.type,
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return createNodeView(OutlineListComponent, {
|
||||
contentDOMFactory: () => {
|
||||
const content = document.createElement("ul");
|
||||
content.classList.add(`${this.name.toLowerCase()}-content-wrapper`);
|
||||
content.style.whiteSpace = "inherit";
|
||||
return { dom: content };
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -33,6 +33,8 @@ import { CodeBlock } from "./extensions/code-block";
|
||||
import { ListItem } from "./extensions/list-item";
|
||||
import { Link } from "./extensions/link";
|
||||
import { PortalProviderAPI, EventDispatcher } from "./extensions/react";
|
||||
import { OutlineList } from "./extensions/outline-list";
|
||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||
|
||||
EditorView.prototype.updateState = function updateState(state) {
|
||||
if (!(this as any).docView) return; // This prevents the matchesNode error on hot reloads
|
||||
@@ -110,6 +112,8 @@ const useTiptap = (
|
||||
onDownloadAttachment,
|
||||
onOpenAttachmentPicker,
|
||||
}),
|
||||
OutlineListItem,
|
||||
OutlineList,
|
||||
ListItem,
|
||||
],
|
||||
onCreate: ({ editor }) => {
|
||||
|
||||
@@ -84,6 +84,7 @@ import {
|
||||
mdiTableRowHeight,
|
||||
mdiMinus,
|
||||
mdiPaletteOutline,
|
||||
mdiCircle,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const Icons = {
|
||||
@@ -161,6 +162,7 @@ export const Icons = {
|
||||
chevronDown: mdiChevronDown,
|
||||
chevronUp: mdiChevronUp,
|
||||
chevronRight: mdiChevronRight,
|
||||
circle: mdiCircle,
|
||||
|
||||
none: "",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user