feat(脑图): 快捷键优化&交互优化
This commit is contained in:
parent
40445cb4c5
commit
748b4e93e4
|
@ -13,6 +13,7 @@
|
|||
:can-show-enter-node="canShowEnterNode"
|
||||
:can-show-more-menu-node-operation="false"
|
||||
:more-menu-other-operation-list="canShowFloatMenu ? moreMenuOtherOperationList : []"
|
||||
:shortcut-list="['expand']"
|
||||
disabled
|
||||
@node-select="handleNodeSelect"
|
||||
@node-unselect="handleNodeUnselect"
|
||||
|
@ -93,6 +94,16 @@
|
|||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #shortCutList>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.pass') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> P </div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.unPass') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> R </div>
|
||||
</div>
|
||||
</template>
|
||||
</MsMinderEditor>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -191,7 +202,7 @@
|
|||
data: {
|
||||
...e.data,
|
||||
id: e.id || e.data?.id || '',
|
||||
text: e.name || e.data?.text || '',
|
||||
text: e.name || e.data?.text.replace(/<\/?p\b[^>]*>/gi, '') || '',
|
||||
resource: modulesCount.value[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 0 ? 'expand' : 'collapse',
|
||||
count: modulesCount.value[e.id],
|
||||
|
|
|
@ -68,6 +68,28 @@
|
|||
<caseCommentList v-else-if="activeExtraKey === 'comments'" :active-case="activeCase" />
|
||||
<bugList v-else :active-case="activeCase" />
|
||||
</template>
|
||||
<template #shortCutList>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('ms.minders.createSiblingModule') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> M </div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('ms.minders.createSiblingCase') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> C </div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('ms.minders.createChildModule') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto">
|
||||
Shift + M
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('ms.minders.createChildCase') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto">
|
||||
Shift + C
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsMinderEditor>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -76,6 +98,7 @@
|
|||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import useShortCut from '@/components/pure/ms-minder-editor/hooks/useShortCut';
|
||||
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
||||
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||
import {
|
||||
|
@ -181,7 +204,7 @@
|
|||
data: {
|
||||
...e.data,
|
||||
id: e.id || e.data?.id || '',
|
||||
text: e.name || e.data?.text || '',
|
||||
text: e.name || e.data?.text.replace(/<\/?p\b[^>]*>/gi, '') || '',
|
||||
resource: props.modulesCount[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 0 ? 'expand' : 'collapse',
|
||||
count: props.modulesCount[e.id],
|
||||
|
@ -634,6 +657,28 @@
|
|||
setPriorityView(true, 'P');
|
||||
}
|
||||
|
||||
const { unbindShortcuts } = useShortCut(
|
||||
{
|
||||
addChildModule: () => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
insertNode(node, 'AppendChildNode', moduleTag);
|
||||
},
|
||||
addChildCase: () => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
insertNode(node, 'AppendChildNode', caseTag);
|
||||
},
|
||||
addSiblingModule: () => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
insertNode(node, 'AppendSiblingNode', moduleTag);
|
||||
},
|
||||
addSiblingCase: () => {
|
||||
const node: MinderJsonNode = window.minder.getSelectedNode();
|
||||
insertNode(node, 'AppendSiblingNode', caseTag);
|
||||
},
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
/**
|
||||
* 标签编辑后,如果将标签修改为模块,则删除已添加的优先级
|
||||
* @param node 选中节点
|
||||
|
@ -953,6 +998,10 @@
|
|||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unbindShortcuts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
export default {
|
||||
'ms.minders.allModule': 'All Modules',
|
||||
'ms.minders.precondition': 'Pre-condition',
|
||||
'ms.minders.stepDesc': 'Step Description',
|
||||
'ms.minders.stepExpect': 'Expected Result',
|
||||
'ms.minders.textDesc': 'Text Description',
|
||||
'ms.minders.remark': 'Remark Information',
|
||||
'ms.minders.caseName': 'Test Case Name',
|
||||
'ms.minders.caseNameNotNull': 'Test Case Name cannot be empty',
|
||||
'ms.minders.commentTotal': '{num} Comments in Total',
|
||||
'ms.minders.text': 'Text',
|
||||
'ms.minders.leaveUnsavedTip': 'The mind map has unsaved changes, are you sure you want to exit?',
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
export default {
|
||||
'ms.minders.allModule': '全部模块',
|
||||
'ms.minders.precondition': '前置条件',
|
||||
'ms.minders.stepDesc': '步骤描述',
|
||||
'ms.minders.stepExpect': '预期结果',
|
||||
'ms.minders.textDesc': '文本描述',
|
||||
'ms.minders.remark': '备注信息',
|
||||
'ms.minders.caseName': '用例名称',
|
||||
'ms.minders.caseNameNotNull': '用例名称不能为空',
|
||||
'ms.minders.commentTotal': '共 {num} 评论',
|
||||
'ms.minders.text': '文本',
|
||||
'ms.minders.leaveUnsavedTip': '脑图有未保存的更改,确认离开吗?',
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import usePriority from '@/components/pure/ms-minder-editor/hooks/useMinderPriority';
|
||||
import type {
|
||||
MinderEvent,
|
||||
MinderJsonNode,
|
||||
|
@ -16,6 +17,7 @@ import { getGenerateId } from '@/utils';
|
|||
export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermission?: boolean }) {
|
||||
const { t } = useI18n();
|
||||
const minderStore = useMinderStore();
|
||||
const { setPriority } = usePriority({ priorityStartWithZero: true, priorityPrefix: 'P' });
|
||||
|
||||
const caseTag = t('common.case');
|
||||
const moduleTag = t('common.module');
|
||||
|
@ -361,13 +363,33 @@ export default function useMinderBaseApi({ hasEditPermission }: { hasEditPermiss
|
|||
* @param value 节点类型
|
||||
*/
|
||||
function insertSpecifyNode(type: string, value: string) {
|
||||
const nodeId = getGenerateId();
|
||||
execInert(type, {
|
||||
id: getGenerateId(),
|
||||
id: nodeId,
|
||||
text: value !== t('ms.minders.text') ? value : '',
|
||||
resource: value !== t('ms.minders.text') ? [value] : [],
|
||||
expandState: 'expand',
|
||||
isNew: true,
|
||||
priority: value === caseTag ? 1 : undefined,
|
||||
});
|
||||
if (value === caseTag) {
|
||||
// 用例节点插入后,插入子节点
|
||||
setPriority('1');
|
||||
nextTick(() => {
|
||||
insertSpecifyNode('AppendChildNode', prerequisiteTag);
|
||||
// 上面插入了子节点前置条件后会选中该节点,所以下面插入同级
|
||||
insertSpecifyNode('AppendSiblingNode', stepTag);
|
||||
insertSpecifyNode('AppendChildNode', stepExpectTag);
|
||||
window.minder.selectById(nodeId);
|
||||
insertSpecifyNode('AppendChildNode', remarkTag);
|
||||
nextTick(() => {
|
||||
// 取消选中备注节点,选中用例节点
|
||||
const remarkNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
window.minder.toggleSelect(remarkNode);
|
||||
window.minder.selectById(nodeId);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
export default {
|
||||
// 功能用例脑图文案
|
||||
'ms.minders.allModule': 'All Modules',
|
||||
'ms.minders.precondition': 'Pre-condition',
|
||||
'ms.minders.stepDesc': 'Step Description',
|
||||
'ms.minders.stepExpect': 'Expected Result',
|
||||
'ms.minders.textDesc': 'Text Description',
|
||||
'ms.minders.remark': 'Remark Information',
|
||||
'ms.minders.caseName': 'Test Case Name',
|
||||
'ms.minders.caseNameNotNull': 'Test Case Name cannot be empty',
|
||||
'ms.minders.commentTotal': '{num} Comments in Total',
|
||||
'ms.minders.text': 'Text',
|
||||
'ms.minders.leaveUnsavedTip': 'The mind map has unsaved changes, are you sure you want to exit?',
|
||||
'ms.minders.createSiblingModule': 'Insert sibling Module',
|
||||
'ms.minders.createSiblingCase': 'Insert sibling Case',
|
||||
'ms.minders.createChildModule': 'Insert child Module',
|
||||
'ms.minders.createChildCase': 'Insert child Case',
|
||||
// 测试规划脑图文案
|
||||
'ms.minders.failStop': 'Failure stop',
|
||||
'ms.minders.failRetry': 'Retry on failure',
|
||||
'ms.minders.stepRetry': 'Step Retry',
|
|
@ -1,4 +1,21 @@
|
|||
export default {
|
||||
// 功能用例脑图文案
|
||||
'ms.minders.allModule': '全部模块',
|
||||
'ms.minders.precondition': '前置条件',
|
||||
'ms.minders.stepDesc': '步骤描述',
|
||||
'ms.minders.stepExpect': '预期结果',
|
||||
'ms.minders.textDesc': '文本描述',
|
||||
'ms.minders.remark': '备注信息',
|
||||
'ms.minders.caseName': '用例名称',
|
||||
'ms.minders.caseNameNotNull': '用例名称不能为空',
|
||||
'ms.minders.commentTotal': '共 {num} 评论',
|
||||
'ms.minders.text': '文本',
|
||||
'ms.minders.leaveUnsavedTip': '脑图有未保存的更改,确认离开吗?',
|
||||
'ms.minders.createSiblingModule': '添加同级模块',
|
||||
'ms.minders.createSiblingCase': '添加同级用例',
|
||||
'ms.minders.createChildModule': '添加子级模块',
|
||||
'ms.minders.createChildCase': '添加子级用例',
|
||||
// 测试规划脑图文案
|
||||
'ms.minders.failStop': '失败停止',
|
||||
'ms.minders.failRetry': '失败重试',
|
||||
'ms.minders.stepRetry': '步骤重试',
|
|
@ -13,6 +13,7 @@
|
|||
:can-show-enter-node="canShowEnterNode"
|
||||
:can-show-more-menu-node-operation="false"
|
||||
:more-menu-other-operation-list="canShowFloatMenu && hasOperationPermission ? moreMenuOtherOperationList : []"
|
||||
:shortcut-list="['expand']"
|
||||
disabled
|
||||
@node-batch-select="handleNodeBatchSelect"
|
||||
@node-select="handleNodeSelect"
|
||||
|
@ -134,6 +135,20 @@
|
|||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #shortCutList>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.success') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> S </div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.fail') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> E </div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.block') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto"> B </div>
|
||||
</div>
|
||||
</template>
|
||||
</MsMinderEditor>
|
||||
<LinkDefectDrawer
|
||||
v-if="isMinderOperation"
|
||||
|
@ -302,7 +317,7 @@
|
|||
...e.data,
|
||||
type: e.type || e.data?.type,
|
||||
id: e.id || e.data?.id || '',
|
||||
text: e.name || e.data?.text || '',
|
||||
text: e.name || e.data?.text.replace(/<\/?p\b[^>]*>/gi, '') || '',
|
||||
resource: modulesCount.value[e.id] !== undefined ? [moduleTag] : e.data?.resource,
|
||||
expandState: e.level === 0 ? 'expand' : 'collapse',
|
||||
count: modulesCount.value[e.id],
|
||||
|
@ -608,14 +623,18 @@
|
|||
);
|
||||
if (actualResultNode) {
|
||||
if (content.length) {
|
||||
actualResultNode.setData('text', content).render();
|
||||
actualResultNode.setData('text', content.replace(/<\/?p\b[^>]*>/gi, '')).render();
|
||||
} else {
|
||||
// 删除实际结果节点
|
||||
window.minder.removeNode(actualResultNode);
|
||||
}
|
||||
} else if (content.length) {
|
||||
actualResultNode = createNode(
|
||||
{ resource: [actualResultTag], text: content, id: `actualResult-${node.data?.id}` },
|
||||
{
|
||||
resource: [actualResultTag],
|
||||
text: content.replace(/<\/?p\b[^>]*>/gi, ''),
|
||||
id: `actualResult-${node.data?.id}`,
|
||||
},
|
||||
node
|
||||
);
|
||||
handleRenderNode(node, [actualResultNode]);
|
||||
|
@ -983,6 +1002,9 @@
|
|||
[executionResultMap.ERROR.statusText]: 5,
|
||||
[executionResultMap.BLOCKED.statusText]: 6,
|
||||
};
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unbindShortcuts();
|
||||
});
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
:can-show-dropdown="canShowDropdown"
|
||||
:dropdown-list="dropdownList"
|
||||
:checked-val="checkedVal"
|
||||
:shortcut-list="['expand', 'addSibling', 'addChild', 'delete']"
|
||||
custom-priority
|
||||
single-tag
|
||||
tag-enable
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { isMacOs } from '@/utils';
|
||||
|
||||
export default defineComponent(() => {
|
||||
export default defineComponent((props: { size?: number }) => {
|
||||
const isMac = isMacOs();
|
||||
|
||||
return () => (isMac ? <icon-command size={14} /> : 'Ctrl');
|
||||
return () => (isMac ? <icon-command size={props.size || 14} /> : 'Ctrl');
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { MinderJsonNode } from '../props';
|
|||
import useMinderOperation, { type MinderOperationProps } from './useMinderOperation';
|
||||
|
||||
type ShortcutKey =
|
||||
| 'save'
|
||||
| 'expand'
|
||||
| 'enter'
|
||||
| 'appendSiblingNode'
|
||||
|
@ -11,7 +12,11 @@ type ShortcutKey =
|
|||
| 'delete'
|
||||
| 'executeToSuccess'
|
||||
| 'executeToBlocked'
|
||||
| 'executeToError';
|
||||
| 'executeToError'
|
||||
| 'addChildCase'
|
||||
| 'addChildModule'
|
||||
| 'addSiblingModule'
|
||||
| 'addSiblingCase';
|
||||
// 快捷键事件映射,combinationShortcuts中定义了组合键事件,key为组合键,value为事件名称;
|
||||
type Shortcuts = {
|
||||
[key in ShortcutKey]?: () => void;
|
||||
|
@ -38,12 +43,14 @@ export default function useShortCut(shortcuts: Shortcuts, options: MinderOperati
|
|||
}
|
||||
const key = event.key.toLowerCase();
|
||||
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
const isShift = event.shiftKey;
|
||||
|
||||
// 定义组合键事件
|
||||
const combinationShortcuts: { [key: string]: ShortcutKey } = {
|
||||
// z: 'undo', // 撤销 TODO:暂时不上撤销和重做
|
||||
// y: 'redo', // 重做
|
||||
enter: 'enter', // 进入节点
|
||||
save: 'save', // 保存
|
||||
};
|
||||
// 定义单键事件
|
||||
const singleShortcuts: { [key: string]: ShortcutKey } = {
|
||||
|
@ -57,12 +64,21 @@ export default function useShortCut(shortcuts: Shortcuts, options: MinderOperati
|
|||
s: 'executeToSuccess', // 执行结果为成功
|
||||
b: 'executeToBlocked', // 执行结果为阻塞
|
||||
e: 'executeToError', // 执行结果为失败
|
||||
m: 'addSiblingModule', // 添加同级模块
|
||||
c: 'addSiblingCase', // 添加同级用例
|
||||
};
|
||||
const shiftCombinationShortcuts: { [key: string]: ShortcutKey } = {
|
||||
m: 'addChildModule', // 添加子级模块
|
||||
c: 'addChildCase', // 添加子级用例
|
||||
};
|
||||
|
||||
let action;
|
||||
if (isCtrlOrCmd && combinationShortcuts[key]) {
|
||||
// 执行组合键事件
|
||||
action = combinationShortcuts[key];
|
||||
} else if (isShift && shiftCombinationShortcuts[key]) {
|
||||
// 执行 shift 组合键事件
|
||||
action = shiftCombinationShortcuts[key];
|
||||
} else if (singleShortcuts[key]) {
|
||||
// 执行单键事件
|
||||
action = singleShortcuts[key];
|
||||
|
|
|
@ -43,14 +43,22 @@
|
|||
<MsIcon type="icon-icon_full_screen_one" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-button v-if="!props.disabled" type="outline" class="px-[8px] py-[2px] text-[12px]" size="small" @click="save">
|
||||
<a-button
|
||||
v-if="!props.disabled"
|
||||
type="outline"
|
||||
class="flex items-center gap-[2px] px-[8px] py-[2px] text-[12px]"
|
||||
size="small"
|
||||
@click="save"
|
||||
>
|
||||
{{ t('minder.main.main.save') }}
|
||||
<div>(<MsCtrlOrCommand :size="12" /> + S)</div>
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCtrlOrCommand from '@/components/pure/ms-ctrl-or-command';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import useFullScreen from '@/hooks/useFullScreen';
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
:disabled="props.disabled"
|
||||
@save="save"
|
||||
/>
|
||||
<Navigator />
|
||||
<Navigator :shortcut-list="props.shortcutList">
|
||||
<template #shortCutList>
|
||||
<slot name="shortCutList"></slot>
|
||||
</template>
|
||||
</Navigator>
|
||||
<div
|
||||
v-if="currentTreePath?.length > 0"
|
||||
class="absolute left-[50%] top-[16px] z-[9] w-[60%] translate-x-[-50%] overflow-hidden bg-white p-[8px]"
|
||||
|
@ -64,6 +68,7 @@
|
|||
MinderJson,
|
||||
MinderJsonNode,
|
||||
MinderJsonNodeData,
|
||||
navigatorProps,
|
||||
priorityProps,
|
||||
tagProps,
|
||||
} from '../props';
|
||||
|
@ -80,6 +85,7 @@
|
|||
...priorityProps,
|
||||
...batchMenuProps,
|
||||
...dropdownMenuProps,
|
||||
...navigatorProps,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
|
@ -341,7 +347,7 @@
|
|||
right: 7px;
|
||||
font-size: 12px;
|
||||
font-family: iconfont;
|
||||
content: '\e6d5';
|
||||
content: '\e6fe';
|
||||
color: var(--color-text-brand);
|
||||
line-height: 16px;
|
||||
transform: translateY(-50%);
|
||||
|
|
|
@ -40,8 +40,8 @@
|
|||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-trigger
|
||||
:popup-translate="[5, -105]"
|
||||
position="right"
|
||||
:popup-translate="[5, -20]"
|
||||
position="rb"
|
||||
class="ms-minder-shortcut-trigger"
|
||||
@popup-visible-change="(val) => (shortcutTriggerVisible = val)"
|
||||
>
|
||||
|
@ -60,7 +60,7 @@
|
|||
<div>{{ t('minder.expand') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">/</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('copy')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.copy') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
|
@ -69,13 +69,13 @@
|
|||
<div class="ms-minder-shortcut-trigger-listitem-icon">C</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('addSibling')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('minder.hotboxMenu.insetBrother') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
<MsIcon type="icon-icon_carriage_return2" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('paste')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('minder.hotboxMenu.paste') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
|
@ -84,13 +84,13 @@
|
|||
<div class="ms-minder-shortcut-trigger-listitem-icon">V</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('addChild')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('minder.hotboxMenu.insetSon') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon ms-minder-shortcut-trigger-listitem-icon-auto">
|
||||
Tab
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('cut')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('minder.hotboxMenu.cut') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
|
@ -99,7 +99,7 @@
|
|||
<div class="ms-minder-shortcut-trigger-listitem-icon">X</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('enter')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('minder.hotboxMenu.enterNode') }}</div>
|
||||
<div class="flex items-center gap-[4px]">
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
|
@ -119,7 +119,7 @@
|
|||
<div class="ms-minder-shortcut-trigger-listitem-icon">Z</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="ms-minder-shortcut-trigger-listitem">
|
||||
<div v-if="props.shortcutList.includes('delete')" class="ms-minder-shortcut-trigger-listitem">
|
||||
<div>{{ t('common.delete') }}</div>
|
||||
<div class="ms-minder-shortcut-trigger-listitem-icon">
|
||||
<MsIcon type="icon-icon_carriage_return1" />
|
||||
|
@ -134,6 +134,7 @@
|
|||
<div class="ms-minder-shortcut-trigger-listitem-icon">Y</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<slot name="shortCutList"></slot>
|
||||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
|
@ -149,14 +150,17 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { navigatorProps } from '../props';
|
||||
import { getLocalStorage, setLocalStorage } from '../script/store';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
const props = defineProps(navigatorProps);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const navPreviewer: Ref<HTMLDivElement | null> = ref(null);
|
||||
|
||||
const isNavOpen = ref(true);
|
||||
const isNavOpen = ref(false);
|
||||
const previewNavigator: Ref<HTMLDivElement | null> = ref(null);
|
||||
const contentView = ref('');
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
<template #batchMenu>
|
||||
<slot name="batchMenu"></slot>
|
||||
</template>
|
||||
<template #shortCutList>
|
||||
<slot name="shortCutList"></slot>
|
||||
</template>
|
||||
</mainEditor>
|
||||
</div>
|
||||
<div class="ms-minder-editor-extra" :class="[extraVisible ? 'ms-minder-editor-extra--visible' : '']">
|
||||
|
@ -51,6 +54,7 @@
|
|||
MinderJson,
|
||||
MinderJsonNode,
|
||||
moleProps,
|
||||
navigatorProps,
|
||||
priorityProps,
|
||||
tagProps,
|
||||
viewMenuProps,
|
||||
|
@ -82,6 +86,7 @@
|
|||
...viewMenuProps,
|
||||
...batchMenuProps,
|
||||
...dropdownMenuProps,
|
||||
...navigatorProps,
|
||||
});
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
|
@ -138,6 +143,9 @@
|
|||
minderStore.dispatchEvent(MinderEventName.ENTER_NODE, undefined, undefined, undefined, [selectedNodes[0]]);
|
||||
}
|
||||
},
|
||||
save: () => {
|
||||
minderStore.dispatchEvent(MinderEventName.SAVE_MINDER);
|
||||
},
|
||||
delete: () => {
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
minderDelete(selectedNodes);
|
||||
|
|
|
@ -328,3 +328,13 @@ export const viewMenuProps = {
|
|||
default: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const navigatorProps = {
|
||||
// 显示的快捷键列表
|
||||
shortcutList: {
|
||||
type: Array as PropType<string[]>,
|
||||
default() {
|
||||
return ['expand', 'addSibling', 'addChild', 'delete', 'cut', 'copy', 'paste', 'enter'];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import '@7polo/kity/dist/kity';
|
||||
import '@7polo/kityminder-core';
|
||||
import clipboard from './runtime/clipboard';
|
||||
import clipboardMimetype from './runtime/clipboard-mimetype';
|
||||
import container from './runtime/container';
|
||||
import drag from './runtime/drag';
|
||||
|
@ -70,7 +69,6 @@ assemble(minder);
|
|||
assemble(receiver);
|
||||
assemble(input);
|
||||
assemble(clipboardMimetype);
|
||||
assemble(clipboard);
|
||||
assemble(drag);
|
||||
assemble(history);
|
||||
assemble(jumping);
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
import { markDeleteNode, resetNodes } from '../tool/utils';
|
||||
|
||||
interface INode {
|
||||
getLevel(): number;
|
||||
isAncestorOf(node: INode): boolean;
|
||||
appendChild(node: INode): INode;
|
||||
}
|
||||
|
||||
interface IData {
|
||||
getRegisterProtocol(protocol: string): {
|
||||
encode: (nodes: Array<INode>) => Array<INode>;
|
||||
decode: (nodes: Array<INode>) => Array<INode>;
|
||||
};
|
||||
}
|
||||
|
||||
export default function ClipboardRuntime(this: any) {
|
||||
const { minder } = this;
|
||||
const { receiver } = this;
|
||||
const Data: IData = window.kityminder.data;
|
||||
|
||||
if (!minder.supportClipboardEvent || window.kity.Browser.gecko) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kmencode = this.MimeType.getMimeTypeProtocol('application/km');
|
||||
const { decode } = Data.getRegisterProtocol('json');
|
||||
let _selectedNodes: Array<INode> = [];
|
||||
|
||||
function encode(nodes: Array<INode>): string {
|
||||
const _nodes = [];
|
||||
for (let i = 0, l = nodes.length; i < l; i++) {
|
||||
// @ts-ignore
|
||||
_nodes.push(minder.exportNode(nodes[i]));
|
||||
}
|
||||
return kmencode(Data.getRegisterProtocol('json').encode(_nodes));
|
||||
}
|
||||
|
||||
const beforeCopy = (e: ClipboardEvent) => {
|
||||
if (document.activeElement === receiver.element) {
|
||||
const clipBoardEvent = e;
|
||||
const state = this.fsm.state();
|
||||
switch (state) {
|
||||
case 'input': {
|
||||
break;
|
||||
}
|
||||
case 'normal': {
|
||||
const nodes = [...minder.getSelectedNodes()];
|
||||
if (nodes.length) {
|
||||
if (nodes.length > 1) {
|
||||
let targetLevel;
|
||||
nodes.sort((a: any, b: any) => {
|
||||
return a.getLevel() - b.getLevel();
|
||||
});
|
||||
// eslint-disable-next-line prefer-const
|
||||
targetLevel = nodes[0].getLevel();
|
||||
if (targetLevel !== nodes[nodes.length - 1].getLevel()) {
|
||||
let pnode;
|
||||
let idx = 0;
|
||||
const l = nodes.length;
|
||||
let pidx = l - 1;
|
||||
|
||||
pnode = nodes[pidx];
|
||||
|
||||
while (pnode.getLevel() !== targetLevel) {
|
||||
idx = 0;
|
||||
while (idx < l && nodes[idx].getLevel() === targetLevel) {
|
||||
if (nodes[idx].isAncestorOf(pnode)) {
|
||||
nodes.splice(pidx, 1);
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
pidx--;
|
||||
pnode = nodes[pidx];
|
||||
}
|
||||
}
|
||||
}
|
||||
const str = encode(nodes);
|
||||
clipBoardEvent.clipboardData?.setData('text/plain', str);
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const beforeCut = (e: ClipboardEvent) => {
|
||||
const { activeElement } = document;
|
||||
if (activeElement === receiver.element) {
|
||||
if (minder.getStatus() !== 'normal') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const clipBoardEvent = e;
|
||||
const state = this.fsm.state();
|
||||
|
||||
switch (state) {
|
||||
case 'input': {
|
||||
break;
|
||||
}
|
||||
case 'normal': {
|
||||
markDeleteNode(minder);
|
||||
const nodes = minder.getSelectedNodes();
|
||||
if (nodes.length) {
|
||||
clipBoardEvent.clipboardData?.setData('text/plain', encode(nodes));
|
||||
minder.execCommand('removenode');
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const beforePaste = (e: ClipboardEvent) => {
|
||||
if (document.activeElement === receiver.element) {
|
||||
if (minder.getStatus() !== 'normal') {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const clipBoardEvent = e;
|
||||
const state = this.fsm.state();
|
||||
const textData = clipBoardEvent.clipboardData?.getData('text/plain');
|
||||
|
||||
switch (state) {
|
||||
case 'input': {
|
||||
// input状态下如果格式为application/km则不进行paste操作
|
||||
if (!this.MimeType.isPureText(textData)) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'normal': {
|
||||
/*
|
||||
* 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理
|
||||
*/
|
||||
const sNodes = minder.getSelectedNodes();
|
||||
|
||||
if (this.MimeType.whichMimeType(textData) === 'application/km') {
|
||||
const nodes = decode(this.MimeType.getPureText(textData));
|
||||
resetNodes(nodes);
|
||||
let _node;
|
||||
sNodes.forEach((node: INode) => {
|
||||
// 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来
|
||||
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||
_node = minder.createNode(null, node);
|
||||
minder.importNode(_node, nodes[i]);
|
||||
_selectedNodes.push(_node);
|
||||
node.appendChild(_node);
|
||||
}
|
||||
});
|
||||
minder.select(_selectedNodes, true);
|
||||
_selectedNodes = [];
|
||||
|
||||
minder.refresh();
|
||||
} else if (clipBoardEvent.clipboardData && clipBoardEvent.clipboardData.items[0].type.indexOf('image') > -1) {
|
||||
const imageFile = clipBoardEvent.clipboardData.items[0].getAsFile();
|
||||
const serverService = window.angular.element(document.body).injector().get('server');
|
||||
|
||||
return serverService.uploadImage(imageFile).then((json: Record<string, any>) => {
|
||||
const resp = json.data;
|
||||
if (resp.errno === 0) {
|
||||
minder.execCommand('image', resp.data.url);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sNodes.forEach((node: INode) => {
|
||||
minder.Text2Children(node, textData);
|
||||
});
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
// 触发命令监听
|
||||
minder.execCommand('paste');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 由editor的receiver统一处理全部事件,包括clipboard事件
|
||||
* @Editor: Naixor
|
||||
* @Date: 2015.9.24
|
||||
*/
|
||||
// TODO: 未来需要支持自定义快捷键处理逻辑
|
||||
// document.addEventListener('copy', (e) => beforeCopy(e));
|
||||
// document.addEventListener('cut', (e) => beforeCut(e));
|
||||
// document.addEventListener('paste', (e) => beforePaste(e));
|
||||
}
|
|
@ -132,6 +132,12 @@ class FSM {
|
|||
|
||||
function FSMRuntime(this: any) {
|
||||
this.fsm = new FSM('normal');
|
||||
this.fsm.when('normal -> normal', (exit: any, enter: any, reason: string, e: KeyboardEvent) => {
|
||||
const arrowKey = ['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'];
|
||||
if (reason === 'shortcut-handle' && arrowKey.includes(e.code)) {
|
||||
this.minder.dispatchKeyEvent(e); // 触发脑图本身的方向快捷键事件
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default FSMRuntime;
|
||||
|
|
|
@ -275,7 +275,7 @@ function InputRuntime(this: any) {
|
|||
}
|
||||
}
|
||||
|
||||
text = text.replace(/^\n*|\n*$/g, '');
|
||||
text = text.replace(/^\n*|\n*$/g, '').replace(/<\/?p\b[^>]*>/gi, ''); // 去除富文本内p标签
|
||||
text = text.replace(new RegExp(`(\n|\r|\n\r)(\u0020|${String.fromCharCode(160)}){4}`, 'g'), '$1\t');
|
||||
this.minder.getSelectedNode().setText(text);
|
||||
if (isBold) {
|
||||
|
|
|
@ -32,6 +32,30 @@ interface IJumpingRuntime {
|
|||
function JumpingRuntime(this: IJumpingRuntime): void {
|
||||
const { fsm, receiver } = this;
|
||||
|
||||
// normal -> *
|
||||
receiver.listen('normal', (e: any) => {
|
||||
// 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable
|
||||
receiver.enable();
|
||||
/**
|
||||
* check
|
||||
* @editor Naixor
|
||||
* @Date 2015-12-2
|
||||
*/
|
||||
switch (e.type) {
|
||||
case 'keydown': {
|
||||
// normal -> normal shortcut
|
||||
fsm.jump('normal', 'shortcut-handle', e); // 触发快捷键事件,这里可能会被脑图自定义快捷键拦截处理,若非自定义快捷键则触发脑图本身的快捷键
|
||||
break;
|
||||
}
|
||||
case 'keyup': {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// input => normal
|
||||
receiver.listen('input', (e: KeyboardEvent) => {
|
||||
receiver.enable();
|
||||
|
|
|
@ -209,6 +209,7 @@ export function createNode(data?: MinderJsonNodeData, parentNode?: MinderJsonNod
|
|||
return window.minder.createNode(
|
||||
{
|
||||
...data,
|
||||
text: data?.text.replace(/<\/?p\b[^>]*>/gi, '') || '',
|
||||
expandState: 'collapse',
|
||||
disabled: true,
|
||||
},
|
||||
|
|
|
@ -87,6 +87,8 @@
|
|||
class="w-[130px]"
|
||||
:disabled="fileSizeLimitLoading || !hasAnyPermission(['SYSTEM_PARAMETER_SETTING_BASE:READ+UPDATE'])"
|
||||
:min="0"
|
||||
:max="1024"
|
||||
:precision="0"
|
||||
mode="button"
|
||||
@blur="() => saveFileSizeLimitConfig()"
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue