init()

in packages/engine/src/inner-plugins/builtin-hotkey.ts [226:546]


    init() {
      const { hotkey, project, logger, canvas } = ctx;
      const { clipboard } = canvas;
      // hotkey binding
      hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => {
        logger.info(`action ${action} is triggered`);

        if (canvas.isInLiveEditing) {
          return;
        }
        // TODO: use focus-tracker
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();

        const sel = doc.selection;
        const topItems = sel.getTopNodes();
        // TODO: check can remove
        topItems.forEach((node) => {
          if (node?.canPerformAction('remove')) {
            node && doc.removeNode(node);
          }
        });
        sel.clear();
      });

      hotkey.bind('escape', (e: KeyboardEvent, action) => {
        logger.info(`action ${action} is triggered`);

        if (canvas.isInLiveEditing) {
          return;
        }
        const sel = project.currentDocument?.selection;
        if (isFormEvent(e) || !sel) {
          return;
        }
        e.preventDefault();

        sel.clear();
        // currentFocus.esc();
      });

      // command + c copy  command + x cut
      hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        const anchorValue = document.getSelection()?.anchorNode?.nodeValue;
        if (anchorValue && typeof anchorValue === 'string') {
          return;
        }
        e.preventDefault();

        let selected = doc.selection.getTopNodes(true);
        selected = selected.filter((node) => {
          return node?.canPerformAction('copy');
        });
        if (!selected || selected.length < 1) {
          return;
        }

        const componentsMap = {};
        const componentsTree = selected.map((item) => item?.exportSchema(IPublicEnumTransformStage.Clone));

        // FIXME: clear node.id

        const data = { type: 'nodeSchema', componentsMap, componentsTree };

        clipboard.setData(data);

        const cutMode = action && action.indexOf('x') > 0;
        if (cutMode) {
          selected.forEach((node) => {
            const parentNode = node?.parent;
            parentNode?.select();
            node?.remove();
          });
        }
      });

      // command + v paste
      hotkey.bind(['command+v', 'ctrl+v'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        // TODO
        const doc = project?.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        /* istanbul ignore next */
        clipboard.waitPasteData(e, ({ componentsTree }) => {
          if (componentsTree) {
            const { target, index } = getSuitableInsertion(ctx, componentsTree) || {};
            if (!target) {
              return;
            }
            let canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
              const dragNodeObject: IPublicTypeDragNodeObject = {
                type: IPublicEnumDragObjectType.Node,
                nodes: [node],
              };
              return doc.checkNesting(target, dragNodeObject);
            });
            if (canAddComponentsTree.length === 0) {
              return;
            }
            const nodes = insertChildren(target, canAddComponentsTree, index);
            if (nodes) {
              doc.selection.selectAll(nodes.map((o) => o.id));
              setTimeout(() => canvas.activeTracker?.track(nodes[0]), 10);
            }
          }
        });
      });

      // command + z undo
      hotkey.bind(['command+z', 'ctrl+z'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const history = project.currentDocument?.history;
        if (isFormEvent(e) || !history) {
          return;
        }

        e.preventDefault();
        const selection = project.currentDocument?.selection;
        const curSelected = selection?.selected && Array.from(selection?.selected);
        history.back();
        selection?.selectAll(curSelected);
      });

      // command + shift + z redo
      hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const history = project.currentDocument?.history;
        if (isFormEvent(e) || !history) {
          return;
        }
        e.preventDefault();
        const selection = project.currentDocument?.selection;
        const curSelected = selection?.selected && Array.from(selection?.selected);
        history.forward();
        selection?.selectAll(curSelected);
      });

      // sibling selection
      hotkey.bind(['left', 'right'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();
        const selected = doc.selection.getTopNodes(true);
        if (!selected || selected.length < 1) {
          return;
        }
        const firstNode = selected[0];
        const silbing = action === 'left' ? firstNode?.prevSibling : firstNode?.nextSibling;
        silbing?.select();
      });

      hotkey.bind(['up', 'down'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();
        const selected = doc.selection.getTopNodes(true);
        if (!selected || selected.length < 1) {
          return;
        }
        const firstNode = selected[0];

        if (action === 'down') {
          const next = getNextForSelect(firstNode, true, firstNode?.parent);
          next?.select();
        } else if (action === 'up') {
          const prev = getPrevForSelect(firstNode, true, firstNode?.parent);
          prev?.select();
        }
      });

      hotkey.bind(['option+left', 'option+right'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();
        const selected = doc.selection.getTopNodes(true);
        if (!selected || selected.length < 1) {
          return;
        }
        // TODO: 此处需要增加判断当前节点是否可被操作移动,原ve里是用 node.canOperating()来判断
        // TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断

        const firstNode = selected[0];
        const parent = firstNode?.parent;
        if (!parent) return;

        const isPrev = action && /(left)$/.test(action);

        const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling;
        if (silbing) {
          if (isPrev) {
            parent.insertBefore(firstNode, silbing, true);
          } else {
            parent.insertAfter(firstNode, silbing, true);
          }
          firstNode?.select();
        }
      });

      hotkey.bind(['option+up'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.currentDocument;
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();
        const selected = doc.selection.getTopNodes(true);
        if (!selected || selected.length < 1) {
          return;
        }
        // TODO: 此处需要增加判断当前节点是否可被操作移动,原ve里是用 node.canOperating()来判断
        // TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断

        const firstNode = selected[0];
        const parent = firstNode?.parent;
        if (!parent) {
          return;
        }

        const silbing = firstNode.prevSibling;
        if (silbing) {
          if (silbing.isContainerNode) {
            const place = getSuitablePlaceForNode(silbing, firstNode, null);
            silbing.insertAfter(firstNode, place.ref, true);
          } else {
            parent.insertBefore(firstNode, silbing, true);
          }
          firstNode?.select();
        } else {
          const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
          if (place) {
            const container = place.container.internalToShellNode();
            container.insertBefore(firstNode, place.ref);
            firstNode?.select();
          }
        }
      });

      hotkey.bind(['option+down'], (e, action) => {
        logger.info(`action ${action} is triggered`);
        if (canvas.isInLiveEditing) {
          return;
        }
        const doc = project.getCurrentDocument();
        if (isFormEvent(e) || !doc) {
          return;
        }
        e.preventDefault();
        const selected = doc.selection.getTopNodes(true);
        if (!selected || selected.length < 1) {
          return;
        }
        // TODO: 此处需要增加判断当前节点是否可被操作移动,原 ve 里是用 node.canOperating() 来判断
        // TODO: 移动逻辑也需要重新梳理,对于移动目标位置的选择,是否可以移入,需要增加判断

        const firstNode = selected[0];
        const parent = firstNode?.parent;
        if (!parent) {
          return;
        }

        const silbing = firstNode.nextSibling;
        if (silbing) {
          if (silbing.isContainerNode) {
            silbing.insertBefore(firstNode, undefined);
          } else {
            parent.insertAfter(firstNode, silbing, true);
          }
          firstNode?.select();
        } else {
          const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
          if (place) {
            const container = place.container.internalToShellNode();
            container.insertAfter(firstNode, place.ref, true);
            firstNode?.select();
          }
        }
      });
    },