| <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> |