feat: ability to create new nodes on enter and delete old nodes on delete
This commit is contained in:
parent
19d302355f
commit
960f07cd11
@ -12,6 +12,7 @@
|
|||||||
"@types/jest": "^24.0.0",
|
"@types/jest": "^24.0.0",
|
||||||
"@types/jwt-decode": "^2.2.1",
|
"@types/jwt-decode": "^2.2.1",
|
||||||
"@types/lodash": "^4.14.149",
|
"@types/lodash": "^4.14.149",
|
||||||
|
"@types/marked": "^1.2.2",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.0.0",
|
||||||
"@types/query-string": "^6.3.0",
|
"@types/query-string": "^6.3.0",
|
||||||
"@types/react": "^16.9.21",
|
"@types/react": "^16.9.21",
|
||||||
@ -22,6 +23,7 @@
|
|||||||
"@types/react-router-dom": "^5.1.3",
|
"@types/react-router-dom": "^5.1.3",
|
||||||
"@types/react-select": "^3.0.13",
|
"@types/react-select": "^3.0.13",
|
||||||
"@types/react-timeago": "^4.1.1",
|
"@types/react-timeago": "^4.1.1",
|
||||||
|
"@types/react-window": "^1.8.2",
|
||||||
"@types/styled-components": "^5.0.0",
|
"@types/styled-components": "^5.0.0",
|
||||||
"apollo-cache-inmemory": "^1.6.5",
|
"apollo-cache-inmemory": "^1.6.5",
|
||||||
"apollo-client": "^2.6.8",
|
"apollo-client": "^2.6.8",
|
||||||
@ -41,6 +43,7 @@
|
|||||||
"immer": "^6.0.3",
|
"immer": "^6.0.3",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
|
"marked": "^2.0.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"query-string": "^6.13.7",
|
"query-string": "^6.13.7",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
@ -56,6 +59,8 @@
|
|||||||
"react-select": "^3.1.0",
|
"react-select": "^3.1.0",
|
||||||
"react-timeago": "^4.4.0",
|
"react-timeago": "^4.4.0",
|
||||||
"react-toastify": "^6.0.8",
|
"react-toastify": "^6.0.8",
|
||||||
|
"react-visibility-sensor": "^5.1.1",
|
||||||
|
"react-window": "^1.8.6",
|
||||||
"rich-markdown-editor": "^10.6.5",
|
"rich-markdown-editor": "^10.6.5",
|
||||||
"styled-components": "^5.0.1",
|
"styled-components": "^5.0.1",
|
||||||
"typescript": "~3.7.2"
|
"typescript": "~3.7.2"
|
||||||
|
@ -36,7 +36,10 @@ const AuthorizedRoutes = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { setUser } = useCurrentUser();
|
const { setUser } = useCurrentUser();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
|
||||||
fetch('/auth/refresh_token', {
|
fetch('/auth/refresh_token', {
|
||||||
|
signal: abortController.signal,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
}).then(async x => {
|
}).then(async x => {
|
||||||
@ -60,6 +63,9 @@ const AuthorizedRoutes = () => {
|
|||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return loading ? null : (
|
return loading ? null : (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -34,9 +34,93 @@ type DraggerProps = {
|
|||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
onDragEnd: (zone: ImpactZone) => void;
|
onDragEnd: (zone: ImpactZone) => void;
|
||||||
initialPos: { x: number; y: number };
|
initialPos: { x: number; y: number };
|
||||||
|
pageRef: React.RefObject<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, isDragging, initialPos }) => {
|
let timer: any = null;
|
||||||
|
|
||||||
|
type windowScrollOptions = {
|
||||||
|
maxScrollX: number;
|
||||||
|
maxScrollY: number;
|
||||||
|
isInTopEdge: boolean;
|
||||||
|
isInBottomEdge: boolean;
|
||||||
|
edgeTop: number;
|
||||||
|
edgeBottom: number;
|
||||||
|
edgeSize: number;
|
||||||
|
viewportY: number;
|
||||||
|
$page: React.RefObject<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
function adjustWindowScroll({
|
||||||
|
maxScrollY,
|
||||||
|
maxScrollX,
|
||||||
|
$page,
|
||||||
|
isInTopEdge,
|
||||||
|
isInBottomEdge,
|
||||||
|
edgeTop,
|
||||||
|
edgeBottom,
|
||||||
|
edgeSize,
|
||||||
|
viewportY,
|
||||||
|
}: windowScrollOptions) {
|
||||||
|
// Get the current scroll position of the document.
|
||||||
|
if ($page.current) {
|
||||||
|
var currentScrollX = $page.current.scrollLeft;
|
||||||
|
var currentScrollY = $page.current.scrollTop;
|
||||||
|
|
||||||
|
// Determine if the window can be scrolled in any particular direction.
|
||||||
|
var canScrollUp = currentScrollY > 0;
|
||||||
|
var canScrollDown = currentScrollY < maxScrollY;
|
||||||
|
|
||||||
|
// Since we can potentially scroll in two directions at the same time,
|
||||||
|
// let's keep track of the next scroll, starting with the current scroll.
|
||||||
|
// Each of these values can then be adjusted independently in the logic
|
||||||
|
// below.
|
||||||
|
var nextScrollX = currentScrollX;
|
||||||
|
var nextScrollY = currentScrollY;
|
||||||
|
|
||||||
|
// As we examine the mouse position within the edge, we want to make the
|
||||||
|
// incremental scroll changes more "intense" the closer that the user
|
||||||
|
// gets the viewport edge. As such, we'll calculate the percentage that
|
||||||
|
// the user has made it "through the edge" when calculating the delta.
|
||||||
|
// Then, that use that percentage to back-off from the "max" step value.
|
||||||
|
var maxStep = 50;
|
||||||
|
|
||||||
|
// Should we scroll up?
|
||||||
|
if (isInTopEdge && canScrollUp) {
|
||||||
|
var intensity = (edgeTop - viewportY) / edgeSize;
|
||||||
|
|
||||||
|
nextScrollY = nextScrollY - maxStep * intensity;
|
||||||
|
|
||||||
|
// Should we scroll down?
|
||||||
|
} else if (isInBottomEdge && canScrollDown) {
|
||||||
|
var intensity = (viewportY - edgeBottom) / edgeSize;
|
||||||
|
|
||||||
|
nextScrollY = nextScrollY + maxStep * intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize invalid maximums. An invalid scroll offset won't break the
|
||||||
|
// subsequent .scrollTo() call; however, it will make it harder to
|
||||||
|
// determine if the .scrollTo() method should have been called in the
|
||||||
|
// first place.
|
||||||
|
nextScrollX = Math.max(0, Math.min(maxScrollX, nextScrollX));
|
||||||
|
nextScrollY = Math.max(0, Math.min(maxScrollY, nextScrollY));
|
||||||
|
|
||||||
|
if (nextScrollX !== currentScrollX || nextScrollY !== currentScrollY) {
|
||||||
|
$page.current.scrollTo(nextScrollX, nextScrollY);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dragger: React.FC<DraggerProps> = ({
|
||||||
|
draggedNodes,
|
||||||
|
container,
|
||||||
|
onDragEnd,
|
||||||
|
isDragging,
|
||||||
|
initialPos,
|
||||||
|
pageRef: $page,
|
||||||
|
}) => {
|
||||||
const [pos, setPos] = useState<{ x: number; y: number }>(initialPos);
|
const [pos, setPos] = useState<{ x: number; y: number }>(initialPos);
|
||||||
const { outline, impact, setImpact } = useDrag();
|
const { outline, impact, setImpact } = useDrag();
|
||||||
const $handle = useRef<HTMLDivElement>(null);
|
const $handle = useRef<HTMLDivElement>(null);
|
||||||
@ -45,6 +129,8 @@ const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, i
|
|||||||
}, [impact]);
|
}, [impact]);
|
||||||
const handleMouseMove = useCallback(
|
const handleMouseMove = useCallback(
|
||||||
e => {
|
e => {
|
||||||
|
var t0 = performance.now();
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { clientX, clientY, pageX, pageY } = e;
|
const { clientX, clientY, pageX, pageY } = e;
|
||||||
setPos({ x: clientX, y: clientY });
|
setPos({ x: clientX, y: clientY });
|
||||||
@ -53,6 +139,61 @@ const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, i
|
|||||||
let aboveNode: null | OutlineNode = null;
|
let aboveNode: null | OutlineNode = null;
|
||||||
let belowNode: null | OutlineNode = null;
|
let belowNode: null | OutlineNode = null;
|
||||||
|
|
||||||
|
const edgeSize = 50;
|
||||||
|
|
||||||
|
const viewportWidth = document.documentElement.clientWidth;
|
||||||
|
const viewportHeight = document.documentElement.clientHeight;
|
||||||
|
|
||||||
|
var edgeTop = edgeSize + 80;
|
||||||
|
var edgeBottom = viewportHeight - edgeSize;
|
||||||
|
|
||||||
|
var isInTopEdge = clientY < edgeTop;
|
||||||
|
var isInBottomEdge = clientY > edgeBottom;
|
||||||
|
|
||||||
|
if ((isInBottomEdge || isInTopEdge) && $page.current) {
|
||||||
|
var documentWidth = Math.max(
|
||||||
|
$page.current.scrollWidth,
|
||||||
|
$page.current.offsetWidth,
|
||||||
|
$page.current.clientWidth,
|
||||||
|
$page.current.scrollWidth,
|
||||||
|
$page.current.offsetWidth,
|
||||||
|
$page.current.clientWidth,
|
||||||
|
);
|
||||||
|
var documentHeight = Math.max(
|
||||||
|
$page.current.scrollHeight,
|
||||||
|
$page.current.offsetHeight,
|
||||||
|
$page.current.clientHeight,
|
||||||
|
$page.current.scrollHeight,
|
||||||
|
$page.current.offsetHeight,
|
||||||
|
$page.current.clientHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
var maxScrollX = documentWidth - viewportWidth;
|
||||||
|
var maxScrollY = documentHeight - viewportHeight;
|
||||||
|
|
||||||
|
(function checkForWindowScroll() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
if (
|
||||||
|
adjustWindowScroll({
|
||||||
|
maxScrollX,
|
||||||
|
maxScrollY,
|
||||||
|
edgeBottom,
|
||||||
|
$page,
|
||||||
|
edgeTop,
|
||||||
|
edgeSize,
|
||||||
|
isInBottomEdge,
|
||||||
|
isInTopEdge,
|
||||||
|
viewportY: clientY,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
timer = setTimeout(checkForWindowScroll, 30);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
|
||||||
if (curPosition === 'before') {
|
if (curPosition === 'before') {
|
||||||
belowNode = curDraggable;
|
belowNode = curDraggable;
|
||||||
} else {
|
} else {
|
||||||
@ -131,21 +272,23 @@ const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (aboveNode) {
|
if (aboveNode) {
|
||||||
const { ancestors } = findNodeDepth(outline.current.published, aboveNode.id);
|
const foundDepth = findNodeDepth(outline.current.published, aboveNode.id);
|
||||||
|
if (foundDepth === null) return;
|
||||||
for (let i = 0; i < draggedNodes.nodes.length; i++) {
|
for (let i = 0; i < draggedNodes.nodes.length; i++) {
|
||||||
const nodeID = draggedNodes.nodes[i];
|
const nodeID = draggedNodes.nodes[i];
|
||||||
if (ancestors.find(c => c === nodeID)) {
|
if (foundDepth.ancestors.find(c => c === nodeID)) {
|
||||||
if (draggedNodes.first) {
|
if (draggedNodes.first) {
|
||||||
belowNode = draggedNodes.first;
|
belowNode = draggedNodes.first;
|
||||||
aboveNode = findNodeAbove(outline.current, aboveNode ? aboveNode.depth : 1, draggedNodes.first);
|
aboveNode = findNodeAbove(outline.current, aboveNode ? aboveNode.depth : 1, draggedNodes.first);
|
||||||
} else {
|
} else {
|
||||||
const { depth } = findNodeDepth(outline.current.published, nodeID);
|
const foundDepth = findNodeDepth(outline.current.published, nodeID);
|
||||||
const nodeDepth = outline.current.nodes.get(depth);
|
if (foundDepth === null) return;
|
||||||
|
const nodeDepth = outline.current.nodes.get(foundDepth.depth);
|
||||||
const targetNode = nodeDepth ? nodeDepth.get(nodeID) : null;
|
const targetNode = nodeDepth ? nodeDepth.get(nodeID) : null;
|
||||||
if (targetNode) {
|
if (targetNode) {
|
||||||
belowNode = targetNode;
|
belowNode = targetNode;
|
||||||
|
|
||||||
aboveNode = findNodeAbove(outline.current, depth, targetNode);
|
aboveNode = findNodeAbove(outline.current, foundDepth.depth, targetNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +361,7 @@ const Dragger: React.FC<DraggerProps> = ({ draggedNodes, container, onDragEnd, i
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
const styles = useMemo(() => {
|
const styles = useMemo(() => {
|
||||||
const position: 'absolute' | 'relative' = isDragging ? 'absolute' : 'relative';
|
const position: 'fixed' | 'relative' = isDragging ? 'fixed' : 'relative';
|
||||||
return {
|
return {
|
||||||
cursor: isDragging ? '-webkit-grabbing' : '-webkit-grab',
|
cursor: isDragging ? '-webkit-grabbing' : '-webkit-grab',
|
||||||
transform: `translate(${pos.x - 10}px, ${pos.y - 4}px)`,
|
transform: `translate(${pos.x - 10}px, ${pos.y - 4}px)`,
|
||||||
|
@ -1,24 +1,101 @@
|
|||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useRef, useEffect, useCallback, useState } from 'react';
|
||||||
import { Dot, CaretDown, CaretRight } from 'shared/icons';
|
import { Dot, CaretDown, CaretRight } from 'shared/icons';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import marked from 'marked';
|
||||||
|
import VisibilitySensor from 'react-visibility-sensor';
|
||||||
|
|
||||||
import { EntryChildren, EntryWrapper, EntryContent, EntryInnerContent, EntryHandle, ExpandButton } from './Styles';
|
import {
|
||||||
|
EntryChildren,
|
||||||
|
EntryWrapper,
|
||||||
|
EntryContent,
|
||||||
|
EntryInnerContent,
|
||||||
|
EntryHandle,
|
||||||
|
ExpandButton,
|
||||||
|
EntryContentEditor,
|
||||||
|
EntryContentDisplay,
|
||||||
|
} from './Styles';
|
||||||
import { useDrag } from './useDrag';
|
import { useDrag } from './useDrag';
|
||||||
|
import { getCaretPosition, setCurrentCursorPosition } from './utils';
|
||||||
|
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||||
|
|
||||||
function getCaretPosition(editableDiv: any) {
|
type EditorProps = {
|
||||||
let caretPos = 0;
|
text: string;
|
||||||
let sel: any = null;
|
initFocus: null | { caret: null | number };
|
||||||
let range: any = null;
|
autoFocus: number | null;
|
||||||
if (window.getSelection) {
|
onChangeCurrentText: (text: string) => void;
|
||||||
sel = window.getSelection();
|
onDeleteEntry: (caret: number) => void;
|
||||||
if (sel && sel.rangeCount) {
|
onBlur: () => void;
|
||||||
range = sel.getRangeAt(0);
|
handleChangeText: (caret: number) => void;
|
||||||
if (range.commonAncestorContainer.parentNode === editableDiv) {
|
onDepthChange: (delta: number) => void;
|
||||||
caretPos = range.endOffset;
|
onCreateEntry: () => void;
|
||||||
|
onNodeFocused: () => void;
|
||||||
|
};
|
||||||
|
const Editor: React.FC<EditorProps> = ({
|
||||||
|
text,
|
||||||
|
onCreateEntry,
|
||||||
|
initFocus,
|
||||||
|
autoFocus,
|
||||||
|
onChangeCurrentText,
|
||||||
|
onDepthChange,
|
||||||
|
onDeleteEntry,
|
||||||
|
onNodeFocused,
|
||||||
|
handleChangeText,
|
||||||
|
onBlur,
|
||||||
|
}) => {
|
||||||
|
const $editor = useRef<HTMLInputElement>(null);
|
||||||
|
useOnOutsideClick($editor, true, () => onBlur(), null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoFocus && $editor.current) {
|
||||||
|
$editor.current.focus();
|
||||||
|
$editor.current.setSelectionRange(autoFocus, autoFocus);
|
||||||
|
onNodeFocused();
|
||||||
}
|
}
|
||||||
|
}, [autoFocus]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (initFocus && $editor.current) {
|
||||||
|
$editor.current.focus();
|
||||||
|
if (initFocus.caret) {
|
||||||
|
$editor.current.setSelectionRange(initFocus.caret ?? 0, initFocus.caret ?? 0);
|
||||||
}
|
}
|
||||||
|
onNodeFocused();
|
||||||
}
|
}
|
||||||
return caretPos;
|
}, []);
|
||||||
}
|
return (
|
||||||
|
<EntryContentEditor
|
||||||
|
value={text}
|
||||||
|
ref={$editor}
|
||||||
|
onChange={e => {
|
||||||
|
onChangeCurrentText(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
// onCreateEntry(parentID, position * 2);
|
||||||
|
onCreateEntry();
|
||||||
|
return;
|
||||||
|
} else if (e.keyCode === 9) {
|
||||||
|
e.preventDefault();
|
||||||
|
onDepthChange(e.shiftKey ? -1 : 1);
|
||||||
|
} else if (e.keyCode === 8) {
|
||||||
|
const caretPos = e.currentTarget.selectionEnd;
|
||||||
|
if (caretPos === 0) {
|
||||||
|
// handleChangeText.flush();
|
||||||
|
// onDeleteEntry(depth, id, currentText, caretPos);
|
||||||
|
onDeleteEntry(caretPos);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (e.key === 'z' && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleChangeText(e.currentTarget.selectionEnd ?? 0);
|
||||||
|
// setCaretPos(e.currentTarget.selectionEnd ?? 0);
|
||||||
|
// handleChangeText();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type EntryProps = {
|
type EntryProps = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -30,21 +107,37 @@ type EntryProps = {
|
|||||||
isRoot?: boolean;
|
isRoot?: boolean;
|
||||||
selection: null | Array<{ id: string }>;
|
selection: null | Array<{ id: string }>;
|
||||||
draggedNodes: null | Array<string>;
|
draggedNodes: null | Array<string>;
|
||||||
|
onNodeFocused: (id: string) => void;
|
||||||
|
text: string;
|
||||||
entries: Array<ItemElement>;
|
entries: Array<ItemElement>;
|
||||||
|
onTextChange: (id: string, prex: string, next: string, caret: number) => void;
|
||||||
onCancelDrag: () => void;
|
onCancelDrag: () => void;
|
||||||
|
autoFocus: null | { caret: null | number };
|
||||||
|
onCreateEntry: (parent: string, nextPositon: number) => void;
|
||||||
position: number;
|
position: number;
|
||||||
chain?: Array<string>;
|
chain?: Array<string>;
|
||||||
|
onHandleClick: (id: string) => void;
|
||||||
|
onDepthChange: (id: string, parent: string, position: number, depth: number, depthDelta: number) => void;
|
||||||
|
onDeleteEntry: (depth: number, id: string, text: string, caretPos: number) => void;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Entry: React.FC<EntryProps> = ({
|
const Entry: React.FC<EntryProps> = ({
|
||||||
id,
|
id,
|
||||||
|
text,
|
||||||
parentID,
|
parentID,
|
||||||
isRoot = false,
|
isRoot = false,
|
||||||
selection,
|
selection,
|
||||||
onToggleCollapse,
|
onToggleCollapse,
|
||||||
|
autoFocus,
|
||||||
onStartSelect,
|
onStartSelect,
|
||||||
|
onHandleClick,
|
||||||
|
onTextChange,
|
||||||
position,
|
position,
|
||||||
|
onNodeFocused,
|
||||||
|
onDepthChange,
|
||||||
|
onCreateEntry,
|
||||||
|
onDeleteEntry,
|
||||||
onCancelDrag,
|
onCancelDrag,
|
||||||
onStartDrag,
|
onStartDrag,
|
||||||
collapsed = false,
|
collapsed = false,
|
||||||
@ -56,8 +149,46 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
const $entry = useRef<HTMLDivElement>(null);
|
const $entry = useRef<HTMLDivElement>(null);
|
||||||
const $children = useRef<HTMLDivElement>(null);
|
const $children = useRef<HTMLDivElement>(null);
|
||||||
const { setNodeDimensions, clearNodeDimensions } = useDrag();
|
const { setNodeDimensions, clearNodeDimensions } = useDrag();
|
||||||
|
if (autoFocus) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const $snapshot = useRef<{ now: string; prev: string }>({ now: text, prev: text });
|
||||||
|
const [currentText, setCurrentText] = useState(text);
|
||||||
|
const [caretPos, setCaretPos] = useState(0);
|
||||||
|
const $firstRun = useRef<boolean>(true);
|
||||||
|
useEffect(() => {
|
||||||
|
if ($firstRun.current) {
|
||||||
|
$firstRun.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('updating text');
|
||||||
|
setCurrentText(text);
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
|
const [editor, setEditor] = useState<{ open: boolean; caret: null | number }>({
|
||||||
|
open: false,
|
||||||
|
caret: null,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (autoFocus) setEditor({ open: true, caret: null });
|
||||||
|
}, [autoFocus]);
|
||||||
|
useEffect(() => {
|
||||||
|
$snapshot.current.now = currentText;
|
||||||
|
}, [currentText]);
|
||||||
|
const handleChangeText = useCallback(
|
||||||
|
_.debounce(() => {
|
||||||
|
onTextChange(id, $snapshot.current.prev, $snapshot.current.now, caretPos);
|
||||||
|
$snapshot.current.prev = $snapshot.current.now;
|
||||||
|
}, 500),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isRoot) return;
|
if (isRoot) return;
|
||||||
|
if (!visible) {
|
||||||
|
clearNodeDimensions(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($entry && $entry.current) {
|
if ($entry && $entry.current) {
|
||||||
setNodeDimensions(id, {
|
setNodeDimensions(id, {
|
||||||
entry: $entry,
|
entry: $entry,
|
||||||
@ -67,7 +198,7 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
clearNodeDimensions(id);
|
clearNodeDimensions(id);
|
||||||
};
|
};
|
||||||
}, [position, depth, entries]);
|
}, [position, depth, entries, visible]);
|
||||||
let showHandle = true;
|
let showHandle = true;
|
||||||
if (draggedNodes && draggedNodes.length === 1 && draggedNodes.find(c => c === id)) {
|
if (draggedNodes && draggedNodes.length === 1 && draggedNodes.find(c => c === id)) {
|
||||||
showHandle = false;
|
showHandle = false;
|
||||||
@ -76,9 +207,52 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
if (selection && selection.find(c => c.id === id)) {
|
if (selection && selection.find(c => c.id === id)) {
|
||||||
isSelected = true;
|
isSelected = true;
|
||||||
}
|
}
|
||||||
let onSaveTimer: any = null;
|
const renderMap: Array<number> = [];
|
||||||
const onSaveTimeout = 300;
|
const renderer = {
|
||||||
|
text(text: any) {
|
||||||
|
const localId = renderMap.length;
|
||||||
|
renderMap.push(text.length);
|
||||||
|
return `<span id="${id}_${localId}">${text}</span>`;
|
||||||
|
},
|
||||||
|
codespan(text: any) {
|
||||||
|
const localId = renderMap.length;
|
||||||
|
renderMap.push(text.length + 2);
|
||||||
|
return `<span class="markdown-code" id="${id}_${localId}">${text}</span>`;
|
||||||
|
},
|
||||||
|
strong(text: string) {
|
||||||
|
const idx = parseInt(text.split('"')[1].split('_')[1]);
|
||||||
|
renderMap[idx] += 4;
|
||||||
|
return text.replace('<span', '<span class="markdown-strong"');
|
||||||
|
},
|
||||||
|
em(text: string) {
|
||||||
|
const idx = parseInt(text.split('"')[1].split('_')[1]);
|
||||||
|
renderMap[idx] += 2;
|
||||||
|
return text.replace('<span', '<span class="markdown-em"');
|
||||||
|
},
|
||||||
|
del(text: string) {
|
||||||
|
const idx = parseInt(text.split('"')[1].split('_')[1]);
|
||||||
|
renderMap[idx] += 2;
|
||||||
|
return text.replace('<span', '<span class="markdown-del"');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
marked.use({ renderer });
|
||||||
|
|
||||||
|
const handleMouseDown = useCallback(
|
||||||
|
_.debounce((e: any) => {
|
||||||
|
onStartDrag({ id, clientX: e.clientX, clientY: e.clientY });
|
||||||
|
}, 100),
|
||||||
|
[],
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
|
<VisibilitySensor
|
||||||
|
onChange={v => {
|
||||||
|
if (v) {
|
||||||
|
setVisible(v);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<EntryWrapper isSelected={isSelected} isDragging={!showHandle}>
|
<EntryWrapper isSelected={isSelected} isDragging={!showHandle}>
|
||||||
{!isRoot && (
|
{!isRoot && (
|
||||||
<EntryContent>
|
<EntryContent>
|
||||||
@ -89,9 +263,12 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
)}
|
)}
|
||||||
{showHandle && (
|
{showHandle && (
|
||||||
<EntryHandle
|
<EntryHandle
|
||||||
onMouseUp={() => onCancelDrag()}
|
onMouseUp={() => {
|
||||||
|
handleMouseDown.cancel();
|
||||||
|
onHandleClick(id);
|
||||||
|
}}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
onStartDrag({ id, clientX: e.clientX, clientY: e.clientY });
|
handleMouseDown(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dot width={18} height={18} />
|
<Dot width={18} height={18} />
|
||||||
@ -101,26 +278,62 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
onStartSelect({ id, depth });
|
onStartSelect({ id, depth });
|
||||||
}}
|
}}
|
||||||
onKeyDown={e => {
|
|
||||||
if (e.key === 'z' && e.ctrlKey) {
|
|
||||||
if ($entry && $entry.current) {
|
|
||||||
console.log(getCaretPosition($entry.current));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clearTimeout(onSaveTimer);
|
|
||||||
if ($entry && $entry.current) {
|
|
||||||
onSaveTimer = setTimeout(() => {
|
|
||||||
if ($entry && $entry.current) {
|
|
||||||
console.log($entry.current.textContent);
|
|
||||||
}
|
|
||||||
}, onSaveTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
contentEditable
|
|
||||||
ref={$entry}
|
ref={$entry}
|
||||||
>
|
>
|
||||||
{`${id.toString()} - ${position}`}
|
{editor.open ? (
|
||||||
|
<Editor
|
||||||
|
onDepthChange={delta => onDepthChange(id, parentID, depth, position, delta)}
|
||||||
|
onBlur={() => setEditor({ open: false, caret: null })}
|
||||||
|
onNodeFocused={() => onNodeFocused(id)}
|
||||||
|
autoFocus={autoFocus ? (autoFocus.caret ? autoFocus.caret : 0) : null}
|
||||||
|
initFocus={editor.open ? { caret: editor.caret } : null}
|
||||||
|
text={currentText}
|
||||||
|
onDeleteEntry={caret => {
|
||||||
|
handleChangeText.flush();
|
||||||
|
onDeleteEntry(depth, id, currentText, caret);
|
||||||
|
}}
|
||||||
|
onCreateEntry={() => {
|
||||||
|
onCreateEntry(parentID, position * 2);
|
||||||
|
}}
|
||||||
|
onChangeCurrentText={text => setCurrentText(text)}
|
||||||
|
handleChangeText={caret => {
|
||||||
|
setCaretPos(caret);
|
||||||
|
handleChangeText();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EntryContentDisplay
|
||||||
|
onClick={e => {
|
||||||
|
let offset = 0;
|
||||||
|
let textNode: any;
|
||||||
|
if (document.caretPositionFromPoint) {
|
||||||
|
// standard
|
||||||
|
const range = document.caretPositionFromPoint(e.pageX, e.pageY);
|
||||||
|
console.dir(range);
|
||||||
|
if (range) {
|
||||||
|
textNode = range.offsetNode;
|
||||||
|
offset = range.offset;
|
||||||
|
}
|
||||||
|
} else if (document.caretRangeFromPoint) {
|
||||||
|
// WebKit
|
||||||
|
const range = document.caretRangeFromPoint(e.pageX, e.pageY);
|
||||||
|
if (range) {
|
||||||
|
textNode = range.startContainer;
|
||||||
|
offset = range.startOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = textNode.parentNode.id.split('_');
|
||||||
|
const index = parseInt(id[1]);
|
||||||
|
let caret = offset;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
caret += renderMap[i];
|
||||||
|
}
|
||||||
|
setEditor({ open: true, caret });
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parseInline(text) }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</EntryInnerContent>
|
</EntryInnerContent>
|
||||||
</EntryContent>
|
</EntryContent>
|
||||||
)}
|
)}
|
||||||
@ -130,13 +343,20 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
.sort((a, b) => a.position - b.position)
|
.sort((a, b) => a.position - b.position)
|
||||||
.map(entry => (
|
.map(entry => (
|
||||||
<Entry
|
<Entry
|
||||||
|
onDeleteEntry={onDeleteEntry}
|
||||||
|
onHandleClick={onHandleClick}
|
||||||
|
onDepthChange={onDepthChange}
|
||||||
parentID={id}
|
parentID={id}
|
||||||
key={entry.id}
|
key={entry.id}
|
||||||
|
onTextChange={onTextChange}
|
||||||
position={entry.position}
|
position={entry.position}
|
||||||
|
text={entry.text}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
draggedNodes={draggedNodes}
|
draggedNodes={draggedNodes}
|
||||||
collapsed={entry.collapsed}
|
collapsed={entry.collapsed}
|
||||||
id={entry.id}
|
id={entry.id}
|
||||||
|
autoFocus={entry.focus}
|
||||||
|
onNodeFocused={onNodeFocused}
|
||||||
onStartSelect={onStartSelect}
|
onStartSelect={onStartSelect}
|
||||||
onStartDrag={onStartDrag}
|
onStartDrag={onStartDrag}
|
||||||
onCancelDrag={onCancelDrag}
|
onCancelDrag={onCancelDrag}
|
||||||
@ -144,11 +364,13 @@ const Entry: React.FC<EntryProps> = ({
|
|||||||
chain={[...chain, id]}
|
chain={[...chain, id]}
|
||||||
selection={selection}
|
selection={selection}
|
||||||
onToggleCollapse={onToggleCollapse}
|
onToggleCollapse={onToggleCollapse}
|
||||||
|
onCreateEntry={onCreateEntry}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</EntryChildren>
|
</EntryChildren>
|
||||||
)}
|
)}
|
||||||
</EntryWrapper>
|
</EntryWrapper>
|
||||||
|
</VisibilitySensor>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,11 +97,84 @@ export const EntryHandle = styled.div`
|
|||||||
stroke: ${p => p.theme.colors.text.primary};
|
stroke: ${p => p.theme.colors.text.primary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const EntryContentDisplay = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 15px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
line-height: 24px;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: ${p => p.theme.colors.text.primary};
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
cursor: text;
|
||||||
|
.markdown-del {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
.markdown-code {
|
||||||
|
margin-top: -4px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 19px;
|
||||||
|
color: ${props => props.theme.colors.primary};
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 4px 5px 0;
|
||||||
|
font-family: 'Consolas', Courier, monospace;
|
||||||
|
background: ${props => props.theme.colors.bg.primary};
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.markdown-em {
|
||||||
|
margin-top: -4px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.markdown-strong {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EntryContentEditor = styled.input`
|
||||||
|
width: 100%;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
line-height: 24px;
|
||||||
|
min-height: 24px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
position: relative;
|
||||||
|
user-select: text;
|
||||||
|
color: ${p => p.theme.colors.text.primary};
|
||||||
|
&::selection {
|
||||||
|
background: #a49de8;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const EntryInnerContent = styled.div`
|
export const EntryInnerContent = styled.div`
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
@ -124,7 +197,7 @@ export const DragDebugWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const DragIndicatorBar = styled.div<{ left: number; top: number; width: number }>`
|
export const DragIndicatorBar = styled.div<{ left: number; top: number; width: number }>`
|
||||||
position: absolute;
|
position: fixed;
|
||||||
width: ${props => props.width}px;
|
width: ${props => props.width}px;
|
||||||
top: ${props => props.top}px;
|
top: ${props => props.top}px;
|
||||||
left: ${props => props.left}px;
|
left: ${props => props.left}px;
|
||||||
@ -160,5 +233,28 @@ export const EntryContent = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const PageContainer = styled.div`
|
export const PageContainer = styled.div`
|
||||||
overflow: scroll;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const PageName = styled.div`
|
||||||
|
position: relative;
|
||||||
|
margin-left: -100px;
|
||||||
|
padding-left: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-color: rgb(170, 170, 170);
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PageNameContent = styled.div`
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
position: relative;
|
||||||
|
user-select: text;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const PageNameText = styled.span``;
|
||||||
|
@ -21,6 +21,9 @@ import {
|
|||||||
EntryContent,
|
EntryContent,
|
||||||
RootWrapper,
|
RootWrapper,
|
||||||
EntryHandle,
|
EntryHandle,
|
||||||
|
PageNameContent,
|
||||||
|
PageNameText,
|
||||||
|
PageName,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
import {
|
import {
|
||||||
transformToTree,
|
transformToTree,
|
||||||
@ -33,14 +36,49 @@ import {
|
|||||||
getNodeOver,
|
getNodeOver,
|
||||||
getCorrectNode,
|
getCorrectNode,
|
||||||
findCommonParent,
|
findCommonParent,
|
||||||
|
getNodeAbove,
|
||||||
|
findNodeAbove,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import NOOP from 'shared/utils/noop';
|
import NOOP from 'shared/utils/noop';
|
||||||
|
|
||||||
|
enum CommandType {
|
||||||
|
MOVE,
|
||||||
|
MERGE,
|
||||||
|
CHANGE_TEXT,
|
||||||
|
DELETE,
|
||||||
|
CREATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveData = {
|
||||||
|
prev: { position: number; parent: string | null };
|
||||||
|
next: { position: number; parent: string | null };
|
||||||
|
};
|
||||||
|
|
||||||
|
type ChangeTextData = {
|
||||||
|
node: {
|
||||||
|
id: string;
|
||||||
|
parentID: string;
|
||||||
|
position: number;
|
||||||
|
};
|
||||||
|
caret: number;
|
||||||
|
prev: string;
|
||||||
|
next: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeleteData = {
|
||||||
|
node: {
|
||||||
|
id: string;
|
||||||
|
parentID: string;
|
||||||
|
position: number;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type OutlineCommand = {
|
type OutlineCommand = {
|
||||||
nodes: Array<{
|
nodes: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
prev: { position: number; parent: string | null };
|
type: CommandType;
|
||||||
next: { position: number; parent: string | null };
|
data: MoveData | DeleteData | ChangeTextData;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,19 +87,49 @@ type ItemCollapsed = {
|
|||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function generateItems(c: number) {
|
||||||
|
const items: Array<ItemElement> = [];
|
||||||
|
for (let i = 0; i < c; i++) {
|
||||||
|
items.push({
|
||||||
|
collapsed: false,
|
||||||
|
focus: null,
|
||||||
|
id: `entry-gen-${i}`,
|
||||||
|
text: `entry-gen-${i}`,
|
||||||
|
parent: 'root',
|
||||||
|
position: 4096 * (6 + i),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
const listItems: Array<ItemElement> = [
|
const listItems: Array<ItemElement> = [
|
||||||
{ id: 'root', position: 4096, parent: null, collapsed: false },
|
{ id: 'root', text: '', position: 4096, parent: null, collapsed: false, focus: null },
|
||||||
{ id: 'entry-1', position: 4096, parent: 'root', collapsed: false },
|
{ id: 'entry-1', text: 'entry-1', position: 4096, parent: 'root', collapsed: false, focus: null },
|
||||||
{ id: 'entry-1_3', position: 4096 * 3, parent: 'entry-1', collapsed: false },
|
{ id: 'entry-1-3', text: 'entry-1-3', position: 4096 * 3, parent: 'entry-1', collapsed: false, focus: null },
|
||||||
{ id: 'entry-1_3_1', position: 4096, parent: 'entry-1_3', collapsed: false },
|
{ id: 'entry-1-3-1', text: 'entry-1-3-1', position: 4096, parent: 'entry-1-3', collapsed: false, focus: null },
|
||||||
{ id: 'entry-1_3_2', position: 4096 * 2, parent: 'entry-1_3', collapsed: false },
|
{ id: 'entry-1-3-2', text: 'entry-1-3-2', position: 4096 * 2, parent: 'entry-1-3', collapsed: false, focus: null },
|
||||||
{ id: 'entry-1_3_3', position: 4096 * 3, parent: 'entry-1_3', collapsed: false },
|
{ id: 'entry-1-3-3', text: 'entry-1-3-3', position: 4096 * 3, parent: 'entry-1-3', collapsed: false, focus: null },
|
||||||
{ id: 'entry-1_3_3_1', position: 4096 * 1, parent: 'entry-1_3_3', collapsed: false },
|
{
|
||||||
{ id: 'entry-1_3_3_1_1', position: 4096 * 1, parent: 'entry-1_3_3_1', collapsed: false },
|
id: 'entry-1-3-3-1',
|
||||||
{ id: 'entry-2', position: 4096 * 2, parent: 'root', collapsed: false },
|
text: '*Hello!* I am `doing super` well ~how~ are **you**?',
|
||||||
{ id: 'entry-3', position: 4096 * 3, parent: 'root', collapsed: false },
|
position: 4096 * 1,
|
||||||
{ id: 'entry-4', position: 4096 * 4, parent: 'root', collapsed: false },
|
parent: 'entry-1-3-3',
|
||||||
{ id: 'entry-5', position: 4096 * 5, parent: 'root', collapsed: false },
|
collapsed: false,
|
||||||
|
focus: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'entry-1-3-3-1-1',
|
||||||
|
text: 'entry-1-3-3-1-1',
|
||||||
|
position: 4096 * 1,
|
||||||
|
parent: 'entry-1-3-3-1',
|
||||||
|
collapsed: false,
|
||||||
|
focus: null,
|
||||||
|
},
|
||||||
|
{ id: 'entry-2', text: 'entry-2', position: 4096 * 2, parent: 'root', collapsed: false, focus: null },
|
||||||
|
{ id: 'entry-3', text: 'entry-3', position: 4096 * 3, parent: 'root', collapsed: false, focus: null },
|
||||||
|
{ id: 'entry-4', text: 'entry-4', position: 4096 * 4, parent: 'root', collapsed: false, focus: null },
|
||||||
|
{ id: 'entry-5', text: 'entry-5', position: 4096 * 5, parent: 'root', collapsed: false, focus: null },
|
||||||
|
...generateItems(100),
|
||||||
];
|
];
|
||||||
|
|
||||||
const Outline: React.FC = () => {
|
const Outline: React.FC = () => {
|
||||||
@ -133,7 +201,11 @@ const Outline: React.FC = () => {
|
|||||||
}
|
}
|
||||||
const parent = curParent ?? 'root';
|
const parent = curParent ?? 'root';
|
||||||
outline.current.published.set(id, parent ?? 'root');
|
outline.current.published.set(id, parent ?? 'root');
|
||||||
const { depth, ancestors } = findNodeDepth(outline.current.published, id);
|
const foundDepth = findNodeDepth(outline.current.published, id);
|
||||||
|
if (foundDepth === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { depth, ancestors } = foundDepth;
|
||||||
const collapsedParent = ancestors.slice(0, -1).find(a => collapsedMap.get(a));
|
const collapsedParent = ancestors.slice(0, -1).find(a => collapsedMap.get(a));
|
||||||
if (collapsedParent) {
|
if (collapsedParent) {
|
||||||
continue;
|
continue;
|
||||||
@ -184,9 +256,29 @@ const Outline: React.FC = () => {
|
|||||||
produce(prevItems, draftItems => {
|
produce(prevItems, draftItems => {
|
||||||
currentCommand.nodes.forEach(node => {
|
currentCommand.nodes.forEach(node => {
|
||||||
const idx = prevItems.findIndex(c => c.id === node.id);
|
const idx = prevItems.findIndex(c => c.id === node.id);
|
||||||
if (idx !== -1) {
|
if (node.type === CommandType.MOVE) {
|
||||||
draftItems[idx].parent = node.prev.parent;
|
if (idx === -1) return;
|
||||||
draftItems[idx].position = node.prev.position;
|
const data = node.data as MoveData;
|
||||||
|
draftItems[idx].parent = data.prev.parent;
|
||||||
|
draftItems[idx].position = data.prev.position;
|
||||||
|
} else if (node.type === CommandType.CHANGE_TEXT) {
|
||||||
|
if (idx === -1) return;
|
||||||
|
const data = node.data as ChangeTextData;
|
||||||
|
draftItems[idx] = produce(prevItems[idx], draftItem => {
|
||||||
|
draftItem.text = data.prev;
|
||||||
|
draftItem.focus = { caret: data.caret };
|
||||||
|
});
|
||||||
|
} else if (node.type === CommandType.DELETE) {
|
||||||
|
const data = node.data as DeleteData;
|
||||||
|
draftItems.push({
|
||||||
|
id: data.node.id,
|
||||||
|
position: data.node.position,
|
||||||
|
parent: data.node.parentID,
|
||||||
|
text: '',
|
||||||
|
focus: { caret: null },
|
||||||
|
children: [],
|
||||||
|
collapsed: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
outlineHistory.current.current--;
|
outlineHistory.current.current--;
|
||||||
@ -201,8 +293,11 @@ const Outline: React.FC = () => {
|
|||||||
currentCommand.nodes.forEach(node => {
|
currentCommand.nodes.forEach(node => {
|
||||||
const idx = prevItems.findIndex(c => c.id === node.id);
|
const idx = prevItems.findIndex(c => c.id === node.id);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
draftItems[idx].parent = node.next.parent;
|
if (node.type === CommandType.MOVE) {
|
||||||
draftItems[idx].position = node.next.position;
|
const data = node.data as MoveData;
|
||||||
|
draftItems[idx].parent = data.next.parent;
|
||||||
|
draftItems[idx].position = data.next.position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
outlineHistory.current.current++;
|
outlineHistory.current.current++;
|
||||||
@ -308,6 +403,8 @@ const Outline: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const $page = useRef<HTMLDivElement>(null);
|
||||||
|
const $pageName = useRef<HTMLDivElement>(null);
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -331,13 +428,9 @@ const Outline: React.FC = () => {
|
|||||||
listPosition = lastChild.position * 2.0;
|
listPosition = lastChild.position * 2.0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(zone.above);
|
|
||||||
console.log(zone.below);
|
|
||||||
const correctNode = getCorrectNode(outline.current, zone.above ? zone.above.node : null, depth);
|
const correctNode = getCorrectNode(outline.current, zone.above ? zone.above.node : null, depth);
|
||||||
console.log(correctNode);
|
|
||||||
const listAbove = validateDepth(correctNode, depth);
|
const listAbove = validateDepth(correctNode, depth);
|
||||||
const listBelow = validateDepth(zone.below ? zone.below.node : null, depth);
|
const listBelow = validateDepth(zone.below ? zone.below.node : null, depth);
|
||||||
console.log(listAbove, listBelow);
|
|
||||||
if (listAbove && listBelow) {
|
if (listAbove && listBelow) {
|
||||||
listPosition = (listAbove.position + listBelow.position) / 2.0;
|
listPosition = (listAbove.position + listBelow.position) / 2.0;
|
||||||
} else if (listAbove && !listBelow) {
|
} else if (listAbove && !listBelow) {
|
||||||
@ -378,10 +471,192 @@ const Outline: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<PageContainer>
|
<PageContainer ref={$page}>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<RootWrapper ref={$content}>
|
<RootWrapper ref={$content}>
|
||||||
|
<PageName>
|
||||||
|
<PageNameContent ref={$pageName}>
|
||||||
|
<PageNameText>entry-1-3-1</PageNameText>
|
||||||
|
</PageNameContent>
|
||||||
|
</PageName>
|
||||||
<Entry
|
<Entry
|
||||||
|
onDepthChange={(id, parentID, position, depth, depthDelta) => {
|
||||||
|
if (depthDelta === -1) {
|
||||||
|
const parentRelation = outline.current.relationships.get(parentID);
|
||||||
|
if (parentRelation) {
|
||||||
|
const nodeIdx = parentRelation.children
|
||||||
|
.sort((a, b) => a.position - b.position)
|
||||||
|
.findIndex(c => c.id === id);
|
||||||
|
if (parentRelation.children.length !== 0) {
|
||||||
|
const grandparent = outline.current.published.get(parentID);
|
||||||
|
if (grandparent) {
|
||||||
|
const grandparentNode = outline.current.relationships.get(grandparent);
|
||||||
|
if (grandparentNode) {
|
||||||
|
const parents = grandparentNode.children.sort((a, b) => a.position - b.position);
|
||||||
|
const parentIdx = parents.findIndex(c => c.id === parentID);
|
||||||
|
if (parentIdx === -1) return;
|
||||||
|
let position = parents[parentIdx].position * 2;
|
||||||
|
const nextParent = parents[parentIdx + 1];
|
||||||
|
if (nextParent) {
|
||||||
|
position = (parents[parentIdx].position + nextParent.position) / 2.0;
|
||||||
|
}
|
||||||
|
setItems(prevItems =>
|
||||||
|
produce(prevItems, draftItems => {
|
||||||
|
const idx = prevItems.findIndex(c => c.id === id);
|
||||||
|
draftItems[idx] = produce(prevItems[idx], draftItem => {
|
||||||
|
draftItem.parent = grandparent;
|
||||||
|
draftItem.position = position;
|
||||||
|
draftItem.focus = { caret: 0 };
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const parent = outline.current.relationships.get(parentID);
|
||||||
|
if (parent) {
|
||||||
|
const nodeIdx = parent.children
|
||||||
|
.sort((a, b) => a.position - b.position)
|
||||||
|
.findIndex(c => c.id === id);
|
||||||
|
const aboveNode = parent.children[nodeIdx - 1];
|
||||||
|
if (aboveNode) {
|
||||||
|
const aboveNodeRelations = outline.current.relationships.get(aboveNode.id);
|
||||||
|
let position = 65535;
|
||||||
|
if (aboveNodeRelations) {
|
||||||
|
const children = aboveNodeRelations.children.sort((a, b) => a.position - b.position);
|
||||||
|
if (children.length !== 0) {
|
||||||
|
position = children[children.length - 1].position * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setItems(prevItems =>
|
||||||
|
produce(prevItems, draftItems => {
|
||||||
|
const idx = prevItems.findIndex(c => c.id === id);
|
||||||
|
draftItems[idx] = produce(prevItems[idx], draftItem => {
|
||||||
|
draftItem.parent = aboveNode.id;
|
||||||
|
draftItem.position = position;
|
||||||
|
draftItem.focus = { caret: 0 };
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onTextChange={(id, prev, next, caret) => {
|
||||||
|
outlineHistory.current.current += 1;
|
||||||
|
const data: ChangeTextData = {
|
||||||
|
node: {
|
||||||
|
id,
|
||||||
|
position: 0,
|
||||||
|
parentID: '',
|
||||||
|
},
|
||||||
|
caret,
|
||||||
|
prev,
|
||||||
|
next,
|
||||||
|
};
|
||||||
|
const command: OutlineCommand = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
type: CommandType.CHANGE_TEXT,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
outlineHistory.current.commands[outlineHistory.current.current] = command;
|
||||||
|
if (outlineHistory.current.commands[outlineHistory.current.current + 1]) {
|
||||||
|
outlineHistory.current.commands.splice(outlineHistory.current.current + 1);
|
||||||
|
}
|
||||||
|
setItems(prevItems =>
|
||||||
|
produce(prevItems, draftItems => {
|
||||||
|
const idx = prevItems.findIndex(c => c.id === id);
|
||||||
|
if (idx !== -1) {
|
||||||
|
draftItems[idx] = produce(prevItems[idx], draftItem => {
|
||||||
|
draftItem.text = next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
text=""
|
||||||
|
autoFocus={null}
|
||||||
|
onDeleteEntry={(depth, id, text, caretPos) => {
|
||||||
|
const nodeDepth = outline.current.nodes.get(depth);
|
||||||
|
if (nodeDepth) {
|
||||||
|
const node = nodeDepth.get(id);
|
||||||
|
if (node) {
|
||||||
|
const nodeAbove = findNodeAbove(outline.current, depth, node);
|
||||||
|
setItems(prevItems => {
|
||||||
|
return produce(prevItems, draftItems => {
|
||||||
|
draftItems = prevItems.filter(c => c.id !== id);
|
||||||
|
const idx = prevItems.findIndex(c => c.id === nodeAbove?.id);
|
||||||
|
if (idx !== -1) {
|
||||||
|
draftItems[idx] = produce(prevItems[idx], draftItem => {
|
||||||
|
draftItem.focus = { caret: draftItem.text.length };
|
||||||
|
const cType = CommandType.DELETE;
|
||||||
|
const data: DeleteData = {
|
||||||
|
node: {
|
||||||
|
id,
|
||||||
|
position: node.position,
|
||||||
|
parentID: node.parent,
|
||||||
|
text: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (text !== '') {
|
||||||
|
draftItem.text += text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const command: OutlineCommand = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
type: cType,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
outlineHistory.current.current += 1;
|
||||||
|
outlineHistory.current.commands[outlineHistory.current.current] = command;
|
||||||
|
if (outlineHistory.current.commands[outlineHistory.current.current + 1]) {
|
||||||
|
outlineHistory.current.commands.splice(outlineHistory.current.current + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return draftItems;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCreateEntry={(parent, position) => {
|
||||||
|
setItems(prevItems =>
|
||||||
|
produce(prevItems, draftItems => {
|
||||||
|
draftItems.push({
|
||||||
|
id: '' + Math.random(),
|
||||||
|
collapsed: false,
|
||||||
|
position,
|
||||||
|
text: '',
|
||||||
|
focus: {
|
||||||
|
caret: null,
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onNodeFocused={id => {
|
||||||
|
setItems(prevItems =>
|
||||||
|
produce(prevItems, draftItems => {
|
||||||
|
const idx = draftItems.findIndex(c => c.id === id);
|
||||||
|
draftItems[idx] = produce(draftItems[idx], draftItem => {
|
||||||
|
draftItem.focus = null;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
onStartSelect={({ id, depth }) => {
|
onStartSelect={({ id, depth }) => {
|
||||||
setSelection(null);
|
setSelection(null);
|
||||||
setSelecting({ isSelecting: true, node: { id, depth } });
|
setSelecting({ isSelecting: true, node: { id, depth } });
|
||||||
@ -407,6 +682,7 @@ const Outline: React.FC = () => {
|
|||||||
setImpact(null);
|
setImpact(null);
|
||||||
setDragging({ show: false, draggedNodes: null, initialPos: { x: 0, y: 0 } });
|
setDragging({ show: false, draggedNodes: null, initialPos: { x: 0, y: 0 } });
|
||||||
}}
|
}}
|
||||||
|
onHandleClick={id => {}}
|
||||||
onStartDrag={e => {
|
onStartDrag={e => {
|
||||||
if (e.id !== 'root') {
|
if (e.id !== 'root') {
|
||||||
if (selectRef.current.hasSelection && selection && selection.nodes.find(c => c.id === e.id)) {
|
if (selectRef.current.hasSelection && selection && selection.nodes.find(c => c.id === e.id)) {
|
||||||
@ -430,6 +706,7 @@ const Outline: React.FC = () => {
|
|||||||
<Dragger
|
<Dragger
|
||||||
container={$content}
|
container={$content}
|
||||||
initialPos={dragging.initialPos}
|
initialPos={dragging.initialPos}
|
||||||
|
pageRef={$page}
|
||||||
draggedNodes={{ nodes: dragging.draggedNodes, first: selection ? selection.first : null }}
|
draggedNodes={{ nodes: dragging.draggedNodes, first: selection ? selection.first : null }}
|
||||||
isDragging={dragging.show}
|
isDragging={dragging.show}
|
||||||
onDragEnd={() => {
|
onDragEnd={() => {
|
||||||
@ -464,6 +741,8 @@ const Outline: React.FC = () => {
|
|||||||
const curDragging = itemsPrev.findIndex(i => i.id === n);
|
const curDragging = itemsPrev.findIndex(i => i.id === n);
|
||||||
command.nodes.push({
|
command.nodes.push({
|
||||||
id: n,
|
id: n,
|
||||||
|
type: CommandType.MOVE,
|
||||||
|
data: {
|
||||||
prev: {
|
prev: {
|
||||||
parent: draftItems[curDragging].parent,
|
parent: draftItems[curDragging].parent,
|
||||||
position: draftItems[curDragging].position,
|
position: draftItems[curDragging].position,
|
||||||
@ -472,6 +751,7 @@ const Outline: React.FC = () => {
|
|||||||
parent: parentID,
|
parent: parentID,
|
||||||
position: listPosition,
|
position: listPosition,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
draftItems[curDragging].parent = parentID;
|
draftItems[curDragging].parent = parentID;
|
||||||
draftItems[curDragging].position = listPosition;
|
draftItems[curDragging].position = listPosition;
|
||||||
|
@ -2,12 +2,10 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
export function getCorrectNode(data: OutlineData, node: OutlineNode | null, depth: number) {
|
export function getCorrectNode(data: OutlineData, node: OutlineNode | null, depth: number) {
|
||||||
if (node) {
|
if (node) {
|
||||||
console.log(depth, node);
|
|
||||||
if (depth === node.depth) {
|
if (depth === node.depth) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
const parent = node.ancestors[depth];
|
const parent = node.ancestors[depth];
|
||||||
console.log('parent', parent);
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
const parentNode = data.relationships.get(parent);
|
const parentNode = data.relationships.get(parent);
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
@ -43,7 +41,6 @@ export function getNodeAbove(node: OutlineNode, startingParent: RelationshipChil
|
|||||||
position: parentNode.position,
|
position: parentNode.position,
|
||||||
children: parentNode.children,
|
children: parentNode.children,
|
||||||
};
|
};
|
||||||
console.log('node above', nodeAbove);
|
|
||||||
}
|
}
|
||||||
hasChildren = false;
|
hasChildren = false;
|
||||||
continue;
|
continue;
|
||||||
@ -65,7 +62,6 @@ export function getNodeAbove(node: OutlineNode, startingParent: RelationshipChil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('final node above', nodeAbove);
|
|
||||||
return nodeAbove;
|
return nodeAbove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +111,6 @@ export function getTargetDepth(mouseX: number, handleLeft: number, availableDept
|
|||||||
let curDepth = availableDepths.max - 1;
|
let curDepth = availableDepths.max - 1;
|
||||||
for (let x = availableDepths.min; x < availableDepths.max; x++) {
|
for (let x = availableDepths.min; x < availableDepths.max; x++) {
|
||||||
const breakpoint = handleLeft - x * 35;
|
const breakpoint = handleLeft - x * 35;
|
||||||
// console.log(`mouseX=${mouseX} breakpoint=${breakpoint} x=${x} curDepth=${curDepth}`);
|
|
||||||
if (mouseX > breakpoint) {
|
if (mouseX > breakpoint) {
|
||||||
return curDepth;
|
return curDepth;
|
||||||
}
|
}
|
||||||
@ -137,10 +132,6 @@ export function findNextDraggable(pos: { x: number; y: number }, outline: Outlin
|
|||||||
const target = dimensions ? getDimensions(dimensions.entry) : null;
|
const target = dimensions ? getDimensions(dimensions.entry) : null;
|
||||||
const children = dimensions ? getDimensions(dimensions.children) : null;
|
const children = dimensions ? getDimensions(dimensions.children) : null;
|
||||||
if (target) {
|
if (target) {
|
||||||
console.log(
|
|
||||||
`[${id}] ${pos.y} <= ${target.bottom} = ${pos.y <= target.bottom} / ${pos.y} >= ${target.top} = ${pos.y >=
|
|
||||||
target.top}`,
|
|
||||||
);
|
|
||||||
if (pos.y <= target.bottom && pos.y >= target.top) {
|
if (pos.y <= target.bottom && pos.y >= target.top) {
|
||||||
const middlePoint = target.top + target.height / 2;
|
const middlePoint = target.top + target.height / 2;
|
||||||
const position: ImpactPosition = pos.y > middlePoint ? 'after' : 'before';
|
const position: ImpactPosition = pos.y > middlePoint ? 'after' : 'before';
|
||||||
@ -152,10 +143,6 @@ export function findNextDraggable(pos: { x: number; y: number }, outline: Outlin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (children) {
|
if (children) {
|
||||||
console.log(
|
|
||||||
`[${id}] ${pos.y} <= ${children.bottom} = ${pos.y <= children.bottom} / ${pos.y} >= ${children.top} = ${pos.y >=
|
|
||||||
children.top}`,
|
|
||||||
);
|
|
||||||
if (pos.y <= children.bottom && pos.y >= children.top) {
|
if (pos.y <= children.bottom && pos.y >= children.top) {
|
||||||
const position: ImpactPosition = 'after';
|
const position: ImpactPosition = 'after';
|
||||||
return { found: false, node, position };
|
return { found: false, node, position };
|
||||||
@ -207,7 +194,7 @@ export function findNodeDepth(published: Map<string, string>, id: string) {
|
|||||||
throw new Error('node depth breaker was thrown');
|
throw new Error('node depth breaker was thrown');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unable to find nextID');
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { depth, ancestors };
|
return { depth, ancestors };
|
||||||
@ -359,3 +346,64 @@ export function getLastChildInBranch(outline: OutlineData, lastParentNode: Outli
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCaretPosition(editableDiv: any) {
|
||||||
|
/*
|
||||||
|
let caretPos = 0;
|
||||||
|
let sel: any = null;
|
||||||
|
let range: any = null;
|
||||||
|
if (window.getSelection) {
|
||||||
|
sel = window.getSelection();
|
||||||
|
if (sel && sel.rangeCount) {
|
||||||
|
range = sel.getRangeAt(0);
|
||||||
|
if (range.commonAncestorContainer.parentNode === editableDiv.current) {
|
||||||
|
caretPos = range.endOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return editableDiv.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRange(node: any, chars: any, range: any) {
|
||||||
|
if (!range) {
|
||||||
|
range = document.createRange();
|
||||||
|
range.selectNode(node);
|
||||||
|
range.setStart(node, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chars.count === 0) {
|
||||||
|
range.setEnd(node, chars.count);
|
||||||
|
} else if (node && chars.count > 0) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
if (node.textContent.length < chars.count) {
|
||||||
|
chars.count -= node.textContent.length;
|
||||||
|
} else {
|
||||||
|
range.setEnd(node, chars.count);
|
||||||
|
chars.count = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var lp = 0; lp < node.childNodes.length; lp++) {
|
||||||
|
range = createRange(node.childNodes[lp], chars, range);
|
||||||
|
|
||||||
|
if (chars.count === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCurrentCursorPosition(element: any, chars: any) {
|
||||||
|
if (chars >= 0) {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = createRange(element, { count: chars }, false);
|
||||||
|
if (range && selection) {
|
||||||
|
range.collapse(false);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
4
frontend/src/taskcafe.d.ts
vendored
4
frontend/src/taskcafe.d.ts
vendored
@ -206,10 +206,14 @@ type ImpactAction = {
|
|||||||
type ItemElement = {
|
type ItemElement = {
|
||||||
id: string;
|
id: string;
|
||||||
parent: null | string;
|
parent: null | string;
|
||||||
|
text: string;
|
||||||
|
focus: null | { caret: number | null };
|
||||||
|
zooming?: { x: number; y: number };
|
||||||
position: number;
|
position: number;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
children?: Array<ItemElement>;
|
children?: Array<ItemElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NodeDimensions = {
|
type NodeDimensions = {
|
||||||
entry: React.RefObject<HTMLElement>;
|
entry: React.RefObject<HTMLElement>;
|
||||||
children: React.RefObject<HTMLElement> | null;
|
children: React.RefObject<HTMLElement> | null;
|
||||||
|
@ -2934,6 +2934,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
|
||||||
integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
|
integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
|
||||||
|
|
||||||
|
"@types/marked@^1.2.2":
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.2.2.tgz#1f858a0e690247ecf3b2eef576f98f86e8d960d4"
|
||||||
|
integrity sha512-wLfw1hnuuDYrFz97IzJja0pdVsC0oedtS4QsKH1/inyW9qkLQbXgMUqEQT0MVtUBx3twjWeInUfjQbhBVLECXw==
|
||||||
|
|
||||||
"@types/mdast@^3.0.0":
|
"@types/mdast@^3.0.0":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
|
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
|
||||||
@ -3088,6 +3093,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-window@^1.8.2":
|
||||||
|
version "1.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.2.tgz#a5a6b2762ce73ffaab7911ee1397cf645f2459fe"
|
||||||
|
integrity sha512-gP1xam68Wc4ZTAee++zx6pTdDAH08rAkQrWm4B4F/y6hhmlT9Mgx2q8lTCXnrPHXsr15XjRN9+K2DLKcz44qEQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*":
|
"@types/react@*":
|
||||||
version "17.0.0"
|
version "17.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8"
|
||||||
@ -11023,6 +11035,11 @@ markdown-to-jsx@^6.11.4:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
unquote "^1.1.0"
|
unquote "^1.1.0"
|
||||||
|
|
||||||
|
marked@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.0.tgz#9662bbcb77ebbded0662a7be66ff929a8611cee5"
|
||||||
|
integrity sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==
|
||||||
|
|
||||||
material-colors@^1.2.1:
|
material-colors@^1.2.1:
|
||||||
version "1.2.6"
|
version "1.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
|
||||||
@ -11113,7 +11130,7 @@ mem@^4.0.0:
|
|||||||
mimic-fn "^2.0.0"
|
mimic-fn "^2.0.0"
|
||||||
p-is-promise "^2.0.0"
|
p-is-promise "^2.0.0"
|
||||||
|
|
||||||
memoize-one@^5.0.0, memoize-one@^5.1.1:
|
"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0, memoize-one@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||||
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||||
@ -14145,6 +14162,21 @@ react-transition-group@^4.3.0, react-transition-group@^4.4.1:
|
|||||||
loose-envify "^1.4.0"
|
loose-envify "^1.4.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
|
react-visibility-sensor@^5.1.1:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-visibility-sensor/-/react-visibility-sensor-5.1.1.tgz#5238380960d3a0b2be0b7faddff38541e337f5a9"
|
||||||
|
integrity sha512-cTUHqIK+zDYpeK19rzW6zF9YfT4486TIgizZW53wEZ+/GPBbK7cNS0EHyJVyHYacwFEvvHLEKfgJndbemWhB/w==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
|
react-window@^1.8.6:
|
||||||
|
version "1.8.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112"
|
||||||
|
integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
memoize-one ">=3.1.1 <6"
|
||||||
|
|
||||||
react@^16.12.0, react@^16.8.3:
|
react@^16.12.0, react@^16.8.3:
|
||||||
version "16.14.0"
|
version "16.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
||||||
|
Loading…
Reference in New Issue
Block a user