From 2c4fa3bfd7b967d4e94f1d5df6f2608b59e8e7a4 Mon Sep 17 00:00:00 2001 From: Quorafind Date: Thu, 15 Sep 2022 23:32:06 +0800 Subject: [PATCH 1/2] Update Obsidian Leaf view --- src/component/CompleteHiddenBtn.tsx | 1 - src/component/ResetBtn.tsx | 1 - src/component/SearchBox.tsx | 5 +- src/component/tree/TreeChildren.tsx | 26 ++++ src/component/tree/TreeNode.tsx | 209 ++++++++++++---------------- src/outlineTree.tsx | 100 +++++++++++++ src/outlinerViewApp.tsx | 35 ----- src/outlinerViewIndex.ts | 4 +- src/store/baseStore.ts | 35 ++++- src/types/tree.d.ts | 6 +- src/utils/commons/OnlyWhen.tsx | 16 +++ src/utils/cursor.ts | 2 +- src/utils/fakeTree.ts | 0 src/utils/tree.ts | 139 ++++++++++-------- src/utils/undoRing.ts | 74 ++++++++++ 15 files changed, 430 insertions(+), 223 deletions(-) create mode 100644 src/outlineTree.tsx delete mode 100644 src/outlinerViewApp.tsx create mode 100644 src/utils/commons/OnlyWhen.tsx create mode 100644 src/utils/fakeTree.ts create mode 100644 src/utils/undoRing.ts diff --git a/src/component/CompleteHiddenBtn.tsx b/src/component/CompleteHiddenBtn.tsx index 86d39d1..e532f7d 100644 --- a/src/component/CompleteHiddenBtn.tsx +++ b/src/component/CompleteHiddenBtn.tsx @@ -10,7 +10,6 @@ const CompleteHiddenBtn: React.FC = () => { const handleClick = () => { changeHiddenComplete(); - renderAll(); }; return ( diff --git a/src/component/ResetBtn.tsx b/src/component/ResetBtn.tsx index 925a325..920cb52 100644 --- a/src/component/ResetBtn.tsx +++ b/src/component/ResetBtn.tsx @@ -10,7 +10,6 @@ const ResetBtn: React.FC = () => { const handleClick = () => { setGlobalTree(TreeNS.makeDefaultTree()); - renderAll(); }; return ( diff --git a/src/component/SearchBox.tsx b/src/component/SearchBox.tsx index 46de8b8..0ca6713 100644 --- a/src/component/SearchBox.tsx +++ b/src/component/SearchBox.tsx @@ -14,6 +14,7 @@ const SearchBox: React.FC = () => { const setGlobalTree = useBaseStore((state) => state.setGlobalTree); const setGlobalTreeBak = useBaseStore((state) => state.setGlobalTreeBak); const setSelected = useBaseStore((state) => state.setSelected); + const setGlobalRenderAllNoUndo = useBaseStore((state) => state.setGlobalRenderAllNoUndo); const handleChange = (event: React.FormEvent) => { if (value?.length > 0) { @@ -23,7 +24,7 @@ const SearchBox: React.FC = () => { if (event.currentTarget.value.length === 0) { setGlobalTree(globalTreeBak); setGlobalTreeBak(null); - renderAllNoUndo(); + setGlobalRenderAllNoUndo(); return; } if (!globalTreeBak) { @@ -32,7 +33,7 @@ const SearchBox: React.FC = () => { } else { setGlobalTree(TreeNS.search(globalTree, event.currentTarget.value)); } - renderAllNoUndo(); + setGlobalRenderAllNoUndo(); }; const handleFocus = () => { diff --git a/src/component/tree/TreeChildren.tsx b/src/component/tree/TreeChildren.tsx index e69de29..e55b139 100644 --- a/src/component/tree/TreeChildren.tsx +++ b/src/component/tree/TreeChildren.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import TreeNode from "./TreeNode"; +import { Tree } from "../../types/tree"; + +interface Props { + style?: React.CSSProperties; + childNodes: Tree[]; +} + +const TreeChildren: React.FC = (props) => { + const { childNodes, style } = props; + let childNodeJSX; + if (childNodes != null) { + childNodeJSX = childNodes.map((node) => { + return ( +
  • + +
  • + ); + }); + } + + return (
      { childNodeJSX }
    ); +}; + +export default TreeChildren; diff --git a/src/component/tree/TreeNode.tsx b/src/component/tree/TreeNode.tsx index 1646895..264a6cb 100644 --- a/src/component/tree/TreeNode.tsx +++ b/src/component/tree/TreeNode.tsx @@ -4,13 +4,16 @@ import { useEffect } from "react"; import { useBaseStore } from "../../store/baseStore"; import { TreeNS } from "../../utils/tree"; import { Cursor } from "../../utils/cursor"; +import TreeChildren from "./TreeChildren"; +import opml from "opml-generator"; interface Props { + topBullet: boolean; node: Tree; } -const ResetBtn: React.FC = (props) => { - const { node } = props; +const TreeNode: React.FC = (props) => { + const { topBullet, node } = props; const inputRef = React.useRef(null); const [mouseOver, setMouseOver] = React.useState(false); @@ -23,10 +26,15 @@ const ResetBtn: React.FC = (props) => { const globalTree = useBaseStore((state) => state.globalTree); const globalTreeBak = useBaseStore((state) => state.globalTreeBak); const globalSkipFocus = useBaseStore((state) => state.globalSkipFocus); - const globalDiffUncommitted = useBaseStore((state) => state.globalDiffUncommitted); + const globalCompletedHidden = useBaseStore((state) => state.globalCompletedHidden); + const globalUndoRing = useBaseStore((state) => state.globalUndoRing); + const setGloablSkipFocus = useBaseStore((state) => state.setGloablSkipFocus); const setGlobalDiffUncommitted = useBaseStore((state) => state.setGlobalDiffUncommitted); const setGlobalTree = useBaseStore((state) => state.setGlobalTree); + const setSelected = useBaseStore((state) => state.setSelected); + const setCaretLoc = useBaseStore((state) => state.setCaretLoc); + const setGlobalRenderAllNoUndo = useBaseStore((state) => state.setGlobalRenderAllNoUndo); useEffect(() => { if (node.uuid === globalTree.selected) { @@ -59,40 +67,24 @@ const ResetBtn: React.FC = (props) => { setClassName('dot togglable dot-collapsed'); } } - - var children = ''; - if (this.props.topBullet || !this.props.node.collapsed) { - children = ( - - ); - } - - if ( - this.props.node.completed && - globalCompletedHidden && - !this.props.topBullet - ) { - return false; - } }; - }, [node]); + }, []); useEffect(() => { return () => { // Content ClassName if (topBullet) { - setContentClassName( 'editable topBullet'); + setContentClassName('editable topBullet'); } if (node.title == 'special_root_title') { - setContentClassName( 'editable topBullet display-none'); + setContentClassName('editable topBullet display-none'); } if (node.completed) { - setContentClassName( 'editable topBullet display-none completed'); + setContentClassName('editable topBullet display-none completed'); } }; }, [topBullet, node]); - useEffect(() => { return () => { if (mouseOver) { @@ -108,12 +100,11 @@ const ResetBtn: React.FC = (props) => { const html = inputRef.current.textContent; if (html !== htmlContent) { setGlobalDiffUncommitted(true); - var currentNode = TreeNS.findFromUUID(globalTree, node.uuid); + const currentNode = TreeNS.findFromUUID(globalTree, node.uuid); currentNode.title = event.target.textContent; - globalTree.caretLoc = Cursor.getCaretCharacterOffsetWithin( + setCaretLoc(Cursor.getCaretCharacterOffsetWithin( inputRef.current - ); - renderAll(); + )); } else { console.assert( false, @@ -121,22 +112,22 @@ const ResetBtn: React.FC = (props) => { ); } setHtmlContent(html); - }, + } const handleClick = (event) => { if (globalSkipFocus) { return; } - var currentNode = TreeNS.findFromUUID(globalTree, node.uuid); - globalTree.selected = currentNode.uuid; + const currentNode = TreeNS.findFromUUID(globalTree, node.uuid); + setSelected(currentNode.uuid); if (event.type === 'focus') { // clicking on the div, not the actual text. Also always fired when switching focus - globalTree.caretLoc = currentNode.title.length; + setCaretLoc(currentNode.title.length); } else { // clicking on the text directly - globalTree.caretLoc = Cursor.getCaretCharacterOffsetWithin( + setCaretLoc(Cursor.getCaretCharacterOffsetWithin( inputRef.current - ); + )); } } @@ -159,115 +150,99 @@ const ResetBtn: React.FC = (props) => { }; if (e.code === KEYS.LEFT) { if (e.ctrlKey) { - TreeNS.zoomOutOne(globalTree); - renderAll(); + setGlobalTree(TreeNS.zoomOutOne(globalTree)); e.preventDefault(); } else { const newCaretLoc = Cursor.getCaretCharacterOffsetWithin( inputRef.current ); if (newCaretLoc === 0) { - TreeNS.selectPreviousNode(globalTree); - var selected = TreeNS.findSelected(globalTree); // TODO could do this faster than two searches - globalTree.caretLoc = selected.title.length; - renderAll(); + setGlobalTree(TreeNS.selectPreviousNode(globalTree)); + const selected = TreeNS.findSelected(globalTree); // TODO could do this faster than two searches + setCaretLoc(selected.title.length); e.preventDefault(); } else { - globalTree.caretLoc = newCaretLoc - 1; + setCaretLoc(newCaretLoc - 1); } } } else if (e.code === KEYS.END && e.ctrlKey) { - TreeNS.selectLastNode(globalTree); - renderAll(); + setGlobalTree(TreeNS.selectLastNode(globalTree)); e.preventDefault(); } else if (e.code === KEYS.HOME && e.ctrlKey) { - TreeNS.selectFirstNode(globalTree); - renderAll(); + setGlobalTree(TreeNS.selectFirstNode(globalTree)); e.preventDefault(); } else if (e.code === KEYS.UP) { if (e.shiftKey && e.ctrlKey) { - TreeNS.shiftUp(globalTree); + setGlobalTree(TreeNS.shiftUp(globalTree)); } else { - TreeNS.selectPreviousNode(globalTree); - globalTree.caretLoc = 0; + setGlobalTree(TreeNS.selectPreviousNode(globalTree)); + setCaretLoc(0); } - renderAll(); e.preventDefault(); } else if (e.code === KEYS.RIGHT) { if (e.ctrlKey) { - var currentNode = TreeNS.findFromUUID(globalTree, this.props.node.uuid); - TreeNS.zoom(currentNode); - renderAll(); + const currentNode = TreeNS.findFromUUID(globalTree, node.uuid); + setGlobalTree(TreeNS.zoom(currentNode)); e.preventDefault(); } else { - let newCaretLoc = Cursor.getCaretCharacterOffsetWithin( + const newCaretLoc = Cursor.getCaretCharacterOffsetWithin( inputRef.current ); if (newCaretLoc === inputRef.current.textContent.length) { - TreeNS.selectNextNode(globalTree); - globalTree.caretLoc = 0; - renderAll(); + setGlobalTree(TreeNS.selectNextNode(globalTree)); + setCaretLoc(0); e.preventDefault(); } else { - globalTree.caretLoc = newCaretLoc + 1; + setCaretLoc(newCaretLoc + 1); } } } else if (e.code === KEYS.DOWN) { if (e.shiftKey && e.ctrlKey) { - TreeNS.shiftDown(globalTree); + setGlobalTree(TreeNS.shiftDown(globalTree)); } else { - TreeNS.selectNextNode(globalTree); - globalTree.caretLoc = 0; + setGlobalTree(TreeNS.selectNextNode(globalTree)); + setCaretLoc(0); } - renderAll(); e.preventDefault(); } else if (e.code === KEYS.ENTER && e.ctrlKey) { - TreeNS.completeCurrent(globalTree); - renderAll(); + setGlobalTree(TreeNS.completeCurrent(globalTree)); e.preventDefault(); } else if (e.code === KEYS.ENTER) { - var caretLoc = Cursor.getCaretCharacterOffsetWithin( - this.refs.input.getDOMNode() - ); - globalTree.caretLoc = caretLoc; - console.log('loc', caretLoc); - TreeNS.newLineAtCursor(globalTree); - renderAll(); + setCaretLoc(Cursor.getCaretCharacterOffsetWithin( + inputRef.current + )); + setGlobalTree(TreeNS.newLineAtCursor(globalTree)); e.preventDefault(); } else if (e.code === KEYS.BACKSPACE) { if (e.ctrlKey && e.shiftKey) { - TreeNS.deleteSelected(globalTree); - renderAll(); + setGlobalTree(TreeNS.deleteSelected(globalTree)); e.preventDefault(); } else { globalTree.caretLoc = Cursor.getCaretCharacterOffsetWithin( inputRef.current ); if (globalTree.caretLoc === 0) { - TreeNS.backspaceAtBeginning(globalTree); - renderAll(); + setGlobalTree(TreeNS.backspaceAtBeginning(globalTree)); e.preventDefault(); } } } else if (e.code === KEYS.TAB) { if (e.shiftKey) { - TreeNS.unindent(globalTree); + setGlobalTree(TreeNS.unindent(globalTree)); } else { - TreeNS.indent(globalTree); + setGlobalTree(TreeNS.indent(globalTree)); } - renderAll(); e.preventDefault(); } else if (e.code === KEYS.SPACE && e.ctrlKey) { - TreeNS.collapseCurrent(globalTree); - renderAll(); + setGlobalTree(TreeNS.collapseCurrent(globalTree)); e.preventDefault(); } else if (e.code === KEYS.Z && (e.ctrlKey || e.metaKey)) { setGlobalTree(TreeNS.clone(globalUndoRing.undo())); - renderAllNoUndo(); + setGlobalRenderAllNoUndo(); e.preventDefault(); } else if (e.code === KEYS.Y && (e.ctrlKey || e.metaKey)) { setGlobalTree(TreeNS.clone(globalUndoRing.redo())); - renderAllNoUndo(); + setGlobalRenderAllNoUndo(); e.preventDefault(); } else if (e.code === KEYS.S && e.ctrlKey) { window.prompt( @@ -276,8 +251,9 @@ const ResetBtn: React.FC = (props) => { ); e.preventDefault(); } else if (e.code === KEYS.C && e.ctrlKey) { - let currentNode = TreeNS.findFromUUID(globalTree, node.uuid); - var outlines = TreeNS.toOutline(currentNode); + const currentNode = TreeNS.findFromUUID(globalTree, node.uuid); + const outlines = TreeNS.toOutline(currentNode); + console.log(opml({}, [outlines])); e.preventDefault(); } else { // console.log(e.keyCode); @@ -285,10 +261,9 @@ const ResetBtn: React.FC = (props) => { } const toggle = () => { - var currentNode = TreeNS.findFromUUID(globalTree, node.uuid); + const currentNode = TreeNS.findFromUUID(globalTree, node.uuid); globalTree.selected = currentNode.uuid; - TreeNS.collapseCurrent(globalTree); - renderAll(); + setGlobalTree(TreeNS.collapseCurrent(globalTree)); } const handleMouseOver = () => { @@ -300,45 +275,45 @@ const ResetBtn: React.FC = (props) => { } const zoom = () => { - const node = TreeNS.findFromUUID(globalTree, node.uuid); - TreeNS.zoom(node); - globalTree.selected = node.uuid; - renderAll(); + const nodeTemp = TreeNS.findFromUUID(globalTree, node.uuid); + setGlobalTree(TreeNS.zoom(nodeTemp)); + setSelected(nodeTemp.uuid); } return ( -
    -
    - { - (!topBullet) ? - - {String.fromCharCode(8226)} - : '' - } -
    -
    - {plus ? '+' : '-'} + (node.completed && globalCompletedHidden && !topBullet) ? null : +
    +
    + { + (!topBullet) ? + + { String.fromCharCode(8226) } + : '' + } +
    +
    + { plus ? '+' : '-' } +
    +
    -
    + { (topBullet || !node.collapsed) ? : '' }
    - {(topBullet || !node.collapsed) ? : ''} -
    ); }; -export default ResetBtn; +export default TreeNode; diff --git a/src/outlineTree.tsx b/src/outlineTree.tsx new file mode 100644 index 0000000..8fd31a5 --- /dev/null +++ b/src/outlineTree.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import SearchBox from './component/SearchBox'; +import ResetBtn from './component/ResetBtn'; +import Breadcrumb from './component/Breadcrumb'; +import DataSavedBtn from './component/DataSavedBtn'; +import CompleteHiddenBtn from './component/CompleteHiddenBtn'; +import TreeNode from "./component/tree/TreeNode"; +import { useBaseStore } from "./store/baseStore"; +import { useEffect } from "react"; +import UndoRing from "./utils/undoRing"; +import { TreeNS } from "./utils/tree"; +import Only from './utils/commons/OnlyWhen'; + +interface Props { +} + +const OutlineTree: React.FC = (): JSX.Element => { + const globalTree = useBaseStore((state) => state.globalTree); + const globalSkipNextUndo = useBaseStore((state) => state.globalSkipNextUndo); + const globalUndoRing = useBaseStore((state) => state.globalUndoRing); + const globalRenderAllNoUndo = useBaseStore((state) => state.globalRenderAllNoUndo); + const globalDiffUncommitted = useBaseStore((state) => state.globalDiffUncommitted); + + const changeHiddenComplete = useBaseStore((state) => state.changeHiddenComplete); + const setGlobalDiffUncommitted = useBaseStore((state) => state.setGlobalDiffUncommitted); + const setGlobalSkipNextUndo = useBaseStore((state) => state.setGlobalSkipNextUndo); + const setGlobalTree = useBaseStore((state) => state.setGlobalTree); + const setGlobalParseTree = useBaseStore((state) => state.setGlobalParseTree); + const setDiff = useBaseStore((state) => state.setDiff); + const setGlobalUndoRing = useBaseStore((state) => state.setGlobalUndoRing); + + useEffect(() => { + console.log("Hellow"); + if (globalTree) return; + + const tempTree = TreeNS.makeDefaultTree(); + setGlobalTree(tempTree); + changeHiddenComplete(TreeNS.isCompletedHidden(tempTree)); + setGlobalParseTree(tempTree); + const newTree = TreeNS.clone(tempTree); + setGlobalUndoRing(new UndoRing(newTree, 50)); // TODO un hardcode + }, []); + + useEffect(() => { + if (globalDiffUncommitted) { + setGlobalDiffUncommitted(false); + const newTree = TreeNS.clone(globalTree); + globalUndoRing.addPending(newTree); + globalUndoRing.commit(); + } + }, [globalDiffUncommitted]); + + + useEffect(() => { + if (!globalTree) return; + const tempGlobalTree = globalTree; + tempGlobalTree.diff['run_full_diff'] = true; + setDiff(tempGlobalTree.diff); + setGlobalSkipNextUndo(true); + }, [globalRenderAllNoUndo]); + + + useEffect(() => { + if (!globalTree) return; + + if (globalSkipNextUndo) { + setGlobalSkipNextUndo(false); + } else if (Object.keys(globalTree.diff).length > 0) { + setGlobalDiffUncommitted(true); + } + setDiff({}); + }, [globalSkipNextUndo]); + + + return ( + +
    +
    + Bearings + +
    + + Import + + +
    + { ' ' } +
    +
    +
    + +
    + +
    +
    +
    + ); +}; + +export default OutlineTree; diff --git a/src/outlinerViewApp.tsx b/src/outlinerViewApp.tsx deleted file mode 100644 index d863ebe..0000000 --- a/src/outlinerViewApp.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from 'react'; -import SearchBox from './component/SearchBox'; -import ResetBtn from './component/ResetBtn'; -import Breadcrumb from './component/Breadcrumb'; -import DataSavedBtn from './component/DataSavedBtn'; -import CompleteHiddenBtn from './component/CompleteHiddenBtn'; - -interface Props { -} - -const OutlinerViewApp: React.FC = (props: Props): JSX.Element => { - return ( -
    -
    - Bearings - -
    - - Import - - -
    - { ' ' } -
    -
    -
    - -
    - -
    -
    - ); -}; - -export default OutlinerViewApp; diff --git a/src/outlinerViewIndex.ts b/src/outlinerViewIndex.ts index 743bfb2..3e840e6 100644 --- a/src/outlinerViewIndex.ts +++ b/src/outlinerViewIndex.ts @@ -2,7 +2,7 @@ import { ItemView, Plugin, WorkspaceLeaf } from "obsidian"; import React from "react"; import ReactDOM from "react-dom"; -import DiceRoller from "./component/DicerRoller"; +import OutlineTree from "./outlineTree"; const VIEW_TYPE = "react-view"; @@ -22,7 +22,7 @@ class MyReactView extends ItemView { } async onOpen(): Promise { - this.reactComponent = React.createElement(DiceRoller); + this.reactComponent = React.createElement(OutlineTree); // eslint-disable-next-line @typescript-eslint/no-explicit-any ReactDOM.render(this.reactComponent, (this as any).contentEl); diff --git a/src/store/baseStore.ts b/src/store/baseStore.ts index 231fd59..97588f1 100644 --- a/src/store/baseStore.ts +++ b/src/store/baseStore.ts @@ -1,5 +1,6 @@ import create from 'zustand'; -import { Tree } from '../types/tree'; +import { DiffMap, Tree } from '../types/tree'; +import UndoRing from "../utils/undoRing"; interface BaseState { globalTree: Tree | null; @@ -10,12 +11,20 @@ interface BaseState { globalCompletedHidden: boolean; globalDiffUncommitted: boolean; globalSkipNextUndo: boolean; - changeHiddenComplete: () => void; + globalRenderAllNoUndo: boolean; + globalUndoRing: UndoRing | null; + changeHiddenComplete: (hidden?: boolean) => void; setGlobalTree: (tree: Tree) => void; setGlobalTreeBak: (tree: Tree) => void; + setGlobalParseTree: (tree: Tree) => void; setGloablSkipFocus: (skip: boolean) => void; setGlobalDiffUncommitted: (diff: boolean) => void; + setGlobalSkipNextUndo: (skip: boolean) => void; + setGlobalUndoRing: (undoRing: UndoRing | null) => void; setSelected: (uuid: string | null) => void; + setDiff: (diff: DiffMap) => void; + setCaretLoc: (caretLoc: number | null) => void; + setGlobalRenderAllNoUndo: () => void; } export const useBaseStore = create((set) => ({ @@ -27,11 +36,16 @@ export const useBaseStore = create((set) => ({ globalCompletedHidden: false, globalDiffUncommitted: false, globalSkipNextUndo: false, - changeHiddenComplete: () => set((state) => ({ globalCompletedHidden: !state.globalCompletedHidden })), + globalRenderAllNoUndo: false, + globalUndoRing: null, + changeHiddenComplete: (hidden?: boolean) => set((state) => ({ globalCompletedHidden: hidden ?? !state.globalCompletedHidden })), setGlobalTree: (tree: Tree | null) => set({ globalTree: tree }), setGlobalTreeBak: (tree: Tree | null) => set({ globalTreeBak: tree }), + setGlobalParseTree: (tree: Tree | null) => set({ globalParseTree: tree }), setGloablSkipFocus: (skip: boolean) => set({ globalSkipFocus: skip }), setGlobalDiffUncommitted: (diff: boolean) => set({ globalDiffUncommitted: diff }), + setGlobalSkipNextUndo: (skip: boolean) => set({ globalSkipNextUndo: skip }), + setGlobalUndoRing: (ring: UndoRing | null) => set({ globalUndoRing: ring }), setSelected: (selected: string | null) => set((state) => ({ globalTree: { @@ -39,4 +53,19 @@ export const useBaseStore = create((set) => ({ selected: selected ? selected : null, }, })), + setDiff: (diff: DiffMap | null) => + set((state) => ({ + globalTree: { + ...state.globalTree, + diff: diff, + }, + })), + setCaretLoc: (caretLoc: number | null) => + set((state) => ({ + globalTree: { + ...state.globalTree, + caretLoc: caretLoc, + }, + })), + setGlobalRenderAllNoUndo: () => set((state) => ({ globalRenderAllNoUndo: !state.globalRenderAllNoUndo })), })); diff --git a/src/types/tree.d.ts b/src/types/tree.d.ts index 30f8ce8..c69d597 100644 --- a/src/types/tree.d.ts +++ b/src/types/tree.d.ts @@ -1,8 +1,8 @@ export interface Tree { - title: string; + title?: string; childNodes?: Tree[] | undefined; uuid?: string; - uuidMap?: UUIDMap[]; + uuidMap?: UUIDMap; parent?: Tree | null; selected?: string | null; zoom?: Tree; @@ -25,5 +25,5 @@ export interface UUIDMap { } export interface DiffMap { - [key: string]: Tree; + [key: string]: boolean; } diff --git a/src/utils/commons/OnlyWhen.tsx b/src/utils/commons/OnlyWhen.tsx new file mode 100644 index 0000000..0e4a2a2 --- /dev/null +++ b/src/utils/commons/OnlyWhen.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { ReactNode } from 'react'; + +interface OnlyWhenProps { + children: ReactNode; + when: boolean; +} + +const OnlyWhen: React.FC = (props: OnlyWhenProps) => { + const { children, when } = props; + return when ? <>{ children } : null; +}; + +const Only = OnlyWhen; + +export default Only; diff --git a/src/utils/cursor.ts b/src/utils/cursor.ts index 8e19a7b..1c67543 100644 --- a/src/utils/cursor.ts +++ b/src/utils/cursor.ts @@ -33,7 +33,7 @@ export namespace Cursor { }; // For contentEditable - export const getCaretCharacterOffsetWithin = function (element) { + export const getCaretCharacterOffsetWithin = function (element): number { let caretOffset = 0; const doc = element.ownerDocument || element.document; const win = doc.defaultView || doc.parentWindow; diff --git a/src/utils/fakeTree.ts b/src/utils/fakeTree.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/tree.ts b/src/utils/tree.ts index 02e1d06..4723a96 100644 --- a/src/utils/tree.ts +++ b/src/utils/tree.ts @@ -25,21 +25,23 @@ export namespace TreeNS { // return ret; // } - export const addDiff = function (node: Tree) { + export const addDiff = function (node: Tree): Tree[] { // TODO, speed this up too... const chain = getParentChain(node); const root = getRoot(node); - chain.forEach(function (n) { + chain.forEach((n) => { root.diff[n.uuid] = true; }); + return chain; }; - export const addAllDiff = function (node: Tree) { + export const addAllDiff = function (node: Tree): Tree { const root = getRoot(node); root.diff['run_full_diff'] = true; + return root; }; - export const getParentChain = function (node: Tree) { + export const getParentChain = function (node: Tree): Tree[] { const ret = []; while (node.title !== 'special_root_title') { ret.push(node); @@ -49,7 +51,7 @@ export namespace TreeNS { return ret; }; - export const selectNextNode = function (tree: Tree) { + export const selectNextNode = function (tree: Tree): Tree { const selected = findSelected(tree); const root = getRoot(tree); const next = findNextNode(selected); @@ -57,9 +59,10 @@ export namespace TreeNS { root.selected = next.uuid; addDiff(next); } + return root; }; - export const selectPreviousNode = function (tree: Tree) { + export const selectPreviousNode = function (tree: Tree): Tree { const selected = findSelected(tree); const root = getRoot(tree); const previous = findPreviousNode(selected); @@ -67,20 +70,23 @@ export namespace TreeNS { root.selected = previous.uuid; addDiff(previous); } + return root; }; // TODO shouldn't this be the last node of the current zoom? - export const selectLastNode = function (tree: Tree) { + export const selectLastNode = function (tree: Tree): Tree { const root = getRoot(tree); const last = findDeepest(root.zoom.childNodes[root.zoom.childNodes.length - 1]); root.selected = last.uuid; root.caretLoc = last.title.length; + return root; }; - export const selectFirstNode = function (tree: Tree) { + export const selectFirstNode = function (tree: Tree): Tree { const root = getRoot(tree); root.selected = root.zoom.uuid; root.caretLoc = 0; + return root; }; export const appendSibling = function (tree, title) { @@ -97,7 +103,7 @@ export namespace TreeNS { return ret; }; - export const newChildAtCursor = function (selected: Tree) { + export const newChildAtCursor = function (selected: Tree): Tree { const ret = makeNode({ title: '', parent: selected }); addDiff(selected.parent); addDiff(selected); @@ -111,13 +117,14 @@ export namespace TreeNS { } root.selected = ret.uuid; root.caretLoc = 0; + return root; }; - export const newLineAtCursor = function (tree: Tree) { + export const newLineAtCursor = function (tree: Tree): Tree { const selected = findSelected(tree); const root = getRoot(tree); - const textStart = selected.title.substr(0, root.caretLoc); - const textRest = selected.title.substr(root.caretLoc); + const textStart = selected.title.slice(0, root.caretLoc); + const textRest = selected.title.slice(root.caretLoc); if (selected === root.zoom || (textRest.length === 0 && selected.childNodes.length > 0 && !selected.collapsed)) { newChildAtCursor(selected); } else { @@ -138,18 +145,21 @@ export namespace TreeNS { } root.caretLoc = 0; } + return root; }; - export const addUUIDPointer = function (tree: Tree) { + export const addUUIDPointer = function (tree: Tree): Tree { const root = getRoot(tree); root.uuidMap[tree.uuid] = tree; + return root; }; - export const addUUIDPointers = function (tree: Tree) { - addUUIDPointer(tree); - tree.childNodes.map(function (child) { + export const addUUIDPointers = (tree: Tree): Tree => { + const root = addUUIDPointer(tree); + tree.childNodes.map((child) => { addUUIDPointers(child); }); + return root; }; export const findFromUUID = function (tree: Tree, uuid: string) { @@ -169,6 +179,7 @@ export namespace TreeNS { export const makeNode = function (args, options?: Option): any { const ret = {}; + console.log(options); setIfReal(ret, args, 'title'); setIfReal(ret, args, 'childNodes', []); setIfReal(ret, args, 'parent'); @@ -239,8 +250,8 @@ export namespace TreeNS { return me; }; - export const indent = function (tree: Tree) { - const selected = findSelected(tree); + export const indent = function (tree: Tree): Tree { + const selected: Tree = findSelected(tree); const childNum = findChildNum(selected); if (childNum == 0) { return; @@ -252,11 +263,12 @@ export namespace TreeNS { selected.parent = newParent; // TODO diff is oldParent + newParent + selected + children of selected addAllDiff(selected); + return getRoot(selected); }; - export const unindent = function (tree: Tree) { - const selected = findSelected(tree); - const root = getRoot(tree); + export const unindent = function (tree: Tree): Tree { + const selected: Tree = findSelected(tree); + const root: Tree = getRoot(tree); if (!selected.parent.parent) { return; } @@ -271,28 +283,30 @@ export namespace TreeNS { selected.parent = newParent; // TODO diff is oldParent + newParent + selected + children of selected addAllDiff(selected); + return root; }; - export const shiftUp = function (tree: Tree) { - const selected = findSelected(tree); - const childNum = findChildNum(selected); - const parent = selected.parent; + export const shiftUp = function (tree: Tree): Tree { + const selected: Tree = findSelected(tree); + const childNum: number = findChildNum(selected); + const parent: Tree = selected.parent; if (childNum == 0) { return; } if (parent.childNodes.length <= 1) { return; } - const tmp = parent.childNodes[childNum]; + const tmp: Tree = parent.childNodes[childNum]; parent.childNodes[childNum] = parent.childNodes[childNum - 1]; parent.childNodes[childNum - 1] = tmp; addDiff(parent); + return getRoot(selected); }; - export const shiftDown = function (tree: Tree) { - const selected = findSelected(tree); - const childNum = findChildNum(selected); - const parent = selected.parent; + export const shiftDown = function (tree: Tree): Tree { + const selected: Tree = findSelected(tree); + const childNum: number = findChildNum(selected); + const parent: Tree = selected.parent; if (childNum == parent.childNodes.length - 1) { return; } @@ -303,10 +317,11 @@ export namespace TreeNS { parent.childNodes[childNum] = parent.childNodes[childNum + 1]; parent.childNodes[childNum + 1] = tmp; addDiff(parent); + return getRoot(selected); }; - export const findChildNum = function (tree) { - let i; + export const findChildNum = function (tree): number { + let i: number; for (i = 0; i < tree.parent.childNodes.length; i++) { if (tree.parent.childNodes[i] == tree) { return i; @@ -315,14 +330,14 @@ export namespace TreeNS { console.assert(false); }; - export const getRoot = function (tree: Tree) { + export const getRoot = function (tree: Tree): Tree { if (tree.title === 'special_root_title') { return tree; } return getRoot(tree.parent); }; - export const getBreadcrumb = function (root: Tree) { + export const getBreadcrumb = function (root: Tree): string[] { if (root.zoom.title === 'special_root_title') { return []; } @@ -331,7 +346,7 @@ export namespace TreeNS { return ret; }; - export const getBreadcrumbInner = function (tree: Tree) { + export const getBreadcrumbInner = function (tree: Tree): string[] { if (tree.title === 'special_root_title') { return []; } @@ -340,7 +355,7 @@ export namespace TreeNS { return ret; }; - export const zoom = function (tree: Tree) { + export const zoom = function (tree: Tree): Tree { if (!tree) { console.log('cannot zoom that high!'); return; @@ -349,9 +364,10 @@ export namespace TreeNS { root.zoom = tree; root.zoomUUID = tree.uuid; addAllDiff(root); + return root; }; - export const zoomOutOne = function (tree: Tree) { + export const zoomOutOne = function (tree: Tree): Tree { const root = getRoot(tree); if (root.zoom) { if (root.zoom.parent) { @@ -364,9 +380,10 @@ export namespace TreeNS { console.assert(false, 'something wrong'); } addAllDiff(root); + return root; }; - export const deleteSelected = function (tree: Tree) { + export const deleteSelected = function (tree: Tree): Tree { // TODO think if this is the root.. const selected = findSelected(tree); let nextSelection = findPreviousNode(selected); @@ -392,9 +409,10 @@ export namespace TreeNS { root.caretLoc = nextSelection.title.length; addDiff(selected.parent); addDiff(nextSelection); + return root; }; - export const backspaceAtBeginning = function (tree: Tree) { + export const backspaceAtBeginning = function (tree: Tree): Tree { // TODO think if this is the root const selected = findSelected(tree); const root = getRoot(tree); @@ -423,9 +441,10 @@ export namespace TreeNS { } addDiff(selected.parent); addDiff(previous); + return root; }; - export const setChildNodes = function (tree: Tree, childNodes: Tree[]) { + export const setChildNodes = function (tree: Tree, childNodes: Tree[]): Tree { // TODO is there a way to stop anyone from explicitly setting childNodes? // We want that because if anyone ever sets childNodes, they should also set the parent // of the children @@ -434,9 +453,10 @@ export namespace TreeNS { for (let i = 0; i < childNodes.length; i++) { childNodes[i].parent = tree; } + return tree; }; - export const findDeepest = function (tree: Tree) { + export const findDeepest = function (tree: Tree): Tree { const completedHidden = isCompletedHidden(tree); if (tree.childNodes && !tree.collapsed) { for (let i = tree.childNodes.length - 1; i >= 0; i--) { @@ -448,7 +468,7 @@ export namespace TreeNS { return tree; }; - export const findSelected = function (node: Tree) { + export const findSelected = function (node: Tree): Tree { const root = getRoot(node); console.assert(root === node); if (!root.selected) { @@ -457,21 +477,22 @@ export namespace TreeNS { return root.uuidMap[root.selected]; }; - export const collapseCurrent = function (tree: Tree) { + export const collapseCurrent = function (tree: Tree): Tree { const selected = findSelected(tree); if (selected.childNodes && selected.childNodes.length > 0) { selected.collapsed = !selected.collapsed; } addAllDiff(selected); + return getRoot(selected); }; export const countVisibleChildren = function (tree: Tree) { - return tree.childNodes.filter(function (n) { + return tree.childNodes.filter((n) => { return !n.completed; }).length; }; - export const completeCurrent = function (tree: Tree) { + export const completeCurrent = function (tree: Tree): Tree { const selected = findSelected(tree); const root = getRoot(tree); if (root.zoom === selected) { @@ -501,9 +522,10 @@ export namespace TreeNS { setCompletedHidden(tree, backup); } addAllDiff(selected); + return root; }; - export const findPreviousNode = function (tree: Tree) { + export const findPreviousNode = function (tree: Tree): Tree { if (!tree || !tree.parent) { return null; } @@ -524,7 +546,7 @@ export namespace TreeNS { return tree.parent; }; - export const findNextNode = function (tree: Tree) { + export const findNextNode = function (tree: Tree): Tree { const root = getRoot(tree); const completedHidden = isCompletedHidden(tree); if (tree.childNodes && tree.childNodes.length > 0 && (!tree.collapsed || root.zoom === tree)) { @@ -537,7 +559,7 @@ export namespace TreeNS { return findNextNodeRec(tree, root.zoom); }; - export const findNextNodeRec = function (tree: Tree, zoom: Tree) { + export const findNextNodeRec = function (tree: Tree, zoom: Tree): Tree { if (!tree || !tree.parent) { return null; } @@ -553,7 +575,7 @@ export namespace TreeNS { return findNextNodeRec(tree.parent, zoom); }; - export const makeTree = function (nodes?: Tree[]) { + export const makeTree = function (nodes?: Tree[]): Tree { let ret: Tree = { title: 'special_root_title', parent: null, childNodes: nodes }; ret = clone(ret); ret.zoom = ret; @@ -592,7 +614,7 @@ export namespace TreeNS { if (indexer.length <= 1) { return tree; } - const parts = indexer.substr(1).split('-'); + const parts = indexer.slice(1).split('-'); for (let i = 0; i < parts.length; i++) { tree = tree.childNodes[parts[i]]; } @@ -614,7 +636,7 @@ export namespace TreeNS { return JSON.stringify(tree); }; - export const fromString = function (s) { + export const fromString = function (s): Tree { const obj = JSON.parse(s); const ret = clone(obj); if (!ret.zoomUUID) { @@ -625,14 +647,14 @@ export namespace TreeNS { return ret; }; - export const equals = function (one: Tree, two: Tree) { + export const equals = function (one: Tree, two: Tree): boolean { return toString(one) === toString(two); }; - export const toOutline = function (tree: Tree) { + export const toOutline = function (tree: Tree): any { const ret = { text: tree.title, - _children: tree.childNodes.map(function (node) { + _children: tree.childNodes.map((node) => { return toOutline(node); }), }; @@ -640,18 +662,19 @@ export namespace TreeNS { return ret; }; - export const setCompletedHidden = function (tree: Tree, isHidden: boolean) { + export const setCompletedHidden = function (tree: Tree, isHidden: boolean): Tree { const root = getRoot(tree); // TODO or assert (tree == root) root.completedHidden = isHidden; + return root; }; - export const isCompletedHidden = function (tree: Tree) { + export const isCompletedHidden = function (tree: Tree): boolean { const root = getRoot(tree); return root.completedHidden; }; - export const recSearch = function (tree: Tree, query: string) { + export const recSearch = function (tree: Tree, query: string): Tree | null { const newTree = { title: tree.title, childNodes: [] }; for (let i = 0; i < tree.childNodes.length; i++) { if (recSearch(tree.childNodes[i], query)) { @@ -683,7 +706,7 @@ export namespace TreeNS { if (obj[i + 1] instanceof Array) { ret.push({ title: obj[i], childNodes: yamlObjToTree(obj[i + 1]) }); i += 1; - } else if (typeof obj[i] === 'object' && obj[i].hasOwnProperty('title')) { + } else if (typeof obj[i] === 'object' && obj[i].hasOwn('title')) { ret.push(obj[i]); } else { ret.push({ title: obj[i] }); diff --git a/src/utils/undoRing.ts b/src/utils/undoRing.ts new file mode 100644 index 0000000..7e6d8ad --- /dev/null +++ b/src/utils/undoRing.ts @@ -0,0 +1,74 @@ +import { Tree } from "../types/tree"; + +export default class UndoRing { + length: number; + ring: Tree[]; + start: number; + end: number; + current: number; + pending: Tree | null; + + constructor(obj: Tree, length: number) { + this.length = length; + this.ring = []; + // TODO should I remove this? + for (let i = 0; i < this.length; i++) { + this.ring.push({ childNodes: [{ title: 'nothing' }] }); + } + this.ring[0] = obj; + this.start = 0; + this.end = 0; + this.current = 0; + this.pending = null; + } + + undo() { + if (this.pending) { + this.commit(); + } + if (this.current == this.end) { + return this.ring[this.current]; + } + this.current = (this.current + this.length - 1) % this.length; + return this.ring[this.current]; + } + + redo() { + if (this.pending) { + this.commit(); + } + if (this.current == this.start) { + return this.ring[this.current]; + } + this.current = (this.current + 1) % this.length; + return this.ring[this.current]; + } + + addPending(obj) { + this.pending = obj; + } + + commit() { + if (this.pending) { + this.start = this.current; + if (this.bufferFull()) { + this._pop(); + } + this.current = this.start = (this.start + 1) % this.length; + this.ring[this.start] = this.pending; + + this.pending = null; + } + } + + _pop() { + this.end = (this.end + 1) % this.length; + } + + bufferFull() { + return ( + this.end - this.start === 1 || + (this.end === 0 && this.start === this.length - 1) + ); + } +} From 8a3f3f443715baa3b3b7538fcbd8a8e84ca46623 Mon Sep 17 00:00:00 2001 From: Quorafind Date: Thu, 24 Nov 2022 00:37:06 +0800 Subject: [PATCH 2/2] chore: update readme --- README.md | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/README.md b/README.md index 52119cf..dab93ab 100644 --- a/README.md +++ b/README.md @@ -1,27 +1 @@ -# obsidian-react-starter - -Code From Original Template: [React Starter](https://github.com/obsidian-community/obsidian-react-starter) - -A starter template for creating an [Obsidian](https://obsidian.md/) plugin with [ReactJS](https://reactjs.org/). - -## Features - -This project comes preconfigured with [Typescript](https://www.typescriptlang.org/), [Vite](https://vitejs.dev/) And [Less](https://lesscss.org/). - -## Getting Started - -Click "use this template" to create your own fork of this repo. Make sure to reference [the official sample plugin](https://github.com/obsidianmd/obsidian-sample-plugin) for information about how to get started with the Obsidian API and how to submit your plugin to the Community Plugin Gallery. - -```bash -# for local development -yarn install -yarn dev - -# for a production bundle -yarn install -yarn build -``` - -## Stats - -The production output of this sample plugin is **~50 KB**. +Not Yet Usable