Skip to content

Commit 066a61e

Browse files
committed
create plugin
1 parent b37ba12 commit 066a61e

File tree

17 files changed

+458
-2
lines changed

17 files changed

+458
-2
lines changed

.babelrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["react", "es2015"]
3+
}

.eslintrc.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"extends": "eslint-config-airbnb",
3+
"env": {
4+
"browser": true,
5+
"es6": true
6+
},
7+
"plugins": [
8+
"react"
9+
],
10+
"parserOptions": {
11+
"ecmaVersion": 6,
12+
"sourceType": "module",
13+
"ecmaFeatures": {
14+
"jsx": true
15+
}
16+
},
17+
"rules": {
18+
"indent": ["error", 2],
19+
"semi": "error",
20+
"eol-last": "off",
21+
"react/jsx-uses-vars": "error",
22+
"react/jsx-uses-react": "error",
23+
"comma-dangle": "off",
24+
"react/jsx-filename-extension": ["error", {"extensions": [".js", ".jsx"]}],
25+
"react/prefer-stateless-function": "off"
26+
}
27+
}

.gitignore

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
bundle.js
2+
node_modules/
3+
*.class
4+
*.log
5+
6+
# sbt specific
7+
dist/*
8+
target/
9+
lib_managed/
10+
src_managed/
11+
project/boot/
12+
project/plugins/project/
13+
14+
# Scala-IDE specific
15+
.scala_dependencies
16+
.classpath
17+
.project
18+
.cache
19+
.settings
20+
21+
# IntelliJ specific
22+
.idea/
23+
.idea_modules/
24+
25+
# Ensime
26+
.ensime
27+
.ensime_cache/

.vscode/tasks.json

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "0.1.0",
5+
"command": "npm",
6+
"isShellCommand": true,
7+
"showOutput": "always",
8+
"suppressTaskName": true,
9+
"tasks": [
10+
{
11+
"taskName": "install",
12+
"args": ["install"]
13+
},
14+
{
15+
"taskName": "update",
16+
"args": ["update"]
17+
},
18+
{
19+
"taskName": "test",
20+
"args": ["run", "test"]
21+
},
22+
{
23+
"taskName": "build",
24+
"isBuildCommand": true,
25+
"args": ["run", "build"]
26+
},
27+
{
28+
"taskName": "watch",
29+
"isBackground": true,
30+
"args": ["run", "watch"]
31+
},
32+
{
33+
"taskName": "release",
34+
"args": ["run", "release"]
35+
}
36+
]
37+
}

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ gitbucket-groupsmenu-plugin
33

44
The plugin provides a file tree of repository [GitBucket](https://github.com/gitbucket/gitbucket).
55

6-
![screenshot](./screenshot.png)
7-
86
Version
97
---
108

build.sbt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name := "gitbucket-explorer-plugin"
2+
organization := "io.github.gitbucket"
3+
version := "1.0.1"
4+
scalaVersion := "2.11.8"
5+
6+
libraryDependencies ++= Seq(
7+
"io.github.gitbucket" %% "gitbucket" % "4.9.0" % "provided",
8+
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided"
9+
)
10+
11+
scalacOptions := Seq("-deprecation", "-feature", "-language:postfixOps")

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "gitbucket-explorer-plugin",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"build": "browserify -t babelify ./scripts/index.js -o ./src/main/resources/explorer/assets/bundle.js | sbt package",
9+
"watch": "watchify -t babelify ./scripts/index.js -o ./src/main/resources/explorer/assets/bundle.js",
10+
"release": "set NODE_ENV=production && browserify ./scripts/index.js -t babelify -t envify | uglifyjs -c warnings=false > ./src/main/resources/explorer/assets/bundle.js | sbt clean package"
11+
},
12+
"author": "tomoki1207",
13+
"license": "MIT",
14+
"devDependencies": {
15+
"babel-preset-es2015": "^6.22.0",
16+
"babel-preset-react": "^6.22.0",
17+
"babelify": "^7.3.0",
18+
"browserify": "^14.0.0",
19+
"envify": "^4.0.0",
20+
"eslint": "^3.14.1",
21+
"eslint-config-airbnb": "^14.0.0",
22+
"eslint-plugin-import": "^2.2.0",
23+
"eslint-plugin-jsx-a11y": "^3.0.2",
24+
"eslint-plugin-react": "^6.9.0",
25+
"uglify-js": "^2.7.5",
26+
"watchify": "^3.9.0"
27+
},
28+
"dependencies": {
29+
"react": "^15.4.2",
30+
"react-dom": "^15.4.2",
31+
"react-localstorage": "^0.3.0",
32+
"react-mixin": "^3.0.5",
33+
"superagent": "^3.4.1"
34+
}
35+
}

project/build.properties

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version = 0.13.12

project/plugins.sbt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
logLevel := Level.Warn
2+
3+
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")

scripts/components/Directory.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { PropTypes } from 'react';
2+
import ReactMixin from 'react-mixin';
3+
import LocalStorageMixin from 'react-localstorage';
4+
import request from 'superagent';
5+
import FileNodes from './FileNodes';
6+
7+
export default class Directory extends React.Component {
8+
9+
static get propTypes() {
10+
return {
11+
url: PropTypes.string,
12+
name: PropTypes.string
13+
};
14+
}
15+
static get getDefaultProps() {
16+
return {
17+
url: '',
18+
name: ''
19+
};
20+
}
21+
22+
constructor(props) {
23+
super(props);
24+
this.state = {
25+
children: [],
26+
expanded: false,
27+
};
28+
}
29+
30+
toggleFolder(path) {
31+
if (this.state.expanded) {
32+
this.setState({
33+
expanded: false,
34+
children: []
35+
});
36+
} else {
37+
this.setState({ expanded: true });
38+
request
39+
.get(path)
40+
.end((err, res) => {
41+
if (err) {
42+
return;
43+
}
44+
this.setState({ children: JSON.parse(res.text) });
45+
});
46+
}
47+
}
48+
49+
render() {
50+
const arrow = this.state.expanded ? 'octicon octicon-chevron-down' : 'octicon octicon-chevron-right';
51+
return (
52+
<li className="folder-node">
53+
<button className="folder-expander" onClick={() => this.toggleFolder(this.props.url)}>
54+
<i className={arrow} />
55+
<i className="menu-icon octicon octicon-file-directory" />
56+
{this.props.name}
57+
</button>
58+
<FileNodes data={this.state.children} style={this.state.expanded ? {} : { display: 'none' }} />
59+
</li>
60+
);
61+
}
62+
}
63+
ReactMixin(Directory.prototype, LocalStorageMixin);

scripts/components/File.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { PropTypes } from 'react';
2+
3+
export default class File extends React.Component {
4+
5+
static get propTypes() {
6+
return {
7+
url: PropTypes.string.isRequired,
8+
name: PropTypes.string.isRequired,
9+
};
10+
}
11+
12+
render() {
13+
return (
14+
<li className="file-node">
15+
<a href={this.props.url}>
16+
<i className="menu-icon octicon octicon-file" />
17+
{this.props.name}
18+
</a>
19+
</li>
20+
);
21+
}
22+
}

scripts/components/FileNodes.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, { PropTypes } from 'react';
2+
import File from './File';
3+
import Directory from './Directory';
4+
5+
export default class FileNodes extends React.Component {
6+
7+
static get propTypes() {
8+
return {
9+
data: PropTypes.array.isRequired,
10+
};
11+
}
12+
13+
render() {
14+
const nodes = this.props.data.map(node => (
15+
node.isDirectory ?
16+
<Directory localStorageKey={node.url} key={node.url} name={node.name} url={node.url.replace('/tree/', '/explore/')} />
17+
: <File key={node.url} name={node.name} url={node.url} />
18+
));
19+
return (
20+
<ul>
21+
{nodes}
22+
</ul>
23+
);
24+
}
25+
}

scripts/components/Root.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import FileNodes from './FileNodes';
3+
import Directory from './Directory';
4+
5+
export default class Root extends Directory {
6+
7+
componentWillMount() {
8+
// FIXME: exists altanarive way?
9+
const baseUrl = document.querySelector('header.main-header a.logo').getAttribute('href');
10+
const relPath = document.location.pathname.replace(baseUrl, '');
11+
this.setState({
12+
rootPath: `${baseUrl}${relPath.match(/^\/?[^/]+\/[^/]+/)}`,
13+
});
14+
}
15+
16+
render() {
17+
const arrow = this.state.expanded ? 'octicon octicon-chevron-down' : 'octicon octicon-chevron-right';
18+
return (
19+
<div className="tree-node">
20+
<button className="root-expander" onClick={() => this.toggleFolder(`${this.state.rootPath}/explore/`)} >
21+
<i className={arrow} />
22+
</button>
23+
<a href={this.state.rootPath} className="submenu-files" >
24+
<i className="menu-icon octicon octicon-file-directory" />
25+
Files
26+
</a>
27+
<div className="file-tree" style={this.state.expanded ? {} : { display: 'none' }} >
28+
<FileNodes data={this.state.children} />
29+
</div>
30+
</div>
31+
);
32+
}
33+
}

scripts/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
import { render } from 'react-dom';
3+
import Root from './components/Root';
4+
5+
render(
6+
<Root />,
7+
document.querySelector('div.main-sidebar ul.sidebar-menu li:first-child')
8+
);

0 commit comments

Comments
 (0)