Stackedit/src/components/Explorer.vue
2022-08-18 17:05:22 +08:00

226 lines
6.5 KiB
Vue

<template>
<div class="explorer flex flex--column">
<div class="side-title flex flex--row flex--space-between">
<div class="flex flex--row" v-if="!showSearch">
<button class="side-title__button side-title__button--new-file button" @click="newItem()" v-title="'创建文件'">
<icon-file-plus></icon-file-plus>
</button>
<button class="side-title__button side-title__button--new-folder button" @click="newItem(true)" v-title="'创建文件夹'">
<icon-folder-plus></icon-folder-plus>
</button>
<button class="side-title__button side-title__button--delete button" @click="deleteItem()" v-title="'删除'">
<icon-delete></icon-delete>
</button>
<button class="side-title__button side-title__button--rename button" @click="editItem()" v-title="'重命名'">
<icon-pen></icon-pen>
</button>
<button class="side-title__button side-title__button--search button" @click="toSearch()" v-title="'搜索文件'">
<icon-file-search></icon-file-search>
</button>
</div>
<div class="flex flex--row" v-else>
<button class="side-title__button button" @click="back()" v-title="'返回资源管理器'">
<icon-dots-horizontal></icon-dots-horizontal>
</button>
<div class="side-title__title">
搜索文件
</div>
</div>
<button class="side-title__button side-title__button--close button" @click="toggleExplorer(false)" v-title="'关闭资源管理器'">
<icon-close></icon-close>
</button>
</div>
<div class="explorer__tree" :class="{'explorer__tree--new-item': !newChildNode.isNil}" v-if="!light" v-show="!showSearch" tabindex="0" @keydown.delete="deleteItem()">
<explorer-node :node="rootNode" :depth="0"></explorer-node>
</div>
<div class="explorer__search" tabindex="0" v-if="!light && showSearch">
<input type="text" v-model="searchText" class="text-input" placeholder="请输入关键字回车" @keyup.enter="search" />
<div class="explorer__search-list">
<div class="search-tips" v-if="searching">正在查询中...</div>
<a class="menu-entry button flex flex--row flex--align-center" :class="{'search-node--selected': currentFileId === fileItem.id}"
v-for="fileItem in searchItems" :key="fileItem.id" @click="clickSearch(fileItem)" href="javascript:void(0)">
{{ fileItem.name }}
</a>
<div class="search-tips">最多返回匹配的50个文档</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import ExplorerNode from './ExplorerNode';
import explorerSvc from '../services/explorerSvc';
import store from '../store';
import MenuEntry from './menus/common/MenuEntry';
import localDbSvc from '../services/localDbSvc';
import badgeSvc from '../services/badgeSvc';
export default {
components: {
ExplorerNode,
MenuEntry,
},
data: () => ({
currentFileId: '',
showSearch: false,
searching: false,
searchText: '',
searchItems: [],
}),
computed: {
...mapState([
'light',
]),
...mapState('explorer', [
'newChildNode',
]),
...mapGetters('explorer', [
'rootNode',
'selectedNode',
]),
workspaceId: () => store.getters['workspace/currentWorkspace'].id,
},
methods: {
...mapActions('data', [
'toggleExplorer',
]),
newItem: isFolder => explorerSvc.newItem(isFolder),
deleteItem: () => explorerSvc.deleteItem(),
editItem() {
const node = this.selectedNode;
if (!node.isTrash && !node.isTemp) {
store.commit('explorer/setEditingId', node.item.id);
}
},
back() {
this.showSearch = false;
},
toSearch() {
this.showSearch = true;
},
search() {
this.searchItems = [];
if (!this.searchText) {
return;
}
this.searching = true;
const allFileById = {};
const filterIds = [];
localDbSvc.getWorkspaceItems(this.workspaceId, (item) => {
if (item.type !== 'file' && item.type !== 'content') {
return;
}
if (item.type === 'file') {
allFileById[item.id] = item;
}
if (filterIds.length >= 50) {
return;
}
const fileId = item.id.split('/')[0];
// 包含了直接跳过
if (filterIds.indexOf(fileId) > -1) {
return;
}
if (item.name && item.name.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
if (item.text && item.text.indexOf(this.searchText) > -1) {
filterIds.push(fileId);
}
}, () => {
filterIds.forEach((it) => {
const file = allFileById[it];
if (file) {
this.searchItems.push(file);
}
});
this.searching = false;
badgeSvc.addBadge('searchFile');
});
},
clickSearch(item) {
const node = store.getters['explorer/nodeMap'][item.id];
if (!node) {
return;
}
store.commit('explorer/setSelectedId', item.id);
// Prevent from freezing the UI while loading the file
setTimeout(() => {
store.commit('file/setCurrentId', item.id);
}, 10);
},
},
created() {
this.$watch(
() => store.getters['file/current'].id,
(currentFileId) => {
this.currentFileId = currentFileId;
store.commit('explorer/setSelectedId', currentFileId);
store.dispatch('explorer/openNode', currentFileId);
}, {
immediate: true,
},
);
},
};
</script>
<style lang="scss">
@import '../styles/variables.scss';
.explorer,
.explorer__tree {
height: 100%;
}
.explorer__tree {
overflow: auto;
/* fake element */
& > .explorer-node > .explorer-node__children > .explorer-node:last-child > .explorer-node__item {
height: 20px;
cursor: auto;
}
}
.explorer__search {
overflow: auto;
.explorer__search-list {
margin-top: 10px;
}
.menu-entry {
font-size: 14px;
padding: 5px;
}
.menu-entry__icon {
width: 0;
margin-left: 0;
border-bottom: 1px solid $hr-color;
}
.search-tips {
font-size: 10px;
background-color: rgba(255, 173, 51, 0.14902);
padding: 5px;
text-align: center;
}
.search-node--selected {
background-color: rgba(0, 0, 0, 0.2);
.app--dark & {
background-color: rgba(0, 0, 0, 0.4);
}
&:focus {
background-color: #39f;
color: #fff;
}
}
}
</style>