fix #260
Former-commit-id: 0d8742754bb756ad3a83599850dae5f477282430 [formerly 5cb7d75b695d8400fc2af87edd551d6450e7365f] [formerly a6a814c40a5ff4f195c4ab470d4fccc92bd8c1c8 [formerly 99c8c92c6c6d1225380dbbfc5b61d4263a129156]] Former-commit-id: 45eba5ff05f8e64fbf33d9d670e19a0cf4880656 [formerly 88dc856045b9d51596f36ce387b1c4f3e85a7d3c] Former-commit-id: 1eadaef460060da8ae71df3c66f242c844992725
This commit is contained in:
parent
95d43a344c
commit
3d54b2bd90
26
.babelrc
26
.babelrc
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"presets": [
|
"presets": [
|
||||||
["env", { "modules": false }],
|
["env", { "modules": false }],
|
||||||
"stage-2"
|
"stage-2"
|
||||||
],
|
],
|
||||||
"plugins": ["transform-runtime"],
|
"plugins": ["transform-runtime"],
|
||||||
"env": {
|
"env": {
|
||||||
"test": {
|
"test": {
|
||||||
"presets": ["env", "stage-2"],
|
"presets": ["env", "stage-2"],
|
||||||
"plugins": [ "istanbul" ]
|
"plugins": [ "istanbul" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
assets/
|
assets/
|
||||||
testdata/
|
testdata/
|
||||||
caddy/
|
caddy/
|
||||||
.github/
|
.github/
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
# 4 space indentation
|
# 4 space indentation
|
||||||
[*.go]
|
[*.go]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 4
|
indent_size = 4
|
|
@ -1,2 +1,2 @@
|
||||||
build/*.js
|
build/*.js
|
||||||
config/*.js
|
config/*.js
|
||||||
|
|
54
.eslintrc.js
54
.eslintrc.js
|
@ -1,27 +1,27 @@
|
||||||
// http://eslint.org/docs/user-guide/configuring
|
// http://eslint.org/docs/user-guide/configuring
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: 'babel-eslint',
|
parser: 'babel-eslint',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
sourceType: 'module'
|
sourceType: 'module'
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
},
|
},
|
||||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||||
extends: 'standard',
|
extends: 'standard',
|
||||||
// required to lint *.vue files
|
// required to lint *.vue files
|
||||||
plugins: [
|
plugins: [
|
||||||
'html'
|
'html'
|
||||||
],
|
],
|
||||||
// add your custom rules here
|
// add your custom rules here
|
||||||
'rules': {
|
'rules': {
|
||||||
// allow paren-less arrow functions
|
// allow paren-less arrow functions
|
||||||
'arrow-parens': 0,
|
'arrow-parens': 0,
|
||||||
// allow async-await
|
// allow async-await
|
||||||
'generator-star-spacing': 0,
|
'generator-star-spacing': 0,
|
||||||
// allow debugger during development
|
// allow debugger during development
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
### Instructions (remove before submitting):
|
### Instructions (remove before submitting):
|
||||||
|
|
||||||
1. Are you asking for help with using Caddy or File Manager? Please use our forum instead: https://forum.caddyserver.com.
|
1. Are you asking for help with using Caddy or File Manager? Please use our forum instead: https://forum.caddyserver.com.
|
||||||
2. If you are filing a bug report, please answer the following questions.
|
2. If you are filing a bug report, please answer the following questions.
|
||||||
3. If your issue is not a bug report, you do not need to use this template.
|
3. If your issue is not a bug report, you do not need to use this template.
|
||||||
4. If not using with Caddy, ignore questions 1 and 2.
|
4. If not using with Caddy, ignore questions 1 and 2.
|
||||||
|
|
||||||
### 1. Have you downloaded File Manager from caddyserver.com? If yes, when have you done that? If no, and you are running a custom build, which is the revision of File Manager's repository?
|
### 1. Have you downloaded File Manager from caddyserver.com? If yes, when have you done that? If no, and you are running a custom build, which is the revision of File Manager's repository?
|
||||||
|
|
||||||
### 2. What is your entire Caddyfile?
|
### 2. What is your entire Caddyfile?
|
||||||
```text
|
```text
|
||||||
(Put Caddyfile here)
|
(Put Caddyfile here)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. What are you trying to do?
|
### 3. What are you trying to do?
|
||||||
|
|
||||||
|
|
||||||
### 4. What did you expect to see?
|
### 4. What did you expect to see?
|
||||||
|
|
||||||
|
|
||||||
### 5. What did you see instead (give full error messages and/or log)?
|
### 5. What did you see instead (give full error messages and/or log)?
|
||||||
|
|
||||||
|
|
||||||
### 6. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?
|
### 6. How can someone who is starting from scratch reproduce this behaviour as minimally as possible?
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
build:
|
build:
|
||||||
main: cmd/filemanager/main.go
|
main: cmd/filemanager/main.go
|
||||||
binary: filemanager
|
binary: filemanager
|
||||||
goos:
|
goos:
|
||||||
- darwin
|
- darwin
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- freebsd
|
- freebsd
|
||||||
- netbsd
|
- netbsd
|
||||||
- openbsd
|
- openbsd
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- 386
|
- 386
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
ignore:
|
ignore:
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 6
|
goarm: 6
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 6
|
goarm: 6
|
||||||
|
|
||||||
archive:
|
archive:
|
||||||
name_template: "{{.Os}}-{{.Arch}}-{{ .ProjectName }}"
|
name_template: "{{.Os}}-{{.Arch}}-{{ .ProjectName }}"
|
||||||
format: tar.gz
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
format: zip
|
||||||
|
|
|
@ -1,46 +1,46 @@
|
||||||
# Contributor Covenant Code of Conduct
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
* Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
* Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
* Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
* Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hacdias@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hacdias@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
[homepage]: http://contributor-covenant.org
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
If you want to contribute or want to build the code from source, you will need to have the most recent version of Go and, if you want to change the static assets (JS, CSS, ...), Node.js installed on your computer. To start developing, you just need to do the following:
|
If you want to contribute or want to build the code from source, you will need to have the most recent version of Go and, if you want to change the static assets (JS, CSS, ...), Node.js installed on your computer. To start developing, you just need to do the following:
|
||||||
|
|
||||||
1. `go get github.com/hacdias/filemanager`
|
1. `go get github.com/hacdias/filemanager`
|
||||||
2. `cd $GOPATH/src/github.com/hacdias/filemanager`
|
2. `cd $GOPATH/src/github.com/hacdias/filemanager`
|
||||||
3. `npm install`
|
3. `npm install`
|
||||||
4. `npm run dev` - regenerates the static assets automatically
|
4. `npm run dev` - regenerates the static assets automatically
|
||||||
5. `go install github.com/hacdias/filemanager/cmd/filemanager`
|
5. `go install github.com/hacdias/filemanager/cmd/filemanager`
|
||||||
6. Execute `$GOPATH/bin/filemanager`
|
6. Execute `$GOPATH/bin/filemanager`
|
||||||
|
|
||||||
The steps 3 and 4 are only required **if you want to develop the front-end**. Otherwise, you can ignore them. Before pulling, if you made any change on assets folder, you must run the `build.sh` script on the root of this repository.
|
The steps 3 and 4 are only required **if you want to develop the front-end**. Otherwise, you can ignore them. Before pulling, if you made any change on assets folder, you must run the `build.sh` script on the root of this repository.
|
||||||
|
|
||||||
If you are using this as a Caddy plugin, you should use its [official instructions for plugins](https://github.com/mholt/caddy/wiki/Extending-Caddy#2-plug-in-your-plugin) and import `github.com/hacdias/filemanager/caddy/filemanager`.
|
If you are using this as a Caddy plugin, you should use its [official instructions for plugins](https://github.com/mholt/caddy/wiki/Extending-Caddy#2-plug-in-your-plugin) and import `github.com/hacdias/filemanager/caddy/filemanager`.
|
||||||
|
|
44
Dockerfile
44
Dockerfile
|
@ -1,22 +1,22 @@
|
||||||
FROM golang:alpine
|
FROM golang:alpine
|
||||||
|
|
||||||
COPY . /go/src/github.com/hacdias/filemanager
|
COPY . /go/src/github.com/hacdias/filemanager
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/hacdias/filemanager
|
WORKDIR /go/src/github.com/hacdias/filemanager
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
RUN go get ./...
|
RUN go get ./...
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
|
WORKDIR /go/src/github.com/hacdias/filemanager/cmd/filemanager
|
||||||
RUN CGO_ENABLED=0 go build -a
|
RUN CGO_ENABLED=0 go build -a
|
||||||
RUN mv filemanager /go/bin/filemanager
|
RUN mv filemanager /go/bin/filemanager
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY --from=0 /go/bin/filemanager /filemanager
|
COPY --from=0 /go/bin/filemanager /filemanager
|
||||||
|
|
||||||
VOLUME /srv
|
VOLUME /srv
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
COPY Docker.json /config.json
|
COPY Docker.json /config.json
|
||||||
|
|
||||||
ENTRYPOINT ["/filemanager"]
|
ENTRYPOINT ["/filemanager"]
|
||||||
CMD ["--config", "/config.json"]
|
CMD ["--config", "/config.json"]
|
||||||
|
|
402
LICENSE.md
402
LICENSE.md
|
@ -1,201 +1,201 @@
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
1. Definitions.
|
1. Definitions.
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
the copyright owner that is granting the License.
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
other entities that control, are controlled by, or are under common
|
other entities that control, are controlled by, or are under common
|
||||||
control with that entity. For the purposes of this definition,
|
control with that entity. For the purposes of this definition,
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
direction or management of such entity, whether by contract or
|
direction or management of such entity, whether by contract or
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
exercising permissions granted by this License.
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
including but not limited to software source code, documentation
|
including but not limited to software source code, documentation
|
||||||
source, and configuration files.
|
source, and configuration files.
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
"Object" form shall mean any form resulting from mechanical
|
||||||
transformation or translation of a Source form, including but
|
transformation or translation of a Source form, including but
|
||||||
not limited to compiled object code, generated documentation,
|
not limited to compiled object code, generated documentation,
|
||||||
and conversions to other media types.
|
and conversions to other media types.
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
Object form, made available under the License, as indicated by a
|
Object form, made available under the License, as indicated by a
|
||||||
copyright notice that is included in or attached to the work
|
copyright notice that is included in or attached to the work
|
||||||
(an example is provided in the Appendix below).
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
form, that is based on (or derived from) the Work and for which the
|
form, that is based on (or derived from) the Work and for which the
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
of this License, Derivative Works shall not include works that remain
|
of this License, Derivative Works shall not include works that remain
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
the Work and Derivative Works thereof.
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
"Contribution" shall mean any work of authorship, including
|
||||||
the original version of the Work and any modifications or additions
|
the original version of the Work and any modifications or additions
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
means any form of electronic, verbal, or written communication sent
|
means any form of electronic, verbal, or written communication sent
|
||||||
to the Licensor or its representatives, including but not limited to
|
to the Licensor or its representatives, including but not limited to
|
||||||
communication on electronic mailing lists, source code control systems,
|
communication on electronic mailing lists, source code control systems,
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
excluding communication that is conspicuously marked or otherwise
|
excluding communication that is conspicuously marked or otherwise
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
subsequently incorporated within the Work.
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
Work and such Derivative Works in Source or Object form.
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
(except as stated in this section) patent license to make, have made,
|
(except as stated in this section) patent license to make, have made,
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
where such license applies only to those patent claims licensable
|
where such license applies only to those patent claims licensable
|
||||||
by such Contributor that are necessarily infringed by their
|
by such Contributor that are necessarily infringed by their
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
institute patent litigation against any entity (including a
|
institute patent litigation against any entity (including a
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
or contributory patent infringement, then any patent licenses
|
or contributory patent infringement, then any patent licenses
|
||||||
granted to You under this License for that Work shall terminate
|
granted to You under this License for that Work shall terminate
|
||||||
as of the date such litigation is filed.
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
modifications, and in Source or Object form, provided that You
|
modifications, and in Source or Object form, provided that You
|
||||||
meet the following conditions:
|
meet the following conditions:
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
(a) You must give any other recipients of the Work or
|
||||||
Derivative Works a copy of this License; and
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
(b) You must cause any modified files to carry prominent notices
|
||||||
stating that You changed the files; and
|
stating that You changed the files; and
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
that You distribute, all copyright, patent, trademark, and
|
that You distribute, all copyright, patent, trademark, and
|
||||||
attribution notices from the Source form of the Work,
|
attribution notices from the Source form of the Work,
|
||||||
excluding those notices that do not pertain to any part of
|
excluding those notices that do not pertain to any part of
|
||||||
the Derivative Works; and
|
the Derivative Works; and
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
distribution, then any Derivative Works that You distribute must
|
distribution, then any Derivative Works that You distribute must
|
||||||
include a readable copy of the attribution notices contained
|
include a readable copy of the attribution notices contained
|
||||||
within such NOTICE file, excluding those notices that do not
|
within such NOTICE file, excluding those notices that do not
|
||||||
pertain to any part of the Derivative Works, in at least one
|
pertain to any part of the Derivative Works, in at least one
|
||||||
of the following places: within a NOTICE text file distributed
|
of the following places: within a NOTICE text file distributed
|
||||||
as part of the Derivative Works; within the Source form or
|
as part of the Derivative Works; within the Source form or
|
||||||
documentation, if provided along with the Derivative Works; or,
|
documentation, if provided along with the Derivative Works; or,
|
||||||
within a display generated by the Derivative Works, if and
|
within a display generated by the Derivative Works, if and
|
||||||
wherever such third-party notices normally appear. The contents
|
wherever such third-party notices normally appear. The contents
|
||||||
of the NOTICE file are for informational purposes only and
|
of the NOTICE file are for informational purposes only and
|
||||||
do not modify the License. You may add Your own attribution
|
do not modify the License. You may add Your own attribution
|
||||||
notices within Derivative Works that You distribute, alongside
|
notices within Derivative Works that You distribute, alongside
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
that such additional attribution notices cannot be construed
|
that such additional attribution notices cannot be construed
|
||||||
as modifying the License.
|
as modifying the License.
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
You may add Your own copyright statement to Your modifications and
|
||||||
may provide additional or different license terms and conditions
|
may provide additional or different license terms and conditions
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
the conditions stated in this License.
|
the conditions stated in this License.
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
this License, without any additional terms or conditions.
|
this License, without any additional terms or conditions.
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
the terms of any separate license agreement you may have executed
|
the terms of any separate license agreement you may have executed
|
||||||
with Licensor regarding such Contributions.
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
except as required for reasonable and customary use in describing the
|
except as required for reasonable and customary use in describing the
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
implied, including, without limitation, any warranties or conditions
|
implied, including, without limitation, any warranties or conditions
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
appropriateness of using or redistributing the Work and assume any
|
appropriateness of using or redistributing the Work and assume any
|
||||||
risks associated with Your exercise of permissions under this License.
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
unless required by applicable law (such as deliberate and grossly
|
unless required by applicable law (such as deliberate and grossly
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
liable to You for damages, including any direct, indirect, special,
|
liable to You for damages, including any direct, indirect, special,
|
||||||
incidental, or consequential damages of any character arising as a
|
incidental, or consequential damages of any character arising as a
|
||||||
result of this License or out of the use or inability to use the
|
result of this License or out of the use or inability to use the
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
other commercial damages or losses), even if such Contributor
|
other commercial damages or losses), even if such Contributor
|
||||||
has been advised of the possibility of such damages.
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
or other liability obligations and/or rights consistent with this
|
or other liability obligations and/or rights consistent with this
|
||||||
License. However, in accepting such obligations, You may act only
|
License. However, in accepting such obligations, You may act only
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
defend, and hold each Contributor harmless for any liability
|
defend, and hold each Contributor harmless for any liability
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
of your accepting any such warranty or additional liability.
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
To apply the Apache License to your work, attach the following
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
replaced with your own identifying information. (Don't include
|
replaced with your own identifying information. (Don't include
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
comment syntax for the file format. We also recommend that a
|
comment syntax for the file format. We also recommend that a
|
||||||
file or class name and description of purpose be included on the
|
file or class name and description of purpose be included on the
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
156
README.md
156
README.md
|
@ -1,78 +1,78 @@
|
||||||

|

|
||||||
|
|
||||||
# filemanager
|
# filemanager
|
||||||
|
|
||||||
[](https://travis-ci.org/hacdias/filemanager)
|
[](https://travis-ci.org/hacdias/filemanager)
|
||||||
[](https://goreportcard.com/report/hacdias/filemanager)
|
[](https://goreportcard.com/report/hacdias/filemanager)
|
||||||
[](http://godoc.org/github.com/hacdias/filemanager)
|
[](http://godoc.org/github.com/hacdias/filemanager)
|
||||||
|
|
||||||
filemanager provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
|
filemanager provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app or as a middleware.
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
|
|
||||||
+ [Getting started](#getting-started)
|
+ [Getting started](#getting-started)
|
||||||
+ [Features](#features)
|
+ [Features](#features)
|
||||||
- [Users](#users)
|
- [Users](#users)
|
||||||
- [Search](#search)
|
- [Search](#search)
|
||||||
+ [Contributing](#contributing)
|
+ [Contributing](#contributing)
|
||||||
+ [Donate](#donate)
|
+ [Donate](#donate)
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
|
|
||||||
You can find the Getting Started guide on the [documentation](https://henriquedias.com/filemanager/quick-start/).
|
You can find the Getting Started guide on the [documentation](https://henriquedias.com/filemanager/quick-start/).
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
Easy login system.
|
Easy login system.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Listings of your files, available in two styles: mosaic and list. You can delete, move, rename, upload and create new files, as well as directories. Single files can be downloaded directly, and multiple files as *.zip*, *.tar*, *.tar.gz*, *.tar.bz2* or *.tar.xz*.
|
Listings of your files, available in two styles: mosaic and list. You can delete, move, rename, upload and create new files, as well as directories. Single files can be downloaded directly, and multiple files as *.zip*, *.tar*, *.tar.gz*, *.tar.bz2* or *.tar.xz*.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
File Manager editor is powered by [Codemirror](https://codemirror.net/) and if you're working with markdown files with metadata, both parts will be separated from each other so you can focus on the content.
|
File Manager editor is powered by [Codemirror](https://codemirror.net/) and if you're working with markdown files with metadata, both parts will be separated from each other so you can focus on the content.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
On the settings page, a regular user can set its own custom CSS to personalize the experience and change its password. For admins, they can manage the permissions of each user, set commands which can be executed when certain events are triggered (such as before saving and after saving) and change plugin's settings.
|
On the settings page, a regular user can set its own custom CSS to personalize the experience and change its password. For admins, they can manage the permissions of each user, set commands which can be executed when certain events are triggered (such as before saving and after saving) and change plugin's settings.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
We also allow the users to search in the directories and execute commands if allowed.
|
We also allow the users to search in the directories and execute commands if allowed.
|
||||||
|
|
||||||
## Users
|
## Users
|
||||||
|
|
||||||
We support multiple users and each user can have its own scope and custom stylesheet. The administrator is able to choose which permissions should be given to the users, as well as the commands they can execute. Each user also have a set of rules, in which he can be prevented or allowed to access some directories (regular expressions included!).
|
We support multiple users and each user can have its own scope and custom stylesheet. The administrator is able to choose which permissions should be given to the users, as well as the commands they can execute. Each user also have a set of rules, in which he can be prevented or allowed to access some directories (regular expressions included!).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Search
|
## Search
|
||||||
|
|
||||||
FileManager allows you to search through your files and it has some options. By default, your search will be something like this:
|
FileManager allows you to search through your files and it has some options. By default, your search will be something like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
this are keywords
|
this are keywords
|
||||||
```
|
```
|
||||||
|
|
||||||
If you search for that it will look at every file that contains "this", "are" or "keywords" on their name. If you want to search for an exact term, you should surround your search by double quotes:
|
If you search for that it will look at every file that contains "this", "are" or "keywords" on their name. If you want to search for an exact term, you should surround your search by double quotes:
|
||||||
|
|
||||||
```
|
```
|
||||||
"this is the name"
|
"this is the name"
|
||||||
```
|
```
|
||||||
|
|
||||||
That will search for any file that contains "this is the name" on its name. It won't search for each separated term this time.
|
That will search for any file that contains "this is the name" on its name. It won't search for each separated term this time.
|
||||||
|
|
||||||
By default, every search will be case sensitive. Although, you can make a case insensitive search by adding `case:insensitive` to the search terms, like this:
|
By default, every search will be case sensitive. Although, you can make a case insensitive search by adding `case:insensitive` to the search terms, like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
this are keywords case:insensitive
|
this are keywords case:insensitive
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
The contributing guidelines can be found [here](https://github.com/hacdias/filemanager/blob/master/CONTRIBUTING.md).
|
The contributing guidelines can be found [here](https://github.com/hacdias/filemanager/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
# Donate
|
# Donate
|
||||||
|
|
||||||
Enjoying this project? You can [donate to its creator](https://henriquedias.com/donate/). He will appreciate.
|
Enjoying this project? You can [donate to its creator](https://henriquedias.com/donate/). He will appreciate.
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
require('./check-versions')()
|
require('./check-versions')()
|
||||||
|
|
||||||
process.env.NODE_ENV = 'production'
|
process.env.NODE_ENV = 'production'
|
||||||
|
|
||||||
var ora = require('ora')
|
var ora = require('ora')
|
||||||
var rm = require('rimraf')
|
var rm = require('rimraf')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var chalk = require('chalk')
|
var chalk = require('chalk')
|
||||||
var webpack = require('webpack')
|
var webpack = require('webpack')
|
||||||
var config = require('./config')
|
var config = require('./config')
|
||||||
var webpackConfig = require('./webpack.prod.conf')
|
var webpackConfig = require('./webpack.prod.conf')
|
||||||
|
|
||||||
var spinner = ora('building for production...')
|
var spinner = ora('building for production...')
|
||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
rm(path.join(config.assetsRoot, config.assetsSubDirectory), err => {
|
rm(path.join(config.assetsRoot, config.assetsSubDirectory), err => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
webpack(webpackConfig, function (err, stats) {
|
webpack(webpackConfig, function (err, stats) {
|
||||||
spinner.stop()
|
spinner.stop()
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
process.stdout.write(stats.toString({
|
process.stdout.write(stats.toString({
|
||||||
colors: true,
|
colors: true,
|
||||||
modules: false,
|
modules: false,
|
||||||
children: false,
|
children: false,
|
||||||
chunks: false,
|
chunks: false,
|
||||||
chunkModules: false
|
chunkModules: false
|
||||||
}) + '\n\n')
|
}) + '\n\n')
|
||||||
|
|
||||||
console.log(chalk.cyan(' Build complete.\n'))
|
console.log(chalk.cyan(' Build complete.\n'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
var chalk = require('chalk')
|
var chalk = require('chalk')
|
||||||
var semver = require('semver')
|
var semver = require('semver')
|
||||||
var packageConfig = require('../../package.json')
|
var packageConfig = require('../../package.json')
|
||||||
var shell = require('shelljs')
|
var shell = require('shelljs')
|
||||||
function exec (cmd) {
|
function exec (cmd) {
|
||||||
return require('child_process').execSync(cmd).toString().trim()
|
return require('child_process').execSync(cmd).toString().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionRequirements = [
|
var versionRequirements = [
|
||||||
{
|
{
|
||||||
name: 'node',
|
name: 'node',
|
||||||
currentVersion: semver.clean(process.version),
|
currentVersion: semver.clean(process.version),
|
||||||
versionRequirement: packageConfig.engines.node
|
versionRequirement: packageConfig.engines.node
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if (shell.which('npm')) {
|
if (shell.which('npm')) {
|
||||||
versionRequirements.push({
|
versionRequirements.push({
|
||||||
name: 'npm',
|
name: 'npm',
|
||||||
currentVersion: exec('npm --version'),
|
currentVersion: exec('npm --version'),
|
||||||
versionRequirement: packageConfig.engines.npm
|
versionRequirement: packageConfig.engines.npm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
var warnings = []
|
var warnings = []
|
||||||
for (var i = 0; i < versionRequirements.length; i++) {
|
for (var i = 0; i < versionRequirements.length; i++) {
|
||||||
var mod = versionRequirements[i]
|
var mod = versionRequirements[i]
|
||||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||||
warnings.push(mod.name + ': ' +
|
warnings.push(mod.name + ': ' +
|
||||||
chalk.red(mod.currentVersion) + ' should be ' +
|
chalk.red(mod.currentVersion) + ' should be ' +
|
||||||
chalk.green(mod.versionRequirement)
|
chalk.green(mod.versionRequirement)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warnings.length) {
|
if (warnings.length) {
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
||||||
console.log()
|
console.log()
|
||||||
for (var i = 0; i < warnings.length; i++) {
|
for (var i = 0; i < warnings.length; i++) {
|
||||||
var warning = warnings[i]
|
var warning = warnings[i]
|
||||||
console.log(' ' + warning)
|
console.log(' ' + warning)
|
||||||
}
|
}
|
||||||
console.log()
|
console.log()
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
index: path.resolve(__dirname, '../dist/index.html'),
|
index: path.resolve(__dirname, '../dist/index.html'),
|
||||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||||
assetsSubDirectory: 'static',
|
assetsSubDirectory: 'static',
|
||||||
assetsPublicPath: '{{ .BaseURL }}/',
|
assetsPublicPath: '{{ .BaseURL }}/',
|
||||||
build: {
|
build: {
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: '"production"'
|
NODE_ENV: '"production"'
|
||||||
},
|
},
|
||||||
productionSourceMap: true,
|
productionSourceMap: true,
|
||||||
// Run the build command with an extra argument to
|
// Run the build command with an extra argument to
|
||||||
// View the bundle analyzer report after build finishes:
|
// View the bundle analyzer report after build finishes:
|
||||||
// `npm run build --report`
|
// `npm run build --report`
|
||||||
// Set to `true` or `false` to always turn it on or off
|
// Set to `true` or `false` to always turn it on or off
|
||||||
bundleAnalyzerReport: process.env.npm_config_report
|
bundleAnalyzerReport: process.env.npm_config_report
|
||||||
},
|
},
|
||||||
dev: {
|
dev: {
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: '"development"'
|
NODE_ENV: '"development"'
|
||||||
},
|
},
|
||||||
produceSourceMap: true
|
produceSourceMap: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
// This service worker file is effectively a 'no-op' that will reset any
|
// This service worker file is effectively a 'no-op' that will reset any
|
||||||
// previous service worker registered for the same host:port combination.
|
// previous service worker registered for the same host:port combination.
|
||||||
// In the production build, this file is replaced with an actual service worker
|
// In the production build, this file is replaced with an actual service worker
|
||||||
// file that will precache your site's local assets.
|
// file that will precache your site's local assets.
|
||||||
// See https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
|
// See https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
|
||||||
|
|
||||||
self.addEventListener('install', () => self.skipWaiting());
|
self.addEventListener('install', () => self.skipWaiting());
|
||||||
|
|
||||||
self.addEventListener('activate', () => {
|
self.addEventListener('activate', () => {
|
||||||
self.clients.matchAll({ type: 'window' }).then(windowClients => {
|
self.clients.matchAll({ type: 'window' }).then(windowClients => {
|
||||||
for (let windowClient of windowClients) {
|
for (let windowClient of windowClients) {
|
||||||
// Force open pages to refresh, so that they have a chance to load the
|
// Force open pages to refresh, so that they have a chance to load the
|
||||||
// fresh navigation response from the local dev server.
|
// fresh navigation response from the local dev server.
|
||||||
windowClient.navigate(windowClient.url);
|
windowClient.navigate(windowClient.url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,55 +1,55 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// Check to make sure service workers are supported in the current browser,
|
// Check to make sure service workers are supported in the current browser,
|
||||||
// and that the current page is accessed from a secure origin. Using a
|
// and that the current page is accessed from a secure origin. Using a
|
||||||
// service worker from an insecure origin will trigger JS console errors.
|
// service worker from an insecure origin will trigger JS console errors.
|
||||||
const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
|
const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === '[::1]' ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
window.addEventListener('load', function() {
|
window.addEventListener('load', function() {
|
||||||
if ('serviceWorker' in navigator &&
|
if ('serviceWorker' in navigator &&
|
||||||
(window.location.protocol === 'https:' || isLocalhost)) {
|
(window.location.protocol === 'https:' || isLocalhost)) {
|
||||||
navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
|
navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
|
||||||
.then(function(registration) {
|
.then(function(registration) {
|
||||||
// updatefound is fired if service-worker.js changes.
|
// updatefound is fired if service-worker.js changes.
|
||||||
registration.onupdatefound = function() {
|
registration.onupdatefound = function() {
|
||||||
// updatefound is also fired the very first time the SW is installed,
|
// updatefound is also fired the very first time the SW is installed,
|
||||||
// and there's no need to prompt for a reload at that point.
|
// and there's no need to prompt for a reload at that point.
|
||||||
// So check here to see if the page is already controlled,
|
// So check here to see if the page is already controlled,
|
||||||
// i.e. whether there's an existing service worker.
|
// i.e. whether there's an existing service worker.
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// The updatefound event implies that registration.installing is set
|
// The updatefound event implies that registration.installing is set
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing;
|
||||||
|
|
||||||
installingWorker.onstatechange = function() {
|
installingWorker.onstatechange = function() {
|
||||||
switch (installingWorker.state) {
|
switch (installingWorker.state) {
|
||||||
case 'installed':
|
case 'installed':
|
||||||
// At this point, the old content will have been purged and the
|
// At this point, the old content will have been purged and the
|
||||||
// fresh content will have been added to the cache.
|
// fresh content will have been added to the cache.
|
||||||
// It's the perfect time to display a "New content is
|
// It's the perfect time to display a "New content is
|
||||||
// available; please refresh." message in the page's interface.
|
// available; please refresh." message in the page's interface.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'redundant':
|
case 'redundant':
|
||||||
throw new Error('The installing ' +
|
throw new Error('The installing ' +
|
||||||
'service worker became redundant.');
|
'service worker became redundant.');
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}).catch(function(e) {
|
}).catch(function(e) {
|
||||||
console.error('Error during service worker registration:', e);
|
console.error('Error during service worker registration:', e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var config = require('./config')
|
var config = require('./config')
|
||||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||||
|
|
||||||
exports.assetsPath = function (_path) {
|
exports.assetsPath = function (_path) {
|
||||||
var assetsSubDirectory = config.assetsSubDirectory
|
var assetsSubDirectory = config.assetsSubDirectory
|
||||||
|
|
||||||
return path.posix.join(assetsSubDirectory, _path)
|
return path.posix.join(assetsSubDirectory, _path)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.cssLoaders = function (options) {
|
exports.cssLoaders = function (options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
var cssLoader = {
|
var cssLoader = {
|
||||||
loader: 'css-loader',
|
loader: 'css-loader',
|
||||||
options: {
|
options: {
|
||||||
minimize: process.env.NODE_ENV === 'production',
|
minimize: process.env.NODE_ENV === 'production',
|
||||||
sourceMap: options.sourceMap
|
sourceMap: options.sourceMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate loader string to be used with extract text plugin
|
// generate loader string to be used with extract text plugin
|
||||||
function generateLoaders (loader, loaderOptions) {
|
function generateLoaders (loader, loaderOptions) {
|
||||||
var loaders = [cssLoader]
|
var loaders = [cssLoader]
|
||||||
if (loader) {
|
if (loader) {
|
||||||
loaders.push({
|
loaders.push({
|
||||||
loader: loader + '-loader',
|
loader: loader + '-loader',
|
||||||
options: Object.assign({}, loaderOptions, {
|
options: Object.assign({}, loaderOptions, {
|
||||||
sourceMap: options.sourceMap
|
sourceMap: options.sourceMap
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract CSS when that option is specified
|
// Extract CSS when that option is specified
|
||||||
// (which is the case during production build)
|
// (which is the case during production build)
|
||||||
if (options.extract) {
|
if (options.extract) {
|
||||||
return ExtractTextPlugin.extract({
|
return ExtractTextPlugin.extract({
|
||||||
use: loaders,
|
use: loaders,
|
||||||
fallback: 'vue-style-loader'
|
fallback: 'vue-style-loader'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return ['vue-style-loader'].concat(loaders)
|
return ['vue-style-loader'].concat(loaders)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
|
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
|
||||||
return {
|
return {
|
||||||
css: generateLoaders(),
|
css: generateLoaders(),
|
||||||
postcss: generateLoaders(),
|
postcss: generateLoaders(),
|
||||||
less: generateLoaders('less'),
|
less: generateLoaders('less'),
|
||||||
sass: generateLoaders('sass', { indentedSyntax: true }),
|
sass: generateLoaders('sass', { indentedSyntax: true }),
|
||||||
scss: generateLoaders('sass'),
|
scss: generateLoaders('sass'),
|
||||||
stylus: generateLoaders('stylus'),
|
stylus: generateLoaders('stylus'),
|
||||||
styl: generateLoaders('stylus')
|
styl: generateLoaders('stylus')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate loaders for standalone style files (outside of .vue)
|
// Generate loaders for standalone style files (outside of .vue)
|
||||||
exports.styleLoaders = function (options) {
|
exports.styleLoaders = function (options) {
|
||||||
var output = []
|
var output = []
|
||||||
var loaders = exports.cssLoaders(options)
|
var loaders = exports.cssLoaders(options)
|
||||||
for (var extension in loaders) {
|
for (var extension in loaders) {
|
||||||
var loader = loaders[extension]
|
var loader = loaders[extension]
|
||||||
output.push({
|
output.push({
|
||||||
test: new RegExp('\\.' + extension + '$'),
|
test: new RegExp('\\.' + extension + '$'),
|
||||||
use: loader
|
use: loader
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var config = require('./config')
|
var config = require('./config')
|
||||||
var isProduction = process.env.NODE_ENV === 'production'
|
var isProduction = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loaders: utils.cssLoaders({
|
loaders: utils.cssLoaders({
|
||||||
sourceMap: isProduction
|
sourceMap: isProduction
|
||||||
? config.build.productionSourceMap
|
? config.build.productionSourceMap
|
||||||
: config.dev.produceSourceMap,
|
: config.dev.produceSourceMap,
|
||||||
extract: isProduction
|
extract: isProduction
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,69 @@
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var config = require('./config')
|
var config = require('./config')
|
||||||
var vueLoaderConfig = require('./vue-loader.conf')
|
var vueLoaderConfig = require('./vue-loader.conf')
|
||||||
|
|
||||||
function resolve (dir) {
|
function resolve (dir) {
|
||||||
return path.join(__dirname, '..', dir)
|
return path.join(__dirname, '..', dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
app: './assets/src/main.js'
|
app: './assets/src/main.js'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: config.assetsRoot,
|
path: config.assetsRoot,
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
publicPath: config.assetsPublicPath
|
publicPath: config.assetsPublicPath
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.vue', '.json'],
|
extensions: ['.js', '.vue', '.json'],
|
||||||
alias: {
|
alias: {
|
||||||
'vue$': 'vue/dist/vue.esm.js',
|
'vue$': 'vue/dist/vue.esm.js',
|
||||||
'@': resolve('src')
|
'@': resolve('src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(yml|yaml)$/,
|
test: /\.(yml|yaml)$/,
|
||||||
loader: 'yml-loader'
|
loader: 'yml-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(js|vue)$/,
|
test: /\.(js|vue)$/,
|
||||||
loader: 'eslint-loader',
|
loader: 'eslint-loader',
|
||||||
enforce: 'pre',
|
enforce: 'pre',
|
||||||
include: [resolve('src'), resolve('test')],
|
include: [resolve('src'), resolve('test')],
|
||||||
options: {
|
options: {
|
||||||
formatter: require('eslint-friendly-formatter')
|
formatter: require('eslint-friendly-formatter')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.vue$/,
|
test: /\.vue$/,
|
||||||
loader: 'vue-loader',
|
loader: 'vue-loader',
|
||||||
options: vueLoaderConfig
|
options: vueLoaderConfig
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
include: [resolve('src'), resolve('test')]
|
include: [resolve('src'), resolve('test')]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
limit: 10000,
|
limit: 10000,
|
||||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||||
loader: 'url-loader',
|
loader: 'url-loader',
|
||||||
options: {
|
options: {
|
||||||
// limit: 10000,
|
// limit: 10000,
|
||||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +1,81 @@
|
||||||
var fs = require('fs')
|
var fs = require('fs')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var utils = require('./utils')
|
var utils = require('./utils')
|
||||||
var webpack = require('webpack')
|
var webpack = require('webpack')
|
||||||
var config = require('./config')
|
var config = require('./config')
|
||||||
var merge = require('webpack-merge')
|
var merge = require('webpack-merge')
|
||||||
var baseWebpackConfig = require('./webpack.base.conf')
|
var baseWebpackConfig = require('./webpack.base.conf')
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||||
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
|
|
||||||
module.exports = merge(baseWebpackConfig, {
|
module.exports = merge(baseWebpackConfig, {
|
||||||
watch: true,
|
watch: true,
|
||||||
module: {
|
module: {
|
||||||
rules: utils.styleLoaders({
|
rules: utils.styleLoaders({
|
||||||
sourceMap: config.dev.produceSourceMap,
|
sourceMap: config.dev.produceSourceMap,
|
||||||
extract: true
|
extract: true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
devtool: '#cheap-module-eval-source-map',
|
devtool: '#cheap-module-eval-source-map',
|
||||||
output: {
|
output: {
|
||||||
path: config.assetsRoot,
|
path: config.assetsRoot,
|
||||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.NoEmitOnErrorsPlugin(),
|
new webpack.NoEmitOnErrorsPlugin(),
|
||||||
new FriendlyErrorsPlugin(),
|
new FriendlyErrorsPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': config.dev.env
|
'process.env': config.dev.env
|
||||||
}),
|
}),
|
||||||
// extract css into its own file
|
// extract css into its own file
|
||||||
new ExtractTextPlugin({
|
new ExtractTextPlugin({
|
||||||
filename: utils.assetsPath('css/[name].[contenthash].css')
|
filename: utils.assetsPath('css/[name].[contenthash].css')
|
||||||
}),
|
}),
|
||||||
// generate dist index.html with correct asset hash for caching.
|
// generate dist index.html with correct asset hash for caching.
|
||||||
// you can customize output by editing /index.html
|
// you can customize output by editing /index.html
|
||||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
filename: config.index,
|
filename: config.index,
|
||||||
template: 'assets/index.html',
|
template: 'assets/index.html',
|
||||||
inject: true,
|
inject: true,
|
||||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||||
chunksSortMode: 'dependency',
|
chunksSortMode: 'dependency',
|
||||||
serviceWorkerLoader: `<script>${fs.readFileSync(path.join(__dirname,
|
serviceWorkerLoader: `<script>${fs.readFileSync(path.join(__dirname,
|
||||||
'./service-worker-dev.js'), 'utf-8')}</script>`
|
'./service-worker-dev.js'), 'utf-8')}</script>`
|
||||||
}),
|
}),
|
||||||
// split vendor js into its own file
|
// split vendor js into its own file
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: 'vendor',
|
name: 'vendor',
|
||||||
minChunks: function (module, count) {
|
minChunks: function (module, count) {
|
||||||
// any required modules inside node_modules are extracted to vendor
|
// any required modules inside node_modules are extracted to vendor
|
||||||
return (
|
return (
|
||||||
module.resource &&
|
module.resource &&
|
||||||
/\.js$/.test(module.resource) &&
|
/\.js$/.test(module.resource) &&
|
||||||
module.resource.indexOf(
|
module.resource.indexOf(
|
||||||
path.join(__dirname, '../../node_modules')
|
path.join(__dirname, '../../node_modules')
|
||||||
) === 0
|
) === 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// extract webpack runtime and module manifest to its own file in order to
|
// extract webpack runtime and module manifest to its own file in order to
|
||||||
// prevent vendor hash from being updated whenever app bundle is updated
|
// prevent vendor hash from being updated whenever app bundle is updated
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: 'manifest',
|
name: 'manifest',
|
||||||
chunks: ['vendor']
|
chunks: ['vendor']
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
{
|
{
|
||||||
from: path.resolve(__dirname, '../static'),
|
from: path.resolve(__dirname, '../static'),
|
||||||
to: config.assetsSubDirectory,
|
to: config.assetsSubDirectory,
|
||||||
ignore: ['.*']
|
ignore: ['.*']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: path.resolve(__dirname, '../../node_modules/codemirror/mode/*/*'),
|
from: path.resolve(__dirname, '../../node_modules/codemirror/mode/*/*'),
|
||||||
to: path.join(config.assetsSubDirectory, 'js/codemirror/mode/[name]/[name].js')
|
to: path.join(config.assetsSubDirectory, 'js/codemirror/mode/[name]/[name].js')
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<svg id="content" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144">
|
<svg id="content" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144">
|
||||||
<circle cx="72" cy="72" r="72" fill="#2979ff"/>
|
<circle cx="72" cy="72" r="72" fill="#2979ff"/>
|
||||||
<circle cx="72" cy="72" r="48" fill="#40c4ff"/>
|
<circle cx="72" cy="72" r="48" fill="#40c4ff"/>
|
||||||
<circle cx="72" cy="72" r="24" fill="#fff"/>
|
<circle cx="72" cy="72" r="24" fill="#fff"/>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 239 B |
|
@ -1,22 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<select v-on:change="change" :value="selected">
|
<select v-on:change="change" :value="selected">
|
||||||
<option value="en">{{ $t('languages.en') }}</option>
|
<option value="en">{{ $t('languages.en') }}</option>
|
||||||
<option value="fr">{{ $t('languages.fr') }}</option>
|
<option value="fr">{{ $t('languages.fr') }}</option>
|
||||||
<option value="pt">{{ $t('languages.pt') }}</option>
|
<option value="pt">{{ $t('languages.pt') }}</option>
|
||||||
<option value="ja">{{ $t('languages.ja') }}</option>
|
<option value="ja">{{ $t('languages.ja') }}</option>
|
||||||
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
|
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
|
||||||
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
|
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'languages',
|
name: 'languages',
|
||||||
props: [ 'selected' ],
|
props: [ 'selected' ],
|
||||||
methods: {
|
methods: {
|
||||||
change (event) {
|
change (event) {
|
||||||
this.$emit('update:selected', event.target.value)
|
this.$emit('update:selected', event.target.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,265 +1,265 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||||
<div id="input">
|
<div id="input">
|
||||||
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
|
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
|
||||||
<i class="material-icons">arrow_back</i>
|
<i class="material-icons">arrow_back</i>
|
||||||
</button>
|
</button>
|
||||||
<i v-else class="material-icons">search</i>
|
<i v-else class="material-icons">search</i>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
@keyup="keyup"
|
@keyup="keyup"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
ref="input"
|
ref="input"
|
||||||
:autofocus="active"
|
:autofocus="active"
|
||||||
v-model.trim="value"
|
v-model.trim="value"
|
||||||
:aria-label="$t('search.writeToSearch')"
|
:aria-label="$t('search.writeToSearch')"
|
||||||
:placeholder="placeholder">
|
:placeholder="placeholder">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="result">
|
<div id="result">
|
||||||
<div>
|
<div>
|
||||||
<template v-if="search.length === 0 && commands.length === 0">
|
<template v-if="search.length === 0 && commands.length === 0">
|
||||||
<p>{{ text }}</p>
|
<p>{{ text }}</p>
|
||||||
|
|
||||||
<template v-if="value.length === 0">
|
<template v-if="value.length === 0">
|
||||||
<div class="boxes">
|
<div class="boxes">
|
||||||
<h3>{{ $t('search.types') }}</h3>
|
<h3>{{ $t('search.types') }}</h3>
|
||||||
<div>
|
<div>
|
||||||
<div tabindex="0"
|
<div tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@click="init('type:image')"
|
@click="init('type:image')"
|
||||||
:aria-label="$t('search.images')">
|
:aria-label="$t('search.images')">
|
||||||
<i class="material-icons">insert_photo</i>
|
<i class="material-icons">insert_photo</i>
|
||||||
<p>{{ $t('search.images') }}</p>
|
<p>{{ $t('search.images') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div tabindex="0"
|
<div tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@click="init('type:audio')"
|
@click="init('type:audio')"
|
||||||
:aria-label="$t('search.music')">
|
:aria-label="$t('search.music')">
|
||||||
<i class="material-icons">volume_up</i>
|
<i class="material-icons">volume_up</i>
|
||||||
<p>{{ $t('search.music') }}</p>
|
<p>{{ $t('search.music') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div tabindex="0"
|
<div tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@click="init('type:video')"
|
@click="init('type:video')"
|
||||||
:aria-label="$t('search.video')">
|
:aria-label="$t('search.video')">
|
||||||
<i class="material-icons">movie</i>
|
<i class="material-icons">movie</i>
|
||||||
<p>{{ $t('search.video') }}</p>
|
<p>{{ $t('search.video') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div tabindex="0"
|
<div tabindex="0"
|
||||||
role="button"
|
role="button"
|
||||||
@click="init('type:pdf')"
|
@click="init('type:pdf')"
|
||||||
:aria-label="$t('search.pdf')">
|
:aria-label="$t('search.pdf')">
|
||||||
<i class="material-icons">picture_as_pdf</i>
|
<i class="material-icons">picture_as_pdf</i>
|
||||||
<p>{{ $t('search.pdf') }}</p>
|
<p>{{ $t('search.pdf') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<ul v-else-if="search.length > 0">
|
<ul v-else-if="search.length > 0">
|
||||||
<li v-for="s in search">
|
<li v-for="s in search">
|
||||||
<router-link @click.native="close" :to="'./' + s.path">
|
<router-link @click.native="close" :to="'./' + s.path">
|
||||||
<i v-if="s.dir" class="material-icons">folder</i>
|
<i v-if="s.dir" class="material-icons">folder</i>
|
||||||
<i v-else class="material-icons">insert_drive_file</i>
|
<i v-else class="material-icons">insert_drive_file</i>
|
||||||
<span>./{{ s.path }}</span>
|
<span>./{{ s.path }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<pre v-else-if="commands.length > 0">
|
<pre v-else-if="commands.length > 0">
|
||||||
<template v-for="c in commands">{{ c }}</template>
|
<template v-for="c in commands">{{ c }}</template>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<p id="renew"><i class="material-icons spin">autorenew</i></p>
|
<p id="renew"><i class="material-icons spin">autorenew</i></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import url from '@/utils/url'
|
import url from '@/utils/url'
|
||||||
import * as api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'search',
|
name: 'search',
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
value: '',
|
value: '',
|
||||||
active: false,
|
active: false,
|
||||||
ongoing: false,
|
ongoing: false,
|
||||||
scrollable: null,
|
scrollable: null,
|
||||||
search: [],
|
search: [],
|
||||||
commands: [],
|
commands: [],
|
||||||
reload: false
|
reload: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
show (val, old) {
|
show (val, old) {
|
||||||
this.active = (val === 'search')
|
this.active = (val === 'search')
|
||||||
|
|
||||||
// If the hover was search and now it's something else
|
// If the hover was search and now it's something else
|
||||||
// we should blur the input.
|
// we should blur the input.
|
||||||
if (old === 'search' && val !== 'search') {
|
if (old === 'search' && val !== 'search') {
|
||||||
if (this.reload) {
|
if (this.reload) {
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.style.overflow = 'auto'
|
document.body.style.overflow = 'auto'
|
||||||
this.reset()
|
this.reset()
|
||||||
this.$refs.input.blur()
|
this.$refs.input.blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are starting to show the search box, we should
|
// If we are starting to show the search box, we should
|
||||||
// focus the input.
|
// focus the input.
|
||||||
if (val === 'search') {
|
if (val === 'search') {
|
||||||
this.reload = false
|
this.reload = false
|
||||||
this.$refs.input.focus()
|
this.$refs.input.focus()
|
||||||
document.body.style.overflow = 'hidden'
|
document.body.style.overflow = 'hidden'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['user', 'show']),
|
...mapState(['user', 'show']),
|
||||||
// Placeholder value.
|
// Placeholder value.
|
||||||
placeholder: function () {
|
placeholder: function () {
|
||||||
if (this.user.allowCommands && this.user.commands.length > 0) {
|
if (this.user.allowCommands && this.user.commands.length > 0) {
|
||||||
return this.$t('search.searchOrCommand')
|
return this.$t('search.searchOrCommand')
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('search.search')
|
return this.$t('search.search')
|
||||||
},
|
},
|
||||||
// The text that is shown on the results' box while
|
// The text that is shown on the results' box while
|
||||||
// there is no search result or command output to show.
|
// there is no search result or command output to show.
|
||||||
text: function () {
|
text: function () {
|
||||||
if (this.ongoing) {
|
if (this.ongoing) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value.length === 0) {
|
if (this.value.length === 0) {
|
||||||
if (this.user.allowCommands && this.user.commands.length > 0) {
|
if (this.user.allowCommands && this.user.commands.length > 0) {
|
||||||
return `${this.$t('search.searchOrSupportedCommand')} ${this.user.commands.join(', ')}.`
|
return `${this.$t('search.searchOrSupportedCommand')} ${this.user.commands.join(', ')}.`
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$t('search.type')
|
this.$t('search.type')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.supported() || !this.user.allowCommands) {
|
if (!this.supported() || !this.user.allowCommands) {
|
||||||
return this.$t('search.pressToSearch')
|
return this.$t('search.pressToSearch')
|
||||||
} else {
|
} else {
|
||||||
return this.$t('search.pressToExecute')
|
return this.$t('search.pressToExecute')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
// Gets the result div which will be scrollable.
|
// Gets the result div which will be scrollable.
|
||||||
this.scrollable = document.querySelector('#search #result')
|
this.scrollable = document.querySelector('#search #result')
|
||||||
|
|
||||||
// Adds the keydown event on window for the ESC key, so
|
// Adds the keydown event on window for the ESC key, so
|
||||||
// when it's pressed, it closes the search window.
|
// when it's pressed, it closes the search window.
|
||||||
window.addEventListener('keydown', (event) => {
|
window.addEventListener('keydown', (event) => {
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Sets the search to active.
|
// Sets the search to active.
|
||||||
open (event) {
|
open (event) {
|
||||||
this.$store.commit('showHover', 'search')
|
this.$store.commit('showHover', 'search')
|
||||||
},
|
},
|
||||||
// Closes the search and prevents the event
|
// Closes the search and prevents the event
|
||||||
// of propagating so it doesn't trigger the
|
// of propagating so it doesn't trigger the
|
||||||
// click event on #search.
|
// click event on #search.
|
||||||
close (event) {
|
close (event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
},
|
},
|
||||||
// Checks if the current input is a supported command.
|
// Checks if the current input is a supported command.
|
||||||
supported () {
|
supported () {
|
||||||
let pieces = this.value.split(' ')
|
let pieces = this.value.split(' ')
|
||||||
|
|
||||||
for (let i = 0; i < this.user.commands.length; i++) {
|
for (let i = 0; i < this.user.commands.length; i++) {
|
||||||
if (pieces[0] === this.user.commands[i]) {
|
if (pieces[0] === this.user.commands[i]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
// Initializes the search with a default value.
|
// Initializes the search with a default value.
|
||||||
init (string) {
|
init (string) {
|
||||||
this.value = string + ' '
|
this.value = string + ' '
|
||||||
this.$refs.input.focus()
|
this.$refs.input.focus()
|
||||||
},
|
},
|
||||||
// Resets the search box value.
|
// Resets the search box value.
|
||||||
reset () {
|
reset () {
|
||||||
this.value = ''
|
this.value = ''
|
||||||
this.active = false
|
this.active = false
|
||||||
this.ongoing = false
|
this.ongoing = false
|
||||||
this.search = []
|
this.search = []
|
||||||
this.commands = []
|
this.commands = []
|
||||||
},
|
},
|
||||||
// When the user presses a key, if it is ESC
|
// When the user presses a key, if it is ESC
|
||||||
// then it will close the search box. Otherwise,
|
// then it will close the search box. Otherwise,
|
||||||
// it will set the search box to active and clean
|
// it will set the search box to active and clean
|
||||||
// the search results, as well as commands'.
|
// the search results, as well as commands'.
|
||||||
keyup (event) {
|
keyup (event) {
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
this.close(event)
|
this.close(event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.search.length = 0
|
this.search.length = 0
|
||||||
this.commands.length = 0
|
this.commands.length = 0
|
||||||
},
|
},
|
||||||
// Submits the input to the server and sets ongoing to true.
|
// Submits the input to the server and sets ongoing to true.
|
||||||
submit (event) {
|
submit (event) {
|
||||||
this.ongoing = true
|
this.ongoing = true
|
||||||
|
|
||||||
let path = this.$route.path
|
let path = this.$route.path
|
||||||
if (this.$store.state.req.kind !== 'listing') {
|
if (this.$store.state.req.kind !== 'listing') {
|
||||||
path = url.removeLastDir(path) + '/'
|
path = url.removeLastDir(path) + '/'
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of being a command.
|
// In case of being a command.
|
||||||
if (this.supported() && this.user.allowCommands) {
|
if (this.supported() && this.user.allowCommands) {
|
||||||
api.command(path, this.value,
|
api.command(path, this.value,
|
||||||
(event) => {
|
(event) => {
|
||||||
this.commands.push(event.data)
|
this.commands.push(event.data)
|
||||||
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
||||||
},
|
},
|
||||||
(event) => {
|
(event) => {
|
||||||
this.reload = true
|
this.reload = true
|
||||||
this.ongoing = false
|
this.ongoing = false
|
||||||
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of being a search.
|
// In case of being a search.
|
||||||
api.search(path, this.value,
|
api.search(path, this.value,
|
||||||
(event) => {
|
(event) => {
|
||||||
let response = JSON.parse(event.data)
|
let response = JSON.parse(event.data)
|
||||||
if (response.path[0] === '/') {
|
if (response.path[0] === '/') {
|
||||||
response.path = response.path.substring(1)
|
response.path = response.path.substring(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.search.push(response)
|
this.search.push(response)
|
||||||
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
||||||
},
|
},
|
||||||
(event) => {
|
(event) => {
|
||||||
this.ongoing = false
|
this.ongoing = false
|
||||||
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
this.scrollable.scrollTop = this.scrollable.scrollHeight
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
|
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
|
||||||
<i class="material-icons">content_copy</i>
|
<i class="material-icons">content_copy</i>
|
||||||
<span>{{ $t('buttons.copyFile') }}</span>
|
<span>{{ $t('buttons.copyFile') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'copy-button',
|
name: 'copy-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'copy')
|
this.$store.commit('showHover', 'copy')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
||||||
<i class="material-icons">delete</i>
|
<i class="material-icons">delete</i>
|
||||||
<span>{{ $t('buttons.delete') }}</span>
|
<span>{{ $t('buttons.delete') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'delete-button',
|
name: 'delete-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'delete')
|
this.$store.commit('showHover', 'delete')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
|
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
|
||||||
<i class="material-icons">file_download</i>
|
<i class="material-icons">file_download</i>
|
||||||
<span>{{ $t('buttons.download') }}</span>
|
<span>{{ $t('buttons.download') }}</span>
|
||||||
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapGetters, mapState} from 'vuex'
|
import {mapGetters, mapState} from 'vuex'
|
||||||
import * as api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'download-button',
|
name: 'download-button',
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['req', 'selected']),
|
...mapState(['req', 'selected']),
|
||||||
...mapGetters(['selectedCount'])
|
...mapGetters(['selectedCount'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
download: function (event) {
|
download: function (event) {
|
||||||
// If we are not on a listing, download the current file.
|
// If we are not on a listing, download the current file.
|
||||||
if (this.req.kind !== 'listing') {
|
if (this.req.kind !== 'listing') {
|
||||||
api.download(null, this.$route.path)
|
api.download(null, this.$route.path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are on a listing and there is one element selected,
|
// If we are on a listing and there is one element selected,
|
||||||
// download it.
|
// download it.
|
||||||
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
|
||||||
api.download(null, this.req.items[this.selected[0]].url)
|
api.download(null, this.req.items[this.selected[0]].url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise show the prompt to choose the formt of the download.
|
// Otherwise show the prompt to choose the formt of the download.
|
||||||
this.$store.commit('showHover', 'download')
|
this.$store.commit('showHover', 'download')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
|
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
|
||||||
<i class="material-icons">info</i>
|
<i class="material-icons">info</i>
|
||||||
<span>{{ $t('buttons.info') }}</span>
|
<span>{{ $t('buttons.info') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'info-button',
|
name: 'info-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'info')
|
this.$store.commit('showHover', 'info')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
||||||
<i class="material-icons">forward</i>
|
<i class="material-icons">forward</i>
|
||||||
<span>{{ $t('buttons.moveFile') }}</span>
|
<span>{{ $t('buttons.moveFile') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'move-button',
|
name: 'move-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'move')
|
this.$store.commit('showHover', 'move')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
|
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
|
||||||
<i class="material-icons">mode_edit</i>
|
<i class="material-icons">mode_edit</i>
|
||||||
<span>{{ $t('buttons.rename') }}</span>
|
<span>{{ $t('buttons.rename') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'rename-button',
|
name: 'rename-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'rename')
|
this.$store.commit('showHover', 'rename')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="show"
|
<button @click="show"
|
||||||
:aria-label="$t('buttons.schedule')"
|
:aria-label="$t('buttons.schedule')"
|
||||||
:title="$t('buttons.schedule')"
|
:title="$t('buttons.schedule')"
|
||||||
id="schedule-button"
|
id="schedule-button"
|
||||||
class="action">
|
class="action">
|
||||||
<i class="material-icons">alarm</i>
|
<i class="material-icons">alarm</i>
|
||||||
<span>{{ $t('buttons.schedule') }}</span>
|
<span>{{ $t('buttons.schedule') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'schedule-button',
|
name: 'schedule-button',
|
||||||
methods: {
|
methods: {
|
||||||
show: function (event) {
|
show: function (event) {
|
||||||
this.$store.commit('showHover', 'schedule')
|
this.$store.commit('showHover', 'schedule')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
|
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
|
||||||
<i class="material-icons">file_upload</i>
|
<i class="material-icons">file_upload</i>
|
||||||
<span>{{ $t('buttons.upload') }}</span>
|
<span>{{ $t('buttons.upload') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'upload-button',
|
name: 'upload-button',
|
||||||
methods: {
|
methods: {
|
||||||
upload: function (event) {
|
upload: function (event) {
|
||||||
document.getElementById('upload-input').click()
|
document.getElementById('upload-input').click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -119,9 +119,21 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.shiftKey && this.selected.length === 1) {
|
if (event.shiftKey && this.selected.length === 1) {
|
||||||
let fi = (this.index > this.selected[0]) ? this.selected[0] : this.index
|
let fi = 0
|
||||||
let la = (this.index > this.selected[0]) ? this.index : this.selected[0]
|
let la = 0
|
||||||
for (; fi <= la; fi++) this.addSelected(fi)
|
|
||||||
|
if (this.index > this.selected[0]) {
|
||||||
|
fi = this.selected[0] + 1
|
||||||
|
la = this.index
|
||||||
|
} else {
|
||||||
|
fi = this.index
|
||||||
|
la = this.selected[0] - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; fi <= la; fi++) {
|
||||||
|
this.addSelected(fi)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,84 +1,84 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card floating">
|
<div class="card floating">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t('prompts.rename') }}</h2>
|
<h2>{{ $t('prompts.rename') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||||
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
<input autofocus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-action">
|
<div class="card-action">
|
||||||
<button class="cancel flat"
|
<button class="cancel flat"
|
||||||
@click="$store.commit('closeHovers')"
|
@click="$store.commit('closeHovers')"
|
||||||
:aria-label="$t('buttons.cancel')"
|
:aria-label="$t('buttons.cancel')"
|
||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
<button @click="submit"
|
<button @click="submit"
|
||||||
class="flat"
|
class="flat"
|
||||||
type="submit"
|
type="submit"
|
||||||
:aria-label="$t('buttons.rename')"
|
:aria-label="$t('buttons.rename')"
|
||||||
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import url from '@/utils/url'
|
import url from '@/utils/url'
|
||||||
import * as api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'rename',
|
name: 'rename',
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
name: ''
|
name: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: mapState(['req', 'selected', 'selectedCount']),
|
computed: mapState(['req', 'selected', 'selectedCount']),
|
||||||
methods: {
|
methods: {
|
||||||
cancel: function (event) {
|
cancel: function (event) {
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
},
|
},
|
||||||
oldName: function () {
|
oldName: function () {
|
||||||
// Get the current name of the file we are editing.
|
// Get the current name of the file we are editing.
|
||||||
if (this.req.kind !== 'listing') {
|
if (this.req.kind !== 'listing') {
|
||||||
return this.req.name
|
return this.req.name
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
if (this.selectedCount === 0 || this.selectedCount > 1) {
|
||||||
// This shouldn't happen.
|
// This shouldn't happen.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.req.items[this.selected[0]].name
|
return this.req.items[this.selected[0]].name
|
||||||
},
|
},
|
||||||
submit: function (event) {
|
submit: function (event) {
|
||||||
let oldLink = ''
|
let oldLink = ''
|
||||||
let newLink = ''
|
let newLink = ''
|
||||||
|
|
||||||
if (this.req.kind !== 'listing') {
|
if (this.req.kind !== 'listing') {
|
||||||
oldLink = this.req.url
|
oldLink = this.req.url
|
||||||
} else {
|
} else {
|
||||||
oldLink = this.req.items[this.selected[0]].url
|
oldLink = this.req.items[this.selected[0]].url
|
||||||
}
|
}
|
||||||
|
|
||||||
this.name = encodeURIComponent(this.name)
|
this.name = encodeURIComponent(this.name)
|
||||||
newLink = url.removeLastDir(oldLink) + '/' + this.name
|
newLink = url.removeLastDir(oldLink) + '/' + this.name
|
||||||
|
|
||||||
api.move([{ from: oldLink, to: newLink }])
|
api.move([{ from: oldLink, to: newLink }])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (this.req.kind !== 'listing') {
|
if (this.req.kind !== 'listing') {
|
||||||
this.$router.push({ path: newLink })
|
this.$router.push({ path: newLink })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.$store.commit('setReload', true)
|
this.$store.commit('setReload', true)
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.$showError(error)
|
this.$showError(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,184 +1,184 @@
|
||||||
@import "~codemirror/lib/codemirror.css";
|
@import "~codemirror/lib/codemirror.css";
|
||||||
@import "~codemirror/theme/ttcn.css";
|
@import "~codemirror/theme/ttcn.css";
|
||||||
#editor {
|
#editor {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor .CodeMirror {
|
#editor .CodeMirror {
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
margin: 2em 0;
|
margin: 2em 0;
|
||||||
border-radius: .5em;
|
border-radius: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor h2 {
|
#editor h2 {
|
||||||
color: rgba(0, 0, 0, 0.3);
|
color: rgba(0, 0, 0, 0.3);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown .CodeMirror {
|
.markdown .CodeMirror {
|
||||||
padding: .75em;
|
padding: .75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .CodeMirror-gutter {
|
.cm-s-markdown .CodeMirror-gutter {
|
||||||
border-right: 1px solid #eff3f5;
|
border-right: 1px solid #eff3f5;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
min-width: 2.5em;
|
min-width: 2.5em;
|
||||||
padding-bottom: 30px;
|
padding-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .CodeMirror-cursor {
|
.cm-s-markdown .CodeMirror-cursor {
|
||||||
border-right: 2px solid #667880;
|
border-right: 2px solid #667880;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .CodeMirror-lines {
|
.cm-s-markdown .CodeMirror-lines {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown {
|
.cm-s-markdown {
|
||||||
color: #3D494E;
|
color: #3D494E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-header {
|
.cm-s-markdown span.cm-header {
|
||||||
color: #3D494E;
|
color: #3D494E;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-variable-2 {
|
.cm-s-markdown span.cm-variable-2 {
|
||||||
color: #3D494E;
|
color: #3D494E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-meta {
|
.cm-s-markdown span.cm-meta {
|
||||||
color: #516066;
|
color: #516066;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-hr {
|
.cm-s-markdown span.cm-hr {
|
||||||
color: #516066;
|
color: #516066;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-comment {
|
.cm-s-markdown span.cm-comment {
|
||||||
color: #868f93;
|
color: #868f93;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-qualifier {
|
.cm-s-markdown span.cm-qualifier {
|
||||||
color: #868f93;
|
color: #868f93;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-number {
|
.cm-s-markdown span.cm-number {
|
||||||
color: #197987;
|
color: #197987;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-variable {
|
.cm-s-markdown span.cm-variable {
|
||||||
color: #197987;
|
color: #197987;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-builtin {
|
.cm-s-markdown span.cm-builtin {
|
||||||
color: #197987;
|
color: #197987;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-link {
|
.cm-s-markdown span.cm-link {
|
||||||
color: #197987;
|
color: #197987;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-tag {
|
.cm-s-markdown span.cm-tag {
|
||||||
color: #197987;
|
color: #197987;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-string {
|
.cm-s-markdown span.cm-string {
|
||||||
color: #48abb9;
|
color: #48abb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-string-2 {
|
.cm-s-markdown span.cm-string-2 {
|
||||||
color: #48abb9;
|
color: #48abb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-quote {
|
.cm-s-markdown span.cm-quote {
|
||||||
color: #48abb9;
|
color: #48abb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-atom {
|
.cm-s-markdown span.cm-atom {
|
||||||
color: #48abb9;
|
color: #48abb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-property {
|
.cm-s-markdown span.cm-property {
|
||||||
color: #82a367;
|
color: #82a367;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-operator {
|
.cm-s-markdown span.cm-operator {
|
||||||
color: #82a367;
|
color: #82a367;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-variable-3 {
|
.cm-s-markdown span.cm-variable-3 {
|
||||||
color: #82a367;
|
color: #82a367;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-attribute {
|
.cm-s-markdown span.cm-attribute {
|
||||||
color: #90bb74;
|
color: #90bb74;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-def {
|
.cm-s-markdown span.cm-def {
|
||||||
color: #90bb74;
|
color: #90bb74;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-keyword {
|
.cm-s-markdown span.cm-keyword {
|
||||||
color: #ec6c45;
|
color: #ec6c45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-bracket {
|
.cm-s-markdown span.cm-bracket {
|
||||||
color: #ec6c45;
|
color: #ec6c45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-error {
|
.cm-s-markdown span.cm-error {
|
||||||
color: #e45346;
|
color: #e45346;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-em {
|
.cm-s-markdown span.cm-em {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown span.cm-strong {
|
.cm-s-markdown span.cm-strong {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-header-1 {
|
.cm-s-markdown .cm-header-1 {
|
||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
line-height: 200%;
|
line-height: 200%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-header-2 {
|
.cm-s-markdown .cm-header-2 {
|
||||||
font-size: 160%;
|
font-size: 160%;
|
||||||
line-height: 160%;
|
line-height: 160%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-header-3 {
|
.cm-s-markdown .cm-header-3 {
|
||||||
font-size: 125%;
|
font-size: 125%;
|
||||||
line-height: 125%;
|
line-height: 125%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-header-4 {
|
.cm-s-markdown .cm-header-4 {
|
||||||
font-size: 110%;
|
font-size: 110%;
|
||||||
line-height: 110%;
|
line-height: 110%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-comment {
|
.cm-s-markdown .cm-comment {
|
||||||
background: rgba(0, 0, 0, .05);
|
background: rgba(0, 0, 0, .05);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-link {
|
.cm-s-markdown .cm-link {
|
||||||
color: #7f8c8d;
|
color: #7f8c8d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-url {
|
.cm-s-markdown .cm-url {
|
||||||
color: #aab2b3;
|
color: #aab2b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-s-markdown .cm-strikethrough {
|
.cm-s-markdown .cm-strikethrough {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,137 +1,137 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
|
||||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
|
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
|
||||||
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
|
||||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
|
||||||
unicode-range: U+1F00-1FFF;
|
unicode-range: U+1F00-1FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
|
||||||
unicode-range: U+0370-03FF;
|
unicode-range: U+0370-03FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
|
||||||
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
|
||||||
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Roboto';
|
font-family: 'Roboto';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
|
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../assets/fonts/material/icons.woff2) format('woff2');
|
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../assets/fonts/material/icons.woff2) format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt .file-list ul li:before,
|
.prompt .file-list ul li:before,
|
||||||
.material-icons {
|
.material-icons {
|
||||||
font-family: 'Material Icons';
|
font-family: 'Material Icons';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
font-feature-settings: 'liga';
|
font-feature-settings: 'liga';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,113 +1,113 @@
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
nav {
|
nav {
|
||||||
width: 10em
|
width: 10em
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
#listing.list .item.header,
|
#listing.list .item.header,
|
||||||
main {
|
main {
|
||||||
width: calc(100% - 13em)
|
width: calc(100% - 13em)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 736px) {
|
@media (max-width: 736px) {
|
||||||
#more {
|
#more {
|
||||||
display: inherit
|
display: inherit
|
||||||
}
|
}
|
||||||
header .overlay {
|
header .overlay {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
#dropdown {
|
#dropdown {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1em;
|
top: 1em;
|
||||||
right: 1em;
|
right: 1em;
|
||||||
display: block;
|
display: block;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
transition: .1s ease-in-out transform;
|
transition: .1s ease-in-out transform;
|
||||||
transform-origin: top right;
|
transform-origin: top right;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
}
|
}
|
||||||
#dropdown > div {
|
#dropdown > div {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
#dropdown.active {
|
#dropdown.active {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
#dropdown .action {
|
#dropdown .action {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#dropdown .action span:not(.counter) {
|
#dropdown .action span:not(.counter) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: .4em;
|
padding: .4em;
|
||||||
}
|
}
|
||||||
#dropdown .counter {
|
#dropdown .counter {
|
||||||
left: 2.25em;
|
left: 2.25em;
|
||||||
}
|
}
|
||||||
#file-selection {
|
#file-selection {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-width: 20em;
|
max-width: 20em;
|
||||||
}
|
}
|
||||||
#file-selection .action {
|
#file-selection .action {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
#file-selection > span {
|
#file-selection > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
color: #6f6f6f;
|
color: #6f6f6f;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
nav {
|
nav {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 16em;
|
width: 16em;
|
||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||||
transition: .1s ease left;
|
transition: .1s ease left;
|
||||||
left: -17em;
|
left: -17em;
|
||||||
}
|
}
|
||||||
nav.active {
|
nav.active {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
header .search-button,
|
header .search-button,
|
||||||
header>div:first-child>.action {
|
header>div:first-child>.action {
|
||||||
display: inherit;
|
display: inherit;
|
||||||
}
|
}
|
||||||
header img {
|
header img {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#listing {
|
#listing {
|
||||||
margin-bottom: 5em;
|
margin-bottom: 5em;
|
||||||
}
|
}
|
||||||
#listing.list .item.header,
|
#listing.list .item.header,
|
||||||
main {
|
main {
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
margin: 0 1em;
|
margin: 0 1em;
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
}
|
}
|
||||||
#search {
|
#search {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#search.active {
|
#search.active {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,200 +1,200 @@
|
||||||
permanent: 永久
|
permanent: 永久
|
||||||
buttons:
|
buttons:
|
||||||
cancel: キャンセル
|
cancel: キャンセル
|
||||||
close: 閉じる
|
close: 閉じる
|
||||||
copy: コピー
|
copy: コピー
|
||||||
copyFile: ファイルをコピー
|
copyFile: ファイルをコピー
|
||||||
copyToClipboard: クリップボードにコピー
|
copyToClipboard: クリップボードにコピー
|
||||||
create: 作成
|
create: 作成
|
||||||
delete: 削除
|
delete: 削除
|
||||||
download: ダウンロード
|
download: ダウンロード
|
||||||
info: 情報
|
info: 情報
|
||||||
more: More
|
more: More
|
||||||
move: 移動
|
move: 移動
|
||||||
moveFile: ファイルを移動
|
moveFile: ファイルを移動
|
||||||
new: 新規
|
new: 新規
|
||||||
next: 次
|
next: 次
|
||||||
ok: OK
|
ok: OK
|
||||||
replace: 置き換える
|
replace: 置き換える
|
||||||
previous: 前
|
previous: 前
|
||||||
rename: 名前を変更
|
rename: 名前を変更
|
||||||
reportIssue: 問題を報告
|
reportIssue: 問題を報告
|
||||||
save: 保存
|
save: 保存
|
||||||
search: 検索
|
search: 検索
|
||||||
select: 選択
|
select: 選択
|
||||||
share: シェア
|
share: シェア
|
||||||
publish: 発表
|
publish: 発表
|
||||||
selectMultiple: 複数選択
|
selectMultiple: 複数選択
|
||||||
schedule: スケジュール
|
schedule: スケジュール
|
||||||
switchView: 表示を切り替わる
|
switchView: 表示を切り替わる
|
||||||
toggleSidebar: サイドバーを表示する
|
toggleSidebar: サイドバーを表示する
|
||||||
update: 更新
|
update: 更新
|
||||||
upload: アップロード
|
upload: アップロード
|
||||||
permalink: 固定リンク
|
permalink: 固定リンク
|
||||||
success:
|
success:
|
||||||
linkCopied: リンクがコピーされました!
|
linkCopied: リンクがコピーされました!
|
||||||
errors:
|
errors:
|
||||||
forbidden: アクセスが拒否されました。
|
forbidden: アクセスが拒否されました。
|
||||||
internal: 内部エラーが発生しました。
|
internal: 内部エラーが発生しました。
|
||||||
notFound: リソースが見つからなりませんでした。
|
notFound: リソースが見つからなりませんでした。
|
||||||
files:
|
files:
|
||||||
folders: フォルダ
|
folders: フォルダ
|
||||||
files: ファイル
|
files: ファイル
|
||||||
body: 本文
|
body: 本文
|
||||||
clear: クリアー
|
clear: クリアー
|
||||||
closePreview: プレビューを閉じる
|
closePreview: プレビューを閉じる
|
||||||
home: ホーム
|
home: ホーム
|
||||||
lastModified: 最終変更
|
lastModified: 最終変更
|
||||||
loading: ローディング...
|
loading: ローディング...
|
||||||
lonely: ここには何もない...
|
lonely: ここには何もない...
|
||||||
metadata: メタデータ
|
metadata: メタデータ
|
||||||
multipleSelectionEnabled: 複数選択有効
|
multipleSelectionEnabled: 複数選択有効
|
||||||
name: 名前
|
name: 名前
|
||||||
size: サイズ
|
size: サイズ
|
||||||
sortByName: 名前によるソート
|
sortByName: 名前によるソート
|
||||||
sortBySize: サイズによるソート
|
sortBySize: サイズによるソート
|
||||||
sortByLastModified: 最終変更日付によるソート
|
sortByLastModified: 最終変更日付によるソート
|
||||||
help:
|
help:
|
||||||
click: ファイルやディレクトリを選択
|
click: ファイルやディレクトリを選択
|
||||||
ctrl:
|
ctrl:
|
||||||
click: 複数のファイルやディレクトリを選択
|
click: 複数のファイルやディレクトリを選択
|
||||||
f: 検索を有効にする
|
f: 検索を有効にする
|
||||||
s: ファイルを保存またはカレントディレクトリをダウンロード
|
s: ファイルを保存またはカレントディレクトリをダウンロード
|
||||||
del: 選択した項目を削除
|
del: 選択した項目を削除
|
||||||
doubleClick: ファイルやディレクトリをオープン
|
doubleClick: ファイルやディレクトリをオープン
|
||||||
esc: 選択をクリアーまたはプロンプトを閉じる
|
esc: 選択をクリアーまたはプロンプトを閉じる
|
||||||
f1: このヘルプを表示
|
f1: このヘルプを表示
|
||||||
f2: ファイルの名前を変更
|
f2: ファイルの名前を変更
|
||||||
help: ヘルプ
|
help: ヘルプ
|
||||||
login:
|
login:
|
||||||
password: パスワード
|
password: パスワード
|
||||||
submit: ログイン
|
submit: ログイン
|
||||||
username: ユーザ名
|
username: ユーザ名
|
||||||
wrongCredentials: ユーザ名またはパスワードが間違っています。
|
wrongCredentials: ユーザ名またはパスワードが間違っています。
|
||||||
prompts:
|
prompts:
|
||||||
copy: コピー
|
copy: コピー
|
||||||
copyMessage: コピーの目標ディレクトリを選択してください:
|
copyMessage: コピーの目標ディレクトリを選択してください:
|
||||||
currentlyNavigating: 現在閲覧しているディレクトリ:
|
currentlyNavigating: 現在閲覧しているディレクトリ:
|
||||||
deleteMessageMultiple: '{count} つのファイルを本当に削除してよろしいですか。'
|
deleteMessageMultiple: '{count} つのファイルを本当に削除してよろしいですか。'
|
||||||
deleteMessageSingle: このファイル/フォルダを本当に削除してよろしいですか。
|
deleteMessageSingle: このファイル/フォルダを本当に削除してよろしいですか。
|
||||||
deleteTitle: ファイルを削除
|
deleteTitle: ファイルを削除
|
||||||
displayName: 名前:
|
displayName: 名前:
|
||||||
download: ファイルをダウンロード
|
download: ファイルをダウンロード
|
||||||
downloadMessage: 圧縮形式を選択してください。
|
downloadMessage: 圧縮形式を選択してください。
|
||||||
error: あるエラーが発生しました。
|
error: あるエラーが発生しました。
|
||||||
fileInfo: ファイル情報
|
fileInfo: ファイル情報
|
||||||
filesSelected: '{count} つのファイルは選択されました。'
|
filesSelected: '{count} つのファイルは選択されました。'
|
||||||
lastModified: 最終変更
|
lastModified: 最終変更
|
||||||
move: 移動
|
move: 移動
|
||||||
moveMessage: 移動の目標ディレクトリを選択してください:
|
moveMessage: 移動の目標ディレクトリを選択してください:
|
||||||
newDir: 新しいディレクトリを作成
|
newDir: 新しいディレクトリを作成
|
||||||
newDirMessage: 新しいディレクトリの名前を入力してください。
|
newDirMessage: 新しいディレクトリの名前を入力してください。
|
||||||
newFile: 新しいファイルを作成
|
newFile: 新しいファイルを作成
|
||||||
newFileMessage: 新しいファイルの名前を入力してください。
|
newFileMessage: 新しいファイルの名前を入力してください。
|
||||||
numberDirs: ディレクトリ個数
|
numberDirs: ディレクトリ個数
|
||||||
numberFiles: ファイル個数
|
numberFiles: ファイル個数
|
||||||
replace: 置き換える
|
replace: 置き換える
|
||||||
replaceMessage: >
|
replaceMessage: >
|
||||||
アップロードするファイルの中でかち合う名前が一つあります。
|
アップロードするファイルの中でかち合う名前が一つあります。
|
||||||
既存のファイルを置き換えりませんか。
|
既存のファイルを置き換えりませんか。
|
||||||
rename: 名前を変更
|
rename: 名前を変更
|
||||||
renameMessage: 名前を変更しようファイルは:
|
renameMessage: 名前を変更しようファイルは:
|
||||||
show: 表示
|
show: 表示
|
||||||
size: サイズ
|
size: サイズ
|
||||||
schedule: スケジュール
|
schedule: スケジュール
|
||||||
scheduleMessage: このポストの発表日付をスケジュールしてください。
|
scheduleMessage: このポストの発表日付をスケジュールしてください。
|
||||||
newArchetype: ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。
|
newArchetype: ある元型に基づいて新しいポストを作成します。ファイルは コンテンツフォルダに作成されます。
|
||||||
settings:
|
settings:
|
||||||
admin: 管理者
|
admin: 管理者
|
||||||
administrator: 管理者
|
administrator: 管理者
|
||||||
allowCommands: コマンドの実行
|
allowCommands: コマンドの実行
|
||||||
allowEdit: ファイルやディレクトリの編集、名前変更と削除
|
allowEdit: ファイルやディレクトリの編集、名前変更と削除
|
||||||
allowNew: ファイルとディレクトリの作成
|
allowNew: ファイルとディレクトリの作成
|
||||||
allowPublish: ポストとぺーじの発表
|
allowPublish: ポストとぺーじの発表
|
||||||
avoidChanges: "(変更を避けるために空白にしてください)"
|
avoidChanges: "(変更を避けるために空白にしてください)"
|
||||||
changePassword: パスワードを変更
|
changePassword: パスワードを変更
|
||||||
commands: コマンド
|
commands: コマンド
|
||||||
commandsHelp: "\
|
commandsHelp: "\
|
||||||
ここで、名前付きイベントに実行するコマンドを設定することができます。\
|
ここで、名前付きイベントに実行するコマンドを設定することができます。\
|
||||||
一行にコマンド一つを入力してください。\
|
一行にコマンド一つを入力してください。\
|
||||||
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
|
イベントはファイルに関連する場合、例えばファイル保存の前にまたは後で、\
|
||||||
環境変数 FILE はファイルのパスに割り当てられます。"
|
環境変数 FILE はファイルのパスに割り当てられます。"
|
||||||
commandsUpdated: コマンドは更新されました!
|
commandsUpdated: コマンドは更新されました!
|
||||||
customStylesheet: カスタムスタイルシ ート
|
customStylesheet: カスタムスタイルシ ート
|
||||||
examples: 例
|
examples: 例
|
||||||
globalSettings: グローバル設定
|
globalSettings: グローバル設定
|
||||||
language: 言語
|
language: 言語
|
||||||
lockPassword: 新しいパスワードを変更に禁止
|
lockPassword: 新しいパスワードを変更に禁止
|
||||||
newPassword: 新しいパスワード
|
newPassword: 新しいパスワード
|
||||||
newPasswordConfirm: 新しいパスワードを確認します
|
newPasswordConfirm: 新しいパスワードを確認します
|
||||||
newUser: 新しいユーザー
|
newUser: 新しいユーザー
|
||||||
password: パスワード
|
password: パスワード
|
||||||
passwordUpdated: パスワードは更新されました!
|
passwordUpdated: パスワードは更新されました!
|
||||||
permissions: 権限
|
permissions: 権限
|
||||||
permissionsHelp: "\
|
permissionsHelp: "\
|
||||||
あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\
|
あなたはユーザーを管理者に設定し、または権限を個々に設定しできます。\
|
||||||
\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。\
|
\"管理者\"を選択する場合、その他のすべての選択肢は自動的に設定されます。\
|
||||||
ユーザーの管理は管理者の権限として保留されました。"
|
ユーザーの管理は管理者の権限として保留されました。"
|
||||||
profileSettings: プロファイル設定
|
profileSettings: プロファイル設定
|
||||||
ruleExample1: "\
|
ruleExample1: "\
|
||||||
各フォルダに名前はドットで始まるファイル(例えば、.git、.gitignore)\
|
各フォルダに名前はドットで始まるファイル(例えば、.git、.gitignore)\
|
||||||
へのアクセスを制限します。"
|
へのアクセスを制限します。"
|
||||||
ruleExample2: 範囲のルートパスに名前は Caddyfile のファイルへのアクセスを制限します。
|
ruleExample2: 範囲のルートパスに名前は Caddyfile のファイルへのアクセスを制限します。
|
||||||
rules: 規則
|
rules: 規則
|
||||||
rulesHelp1: "\
|
rulesHelp1: "\
|
||||||
ここに、あなたはこのユーザーの許可または拒否規則を設定できます。\
|
ここに、あなたはこのユーザーの許可または拒否規則を設定できます。\
|
||||||
ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。\
|
ブロックされたファイルはリストに表示されません、それではアクセスも制限されます。\
|
||||||
正規表現(regex)のサポートと範囲に相対のパスが提供されています。"
|
正規表現(regex)のサポートと範囲に相対のパスが提供されています。"
|
||||||
rulesHelp2: "\
|
rulesHelp2: "\
|
||||||
一行に規則一つを入力してください、\
|
一行に規則一つを入力してください、\
|
||||||
その間に規則はキーワード {0} や {1} で始める必要があります。\
|
その間に規則はキーワード {0} や {1} で始める必要があります。\
|
||||||
そして正規表現を使う場合、{2} と入力し、表現やパスを入力してください。"
|
そして正規表現を使う場合、{2} と入力し、表現やパスを入力してください。"
|
||||||
scope: 範囲
|
scope: 範囲
|
||||||
settingsUpdated: 設定は更新されました!
|
settingsUpdated: 設定は更新されました!
|
||||||
user: ユーザー
|
user: ユーザー
|
||||||
userCommands: ユーザーのコマンド
|
userCommands: ユーザーのコマンド
|
||||||
userCommandsHelp: "\
|
userCommandsHelp: "\
|
||||||
空白区切りの有効のコマンドのリストを指定してください。\
|
空白区切りの有効のコマンドのリストを指定してください。\
|
||||||
例:"
|
例:"
|
||||||
userCreated: ユーザーは作成されました!
|
userCreated: ユーザーは作成されました!
|
||||||
userDeleted: ユーザーは削除されました!
|
userDeleted: ユーザーは削除されました!
|
||||||
userManagement: ユーザー管理
|
userManagement: ユーザー管理
|
||||||
username: ユーザー名
|
username: ユーザー名
|
||||||
users: ユーザー
|
users: ユーザー
|
||||||
userUpdated: ユーザーは更新されました!
|
userUpdated: ユーザーは更新されました!
|
||||||
sidebar:
|
sidebar:
|
||||||
help: ヘルプ
|
help: ヘルプ
|
||||||
logout: ログアウト
|
logout: ログアウト
|
||||||
myFiles: 私のファイル
|
myFiles: 私のファイル
|
||||||
newFile: 新しいファイルを作成
|
newFile: 新しいファイルを作成
|
||||||
newFolder: 新しいフォルダを作成
|
newFolder: 新しいフォルダを作成
|
||||||
settings: 設定
|
settings: 設定
|
||||||
siteSettings: サイト設定
|
siteSettings: サイト設定
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
preview: プレビュー
|
preview: プレビュー
|
||||||
search:
|
search:
|
||||||
images: 画像
|
images: 画像
|
||||||
music: 音楽
|
music: 音楽
|
||||||
pdf: PDF
|
pdf: PDF
|
||||||
pressToExecute: Enter を押して実行します。
|
pressToExecute: Enter を押して実行します。
|
||||||
pressToSearch: Enter を押して検索します。
|
pressToSearch: Enter を押して検索します。
|
||||||
search: 検索...
|
search: 検索...
|
||||||
searchOrCommand: コマンドを検索または実行します。
|
searchOrCommand: コマンドを検索または実行します。
|
||||||
searchOrSupportedCommand: サポートしているコマンドを検索または実行します:
|
searchOrSupportedCommand: サポートしているコマンドを検索または実行します:
|
||||||
type: キーワードを入力し、Enter を押して検索します。
|
type: キーワードを入力し、Enter を押して検索します。
|
||||||
types: 種類
|
types: 種類
|
||||||
video: ビデオ
|
video: ビデオ
|
||||||
writeToSearch: ここにキーワードを入力してください
|
writeToSearch: ここにキーワードを入力してください
|
||||||
languages:
|
languages:
|
||||||
en: English
|
en: English
|
||||||
fr: Français
|
fr: Français
|
||||||
pt: Português
|
pt: Português
|
||||||
ja: 日本語
|
ja: 日本語
|
||||||
zhCN: 中文 (简体)
|
zhCN: 中文 (简体)
|
||||||
zhTW: 中文 (繁體)
|
zhTW: 中文 (繁體)
|
||||||
time:
|
time:
|
||||||
unit: 時間単位
|
unit: 時間単位
|
||||||
seconds: 秒
|
seconds: 秒
|
||||||
minutes: 分
|
minutes: 分
|
||||||
hours: 時間
|
hours: 時間
|
||||||
days: 日
|
days: 日
|
||||||
|
|
|
@ -1,198 +1,198 @@
|
||||||
permanent: 永久
|
permanent: 永久
|
||||||
buttons:
|
buttons:
|
||||||
cancel: 取消
|
cancel: 取消
|
||||||
close: 关闭
|
close: 关闭
|
||||||
copy: 复制
|
copy: 复制
|
||||||
copyFile: 复制文件
|
copyFile: 复制文件
|
||||||
copyToClipboard: 复制到剪贴板
|
copyToClipboard: 复制到剪贴板
|
||||||
create: 创建
|
create: 创建
|
||||||
delete: 删除
|
delete: 删除
|
||||||
download: 下载
|
download: 下载
|
||||||
info: 信息
|
info: 信息
|
||||||
more: 更多
|
more: 更多
|
||||||
move: 移动
|
move: 移动
|
||||||
moveFile: 移动文件
|
moveFile: 移动文件
|
||||||
new: 新
|
new: 新
|
||||||
next: 下一个
|
next: 下一个
|
||||||
ok: 确定
|
ok: 确定
|
||||||
replace: 替换
|
replace: 替换
|
||||||
previous: 上一个
|
previous: 上一个
|
||||||
rename: 重命名
|
rename: 重命名
|
||||||
reportIssue: 报告问题
|
reportIssue: 报告问题
|
||||||
save: 保存
|
save: 保存
|
||||||
search: 搜索
|
search: 搜索
|
||||||
select: 选择
|
select: 选择
|
||||||
share: 分享
|
share: 分享
|
||||||
publish: 发布
|
publish: 发布
|
||||||
selectMultiple: 选择多个
|
selectMultiple: 选择多个
|
||||||
schedule: 计划
|
schedule: 计划
|
||||||
switchView: 切换显示方式
|
switchView: 切换显示方式
|
||||||
toggleSidebar: 切换侧边栏
|
toggleSidebar: 切换侧边栏
|
||||||
update: 更新
|
update: 更新
|
||||||
upload: 上传
|
upload: 上传
|
||||||
permalink: 获取永久链接
|
permalink: 获取永久链接
|
||||||
success:
|
success:
|
||||||
linkCopied: 链接已复制!
|
linkCopied: 链接已复制!
|
||||||
errors:
|
errors:
|
||||||
forbidden: 你被禁止访问。
|
forbidden: 你被禁止访问。
|
||||||
internal: 内部出现麻烦了。
|
internal: 内部出现麻烦了。
|
||||||
notFound: 找不到文件。
|
notFound: 找不到文件。
|
||||||
files:
|
files:
|
||||||
folders: 文件夹
|
folders: 文件夹
|
||||||
files: 文件
|
files: 文件
|
||||||
body: Body
|
body: Body
|
||||||
clear: 清空
|
clear: 清空
|
||||||
closePreview: 关闭预览
|
closePreview: 关闭预览
|
||||||
home: 主页
|
home: 主页
|
||||||
lastModified: 最后修改
|
lastModified: 最后修改
|
||||||
loading: 加载中...
|
loading: 加载中...
|
||||||
lonely: 这里没有任何文件...
|
lonely: 这里没有任何文件...
|
||||||
metadata: 元数据
|
metadata: 元数据
|
||||||
multipleSelectionEnabled: 多选模式已开启
|
multipleSelectionEnabled: 多选模式已开启
|
||||||
name: 名称
|
name: 名称
|
||||||
size: 大小
|
size: 大小
|
||||||
sortByName: 按名称排序
|
sortByName: 按名称排序
|
||||||
sortBySize: 按大小排序
|
sortBySize: 按大小排序
|
||||||
sortByLastModified: 按最后修改时间排序
|
sortByLastModified: 按最后修改时间排序
|
||||||
help:
|
help:
|
||||||
click: 选择文件或目录
|
click: 选择文件或目录
|
||||||
ctrl:
|
ctrl:
|
||||||
click: 选择多个文件或目录
|
click: 选择多个文件或目录
|
||||||
f: 打开搜索框
|
f: 打开搜索框
|
||||||
s: 保存文件或下载当前文件夹
|
s: 保存文件或下载当前文件夹
|
||||||
del: 删除所选的文件/文件夹
|
del: 删除所选的文件/文件夹
|
||||||
doubleClick: 打开文件/文件夹
|
doubleClick: 打开文件/文件夹
|
||||||
esc: 清除已选项或关闭提示信息
|
esc: 清除已选项或关闭提示信息
|
||||||
f1: 显示该帮助信息
|
f1: 显示该帮助信息
|
||||||
f2: 重命名文件/文件夹
|
f2: 重命名文件/文件夹
|
||||||
help: 帮助
|
help: 帮助
|
||||||
login:
|
login:
|
||||||
password: 密码
|
password: 密码
|
||||||
submit: 登录
|
submit: 登录
|
||||||
username: 用户名
|
username: 用户名
|
||||||
wrongCredentials: 用户名或密码错误
|
wrongCredentials: 用户名或密码错误
|
||||||
prompts:
|
prompts:
|
||||||
copy: 复制
|
copy: 复制
|
||||||
copyMessage: 请选择欲复制至的目录:
|
copyMessage: 请选择欲复制至的目录:
|
||||||
currentlyNavigating: 当前目录:
|
currentlyNavigating: 当前目录:
|
||||||
deleteMessageMultiple: 你确定要删除这 {count} 个文件吗?
|
deleteMessageMultiple: 你确定要删除这 {count} 个文件吗?
|
||||||
deleteMessageSingle: 你确定要删除这个文件/文件夹吗?
|
deleteMessageSingle: 你确定要删除这个文件/文件夹吗?
|
||||||
deleteTitle: 删除文件
|
deleteTitle: 删除文件
|
||||||
displayName: 名称:
|
displayName: 名称:
|
||||||
download: 下载文件
|
download: 下载文件
|
||||||
downloadMessage: 请选择要下载的压缩格式。
|
downloadMessage: 请选择要下载的压缩格式。
|
||||||
error: 出了一点问题...
|
error: 出了一点问题...
|
||||||
fileInfo: 文件信息
|
fileInfo: 文件信息
|
||||||
filesSelected: 已选择 {count} 个文件。
|
filesSelected: 已选择 {count} 个文件。
|
||||||
lastModified: 最后修改
|
lastModified: 最后修改
|
||||||
move: 移动
|
move: 移动
|
||||||
moveMessage: 请选择欲移动至的目录:
|
moveMessage: 请选择欲移动至的目录:
|
||||||
newDir: 新建目录
|
newDir: 新建目录
|
||||||
newDirMessage: 请输入新目录的名称。
|
newDirMessage: 请输入新目录的名称。
|
||||||
newFile: 新建文件
|
newFile: 新建文件
|
||||||
newFileMessage: 请输入新文件的名称。
|
newFileMessage: 请输入新文件的名称。
|
||||||
numberDirs: 目录数
|
numberDirs: 目录数
|
||||||
numberFiles: 文件数
|
numberFiles: 文件数
|
||||||
replace: 替换
|
replace: 替换
|
||||||
replaceMessage: "\
|
replaceMessage: "\
|
||||||
您尝试上传的文件中有一个与现有文件的名称存在冲突。\
|
您尝试上传的文件中有一个与现有文件的名称存在冲突。\
|
||||||
是否替换现有的同名文件?"
|
是否替换现有的同名文件?"
|
||||||
rename: 重命名
|
rename: 重命名
|
||||||
renameMessage: 请输入新名称,旧名称为:
|
renameMessage: 请输入新名称,旧名称为:
|
||||||
show: 揭示
|
show: 揭示
|
||||||
size: 大小
|
size: 大小
|
||||||
schedule: 计划
|
schedule: 计划
|
||||||
scheduleMessage: 请选择发布这篇帖子的日期。
|
scheduleMessage: 请选择发布这篇帖子的日期。
|
||||||
newArchetype: 创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。
|
newArchetype: 创建一个基于原型的新帖子。您的文件将会创建在内容文件夹中。
|
||||||
settings:
|
settings:
|
||||||
admin: 管理员
|
admin: 管理员
|
||||||
administrator: 管理员
|
administrator: 管理员
|
||||||
allowCommands: 执行命令(Linux 代码)
|
allowCommands: 执行命令(Linux 代码)
|
||||||
allowEdit: 编辑、重命名或删除文件/目录
|
allowEdit: 编辑、重命名或删除文件/目录
|
||||||
allowNew: 创建新文件和目录
|
allowNew: 创建新文件和目录
|
||||||
allowPublish: 发布新的帖子与页面
|
allowPublish: 发布新的帖子与页面
|
||||||
avoidChanges: '(留空以避免更改)'
|
avoidChanges: '(留空以避免更改)'
|
||||||
changePassword: 更改密码
|
changePassword: 更改密码
|
||||||
commands: 命令(linux 代码)
|
commands: 命令(linux 代码)
|
||||||
commandsHelp: "\
|
commandsHelp: "\
|
||||||
在这里,您可以设置在指定事件下执行的命令,一行一条。\
|
在这里,您可以设置在指定事件下执行的命令,一行一条。\
|
||||||
若事件与文件相关,如“在保存文件前”,\
|
若事件与文件相关,如“在保存文件前”,\
|
||||||
则文件的路径会被赋值给环境变量 \"FILE\"。"
|
则文件的路径会被赋值给环境变量 \"FILE\"。"
|
||||||
commandsUpdated: 命令已更新!
|
commandsUpdated: 命令已更新!
|
||||||
customStylesheet: 自定义样式表
|
customStylesheet: 自定义样式表
|
||||||
examples: 例子
|
examples: 例子
|
||||||
globalSettings: 全局设置
|
globalSettings: 全局设置
|
||||||
language: 语言
|
language: 语言
|
||||||
lockPassword: 禁止用户修改密码
|
lockPassword: 禁止用户修改密码
|
||||||
newPassword: 您的新密码
|
newPassword: 您的新密码
|
||||||
newPasswordConfirm: 重输一遍新密码
|
newPasswordConfirm: 重输一遍新密码
|
||||||
newUser: 新建用户
|
newUser: 新建用户
|
||||||
password: 密码
|
password: 密码
|
||||||
passwordUpdated: 密码已更新!
|
passwordUpdated: 密码已更新!
|
||||||
permissions: 权限
|
permissions: 权限
|
||||||
permissionsHelp: "\
|
permissionsHelp: "\
|
||||||
您可以将该用户设置为管理员,也可以单独选择各项权限。\
|
您可以将该用户设置为管理员,也可以单独选择各项权限。\
|
||||||
如果选择了“管理员”,则其他的选项会被自动勾上,\
|
如果选择了“管理员”,则其他的选项会被自动勾上,\
|
||||||
同时该用户可以管理其他用户。"
|
同时该用户可以管理其他用户。"
|
||||||
profileSettings: 配置文件设置
|
profileSettings: 配置文件设置
|
||||||
ruleExample1: "\
|
ruleExample1: "\
|
||||||
阻止用户访问所有文件夹下任何以 . 开头的文件\
|
阻止用户访问所有文件夹下任何以 . 开头的文件\
|
||||||
(隐藏文件, 例如: .git, .gitignore)。"
|
(隐藏文件, 例如: .git, .gitignore)。"
|
||||||
ruleExample2: 阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。
|
ruleExample2: 阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。
|
||||||
rules: 规则
|
rules: 规则
|
||||||
rulesHelp1: "\
|
rulesHelp1: "\
|
||||||
您可以为该用户制定一组黑名单或白名单式的规则,\
|
您可以为该用户制定一组黑名单或白名单式的规则,\
|
||||||
被屏蔽的文件将不会显示在列表中,用户也无权限访问,\
|
被屏蔽的文件将不会显示在列表中,用户也无权限访问,\
|
||||||
支持相对于目录范围的路径。"
|
支持相对于目录范围的路径。"
|
||||||
rulesHelp2: "\
|
rulesHelp2: "\
|
||||||
每行一条规则,且必须以关键词 {0} 或 {1} 开头。\
|
每行一条规则,且必须以关键词 {0} 或 {1} 开头。\
|
||||||
如要使用正则表达式,请在加上 {2} 之后再附上表达式或路径。"
|
如要使用正则表达式,请在加上 {2} 之后再附上表达式或路径。"
|
||||||
scope: 目录范围
|
scope: 目录范围
|
||||||
settingsUpdated: 设置已更新!
|
settingsUpdated: 设置已更新!
|
||||||
user: 用户
|
user: 用户
|
||||||
userCommands: 用户命令(Linux 代码)
|
userCommands: 用户命令(Linux 代码)
|
||||||
userCommandsHelp: "\
|
userCommandsHelp: "\
|
||||||
指定该用户可以执行的命令(Linux 代码),用空格分隔。\
|
指定该用户可以执行的命令(Linux 代码),用空格分隔。\
|
||||||
例如:"
|
例如:"
|
||||||
userCreated: 用户已创建!
|
userCreated: 用户已创建!
|
||||||
userDeleted: 用户已删除!
|
userDeleted: 用户已删除!
|
||||||
userManagement: 用户管理
|
userManagement: 用户管理
|
||||||
username: 用户名
|
username: 用户名
|
||||||
users: 用户
|
users: 用户
|
||||||
userUpdated: 用户已更新!
|
userUpdated: 用户已更新!
|
||||||
sidebar:
|
sidebar:
|
||||||
help: 帮助
|
help: 帮助
|
||||||
logout: 登出
|
logout: 登出
|
||||||
myFiles: 我的文件
|
myFiles: 我的文件
|
||||||
newFile: 新建文件
|
newFile: 新建文件
|
||||||
newFolder: 新建文件夹
|
newFolder: 新建文件夹
|
||||||
settings: 设置
|
settings: 设置
|
||||||
siteSettings: 网站设置
|
siteSettings: 网站设置
|
||||||
hugoNew: Hugo New
|
hugoNew: Hugo New
|
||||||
preview: 预览
|
preview: 预览
|
||||||
search:
|
search:
|
||||||
images: 图像
|
images: 图像
|
||||||
music: 音乐
|
music: 音乐
|
||||||
pdf: PDF
|
pdf: PDF
|
||||||
pressToExecute: 按回车键执行。
|
pressToExecute: 按回车键执行。
|
||||||
pressToSearch: 按回车键搜索。
|
pressToSearch: 按回车键搜索。
|
||||||
search: 搜索...
|
search: 搜索...
|
||||||
searchOrCommand: 搜索或者执行命令(Linux 代码)...
|
searchOrCommand: 搜索或者执行命令(Linux 代码)...
|
||||||
searchOrSupportedCommand: 搜索或使用您可以使用的命令(一次只能执行一个命令):
|
searchOrSupportedCommand: 搜索或使用您可以使用的命令(一次只能执行一个命令):
|
||||||
type: 键入并按回车键进行搜索。
|
type: 键入并按回车键进行搜索。
|
||||||
types: 类型
|
types: 类型
|
||||||
video: 视频
|
video: 视频
|
||||||
writeToSearch: 请输入要搜索的内容
|
writeToSearch: 请输入要搜索的内容
|
||||||
languages:
|
languages:
|
||||||
en: English
|
en: English
|
||||||
fr: Français
|
fr: Français
|
||||||
pt: Português
|
pt: Português
|
||||||
ja: 日本語
|
ja: 日本語
|
||||||
zhCN: 中文 (简体)
|
zhCN: 中文 (简体)
|
||||||
zhTW: 中文 (繁體)
|
zhTW: 中文 (繁體)
|
||||||
time:
|
time:
|
||||||
unit: 时间单位
|
unit: 时间单位
|
||||||
seconds: 秒
|
seconds: 秒
|
||||||
minutes: 分钟
|
minutes: 分钟
|
||||||
hours: 小时
|
hours: 小时
|
||||||
days: 天
|
days: 天
|
||||||
|
|
|
@ -1,60 +1,60 @@
|
||||||
// Most of the code from this file comes from:
|
// Most of the code from this file comes from:
|
||||||
// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js
|
// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js
|
||||||
import * as CodeMirror from 'codemirror'
|
import * as CodeMirror from 'codemirror'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
// Make CodeMirror available globally so the modes' can register themselves.
|
// Make CodeMirror available globally so the modes' can register themselves.
|
||||||
window.CodeMirror = CodeMirror
|
window.CodeMirror = CodeMirror
|
||||||
CodeMirror.modeURL = store.state.baseURL + '/static/js/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = store.state.baseURL + '/static/js/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
var loading = {}
|
var loading = {}
|
||||||
|
|
||||||
function splitCallback (cont, n) {
|
function splitCallback (cont, n) {
|
||||||
var countDown = n
|
var countDown = n
|
||||||
return function () {
|
return function () {
|
||||||
if (--countDown === 0) cont()
|
if (--countDown === 0) cont()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureDeps (mode, cont) {
|
function ensureDeps (mode, cont) {
|
||||||
var deps = CodeMirror.modes[mode].dependencies
|
var deps = CodeMirror.modes[mode].dependencies
|
||||||
if (!deps) return cont()
|
if (!deps) return cont()
|
||||||
var missing = []
|
var missing = []
|
||||||
for (var i = 0; i < deps.length; ++i) {
|
for (var i = 0; i < deps.length; ++i) {
|
||||||
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
|
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
|
||||||
}
|
}
|
||||||
if (!missing.length) return cont()
|
if (!missing.length) return cont()
|
||||||
var split = splitCallback(cont, missing.length)
|
var split = splitCallback(cont, missing.length)
|
||||||
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
|
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.requireMode = function (mode, cont) {
|
CodeMirror.requireMode = function (mode, cont) {
|
||||||
if (typeof mode !== 'string') mode = mode.name
|
if (typeof mode !== 'string') mode = mode.name
|
||||||
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
|
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
|
||||||
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
|
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
|
||||||
|
|
||||||
var file = CodeMirror.modeURL.replace(/%N/g, mode)
|
var file = CodeMirror.modeURL.replace(/%N/g, mode)
|
||||||
|
|
||||||
var script = document.createElement('script')
|
var script = document.createElement('script')
|
||||||
script.src = file
|
script.src = file
|
||||||
var others = document.getElementsByTagName('script')[0]
|
var others = document.getElementsByTagName('script')[0]
|
||||||
var list = loading[mode] = [cont]
|
var list = loading[mode] = [cont]
|
||||||
|
|
||||||
CodeMirror.on(script, 'load', function () {
|
CodeMirror.on(script, 'load', function () {
|
||||||
ensureDeps(mode, function () {
|
ensureDeps(mode, function () {
|
||||||
for (var i = 0; i < list.length; ++i) list[i]()
|
for (var i = 0; i < list.length; ++i) list[i]()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
others.parentNode.insertBefore(script, others)
|
others.parentNode.insertBefore(script, others)
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.autoLoadMode = function (instance, mode) {
|
CodeMirror.autoLoadMode = function (instance, mode) {
|
||||||
if (CodeMirror.modes.hasOwnProperty(mode)) return
|
if (CodeMirror.modes.hasOwnProperty(mode)) return
|
||||||
|
|
||||||
CodeMirror.requireMode(mode, function () {
|
CodeMirror.requireMode(mode, function () {
|
||||||
instance.setOption('mode', mode)
|
instance.setOption('mode', mode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CodeMirror
|
export default CodeMirror
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function (name) {
|
export default function (name) {
|
||||||
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
|
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
|
||||||
return document.cookie.replace(re, '$1')
|
return document.cookie.replace(re, '$1')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
export default function getRule (rules) {
|
export default function getRule (rules) {
|
||||||
for (let i = 0; i < rules.length; i++) {
|
for (let i = 0; i < rules.length; i++) {
|
||||||
rules[i] = rules[i].toLowerCase()
|
rules[i] = rules[i].toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = null
|
let result = null
|
||||||
let find = Array.prototype.find
|
let find = Array.prototype.find
|
||||||
|
|
||||||
find.call(document.styleSheets, styleSheet => {
|
find.call(document.styleSheets, styleSheet => {
|
||||||
result = find.call(styleSheet.cssRules, cssRule => {
|
result = find.call(styleSheet.cssRules, cssRule => {
|
||||||
let found = false
|
let found = false
|
||||||
|
|
||||||
if (cssRule instanceof window.CSSStyleRule) {
|
if (cssRule instanceof window.CSSStyleRule) {
|
||||||
for (let i = 0; i < rules.length; i++) {
|
for (let i = 0; i < rules.length; i++) {
|
||||||
if (cssRule.selectorText.toLowerCase() === rules[i]) {
|
if (cssRule.selectorText.toLowerCase() === rules[i]) {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return found
|
return found
|
||||||
})
|
})
|
||||||
|
|
||||||
return result != null
|
return result != null
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
function removeLastDir (url) {
|
function removeLastDir (url) {
|
||||||
var arr = url.split('/')
|
var arr = url.split('/')
|
||||||
if (arr.pop() === '') {
|
if (arr.pop() === '') {
|
||||||
arr.pop()
|
arr.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr.join('/')
|
return arr.join('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
removeLastDir: removeLastDir
|
removeLastDir: removeLastDir
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,231 +1,231 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div id="breadcrumbs">
|
<div id="breadcrumbs">
|
||||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||||
<i class="material-icons">home</i>
|
<i class="material-icons">home</i>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span v-for="link in breadcrumbs" :key="link.name">
|
<span v-for="link in breadcrumbs" :key="link.name">
|
||||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error">
|
<div v-if="error">
|
||||||
<not-found v-if="error.message === '404'"></not-found>
|
<not-found v-if="error.message === '404'"></not-found>
|
||||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||||
<internal-error v-else></internal-error>
|
<internal-error v-else></internal-error>
|
||||||
</div>
|
</div>
|
||||||
<editor v-else-if="isEditor"></editor>
|
<editor v-else-if="isEditor"></editor>
|
||||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||||
<preview v-else-if="isPreview"></preview>
|
<preview v-else-if="isPreview"></preview>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<span>{{ $t('files.loading') }}</span>
|
<span>{{ $t('files.loading') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Forbidden from './errors/403'
|
import Forbidden from './errors/403'
|
||||||
import NotFound from './errors/404'
|
import NotFound from './errors/404'
|
||||||
import InternalError from './errors/500'
|
import InternalError from './errors/500'
|
||||||
import Preview from '@/components/files/Preview'
|
import Preview from '@/components/files/Preview'
|
||||||
import Listing from '@/components/files/Listing'
|
import Listing from '@/components/files/Listing'
|
||||||
import Editor from '@/components/files/Editor'
|
import Editor from '@/components/files/Editor'
|
||||||
import * as api from '@/utils/api'
|
import * as api from '@/utils/api'
|
||||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'files',
|
name: 'files',
|
||||||
components: {
|
components: {
|
||||||
Forbidden,
|
Forbidden,
|
||||||
NotFound,
|
NotFound,
|
||||||
InternalError,
|
InternalError,
|
||||||
Preview,
|
Preview,
|
||||||
Listing,
|
Listing,
|
||||||
Editor
|
Editor
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
'selectedCount'
|
'selectedCount'
|
||||||
]),
|
]),
|
||||||
...mapState([
|
...mapState([
|
||||||
'req',
|
'req',
|
||||||
'user',
|
'user',
|
||||||
'reload',
|
'reload',
|
||||||
'multiple',
|
'multiple',
|
||||||
'loading'
|
'loading'
|
||||||
]),
|
]),
|
||||||
isListing () {
|
isListing () {
|
||||||
return this.req.kind === 'listing' && !this.loading
|
return this.req.kind === 'listing' && !this.loading
|
||||||
},
|
},
|
||||||
isPreview () {
|
isPreview () {
|
||||||
return this.req.kind === 'preview' && !this.loading
|
return this.req.kind === 'preview' && !this.loading
|
||||||
},
|
},
|
||||||
isEditor () {
|
isEditor () {
|
||||||
return this.req.kind === 'editor' && !this.loading
|
return this.req.kind === 'editor' && !this.loading
|
||||||
},
|
},
|
||||||
breadcrumbs () {
|
breadcrumbs () {
|
||||||
let parts = this.$route.path.split('/')
|
let parts = this.$route.path.split('/')
|
||||||
|
|
||||||
if (parts[0] === '') {
|
if (parts[0] === '') {
|
||||||
parts.shift()
|
parts.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parts[parts.length - 1] === '') {
|
if (parts[parts.length - 1] === '') {
|
||||||
parts.pop()
|
parts.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
let breadcrumbs = []
|
let breadcrumbs = []
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
||||||
} else {
|
} else {
|
||||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
breadcrumbs.shift()
|
breadcrumbs.shift()
|
||||||
|
|
||||||
if (breadcrumbs.length > 3) {
|
if (breadcrumbs.length > 3) {
|
||||||
while (breadcrumbs.length !== 4) {
|
while (breadcrumbs.length !== 4) {
|
||||||
breadcrumbs.shift()
|
breadcrumbs.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
breadcrumbs[0].name = '...'
|
breadcrumbs[0].name = '...'
|
||||||
}
|
}
|
||||||
|
|
||||||
return breadcrumbs
|
return breadcrumbs
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
error: null
|
error: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route': 'fetchData',
|
'$route': 'fetchData',
|
||||||
'reload': function () {
|
'reload': function () {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
window.addEventListener('keydown', this.keyEvent)
|
window.addEventListener('keydown', this.keyEvent)
|
||||||
window.addEventListener('scroll', this.scroll)
|
window.addEventListener('scroll', this.scroll)
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.keyEvent)
|
window.removeEventListener('keydown', this.keyEvent)
|
||||||
window.removeEventListener('scroll', this.scroll)
|
window.removeEventListener('scroll', this.scroll)
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
this.$store.commit('updateRequest', {})
|
this.$store.commit('updateRequest', {})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations([ 'setLoading' ]),
|
...mapMutations([ 'setLoading' ]),
|
||||||
fetchData () {
|
fetchData () {
|
||||||
// Reset view information.
|
// Reset view information.
|
||||||
this.$store.commit('setReload', false)
|
this.$store.commit('setReload', false)
|
||||||
this.$store.commit('resetSelected')
|
this.$store.commit('resetSelected')
|
||||||
this.$store.commit('multiple', false)
|
this.$store.commit('multiple', false)
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
// Set loading to true and reset the error.
|
// Set loading to true and reset the error.
|
||||||
this.setLoading(true)
|
this.setLoading(true)
|
||||||
this.error = null
|
this.error = null
|
||||||
|
|
||||||
let url = this.$route.path
|
let url = this.$route.path
|
||||||
if (url === '') url = '/'
|
if (url === '') url = '/'
|
||||||
if (url[0] !== '/') url = '/' + url
|
if (url[0] !== '/') url = '/' + url
|
||||||
|
|
||||||
api.fetch(url)
|
api.fetch(url)
|
||||||
.then((req) => {
|
.then((req) => {
|
||||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('updateRequest', req)
|
this.$store.commit('updateRequest', req)
|
||||||
document.title = req.name
|
document.title = req.name
|
||||||
this.setLoading(false)
|
this.setLoading(false)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.setLoading(false)
|
this.setLoading(false)
|
||||||
this.error = error
|
this.error = error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
keyEvent (event) {
|
keyEvent (event) {
|
||||||
// Esc!
|
// Esc!
|
||||||
if (event.keyCode === 27) {
|
if (event.keyCode === 27) {
|
||||||
this.$store.commit('closeHovers')
|
this.$store.commit('closeHovers')
|
||||||
|
|
||||||
// If we're on a listing, unselect all
|
// If we're on a listing, unselect all
|
||||||
// files and folders.
|
// files and folders.
|
||||||
if (this.req.kind === 'listing') {
|
if (this.req.kind === 'listing') {
|
||||||
this.$store.commit('resetSelected')
|
this.$store.commit('resetSelected')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Del!
|
// Del!
|
||||||
if (event.keyCode === 46) {
|
if (event.keyCode === 46) {
|
||||||
if (this.req.kind === 'editor' ||
|
if (this.req.kind === 'editor' ||
|
||||||
this.$route.name !== 'Files' ||
|
this.$route.name !== 'Files' ||
|
||||||
this.loading ||
|
this.loading ||
|
||||||
!this.user.allowEdit ||
|
!this.user.allowEdit ||
|
||||||
(this.req.kind === 'listing' && this.selectedCount === 0)) return
|
(this.req.kind === 'listing' && this.selectedCount === 0)) return
|
||||||
|
|
||||||
this.$store.commit('showHover', 'delete')
|
this.$store.commit('showHover', 'delete')
|
||||||
}
|
}
|
||||||
|
|
||||||
// F1!
|
// F1!
|
||||||
if (event.keyCode === 112) {
|
if (event.keyCode === 112) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.$store.commit('showHover', 'help')
|
this.$store.commit('showHover', 'help')
|
||||||
}
|
}
|
||||||
|
|
||||||
// F2!
|
// F2!
|
||||||
if (event.keyCode === 113) {
|
if (event.keyCode === 113) {
|
||||||
if (this.req.kind === 'editor' ||
|
if (this.req.kind === 'editor' ||
|
||||||
this.$route.name !== 'Files' ||
|
this.$route.name !== 'Files' ||
|
||||||
this.loading ||
|
this.loading ||
|
||||||
!this.user.allowEdit ||
|
!this.user.allowEdit ||
|
||||||
(this.req.kind === 'listing' && this.selectedCount === 0) ||
|
(this.req.kind === 'listing' && this.selectedCount === 0) ||
|
||||||
(this.req.kind === 'listing' && this.selectedCount > 1)) return
|
(this.req.kind === 'listing' && this.selectedCount > 1)) return
|
||||||
|
|
||||||
this.$store.commit('showHover', 'rename')
|
this.$store.commit('showHover', 'rename')
|
||||||
}
|
}
|
||||||
|
|
||||||
// CTRL + S
|
// CTRL + S
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (this.req.kind !== 'editor') {
|
if (this.req.kind !== 'editor') {
|
||||||
document.getElementById('download-button').click()
|
document.getElementById('download-button').click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scroll (event) {
|
scroll (event) {
|
||||||
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
|
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
|
||||||
|
|
||||||
let top = 112 - window.scrollY
|
let top = 112 - window.scrollY
|
||||||
|
|
||||||
if (top < 64) {
|
if (top < 64) {
|
||||||
top = 64
|
top = 64
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||||
},
|
},
|
||||||
openSidebar () {
|
openSidebar () {
|
||||||
this.$store.commit('showHover', 'sidebar')
|
this.$store.commit('showHover', 'sidebar')
|
||||||
},
|
},
|
||||||
openSearch () {
|
openSearch () {
|
||||||
this.$store.commit('showHover', 'search')
|
this.$store.commit('showHover', 'search')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">error</i>
|
<i class="material-icons">error</i>
|
||||||
<span>{{ $t('errors.forbidden') }}</span>
|
<span>{{ $t('errors.forbidden') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {name: 'forbidden'}
|
export default {name: 'forbidden'}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">gps_off</i>
|
<i class="material-icons">gps_off</i>
|
||||||
<span>{{ $t('errors.notFound') }}</span>
|
<span>{{ $t('errors.notFound') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {name: 'not-found'}
|
export default {name: 'not-found'}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="message">
|
<h2 class="message">
|
||||||
<i class="material-icons">error_outline</i>
|
<i class="material-icons">error_outline</i>
|
||||||
<span>{{ $t('errors.internal') }}</span>
|
<span>{{ $t('errors.internal') }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {name: 'internal-error'}
|
export default {name: 'internal-error'}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "File Manager",
|
"name": "File Manager",
|
||||||
"short_name": "File Manager",
|
"short_name": "File Manager",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-192x192.png",
|
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-512x512.png",
|
"src": "{{ .BaseURL }}/static/img/icons/android-chrome-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "{{ .BaseURL }}/",
|
"start_url": "{{ .BaseURL }}/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"theme_color": "#2979ff"
|
"theme_color": "#2979ff"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,50 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
<title>File Manager</title>
|
<title>File Manager</title>
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||||
<!--[if IE]><link rel="shortcut icon" href="{{ .BaseURL }}/static/img/icons/favicon.ico"><![endif]-->
|
<!--[if IE]><link rel="shortcut icon" href="{{ .BaseURL }}/static/img/icons/favicon.ico"><![endif]-->
|
||||||
<link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
|
<link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
|
||||||
<meta name="theme-color" content="#2979ff">
|
<meta name="theme-color" content="#2979ff">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="assets">
|
<meta name="apple-mobile-web-app-title" content="assets">
|
||||||
<link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
|
<link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
|
||||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
||||||
<meta name="msapplication-TileColor" content="#2979ff">
|
<meta name="msapplication-TileColor" content="#2979ff">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
color: #6f6f6f;
|
color: #6f6f6f;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
}
|
}
|
||||||
body > div {
|
body > div {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
padding: 2em 3em;
|
padding: 2em 3em;
|
||||||
}
|
}
|
||||||
body > a * {
|
body > a * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div><h1>404 Not Found</h1></div>
|
<div><h1>404 Not Found</h1></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,85 +1,85 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
<title>{{ .File.Name }}</title>
|
<title>{{ .File.Name }}</title>
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||||
<!--[if IE]><link rel="shortcut icon" href="{{ .BaseURL }}/static/img/icons/favicon.ico"><![endif]-->
|
<!--[if IE]><link rel="shortcut icon" href="{{ .BaseURL }}/static/img/icons/favicon.ico"><![endif]-->
|
||||||
<link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
|
<link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
|
||||||
<meta name="theme-color" content="#2979ff">
|
<meta name="theme-color" content="#2979ff">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="assets">
|
<meta name="apple-mobile-web-app-title" content="assets">
|
||||||
<link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
|
<link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
|
||||||
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
|
||||||
<meta name="msapplication-TileColor" content="#2979ff">
|
<meta name="msapplication-TileColor" content="#2979ff">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
color: #6f6f6f;
|
color: #6f6f6f;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
body > a {
|
body > a {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-width: 25em;
|
max-width: 25em;
|
||||||
}
|
}
|
||||||
body > a > div:first-child {
|
body > a > div:first-child {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: rgba(0, 0, 0, 0.5);
|
color: rgba(0, 0, 0, 0.5);
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
body > a > div:last-child {
|
body > a > div:last-child {
|
||||||
padding: 2em 3em;
|
padding: 2em 3em;
|
||||||
}
|
}
|
||||||
body > a * {
|
body > a * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
body > a h1 {
|
body > a h1 {
|
||||||
margin-top: .2em;
|
margin-top: .2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="?dl=1">
|
<a href="?dl=1">
|
||||||
<div>Download {{ if .File.IsDir }}Folder{{ else }}File{{ end }}</div>
|
<div>Download {{ if .File.IsDir }}Folder{{ else }}File{{ end }}</div>
|
||||||
<div>
|
<div>
|
||||||
{{ if .File.IsDir -}}
|
{{ if .File.IsDir -}}
|
||||||
<svg fill="#40c4ff" height="150" viewBox="0 0 24 24" width="150" xmlns="http://www.w3.org/2000/svg">
|
<svg fill="#40c4ff" height="150" viewBox="0 0 24 24" width="150" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
|
<path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ else -}}
|
{{ else -}}
|
||||||
<svg fill="#40c4ff" height="150" viewBox="0 0 24 24" width="150" xmlns="http://www.w3.org/2000/svg">
|
<svg fill="#40c4ff" height="150" viewBox="0 0 24 24" width="150" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"/>
|
<path d="M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6H6zm7 7V3.5L18.5 9H13z"/>
|
||||||
<path d="M0 0h24v24H0z" fill="none"/>
|
<path d="M0 0h24v24H0z" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
<h1>{{ .File.Name }}</h1>
|
<h1>{{ .File.Name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigStore is a configuration store.
|
// ConfigStore is a configuration store.
|
||||||
type ConfigStore struct {
|
type ConfigStore struct {
|
||||||
DB *storm.DB
|
DB *storm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a configuration from the database to an interface.
|
// Get gets a configuration from the database to an interface.
|
||||||
func (c ConfigStore) Get(name string, to interface{}) error {
|
func (c ConfigStore) Get(name string, to interface{}) error {
|
||||||
err := c.DB.Get("config", name, to)
|
err := c.DB.Get("config", name, to)
|
||||||
if err == storm.ErrNotFound {
|
if err == storm.ErrNotFound {
|
||||||
return fm.ErrNotExist
|
return fm.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves a configuration from an interface to the database.
|
// Save saves a configuration from an interface to the database.
|
||||||
func (c ConfigStore) Save(name string, from interface{}) error {
|
func (c ConfigStore) Save(name string, from interface{}) error {
|
||||||
return c.DB.Set("config", name, from)
|
return c.DB.Set("config", name, from)
|
||||||
}
|
}
|
||||||
|
|
180
bolt/users.go
180
bolt/users.go
|
@ -1,90 +1,90 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersStore is a users store.
|
// UsersStore is a users store.
|
||||||
type UsersStore struct {
|
type UsersStore struct {
|
||||||
DB *storm.DB
|
DB *storm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a user with a certain id from the database.
|
// Get gets a user with a certain id from the database.
|
||||||
func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) {
|
func (u UsersStore) Get(id int, builder fm.FSBuilder) (*fm.User, error) {
|
||||||
var us fm.User
|
var us fm.User
|
||||||
err := u.DB.One("ID", id, &us)
|
err := u.DB.One("ID", id, &us)
|
||||||
if err == storm.ErrNotFound {
|
if err == storm.ErrNotFound {
|
||||||
return nil, fm.ErrNotExist
|
return nil, fm.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
us.FileSystem = builder(us.Scope)
|
us.FileSystem = builder(us.Scope)
|
||||||
return &us, nil
|
return &us, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByUsername gets a user with a certain username from the database.
|
// GetByUsername gets a user with a certain username from the database.
|
||||||
func (u UsersStore) GetByUsername(username string, builder fm.FSBuilder) (*fm.User, error) {
|
func (u UsersStore) GetByUsername(username string, builder fm.FSBuilder) (*fm.User, error) {
|
||||||
var us fm.User
|
var us fm.User
|
||||||
err := u.DB.One("Username", username, &us)
|
err := u.DB.One("Username", username, &us)
|
||||||
if err == storm.ErrNotFound {
|
if err == storm.ErrNotFound {
|
||||||
return nil, fm.ErrNotExist
|
return nil, fm.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
us.FileSystem = builder(us.Scope)
|
us.FileSystem = builder(us.Scope)
|
||||||
return &us, nil
|
return &us, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets gets all the users from the database.
|
// Gets gets all the users from the database.
|
||||||
func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) {
|
func (u UsersStore) Gets(builder fm.FSBuilder) ([]*fm.User, error) {
|
||||||
var us []*fm.User
|
var us []*fm.User
|
||||||
err := u.DB.All(&us)
|
err := u.DB.All(&us)
|
||||||
if err == storm.ErrNotFound {
|
if err == storm.ErrNotFound {
|
||||||
return nil, fm.ErrNotExist
|
return nil, fm.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return us, err
|
return us, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range us {
|
for _, user := range us {
|
||||||
user.FileSystem = builder(user.Scope)
|
user.FileSystem = builder(user.Scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
return us, err
|
return us, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates the whole user object or only certain fields.
|
// Update updates the whole user object or only certain fields.
|
||||||
func (u UsersStore) Update(us *fm.User, fields ...string) error {
|
func (u UsersStore) Update(us *fm.User, fields ...string) error {
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
return u.Save(us)
|
return u.Save(us)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
val := reflect.ValueOf(us).Elem().FieldByName(field).Interface()
|
val := reflect.ValueOf(us).Elem().FieldByName(field).Interface()
|
||||||
if err := u.DB.UpdateField(us, field, val); err != nil {
|
if err := u.DB.UpdateField(us, field, val); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves a user to the database.
|
// Save saves a user to the database.
|
||||||
func (u UsersStore) Save(us *fm.User) error {
|
func (u UsersStore) Save(us *fm.User) error {
|
||||||
return u.DB.Save(us)
|
return u.DB.Save(us)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a user from the database.
|
// Delete deletes a user from the database.
|
||||||
func (u UsersStore) Delete(id int) error {
|
func (u UsersStore) Delete(id int) error {
|
||||||
return u.DB.DeleteStruct(&fm.User{ID: id})
|
return u.DB.DeleteStruct(&fm.User{ID: id})
|
||||||
}
|
}
|
||||||
|
|
26
build.sh
26
build.sh
|
@ -1,13 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Install rice tool if not present
|
# Install rice tool if not present
|
||||||
if ! [ -x "$(command -v rice)" ]; then
|
if ! [ -x "$(command -v rice)" ]; then
|
||||||
go get github.com/GeertJohan/go.rice/rice
|
go get github.com/GeertJohan/go.rice/rice
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean the dist folder and build the assets
|
# Clean the dist folder and build the assets
|
||||||
rm -rf assets/dist
|
rm -rf assets/dist
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Embed the assets using rice
|
# Embed the assets using rice
|
||||||
rice embed-go
|
rice embed-go
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
// Package filemanager provides middleware for managing files in a directory
|
// Package filemanager provides middleware for managing files in a directory
|
||||||
// when directory path is requested instead of a specific file. Based on browse
|
// when directory path is requested instead of a specific file. Based on browse
|
||||||
// middleware.
|
// middleware.
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/caddy/parser"
|
"github.com/hacdias/filemanager/caddy/parser"
|
||||||
h "github.com/hacdias/filemanager/http"
|
h "github.com/hacdias/filemanager/http"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterPlugin("filemanager", caddy.Plugin{
|
caddy.RegisterPlugin("filemanager", caddy.Plugin{
|
||||||
ServerType: "http",
|
ServerType: "http",
|
||||||
Action: setup,
|
Action: setup,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type plugin struct {
|
type plugin struct {
|
||||||
Next httpserver.Handler
|
Next httpserver.Handler
|
||||||
Configs []*filemanager.FileManager
|
Configs []*filemanager.FileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||||
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for i := range f.Configs {
|
for i := range f.Configs {
|
||||||
// Checks if this Path should be handled by File Manager.
|
// Checks if this Path should be handled by File Manager.
|
||||||
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Next.ServeHTTP(w, r)
|
return f.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup configures a new FileManager middleware instance.
|
// setup configures a new FileManager middleware instance.
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
configs, err := parser.Parse(c, "")
|
configs, err := parser.Parse(c, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||||
return plugin{Configs: configs, Next: next}
|
return plugin{Configs: configs, Next: next}
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
package hugo
|
package hugo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/caddy/parser"
|
"github.com/hacdias/filemanager/caddy/parser"
|
||||||
h "github.com/hacdias/filemanager/http"
|
h "github.com/hacdias/filemanager/http"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterPlugin("hugo", caddy.Plugin{
|
caddy.RegisterPlugin("hugo", caddy.Plugin{
|
||||||
ServerType: "http",
|
ServerType: "http",
|
||||||
Action: setup,
|
Action: setup,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type plugin struct {
|
type plugin struct {
|
||||||
Next httpserver.Handler
|
Next httpserver.Handler
|
||||||
Configs []*filemanager.FileManager
|
Configs []*filemanager.FileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||||
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for i := range f.Configs {
|
for i := range f.Configs {
|
||||||
// Checks if this Path should be handled by File Manager.
|
// Checks if this Path should be handled by File Manager.
|
||||||
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Next.ServeHTTP(w, r)
|
return f.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup configures a new FileManager middleware instance.
|
// setup configures a new FileManager middleware instance.
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
configs, err := parser.Parse(c, "hugo")
|
configs, err := parser.Parse(c, "hugo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||||
return plugin{Configs: configs, Next: next}
|
return plugin{Configs: configs, Next: next}
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,52 @@
|
||||||
package jekyll
|
package jekyll
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/caddy/parser"
|
"github.com/hacdias/filemanager/caddy/parser"
|
||||||
h "github.com/hacdias/filemanager/http"
|
h "github.com/hacdias/filemanager/http"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterPlugin("jekyll", caddy.Plugin{
|
caddy.RegisterPlugin("jekyll", caddy.Plugin{
|
||||||
ServerType: "http",
|
ServerType: "http",
|
||||||
Action: setup,
|
Action: setup,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type plugin struct {
|
type plugin struct {
|
||||||
Next httpserver.Handler
|
Next httpserver.Handler
|
||||||
Configs []*filemanager.FileManager
|
Configs []*filemanager.FileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||||
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
for i := range f.Configs {
|
for i := range f.Configs {
|
||||||
// Checks if this Path should be handled by File Manager.
|
// Checks if this Path should be handled by File Manager.
|
||||||
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
h.Handler(f.Configs[i]).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Next.ServeHTTP(w, r)
|
return f.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup configures a new FileManager middleware instance.
|
// setup configures a new FileManager middleware instance.
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
configs, err := parser.Parse(c, "jekyll")
|
configs, err := parser.Parse(c, "jekyll")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||||
return plugin{Configs: configs, Next: next}
|
return plugin{Configs: configs, Next: next}
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,294 +1,294 @@
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/bolt"
|
"github.com/hacdias/filemanager/bolt"
|
||||||
"github.com/hacdias/filemanager/staticgen"
|
"github.com/hacdias/filemanager/staticgen"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var databases = map[string]*storm.DB{}
|
var databases = map[string]*storm.DB{}
|
||||||
|
|
||||||
// Parse ...
|
// Parse ...
|
||||||
func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, error) {
|
func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, error) {
|
||||||
var (
|
var (
|
||||||
configs []*filemanager.FileManager
|
configs []*filemanager.FileManager
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
u := &filemanager.User{
|
u := &filemanager.User{
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
AllowCommands: true,
|
AllowCommands: true,
|
||||||
AllowEdit: true,
|
AllowEdit: true,
|
||||||
AllowNew: true,
|
AllowNew: true,
|
||||||
AllowPublish: true,
|
AllowPublish: true,
|
||||||
Commands: []string{"git", "svn", "hg"},
|
Commands: []string{"git", "svn", "hg"},
|
||||||
CSS: "",
|
CSS: "",
|
||||||
Rules: []*filemanager.Rule{{
|
Rules: []*filemanager.Rule{{
|
||||||
Regex: true,
|
Regex: true,
|
||||||
Allow: false,
|
Allow: false,
|
||||||
Regexp: &filemanager.Regexp{Raw: "\\/\\..+"},
|
Regexp: &filemanager.Regexp{Raw: "\\/\\..+"},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := "/"
|
baseURL := "/"
|
||||||
scope := "."
|
scope := "."
|
||||||
database := ""
|
database := ""
|
||||||
noAuth := false
|
noAuth := false
|
||||||
reCaptchaKey := ""
|
reCaptchaKey := ""
|
||||||
reCaptchaSecret := ""
|
reCaptchaSecret := ""
|
||||||
|
|
||||||
if plugin != "" {
|
if plugin != "" {
|
||||||
baseURL = "/admin"
|
baseURL = "/admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the baseURL and scope
|
// Get the baseURL and scope
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
if plugin == "" {
|
if plugin == "" {
|
||||||
if len(args) >= 1 {
|
if len(args) >= 1 {
|
||||||
baseURL = args[0]
|
baseURL = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
scope = args[1]
|
scope = args[1]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(args) >= 1 {
|
if len(args) >= 1 {
|
||||||
scope = args[0]
|
scope = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
baseURL = args[1]
|
baseURL = args[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
case "database":
|
case "database":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
database = c.Val()
|
database = c.Val()
|
||||||
case "locale":
|
case "locale":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Locale = c.Val()
|
u.Locale = c.Val()
|
||||||
case "allow_commands":
|
case "allow_commands":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
u.AllowCommands = true
|
u.AllowCommands = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u.AllowCommands, err = strconv.ParseBool(c.Val())
|
u.AllowCommands, err = strconv.ParseBool(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "allow_edit":
|
case "allow_edit":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
u.AllowEdit = true
|
u.AllowEdit = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u.AllowEdit, err = strconv.ParseBool(c.Val())
|
u.AllowEdit, err = strconv.ParseBool(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "allow_new":
|
case "allow_new":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
u.AllowNew = true
|
u.AllowNew = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u.AllowNew, err = strconv.ParseBool(c.Val())
|
u.AllowNew, err = strconv.ParseBool(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "allow_publish":
|
case "allow_publish":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
u.AllowPublish = true
|
u.AllowPublish = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
u.AllowPublish, err = strconv.ParseBool(c.Val())
|
u.AllowPublish, err = strconv.ParseBool(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "commands":
|
case "commands":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Commands = strings.Split(c.Val(), " ")
|
u.Commands = strings.Split(c.Val(), " ")
|
||||||
case "css":
|
case "css":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
file := c.Val()
|
file := c.Val()
|
||||||
css, err := ioutil.ReadFile(file)
|
css, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.CSS = string(css)
|
u.CSS = string(css)
|
||||||
case "view_mode":
|
case "view_mode":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
u.ViewMode = c.Val()
|
u.ViewMode = c.Val()
|
||||||
if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
|
if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
case "recaptcha_key":
|
case "recaptcha_key":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
reCaptchaKey = c.Val()
|
reCaptchaKey = c.Val()
|
||||||
case "recaptcha_secret":
|
case "recaptcha_secret":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return nil, c.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
reCaptchaSecret = c.Val()
|
reCaptchaSecret = c.Val()
|
||||||
case "no_auth":
|
case "no_auth":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
noAuth = true
|
noAuth = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
noAuth, err = strconv.ParseBool(c.Val())
|
noAuth, err = strconv.ParseBool(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
caddyConf := httpserver.GetConfig(c)
|
caddyConf := httpserver.GetConfig(c)
|
||||||
|
|
||||||
path := filepath.Join(caddy.AssetsPath(), "filemanager")
|
path := filepath.Join(caddy.AssetsPath(), "filemanager")
|
||||||
err := os.MkdirAll(path, 0700)
|
err := os.MkdirAll(path, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is a database path and it is not absolute,
|
// if there is a database path and it is not absolute,
|
||||||
// it will be relative to Caddy folder.
|
// it will be relative to Caddy folder.
|
||||||
if !filepath.IsAbs(database) && database != "" {
|
if !filepath.IsAbs(database) && database != "" {
|
||||||
database = filepath.Join(path, database)
|
database = filepath.Join(path, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no database path on the settings,
|
// If there is no database path on the settings,
|
||||||
// store one in .caddy/filemanager/name.db.
|
// store one in .caddy/filemanager/name.db.
|
||||||
if database == "" {
|
if database == "" {
|
||||||
// The name of the database is the hashed value of a string composed
|
// The name of the database is the hashed value of a string composed
|
||||||
// by the host, address path and the baseurl of this File Manager
|
// by the host, address path and the baseurl of this File Manager
|
||||||
// instance.
|
// instance.
|
||||||
hasher := md5.New()
|
hasher := md5.New()
|
||||||
hasher.Write([]byte(caddyConf.Addr.Host + caddyConf.Addr.Path + baseURL))
|
hasher.Write([]byte(caddyConf.Addr.Host + caddyConf.Addr.Path + baseURL))
|
||||||
sha := hex.EncodeToString(hasher.Sum(nil))
|
sha := hex.EncodeToString(hasher.Sum(nil))
|
||||||
database = filepath.Join(path, sha+".db")
|
database = filepath.Join(path, sha+".db")
|
||||||
|
|
||||||
fmt.Println("[WARNING] A database is going to be created for your File Manager instance at " + database +
|
fmt.Println("[WARNING] A database is going to be created for your File Manager instance at " + database +
|
||||||
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
|
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Scope = scope
|
u.Scope = scope
|
||||||
u.FileSystem = fileutils.Dir(scope)
|
u.FileSystem = fileutils.Dir(scope)
|
||||||
|
|
||||||
var db *storm.DB
|
var db *storm.DB
|
||||||
if stored, ok := databases[database]; ok {
|
if stored, ok := databases[database]; ok {
|
||||||
db = stored
|
db = stored
|
||||||
} else {
|
} else {
|
||||||
db, err = storm.Open(database)
|
db, err = storm.Open(database)
|
||||||
databases[database] = db
|
databases[database] = db
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := &filemanager.FileManager{
|
m := &filemanager.FileManager{
|
||||||
NoAuth: noAuth,
|
NoAuth: noAuth,
|
||||||
BaseURL: "",
|
BaseURL: "",
|
||||||
PrefixURL: "",
|
PrefixURL: "",
|
||||||
ReCaptchaKey: reCaptchaKey,
|
ReCaptchaKey: reCaptchaKey,
|
||||||
ReCaptchaSecret: reCaptchaSecret,
|
ReCaptchaSecret: reCaptchaSecret,
|
||||||
DefaultUser: u,
|
DefaultUser: u,
|
||||||
Store: &filemanager.Store{
|
Store: &filemanager.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
Users: bolt.UsersStore{DB: db},
|
Users: bolt.UsersStore{DB: db},
|
||||||
Share: bolt.ShareStore{DB: db},
|
Share: bolt.ShareStore{DB: db},
|
||||||
},
|
},
|
||||||
NewFS: func(scope string) filemanager.FileSystem {
|
NewFS: func(scope string) filemanager.FileSystem {
|
||||||
return fileutils.Dir(scope)
|
return fileutils.Dir(scope)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.Setup()
|
err = m.Setup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch plugin {
|
switch plugin {
|
||||||
case "hugo":
|
case "hugo":
|
||||||
// Initialize the default settings for Hugo.
|
// Initialize the default settings for Hugo.
|
||||||
hugo := &staticgen.Hugo{
|
hugo := &staticgen.Hugo{
|
||||||
Root: scope,
|
Root: scope,
|
||||||
Public: filepath.Join(scope, "public"),
|
Public: filepath.Join(scope, "public"),
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches Hugo plugin to this file manager instance.
|
// Attaches Hugo plugin to this file manager instance.
|
||||||
err = m.Attach(hugo)
|
err = m.Attach(hugo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "jekyll":
|
case "jekyll":
|
||||||
// Initialize the default settings for Jekyll.
|
// Initialize the default settings for Jekyll.
|
||||||
jekyll := &staticgen.Jekyll{
|
jekyll := &staticgen.Jekyll{
|
||||||
Root: scope,
|
Root: scope,
|
||||||
Public: filepath.Join(scope, "_site"),
|
Public: filepath.Join(scope, "_site"),
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches Hugo plugin to this file manager instance.
|
// Attaches Hugo plugin to this file manager instance.
|
||||||
err = m.Attach(jekyll)
|
err = m.Attach(jekyll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.NoAuth = noAuth
|
m.NoAuth = noAuth
|
||||||
m.SetBaseURL(baseURL)
|
m.SetBaseURL(baseURL)
|
||||||
m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/"))
|
m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/"))
|
||||||
|
|
||||||
configs = append(configs, m)
|
configs = append(configs, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return configs, nil
|
return configs, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,249 +1,249 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/asdine/storm"
|
"github.com/asdine/storm"
|
||||||
|
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
"github.com/hacdias/filemanager"
|
"github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/filemanager/bolt"
|
"github.com/hacdias/filemanager/bolt"
|
||||||
h "github.com/hacdias/filemanager/http"
|
h "github.com/hacdias/filemanager/http"
|
||||||
"github.com/hacdias/filemanager/staticgen"
|
"github.com/hacdias/filemanager/staticgen"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
flag "github.com/spf13/pflag"
|
flag "github.com/spf13/pflag"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addr string
|
addr string
|
||||||
config string
|
config string
|
||||||
database string
|
database string
|
||||||
scope string
|
scope string
|
||||||
commands string
|
commands string
|
||||||
logfile string
|
logfile string
|
||||||
staticg string
|
staticg string
|
||||||
locale string
|
locale string
|
||||||
baseurl string
|
baseurl string
|
||||||
prefixurl string
|
prefixurl string
|
||||||
viewMode string
|
viewMode string
|
||||||
recaptchakey string
|
recaptchakey string
|
||||||
recaptchasecret string
|
recaptchasecret string
|
||||||
port int
|
port int
|
||||||
noAuth bool
|
noAuth bool
|
||||||
allowCommands bool
|
allowCommands bool
|
||||||
allowEdit bool
|
allowEdit bool
|
||||||
allowNew bool
|
allowNew bool
|
||||||
allowPublish bool
|
allowPublish bool
|
||||||
showVer bool
|
showVer bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVarP(&config, "config", "c", "", "Configuration file")
|
flag.StringVarP(&config, "config", "c", "", "Configuration file")
|
||||||
flag.IntVarP(&port, "port", "p", 0, "HTTP Port (default is random)")
|
flag.IntVarP(&port, "port", "p", 0, "HTTP Port (default is random)")
|
||||||
flag.StringVarP(&addr, "address", "a", "", "Address to listen to (default is all of them)")
|
flag.StringVarP(&addr, "address", "a", "", "Address to listen to (default is all of them)")
|
||||||
flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
|
flag.StringVarP(&database, "database", "d", "./filemanager.db", "Database file")
|
||||||
flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
|
flag.StringVarP(&logfile, "log", "l", "stdout", "Errors logger; can use 'stdout', 'stderr' or file")
|
||||||
flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
|
flag.StringVarP(&scope, "scope", "s", ".", "Default scope option for new users")
|
||||||
flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
|
flag.StringVarP(&baseurl, "baseurl", "b", "", "Base URL")
|
||||||
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
||||||
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
||||||
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
|
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
|
||||||
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
|
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
|
||||||
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
|
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
|
||||||
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
|
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
|
||||||
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
|
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
|
||||||
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
||||||
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
||||||
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
||||||
flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
|
flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
|
||||||
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
|
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
|
||||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupViper() {
|
func setupViper() {
|
||||||
viper.SetDefault("Address", "")
|
viper.SetDefault("Address", "")
|
||||||
viper.SetDefault("Port", "0")
|
viper.SetDefault("Port", "0")
|
||||||
viper.SetDefault("Database", "./filemanager.db")
|
viper.SetDefault("Database", "./filemanager.db")
|
||||||
viper.SetDefault("Scope", ".")
|
viper.SetDefault("Scope", ".")
|
||||||
viper.SetDefault("Logger", "stdout")
|
viper.SetDefault("Logger", "stdout")
|
||||||
viper.SetDefault("Commands", []string{"git", "svn", "hg"})
|
viper.SetDefault("Commands", []string{"git", "svn", "hg"})
|
||||||
viper.SetDefault("AllowCommmands", true)
|
viper.SetDefault("AllowCommmands", true)
|
||||||
viper.SetDefault("AllowEdit", true)
|
viper.SetDefault("AllowEdit", true)
|
||||||
viper.SetDefault("AllowNew", true)
|
viper.SetDefault("AllowNew", true)
|
||||||
viper.SetDefault("AllowPublish", true)
|
viper.SetDefault("AllowPublish", true)
|
||||||
viper.SetDefault("StaticGen", "")
|
viper.SetDefault("StaticGen", "")
|
||||||
viper.SetDefault("Locale", "")
|
viper.SetDefault("Locale", "")
|
||||||
viper.SetDefault("NoAuth", false)
|
viper.SetDefault("NoAuth", false)
|
||||||
viper.SetDefault("BaseURL", "")
|
viper.SetDefault("BaseURL", "")
|
||||||
viper.SetDefault("PrefixURL", "")
|
viper.SetDefault("PrefixURL", "")
|
||||||
viper.SetDefault("ViewMode", filemanager.MosaicViewMode)
|
viper.SetDefault("ViewMode", filemanager.MosaicViewMode)
|
||||||
viper.SetDefault("ReCaptchaKey", "")
|
viper.SetDefault("ReCaptchaKey", "")
|
||||||
viper.SetDefault("ReCaptchaSecret", "")
|
viper.SetDefault("ReCaptchaSecret", "")
|
||||||
|
|
||||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||||
viper.BindPFlag("Database", flag.Lookup("database"))
|
viper.BindPFlag("Database", flag.Lookup("database"))
|
||||||
viper.BindPFlag("Scope", flag.Lookup("scope"))
|
viper.BindPFlag("Scope", flag.Lookup("scope"))
|
||||||
viper.BindPFlag("Logger", flag.Lookup("log"))
|
viper.BindPFlag("Logger", flag.Lookup("log"))
|
||||||
viper.BindPFlag("Commands", flag.Lookup("commands"))
|
viper.BindPFlag("Commands", flag.Lookup("commands"))
|
||||||
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
|
viper.BindPFlag("AllowCommands", flag.Lookup("allow-commands"))
|
||||||
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
viper.BindPFlag("AllowEdit", flag.Lookup("allow-edit"))
|
||||||
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
viper.BindPFlag("AlowNew", flag.Lookup("allow-new"))
|
||||||
viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish"))
|
viper.BindPFlag("AllowPublish", flag.Lookup("allow-publish"))
|
||||||
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
viper.BindPFlag("Locale", flag.Lookup("locale"))
|
||||||
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
viper.BindPFlag("StaticGen", flag.Lookup("staticgen"))
|
||||||
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
viper.BindPFlag("NoAuth", flag.Lookup("no-auth"))
|
||||||
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
||||||
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
||||||
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
|
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
|
||||||
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
|
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
|
||||||
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
|
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
|
||||||
|
|
||||||
viper.SetConfigName("filemanager")
|
viper.SetConfigName("filemanager")
|
||||||
viper.AddConfigPath(".")
|
viper.AddConfigPath(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersion() {
|
func printVersion() {
|
||||||
fmt.Println("filemanager version", filemanager.Version)
|
fmt.Println("filemanager version", filemanager.Version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
setupViper()
|
setupViper()
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if showVer {
|
if showVer {
|
||||||
printVersion()
|
printVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a configuration file if set.
|
// Add a configuration file if set.
|
||||||
if config != "" {
|
if config != "" {
|
||||||
ext := filepath.Ext(config)
|
ext := filepath.Ext(config)
|
||||||
dir := filepath.Dir(config)
|
dir := filepath.Dir(config)
|
||||||
config = strings.TrimSuffix(config, ext)
|
config = strings.TrimSuffix(config, ext)
|
||||||
|
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
viper.AddConfigPath(dir)
|
viper.AddConfigPath(dir)
|
||||||
config = strings.TrimPrefix(config, dir)
|
config = strings.TrimPrefix(config, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetConfigName(config)
|
viper.SetConfigName(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read configuration from a file if exists.
|
// Read configuration from a file if exists.
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(viper.ConfigParseError); ok {
|
if _, ok := err.(viper.ConfigParseError); ok {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up process log before anything bad happens.
|
// Set up process log before anything bad happens.
|
||||||
switch viper.GetString("Logger") {
|
switch viper.GetString("Logger") {
|
||||||
case "stdout":
|
case "stdout":
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
case "stderr":
|
case "stderr":
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
case "":
|
case "":
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
default:
|
default:
|
||||||
log.SetOutput(&lumberjack.Logger{
|
log.SetOutput(&lumberjack.Logger{
|
||||||
Filename: logfile,
|
Filename: logfile,
|
||||||
MaxSize: 100,
|
MaxSize: 100,
|
||||||
MaxAge: 14,
|
MaxAge: 14,
|
||||||
MaxBackups: 10,
|
MaxBackups: 10,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds the address and a listener.
|
// Builds the address and a listener.
|
||||||
laddr := viper.GetString("Address") + ":" + viper.GetString("Port")
|
laddr := viper.GetString("Address") + ":" + viper.GetString("Port")
|
||||||
listener, err := net.Listen("tcp", laddr)
|
listener, err := net.Listen("tcp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the user the port in which is listening.
|
// Tell the user the port in which is listening.
|
||||||
fmt.Println("Listening on", listener.Addr().String())
|
fmt.Println("Listening on", listener.Addr().String())
|
||||||
|
|
||||||
// Starts the server.
|
// Starts the server.
|
||||||
if err := http.Serve(listener, handler()); err != nil {
|
if err := http.Serve(listener, handler()); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handler() http.Handler {
|
func handler() http.Handler {
|
||||||
db, err := storm.Open(viper.GetString("Database"))
|
db, err := storm.Open(viper.GetString("Database"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := &filemanager.FileManager{
|
fm := &filemanager.FileManager{
|
||||||
NoAuth: viper.GetBool("NoAuth"),
|
NoAuth: viper.GetBool("NoAuth"),
|
||||||
BaseURL: viper.GetString("BaseURL"),
|
BaseURL: viper.GetString("BaseURL"),
|
||||||
PrefixURL: viper.GetString("PrefixURL"),
|
PrefixURL: viper.GetString("PrefixURL"),
|
||||||
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
|
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
|
||||||
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
|
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
|
||||||
DefaultUser: &filemanager.User{
|
DefaultUser: &filemanager.User{
|
||||||
AllowCommands: viper.GetBool("AllowCommands"),
|
AllowCommands: viper.GetBool("AllowCommands"),
|
||||||
AllowEdit: viper.GetBool("AllowEdit"),
|
AllowEdit: viper.GetBool("AllowEdit"),
|
||||||
AllowNew: viper.GetBool("AllowNew"),
|
AllowNew: viper.GetBool("AllowNew"),
|
||||||
AllowPublish: viper.GetBool("AllowPublish"),
|
AllowPublish: viper.GetBool("AllowPublish"),
|
||||||
Commands: viper.GetStringSlice("Commands"),
|
Commands: viper.GetStringSlice("Commands"),
|
||||||
Rules: []*filemanager.Rule{},
|
Rules: []*filemanager.Rule{},
|
||||||
Locale: viper.GetString("Locale"),
|
Locale: viper.GetString("Locale"),
|
||||||
CSS: "",
|
CSS: "",
|
||||||
Scope: viper.GetString("Scope"),
|
Scope: viper.GetString("Scope"),
|
||||||
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
FileSystem: fileutils.Dir(viper.GetString("Scope")),
|
||||||
ViewMode: viper.GetString("ViewMode"),
|
ViewMode: viper.GetString("ViewMode"),
|
||||||
},
|
},
|
||||||
Store: &filemanager.Store{
|
Store: &filemanager.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
Users: bolt.UsersStore{DB: db},
|
Users: bolt.UsersStore{DB: db},
|
||||||
Share: bolt.ShareStore{DB: db},
|
Share: bolt.ShareStore{DB: db},
|
||||||
},
|
},
|
||||||
NewFS: func(scope string) filemanager.FileSystem {
|
NewFS: func(scope string) filemanager.FileSystem {
|
||||||
return fileutils.Dir(scope)
|
return fileutils.Dir(scope)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fm.Setup()
|
err = fm.Setup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch viper.GetString("StaticGen") {
|
switch viper.GetString("StaticGen") {
|
||||||
case "hugo":
|
case "hugo":
|
||||||
hugo := &staticgen.Hugo{
|
hugo := &staticgen.Hugo{
|
||||||
Root: viper.GetString("Scope"),
|
Root: viper.GetString("Scope"),
|
||||||
Public: filepath.Join(viper.GetString("Scope"), "public"),
|
Public: filepath.Join(viper.GetString("Scope"), "public"),
|
||||||
Args: []string{},
|
Args: []string{},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = fm.Attach(hugo); err != nil {
|
if err = fm.Attach(hugo); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
case "jekyll":
|
case "jekyll":
|
||||||
jekyll := &staticgen.Jekyll{
|
jekyll := &staticgen.Jekyll{
|
||||||
Root: viper.GetString("Scope"),
|
Root: viper.GetString("Scope"),
|
||||||
Public: filepath.Join(viper.GetString("Scope"), "_site"),
|
Public: filepath.Join(viper.GetString("Scope"), "_site"),
|
||||||
Args: []string{"build"},
|
Args: []string{"build"},
|
||||||
CleanPublic: true,
|
CleanPublic: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = fm.Attach(jekyll); err != nil {
|
if err = fm.Attach(jekyll); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.Handler(fm)
|
return h.Handler(fm)
|
||||||
}
|
}
|
||||||
|
|
146
doc.go
146
doc.go
|
@ -1,73 +1,73 @@
|
||||||
/*
|
/*
|
||||||
Package filemanager provides a web interface to access your files
|
Package filemanager provides a web interface to access your files
|
||||||
wherever you are. To use this package as a middleware for your app,
|
wherever you are. To use this package as a middleware for your app,
|
||||||
you'll need to import both File Manager and File Manager HTTP packages.
|
you'll need to import both File Manager and File Manager HTTP packages.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
h "github.com/hacdias/filemanager/http"
|
h "github.com/hacdias/filemanager/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
Then, you should create a new FileManager object with your options. In this
|
Then, you should create a new FileManager object with your options. In this
|
||||||
case, I'm using BoltDB (via Storm package) as a Store. So, you'll also need
|
case, I'm using BoltDB (via Storm package) as a Store. So, you'll also need
|
||||||
to import "github.com/hacdias/filemanager/bolt".
|
to import "github.com/hacdias/filemanager/bolt".
|
||||||
|
|
||||||
db, _ := storm.Open("bolt.db")
|
db, _ := storm.Open("bolt.db")
|
||||||
|
|
||||||
m := &fm.FileManager{
|
m := &fm.FileManager{
|
||||||
NoAuth: false,
|
NoAuth: false,
|
||||||
DefaultUser: &fm.User{
|
DefaultUser: &fm.User{
|
||||||
AllowCommands: true,
|
AllowCommands: true,
|
||||||
AllowEdit: true,
|
AllowEdit: true,
|
||||||
AllowNew: true,
|
AllowNew: true,
|
||||||
AllowPublish: true,
|
AllowPublish: true,
|
||||||
Commands: []string{"git"},
|
Commands: []string{"git"},
|
||||||
Rules: []*fm.Rule{},
|
Rules: []*fm.Rule{},
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
CSS: "",
|
CSS: "",
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
FileSystem: fileutils.Dir("."),
|
FileSystem: fileutils.Dir("."),
|
||||||
},
|
},
|
||||||
Store: &fm.Store{
|
Store: &fm.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
Users: bolt.UsersStore{DB: db},
|
Users: bolt.UsersStore{DB: db},
|
||||||
Share: bolt.ShareStore{DB: db},
|
Share: bolt.ShareStore{DB: db},
|
||||||
},
|
},
|
||||||
NewFS: func(scope string) fm.FileSystem {
|
NewFS: func(scope string) fm.FileSystem {
|
||||||
return fileutils.Dir(scope)
|
return fileutils.Dir(scope)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
The credentials for the first user are always 'admin' for both the user and
|
The credentials for the first user are always 'admin' for both the user and
|
||||||
the password, and they can be changed later through the settings. The first
|
the password, and they can be changed later through the settings. The first
|
||||||
user is always an Admin and has all of the permissions set to 'true'.
|
user is always an Admin and has all of the permissions set to 'true'.
|
||||||
|
|
||||||
Then, you should set the Prefix URL and the Base URL, using the following
|
Then, you should set the Prefix URL and the Base URL, using the following
|
||||||
functions:
|
functions:
|
||||||
|
|
||||||
m.SetBaseURL("/")
|
m.SetBaseURL("/")
|
||||||
m.SetPrefixURL("/")
|
m.SetPrefixURL("/")
|
||||||
|
|
||||||
The Prefix URL is a part of the path that is already stripped from the
|
The Prefix URL is a part of the path that is already stripped from the
|
||||||
r.URL.Path variable before the request arrives to File Manager's handler.
|
r.URL.Path variable before the request arrives to File Manager's handler.
|
||||||
This is a function that will rarely be used. You can see one example on Caddy
|
This is a function that will rarely be used. You can see one example on Caddy
|
||||||
filemanager plugin.
|
filemanager plugin.
|
||||||
|
|
||||||
The Base URL is the URL path where you want File Manager to be available in. If
|
The Base URL is the URL path where you want File Manager to be available in. If
|
||||||
you want to be available at the root path, you should call:
|
you want to be available at the root path, you should call:
|
||||||
|
|
||||||
m.SetBaseURL("/")
|
m.SetBaseURL("/")
|
||||||
|
|
||||||
But if you want to access it at '/admin', you would call:
|
But if you want to access it at '/admin', you would call:
|
||||||
|
|
||||||
m.SetBaseURL("/admin")
|
m.SetBaseURL("/admin")
|
||||||
|
|
||||||
Now, that you already have a File Manager instance created, you just need to
|
Now, that you already have a File Manager instance created, you just need to
|
||||||
add it to your handlers using m.ServeHTTP which is compatible to http.Handler.
|
add it to your handlers using m.ServeHTTP which is compatible to http.Handler.
|
||||||
We also have a m.ServeWithErrorsHTTP that returns the status code and an error.
|
We also have a m.ServeWithErrorsHTTP that returns the status code and an error.
|
||||||
|
|
||||||
One simple implementation for this, at port 80, in the root of the domain, would be:
|
One simple implementation for this, at port 80, in the root of the domain, would be:
|
||||||
|
|
||||||
http.ListenAndServe(":80", h.Handler(m))
|
http.ListenAndServe(":80", h.Handler(m))
|
||||||
*/
|
*/
|
||||||
package filemanager
|
package filemanager
|
||||||
|
|
688
http/http.go
688
http/http.go
|
@ -1,344 +1,344 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler returns a function compatible with http.HandleFunc.
|
// Handler returns a function compatible with http.HandleFunc.
|
||||||
func Handler(m *fm.FileManager) http.Handler {
|
func Handler(m *fm.FileManager) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
code, err := serve(&fm.Context{
|
code, err := serve(&fm.Context{
|
||||||
FileManager: m,
|
FileManager: m,
|
||||||
User: nil,
|
User: nil,
|
||||||
File: nil,
|
File: nil,
|
||||||
}, w, r)
|
}, w, r)
|
||||||
|
|
||||||
if code >= 400 {
|
if code >= 400 {
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
|
|
||||||
txt := http.StatusText(code)
|
txt := http.StatusText(code)
|
||||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||||
w.Write([]byte(txt + "\n"))
|
w.Write([]byte(txt + "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve is the main entry point of this HTML application.
|
// serve is the main entry point of this HTML application.
|
||||||
func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func serve(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
|
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
|
||||||
// returns a 404 fm.Error because we're not supposed to be here!
|
// returns a 404 fm.Error because we're not supposed to be here!
|
||||||
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
p := strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
||||||
|
|
||||||
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
|
if len(p) >= len(r.URL.Path) && c.BaseURL != "" {
|
||||||
return http.StatusNotFound, nil
|
return http.StatusNotFound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
r.URL.Path = p
|
r.URL.Path = p
|
||||||
|
|
||||||
// Check if this request is made to the service worker. If so,
|
// Check if this request is made to the service worker. If so,
|
||||||
// pass it through a template to add the needed variables.
|
// pass it through a template to add the needed variables.
|
||||||
if r.URL.Path == "/sw.js" {
|
if r.URL.Path == "/sw.js" {
|
||||||
return renderFile(c, w, "sw.js")
|
return renderFile(c, w, "sw.js")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if this request is made to the static assets folder. If so, and
|
// Checks if this request is made to the static assets folder. If so, and
|
||||||
// if it is a GET request, returns with the asset. Otherwise, returns
|
// if it is a GET request, returns with the asset. Otherwise, returns
|
||||||
// a status not implemented.
|
// a status not implemented.
|
||||||
if matchURL(r.URL.Path, "/static") {
|
if matchURL(r.URL.Path, "/static") {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
return http.StatusNotImplemented, nil
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return staticHandler(c, w, r)
|
return staticHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if this request is made to the API and directs to the
|
// Checks if this request is made to the API and directs to the
|
||||||
// API handler if so.
|
// API handler if so.
|
||||||
if matchURL(r.URL.Path, "/api") {
|
if matchURL(r.URL.Path, "/api") {
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
|
||||||
return apiHandler(c, w, r)
|
return apiHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a request to the preview and a static website generator is
|
// If it is a request to the preview and a static website generator is
|
||||||
// active, build the preview.
|
// active, build the preview.
|
||||||
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
|
if strings.HasPrefix(r.URL.Path, "/preview") && c.StaticGen != nil {
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/preview")
|
||||||
return c.StaticGen.Preview(c, w, r)
|
return c.StaticGen.Preview(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(r.URL.Path, "/share/") {
|
if strings.HasPrefix(r.URL.Path, "/share/") {
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/share/")
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/share/")
|
||||||
return sharePage(c, w, r)
|
return sharePage(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any other request should show the index.html file.
|
// Any other request should show the index.html file.
|
||||||
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
w.Header().Set("x-frame-options", "SAMEORIGIN")
|
||||||
w.Header().Set("x-content-type", "nosniff")
|
w.Header().Set("x-content-type", "nosniff")
|
||||||
w.Header().Set("x-xss-protection", "1; mode=block")
|
w.Header().Set("x-xss-protection", "1; mode=block")
|
||||||
|
|
||||||
return renderFile(c, w, "index.html")
|
return renderFile(c, w, "index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
// staticHandler handles the static assets path.
|
// staticHandler handles the static assets path.
|
||||||
func staticHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func staticHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path != "/static/manifest.json" {
|
if r.URL.Path != "/static/manifest.json" {
|
||||||
http.FileServer(c.Assets.HTTPBox()).ServeHTTP(w, r)
|
http.FileServer(c.Assets.HTTPBox()).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderFile(c, w, "static/manifest.json")
|
return renderFile(c, w, "static/manifest.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiHandler is the main entry point for the /api endpoint.
|
// apiHandler is the main entry point for the /api endpoint.
|
||||||
func apiHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func apiHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if r.URL.Path == "/auth/get" {
|
if r.URL.Path == "/auth/get" {
|
||||||
return authHandler(c, w, r)
|
return authHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path == "/auth/renew" {
|
if r.URL.Path == "/auth/renew" {
|
||||||
return renewAuthHandler(c, w, r)
|
return renewAuthHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
valid, _ := validateAuth(c, r)
|
valid, _ := validateAuth(c, r)
|
||||||
if !valid {
|
if !valid {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Router, r.URL.Path = splitURL(r.URL.Path)
|
c.Router, r.URL.Path = splitURL(r.URL.Path)
|
||||||
|
|
||||||
if !c.User.Allowed(r.URL.Path) {
|
if !c.User.Allowed(r.URL.Path) {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
if c.StaticGen != nil {
|
||||||
// If we are using the 'magic url' for the settings,
|
// If we are using the 'magic url' for the settings,
|
||||||
// we should redirect the request for the acutual path.
|
// we should redirect the request for the acutual path.
|
||||||
if r.URL.Path == "/settings" {
|
if r.URL.Path == "/settings" {
|
||||||
r.URL.Path = c.StaticGen.SettingsPath()
|
r.URL.Path = c.StaticGen.SettingsPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executes the Static website generator hook.
|
// Executes the Static website generator hook.
|
||||||
code, err := c.StaticGen.Hook(c, w, r)
|
code, err := c.StaticGen.Hook(c, w, r)
|
||||||
if code != 0 || err != nil {
|
if code != 0 || err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Router == "checksum" || c.Router == "download" {
|
if c.Router == "checksum" || c.Router == "download" {
|
||||||
var err error
|
var err error
|
||||||
c.File, err = fm.GetInfo(r.URL, c.FileManager, c.User)
|
c.File, err = fm.GetInfo(r.URL, c.FileManager, c.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var code int
|
var code int
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch c.Router {
|
switch c.Router {
|
||||||
case "download":
|
case "download":
|
||||||
code, err = downloadHandler(c, w, r)
|
code, err = downloadHandler(c, w, r)
|
||||||
case "checksum":
|
case "checksum":
|
||||||
code, err = checksumHandler(c, w, r)
|
code, err = checksumHandler(c, w, r)
|
||||||
case "command":
|
case "command":
|
||||||
code, err = command(c, w, r)
|
code, err = command(c, w, r)
|
||||||
case "search":
|
case "search":
|
||||||
code, err = search(c, w, r)
|
code, err = search(c, w, r)
|
||||||
case "resource":
|
case "resource":
|
||||||
code, err = resourceHandler(c, w, r)
|
code, err = resourceHandler(c, w, r)
|
||||||
case "users":
|
case "users":
|
||||||
code, err = usersHandler(c, w, r)
|
code, err = usersHandler(c, w, r)
|
||||||
case "settings":
|
case "settings":
|
||||||
code, err = settingsHandler(c, w, r)
|
code, err = settingsHandler(c, w, r)
|
||||||
case "share":
|
case "share":
|
||||||
code, err = shareHandler(c, w, r)
|
code, err = shareHandler(c, w, r)
|
||||||
default:
|
default:
|
||||||
code = http.StatusNotFound
|
code = http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
||||||
func checksumHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func checksumHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
query := r.URL.Query().Get("algo")
|
query := r.URL.Query().Get("algo")
|
||||||
|
|
||||||
val, err := c.File.Checksum(query)
|
val, err := c.File.Checksum(query)
|
||||||
if err == fm.ErrInvalidOption {
|
if err == fm.ErrInvalidOption {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte(val))
|
w.Write([]byte(val))
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitURL splits the path and returns everything that stands
|
// splitURL splits the path and returns everything that stands
|
||||||
// before the first slash and everything that goes after.
|
// before the first slash and everything that goes after.
|
||||||
func splitURL(path string) (string, string) {
|
func splitURL(path string) (string, string) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
|
||||||
i := strings.Index(path, "/")
|
i := strings.Index(path, "/")
|
||||||
if i == -1 {
|
if i == -1 {
|
||||||
return "", path
|
return "", path
|
||||||
}
|
}
|
||||||
|
|
||||||
return path[0:i], path[i:]
|
return path[0:i], path[i:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderFile renders a file using a template with some needed variables.
|
// renderFile renders a file using a template with some needed variables.
|
||||||
func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error) {
|
func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error) {
|
||||||
tpl := template.Must(template.New("file").Parse(c.Assets.MustString(file)))
|
tpl := template.Must(template.New("file").Parse(c.Assets.MustString(file)))
|
||||||
|
|
||||||
var contentType string
|
var contentType string
|
||||||
switch filepath.Ext(file) {
|
switch filepath.Ext(file) {
|
||||||
case ".html":
|
case ".html":
|
||||||
contentType = "text/html"
|
contentType = "text/html"
|
||||||
case ".js":
|
case ".js":
|
||||||
contentType = "application/javascript"
|
contentType = "application/javascript"
|
||||||
case ".json":
|
case ".json":
|
||||||
contentType = "application/json"
|
contentType = "application/json"
|
||||||
default:
|
default:
|
||||||
contentType = "text"
|
contentType = "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"BaseURL": c.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"NoAuth": c.NoAuth,
|
"NoAuth": c.NoAuth,
|
||||||
"Version": fm.Version,
|
"Version": fm.Version,
|
||||||
"CSS": template.CSS(c.CSS),
|
"CSS": template.CSS(c.CSS),
|
||||||
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
|
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
|
||||||
"ReCaptchaKey": c.ReCaptchaKey,
|
"ReCaptchaKey": c.ReCaptchaKey,
|
||||||
"ReCaptchaSecret": c.ReCaptchaSecret,
|
"ReCaptchaSecret": c.ReCaptchaSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
if c.StaticGen != nil {
|
||||||
data["StaticGen"] = c.StaticGen.Name()
|
data["StaticGen"] = c.StaticGen.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tpl.Execute(w, data)
|
err := tpl.Execute(w, data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// sharePage build the share page.
|
// sharePage build the share page.
|
||||||
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
s, err := c.Store.Share.Get(r.URL.Path)
|
s, err := c.Store.Share.Get(r.URL.Path)
|
||||||
if err == fm.ErrNotExist {
|
if err == fm.ErrNotExist {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return renderFile(c, w, "static/share/404.html")
|
return renderFile(c, w, "static/share/404.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
if s.Expires && s.ExpireDate.Before(time.Now()) {
|
||||||
c.Store.Share.Delete(s.Hash)
|
c.Store.Share.Delete(s.Hash)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return renderFile(c, w, "static/share/404.html")
|
return renderFile(c, w, "static/share/404.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
r.URL.Path = s.Path
|
r.URL.Path = s.Path
|
||||||
|
|
||||||
info, err := os.Stat(s.Path)
|
info, err := os.Stat(s.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Store.Share.Delete(s.Hash)
|
c.Store.Share.Delete(s.Hash)
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.File = &fm.File{
|
c.File = &fm.File{
|
||||||
Path: s.Path,
|
Path: s.Path,
|
||||||
Name: info.Name(),
|
Name: info.Name(),
|
||||||
ModTime: info.ModTime(),
|
ModTime: info.ModTime(),
|
||||||
Mode: info.Mode(),
|
Mode: info.Mode(),
|
||||||
IsDir: info.IsDir(),
|
IsDir: info.IsDir(),
|
||||||
Size: info.Size(),
|
Size: info.Size(),
|
||||||
}
|
}
|
||||||
|
|
||||||
dl := r.URL.Query().Get("dl")
|
dl := r.URL.Query().Get("dl")
|
||||||
|
|
||||||
if dl == "" || dl == "0" {
|
if dl == "" || dl == "0" {
|
||||||
tpl := template.Must(template.New("file").Parse(c.Assets.MustString("static/share/index.html")))
|
tpl := template.Must(template.New("file").Parse(c.Assets.MustString("static/share/index.html")))
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
err := tpl.Execute(w, map[string]interface{}{
|
err := tpl.Execute(w, map[string]interface{}{
|
||||||
"BaseURL": c.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"File": c.File,
|
"File": c.File,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadHandler(c, w, r)
|
return downloadHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderJSON prints the JSON version of data to the browser.
|
// renderJSON prints the JSON version of data to the browser.
|
||||||
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
|
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
|
||||||
marsh, err := json.Marshal(data)
|
marsh, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
if _, err := w.Write(marsh); err != nil {
|
if _, err := w.Write(marsh); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// matchURL checks if the first URL matches the second.
|
// matchURL checks if the first URL matches the second.
|
||||||
func matchURL(first, second string) bool {
|
func matchURL(first, second string) bool {
|
||||||
first = strings.ToLower(first)
|
first = strings.ToLower(first)
|
||||||
second = strings.ToLower(second)
|
second = strings.ToLower(second)
|
||||||
|
|
||||||
return strings.HasPrefix(first, second)
|
return strings.HasPrefix(first, second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorToHTTP converts errors to HTTP Status Code.
|
// ErrorToHTTP converts errors to HTTP Status Code.
|
||||||
func ErrorToHTTP(err error, gone bool) int {
|
func ErrorToHTTP(err error, gone bool) int {
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
return http.StatusOK
|
return http.StatusOK
|
||||||
case os.IsPermission(err):
|
case os.IsPermission(err):
|
||||||
return http.StatusForbidden
|
return http.StatusForbidden
|
||||||
case os.IsNotExist(err):
|
case os.IsNotExist(err):
|
||||||
if !gone {
|
if !gone {
|
||||||
return http.StatusNotFound
|
return http.StatusNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusGone
|
return http.StatusGone
|
||||||
case os.IsExist(err):
|
case os.IsExist(err):
|
||||||
return http.StatusConflict
|
return http.StatusConflict
|
||||||
default:
|
default:
|
||||||
return http.StatusInternalServerError
|
return http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
772
http/resource.go
772
http/resource.go
|
@ -1,386 +1,386 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/fileutils"
|
"github.com/hacdias/fileutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// sanitizeURL sanitizes the URL to prevent path transversal
|
// sanitizeURL sanitizes the URL to prevent path transversal
|
||||||
// using fileutils.SlashClean and adds the trailing slash bar.
|
// using fileutils.SlashClean and adds the trailing slash bar.
|
||||||
func sanitizeURL(url string) string {
|
func sanitizeURL(url string) string {
|
||||||
path := fileutils.SlashClean(url)
|
path := fileutils.SlashClean(url)
|
||||||
if strings.HasSuffix(url, "/") && path != "/" {
|
if strings.HasSuffix(url, "/") && path != "/" {
|
||||||
return path + "/"
|
return path + "/"
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
r.URL.Path = sanitizeURL(r.URL.Path)
|
r.URL.Path = sanitizeURL(r.URL.Path)
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
return resourceGetHandler(c, w, r)
|
return resourceGetHandler(c, w, r)
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
return resourceDeleteHandler(c, w, r)
|
return resourceDeleteHandler(c, w, r)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
path := filepath.Join(c.User.Scope, r.URL.Path)
|
path := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
if err := c.Runner("before_save", path, "", c.User); err != nil {
|
if err := c.Runner("before_save", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := resourcePostPutHandler(c, w, r)
|
code, err := resourcePostPutHandler(c, w, r)
|
||||||
if code != http.StatusOK {
|
if code != http.StatusOK {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// After save command handler.
|
// After save command handler.
|
||||||
if err := c.Runner("after_save", path, "", c.User); err != nil {
|
if err := c.Runner("after_save", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return code, err
|
return code, err
|
||||||
case http.MethodPatch:
|
case http.MethodPatch:
|
||||||
return resourcePatchHandler(c, w, r)
|
return resourcePatchHandler(c, w, r)
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
return resourcePostPutHandler(c, w, r)
|
return resourcePostPutHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusNotImplemented, nil
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Gets the information of the directory/file.
|
// Gets the information of the directory/file.
|
||||||
f, err := fm.GetInfo(r.URL, c.FileManager, c.User)
|
f, err := fm.GetInfo(r.URL, c.FileManager, c.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a dir and the path doesn't end with a trailing slash,
|
// If it's a dir and the path doesn't end with a trailing slash,
|
||||||
// add a trailing slash to the path.
|
// add a trailing slash to the path.
|
||||||
if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
|
if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
|
||||||
r.URL.Path = r.URL.Path + "/"
|
r.URL.Path = r.URL.Path + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a dir, go and serve the listing.
|
// If it is a dir, go and serve the listing.
|
||||||
if f.IsDir {
|
if f.IsDir {
|
||||||
c.File = f
|
c.File = f
|
||||||
return listingHandler(c, w, r)
|
return listingHandler(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to get the file type.
|
// Tries to get the file type.
|
||||||
if err = f.GetFileType(true); err != nil {
|
if err = f.GetFileType(true); err != nil {
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve a preview if the file can't be edited or the
|
// Serve a preview if the file can't be edited or the
|
||||||
// user has no permission to edit this file. Otherwise,
|
// user has no permission to edit this file. Otherwise,
|
||||||
// just serve the editor.
|
// just serve the editor.
|
||||||
if !f.CanBeEdited() || !c.User.AllowEdit {
|
if !f.CanBeEdited() || !c.User.AllowEdit {
|
||||||
f.Kind = "preview"
|
f.Kind = "preview"
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Kind = "editor"
|
f.Kind = "editor"
|
||||||
|
|
||||||
// Tries to get the editor data.
|
// Tries to get the editor data.
|
||||||
if err = f.GetEditor(); err != nil {
|
if err = f.GetEditor(); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func listingHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
f := c.File
|
f := c.File
|
||||||
f.Kind = "listing"
|
f.Kind = "listing"
|
||||||
|
|
||||||
// Tries to get the listing data.
|
// Tries to get the listing data.
|
||||||
if err := f.GetListing(c.User, r); err != nil {
|
if err := f.GetListing(c.User, r); err != nil {
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
listing := f.Listing
|
listing := f.Listing
|
||||||
|
|
||||||
// Defines the cookie scope.
|
// Defines the cookie scope.
|
||||||
cookieScope := c.RootURL()
|
cookieScope := c.RootURL()
|
||||||
if cookieScope == "" {
|
if cookieScope == "" {
|
||||||
cookieScope = "/"
|
cookieScope = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the query values into the Listing struct
|
// Copy the query values into the Listing struct
|
||||||
if sort, order, err := handleSortOrder(w, r, cookieScope); err == nil {
|
if sort, order, err := handleSortOrder(w, r, cookieScope); err == nil {
|
||||||
listing.Sort = sort
|
listing.Sort = sort
|
||||||
listing.Order = order
|
listing.Order = order
|
||||||
} else {
|
} else {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
}
|
}
|
||||||
|
|
||||||
listing.ApplySort()
|
listing.ApplySort()
|
||||||
return renderJSON(w, f)
|
return renderJSON(w, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourceDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Prevent the removal of the root directory.
|
// Prevent the removal of the root directory.
|
||||||
if r.URL.Path == "/" || !c.User.AllowEdit {
|
if r.URL.Path == "/" || !c.User.AllowEdit {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the before trigger.
|
// Fire the before trigger.
|
||||||
if err := c.Runner("before_delete", r.URL.Path, "", c.User); err != nil {
|
if err := c.Runner("before_delete", r.URL.Path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the file or folder.
|
// Remove the file or folder.
|
||||||
err := c.User.FileSystem.RemoveAll(r.URL.Path)
|
err := c.User.FileSystem.RemoveAll(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("after_delete", r.URL.Path, "", c.User); err != nil {
|
if err := c.Runner("after_delete", r.URL.Path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePostPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.AllowNew && r.Method == http.MethodPost {
|
if !c.User.AllowNew && r.Method == http.MethodPost {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.User.AllowEdit && r.Method == http.MethodPut {
|
if !c.User.AllowEdit && r.Method == http.MethodPut {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard any invalid upload before returning to avoid connection
|
// Discard any invalid upload before returning to avoid connection
|
||||||
// reset error.
|
// reset error.
|
||||||
defer func() {
|
defer func() {
|
||||||
io.Copy(ioutil.Discard, r.Body)
|
io.Copy(ioutil.Discard, r.Body)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Checks if the current request is for a directory and not a file.
|
// Checks if the current request is for a directory and not a file.
|
||||||
if strings.HasSuffix(r.URL.Path, "/") {
|
if strings.HasSuffix(r.URL.Path, "/") {
|
||||||
// If the method is PUT, we return 405 Method not Allowed, because
|
// If the method is PUT, we return 405 Method not Allowed, because
|
||||||
// POST should be used instead.
|
// POST should be used instead.
|
||||||
if r.Method == http.MethodPut {
|
if r.Method == http.MethodPut {
|
||||||
return http.StatusMethodNotAllowed, nil
|
return http.StatusMethodNotAllowed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise we try to create the directory.
|
// Otherwise we try to create the directory.
|
||||||
err := c.User.FileSystem.Mkdir(r.URL.Path, 0776)
|
err := c.User.FileSystem.Mkdir(r.URL.Path, 0776)
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If using POST method, we are trying to create a new file so it is not
|
// If using POST method, we are trying to create a new file so it is not
|
||||||
// desirable to override an already existent file. Thus, we check
|
// desirable to override an already existent file. Thus, we check
|
||||||
// if the file already exists. If so, we just return a 409 Conflict.
|
// if the file already exists. If so, we just return a 409 Conflict.
|
||||||
if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
|
if r.Method == http.MethodPost && r.Header.Get("Action") != "override" {
|
||||||
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
|
if _, err := c.User.FileSystem.Stat(r.URL.Path); err == nil {
|
||||||
return http.StatusConflict, errors.New("There is already a file on that path")
|
return http.StatusConflict, errors.New("There is already a file on that path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fire the before trigger.
|
// Fire the before trigger.
|
||||||
if err := c.Runner("before_upload", r.URL.Path, "", c.User); err != nil {
|
if err := c.Runner("before_upload", r.URL.Path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create/Open the file.
|
// Create/Open the file.
|
||||||
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
|
f, err := c.User.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0776)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Copies the new content for the file.
|
// Copies the new content for the file.
|
||||||
_, err = io.Copy(f, r.Body)
|
_, err = io.Copy(f, r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the info about the file.
|
// Gets the info about the file.
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, false), err
|
return ErrorToHTTP(err, false), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this instance has a Static Generator and handles publishing
|
// Check if this instance has a Static Generator and handles publishing
|
||||||
// or scheduling if it's the case.
|
// or scheduling if it's the case.
|
||||||
if c.StaticGen != nil {
|
if c.StaticGen != nil {
|
||||||
code, err := resourcePublishSchedule(c, w, r)
|
code, err := resourcePublishSchedule(c, w, r)
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes the ETag Header.
|
// Writes the ETag Header.
|
||||||
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
|
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("after_upload", r.URL.Path, "", c.User); err != nil {
|
if err := c.Runner("after_upload", r.URL.Path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePublishSchedule(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePublishSchedule(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
publish := r.Header.Get("Publish")
|
publish := r.Header.Get("Publish")
|
||||||
schedule := r.Header.Get("Schedule")
|
schedule := r.Header.Get("Schedule")
|
||||||
|
|
||||||
if publish != "true" && schedule == "" {
|
if publish != "true" && schedule == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.User.AllowPublish {
|
if !c.User.AllowPublish {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if publish == "true" {
|
if publish == "true" {
|
||||||
return resourcePublish(c, w, r)
|
return resourcePublish(c, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := time.Parse("2006-01-02T15:04", schedule)
|
t, err := time.Parse("2006-01-02T15:04", schedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Cron.AddFunc(t.Format("05 04 15 02 01 *"), func() {
|
c.Cron.AddFunc(t.Format("05 04 15 02 01 *"), func() {
|
||||||
_, err := resourcePublish(c, w, r)
|
_, err := resourcePublish(c, w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePublish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
path := filepath.Join(c.User.Scope, r.URL.Path)
|
path := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
|
|
||||||
// Before save command handler.
|
// Before save command handler.
|
||||||
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := c.StaticGen.Publish(c, w, r)
|
code, err := c.StaticGen.Publish(c, w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return code, err
|
return code, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executed the before publish command.
|
// Executed the before publish command.
|
||||||
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
if err := c.Runner("before_publish", path, "", c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resourcePatchHandler is the entry point for resource handler.
|
// resourcePatchHandler is the entry point for resource handler.
|
||||||
func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func resourcePatchHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
if !c.User.AllowEdit {
|
if !c.User.AllowEdit {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dst := r.Header.Get("Destination")
|
dst := r.Header.Get("Destination")
|
||||||
action := r.Header.Get("Action")
|
action := r.Header.Get("Action")
|
||||||
dst, err := url.QueryUnescape(dst)
|
dst, err := url.QueryUnescape(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
src := r.URL.Path
|
src := r.URL.Path
|
||||||
|
|
||||||
if dst == "/" || src == "/" {
|
if dst == "/" || src == "/" {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == "copy" {
|
if action == "copy" {
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("before_copy", src, dst, c.User); err != nil {
|
if err := c.Runner("before_copy", src, dst, c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the file.
|
// Copy the file.
|
||||||
err = c.User.FileSystem.Copy(src, dst)
|
err = c.User.FileSystem.Copy(src, dst)
|
||||||
|
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("after_copy", src, dst, c.User); err != nil {
|
if err := c.Runner("after_copy", src, dst, c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("before_rename", src, dst, c.User); err != nil {
|
if err := c.Runner("before_rename", src, dst, c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename the file.
|
// Rename the file.
|
||||||
err = c.User.FileSystem.Rename(src, dst)
|
err = c.User.FileSystem.Rename(src, dst)
|
||||||
|
|
||||||
// Fire the after trigger.
|
// Fire the after trigger.
|
||||||
if err := c.Runner("after_rename", src, dst, c.User); err != nil {
|
if err := c.Runner("after_rename", src, dst, c.User); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ErrorToHTTP(err, true), err
|
return ErrorToHTTP(err, true), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
||||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
||||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
|
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
|
||||||
sort = r.URL.Query().Get("sort")
|
sort = r.URL.Query().Get("sort")
|
||||||
order = r.URL.Query().Get("order")
|
order = r.URL.Query().Get("order")
|
||||||
|
|
||||||
// If the query 'sort' or 'order' is empty, use defaults or any values
|
// If the query 'sort' or 'order' is empty, use defaults or any values
|
||||||
// previously saved in Cookies.
|
// previously saved in Cookies.
|
||||||
switch sort {
|
switch sort {
|
||||||
case "":
|
case "":
|
||||||
sort = "name"
|
sort = "name"
|
||||||
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
|
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
|
||||||
sort = sortCookie.Value
|
sort = sortCookie.Value
|
||||||
}
|
}
|
||||||
case "name", "size":
|
case "name", "size":
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "sort",
|
Name: "sort",
|
||||||
Value: sort,
|
Value: sort,
|
||||||
MaxAge: 31536000,
|
MaxAge: 31536000,
|
||||||
Path: scope,
|
Path: scope,
|
||||||
Secure: r.TLS != nil,
|
Secure: r.TLS != nil,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
switch order {
|
switch order {
|
||||||
case "":
|
case "":
|
||||||
order = "asc"
|
order = "asc"
|
||||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||||
order = orderCookie.Value
|
order = orderCookie.Value
|
||||||
}
|
}
|
||||||
case "asc", "desc":
|
case "asc", "desc":
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "order",
|
Name: "order",
|
||||||
Value: order,
|
Value: order,
|
||||||
MaxAge: 31536000,
|
MaxAge: 31536000,
|
||||||
Path: scope,
|
Path: scope,
|
||||||
Secure: r.TLS != nil,
|
Secure: r.TLS != nil,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,339 +1,339 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cmdNotImplemented = []byte("Command not implemented.")
|
cmdNotImplemented = []byte("Command not implemented.")
|
||||||
cmdNotAllowed = []byte("Command not allowed.")
|
cmdNotAllowed = []byte("Command not allowed.")
|
||||||
)
|
)
|
||||||
|
|
||||||
// command handles the requests for VCS related commands: git, svn and mercurial
|
// command handles the requests for VCS related commands: git, svn and mercurial
|
||||||
func command(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func command(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Upgrades the connection to a websocket and checks for fm.Errors.
|
// Upgrades the connection to a websocket and checks for fm.Errors.
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
message []byte
|
message []byte
|
||||||
command []string
|
command []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Starts an infinite loop until a valid command is captured.
|
// Starts an infinite loop until a valid command is captured.
|
||||||
for {
|
for {
|
||||||
_, message, err = conn.ReadMessage()
|
_, message, err = conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
command = strings.Split(string(message), " ")
|
command = strings.Split(string(message), " ")
|
||||||
if len(command) != 0 {
|
if len(command) != 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the command is allowed
|
// Check if the command is allowed
|
||||||
allowed := false
|
allowed := false
|
||||||
|
|
||||||
for _, cmd := range c.User.Commands {
|
for _, cmd := range c.User.Commands {
|
||||||
if cmd == command[0] {
|
if cmd == command[0] {
|
||||||
allowed = true
|
allowed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allowed {
|
if !allowed {
|
||||||
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotAllowed)
|
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotAllowed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the program is talled is installed on the computer.
|
// Check if the program is talled is installed on the computer.
|
||||||
if _, err = exec.LookPath(command[0]); err != nil {
|
if _, err = exec.LookPath(command[0]); err != nil {
|
||||||
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotImplemented)
|
err = conn.WriteMessage(websocket.BinaryMessage, cmdNotImplemented)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.StatusNotImplemented, nil
|
return http.StatusNotImplemented, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the path and initializes a buffer.
|
// Gets the path and initializes a buffer.
|
||||||
path := c.User.Scope + "/" + r.URL.Path
|
path := c.User.Scope + "/" + r.URL.Path
|
||||||
path = filepath.Clean(path)
|
path = filepath.Clean(path)
|
||||||
buff := new(bytes.Buffer)
|
buff := new(bytes.Buffer)
|
||||||
|
|
||||||
// Sets up the command executation.
|
// Sets up the command executation.
|
||||||
cmd := exec.Command(command[0], command[1:]...)
|
cmd := exec.Command(command[0], command[1:]...)
|
||||||
cmd.Dir = path
|
cmd.Dir = path
|
||||||
cmd.Stderr = buff
|
cmd.Stderr = buff
|
||||||
cmd.Stdout = buff
|
cmd.Stdout = buff
|
||||||
|
|
||||||
// Starts the command and checks for fm.Errors.
|
// Starts the command and checks for fm.Errors.
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a 'done' variable to check whetever the command has already finished
|
// Set a 'done' variable to check whetever the command has already finished
|
||||||
// running or not. This verification is done using a goroutine that uses the
|
// running or not. This verification is done using a goroutine that uses the
|
||||||
// method .Wait() from the command.
|
// method .Wait() from the command.
|
||||||
done := false
|
done := false
|
||||||
go func() {
|
go func() {
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
done = true
|
done = true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Function to print the current information on the buffer to the connection.
|
// Function to print the current information on the buffer to the connection.
|
||||||
print := func() error {
|
print := func() error {
|
||||||
by := buff.Bytes()
|
by := buff.Bytes()
|
||||||
if len(by) > 0 {
|
if len(by) > 0 {
|
||||||
err = conn.WriteMessage(websocket.TextMessage, by)
|
err = conn.WriteMessage(websocket.TextMessage, by)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// While the command hasn't finished running, continue sending the output
|
// While the command hasn't finished running, continue sending the output
|
||||||
// to the client in intervals of 100 milliseconds.
|
// to the client in intervals of 100 milliseconds.
|
||||||
for !done {
|
for !done {
|
||||||
if err = print(); err != nil {
|
if err = print(); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// After the command is done executing, send the output one more time to the
|
// After the command is done executing, send the output one more time to the
|
||||||
// browser to make sure it gets the latest information.
|
// browser to make sure it gets the latest information.
|
||||||
if err = print(); err != nil {
|
if err = print(); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
typeRegexp = regexp.MustCompile(`type:(\w+)`)
|
typeRegexp = regexp.MustCompile(`type:(\w+)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type condition func(path string) bool
|
type condition func(path string) bool
|
||||||
|
|
||||||
type searchOptions struct {
|
type searchOptions struct {
|
||||||
CaseInsensitive bool
|
CaseInsensitive bool
|
||||||
Conditions []condition
|
Conditions []condition
|
||||||
Terms []string
|
Terms []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func extensionCondition(extension string) condition {
|
func extensionCondition(extension string) condition {
|
||||||
return func(path string) bool {
|
return func(path string) bool {
|
||||||
return filepath.Ext(path) == "."+extension
|
return filepath.Ext(path) == "."+extension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageCondition(path string) bool {
|
func imageCondition(path string) bool {
|
||||||
extension := filepath.Ext(path)
|
extension := filepath.Ext(path)
|
||||||
mimetype := mime.TypeByExtension(extension)
|
mimetype := mime.TypeByExtension(extension)
|
||||||
|
|
||||||
return strings.HasPrefix(mimetype, "image")
|
return strings.HasPrefix(mimetype, "image")
|
||||||
}
|
}
|
||||||
|
|
||||||
func audioCondition(path string) bool {
|
func audioCondition(path string) bool {
|
||||||
extension := filepath.Ext(path)
|
extension := filepath.Ext(path)
|
||||||
mimetype := mime.TypeByExtension(extension)
|
mimetype := mime.TypeByExtension(extension)
|
||||||
|
|
||||||
return strings.HasPrefix(mimetype, "audio")
|
return strings.HasPrefix(mimetype, "audio")
|
||||||
}
|
}
|
||||||
|
|
||||||
func videoCondition(path string) bool {
|
func videoCondition(path string) bool {
|
||||||
extension := filepath.Ext(path)
|
extension := filepath.Ext(path)
|
||||||
mimetype := mime.TypeByExtension(extension)
|
mimetype := mime.TypeByExtension(extension)
|
||||||
|
|
||||||
return strings.HasPrefix(mimetype, "video")
|
return strings.HasPrefix(mimetype, "video")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSearch(value string) *searchOptions {
|
func parseSearch(value string) *searchOptions {
|
||||||
opts := &searchOptions{
|
opts := &searchOptions{
|
||||||
CaseInsensitive: strings.Contains(value, "case:insensitive"),
|
CaseInsensitive: strings.Contains(value, "case:insensitive"),
|
||||||
Conditions: []condition{},
|
Conditions: []condition{},
|
||||||
Terms: []string{},
|
Terms: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes the options from the value
|
// removes the options from the value
|
||||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
value = strings.Replace(value, "case:insensitive", "", -1)
|
||||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
value = strings.Replace(value, "case:sensitive", "", -1)
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
if len(t) == 1 {
|
if len(t) == 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t[1] {
|
switch t[1] {
|
||||||
case "image":
|
case "image":
|
||||||
opts.Conditions = append(opts.Conditions, imageCondition)
|
opts.Conditions = append(opts.Conditions, imageCondition)
|
||||||
case "audio", "music":
|
case "audio", "music":
|
||||||
opts.Conditions = append(opts.Conditions, audioCondition)
|
opts.Conditions = append(opts.Conditions, audioCondition)
|
||||||
case "video":
|
case "video":
|
||||||
opts.Conditions = append(opts.Conditions, videoCondition)
|
opts.Conditions = append(opts.Conditions, videoCondition)
|
||||||
default:
|
default:
|
||||||
opts.Conditions = append(opts.Conditions, extensionCondition(t[1]))
|
opts.Conditions = append(opts.Conditions, extensionCondition(t[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(types) > 0 {
|
if len(types) > 0 {
|
||||||
// Remove the fields from the search value.
|
// Remove the fields from the search value.
|
||||||
value = typeRegexp.ReplaceAllString(value, "")
|
value = typeRegexp.ReplaceAllString(value, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's canse insensitive, put everything in lowercase.
|
// If it's canse insensitive, put everything in lowercase.
|
||||||
if opts.CaseInsensitive {
|
if opts.CaseInsensitive {
|
||||||
value = strings.ToLower(value)
|
value = strings.ToLower(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the spaces from the search value.
|
// Remove the spaces from the search value.
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
|
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the value starts with " and finishes what that character, we will
|
// if the value starts with " and finishes what that character, we will
|
||||||
// only search for that term
|
// only search for that term
|
||||||
if value[0] == '"' && value[len(value)-1] == '"' {
|
if value[0] == '"' && value[len(value)-1] == '"' {
|
||||||
unique := strings.TrimPrefix(value, "\"")
|
unique := strings.TrimPrefix(value, "\"")
|
||||||
unique = strings.TrimSuffix(unique, "\"")
|
unique = strings.TrimSuffix(unique, "\"")
|
||||||
|
|
||||||
opts.Terms = []string{unique}
|
opts.Terms = []string{unique}
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Terms = strings.Split(value, " ")
|
opts.Terms = strings.Split(value, " ")
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
// search searches for a file or directory.
|
// search searches for a file or directory.
|
||||||
func search(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func search(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Upgrades the connection to a websocket and checks for fm.Errors.
|
// Upgrades the connection to a websocket and checks for fm.Errors.
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
value string
|
value string
|
||||||
search *searchOptions
|
search *searchOptions
|
||||||
message []byte
|
message []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
// Starts an infinite loop until a valid command is captured.
|
// Starts an infinite loop until a valid command is captured.
|
||||||
for {
|
for {
|
||||||
_, message, err = conn.ReadMessage()
|
_, message, err = conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(message) != 0 {
|
if len(message) != 0 {
|
||||||
value = string(message)
|
value = string(message)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search = parseSearch(value)
|
search = parseSearch(value)
|
||||||
scope := strings.TrimPrefix(r.URL.Path, "/")
|
scope := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
scope = "/" + scope
|
scope = "/" + scope
|
||||||
scope = c.User.Scope + scope
|
scope = c.User.Scope + scope
|
||||||
scope = strings.Replace(scope, "\\", "/", -1)
|
scope = strings.Replace(scope, "\\", "/", -1)
|
||||||
scope = filepath.Clean(scope)
|
scope = filepath.Clean(scope)
|
||||||
|
|
||||||
err = filepath.Walk(scope, func(path string, f os.FileInfo, err error) error {
|
err = filepath.Walk(scope, func(path string, f os.FileInfo, err error) error {
|
||||||
if search.CaseInsensitive {
|
if search.CaseInsensitive {
|
||||||
path = strings.ToLower(path)
|
path = strings.ToLower(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
path = strings.TrimPrefix(path, scope)
|
path = strings.TrimPrefix(path, scope)
|
||||||
path = strings.TrimPrefix(path, "/")
|
path = strings.TrimPrefix(path, "/")
|
||||||
path = strings.Replace(path, "\\", "/", -1)
|
path = strings.Replace(path, "\\", "/", -1)
|
||||||
|
|
||||||
// Only execute if there are conditions to meet.
|
// Only execute if there are conditions to meet.
|
||||||
if len(search.Conditions) > 0 {
|
if len(search.Conditions) > 0 {
|
||||||
match := false
|
match := false
|
||||||
|
|
||||||
for _, t := range search.Conditions {
|
for _, t := range search.Conditions {
|
||||||
if t(path) {
|
if t(path) {
|
||||||
match = true
|
match = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If doesn't meet the condition, go to the next.
|
// If doesn't meet the condition, go to the next.
|
||||||
if !match {
|
if !match {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(search.Terms) > 0 {
|
if len(search.Terms) > 0 {
|
||||||
is := false
|
is := false
|
||||||
|
|
||||||
// Checks if matches the terms and if it is allowed.
|
// Checks if matches the terms and if it is allowed.
|
||||||
for _, term := range search.Terms {
|
for _, term := range search.Terms {
|
||||||
if is {
|
if is {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(path, term) {
|
if strings.Contains(path, term) {
|
||||||
if !c.User.Allowed(path) {
|
if !c.User.Allowed(path) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
is = true
|
is = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is {
|
if !is {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, _ := json.Marshal(map[string]interface{}{
|
response, _ := json.Marshal(map[string]interface{}{
|
||||||
"dir": f.IsDir(),
|
"dir": f.IsDir(),
|
||||||
"path": path,
|
"path": path,
|
||||||
})
|
})
|
||||||
|
|
||||||
return conn.WriteMessage(websocket.TextMessage, response)
|
return conn.WriteMessage(websocket.TextMessage, response)
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
882c59547968e4fb9a65aa8d1c838409f198e51b
|
|
|
@ -1,194 +1,194 @@
|
||||||
package staticgen
|
package staticgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
"github.com/hacdias/varutils"
|
"github.com/hacdias/varutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
errUnsupportedFileType = errors.New("The type of the provided file isn't supported for this action")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hugo is the Hugo static website generator.
|
// Hugo is the Hugo static website generator.
|
||||||
type Hugo struct {
|
type Hugo struct {
|
||||||
// Website root
|
// Website root
|
||||||
Root string `name:"Website Root"`
|
Root string `name:"Website Root"`
|
||||||
// Public folder
|
// Public folder
|
||||||
Public string `name:"Public Directory"`
|
Public string `name:"Public Directory"`
|
||||||
// Hugo executable path
|
// Hugo executable path
|
||||||
Exe string `name:"Hugo Executable"`
|
Exe string `name:"Hugo Executable"`
|
||||||
// Hugo arguments
|
// Hugo arguments
|
||||||
Args []string `name:"Hugo Arguments"`
|
Args []string `name:"Hugo Arguments"`
|
||||||
// Indicates if we should clean public before a new publish.
|
// Indicates if we should clean public before a new publish.
|
||||||
CleanPublic bool `name:"Clean Public"`
|
CleanPublic bool `name:"Clean Public"`
|
||||||
// previewPath is the temporary path for a preview
|
// previewPath is the temporary path for a preview
|
||||||
previewPath string
|
previewPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsPath retrieves the correct settings path.
|
// SettingsPath retrieves the correct settings path.
|
||||||
func (h Hugo) SettingsPath() string {
|
func (h Hugo) SettingsPath() string {
|
||||||
var frontmatter string
|
var frontmatter string
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(h.Root, "config.yaml")); err == nil {
|
if _, err = os.Stat(filepath.Join(h.Root, "config.yaml")); err == nil {
|
||||||
frontmatter = "yaml"
|
frontmatter = "yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(h.Root, "config.json")); err == nil {
|
if _, err = os.Stat(filepath.Join(h.Root, "config.json")); err == nil {
|
||||||
frontmatter = "json"
|
frontmatter = "json"
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(filepath.Join(h.Root, "config.toml")); err == nil {
|
if _, err = os.Stat(filepath.Join(h.Root, "config.toml")); err == nil {
|
||||||
frontmatter = "toml"
|
frontmatter = "toml"
|
||||||
}
|
}
|
||||||
|
|
||||||
if frontmatter == "" {
|
if frontmatter == "" {
|
||||||
return "/settings"
|
return "/settings"
|
||||||
}
|
}
|
||||||
|
|
||||||
return "/config." + frontmatter
|
return "/config." + frontmatter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name is the plugin's name.
|
// Name is the plugin's name.
|
||||||
func (h Hugo) Name() string {
|
func (h Hugo) Name() string {
|
||||||
return "hugo"
|
return "hugo"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook is the pre-api handler.
|
// Hook is the pre-api handler.
|
||||||
func (h Hugo) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Hugo) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// If we are not using HTTP Post, we shall return Method Not Allowed
|
// If we are not using HTTP Post, we shall return Method Not Allowed
|
||||||
// since we are only working with this method.
|
// since we are only working with this method.
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Router != "resource" {
|
if c.Router != "resource" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only care about creating new files from archetypes here. So...
|
// We only care about creating new files from archetypes here. So...
|
||||||
if r.Header.Get("Archetype") == "" {
|
if r.Header.Get("Archetype") == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.User.AllowNew {
|
if !c.User.AllowNew {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
archetype := r.Header.Get("archetype")
|
archetype := r.Header.Get("archetype")
|
||||||
|
|
||||||
ext := filepath.Ext(filename)
|
ext := filepath.Ext(filename)
|
||||||
|
|
||||||
// If the request isn't for a markdown file, we can't
|
// If the request isn't for a markdown file, we can't
|
||||||
// handle it.
|
// handle it.
|
||||||
if ext != ".markdown" && ext != ".md" {
|
if ext != ".markdown" && ext != ".md" {
|
||||||
return http.StatusBadRequest, errUnsupportedFileType
|
return http.StatusBadRequest, errUnsupportedFileType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to create a new file based on this archetype.
|
// Tries to create a new file based on this archetype.
|
||||||
args := []string{"new", filename, "--kind", archetype}
|
args := []string{"new", filename, "--kind", archetype}
|
||||||
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes the location of the new file to the Header.
|
// Writes the location of the new file to the Header.
|
||||||
w.Header().Set("Location", "/files/content/"+filename)
|
w.Header().Set("Location", "/files/content/"+filename)
|
||||||
return http.StatusCreated, nil
|
return http.StatusCreated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish publishes a post.
|
// Publish publishes a post.
|
||||||
func (h Hugo) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Hugo) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
|
|
||||||
// We only run undraft command if it is a file.
|
// We only run undraft command if it is a file.
|
||||||
if strings.HasSuffix(filename, ".md") && strings.HasSuffix(filename, ".markdown") {
|
if strings.HasSuffix(filename, ".md") && strings.HasSuffix(filename, ".markdown") {
|
||||||
if err := h.undraft(filename); err != nil {
|
if err := h.undraft(filename); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regenerates the file
|
// Regenerates the file
|
||||||
h.run(false)
|
h.run(false)
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview handles the preview path.
|
// Preview handles the preview path.
|
||||||
func (h *Hugo) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h *Hugo) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Get a new temporary path if there is none.
|
// Get a new temporary path if there is none.
|
||||||
if h.previewPath == "" {
|
if h.previewPath == "" {
|
||||||
path, err := ioutil.TempDir("", "")
|
path, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h.previewPath = path
|
h.previewPath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the arguments to execute Hugo: change the base URL,
|
// Build the arguments to execute Hugo: change the base URL,
|
||||||
// build the drafts and update the destination.
|
// build the drafts and update the destination.
|
||||||
args := h.Args
|
args := h.Args
|
||||||
args = append(args, "--baseURL", c.RootURL()+"/preview/")
|
args = append(args, "--baseURL", c.RootURL()+"/preview/")
|
||||||
args = append(args, "--buildDrafts")
|
args = append(args, "--buildDrafts")
|
||||||
args = append(args, "--destination", h.previewPath)
|
args = append(args, "--destination", h.previewPath)
|
||||||
|
|
||||||
// Builds the preview.
|
// Builds the preview.
|
||||||
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
if err := runCommand(h.Exe, args, h.Root); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serves the temporary path with the preview.
|
// Serves the temporary path with the preview.
|
||||||
http.FileServer(http.Dir(h.previewPath)).ServeHTTP(w, r)
|
http.FileServer(http.Dir(h.previewPath)).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Hugo) run(force bool) {
|
func (h Hugo) run(force bool) {
|
||||||
// If the CleanPublic option is enabled, clean it.
|
// If the CleanPublic option is enabled, clean it.
|
||||||
if h.CleanPublic {
|
if h.CleanPublic {
|
||||||
os.RemoveAll(h.Public)
|
os.RemoveAll(h.Public)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent running if watching is enabled
|
// Prevent running if watching is enabled
|
||||||
if b, pos := varutils.StringInSlice("--watch", h.Args); b && !force {
|
if b, pos := varutils.StringInSlice("--watch", h.Args); b && !force {
|
||||||
if len(h.Args) > pos && h.Args[pos+1] != "false" {
|
if len(h.Args) > pos && h.Args[pos+1] != "false" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(h.Args) == pos+1 {
|
if len(h.Args) == pos+1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runCommand(h.Exe, h.Args, h.Root); err != nil {
|
if err := runCommand(h.Exe, h.Args, h.Root); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Hugo) undraft(file string) error {
|
func (h Hugo) undraft(file string) error {
|
||||||
args := []string{"undraft", file}
|
args := []string{"undraft", file}
|
||||||
if err := runCommand(h.Exe, args, h.Root); err != nil && !strings.Contains(err.Error(), "not a Draft") {
|
if err := runCommand(h.Exe, args, h.Root); err != nil && !strings.Contains(err.Error(), "not a Draft") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup sets up the plugin.
|
// Setup sets up the plugin.
|
||||||
func (h *Hugo) Setup() error {
|
func (h *Hugo) Setup() error {
|
||||||
var err error
|
var err error
|
||||||
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
if h.Exe, err = exec.LookPath("hugo"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,125 +1,125 @@
|
||||||
package staticgen
|
package staticgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fm "github.com/hacdias/filemanager"
|
fm "github.com/hacdias/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Jekyll is the Jekyll static website generator.
|
// Jekyll is the Jekyll static website generator.
|
||||||
type Jekyll struct {
|
type Jekyll struct {
|
||||||
// Website root
|
// Website root
|
||||||
Root string `name:"Website Root"`
|
Root string `name:"Website Root"`
|
||||||
// Public folder
|
// Public folder
|
||||||
Public string `name:"Public Directory"`
|
Public string `name:"Public Directory"`
|
||||||
// Jekyll executable path
|
// Jekyll executable path
|
||||||
Exe string `name:"Executable"`
|
Exe string `name:"Executable"`
|
||||||
// Jekyll arguments
|
// Jekyll arguments
|
||||||
Args []string `name:"Arguments"`
|
Args []string `name:"Arguments"`
|
||||||
// Indicates if we should clean public before a new publish.
|
// Indicates if we should clean public before a new publish.
|
||||||
CleanPublic bool `name:"Clean Public"`
|
CleanPublic bool `name:"Clean Public"`
|
||||||
// previewPath is the temporary path for a preview
|
// previewPath is the temporary path for a preview
|
||||||
previewPath string
|
previewPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name is the plugin's name.
|
// Name is the plugin's name.
|
||||||
func (j Jekyll) Name() string {
|
func (j Jekyll) Name() string {
|
||||||
return "jekyll"
|
return "jekyll"
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsPath retrieves the correct settings path.
|
// SettingsPath retrieves the correct settings path.
|
||||||
func (j Jekyll) SettingsPath() string {
|
func (j Jekyll) SettingsPath() string {
|
||||||
return "/_config.yml"
|
return "/_config.yml"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook is the pre-api handler.
|
// Hook is the pre-api handler.
|
||||||
func (j Jekyll) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (j Jekyll) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish publishes a post.
|
// Publish publishes a post.
|
||||||
func (j Jekyll) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (j Jekyll) Publish(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
||||||
|
|
||||||
// We only run undraft command if it is a file.
|
// We only run undraft command if it is a file.
|
||||||
if err := j.undraft(filename); err != nil {
|
if err := j.undraft(filename); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regenerates the file
|
// Regenerates the file
|
||||||
j.run()
|
j.run()
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preview handles the preview path.
|
// Preview handles the preview path.
|
||||||
func (j *Jekyll) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func (j *Jekyll) Preview(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Get a new temporary path if there is none.
|
// Get a new temporary path if there is none.
|
||||||
if j.previewPath == "" {
|
if j.previewPath == "" {
|
||||||
path, err := ioutil.TempDir("", "")
|
path, err := ioutil.TempDir("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
j.previewPath = path
|
j.previewPath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the arguments to execute Hugo: change the base URL,
|
// Build the arguments to execute Hugo: change the base URL,
|
||||||
// build the drafts and update the destination.
|
// build the drafts and update the destination.
|
||||||
args := j.Args
|
args := j.Args
|
||||||
args = append(args, "--baseurl", c.RootURL()+"/preview/")
|
args = append(args, "--baseurl", c.RootURL()+"/preview/")
|
||||||
args = append(args, "--drafts")
|
args = append(args, "--drafts")
|
||||||
args = append(args, "--destination", j.previewPath)
|
args = append(args, "--destination", j.previewPath)
|
||||||
|
|
||||||
// Builds the preview.
|
// Builds the preview.
|
||||||
if err := runCommand(j.Exe, args, j.Root); err != nil {
|
if err := runCommand(j.Exe, args, j.Root); err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serves the temporary path with the preview.
|
// Serves the temporary path with the preview.
|
||||||
http.FileServer(http.Dir(j.previewPath)).ServeHTTP(w, r)
|
http.FileServer(http.Dir(j.previewPath)).ServeHTTP(w, r)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j Jekyll) run() {
|
func (j Jekyll) run() {
|
||||||
// If the CleanPublic option is enabled, clean it.
|
// If the CleanPublic option is enabled, clean it.
|
||||||
if j.CleanPublic {
|
if j.CleanPublic {
|
||||||
os.RemoveAll(j.Public)
|
os.RemoveAll(j.Public)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runCommand(j.Exe, j.Args, j.Root); err != nil {
|
if err := runCommand(j.Exe, j.Args, j.Root); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j Jekyll) undraft(file string) error {
|
func (j Jekyll) undraft(file string) error {
|
||||||
if !strings.Contains(file, "_drafts") {
|
if !strings.Contains(file, "_drafts") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Rename(file, strings.Replace(file, "_drafts", "_posts", 1))
|
return os.Rename(file, strings.Replace(file, "_drafts", "_posts", 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup sets up the plugin.
|
// Setup sets up the plugin.
|
||||||
func (j *Jekyll) Setup() error {
|
func (j *Jekyll) Setup() error {
|
||||||
var err error
|
var err error
|
||||||
if j.Exe, err = exec.LookPath("jekyll"); err != nil {
|
if j.Exe, err = exec.LookPath("jekyll"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(j.Args) == 0 {
|
if len(j.Args) == 0 {
|
||||||
j.Args = []string{"build"}
|
j.Args = []string{"build"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if j.Args[0] != "build" {
|
if j.Args[0] != "build" {
|
||||||
j.Args = append([]string{"build"}, j.Args...)
|
j.Args = append([]string{"build"}, j.Args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
package staticgen
|
package staticgen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runCommand executes an external command
|
// runCommand executes an external command
|
||||||
func runCommand(command string, args []string, path string) error {
|
func runCommand(command string, args []string, path string) error {
|
||||||
cmd := exec.Command(command, args...)
|
cmd := exec.Command(command, args...)
|
||||||
cmd.Dir = path
|
cmd.Dir = path
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(string(out))
|
return errors.New(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue