blob: 35f50d13e015babfad21b88bdcd139bd288ec6e9 [file] [log] [blame]
<template>
<div v-loading="loading" class="doc-nav">
<h3 v-if="isOption">{{title}}</h3>
<div class="toolbox">
<span v-if="isOption" class="item" @click="collapseAll"><i class="el-icon-s-fold"></i>{{$t('nav.collapseAll')}}</span>
</div>
<el-tree
node-key="path"
empty-text=""
ref="tree"
:props="props"
lazy
:default-expanded-keys="expandedKeys"
:indent="10"
:expand-on-click-node="false"
:load="loadTreeNode"
:data="treeData"
:current-node-key="initialSelectedNode"
@current-change="onSelectNode"
>
<div class="doc-nav-item" slot-scope="{ node, data }" v-if="shared.docType !== 'tutorial'">
<!-- <el-tooltip :content="data.path" placement="top"> -->
<span>{{ node.expanded ? (data.labelExpanded || data.label) : data.label }}</span>
<!-- </el-tooltip> -->
<span v-if="data.defaultValue != null" class="default-value">{{data.defaultValue}}</span>
<span v-else-if="node.isLeaf" class="default-value">...</span>
<span v-if="!data.isRoot && !node.expanded">,</span>
</div>
<div class="doc-nav-item" slot-scope="{ node, data }" v-else>
<span>{{ data.label }}</span>
</div>
</el-tree>
</div>
</template>
<script>
import {getOutlineAsync} from '../docHelper';
import {store, isOptionDoc} from '../store';
import Vue from 'vue';
import {directTo} from '../route';
import scrollIntoView from 'scroll-into-view';
function joinPath(a, b, connector) {
return a ? (a + connector + b) : b;
}
export function createChildren(currentNode, currentSource) {
function createNode(source, parentNode) {
let childNode = {
// type: source.type,
path: source.path
};
childNode.path = source.path;
if (source.arrayItemType) {
childNode.label = `{type: ${source.arrayItemType}, ...}`;
childNode.labelExpanded = '{';
}
// Array also may has properties.
else if (source.default != null) {
childNode.defaultValue = source.default;
// Leave the space to show default value.
childNode.label = source.prop + ': ';
childNode.leaf = true;
}
else if (source.isArray) {
childNode.label = source.prop + ': [{...}]';
childNode.labelExpanded = source.prop + ': [{';
}
else if (source.isObject) {
childNode.label = source.prop + ': {...}';
childNode.labelExpanded = source.prop + ': {';
}
else {
childNode.label = source.prop;
childNode.leaf = true;
}
// TODO. A better way to query source. Avoid `ref` and `freeze`
childNode.$source = Object.freeze(source);
return childNode;
}
let children = [];
if (currentSource.children) {
currentSource.children.forEach(childSource => {
children.push(createNode(childSource, currentNode));
});
}
return children;
};
export default {
data() {
return {
props: {
isLeaf: 'leaf'
},
title: 'setOption({',
treeData: [],
expandedKeys: [],
loading: true,
shared: store,
initialSelectedNode: store.currentPath
}
},
created() {
this.updateTreeSelectionAndExpand();
},
computed: {
isOption() {
return isOptionDoc();
}
},
methods: {
updateTreeSelectionAndExpand() {
this.expandedKeys = [];
// Expand parent node of selected and ancestor nodes.
let ancestorPath = this.shared.currentPath;
let idx;
while ((idx = ancestorPath.lastIndexOf('.')) >= 0
|| (idx = ancestorPath.lastIndexOf('-')) >= 0
) {
ancestorPath = ancestorPath.substr(0, idx);
this.expandedKeys.push(ancestorPath)
}
},
loadTreeNode(node, resolve) {
// Root node
if (node.level === 0) {
this.loading = false;
getOutlineAsync().then(source => {
resolve(createChildren(node.data, source));
// Scroll to current node.
// FIXME Side effect.
setTimeout(() => {
this.scrollToCurrentTreeNode();
}, 200);
});
}
else if (node.data.children && node.data.children.length) {
resolve(node.data.children);
}
else if (node.data.$source) {
resolve(createChildren(node.data, node.data.$source));
}
else {
resolve([]);
}
},
onSelectNode(nodeData, node) {
this.shared.currentPath = nodeData.path;
},
scrollToCurrentTreeNode() {
let node = this.$el.querySelector('.el-tree-node.is-current');
if (node) {
let nodeRect = node.getBoundingClientRect();
let rootRect = this.$el.getBoundingClientRect();
if (nodeRect.top > rootRect.bottom || nodeRect.bottom < rootRect.top) { // Not visible
// node.scrollIntoView(true, {
// behavior: 'smooth'
// });
scrollIntoView(node, {
time: 500,
align: {
top: 0.1
}
});
}
}
},
manualSelectNode(nodePath) {
this.updateTreeSelectionAndExpand();
// Highlight after all expanded nodes loaded.
setTimeout(() => {
// Cancel previous selection
this.$refs.tree.setCurrentKey(null);
this.$refs.tree.setCurrentKey(nodePath);
setTimeout(() => { // Scroll to selected node after set.
this.scrollToCurrentTreeNode();
}, 200);
}, 50);
},
collapseAll() {
for (let key in this.$refs.tree.store.nodesMap) {
this.$refs.tree.store.nodesMap[key].expanded = false;
}
}
},
watch: {
'shared.currentPath'(newVal) {
directTo(newVal);
this.manualSelectNode(newVal);
}
}
}
</script>
<style lang="scss">
.doc-nav {
h3 {
margin: 0;
padding: 5px;
font-family: Monaco, 'Source Code Pro', monospace;
font-size:14px;
}
.toolbox {
position: absolute;
right: 10px;
top: 5px;
.item {
font-size: 14px;
cursor: pointer;
color: #337ab7;
&:hover {
text-decoration: underline;
}
}
}
.el-tree {
padding-left: 6px;
}
.el-tree-node {
color: #333;
.el-tree-node__content {
height: 24px;
}
&.is-current {
&>.el-tree-node__content {
background-color: #B03A5B;
color: #fff;
.default-value {
color: #eee;
}
}
}
// &:focus {
// &>.el-tree-node__content {
// background-color: #B03A5B;
// }
// }
}
.doc-nav-item {
font-family: 'Source Code Pro', monospace;
font-size: 13px;
white-space: nowrap;
.default-value {
color: #999;
}
}
}
// Special configuration for tutorial, option, api
.ec-doc-tutorial, .ec-doc-api {
.doc-nav {
.el-tree {
padding-left: 0;
margin-top: 10px;
}
}
}
.ec-doc-tutorial {
.doc-nav {
.el-tree-node {
.el-tree-node__content {
height: 32px;
}
.doc-nav-item {
margin-left: -10px;
font-family: "Source Sans Pro", "Helvetica Neue", "Segoe UI", Arial, "PingFang SC", STHeiti, "Microsoft Yahei", sans-serif;
}
}
}
}
</style>