Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a61ade62e5 | ||
|
|
08de5efeb4 | ||
|
|
74f690a71b | ||
|
|
1d4a3005ff | ||
|
|
7447a530ee | ||
|
|
fa9396f0f4 | ||
|
|
172bbb1828 | ||
|
|
410aa5d9da | ||
|
|
736cef7127 | ||
|
|
f4ceb7163e | ||
|
|
7bd4fbc0cb | ||
|
|
2ed4658369 | ||
|
|
735312982c | ||
|
|
4ddb3f5a34 | ||
|
|
c947228ac4 | ||
|
|
505af7d9d7 | ||
|
|
2c13ac4ac1 | ||
|
|
b5fbab2072 | ||
|
|
005b184d59 | ||
|
|
372bd813d9 | ||
|
|
52599314b0 | ||
|
|
f61e71e44f | ||
|
|
e5265b6632 | ||
|
|
003f361956 | ||
|
|
cc8369d83e | ||
|
|
aeb583a0bc | ||
|
|
41f07c64eb | ||
|
|
98588a66a7 | ||
|
|
49f32f876d | ||
|
|
aeb0f37e20 | ||
|
|
59a0daa293 | ||
|
|
c716d126eb |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea
|
||||
.vscode
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -1,6 +1,6 @@
|
||||
language: go
|
||||
|
||||
go: 1.8.3
|
||||
go: 1.x
|
||||
|
||||
env:
|
||||
- "PATH=/home/travis/gopath/bin:$PATH"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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/cmd/filemanager`
|
||||
2. `cd $GOPATH/src/github.com/hacdias/filemanager`
|
||||
3. `npm install`
|
||||
4. `npm run dev` - regenerates the static assets automatically
|
||||
|
||||
@@ -13,6 +13,7 @@ RUN mv filemanager /go/bin/filemanager
|
||||
FROM scratch
|
||||
COPY --from=0 /go/bin/filemanager /filemanager
|
||||
|
||||
VOLUME /tmp
|
||||
VOLUME /srv
|
||||
EXPOSE 80
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
[](https://travis-ci.org/hacdias/filemanager)
|
||||
[](https://goreportcard.com/report/hacdias/filemanager)
|
||||
[](http://godoc.org/github.com/hacdias/filemanager)
|
||||
[](https://github.com/hacdias/filemanager/releases/latest)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<option value="ja">{{ $t('languages.ja') }}</option>
|
||||
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
|
||||
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
|
||||
<option value="es">{{ $t('languages.es') }}</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ export default {
|
||||
new (url, type) {
|
||||
url = removePrefix(url)
|
||||
|
||||
if (!url.endsWith('.md') && !url.endsWith('.markdown')) {
|
||||
url += '.markdown'
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${this.$store.state.baseURL}/api/resource${url}`, true)
|
||||
|
||||
@@ -192,6 +192,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
time:
|
||||
unit: Time Unit
|
||||
seconds: Seconds
|
||||
|
||||
202
assets/src/i18n/es.yaml
Normal file
202
assets/src/i18n/es.yaml
Normal file
@@ -0,0 +1,202 @@
|
||||
permanent: Permanente
|
||||
buttons:
|
||||
cancel: Cancelar
|
||||
close: Cerrar
|
||||
copy: Copiar
|
||||
copyFile: Copiar archivo
|
||||
copyToClipboard: Copiar al portapapeles
|
||||
create: Crear
|
||||
delete: Borrar
|
||||
download: Descargar
|
||||
info: Info
|
||||
more: Más
|
||||
move: Mover
|
||||
moveFile: Mover archivo
|
||||
new: Nuevo
|
||||
next: Siguiente
|
||||
ok: OK
|
||||
replace: Reemplazar
|
||||
previous: Anterior
|
||||
rename: Renombrar
|
||||
reportIssue: Reportar problema
|
||||
save: Guardar
|
||||
search: Buscar
|
||||
select: Seleccionar
|
||||
share: Compartir
|
||||
publish: Publicar
|
||||
selectMultiple: Selección múltiple
|
||||
schedule: Programar
|
||||
switchView: Cambiar vista
|
||||
toggleSidebar: Mostrar/Ocultar menú
|
||||
update: Actualizar
|
||||
upload: Subir
|
||||
permalink: Link permanente
|
||||
success:
|
||||
linkCopied: ¡Link copiado!
|
||||
errors:
|
||||
forbidden: No eres bienvenido aquí.
|
||||
internal: La verdad es que algo ha ido mal.
|
||||
notFound: No se puede acceder a este lugar.
|
||||
files:
|
||||
folders: Carpetas
|
||||
files: Archivos
|
||||
body: Cuerpo
|
||||
clear: Limpiar
|
||||
closePreview: Cerrar vista previa
|
||||
home: Inicio
|
||||
lastModified: Última modificación
|
||||
loading: Cargando...
|
||||
lonely: Uno se siente muy sólo aquí...
|
||||
metadata: Metadatos
|
||||
multipleSelectionEnabled: Selección múltiple activada
|
||||
name: Nombre
|
||||
size: Tamaño
|
||||
sortByName: Ordenar por nombre
|
||||
sortBySize: Ordenar por tamaño
|
||||
sortByLastModified: Ordenar por última modificación
|
||||
help:
|
||||
click: seleccionar archivo o carpeta
|
||||
ctrl:
|
||||
click: seleccionar múltiples archivos o carpetas
|
||||
f: abre la búsqueda
|
||||
s: guarda un archivo o lo descarga a la carpeta en la que estás
|
||||
del: elimina los items seleccionados
|
||||
doubleClick: abre un archivo o carpeta
|
||||
esc: limpia la selección y/o cierra la ventana
|
||||
f1: esta información
|
||||
f2: renombrar archivo
|
||||
help: Ayuda
|
||||
login:
|
||||
password: Contraseña
|
||||
submit: Iniciar sesión
|
||||
username: Usuario
|
||||
wrongCredentials: Usuario y/o contraseña incorrectos
|
||||
prompts:
|
||||
copy: Copiar
|
||||
copyMessage: 'Elige el lugar donde quieres copiar tus archivos:'
|
||||
currentlyNavigating: 'Actualmente estás en:'
|
||||
deleteMessageMultiple: ¿Estás seguro que quieres eliminar {count} archivo(s)?
|
||||
deleteMessageSingle: ¿Estás seguro que quieres eliminar este archivo/carpeta?
|
||||
deleteTitle: Borrar archivos
|
||||
displayName: 'Nombre:'
|
||||
download: Descargar archivos
|
||||
downloadMessage: Elige el formato de descarga.
|
||||
error: Algo ha fallado
|
||||
fileInfo: Información del archivo
|
||||
filesSelected: "{count} archivos seleccionados."
|
||||
lastModified: Última modificación
|
||||
move: Mover
|
||||
moveMessage: 'Elige una nueva casa para tus archivo(s)/carpeta(s):'
|
||||
newDir: Nueva carpeta
|
||||
newDirMessage: Escribe el nombre de la nueva carpeta.
|
||||
newFile: Nuevo archivo
|
||||
newFileMessage: Escribe el nombre del nuevo archivo.
|
||||
numberDirs: Número de carpetas
|
||||
numberFiles: Número de archivos
|
||||
replace: Reemplazar
|
||||
replaceMessage: >
|
||||
Uno de los archivos ue intentas subir está creando conflicto por su nombre.
|
||||
¿Quieres cambiar el nombre del ya existente?
|
||||
rename: Renombrar
|
||||
renameMessage: Escribe el nuevo nombre para
|
||||
show: Mostrar
|
||||
size: Tamaño
|
||||
schedule: Programar
|
||||
scheduleMessage: Elige una hora y fecha para programar la publicación de este post.
|
||||
newArchetype: Crea un nuevo post basado en un arquetipo. Tu archivo será creado en la carpeta de contenido.
|
||||
settings:
|
||||
admin: Admin
|
||||
administrator: Administrador
|
||||
allowCommands: Ejecutar comandos
|
||||
allowEdit: Editar, renombrar y borrar archivos o carpetas
|
||||
allowNew: Crear nuevos archivos y carpetas
|
||||
allowPublish: Publicar nuevos posts y páginas
|
||||
avoidChanges: "(dejar en blanco para evitar cambios)"
|
||||
changePassword: Cambiar contraseña
|
||||
commands: Comandos
|
||||
commandsHelp: >
|
||||
Aquí puedes crear comandos que serán ejecutados en los eventos. Debes
|
||||
escribir un comando por linea. Si el evento está relacionado con archivos, como
|
||||
por ejemplo, antes y después de guardar, la variable de entorno "FILE" estará
|
||||
disponible en la ruta del archivo.
|
||||
commandsUpdated: ¡Comandos actualizados!
|
||||
customStylesheet: Modificar hoja de estilos
|
||||
examples: Ejemplos
|
||||
globalSettings: Ajustes globales
|
||||
language: Idioma
|
||||
lockPassword: Evitar que el usuario cambie la contraseña
|
||||
newPassword: Tu nueva contraseña
|
||||
newPasswordConfirm: Confirma tu contraseña
|
||||
newUser: Nuevo usuario
|
||||
password: Contraseña
|
||||
passwordUpdated: ¡Contraseña actualizada!
|
||||
permissions: Permisos
|
||||
permissionsHelp: >
|
||||
Puedes nombrar al usuario como administrador o elegir los permisos
|
||||
individualmente. Si seleccionas "Administrador", todas las otras opciones
|
||||
serán activadas automáticamente. La administración de usuarios es un privilegio de administrador.
|
||||
profileSettings: Ajustes del perfil
|
||||
ruleExample1: >
|
||||
previene el acceso a una extensión de archivo (Como .git) en
|
||||
cada carpeta.
|
||||
ruleExample2: bloquea el acceso al archivo llamado Caddyfile en la carpeta raíz.
|
||||
rules: Reglas
|
||||
rulesHelp1: >
|
||||
Aquí puedes definir un conjunto de reglas de permisos para este usuario
|
||||
específico. Los archivos bloqueados no se mostrarán en las listas y no serán accesibles
|
||||
por el usuario. Puedes utilizar regex y rutas relativas a la raíz del usuario.
|
||||
rulesHelp2: >
|
||||
Cada regla va en una línea diferente, y debe comenzar con la palabra clave
|
||||
{0} or {1}. Entonces, debes escribir {2} si estás usando una expresión regular (REGEX) y
|
||||
luego la expresión o la ruta.
|
||||
scope: Raíz
|
||||
settingsUpdated: ¡Ajustes actualizados!
|
||||
user: Usuario
|
||||
userCommands: Comandos
|
||||
userCommandsHelp: >
|
||||
Una lista separada por espacios con los comandos permitidos para este usuario.
|
||||
Ejemplo:
|
||||
userCreated: ¡Usuario creado!
|
||||
userDeleted: ¡Usuario eliminado!
|
||||
userManagement: Administración de usuarios
|
||||
username: Usuario
|
||||
users: Usuarios
|
||||
userUpdated: ¡Usuario actualizado!
|
||||
sidebar:
|
||||
help: Ayuda
|
||||
logout: Cerrar sesión
|
||||
myFiles: Mis archivos
|
||||
newFile: Nuevo archivo
|
||||
newFolder: Nueva carpeta
|
||||
settings: Ajustes
|
||||
siteSettings: Ajustes del sitio
|
||||
hugoNew: Nuevo Hugo
|
||||
preview: Vista previa
|
||||
search:
|
||||
images: Images
|
||||
music: Música
|
||||
pdf: PDF
|
||||
pressToExecute: Presiona enter para ejecutar.
|
||||
pressToSearch: Presiona enter para buscar.
|
||||
search: Buscar...
|
||||
searchOrCommand: Buscar o ejecutar un comando...
|
||||
searchOrSupportedCommand: 'Buscar o ejecutar uno de los comandos soportados:'
|
||||
type: Escribe y presiona enter para buscar.
|
||||
types: Tipos
|
||||
video: Vídeo
|
||||
writeToSearch: Escribe aquí para buscar
|
||||
languages:
|
||||
en: English
|
||||
fr: Français
|
||||
pt: Português
|
||||
es: Español
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
|
||||
time:
|
||||
unit: Unidad
|
||||
seconds: Segundos
|
||||
minutes: Minutos
|
||||
hours: Horas
|
||||
days: Días
|
||||
@@ -185,6 +185,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
time:
|
||||
unit: Unité de temps
|
||||
seconds: Secondes
|
||||
|
||||
@@ -6,6 +6,7 @@ import pt from './pt.yaml'
|
||||
import ja from './ja.yaml'
|
||||
import zhCN from './zh-cn.yaml'
|
||||
import zhTW from './zh-tw.yaml'
|
||||
import es from './es.yaml'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
@@ -33,6 +34,9 @@ export function detectLocale () {
|
||||
case /^zh.*/i.test(locale):
|
||||
locale = 'zh-cn'
|
||||
break
|
||||
case /^es.*/i.test(locale):
|
||||
locale = 'es'
|
||||
break
|
||||
default:
|
||||
locale = 'en'
|
||||
}
|
||||
@@ -49,7 +53,8 @@ const i18n = new VueI18n({
|
||||
'pt': pt,
|
||||
'ja': ja,
|
||||
'zh-cn': zhCN,
|
||||
'zh-tw': zhTW
|
||||
'zh-tw': zhTW,
|
||||
'es': es
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
time:
|
||||
unit: 時間単位
|
||||
seconds: 秒
|
||||
|
||||
@@ -73,6 +73,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
login:
|
||||
password: Palavra-passe
|
||||
submit: Login
|
||||
|
||||
@@ -190,6 +190,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
time:
|
||||
unit: 时间单位
|
||||
seconds: 秒
|
||||
|
||||
@@ -190,6 +190,7 @@ languages:
|
||||
ja: 日本語
|
||||
zhCN: 中文 (简体)
|
||||
zhTW: 中文 (繁體)
|
||||
es: Español
|
||||
time:
|
||||
unit: 時間單位
|
||||
seconds: 秒
|
||||
|
||||
@@ -3,8 +3,8 @@ import Router from 'vue-router'
|
||||
import Login from '@/views/Login'
|
||||
import Layout from '@/views/Layout'
|
||||
import Files from '@/views/Files'
|
||||
import Users from '@/views/Settings/Users'
|
||||
import User from '@/views/Settings/User'
|
||||
import Users from '@/views/settings/Users'
|
||||
import User from '@/views/settings/User'
|
||||
import Settings from '@/views/Settings'
|
||||
import GlobalSettings from '@/views/settings/Global'
|
||||
import ProfileSettings from '@/views/settings/Profile'
|
||||
|
||||
@@ -1,231 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="breadcrumbs">
|
||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
|
||||
<span v-for="link in breadcrumbs" :key="link.name">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||
<preview v-else-if="isPreview"></preview>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
import Preview from '@/components/files/Preview'
|
||||
import Listing from '@/components/files/Listing'
|
||||
import Editor from '@/components/files/Editor'
|
||||
import * as api from '@/utils/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'files',
|
||||
components: {
|
||||
Forbidden,
|
||||
NotFound,
|
||||
InternalError,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'selectedCount'
|
||||
]),
|
||||
...mapState([
|
||||
'req',
|
||||
'user',
|
||||
'reload',
|
||||
'multiple',
|
||||
'loading'
|
||||
]),
|
||||
isListing () {
|
||||
return this.req.kind === 'listing' && !this.loading
|
||||
},
|
||||
isPreview () {
|
||||
return this.req.kind === 'preview' && !this.loading
|
||||
},
|
||||
isEditor () {
|
||||
return this.req.kind === 'editor' && !this.loading
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
breadcrumbs.shift()
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'reload': function () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setLoading' ]),
|
||||
fetchData () {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
api.fetch(url)
|
||||
.then((req) => {
|
||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||
}
|
||||
|
||||
this.$store.commit('updateRequest', req)
|
||||
document.title = req.name
|
||||
this.setLoading(false)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setLoading(false)
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
keyEvent (event) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// If we're on a listing, unselect all
|
||||
// files and folders.
|
||||
if (this.req.kind === 'listing') {
|
||||
this.$store.commit('resetSelected')
|
||||
}
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0)) return
|
||||
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('showHover', 'help')
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0) ||
|
||||
(this.req.kind === 'listing' && this.selectedCount > 1)) return
|
||||
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
|
||||
// CTRL + S
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.req.kind !== 'editor') {
|
||||
document.getElementById('download-button').click()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll (event) {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.req.display === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
|
||||
if (top < 64) {
|
||||
top = 64
|
||||
}
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
},
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div id="breadcrumbs">
|
||||
<router-link to="/files/" :aria-label="$t('files.home')" :title="$t('files.home')">
|
||||
<i class="material-icons">home</i>
|
||||
</router-link>
|
||||
|
||||
<span v-for="link in breadcrumbs" :key="link.name">
|
||||
<span class="chevron"><i class="material-icons">keyboard_arrow_right</i></span>
|
||||
<router-link :to="link.url">{{ link.name }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="error">
|
||||
<not-found v-if="error.message === '404'"></not-found>
|
||||
<forbidden v-else-if="error.message === '403'"></forbidden>
|
||||
<internal-error v-else></internal-error>
|
||||
</div>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||
<preview v-else-if="isPreview"></preview>
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Forbidden from './errors/403'
|
||||
import NotFound from './errors/404'
|
||||
import InternalError from './errors/500'
|
||||
import Preview from '@/components/files/Preview'
|
||||
import Listing from '@/components/files/Listing'
|
||||
import Editor from '@/components/files/Editor'
|
||||
import * as api from '@/utils/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'files',
|
||||
components: {
|
||||
Forbidden,
|
||||
NotFound,
|
||||
InternalError,
|
||||
Preview,
|
||||
Listing,
|
||||
Editor
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'selectedCount'
|
||||
]),
|
||||
...mapState([
|
||||
'req',
|
||||
'user',
|
||||
'reload',
|
||||
'multiple',
|
||||
'loading'
|
||||
]),
|
||||
isListing () {
|
||||
return this.req.kind === 'listing' && !this.loading
|
||||
},
|
||||
isPreview () {
|
||||
return this.req.kind === 'preview' && !this.loading
|
||||
},
|
||||
isEditor () {
|
||||
return this.req.kind === 'editor' && !this.loading
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
}
|
||||
|
||||
if (parts[parts.length - 1] === '') {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: '/' + parts[i] + '/' })
|
||||
} else {
|
||||
breadcrumbs.push({ name: decodeURIComponent(parts[i]), url: breadcrumbs[i - 1].url + parts[i] + '/' })
|
||||
}
|
||||
}
|
||||
|
||||
breadcrumbs.shift()
|
||||
|
||||
if (breadcrumbs.length > 3) {
|
||||
while (breadcrumbs.length !== 4) {
|
||||
breadcrumbs.shift()
|
||||
}
|
||||
|
||||
breadcrumbs[0].name = '...'
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
error: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'reload': function () {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setLoading' ]),
|
||||
fetchData () {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// Set loading to true and reset the error.
|
||||
this.setLoading(true)
|
||||
this.error = null
|
||||
|
||||
let url = this.$route.path
|
||||
if (url === '') url = '/'
|
||||
if (url[0] !== '/') url = '/' + url
|
||||
|
||||
api.fetch(url)
|
||||
.then((req) => {
|
||||
if (!url.endsWith('/') && req.url.endsWith('/')) {
|
||||
window.history.replaceState(window.history.state, document.title, window.location.pathname + '/')
|
||||
}
|
||||
|
||||
this.$store.commit('updateRequest', req)
|
||||
document.title = req.name
|
||||
this.setLoading(false)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setLoading(false)
|
||||
this.error = error
|
||||
})
|
||||
},
|
||||
keyEvent (event) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit('closeHovers')
|
||||
|
||||
// If we're on a listing, unselect all
|
||||
// files and folders.
|
||||
if (this.req.kind === 'listing') {
|
||||
this.$store.commit('resetSelected')
|
||||
}
|
||||
}
|
||||
|
||||
// Del!
|
||||
if (event.keyCode === 46) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0)) return
|
||||
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
|
||||
// F1!
|
||||
if (event.keyCode === 112) {
|
||||
event.preventDefault()
|
||||
this.$store.commit('showHover', 'help')
|
||||
}
|
||||
|
||||
// F2!
|
||||
if (event.keyCode === 113) {
|
||||
if (this.req.kind === 'editor' ||
|
||||
this.$route.name !== 'Files' ||
|
||||
this.loading ||
|
||||
!this.user.allowEdit ||
|
||||
(this.req.kind === 'listing' && this.selectedCount === 0) ||
|
||||
(this.req.kind === 'listing' && this.selectedCount > 1)) return
|
||||
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
|
||||
// CTRL + S
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
if (String.fromCharCode(event.which).toLowerCase() === 's') {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.req.kind !== 'editor') {
|
||||
document.getElementById('download-button').click()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll (event) {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.user.viewMode === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
|
||||
if (top < 64) {
|
||||
top = 64
|
||||
}
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
},
|
||||
openSidebar () {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openSearch () {
|
||||
this.$store.commit('showHover', 'search')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,21 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p>
|
||||
<label for="username">{{ $t('settings.username') }}</label>
|
||||
<input type="text" v-model="username" id="username">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="password">{{ $t('settings.password') }}</label>
|
||||
<input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="scope">{{ $t('settings.scope') }}</label>
|
||||
<input type="text" v-model="filesystem" id="scope">
|
||||
</p>
|
||||
|
||||
<p><label for="username">{{ $t('settings.username') }}</label><input type="text" v-model="username" id="username"></p>
|
||||
<p><label for="password">{{ $t('settings.password') }}</label><input type="password" :placeholder="passwordPlaceholder" v-model="password" id="password"></p>
|
||||
<p><label for="scope">{{ $t('settings.scope') }}</label><input type="text" v-model="filesystem" id="scope"></p>
|
||||
<p>
|
||||
<label for="locale">{{ $t('settings.language') }}</label>
|
||||
<languages id="locale" :selected.sync="locale"></languages>
|
||||
@@ -91,6 +102,7 @@ export default {
|
||||
components: { Languages },
|
||||
data: () => {
|
||||
return {
|
||||
originalUser: null,
|
||||
id: 0,
|
||||
admin: false,
|
||||
allowNew: false,
|
||||
@@ -141,6 +153,7 @@ export default {
|
||||
}
|
||||
|
||||
getUser(user).then(user => {
|
||||
this.originalUser = user
|
||||
this.id = user.ID
|
||||
this.admin = user.admin
|
||||
this.allowCommands = user.allowCommands
|
||||
@@ -242,23 +255,21 @@ export default {
|
||||
})
|
||||
},
|
||||
parseForm () {
|
||||
let user = {
|
||||
ID: this.id,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
lockPassword: this.lockPassword,
|
||||
filesystem: this.filesystem,
|
||||
admin: this.admin,
|
||||
allowCommands: this.allowCommands,
|
||||
allowNew: this.allowNew,
|
||||
allowEdit: this.allowEdit,
|
||||
allowPublish: this.allowPublish,
|
||||
permissions: this.permissions,
|
||||
css: this.css,
|
||||
locale: this.locale,
|
||||
commands: this.commands.split(' '),
|
||||
rules: []
|
||||
}
|
||||
let user = this.originalUser
|
||||
user.username = this.username
|
||||
user.password = this.password
|
||||
user.lockPassword = this.lockPassword
|
||||
user.filesystem = this.filesystem
|
||||
user.admin = this.admin
|
||||
user.allowCommands = this.allowCommands
|
||||
user.allowNew = this.allowNew
|
||||
user.allowEdit = this.allowEdit
|
||||
user.allowPublish = this.allowPublish
|
||||
user.permissions = this.permissions
|
||||
user.css = this.css
|
||||
user.locale = this.locale
|
||||
user.commands = this.commands.split(' ')
|
||||
user.rules = []
|
||||
|
||||
let rules = this.rules.split('\n')
|
||||
|
||||
|
||||
68
file.go
68
file.go
@@ -133,6 +133,16 @@ func (i *File) GetListing(u *User, r *http.Request) error {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(f.Mode().String(), "L") {
|
||||
// It's a symbolic link
|
||||
// The FileInfo from Readdir treats symbolic link as a file only.
|
||||
info, err := os.Stat(f.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f = info
|
||||
}
|
||||
|
||||
if f.IsDir() {
|
||||
name += "/"
|
||||
dirCount++
|
||||
@@ -187,7 +197,6 @@ func (i *File) GetEditor() error {
|
||||
// If there is an error, just ignore it and return nil.
|
||||
// This way, the file can be served for editing.
|
||||
if err != nil {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -251,15 +260,12 @@ func (i *File) GetFileType(checkContent bool) error {
|
||||
|
||||
// If the type isn't text (and is blob for example), it will check some
|
||||
// common types that are mistaken not to be text.
|
||||
for _, extension := range textExtensions {
|
||||
if strings.HasSuffix(i.Name, extension) {
|
||||
i.Type = "text"
|
||||
goto End
|
||||
}
|
||||
if isInTextExtensions(i.Name) {
|
||||
i.Type = "text"
|
||||
} else {
|
||||
i.Type = "blob"
|
||||
}
|
||||
|
||||
i.Type = "blob"
|
||||
|
||||
End:
|
||||
// If the file type is text, save its content.
|
||||
if i.Type == "text" {
|
||||
@@ -405,22 +411,38 @@ func (l byModified) Less(i, j int) bool {
|
||||
return iModified.Sub(jModified) < 0
|
||||
}
|
||||
|
||||
var textExtensions = [...]string{
|
||||
".md", ".markdown", ".mdown", ".mmark",
|
||||
".asciidoc", ".adoc", ".ad",
|
||||
".rst",
|
||||
".json", ".toml", ".yaml", ".csv", ".xml", ".rss", ".conf", ".ini",
|
||||
".tex", ".sty",
|
||||
".css", ".sass", ".scss",
|
||||
".js",
|
||||
".html",
|
||||
".txt", ".rtf",
|
||||
".sh", ".bash", ".ps1", ".bat", ".cmd",
|
||||
".php", ".pl", ".py",
|
||||
// textExtensions is the sorted list of text extensions which
|
||||
// can be edited.
|
||||
var textExtensions = []string{
|
||||
".ad", ".ada", ".adoc", ".asciidoc",
|
||||
".bas", ".bash", ".bat",
|
||||
".c", ".cc", ".cmd", ".conf", ".cpp", ".cr", ".cs", ".css", ".csv",
|
||||
".d",
|
||||
".f", ".f90",
|
||||
".h", ".hh", ".hpp", ".htaccess", ".html",
|
||||
".ini",
|
||||
".java", ".js", ".json",
|
||||
".markdown", ".md", ".mdown", ".mmark",
|
||||
".nim",
|
||||
".php", ".pl", ".ps1", ".py",
|
||||
".rss", ".rst", ".rtf",
|
||||
".sass", ".scss", ".sh", ".sty",
|
||||
".tex", ".tml", ".toml", ".txt",
|
||||
".vala", ".vapi",
|
||||
".xml",
|
||||
".yaml", ".yml",
|
||||
"Caddyfile",
|
||||
".htaccess",
|
||||
".c", ".cc", ".h", ".hh", ".cpp", ".hpp", ".f90",
|
||||
".f", ".bas", ".d", ".ada", ".nim", ".cr", ".java", ".cs", ".vala", ".vapi",
|
||||
}
|
||||
|
||||
// isInTextExtensions checks if a file can be edited by its extensions.
|
||||
func isInTextExtensions(name string) bool {
|
||||
search := filepath.Ext(name)
|
||||
if search == "" {
|
||||
search = name
|
||||
}
|
||||
|
||||
i := sort.SearchStrings(textExtensions, search)
|
||||
return i < len(textExtensions) && textExtensions[i] == search
|
||||
}
|
||||
|
||||
// hasRune checks if the file has the frontmatter rune
|
||||
|
||||
@@ -21,10 +21,9 @@ import (
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
// Version is the current File Manager version.
|
||||
const (
|
||||
// Version is the current File Manager version.
|
||||
Version = "1.3.8"
|
||||
Version = "1.3.12"
|
||||
|
||||
ListViewMode = "list"
|
||||
MosaicViewMode = "mosaic"
|
||||
@@ -39,7 +38,7 @@ var (
|
||||
ErrEmptyScope = errors.New("scope is empty")
|
||||
ErrWrongDataType = errors.New("wrong data type")
|
||||
ErrInvalidUpdateField = errors.New("invalid field to update")
|
||||
ErrInvalidOption = errors.New("Invalid option")
|
||||
ErrInvalidOption = errors.New("invalid option")
|
||||
)
|
||||
|
||||
// FileManager is a file manager instance. It should be creating using the
|
||||
|
||||
21
http/auth.go
21
http/auth.go
@@ -1,9 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -14,22 +12,23 @@ import (
|
||||
fm "github.com/hacdias/filemanager"
|
||||
)
|
||||
|
||||
const reCaptchaAPI = "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
type cred struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
Recaptcha string `json:"recaptcha"`
|
||||
ReCaptcha string `json:"recaptcha"`
|
||||
}
|
||||
|
||||
// recaptcha checks the recaptcha code.
|
||||
func recaptcha(secret string, response string) (bool, error) {
|
||||
api := "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
// reCaptcha checks the reCaptcha code.
|
||||
func reCaptcha(secret string, response string) (bool, error) {
|
||||
body := url.Values{}
|
||||
body.Set("secret", secret)
|
||||
body.Add("response", response)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Post(api, "application/x-www-form-urlencoded", bytes.NewBufferString(body.Encode()))
|
||||
|
||||
resp, err := client.Post(reCaptchaAPI, "application/x-www-form-urlencoded", strings.NewReader(body.Encode()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -53,7 +52,7 @@ func recaptcha(secret string, response string) (bool, error) {
|
||||
return data.Success, nil
|
||||
}
|
||||
|
||||
// authHandler proccesses the authentication for the user.
|
||||
// authHandler processes the authentication for the user.
|
||||
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// NoAuth instances shouldn't call this method.
|
||||
if c.NoAuth {
|
||||
@@ -73,9 +72,8 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
|
||||
|
||||
// If ReCaptcha is enabled, check the code.
|
||||
if len(c.ReCaptchaSecret) > 0 {
|
||||
ok, err := recaptcha(c.ReCaptchaSecret, cred.Recaptcha)
|
||||
ok, err := reCaptcha(c.ReCaptchaSecret, cred.ReCaptcha)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
|
||||
@@ -179,6 +177,7 @@ func validateAuth(c *fm.Context, r *http.Request) (bool, *fm.User) {
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return c.Key, nil
|
||||
}
|
||||
|
||||
var claims claims
|
||||
token, err := request.ParseFromRequestWithClaims(r,
|
||||
extractor{},
|
||||
|
||||
@@ -17,21 +17,13 @@ import (
|
||||
// downloadHandler creates an archive in one of the supported formats (zip, tar,
|
||||
// tar.gz or tar.bz2) and sends it to be downloaded.
|
||||
func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
query := r.URL.Query().Get("format")
|
||||
|
||||
// If the file isn't a directory, serve it using http.ServeFile. We display it
|
||||
// inline if it is requested.
|
||||
if !c.File.IsDir {
|
||||
if r.URL.Query().Get("inline") == "true" {
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
} else {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=\""+c.File.Name+"\"")
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, c.File.Path)
|
||||
return 0, nil
|
||||
return downloadFileHandler(c, w, r)
|
||||
}
|
||||
|
||||
query := r.URL.Query().Get("format")
|
||||
files := []string{}
|
||||
names := strings.Split(r.URL.Query().Get("files"), ",")
|
||||
|
||||
@@ -111,3 +103,25 @@ func downloadHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
||||
_, err = io.Copy(w, file)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func downloadFileHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Query().Get("inline") == "true" {
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
} else {
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="`+c.File.Name+`"`)
|
||||
}
|
||||
|
||||
file, err := os.Open(c.File.Path)
|
||||
defer file.Close()
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(w, file)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
@@ -160,6 +160,11 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
|
||||
u.Rules = []*fm.Rule{}
|
||||
}
|
||||
|
||||
// If the view mode is empty, initialize with the default one.
|
||||
if u.ViewMode == "" {
|
||||
u.ViewMode = c.DefaultUser.ViewMode
|
||||
}
|
||||
|
||||
// Initialize commands if not initialized.
|
||||
if u.Commands == nil {
|
||||
u.Commands = []string{}
|
||||
|
||||
9161
package-lock.json
generated
9161
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
package.json
94
package.json
@@ -10,62 +10,62 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^1.7.1",
|
||||
"codemirror": "^5.27.4",
|
||||
"filesize": "^3.5.10",
|
||||
"moment": "^2.18.1",
|
||||
"codemirror": "^5.31.0",
|
||||
"filesize": "^3.5.11",
|
||||
"moment": "^2.19.2",
|
||||
"normalize.css": "^7.0.0",
|
||||
"noty": "^3.1.2",
|
||||
"vue": "^2.3.3",
|
||||
"vue-i18n": "^7.1.0",
|
||||
"vue-router": "^2.7.0",
|
||||
"vuex": "^2.3.1"
|
||||
"noty": "^3.1.3",
|
||||
"vue": "^2.5.8",
|
||||
"vue-i18n": "^7.3.2",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^7.1.1",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"chalk": "^2.0.1",
|
||||
"connect-history-api-fallback": "^1.3.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"eslint": "^4.3.0",
|
||||
"autoprefixer": "^7.1.6",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.0.2",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"babel-register": "^6.26.0",
|
||||
"chalk": "^2.3.0",
|
||||
"connect-history-api-fallback": "^1.5.0",
|
||||
"copy-webpack-plugin": "^4.2.1",
|
||||
"css-loader": "^0.28.7",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-friendly-formatter": "^3.0.0",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-html": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.1.1",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-loader": "^1.9.0",
|
||||
"eslint-plugin-html": "^4.0.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-node": "^5.2.1",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.14.1",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-proxy-middleware": "^0.17.3",
|
||||
"express": "^4.16.2",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.5",
|
||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"http-proxy-middleware": "^0.17.4",
|
||||
"opn": "^5.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^3.0.0",
|
||||
"ora": "^1.2.0",
|
||||
"rimraf": "^2.6.0",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||
"ora": "^1.3.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"semver": "^5.4.1",
|
||||
"shelljs": "^0.7.8",
|
||||
"sw-precache-webpack-plugin": "^0.11.4",
|
||||
"uglify-js": "^3.0.23",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^13.0.2",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.3.3",
|
||||
"webpack": "^3.4.1",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.0",
|
||||
"webpack-hot-middleware": "^2.18.0",
|
||||
"webpack-merge": "^4.1.0",
|
||||
"uglify-js": "^3.1.10",
|
||||
"url-loader": "^0.6.2",
|
||||
"vue-loader": "^13.5.0",
|
||||
"vue-style-loader": "^3.0.3",
|
||||
"vue-template-compiler": "^2.5.8",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-bundle-analyzer": "^2.9.1",
|
||||
"webpack-dev-middleware": "^1.12.0",
|
||||
"webpack-hot-middleware": "^2.20.0",
|
||||
"webpack-merge": "^4.1.1",
|
||||
"yml-loader": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
9536ea589ebfb34b4ccbf549776ff8b4c70d6dd6
|
||||
b7c6fe9138fe86f6f8c6931518f2e7a5863ead6c
|
||||
@@ -84,7 +84,8 @@ func (h Hugo) Hook(c *fm.Context, w http.ResponseWriter, r *http.Request) (int,
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
filename := filepath.Join(c.User.Scope, r.URL.Path)
|
||||
filename := filepath.Clean(r.URL.Path)
|
||||
filename = strings.TrimPrefix(filename, string(filepath.Separator))
|
||||
archetype := r.Header.Get("archetype")
|
||||
|
||||
ext := filepath.Ext(filename)
|
||||
|
||||
Reference in New Issue
Block a user