diff --git a/.babelrc b/.babelrc
index b16cc73..6464282 100644
--- a/.babelrc
+++ b/.babelrc
@@ -11,6 +11,7 @@
"react"
],
"plugins": [
+ "transform-decorators-legacy",
"transform-class-properties",
["transform-object-rest-spread", { "useBuiltIns": true }]
]
diff --git a/package.json b/package.json
index 32057d3..f1beb95 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"babel-loader": "^7.1.2",
"babel-plugin-react-require": "^3.0.0",
"babel-plugin-transform-class-properties": "^6.24.1",
+ "babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
diff --git a/src/components/FileExplorer.js b/src/components/FileExplorer.js
index fa72dca..69bee78 100644
--- a/src/components/FileExplorer.js
+++ b/src/components/FileExplorer.js
@@ -2,187 +2,15 @@ import React from 'react'
import PropTypes from "prop-types";
import connect from '../driver/connect'
+import { FileExplorer as FileExplorerCore } from '../driver/core'
import SearchBar from './SearchBar'
import Node from './Node'
import cx from '../utils/cx'
-import DOMHelper from '../utils/DOMHelper'
-import treeParser from '../utils/treeParser'
-import URLHelper from '../utils/URLHelper'
-import VisibleNodesGenerator from '../utils/VisibleNodesGenerator'
-function getVisibleParentNode(nodes, focusedNode, depths) {
- const focusedNodeIndex = nodes.indexOf(focusedNode)
- const focusedNodeDepth = depths.get(focusedNode)
- let indexOfParentNode = focusedNodeIndex - 1
- while (
- indexOfParentNode !== -1 &&
- depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth
- ) {
- --indexOfParentNode
- }
- const parentNode = nodes[indexOfParentNode]
- return parentNode
-}
-
-const tasksAfterRender = []
-const visibleNodesGenerator = new VisibleNodesGenerator()
-
-const init = dispatch => () => dispatch(async (state, { treeData, metaData, compressSingletonFolder }) => {
- const { root } = treeParser.parse(treeData, metaData)
- visibleNodesGenerator.setCompress(compressSingletonFolder)
- await visibleNodesGenerator.plantTree(root)
- const currentPath = URLHelper.getCurrentPath(true)
- tasksAfterRender.push(DOMHelper.focusSearchInput)
- if (currentPath.length) {
- const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
- if (nodeExpandedTo) {
- visibleNodesGenerator.focusNode(nodeExpandedTo)
- const { nodes } = visibleNodesGenerator.visibleNodes
- tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo)))
- }
- }
- dispatch(updateVisibleNodes)
-})
-
-const execAfterRender = dispatch => () => {
- for (const task of tasksAfterRender) {
- task()
- }
- tasksAfterRender.length = 0
-}
-
-const handleKeyDown = dispatch => ({ key }) => dispatch(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => {
- if (focusedNode) {
- const focusedNodeIndex = nodes.indexOf(focusedNode)
- switch (key) {
- case 'ArrowUp':
- // focus on previous node
- if (focusedNodeIndex === 0) {
- dispatch(focusNode, null)
- tasksAfterRender.push(DOMHelper.focusSearchInput)
- } else {
- dispatch(focusNode, nodes[focusedNodeIndex - 1])
- }
- break
-
- case 'ArrowDown':
- // focus on next node
- if (focusedNodeIndex + 1 < nodes.length) {
- dispatch(focusNode, nodes[focusedNodeIndex + 1])
- } else {
- dispatch(focusNode, null)
- tasksAfterRender.push(DOMHelper.focusSearchInput)
- }
- break
-
- case 'ArrowLeft':
- // collapse node or go to parent node
- if (expandedNodes.has(focusedNode)) {
- dispatch(setExpand, focusedNode, false)
- } else {
- // go forward to the start of the list, find the closest node with lower depth
- const parentNode = getVisibleParentNode(nodes, focusedNode, depths)
- if (parentNode) {
- dispatch(focusNode, parentNode)
- }
- }
- break
-
- // consider the two keys as 'confirm' key
- case 'ArrowRight':
- // expand node or focus on first content node or redirect to file page
- if (focusedNode.type === 'tree') {
- if (expandedNodes.has(focusedNode)) {
- const nextNode = nodes[focusedNodeIndex + 1]
- if (depths.get(nextNode) > depths.get(focusedNode)) {
- dispatch(focusNode, nextNode)
- }
- } else {
- dispatch(setExpand, focusedNode, true)
- }
- } else if (focusedNode.type === 'blob') {
- DOMHelper.loadWithPJAX(focusedNode.url)
- } else if (focusedNode.type === 'commit') {
- // redirect to its parent folder
- DOMHelper.loadWithPJAX(focusedNode.parent.url)
- }
- break
- case 'Enter':
- // expand node or redirect to file page
- if (focusedNode.type === 'tree') {
- dispatch(setExpand, focusedNode, true)
- } else if (focusedNode.type === 'blob') {
- DOMHelper.loadWithPJAX(focusedNode.url)
- } else if (focusedNode.type === 'commit') {
- // redirect to its parent folder
- DOMHelper.loadWithPJAX(focusedNode.parent.url)
- }
- break
-
- }
- } else {
- // now search input is focused
- if (nodes.length) {
- switch (key) {
- case 'ArrowDown':
- dispatch(focusNode, nodes[0])
- break
- case 'ArrowUp':
- dispatch(focusNode, nodes[nodes.length - 1])
- break
- }
- }
- }
-})
-
-const handleSearchKeyChange = dispatch => async event => {
- const searchKey = event.target.value
- await visibleNodesGenerator.search(searchKey)
- dispatch(updateVisibleNodes)
-}
-
-const setExpand = dispatch => (node, expand) => {
- visibleNodesGenerator.setExpand(node, expand)
- dispatch(focusNode, node)
- tasksAfterRender.push(DOMHelper.focusSearchInput)
-}
-
-const toggleNodeExpansion = dispatch => (node, skipScrollToNode) => {
- visibleNodesGenerator.toggleExpand(node)
- dispatch(focusNode, node, skipScrollToNode)
- tasksAfterRender.push(DOMHelper.focusFileExplorer)
-}
-
-const focusNode = dispatch => (node, skipScroll) => dispatch(({ visibleNodes: { nodes } }) => {
- visibleNodesGenerator.focusNode(node)
- if (node && !skipScroll) {
- // when focus a node not in viewport(by keyboard), scroll to it
- const indexOfToBeFocusedNode = nodes.indexOf(node)
- tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode))
- tasksAfterRender.push(DOMHelper.focusSearchInput)
- }
- dispatch(updateVisibleNodes)
-})
-
-const onNodeClick = dispatch => (node) => {
- if (node.type === 'tree') {
- dispatch(toggleNodeExpansion, node, true)
- } else if (node.type === 'blob') {
- dispatch(focusNode, node, true)
- DOMHelper.loadWithPJAX(node.url)
- } else if (node.type === 'commit') {
- DOMHelper.loadWithPJAX(node.parent.url)
- }
-}
-
-const updateVisibleNodes = dispatch => () => {
- const { visibleNodes } = visibleNodesGenerator
- dispatch({ visibleNodes })
-}
-
-class List extends React.Component {
+@connect(FileExplorerCore)
+export default class FileExplorer extends React.Component {
static propTyps = {
treeData: PropTypes.object,
metaData: PropTypes.object,
@@ -260,15 +88,3 @@ class List extends React.Component {
)
}
}
-
-export default connect({
- init,
- execAfterRender,
- handleKeyDown,
- handleSearchKeyChange,
- setExpand,
- toggleNodeExpansion,
- focusNode,
- onNodeClick,
- updateVisibleNodes,
-})(List)
diff --git a/src/components/SideBar.js b/src/components/SideBar.js
index fbf89b8..4cf539a 100644
--- a/src/components/SideBar.js
+++ b/src/components/SideBar.js
@@ -1,6 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
+import { SideBar as SideBarCore } from '../driver/core'
+import connect from '../driver/connect'
+
import FileExplorer from './FileExplorer'
import ToggleShowButton from './ToggleShowButton'
import MetaBar from './MetaBar'
@@ -12,6 +15,7 @@ import cx from '../utils/cx'
const baseSize = 260
+@connect(SideBarCore)
export default class Gitako extends React.PureComponent {
static propTypes = {
// initial width of side bar
diff --git a/src/content.js b/src/content.js
index 5090739..f1ed56b 100644
--- a/src/content.js
+++ b/src/content.js
@@ -1,15 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom'
-import connect from './driver/connect'
-import core from './driver/core'
import SideBar from './components/SideBar'
import './content.less'
-const ConnectedSideBar = connect(core)(SideBar)
-
const SideBarElement = document.createElement('div')
document.body.appendChild(SideBarElement)
-ReactDOM.render(, SideBarElement)
+ReactDOM.render(, SideBarElement)
diff --git a/src/driver/connect.js b/src/driver/connect.js
index c526482..0d11a62 100644
--- a/src/driver/connect.js
+++ b/src/driver/connect.js
@@ -1,9 +1,5 @@
import React from 'react'
-function isObject(target) {
- return typeof target === 'object' && target !== null
-}
-
function link(instance, sources) {
const wrappedMethods = {/* sources[key] -> wrappedMethods.method */}
const map = new Map(/* sources.creator -> wrappedMethods.method */)
diff --git a/src/driver/core/FileExplorer.js b/src/driver/core/FileExplorer.js
new file mode 100644
index 0000000..c876a65
--- /dev/null
+++ b/src/driver/core/FileExplorer.js
@@ -0,0 +1,186 @@
+import DOMHelper from '../../utils/DOMHelper'
+import treeParser from '../../utils/treeParser'
+import URLHelper from '../../utils/URLHelper'
+import VisibleNodesGenerator from '../../utils/VisibleNodesGenerator'
+
+function getVisibleParentNode(nodes, focusedNode, depths) {
+ const focusedNodeIndex = nodes.indexOf(focusedNode)
+ const focusedNodeDepth = depths.get(focusedNode)
+ let indexOfParentNode = focusedNodeIndex - 1
+ while (
+ indexOfParentNode !== -1 &&
+ depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth
+ ) {
+ --indexOfParentNode
+ }
+ const parentNode = nodes[indexOfParentNode]
+ return parentNode
+}
+
+const tasksAfterRender = []
+const visibleNodesGenerator = new VisibleNodesGenerator()
+
+const init = dispatch => () => dispatch(async (state, { treeData, metaData, compressSingletonFolder }) => {
+ const { root } = treeParser.parse(treeData, metaData)
+ visibleNodesGenerator.setCompress(compressSingletonFolder)
+ await visibleNodesGenerator.plantTree(root)
+ const currentPath = URLHelper.getCurrentPath(true)
+ tasksAfterRender.push(DOMHelper.focusSearchInput)
+ if (currentPath.length) {
+ const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
+ if (nodeExpandedTo) {
+ visibleNodesGenerator.focusNode(nodeExpandedTo)
+ const { nodes } = visibleNodesGenerator.visibleNodes
+ tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo)))
+ }
+ }
+ dispatch(updateVisibleNodes)
+})
+
+const execAfterRender = dispatch => () => {
+ for (const task of tasksAfterRender) {
+ task()
+ }
+ tasksAfterRender.length = 0
+}
+
+const handleKeyDown = dispatch => ({ key }) => dispatch(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => {
+ if (focusedNode) {
+ const focusedNodeIndex = nodes.indexOf(focusedNode)
+ switch (key) {
+ case 'ArrowUp':
+ // focus on previous node
+ if (focusedNodeIndex === 0) {
+ dispatch(focusNode, null)
+ tasksAfterRender.push(DOMHelper.focusSearchInput)
+ } else {
+ dispatch(focusNode, nodes[focusedNodeIndex - 1])
+ }
+ break
+
+ case 'ArrowDown':
+ // focus on next node
+ if (focusedNodeIndex + 1 < nodes.length) {
+ dispatch(focusNode, nodes[focusedNodeIndex + 1])
+ } else {
+ dispatch(focusNode, null)
+ tasksAfterRender.push(DOMHelper.focusSearchInput)
+ }
+ break
+
+ case 'ArrowLeft':
+ // collapse node or go to parent node
+ if (expandedNodes.has(focusedNode)) {
+ dispatch(setExpand, focusedNode, false)
+ } else {
+ // go forward to the start of the list, find the closest node with lower depth
+ const parentNode = getVisibleParentNode(nodes, focusedNode, depths)
+ if (parentNode) {
+ dispatch(focusNode, parentNode)
+ }
+ }
+ break
+
+ // consider the two keys as 'confirm' key
+ case 'ArrowRight':
+ // expand node or focus on first content node or redirect to file page
+ if (focusedNode.type === 'tree') {
+ if (expandedNodes.has(focusedNode)) {
+ const nextNode = nodes[focusedNodeIndex + 1]
+ if (depths.get(nextNode) > depths.get(focusedNode)) {
+ dispatch(focusNode, nextNode)
+ }
+ } else {
+ dispatch(setExpand, focusedNode, true)
+ }
+ } else if (focusedNode.type === 'blob') {
+ DOMHelper.loadWithPJAX(focusedNode.url)
+ } else if (focusedNode.type === 'commit') {
+ // redirect to its parent folder
+ DOMHelper.loadWithPJAX(focusedNode.parent.url)
+ }
+ break
+ case 'Enter':
+ // expand node or redirect to file page
+ if (focusedNode.type === 'tree') {
+ dispatch(setExpand, focusedNode, true)
+ } else if (focusedNode.type === 'blob') {
+ DOMHelper.loadWithPJAX(focusedNode.url)
+ } else if (focusedNode.type === 'commit') {
+ // redirect to its parent folder
+ DOMHelper.loadWithPJAX(focusedNode.parent.url)
+ }
+ break
+
+ }
+ } else {
+ // now search input is focused
+ if (nodes.length) {
+ switch (key) {
+ case 'ArrowDown':
+ dispatch(focusNode, nodes[0])
+ break
+ case 'ArrowUp':
+ dispatch(focusNode, nodes[nodes.length - 1])
+ break
+ }
+ }
+ }
+})
+
+const handleSearchKeyChange = dispatch => async event => {
+ const searchKey = event.target.value
+ await visibleNodesGenerator.search(searchKey)
+ dispatch(updateVisibleNodes)
+}
+
+const setExpand = dispatch => (node, expand) => {
+ visibleNodesGenerator.setExpand(node, expand)
+ dispatch(focusNode, node)
+ tasksAfterRender.push(DOMHelper.focusSearchInput)
+}
+
+const toggleNodeExpansion = dispatch => (node, skipScrollToNode) => {
+ visibleNodesGenerator.toggleExpand(node)
+ dispatch(focusNode, node, skipScrollToNode)
+ tasksAfterRender.push(DOMHelper.focusFileExplorer)
+}
+
+const focusNode = dispatch => (node, skipScroll) => dispatch(({ visibleNodes: { nodes } }) => {
+ visibleNodesGenerator.focusNode(node)
+ if (node && !skipScroll) {
+ // when focus a node not in viewport(by keyboard), scroll to it
+ const indexOfToBeFocusedNode = nodes.indexOf(node)
+ tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode))
+ tasksAfterRender.push(DOMHelper.focusSearchInput)
+ }
+ dispatch(updateVisibleNodes)
+})
+
+const onNodeClick = dispatch => (node) => {
+ if (node.type === 'tree') {
+ dispatch(toggleNodeExpansion, node, true)
+ } else if (node.type === 'blob') {
+ dispatch(focusNode, node, true)
+ DOMHelper.loadWithPJAX(node.url)
+ } else if (node.type === 'commit') {
+ DOMHelper.loadWithPJAX(node.parent.url)
+ }
+}
+
+const updateVisibleNodes = dispatch => () => {
+ const { visibleNodes } = visibleNodesGenerator
+ dispatch({ visibleNodes })
+}
+
+export default {
+ init,
+ execAfterRender,
+ handleKeyDown,
+ handleSearchKeyChange,
+ setExpand,
+ toggleNodeExpansion,
+ focusNode,
+ onNodeClick,
+ updateVisibleNodes,
+}
diff --git a/src/driver/core.js b/src/driver/core/SideBar.js
similarity index 91%
rename from src/driver/core.js
rename to src/driver/core/SideBar.js
index 298f9d7..c380c3d 100644
--- a/src/driver/core.js
+++ b/src/driver/core/SideBar.js
@@ -1,8 +1,8 @@
-import DOMHelper, { REPO_TYPE_PRIVATE } from '../utils/DOMHelper'
-import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS } from '../utils/GitHubHelper'
-import configHelper from '../utils/configHelper'
-import URLHelper from '../utils/URLHelper'
-import keyHelper from '../utils/keyHelper'
+import DOMHelper, { REPO_TYPE_PRIVATE } from '../../utils/DOMHelper'
+import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS } from '../../utils/GitHubHelper'
+import configHelper from '../../utils/configHelper'
+import URLHelper from '../../utils/URLHelper'
+import keyHelper from '../../utils/keyHelper'
const init = dispatch => async () => {
try {
diff --git a/src/driver/core/index.js b/src/driver/core/index.js
new file mode 100644
index 0000000..071ab89
--- /dev/null
+++ b/src/driver/core/index.js
@@ -0,0 +1,2 @@
+export { default as SideBar } from './SideBar'
+export { default as FileExplorer } from './FileExplorer'