feat:Support compress image view
This commit is contained in:
parent
6d899a6335
commit
aadfce0e65
198
frontend/.eslintrc.js
Normal file
198
frontend/.eslintrc.js
Normal file
@ -0,0 +1,198 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
|
||||
// add your custom rules here
|
||||
// it is base on https://github.com/vuejs/eslint-config-vue
|
||||
rules: {
|
||||
'vue/max-attributes-per-line': [2, {
|
||||
'singleline': 10,
|
||||
'multiline': {
|
||||
'max': 1,
|
||||
'allowFirstLine': false
|
||||
}
|
||||
}],
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/name-property-casing': ['error', 'PascalCase'],
|
||||
'vue/no-v-html': 'off',
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'block-spacing': [2, 'always'],
|
||||
'brace-style': [2, '1tbs', {
|
||||
'allowSingleLine': true
|
||||
}],
|
||||
'camelcase': [0, {
|
||||
'properties': 'always'
|
||||
}],
|
||||
'comma-dangle': [2, 'never'],
|
||||
'comma-spacing': [2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'comma-style': [2, 'last'],
|
||||
'constructor-super': 2,
|
||||
'curly': [2, 'multi-line'],
|
||||
'dot-location': [2, 'property'],
|
||||
'eol-last': 2,
|
||||
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
|
||||
'generator-star-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'handle-callback-err': [2, '^(err|error)$'],
|
||||
'indent': [2, 2, {
|
||||
'SwitchCase': 1
|
||||
}],
|
||||
'jsx-quotes': [2, 'prefer-single'],
|
||||
'key-spacing': [2, {
|
||||
'beforeColon': false,
|
||||
'afterColon': true
|
||||
}],
|
||||
'keyword-spacing': [2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'new-cap': [2, {
|
||||
'newIsCap': true,
|
||||
'capIsNew': false
|
||||
}],
|
||||
'new-parens': 2,
|
||||
'no-array-constructor': 2,
|
||||
'no-caller': 2,
|
||||
'no-console': 'off',
|
||||
'no-class-assign': 2,
|
||||
'no-cond-assign': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-control-regex': 0,
|
||||
'no-delete-var': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-class-members': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty-character-class': 2,
|
||||
'no-empty-pattern': 2,
|
||||
'no-eval': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-parens': [2, 'functions'],
|
||||
'no-fallthrough': 2,
|
||||
'no-floating-decimal': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-implied-eval': 2,
|
||||
'no-inner-declarations': [2, 'functions'],
|
||||
'no-invalid-regexp': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-iterator': 2,
|
||||
'no-label-var': 2,
|
||||
'no-labels': [2, {
|
||||
'allowLoop': false,
|
||||
'allowSwitch': false
|
||||
}],
|
||||
'no-lone-blocks': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [2, {
|
||||
'max': 1
|
||||
}],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new-object': 2,
|
||||
'no-new-require': 2,
|
||||
'no-new-symbol': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-octal': 2,
|
||||
'no-octal-escape': 2,
|
||||
'no-path-concat': 2,
|
||||
'no-proto': 2,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-return-assign': [2, 'except-parens'],
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-undef': 2,
|
||||
'no-undef-init': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unneeded-ternary': [2, {
|
||||
'defaultAssignment': false
|
||||
}],
|
||||
'no-unreachable': 2,
|
||||
'no-unsafe-finally': 2,
|
||||
'no-unused-vars': [2, {
|
||||
'vars': 'all',
|
||||
'args': 'none'
|
||||
}],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-constructor': 2,
|
||||
'no-useless-escape': 0,
|
||||
'no-whitespace-before-property': 2,
|
||||
'no-with': 2,
|
||||
'one-var': [2, {
|
||||
'initialized': 'never'
|
||||
}],
|
||||
'operator-linebreak': [2, 'after', {
|
||||
'overrides': {
|
||||
'?': 'before',
|
||||
':': 'before'
|
||||
}
|
||||
}],
|
||||
'padded-blocks': [2, 'never'],
|
||||
'quotes': [2, 'single', {
|
||||
'avoidEscape': true,
|
||||
'allowTemplateLiterals': true
|
||||
}],
|
||||
'semi': [2, 'never'],
|
||||
'semi-spacing': [2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'space-before-function-paren': [2, 'never'],
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-infix-ops': 2,
|
||||
'space-unary-ops': [2, {
|
||||
'words': true,
|
||||
'nonwords': false
|
||||
}],
|
||||
'spaced-comment': [2, 'always', {
|
||||
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
|
||||
}],
|
||||
'template-curly-spacing': [2, 'never'],
|
||||
'use-isnan': 2,
|
||||
'valid-typeof': 2,
|
||||
'wrap-iife': [2, 'any'],
|
||||
'yield-star-spacing': [2, 'both'],
|
||||
'yoda': [2, 'never'],
|
||||
'prefer-const': 2,
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
'object-curly-spacing': [2, 'always', {
|
||||
objectsInObjects: false
|
||||
}],
|
||||
'array-bracket-spacing': [2, 'never']
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'app',
|
||||
mounted () {
|
||||
name: 'App',
|
||||
mounted() {
|
||||
const loading = document.getElementById('loading')
|
||||
loading.classList.add('done')
|
||||
|
||||
setTimeout(function () {
|
||||
setTimeout(function() {
|
||||
loading.parentNode.removeChild(loading)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export default function command(url, command, onmessage, onclose) {
|
||||
url = removePrefix(url)
|
||||
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`
|
||||
|
||||
let conn = new window.WebSocket(url)
|
||||
const conn = new window.WebSocket(url)
|
||||
conn.onopen = () => conn.send(command)
|
||||
conn.onmessage = onmessage
|
||||
conn.onclose = onclose
|
||||
|
||||
@ -2,13 +2,13 @@ import { fetchURL, removePrefix } from './utils'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import store from '@/store'
|
||||
|
||||
export async function fetch (url) {
|
||||
export async function fetch(url) {
|
||||
url = removePrefix(url)
|
||||
|
||||
const res = await fetchURL(`/api/resources${url}`, {})
|
||||
|
||||
if (res.status === 200) {
|
||||
let data = await res.json()
|
||||
const data = await res.json()
|
||||
data.url = `/files${url}`
|
||||
|
||||
if (data.isDir) {
|
||||
@ -31,10 +31,10 @@ export async function fetch (url) {
|
||||
}
|
||||
}
|
||||
|
||||
async function resourceAction (url, method, content) {
|
||||
async function resourceAction(url, method, content) {
|
||||
url = removePrefix(url)
|
||||
|
||||
let opts = { method }
|
||||
const opts = { method }
|
||||
|
||||
if (content) {
|
||||
opts.body = content
|
||||
@ -49,15 +49,15 @@ async function resourceAction (url, method, content) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove (url) {
|
||||
export async function remove(url) {
|
||||
return resourceAction(url, 'DELETE')
|
||||
}
|
||||
|
||||
export async function put (url, content = '') {
|
||||
export async function put(url, content = '') {
|
||||
return resourceAction(url, 'PUT', content)
|
||||
}
|
||||
|
||||
export function download (format, ...files) {
|
||||
export function download(format, ...files) {
|
||||
let url = `${baseURL}/api/raw`
|
||||
|
||||
if (files.length === 1) {
|
||||
@ -65,7 +65,7 @@ export function download (format, ...files) {
|
||||
} else {
|
||||
let arg = ''
|
||||
|
||||
for (let file of files) {
|
||||
for (const file of files) {
|
||||
arg += removePrefix(file) + ','
|
||||
}
|
||||
|
||||
@ -82,11 +82,11 @@ export function download (format, ...files) {
|
||||
window.open(url)
|
||||
}
|
||||
|
||||
export async function post (url, content = '', overwrite = false, onupload) {
|
||||
export async function post(url, content = '', overwrite = false, onupload) {
|
||||
url = removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new XMLHttpRequest()
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
|
||||
request.setRequestHeader('X-Auth', store.state.jwt)
|
||||
|
||||
@ -95,7 +95,7 @@ export async function post (url, content = '', overwrite = false, onupload) {
|
||||
}
|
||||
|
||||
// Send a message to user before closing the tab during file upload
|
||||
window.onbeforeunload = () => "Files are being uploaded."
|
||||
window.onbeforeunload = () => 'Files are being uploaded.'
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
@ -116,10 +116,10 @@ export async function post (url, content = '', overwrite = false, onupload) {
|
||||
}).finally(() => { window.onbeforeunload = null })
|
||||
}
|
||||
|
||||
function moveCopy (items, copy = false) {
|
||||
let promises = []
|
||||
function moveCopy(items, copy = false) {
|
||||
const promises = []
|
||||
|
||||
for (let item of items) {
|
||||
for (const item of items) {
|
||||
const from = removePrefix(item.from)
|
||||
const to = encodeURIComponent(removePrefix(item.to))
|
||||
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}`
|
||||
@ -129,15 +129,15 @@ function moveCopy (items, copy = false) {
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
export function move (items) {
|
||||
export function move(items) {
|
||||
return moveCopy(items)
|
||||
}
|
||||
|
||||
export function copy (items) {
|
||||
export function copy(items) {
|
||||
return moveCopy(items, true)
|
||||
}
|
||||
|
||||
export async function checksum (url, algo) {
|
||||
export async function checksum(url, algo) {
|
||||
const data = await resourceAction(`${url}?checksum=${algo}`, 'GET')
|
||||
return (await data.json()).checksums[algo]
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { fetchJSON, removePrefix } from './utils'
|
||||
|
||||
export default async function search (url, query) {
|
||||
export default async function search(url, query) {
|
||||
url = removePrefix(url)
|
||||
query = encodeURIComponent(query)
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { fetchURL, fetchJSON } from './utils'
|
||||
|
||||
export function get () {
|
||||
export function get() {
|
||||
return fetchJSON(`/api/settings`, {})
|
||||
}
|
||||
|
||||
export async function update (settings) {
|
||||
export async function update(settings) {
|
||||
const res = await fetchURL(`/api/settings`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(settings)
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { fetchURL, fetchJSON } from './utils'
|
||||
|
||||
export async function getAll () {
|
||||
export async function getAll() {
|
||||
return fetchJSON(`/api/users`, {})
|
||||
}
|
||||
|
||||
export async function get (id) {
|
||||
export async function get(id) {
|
||||
return fetchJSON(`/api/users/${id}`, {})
|
||||
}
|
||||
|
||||
export async function create (user) {
|
||||
export async function create(user) {
|
||||
const res = await fetchURL(`/api/users`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
@ -23,10 +23,9 @@ export async function create (user) {
|
||||
} else {
|
||||
throw new Error(res.status)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function update (user, which = ['all']) {
|
||||
export async function update(user, which = ['all']) {
|
||||
const res = await fetchURL(`/api/users/${user.id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
@ -41,7 +40,7 @@ export async function update (user, which = ['all']) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove (id) {
|
||||
export async function remove(id) {
|
||||
const res = await fetchURL(`/api/users/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
@ -2,11 +2,11 @@ import store from '@/store'
|
||||
import { renew } from '@/utils/auth'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
|
||||
export async function fetchURL (url, opts) {
|
||||
export async function fetchURL(url, opts) {
|
||||
opts = opts || {}
|
||||
opts.headers = opts.headers || {}
|
||||
|
||||
let { headers, ...rest } = opts
|
||||
const { headers, ...rest } = opts
|
||||
|
||||
const res = await fetch(`${baseURL}${url}`, {
|
||||
headers: {
|
||||
@ -23,7 +23,7 @@ export async function fetchURL (url, opts) {
|
||||
return res
|
||||
}
|
||||
|
||||
export async function fetchJSON (url, opts) {
|
||||
export async function fetchJSON(url, opts) {
|
||||
const res = await fetchURL(url, opts)
|
||||
|
||||
if (res.status === 200) {
|
||||
@ -33,7 +33,7 @@ export async function fetchJSON (url, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
export function removePrefix (url) {
|
||||
export function removePrefix(url) {
|
||||
if (url.startsWith('/files')) {
|
||||
url = url.slice(6)
|
||||
}
|
||||
|
||||
@ -1,60 +1,60 @@
|
||||
<template>
|
||||
<header>
|
||||
<div>
|
||||
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
|
||||
<button :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action" @click="openSidebar">
|
||||
<i class="material-icons">menu</i>
|
||||
</button>
|
||||
<img :src="logoURL" alt="File Browser">
|
||||
<search v-if="isLogged"></search>
|
||||
<search v-if="isLogged" />
|
||||
</div>
|
||||
<div>
|
||||
<template v-if="isLogged">
|
||||
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
|
||||
<button :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action" @click="openSearch">
|
||||
<i class="material-icons">search</i>
|
||||
</button>
|
||||
|
||||
<button v-show="showSaveButton" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" class="action" id="save-button">
|
||||
<button v-show="showSaveButton" id="save-button" :aria-label="$t('buttons.save')" :title="$t('buttons.save')" class="action">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
|
||||
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
|
||||
<button id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action" @click="openMore">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
|
||||
<!-- Menu that shows on listing AND mobile when there are files selected -->
|
||||
<div id="file-selection" v-if="isMobile && isListing">
|
||||
<div v-if="isMobile && isListing" id="file-selection">
|
||||
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
|
||||
<share-button v-show="showShareButton"></share-button>
|
||||
<rename-button v-show="showRenameButton"></rename-button>
|
||||
<copy-button v-show="showCopyButton"></copy-button>
|
||||
<move-button v-show="showMoveButton"></move-button>
|
||||
<delete-button v-show="showDeleteButton"></delete-button>
|
||||
<share-button v-show="showShareButton" />
|
||||
<rename-button v-show="showRenameButton" />
|
||||
<copy-button v-show="showCopyButton" />
|
||||
<move-button v-show="showMoveButton" />
|
||||
<delete-button v-show="showDeleteButton" />
|
||||
</div>
|
||||
|
||||
<!-- This buttons are shown on a dropdown on mobile phones -->
|
||||
<div id="dropdown" :class="{ active: showMore }">
|
||||
<div v-if="!isListing || !isMobile">
|
||||
<share-button v-show="showShareButton"></share-button>
|
||||
<rename-button v-show="showRenameButton"></rename-button>
|
||||
<copy-button v-show="showCopyButton"></copy-button>
|
||||
<move-button v-show="showMoveButton"></move-button>
|
||||
<delete-button v-show="showDeleteButton"></delete-button>
|
||||
<share-button v-show="showShareButton" />
|
||||
<rename-button v-show="showRenameButton" />
|
||||
<copy-button v-show="showCopyButton" />
|
||||
<move-button v-show="showMoveButton" />
|
||||
<delete-button v-show="showDeleteButton" />
|
||||
</div>
|
||||
|
||||
<shell-button v-show="user.perm.execute" />
|
||||
<switch-button v-show="isListing"></switch-button>
|
||||
<download-button v-show="showDownloadButton"></download-button>
|
||||
<upload-button v-show="showUpload"></upload-button>
|
||||
<info-button v-show="isFiles"></info-button>
|
||||
<switch-button v-show="isListing" />
|
||||
<download-button v-show="showDownloadButton" />
|
||||
<upload-button v-show="showUpload" />
|
||||
<info-button v-show="isFiles" />
|
||||
|
||||
<button v-show="isListing" @click="toggleMultipleSelection" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" >
|
||||
<button v-show="isListing" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action" @click="toggleMultipleSelection">
|
||||
<i class="material-icons">check_circle</i>
|
||||
<span>{{ $t('buttons.select') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
<div v-show="showOverlay" class="overlay" @click="resetPrompts" />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@ -71,13 +71,13 @@ import MoveButton from './buttons/Move'
|
||||
import CopyButton from './buttons/Copy'
|
||||
import ShareButton from './buttons/Share'
|
||||
import ShellButton from './buttons/Shell'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { logoURL } from '@/utils/constants'
|
||||
import * as api from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'header-layout',
|
||||
name: 'HeaderLayout',
|
||||
components: {
|
||||
Search,
|
||||
InfoButton,
|
||||
@ -91,7 +91,7 @@ export default {
|
||||
MoveButton,
|
||||
ShellButton
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
pluginData: {
|
||||
@ -102,7 +102,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.width = window.innerWidth
|
||||
})
|
||||
@ -123,65 +123,65 @@ export default {
|
||||
'multiple'
|
||||
]),
|
||||
logoURL: () => logoURL,
|
||||
isMobile () {
|
||||
isMobile() {
|
||||
return this.width <= 736
|
||||
},
|
||||
showUpload () {
|
||||
showUpload() {
|
||||
return this.isListing && this.user.perm.create
|
||||
},
|
||||
showSaveButton () {
|
||||
showSaveButton() {
|
||||
return this.isEditor && this.user.perm.modify
|
||||
},
|
||||
showDownloadButton () {
|
||||
showDownloadButton() {
|
||||
return this.isFiles && this.user.perm.download
|
||||
},
|
||||
showDeleteButton () {
|
||||
showDeleteButton() {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount !== 0 && this.user.perm.delete)
|
||||
: this.user.perm.delete)
|
||||
},
|
||||
showRenameButton () {
|
||||
showRenameButton() {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount === 1 && this.user.perm.rename)
|
||||
: this.user.perm.rename)
|
||||
},
|
||||
showShareButton () {
|
||||
showShareButton() {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount === 1 && this.user.perm.share)
|
||||
: this.user.perm.share)
|
||||
},
|
||||
showMoveButton () {
|
||||
showMoveButton() {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount > 0 && this.user.perm.rename)
|
||||
: this.user.perm.rename)
|
||||
},
|
||||
showCopyButton () {
|
||||
showCopyButton() {
|
||||
return this.isFiles && (this.isListing
|
||||
? (this.selectedCount > 0 && this.user.perm.create)
|
||||
: this.user.perm.create)
|
||||
},
|
||||
showMore () {
|
||||
showMore() {
|
||||
return this.isFiles && this.$store.state.show === 'more'
|
||||
},
|
||||
showOverlay () {
|
||||
showOverlay() {
|
||||
return this.showMore
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSidebar () {
|
||||
openSidebar() {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openMore () {
|
||||
openMore() {
|
||||
this.$store.commit('showHover', 'more')
|
||||
},
|
||||
openSearch () {
|
||||
openSearch() {
|
||||
this.$store.commit('showHover', 'search')
|
||||
},
|
||||
toggleMultipleSelection () {
|
||||
toggleMultipleSelection() {
|
||||
this.$store.commit('multiple', !this.multiple)
|
||||
this.resetPrompts()
|
||||
},
|
||||
resetPrompts () {
|
||||
resetPrompts() {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
|
||||
<div id="search" :class="{ active , ongoing }" @click="open">
|
||||
<div id="input">
|
||||
<button
|
||||
v-if="active"
|
||||
class="action"
|
||||
@click="close"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')"
|
||||
@click="close"
|
||||
>
|
||||
<i class="material-icons">arrow_back</i>
|
||||
</button>
|
||||
<i v-else class="material-icons">search</i>
|
||||
<input
|
||||
type="text"
|
||||
@keyup.exact="keyup"
|
||||
@keyup.enter="submit"
|
||||
ref="input"
|
||||
:autofocus="active"
|
||||
v-model.trim="value"
|
||||
type="text"
|
||||
:autofocus="active"
|
||||
:aria-label="$t('search.search')"
|
||||
:placeholder="$t('search.search')"
|
||||
@keyup.exact="keyup"
|
||||
@keyup.enter="submit"
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -33,14 +33,14 @@
|
||||
<h3>{{ $t('search.types') }}</h3>
|
||||
<div>
|
||||
<div
|
||||
tabindex="0"
|
||||
v-for="(v,k) in boxes"
|
||||
:key="k"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@click="init('type:'+k)"
|
||||
:aria-label="$t('search.'+v.label)"
|
||||
@click="init('type:'+k)"
|
||||
>
|
||||
<i class="material-icons">{{v.icon}}</i>
|
||||
<i class="material-icons">{{ v.icon }}</i>
|
||||
<p>{{ $t('search.'+v.label) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -49,7 +49,7 @@
|
||||
</template>
|
||||
<ul v-show="results.length > 0">
|
||||
<li v-for="(s,k) in filteredResults" :key="k">
|
||||
<router-link @click.native="close" :to="'./' + s.path">
|
||||
<router-link :to="'./' + s.path" @click.native="close">
|
||||
<i v-if="s.dir" class="material-icons">folder</i>
|
||||
<i v-else class="material-icons">insert_drive_file</i>
|
||||
<span>./{{ s.path }}</span>
|
||||
@ -65,22 +65,22 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations } from "vuex"
|
||||
import url from "@/utils/url"
|
||||
import { search } from "@/api"
|
||||
import { mapState, mapGetters, mapMutations } from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import { search } from '@/api'
|
||||
|
||||
var boxes = {
|
||||
image: { label: "images", icon: "insert_photo" },
|
||||
audio: { label: "music", icon: "volume_up" },
|
||||
video: { label: "video", icon: "movie" },
|
||||
pdf: { label: "pdf", icon: "picture_as_pdf" }
|
||||
image: { label: 'images', icon: 'insert_photo' },
|
||||
audio: { label: 'music', icon: 'volume_up' },
|
||||
video: { label: 'video', icon: 'movie' },
|
||||
pdf: { label: 'pdf', icon: 'picture_as_pdf' }
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "search",
|
||||
name: 'Search',
|
||||
data: function() {
|
||||
return {
|
||||
value: "",
|
||||
value: '',
|
||||
active: false,
|
||||
ongoing: false,
|
||||
results: [],
|
||||
@ -90,15 +90,15 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show (val, old) {
|
||||
this.active = val === "search"
|
||||
show(val, old) {
|
||||
this.active = val === 'search'
|
||||
|
||||
if (old === "search" && !this.active) {
|
||||
if (old === 'search' && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true)
|
||||
}
|
||||
|
||||
document.body.style.overflow = "auto"
|
||||
document.body.style.overflow = 'auto'
|
||||
this.reset()
|
||||
this.value = ''
|
||||
this.active = false
|
||||
@ -106,18 +106,18 @@ export default {
|
||||
} else if (this.active) {
|
||||
this.reload = false
|
||||
this.$refs.input.focus()
|
||||
document.body.style.overflow = "hidden"
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
},
|
||||
value () {
|
||||
value() {
|
||||
if (this.results.length) {
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["user", "show"]),
|
||||
...mapGetters(["isListing"]),
|
||||
...mapState(['user', 'show']),
|
||||
...mapGetters(['isListing']),
|
||||
boxes() {
|
||||
return boxes
|
||||
},
|
||||
@ -126,17 +126,17 @@ export default {
|
||||
},
|
||||
text() {
|
||||
if (this.ongoing) {
|
||||
return ""
|
||||
return ''
|
||||
}
|
||||
|
||||
return this.value === '' ? this.$t("search.typeToSearch") : this.$t("search.pressToSearch")
|
||||
return this.value === '' ? this.$t('search.typeToSearch') : this.$t('search.pressToSearch')
|
||||
},
|
||||
filteredResults () {
|
||||
filteredResults() {
|
||||
return this.results.slice(0, this.resultsCount)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("keydown", event => {
|
||||
window.addEventListener('keydown', event => {
|
||||
if (event.keyCode === 27) {
|
||||
this.closeHovers()
|
||||
}
|
||||
@ -149,9 +149,9 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
||||
...mapMutations(['showHover', 'closeHovers', 'setReload']),
|
||||
open() {
|
||||
this.showHover("search")
|
||||
this.showHover('search')
|
||||
},
|
||||
close(event) {
|
||||
event.stopPropagation()
|
||||
@ -166,11 +166,11 @@ export default {
|
||||
|
||||
this.results.length = 0
|
||||
},
|
||||
init (string) {
|
||||
init(string) {
|
||||
this.value = `${string} `
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
reset () {
|
||||
reset() {
|
||||
this.ongoing = false
|
||||
this.resultsCount = 50
|
||||
this.results = []
|
||||
@ -184,12 +184,11 @@ export default {
|
||||
|
||||
let path = this.$route.path
|
||||
if (!this.isListing) {
|
||||
path = url.removeLastDir(path) + "/"
|
||||
path = url.removeLastDir(path) + '/'
|
||||
}
|
||||
|
||||
this.ongoing = true
|
||||
|
||||
|
||||
this.results = await search(path, this.value)
|
||||
this.ongoing = false
|
||||
}
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
<template>
|
||||
<div @click="focus" class="shell" ref="scrollable" :class="{ ['shell--hidden']: !showShell}">
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result" >
|
||||
<div ref="scrollable" class="shell" :class="{ ['shell--hidden']: !showShell}" @click="focus">
|
||||
<div v-for="(c, index) in content" :key="index" class="shell__result">
|
||||
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||
<pre class="shell__text">{{ c.text }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }" >
|
||||
<div class="shell__result" :class="{ 'shell__result--hidden': !canInput }">
|
||||
<div class="shell__prompt"><i class="material-icons">chevron_right</i></div>
|
||||
<pre
|
||||
tabindex="0"
|
||||
ref="input"
|
||||
tabindex="0"
|
||||
class="shell__text"
|
||||
contenteditable="true"
|
||||
@keydown.prevent.38="historyUp"
|
||||
@keydown.prevent.40="historyDown"
|
||||
@keypress.prevent.enter="submit" />
|
||||
@keypress.prevent.enter="submit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -24,11 +25,11 @@ import { mapMutations, mapState, mapGetters } from 'vuex'
|
||||
import { commands } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'shell',
|
||||
name: 'Shell',
|
||||
computed: {
|
||||
...mapState([ 'user', 'showShell' ]),
|
||||
...mapGetters([ 'isFiles', 'isLogged' ]),
|
||||
path: function () {
|
||||
...mapState(['user', 'showShell']),
|
||||
...mapGetters(['isFiles', 'isLogged']),
|
||||
path: function() {
|
||||
if (this.isFiles) {
|
||||
return this.$route.path
|
||||
}
|
||||
@ -43,20 +44,20 @@ export default {
|
||||
canInput: true
|
||||
}),
|
||||
methods: {
|
||||
...mapMutations([ 'toggleShell' ]),
|
||||
scroll: function () {
|
||||
...mapMutations(['toggleShell']),
|
||||
scroll: function() {
|
||||
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight
|
||||
},
|
||||
focus: function () {
|
||||
focus: function() {
|
||||
this.$refs.input.focus()
|
||||
},
|
||||
historyUp () {
|
||||
historyUp() {
|
||||
if (this.historyPos > 0) {
|
||||
this.$refs.input.innerText = this.history[--this.historyPos]
|
||||
this.focus()
|
||||
}
|
||||
},
|
||||
historyDown () {
|
||||
historyDown() {
|
||||
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
|
||||
this.$refs.input.innerText = this.history[++this.historyPos]
|
||||
this.focus()
|
||||
@ -65,7 +66,7 @@ export default {
|
||||
this.$refs.input.innerText = ''
|
||||
}
|
||||
},
|
||||
submit: function (event) {
|
||||
submit: function(event) {
|
||||
const cmd = event.target.innerText.trim()
|
||||
|
||||
if (cmd === '') {
|
||||
@ -87,7 +88,7 @@ export default {
|
||||
this.canInput = false
|
||||
event.target.innerHTML = ''
|
||||
|
||||
let results = {
|
||||
const results = {
|
||||
text: `${cmd}\n\n`
|
||||
}
|
||||
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
</router-link>
|
||||
|
||||
<div v-if="user.perm.create">
|
||||
<button @click="$store.commit('showHover', 'newDir')" class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')">
|
||||
<button class="action" :aria-label="$t('sidebar.newFolder')" :title="$t('sidebar.newFolder')" @click="$store.commit('showHover', 'newDir')">
|
||||
<i class="material-icons">create_new_folder</i>
|
||||
<span>{{ $t('sidebar.newFolder') }}</span>
|
||||
</button>
|
||||
|
||||
<button @click="$store.commit('showHover', 'newFile')" class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')">
|
||||
<button class="action" :aria-label="$t('sidebar.newFile')" :title="$t('sidebar.newFile')" @click="$store.commit('showHover', 'newFile')">
|
||||
<i class="material-icons">note_add</i>
|
||||
<span>{{ $t('sidebar.newFile') }}</span>
|
||||
</button>
|
||||
@ -24,7 +24,7 @@
|
||||
<span>{{ $t('sidebar.settings') }}</span>
|
||||
</router-link>
|
||||
|
||||
<button v-if="authMethod == 'json'" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
|
||||
<button v-if="authMethod == 'json'" id="logout" class="action" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')" @click="logout">
|
||||
<i class="material-icons">exit_to_app</i>
|
||||
<span>{{ $t('sidebar.logout') }}</span>
|
||||
</button>
|
||||
@ -59,11 +59,11 @@ import * as auth from '@/utils/auth'
|
||||
import { version, signup, disableExternal, noAuth, authMethod } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
name: 'Sidebar',
|
||||
computed: {
|
||||
...mapState([ 'user' ]),
|
||||
...mapGetters([ 'isLogged' ]),
|
||||
active () {
|
||||
...mapState(['user']),
|
||||
...mapGetters(['isLogged']),
|
||||
active() {
|
||||
return this.$store.state.show === 'sidebar'
|
||||
},
|
||||
signup: () => signup,
|
||||
@ -73,7 +73,7 @@ export default {
|
||||
authMethod: () => authMethod
|
||||
},
|
||||
methods: {
|
||||
help () {
|
||||
help() {
|
||||
this.$store.commit('showHover', 'help')
|
||||
},
|
||||
logout: auth.logout
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
|
||||
<button id="copy-button" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" @click="show">
|
||||
<i class="material-icons">content_copy</i>
|
||||
<span>{{ $t('buttons.copyFile') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'copy-button',
|
||||
name: 'CopyButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('showHover', 'copy')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
|
||||
<button id="delete-button" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" @click="show">
|
||||
<i class="material-icons">delete</i>
|
||||
<span>{{ $t('buttons.delete') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'delete-button',
|
||||
name: 'DeleteButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('showHover', 'delete')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
|
||||
<button id="download-button" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" class="action" @click="download">
|
||||
<i class="material-icons">file_download</i>
|
||||
<span>{{ $t('buttons.download') }}</span>
|
||||
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
|
||||
@ -7,17 +7,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'download-button',
|
||||
name: 'DownloadButton',
|
||||
computed: {
|
||||
...mapState(['req', 'selected']),
|
||||
...mapGetters(['isListing', 'selectedCount'])
|
||||
},
|
||||
methods: {
|
||||
download: function () {
|
||||
download: function() {
|
||||
if (!this.isListing) {
|
||||
api.download(null, this.$route.path)
|
||||
return
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'info-button',
|
||||
name: 'InfoButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('showHover', 'info')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
|
||||
<button id="move-button" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" @click="show">
|
||||
<i class="material-icons">forward</i>
|
||||
<span>{{ $t('buttons.moveFile') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'move-button',
|
||||
name: 'MoveButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('showHover', 'move')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
|
||||
<button id="rename-button" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" @click="show">
|
||||
<i class="material-icons">mode_edit</i>
|
||||
<span>{{ $t('buttons.rename') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rename-button',
|
||||
name: 'RenameButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('showHover', 'rename')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action">
|
||||
<button :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action" @click="show">
|
||||
<i class="material-icons">share</i>
|
||||
<span>{{ $t('buttons.share') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'share-button',
|
||||
name: 'ShareButton',
|
||||
methods: {
|
||||
show () {
|
||||
show() {
|
||||
this.$store.commit('showHover', 'share')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="show" :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action">
|
||||
<button :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action" @click="show">
|
||||
<i class="material-icons">code</i>
|
||||
<span>{{ $t('buttons.shell') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'shell-button',
|
||||
name: 'ShellButton',
|
||||
methods: {
|
||||
show: function () {
|
||||
show: function() {
|
||||
this.$store.commit('toggleShell')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
|
||||
<button id="switch-view-button" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" @click="change">
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<span>{{ $t('buttons.switchView') }}</span>
|
||||
</button>
|
||||
@ -10,17 +10,17 @@ import { mapState, mapMutations } from 'vuex'
|
||||
import { users as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'switch-button',
|
||||
name: 'SwitchButton',
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
icon: function () {
|
||||
icon: function() {
|
||||
if (this.user.viewMode === 'mosaic') return 'view_list'
|
||||
return 'view_module'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'updateUser', 'closeHovers' ]),
|
||||
change: async function () {
|
||||
...mapMutations(['updateUser', 'closeHovers']),
|
||||
change: async function() {
|
||||
this.closeHovers()
|
||||
|
||||
const data = {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
|
||||
<button id="upload-button" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" @click="upload">
|
||||
<i class="material-icons">file_upload</i>
|
||||
<span>{{ $t('buttons.upload') }}</span>
|
||||
</button>
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'upload-button',
|
||||
name: 'UploadButton',
|
||||
methods: {
|
||||
upload: function () {
|
||||
upload: function() {
|
||||
document.getElementById('upload-input').click()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<form id="editor"></form>
|
||||
<form id="editor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -13,24 +13,24 @@ import 'ace-builds/webpack-resolver'
|
||||
import { theme } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'editor',
|
||||
name: 'Editor',
|
||||
computed: {
|
||||
...mapState(['req'])
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
document.getElementById('save-button').addEventListener('click', this.save)
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
document.getElementById('save-button').removeEventListener('click', this.save)
|
||||
this.editor.destroy();
|
||||
this.editor.destroy()
|
||||
},
|
||||
mounted: function () {
|
||||
const fileContent = this.req.content || '';
|
||||
mounted: function() {
|
||||
const fileContent = this.req.content || ''
|
||||
|
||||
this.editor = ace.edit('editor', {
|
||||
maxLines: Infinity,
|
||||
@ -43,12 +43,12 @@ export default {
|
||||
wrap: true
|
||||
})
|
||||
|
||||
if (theme == 'dark') {
|
||||
this.editor.setTheme("ace/theme/twilight");
|
||||
if (theme === 'dark') {
|
||||
this.editor.setTheme('ace/theme/twilight')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
keyEvent (event) {
|
||||
keyEvent(event) {
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
return
|
||||
}
|
||||
@ -60,7 +60,7 @@ export default {
|
||||
event.preventDefault()
|
||||
this.save()
|
||||
},
|
||||
async save () {
|
||||
async save() {
|
||||
const button = 'save'
|
||||
buttons.loading('save')
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="image-ex-container"
|
||||
ref="container"
|
||||
class="image-ex-container"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@dblclick="zoomAuto"
|
||||
@ -10,7 +10,7 @@
|
||||
@mouseup="mouseUp"
|
||||
@wheel="wheelMove"
|
||||
>
|
||||
<img :src="src" class="image-ex-img" ref="imgex" @load="setCenter">
|
||||
<img ref="imgex" :src="src" class="image-ex-img" @load="setCenter">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@ -54,20 +54,20 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let container = this.$refs.container
|
||||
const container = this.$refs.container
|
||||
this.classList.forEach(className => container.classList.add(className))
|
||||
// set width and height if they are zero
|
||||
if (getComputedStyle(container).width === "0px") {
|
||||
container.style.width = "100%"
|
||||
if (getComputedStyle(container).width === '0px') {
|
||||
container.style.width = '100%'
|
||||
}
|
||||
if (getComputedStyle(container).height === "0px") {
|
||||
container.style.height = "100%"
|
||||
if (getComputedStyle(container).height === '0px') {
|
||||
container.style.height = '100%'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setCenter() {
|
||||
let container = this.$refs.container
|
||||
let img = this.$refs.imgex
|
||||
const container = this.$refs.container
|
||||
const img = this.$refs.imgex
|
||||
|
||||
let rate = Math.min(
|
||||
container.clientWidth / img.clientWidth,
|
||||
@ -80,7 +80,7 @@ export default {
|
||||
img.width = Math.floor(img.clientWidth * rate)
|
||||
img.style.top = `${Math.floor((container.clientHeight - img.clientHeight) / 2)}px`
|
||||
img.style.left = `${Math.floor((container.clientWidth - img.clientWidth) / 2)}px`
|
||||
document.addEventListener('mouseup', () => this.inDrag = false )
|
||||
document.addEventListener('mouseup', () => { this.inDrag = false })
|
||||
},
|
||||
mousedownStart(event) {
|
||||
this.lastX = null
|
||||
@ -126,7 +126,7 @@ export default {
|
||||
this.lastY = event.targetTouches[0].pageY
|
||||
return
|
||||
}
|
||||
let step = this.$refs.imgex.width / 5
|
||||
const step = this.$refs.imgex.width / 5
|
||||
if (event.targetTouches.length === 2) {
|
||||
this.moveDisabled = true
|
||||
clearTimeout(this.disabledTimer)
|
||||
@ -135,9 +135,9 @@ export default {
|
||||
this.moveDisabledTime
|
||||
)
|
||||
|
||||
let p1 = event.targetTouches[0]
|
||||
let p2 = event.targetTouches[1]
|
||||
let touchDistance = Math.sqrt(
|
||||
const p1 = event.targetTouches[0]
|
||||
const p2 = event.targetTouches[1]
|
||||
const touchDistance = Math.sqrt(
|
||||
Math.pow(p2.pageX - p1.pageX, 2) + Math.pow(p2.pageY - p1.pageY, 2)
|
||||
)
|
||||
if (!this.lastTouchDistance) {
|
||||
@ -149,8 +149,8 @@ export default {
|
||||
this.setZoom()
|
||||
} else if (event.targetTouches.length === 1) {
|
||||
if (this.moveDisabled) return
|
||||
let x = event.targetTouches[0].pageX - this.lastX
|
||||
let y = event.targetTouches[0].pageY - this.lastY
|
||||
const x = event.targetTouches[0].pageX - this.lastX
|
||||
const y = event.targetTouches[0].pageY - this.lastY
|
||||
if (Math.abs(x) >= step && Math.abs(y) >= step) return
|
||||
this.lastX = event.targetTouches[0].pageX
|
||||
this.lastY = event.targetTouches[0].pageY
|
||||
@ -158,7 +158,7 @@ export default {
|
||||
}
|
||||
},
|
||||
doMove(x, y) {
|
||||
let style = this.$refs.imgex.style
|
||||
const style = this.$refs.imgex.style
|
||||
style.left = `${this.pxStringToNumber(style.left) + x}px`
|
||||
style.top = `${this.pxStringToNumber(style.top) + y}px`
|
||||
},
|
||||
@ -172,7 +172,7 @@ export default {
|
||||
this.$refs.imgex.style.transform = `scale(${this.scale})`
|
||||
},
|
||||
pxStringToNumber(style) {
|
||||
return +style.replace("px", "")
|
||||
return +style.replace('px', '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,41 +4,53 @@
|
||||
<i class="material-icons">sentiment_dissatisfied</i>
|
||||
<span>{{ $t('files.lonely') }}</span>
|
||||
</h2>
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input id="upload-input" style="display:none" type="file" multiple @change="uploadInput($event)">
|
||||
</div>
|
||||
<div v-else id="listing"
|
||||
<div
|
||||
v-else
|
||||
id="listing"
|
||||
:class="user.viewMode"
|
||||
@dragenter="dragEnter"
|
||||
@dragend="dragEnd">
|
||||
@dragend="dragEnd"
|
||||
>
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div></div>
|
||||
<div />
|
||||
<div>
|
||||
<p :class="{ active: nameSorted }" class="name"
|
||||
<p
|
||||
:class="{ active: nameSorted }"
|
||||
class="name"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('name')"
|
||||
:title="$t('files.sortByName')"
|
||||
:aria-label="$t('files.sortByName')">
|
||||
:aria-label="$t('files.sortByName')"
|
||||
@click="sort('name')"
|
||||
>
|
||||
<span>{{ $t('files.name') }}</span>
|
||||
<i class="material-icons">{{ nameIcon }}</i>
|
||||
</p>
|
||||
|
||||
<p :class="{ active: sizeSorted }" class="size"
|
||||
<p
|
||||
:class="{ active: sizeSorted }"
|
||||
class="size"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('size')"
|
||||
:title="$t('files.sortBySize')"
|
||||
:aria-label="$t('files.sortBySize')">
|
||||
:aria-label="$t('files.sortBySize')"
|
||||
@click="sort('size')"
|
||||
>
|
||||
<span>{{ $t('files.size') }}</span>
|
||||
<i class="material-icons">{{ sizeIcon }}</i>
|
||||
</p>
|
||||
<p :class="{ active: modifiedSorted }" class="modified"
|
||||
<p
|
||||
:class="{ active: modifiedSorted }"
|
||||
class="modified"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="sort('modified')"
|
||||
:title="$t('files.sortByLastModified')"
|
||||
:aria-label="$t('files.sortByLastModified')">
|
||||
:aria-label="$t('files.sortByLastModified')"
|
||||
@click="sort('modified')"
|
||||
>
|
||||
<span>{{ $t('files.lastModified') }}</span>
|
||||
<i class="material-icons">{{ modifiedIcon }}</i>
|
||||
</p>
|
||||
@ -48,37 +60,39 @@
|
||||
|
||||
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
|
||||
<div v-if="req.numDirs > 0">
|
||||
<item v-for="(item) in dirs"
|
||||
<item
|
||||
v-for="(item) in dirs"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
:index="item.index"
|
||||
:name="item.name"
|
||||
:is-dir="item.isDir"
|
||||
:url="item.url"
|
||||
:modified="item.modified"
|
||||
:type="item.type"
|
||||
:size="item.size"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
|
||||
<div v-if="req.numFiles > 0">
|
||||
<item v-for="(item) in files"
|
||||
<item
|
||||
v-for="(item) in files"
|
||||
:key="base64(item.name)"
|
||||
v-bind:index="item.index"
|
||||
v-bind:name="item.name"
|
||||
v-bind:isDir="item.isDir"
|
||||
v-bind:url="item.url"
|
||||
v-bind:modified="item.modified"
|
||||
v-bind:type="item.type"
|
||||
v-bind:size="item.size">
|
||||
</item>
|
||||
:index="item.index"
|
||||
:name="item.name"
|
||||
:is-dir="item.isDir"
|
||||
:url="item.url"
|
||||
:modified="item.modified"
|
||||
:type="item.type"
|
||||
:size="item.size"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
|
||||
<input id="upload-input" style="display:none" type="file" multiple @change="uploadInput($event)">
|
||||
|
||||
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
|
||||
<div id="multiple-selection" :class="{ active: $store.state.multiple }">
|
||||
<p>{{ $t('files.multipleSelectionEnabled') }}</p>
|
||||
<div @click="$store.commit('multiple', false)" tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action">
|
||||
<div tabindex="0" role="button" :title="$t('files.clear')" :aria-label="$t('files.clear')" class="action" @click="$store.commit('multiple', false)">
|
||||
<i class="material-icons">clear</i>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,28 +108,28 @@ import buttons from '@/utils/buttons'
|
||||
import url from '@/utils/url'
|
||||
|
||||
export default {
|
||||
name: 'listing',
|
||||
name: 'Listing',
|
||||
components: { Item },
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
show: 50
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'selected', 'user']),
|
||||
nameSorted () {
|
||||
nameSorted() {
|
||||
return (this.req.sorting.by === 'name')
|
||||
},
|
||||
sizeSorted () {
|
||||
sizeSorted() {
|
||||
return (this.req.sorting.by === 'size')
|
||||
},
|
||||
modifiedSorted () {
|
||||
modifiedSorted() {
|
||||
return (this.req.sorting.by === 'modified')
|
||||
},
|
||||
ascOrdered () {
|
||||
ascOrdered() {
|
||||
return this.req.sorting.asc
|
||||
},
|
||||
items () {
|
||||
items() {
|
||||
const dirs = []
|
||||
const files = []
|
||||
|
||||
@ -129,31 +143,31 @@ export default {
|
||||
|
||||
return { dirs, files }
|
||||
},
|
||||
dirs () {
|
||||
dirs() {
|
||||
return this.items.dirs.slice(0, this.show)
|
||||
},
|
||||
files () {
|
||||
files() {
|
||||
let show = this.show - this.items.dirs.length
|
||||
|
||||
if (show < 0) show = 0
|
||||
|
||||
return this.items.files.slice(0, show)
|
||||
},
|
||||
nameIcon () {
|
||||
nameIcon() {
|
||||
if (this.nameSorted && !this.ascOrdered) {
|
||||
return 'arrow_upward'
|
||||
}
|
||||
|
||||
return 'arrow_downward'
|
||||
},
|
||||
sizeIcon () {
|
||||
sizeIcon() {
|
||||
if (this.sizeSorted && this.ascOrdered) {
|
||||
return 'arrow_downward'
|
||||
}
|
||||
|
||||
return 'arrow_upward'
|
||||
},
|
||||
modifiedIcon () {
|
||||
modifiedIcon() {
|
||||
if (this.modifiedSorted && this.ascOrdered) {
|
||||
return 'arrow_downward'
|
||||
}
|
||||
@ -161,7 +175,7 @@ export default {
|
||||
return 'arrow_upward'
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
mounted: function() {
|
||||
// Check the columns size for the first time.
|
||||
this.resizeEvent()
|
||||
|
||||
@ -172,7 +186,7 @@ export default {
|
||||
document.addEventListener('dragover', this.preventDefault)
|
||||
document.addEventListener('drop', this.drop)
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
// Remove event listeners before destroying this page.
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('resize', this.resizeEvent)
|
||||
@ -181,16 +195,16 @@ export default {
|
||||
document.removeEventListener('drop', this.drop)
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'updateUser' ]),
|
||||
base64: function (name) {
|
||||
...mapMutations(['updateUser']),
|
||||
base64: function(name) {
|
||||
return window.btoa(unescape(encodeURIComponent(name)))
|
||||
},
|
||||
keyEvent (event) {
|
||||
keyEvent(event) {
|
||||
if (!event.ctrlKey && !event.metaKey) {
|
||||
return
|
||||
}
|
||||
|
||||
let key = String.fromCharCode(event.which).toLowerCase()
|
||||
const key = String.fromCharCode(event.which).toLowerCase()
|
||||
|
||||
switch (key) {
|
||||
case 'f':
|
||||
@ -206,25 +220,25 @@ export default {
|
||||
break
|
||||
}
|
||||
},
|
||||
preventDefault (event) {
|
||||
preventDefault(event) {
|
||||
// Wrapper around prevent default.
|
||||
event.preventDefault()
|
||||
},
|
||||
copyCut (event, key) {
|
||||
copyCut(event, key) {
|
||||
if (event.target.tagName.toLowerCase() === 'input') {
|
||||
return
|
||||
}
|
||||
|
||||
let items = []
|
||||
const items = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
for (const i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
name: encodeURIComponent(this.req.items[i].name)
|
||||
})
|
||||
}
|
||||
|
||||
if (items.length == 0) {
|
||||
if (items.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -233,14 +247,14 @@ export default {
|
||||
items: items
|
||||
})
|
||||
},
|
||||
paste (event) {
|
||||
paste(event) {
|
||||
if (event.target.tagName.toLowerCase() === 'input') {
|
||||
return
|
||||
}
|
||||
|
||||
let items = []
|
||||
const items = []
|
||||
|
||||
for (let item of this.$store.state.clipboard.items) {
|
||||
for (const item of this.$store.state.clipboard.items) {
|
||||
const from = item.from.endsWith('/') ? item.from.slice(0, -1) : item.from
|
||||
const to = this.$route.path + item.name
|
||||
items.push({ from, to })
|
||||
@ -261,36 +275,36 @@ export default {
|
||||
this.$store.commit('setReload', true)
|
||||
}).catch(this.$showError)
|
||||
},
|
||||
resizeEvent () {
|
||||
resizeEvent() {
|
||||
// Update the columns size based on the window width.
|
||||
let columns = Math.floor(document.querySelector('main').offsetWidth / 300)
|
||||
let items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
|
||||
const items = css(['#listing.mosaic .item', '.mosaic#listing .item'])
|
||||
if (columns === 0) columns = 1
|
||||
items.style.width = `calc(${100 / columns}% - 1em)`
|
||||
},
|
||||
scrollEvent () {
|
||||
scrollEvent() {
|
||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
|
||||
this.show += 50
|
||||
}
|
||||
},
|
||||
dragEnter () {
|
||||
dragEnter() {
|
||||
// When the user starts dragging an item, put every
|
||||
// file on the listing with 50% opacity.
|
||||
let items = document.getElementsByClassName('item')
|
||||
const items = document.getElementsByClassName('item')
|
||||
|
||||
Array.from(items).forEach(file => {
|
||||
file.style.opacity = 0.5
|
||||
})
|
||||
},
|
||||
dragEnd () {
|
||||
dragEnd() {
|
||||
this.resetOpacity()
|
||||
},
|
||||
drop: function (event) {
|
||||
drop: function(event) {
|
||||
event.preventDefault()
|
||||
this.resetOpacity()
|
||||
|
||||
let dt = event.dataTransfer
|
||||
let files = dt.files
|
||||
const dt = event.dataTransfer
|
||||
const files = dt.files
|
||||
let el = event.target
|
||||
|
||||
if (files.length <= 0) return
|
||||
@ -318,14 +332,14 @@ export default {
|
||||
|
||||
this.checkConflict(files, this.req.items, base)
|
||||
},
|
||||
checkConflict (files, items, base) {
|
||||
checkConflict(files, items, base) {
|
||||
if (typeof items === 'undefined' || items === null) {
|
||||
items = []
|
||||
}
|
||||
|
||||
let conflict = false
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let res = items.findIndex(function hasConflict (element) {
|
||||
const res = items.findIndex(function hasConflict(element) {
|
||||
return (element.name === this)
|
||||
}, files[i].name)
|
||||
|
||||
@ -349,22 +363,22 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
uploadInput (event) {
|
||||
uploadInput(event) {
|
||||
this.checkConflict(event.currentTarget.files, this.req.items, '')
|
||||
},
|
||||
resetOpacity () {
|
||||
let items = document.getElementsByClassName('item')
|
||||
resetOpacity() {
|
||||
const items = document.getElementsByClassName('item')
|
||||
|
||||
Array.from(items).forEach(file => {
|
||||
file.style.opacity = 1
|
||||
})
|
||||
},
|
||||
handleFiles (files, base, overwrite = false) {
|
||||
handleFiles(files, base, overwrite = false) {
|
||||
buttons.loading('upload')
|
||||
let promises = []
|
||||
let progress = new Array(files.length).fill(0)
|
||||
const promises = []
|
||||
const progress = new Array(files.length).fill(0)
|
||||
|
||||
let onupload = (id) => (event) => {
|
||||
const onupload = (id) => (event) => {
|
||||
progress[id] = (event.loaded / event.total) * 100
|
||||
|
||||
let sum = 0
|
||||
@ -376,12 +390,12 @@ export default {
|
||||
}
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files[i]
|
||||
let filenameEncoded = url.encodeRFC5987ValueChars(file.name)
|
||||
const file = files[i]
|
||||
const filenameEncoded = url.encodeRFC5987ValueChars(file.name)
|
||||
promises.push(api.post(this.$route.path + base + filenameEncoded, file, overwrite, onupload(i)))
|
||||
}
|
||||
|
||||
let finish = () => {
|
||||
const finish = () => {
|
||||
buttons.success('upload')
|
||||
this.$store.commit('setProgress', 0)
|
||||
}
|
||||
@ -398,7 +412,7 @@ export default {
|
||||
|
||||
return false
|
||||
},
|
||||
async sort (by) {
|
||||
async sort(by) {
|
||||
let asc = false
|
||||
|
||||
if (by === 'name') {
|
||||
@ -416,7 +430,7 @@ export default {
|
||||
}
|
||||
|
||||
try {
|
||||
await users.update({ id: this.user.id, sorting: { by, asc } }, ['sorting'])
|
||||
await users.update({ id: this.user.id, sorting: { by, asc }}, ['sorting'])
|
||||
} catch (e) {
|
||||
this.$showError(e)
|
||||
}
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
:aria-label="name"
|
||||
:aria-selected="isSelected">
|
||||
<div>
|
||||
<i class="material-icons">{{ icon }}</i>
|
||||
<img v-if="type==='image'" :src="thumbnailUrl">
|
||||
<i v-else class="material-icons">{{ icon }}</i>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -31,25 +32,26 @@
|
||||
|
||||
<script>
|
||||
import { mapMutations, mapGetters, mapState } from 'vuex'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'item',
|
||||
data: function () {
|
||||
name: 'Item',
|
||||
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
|
||||
data: function() {
|
||||
return {
|
||||
touches: 0
|
||||
}
|
||||
},
|
||||
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
|
||||
computed: {
|
||||
...mapState(['selected', 'req', 'user']),
|
||||
...mapState(['selected', 'req', 'jwt']),
|
||||
...mapGetters(['selectedCount']),
|
||||
isSelected () {
|
||||
return (this.selected.indexOf(this.index) !== -1)
|
||||
isSelected() {
|
||||
return this.selected.indexOf(this.index) !== -1
|
||||
},
|
||||
icon () {
|
||||
icon() {
|
||||
if (this.isDir) return 'folder'
|
||||
if (this.type === 'image') return 'insert_photo'
|
||||
if (this.type === 'audio') return 'volume_up'
|
||||
@ -59,27 +61,31 @@ export default {
|
||||
isDraggable () {
|
||||
return this.user.perm.rename
|
||||
},
|
||||
canDrop () {
|
||||
canDrop() {
|
||||
if (!this.isDir) return false
|
||||
|
||||
for (let i of this.selected) {
|
||||
for (const i of this.selected) {
|
||||
if (this.req.items[i].url === this.url) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
thumbnailUrl() {
|
||||
const path = this.url.replace(/^\/files\//, '')
|
||||
return `${baseURL}/api/thumbnail/${path}?auth=${this.jwt}&inline=true`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['addSelected', 'removeSelected', 'resetSelected']),
|
||||
humanSize: function () {
|
||||
humanSize: function() {
|
||||
return filesize(this.size)
|
||||
},
|
||||
humanTime: function () {
|
||||
humanTime: function() {
|
||||
return moment(this.modified).fromNow()
|
||||
},
|
||||
dragStart: function () {
|
||||
dragStart: function() {
|
||||
if (this.selectedCount === 0) {
|
||||
this.addSelected(this.index)
|
||||
return
|
||||
@ -90,7 +96,7 @@ export default {
|
||||
this.addSelected(this.index)
|
||||
}
|
||||
},
|
||||
dragOver: function (event) {
|
||||
dragOver: function(event) {
|
||||
if (!this.canDrop) return
|
||||
|
||||
event.preventDefault()
|
||||
@ -104,28 +110,29 @@ export default {
|
||||
|
||||
el.style.opacity = 1
|
||||
},
|
||||
drop: function (event) {
|
||||
drop: function(event) {
|
||||
if (!this.canDrop) return
|
||||
event.preventDefault()
|
||||
|
||||
if (this.selectedCount === 0) return
|
||||
|
||||
let items = []
|
||||
const items = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
for (const i of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[i].url,
|
||||
to: this.url + this.req.items[i].name
|
||||
})
|
||||
}
|
||||
|
||||
api.move(items)
|
||||
api
|
||||
.move(items)
|
||||
.then(() => {
|
||||
this.$store.commit('setReload', true)
|
||||
})
|
||||
.catch(this.$showError)
|
||||
},
|
||||
click: function (event) {
|
||||
click: function(event) {
|
||||
if (this.selectedCount !== 0) event.preventDefault()
|
||||
if (this.$store.state.selected.indexOf(this.index) !== -1) {
|
||||
this.removeSelected(this.index)
|
||||
@ -154,7 +161,7 @@ export default {
|
||||
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
|
||||
this.addSelected(this.index)
|
||||
},
|
||||
touchstart () {
|
||||
touchstart() {
|
||||
setTimeout(() => {
|
||||
this.touches = 0
|
||||
}, 300)
|
||||
@ -164,8 +171,8 @@ export default {
|
||||
this.open()
|
||||
}
|
||||
},
|
||||
open: function () {
|
||||
this.$router.push({path: this.url})
|
||||
open: function() {
|
||||
this.$router.push({ path: this.url })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,40 +1,63 @@
|
||||
<template>
|
||||
<div id="previewer">
|
||||
<div class="bar">
|
||||
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
|
||||
<button
|
||||
id="close"
|
||||
class="action"
|
||||
:title="$t('files.closePreview')"
|
||||
:aria-label="$t('files.closePreview')"
|
||||
@click="back"
|
||||
>
|
||||
<i class="material-icons">close</i>
|
||||
</button>
|
||||
|
||||
<rename-button v-if="user.perm.rename"></rename-button>
|
||||
<delete-button v-if="user.perm.delete"></delete-button>
|
||||
<download-button v-if="user.perm.download"></download-button>
|
||||
<info-button></info-button>
|
||||
<rename-button v-if="user.perm.rename" />
|
||||
<delete-button v-if="user.perm.delete" />
|
||||
<download-button v-if="user.perm.download" />
|
||||
<info-button />
|
||||
</div>
|
||||
|
||||
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
|
||||
<button
|
||||
v-show="hasPrevious"
|
||||
class="action"
|
||||
:aria-label="$t('buttons.previous')"
|
||||
:title="$t('buttons.previous')"
|
||||
@click="prev"
|
||||
>
|
||||
<i class="material-icons">chevron_left</i>
|
||||
</button>
|
||||
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
|
||||
<button
|
||||
v-show="hasNext"
|
||||
class="action"
|
||||
:aria-label="$t('buttons.next')"
|
||||
:title="$t('buttons.next')"
|
||||
@click="next"
|
||||
>
|
||||
<i class="material-icons">chevron_right</i>
|
||||
</button>
|
||||
|
||||
<div class="preview">
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw" />
|
||||
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls />
|
||||
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
|
||||
<track
|
||||
kind="captions"
|
||||
v-for="(sub, index) in subtitles"
|
||||
:key="index"
|
||||
kind="captions"
|
||||
:src="sub"
|
||||
:label="'Subtitle ' + index" :default="index === 0">
|
||||
Sorry, your browser doesn't support embedded videos,
|
||||
but don't worry, you can <a :href="download">download it</a>
|
||||
:label="'Subtitle ' + index"
|
||||
:default="index === 0"
|
||||
>Sorry, your browser doesn't support embedded videos,
|
||||
but don't worry, you can
|
||||
<a :href="download">download it</a>
|
||||
and watch it with your favorite video player!
|
||||
</video>
|
||||
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object>
|
||||
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw" />
|
||||
<a v-else-if="req.type == 'blob'" :href="download">
|
||||
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
|
||||
<h2 class="message">
|
||||
{{ $t('buttons.download') }}
|
||||
<i class="material-icons">file_download</i>
|
||||
</h2>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -51,15 +74,10 @@ import RenameButton from '@/components/buttons/Rename'
|
||||
import DownloadButton from '@/components/buttons/Download'
|
||||
import ExtendedImage from './ExtendedImage'
|
||||
|
||||
const mediaTypes = [
|
||||
"image",
|
||||
"video",
|
||||
"audio",
|
||||
"blob"
|
||||
]
|
||||
const mediaTypes = ['image', 'video', 'audio', 'blob']
|
||||
|
||||
export default {
|
||||
name: 'preview',
|
||||
name: 'Preview',
|
||||
components: {
|
||||
InfoButton,
|
||||
DeleteButton,
|
||||
@ -67,7 +85,7 @@ export default {
|
||||
DownloadButton,
|
||||
ExtendedImage
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
previousLink: '',
|
||||
nextLink: '',
|
||||
@ -77,24 +95,33 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(['req', 'user', 'oldReq', 'jwt']),
|
||||
hasPrevious () {
|
||||
return (this.previousLink !== '')
|
||||
hasPrevious() {
|
||||
return this.previousLink !== ''
|
||||
},
|
||||
hasNext () {
|
||||
return (this.nextLink !== '')
|
||||
hasNext() {
|
||||
return this.nextLink !== ''
|
||||
},
|
||||
download () {
|
||||
download() {
|
||||
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
|
||||
},
|
||||
raw () {
|
||||
return `${this.download}&inline=true`
|
||||
thumbnail() {
|
||||
if (this.req.type === 'image') {
|
||||
return `${baseURL}/api/thumbnail${this.req.path}?auth=${this.jwt}`
|
||||
} else {
|
||||
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
raw() {
|
||||
return `${this.thumbnail}&inline=true`
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
window.addEventListener('keyup', this.key)
|
||||
|
||||
if (this.req.subtitles) {
|
||||
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
|
||||
this.subtitles = this.req.subtitles.map(
|
||||
sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -109,30 +136,32 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keyup', this.key)
|
||||
},
|
||||
methods: {
|
||||
back () {
|
||||
let uri = url.removeLastDir(this.$route.path) + '/'
|
||||
back() {
|
||||
const uri = url.removeLastDir(this.$route.path) + '/'
|
||||
this.$router.push({ path: uri })
|
||||
},
|
||||
prev () {
|
||||
prev() {
|
||||
this.$router.push({ path: this.previousLink })
|
||||
},
|
||||
next () {
|
||||
next() {
|
||||
this.$router.push({ path: this.nextLink })
|
||||
},
|
||||
key (event) {
|
||||
key(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (event.which === 13 || event.which === 39) { // right arrow
|
||||
if (event.which === 13 || event.which === 39) {
|
||||
// right arrow
|
||||
if (this.hasNext) this.next()
|
||||
} else if (event.which === 37) { // left arrow
|
||||
} else if (event.which === 37) {
|
||||
// left arrow
|
||||
if (this.hasPrevious) this.prev()
|
||||
}
|
||||
},
|
||||
updateLinks (items) {
|
||||
updateLinks(items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].name !== this.req.name) {
|
||||
continue
|
||||
|
||||
@ -6,19 +6,23 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.copyMessage') }}</p>
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
<file-list @update:selected="val => dest = val" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat"
|
||||
@click="copy"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
||||
:title="$t('buttons.copy')"
|
||||
@click="copy"
|
||||
>{{ $t('buttons.copy') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -30,9 +34,9 @@ import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'copy',
|
||||
name: 'Copy',
|
||||
components: { FileList },
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
current: window.location.pathname,
|
||||
dest: null
|
||||
@ -40,13 +44,13 @@ export default {
|
||||
},
|
||||
computed: mapState(['req', 'selected']),
|
||||
methods: {
|
||||
copy: async function (event) {
|
||||
copy: async function(event) {
|
||||
event.preventDefault()
|
||||
buttons.loading('copy')
|
||||
let items = []
|
||||
const items = []
|
||||
|
||||
// Create a new promise for each file.
|
||||
for (let item of this.selected) {
|
||||
for (const item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name)
|
||||
|
||||
@ -5,33 +5,37 @@
|
||||
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
|
||||
</div>
|
||||
<div class="card-action">
|
||||
<button @click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button @click="submit"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
:title="$t('buttons.delete')"
|
||||
@click="submit"
|
||||
>{{ $t('buttons.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapMutations, mapState} from 'vuex'
|
||||
import { mapGetters, mapMutations, mapState } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
import url from '@/utils/url'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'delete',
|
||||
name: 'Delete',
|
||||
computed: {
|
||||
...mapGetters(['isListing', 'selectedCount']),
|
||||
...mapState(['req', 'selected'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['closeHovers']),
|
||||
submit: async function () {
|
||||
submit: async function() {
|
||||
this.closeHovers()
|
||||
buttons.loading('delete')
|
||||
|
||||
@ -47,8 +51,8 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
let promises = []
|
||||
for (let index of this.selected) {
|
||||
const promises = []
|
||||
for (const index of this.selected) {
|
||||
promises.push(api.remove(this.req.items[index].url))
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="card floating" id="download">
|
||||
<div id="download" class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('prompts.download') }}</h2>
|
||||
</div>
|
||||
@ -7,35 +7,35 @@
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.downloadMessage') }}</p>
|
||||
|
||||
<button class="button button--block" @click="download('zip')" v-focus>zip</button>
|
||||
<button class="button button--block" @click="download('tar')" v-focus>tar</button>
|
||||
<button class="button button--block" @click="download('targz')" v-focus>tar.gz</button>
|
||||
<button class="button button--block" @click="download('tarbz2')" v-focus>tar.bz2</button>
|
||||
<button class="button button--block" @click="download('tarxz')" v-focus>tar.xz</button>
|
||||
<button class="button button--block" @click="download('tarlz4')" v-focus>tar.lz4</button>
|
||||
<button class="button button--block" @click="download('tarsz')" v-focus>tar.sz</button>
|
||||
<button v-focus class="button button--block" @click="download('zip')">zip</button>
|
||||
<button v-focus class="button button--block" @click="download('tar')">tar</button>
|
||||
<button v-focus class="button button--block" @click="download('targz')">tar.gz</button>
|
||||
<button v-focus class="button button--block" @click="download('tarbz2')">tar.bz2</button>
|
||||
<button v-focus class="button button--block" @click="download('tarxz')">tar.xz</button>
|
||||
<button v-focus class="button button--block" @click="download('tarlz4')">tar.lz4</button>
|
||||
<button v-focus class="button button--block" @click="download('tarsz')">tar.sz</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'download',
|
||||
name: 'Download',
|
||||
computed: {
|
||||
...mapState(['selected', 'req']),
|
||||
...mapGetters(['selectedCount'])
|
||||
},
|
||||
methods: {
|
||||
download: function (format) {
|
||||
download: function(format) {
|
||||
if (this.selectedCount === 0) {
|
||||
api.download(format, this.$route.path)
|
||||
} else {
|
||||
let files = []
|
||||
const files = []
|
||||
|
||||
for (let i of this.selected) {
|
||||
for (const i of this.selected) {
|
||||
files.push(this.req.items[i].url)
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="file-list">
|
||||
<li @click="select"
|
||||
@touchstart="touchstart"
|
||||
@dblclick="next"
|
||||
<li
|
||||
v-for="item in items"
|
||||
:key="item.name"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
:aria-label="item.name"
|
||||
:aria-selected="selected == item.url"
|
||||
:key="item.name" v-for="item in items"
|
||||
:data-url="item.url">{{ item.name }}</li>
|
||||
:data-url="item.url"
|
||||
@click="select"
|
||||
@touchstart="touchstart"
|
||||
@dblclick="next"
|
||||
>{{ item.name }}</li>
|
||||
</ul>
|
||||
|
||||
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p>
|
||||
@ -22,8 +25,8 @@ import url from '@/utils/url'
|
||||
import { files } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'file-list',
|
||||
data: function () {
|
||||
name: 'FileList',
|
||||
data: function() {
|
||||
return {
|
||||
items: [],
|
||||
touches: {
|
||||
@ -35,12 +38,12 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'req' ]),
|
||||
nav () {
|
||||
...mapState(['req']),
|
||||
nav() {
|
||||
return decodeURIComponent(this.current)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
// If we're showing this on a listing,
|
||||
// we can use the current request object
|
||||
// to fill the move options.
|
||||
@ -56,7 +59,7 @@ export default {
|
||||
.catch(this.$showError)
|
||||
},
|
||||
methods: {
|
||||
fillOptions (req) {
|
||||
fillOptions(req) {
|
||||
// Sets the current path and resets
|
||||
// the current items.
|
||||
this.current = req.url
|
||||
@ -79,7 +82,7 @@ export default {
|
||||
|
||||
// Otherwise we add every directory to the
|
||||
// move options.
|
||||
for (let item of req.items) {
|
||||
for (const item of req.items) {
|
||||
if (!item.isDir) continue
|
||||
|
||||
this.items.push({
|
||||
@ -88,18 +91,18 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
next: function (event) {
|
||||
next: function(event) {
|
||||
// Retrieves the URL of the directory the user
|
||||
// just clicked in and fill the options with its
|
||||
// content.
|
||||
let uri = event.currentTarget.dataset.url
|
||||
const uri = event.currentTarget.dataset.url
|
||||
|
||||
files.fetch(uri)
|
||||
.then(this.fillOptions)
|
||||
.catch(this.$showError)
|
||||
},
|
||||
touchstart (event) {
|
||||
let url = event.currentTarget.dataset.url
|
||||
touchstart(event) {
|
||||
const url = event.currentTarget.dataset.url
|
||||
|
||||
// In 300 milliseconds, we shall reset the count.
|
||||
setTimeout(() => {
|
||||
@ -123,7 +126,7 @@ export default {
|
||||
this.next(event)
|
||||
}
|
||||
},
|
||||
select: function (event) {
|
||||
select: function(event) {
|
||||
// If the element is already selected, unselect it.
|
||||
if (this.selected === event.currentTarget.dataset.url) {
|
||||
this.selected = null
|
||||
|
||||
@ -19,16 +19,18 @@
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
type="submit"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||
:title="$t('buttons.ok')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default { name: 'help' }
|
||||
export default { name: 'Help' }
|
||||
</script>
|
||||
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
<div class="card-content">
|
||||
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
|
||||
|
||||
<p class="break-word" v-if="selected.length < 2"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
|
||||
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length"></span> {{ humanSize }}</p>
|
||||
<p v-if="selected.length < 2" class="break-word"><strong>{{ $t('prompts.displayName') }}</strong> {{ name }}</p>
|
||||
<p v-if="!dir || selected.length > 1"><strong>{{ $t('prompts.size') }}:</strong> <span id="content_length" /> {{ humanSize }}</p>
|
||||
<p v-if="selected.length < 2"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p>
|
||||
|
||||
<template v-if="dir && selected.length === 0">
|
||||
@ -25,57 +25,59 @@
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button type="submit"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
type="submit"
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.ok')"
|
||||
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
|
||||
:title="$t('buttons.ok')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.ok') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState, mapGetters} from 'vuex'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import filesize from 'filesize'
|
||||
import moment from 'moment'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'info',
|
||||
name: 'Info',
|
||||
computed: {
|
||||
...mapState(['req', 'selected']),
|
||||
...mapGetters(['selectedCount', 'isListing']),
|
||||
humanSize: function () {
|
||||
humanSize: function() {
|
||||
if (this.selectedCount === 0 || !this.isListing) {
|
||||
return filesize(this.req.size)
|
||||
}
|
||||
|
||||
let sum = 0
|
||||
|
||||
for (let selected of this.selected) {
|
||||
for (const selected of this.selected) {
|
||||
sum += this.req.items[selected].size
|
||||
}
|
||||
|
||||
return filesize(sum)
|
||||
},
|
||||
humanTime: function () {
|
||||
humanTime: function() {
|
||||
if (this.selectedCount === 0) {
|
||||
return moment(this.req.modified).fromNow()
|
||||
}
|
||||
|
||||
return moment(this.req.items[this.selected[0]]).fromNow()
|
||||
},
|
||||
name: function () {
|
||||
name: function() {
|
||||
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name
|
||||
},
|
||||
dir: function () {
|
||||
dir: function() {
|
||||
return this.selectedCount > 1 || (this.selectedCount === 0
|
||||
? this.req.isDir
|
||||
: this.req.items[this.selected[0]].isDir)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checksum: async function (event, algo) {
|
||||
checksum: async function(event, algo) {
|
||||
event.preventDefault()
|
||||
|
||||
let link
|
||||
|
||||
@ -5,19 +5,23 @@
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<file-list @update:selected="val => dest = val"></file-list>
|
||||
<file-list @update:selected="val => dest = val" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat"
|
||||
@click="move"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
||||
:title="$t('buttons.move')"
|
||||
@click="move"
|
||||
>{{ $t('buttons.move') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -29,9 +33,9 @@ import { files as api } from '@/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'move',
|
||||
name: 'Move',
|
||||
components: { FileList },
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
current: window.location.pathname,
|
||||
dest: null
|
||||
@ -39,12 +43,12 @@ export default {
|
||||
},
|
||||
computed: mapState(['req', 'selected']),
|
||||
methods: {
|
||||
move: async function (event) {
|
||||
move: async function(event) {
|
||||
event.preventDefault()
|
||||
buttons.loading('move')
|
||||
let items = []
|
||||
const items = []
|
||||
|
||||
for (let item of this.selected) {
|
||||
for (const item of this.selected) {
|
||||
items.push({
|
||||
from: this.req.items[item].url,
|
||||
to: this.dest + encodeURIComponent(this.req.items[item].name)
|
||||
|
||||
@ -6,15 +6,15 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.newDirMessage') }}</p>
|
||||
<input class="input input--block" type="text" @keyup.enter="submit" v-model.trim="name" v-focus>
|
||||
<input v-model.trim="name" v-focus class="input input--block" type="text" @keyup.enter="submit">
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@ -32,14 +32,14 @@ import { files as api } from '@/api'
|
||||
import url from '@/utils/url'
|
||||
|
||||
export default {
|
||||
name: 'new-dir',
|
||||
name: 'NewDir',
|
||||
data: function() {
|
||||
return {
|
||||
name: ''
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isFiles', 'isListing' ])
|
||||
...mapGetters(['isFiles', 'isListing'])
|
||||
},
|
||||
methods: {
|
||||
submit: async function(event) {
|
||||
@ -66,6 +66,6 @@ export default {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -6,21 +6,21 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.newFileMessage') }}</p>
|
||||
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<input v-model.trim="name" v-focus class="input input--block" type="text" @keyup.enter="submit">
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="submit"
|
||||
:aria-label="$t('buttons.create')"
|
||||
:title="$t('buttons.create')"
|
||||
@click="submit"
|
||||
>{{ $t('buttons.create') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,14 +32,14 @@ import { files as api } from '@/api'
|
||||
import url from '@/utils/url'
|
||||
|
||||
export default {
|
||||
name: 'new-file',
|
||||
name: 'NewFile',
|
||||
data: function() {
|
||||
return {
|
||||
name: ''
|
||||
};
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isFiles', 'isListing' ])
|
||||
...mapGetters(['isFiles', 'isListing'])
|
||||
},
|
||||
methods: {
|
||||
submit: async function(event) {
|
||||
@ -66,6 +66,6 @@ export default {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<component :is="currentComponent"></component>
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
<component :is="currentComponent" />
|
||||
<div v-show="showOverlay" class="overlay" @click="resetPrompts" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -21,7 +21,7 @@ import { mapState } from 'vuex'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'prompts',
|
||||
name: 'Prompts',
|
||||
components: {
|
||||
Info,
|
||||
Delete,
|
||||
@ -35,7 +35,7 @@ export default {
|
||||
Help,
|
||||
Replace
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
pluginData: {
|
||||
buttons,
|
||||
@ -46,7 +46,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(['show', 'plugins']),
|
||||
currentComponent: function () {
|
||||
currentComponent: function() {
|
||||
const matched = [
|
||||
'info',
|
||||
'help',
|
||||
@ -59,16 +59,16 @@ export default {
|
||||
'download',
|
||||
'replace',
|
||||
'share'
|
||||
].indexOf(this.show) >= 0;
|
||||
].indexOf(this.show) >= 0
|
||||
|
||||
return matched && this.show || null;
|
||||
return matched && this.show || null
|
||||
},
|
||||
showOverlay: function () {
|
||||
showOverlay: function() {
|
||||
return (this.show !== null && this.show !== 'search' && this.show !== 'more')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetPrompts () {
|
||||
resetPrompts() {
|
||||
this.$store.commit('closeHovers')
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,19 +6,23 @@
|
||||
|
||||
<div class="card-content">
|
||||
<p>{{ $t('prompts.renameMessage') }} <code>{{ oldName() }}</code>:</p>
|
||||
<input class="input input--block" v-focus type="text" @keyup.enter="submit" v-model.trim="name">
|
||||
<input v-model.trim="name" v-focus class="input input--block" type="text" @keyup.enter="submit">
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button @click="submit"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat"
|
||||
type="submit"
|
||||
:aria-label="$t('buttons.rename')"
|
||||
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
|
||||
:title="$t('buttons.rename')"
|
||||
@click="submit"
|
||||
>{{ $t('buttons.rename') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -29,13 +33,13 @@ import url from '@/utils/url'
|
||||
import { files as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'rename',
|
||||
data: function () {
|
||||
name: 'Rename',
|
||||
data: function() {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.name = this.oldName()
|
||||
},
|
||||
computed: {
|
||||
@ -43,10 +47,10 @@ export default {
|
||||
...mapGetters(['isListing'])
|
||||
},
|
||||
methods: {
|
||||
cancel: function () {
|
||||
cancel: function() {
|
||||
this.$store.commit('closeHovers')
|
||||
},
|
||||
oldName: function () {
|
||||
oldName: function() {
|
||||
if (!this.isListing) {
|
||||
return this.req.name
|
||||
}
|
||||
@ -58,7 +62,7 @@ export default {
|
||||
|
||||
return this.req.items[this.selected[0]].name
|
||||
},
|
||||
submit: async function () {
|
||||
submit: async function() {
|
||||
let oldLink = ''
|
||||
let newLink = ''
|
||||
|
||||
|
||||
@ -9,14 +9,18 @@
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="button button--flat button--red"
|
||||
@click="showConfirm"
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.cancel') }}</button>
|
||||
<button
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.replace')"
|
||||
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
|
||||
:title="$t('buttons.replace')"
|
||||
@click="showConfirm"
|
||||
>{{ $t('buttons.replace') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -25,7 +29,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'replace',
|
||||
name: 'Replace',
|
||||
computed: mapState(['showConfirm'])
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="card floating" id="share">
|
||||
<div id="share" class="card floating">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('buttons.share') }}</h2>
|
||||
</div>
|
||||
@ -7,7 +7,7 @@
|
||||
<div class="card-content">
|
||||
<ul>
|
||||
<li v-if="!hasPermanent">
|
||||
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
|
||||
<a :aria-label="$t('buttons.permalink')" @click="getPermalink">{{ $t('buttons.permalink') }}</a>
|
||||
</li>
|
||||
|
||||
<li v-for="link in links" :key="link.hash">
|
||||
@ -16,43 +16,53 @@
|
||||
<template v-else>{{ $t('permanent') }}</template>
|
||||
</a>
|
||||
|
||||
<button class="action"
|
||||
@click="deleteLink($event, link)"
|
||||
<button
|
||||
class="action"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||
:title="$t('buttons.delete')"
|
||||
@click="deleteLink($event, link)"
|
||||
><i class="material-icons">delete</i></button>
|
||||
|
||||
<button class="action copy-clipboard"
|
||||
<button
|
||||
class="action copy-clipboard"
|
||||
:data-clipboard-text="buildLink(link.hash)"
|
||||
:aria-label="$t('buttons.copyToClipboard')"
|
||||
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||
:title="$t('buttons.copyToClipboard')"
|
||||
><i class="material-icons">content_paste</i></button>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input v-focus
|
||||
<input
|
||||
v-model.trim="time"
|
||||
v-focus
|
||||
type="number"
|
||||
max="2147483647"
|
||||
min="0"
|
||||
@keyup.enter="submit"
|
||||
v-model.trim="time">
|
||||
>
|
||||
<select v-model="unit" :aria-label="$t('time.unit')">
|
||||
<option value="seconds">{{ $t('time.seconds') }}</option>
|
||||
<option value="minutes">{{ $t('time.minutes') }}</option>
|
||||
<option value="hours">{{ $t('time.hours') }}</option>
|
||||
<option value="days">{{ $t('time.days') }}</option>
|
||||
</select>
|
||||
<button class="action"
|
||||
@click="submit"
|
||||
<button
|
||||
class="action"
|
||||
:aria-label="$t('buttons.create')"
|
||||
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
|
||||
:title="$t('buttons.create')"
|
||||
@click="submit"
|
||||
><i class="material-icons">add</i></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat"
|
||||
@click="$store.commit('closeHovers')"
|
||||
<button
|
||||
class="button button--flat"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
|
||||
:title="$t('buttons.close')"
|
||||
@click="$store.commit('closeHovers')"
|
||||
>{{ $t('buttons.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -65,8 +75,8 @@ import moment from 'moment'
|
||||
import Clipboard from 'clipboard'
|
||||
|
||||
export default {
|
||||
name: 'share',
|
||||
data: function () {
|
||||
name: 'Share',
|
||||
data: function() {
|
||||
return {
|
||||
time: '',
|
||||
unit: 'hours',
|
||||
@ -76,9 +86,9 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'req', 'selected', 'selectedCount' ]),
|
||||
...mapGetters([ 'isListing' ]),
|
||||
url () {
|
||||
...mapState(['req', 'selected', 'selectedCount']),
|
||||
...mapGetters(['isListing']),
|
||||
url() {
|
||||
if (!this.isListing) {
|
||||
return this.$route.path
|
||||
}
|
||||
@ -91,13 +101,13 @@ export default {
|
||||
return this.req.items[this.selected[0]].url
|
||||
}
|
||||
},
|
||||
async beforeMount () {
|
||||
async beforeMount() {
|
||||
try {
|
||||
const links = await api.get(this.url)
|
||||
this.links = links
|
||||
this.sort()
|
||||
|
||||
for (let link of this.links) {
|
||||
for (const link of this.links) {
|
||||
if (link.expire === 0) {
|
||||
this.hasPermanent = true
|
||||
break
|
||||
@ -107,17 +117,17 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.clip = new Clipboard('.copy-clipboard')
|
||||
this.clip.on('success', () => {
|
||||
this.$showSuccess(this.$t('success.linkCopied'))
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
this.clip.destroy()
|
||||
},
|
||||
methods: {
|
||||
submit: async function () {
|
||||
submit: async function() {
|
||||
if (!this.time) return
|
||||
|
||||
try {
|
||||
@ -128,7 +138,7 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
getPermalink: async function () {
|
||||
getPermalink: async function() {
|
||||
try {
|
||||
const res = await api.create(this.url)
|
||||
this.links.push(res)
|
||||
@ -138,7 +148,7 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
deleteLink: async function (event, link) {
|
||||
deleteLink: async function(event, link) {
|
||||
event.preventDefault()
|
||||
try {
|
||||
await api.remove(link.hash)
|
||||
@ -148,13 +158,13 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
humanTime (time) {
|
||||
humanTime(time) {
|
||||
return moment(time * 1000).fromNow()
|
||||
},
|
||||
buildLink (hash) {
|
||||
buildLink(hash) {
|
||||
return `${window.location.origin}${baseURL}/share/${hash}`
|
||||
},
|
||||
sort () {
|
||||
sort() {
|
||||
this.links = this.links.sort((a, b) => {
|
||||
if (a.expire === 0) return -1
|
||||
if (b.expire === 0) return 1
|
||||
|
||||
@ -2,20 +2,20 @@
|
||||
<div>
|
||||
<h3>{{ $t('settings.userCommands') }}</h3>
|
||||
<p class="small">{{ $t('settings.userCommandsHelp') }} <i>git svn hg</i>.</p>
|
||||
<input class="input input--block" type="text" v-model.trim="raw">
|
||||
<input v-model.trim="raw" class="input input--block" type="text">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'permissions',
|
||||
name: 'Permissions',
|
||||
props: ['commands'],
|
||||
computed: {
|
||||
raw: {
|
||||
get () {
|
||||
get() {
|
||||
return this.commands.join(' ')
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
this.$emit('update:commands', value.split(' '))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<select v-on:change="change" :value="locale">
|
||||
<select :value="locale" @change="change">
|
||||
<option v-for="(language, value) in locales" :key="value" :value="value">{{ $t('languages.' + language) }}</option>
|
||||
</select>
|
||||
</template>
|
||||
@ -7,10 +7,10 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'languages',
|
||||
props: [ 'locale' ],
|
||||
name: 'Languages',
|
||||
props: ['locale'],
|
||||
data() {
|
||||
let dataObj = {
|
||||
const dataObj = {
|
||||
locales: {
|
||||
ar: 'ar',
|
||||
de: 'de',
|
||||
@ -31,14 +31,14 @@ export default {
|
||||
'zh-cn': 'zhCN',
|
||||
'zh-tw': 'zhTW'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(dataObj, "locales", { configurable: false, writable: false });
|
||||
Object.defineProperty(dataObj, 'locales', { configurable: false, writable: false })
|
||||
|
||||
return dataObj;
|
||||
return dataObj
|
||||
},
|
||||
methods: {
|
||||
change (event) {
|
||||
change(event) {
|
||||
this.$emit('update:locale', event.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,28 +3,28 @@
|
||||
<h3>{{ $t('settings.permissions') }}</h3>
|
||||
<p class="small">{{ $t('settings.permissionsHelp') }}</p>
|
||||
|
||||
<p><input type="checkbox" v-model="admin"> {{ $t('settings.administrator') }}</p>
|
||||
<p><input v-model="admin" type="checkbox"> {{ $t('settings.administrator') }}</p>
|
||||
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.create"> {{ $t('settings.perm.create') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
|
||||
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
|
||||
<p><input v-model="perm.create" type="checkbox" :disabled="admin"> {{ $t('settings.perm.create') }}</p>
|
||||
<p><input v-model="perm.delete" type="checkbox" :disabled="admin"> {{ $t('settings.perm.delete') }}</p>
|
||||
<p><input v-model="perm.download" type="checkbox" :disabled="admin"> {{ $t('settings.perm.download') }}</p>
|
||||
<p><input v-model="perm.modify" type="checkbox" :disabled="admin"> {{ $t('settings.perm.modify') }}</p>
|
||||
<p><input v-model="perm.execute" type="checkbox" :disabled="admin"> {{ $t('settings.perm.execute') }}</p>
|
||||
<p><input v-model="perm.rename" type="checkbox" :disabled="admin"> {{ $t('settings.perm.rename') }}</p>
|
||||
<p><input v-model="perm.share" type="checkbox" :disabled="admin"> {{ $t('settings.perm.share') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'permissions',
|
||||
name: 'Permissions',
|
||||
props: ['perm'],
|
||||
computed: {
|
||||
admin: {
|
||||
get () {
|
||||
get() {
|
||||
return this.perm.admin
|
||||
},
|
||||
set (value) {
|
||||
set(value) {
|
||||
if (value) {
|
||||
for (const key in this.perm) {
|
||||
this.perm[key] = true
|
||||
|
||||
@ -1,43 +1,45 @@
|
||||
<template>
|
||||
<form class="rules small">
|
||||
<div v-for="(rule, index) in rules" :key="index">
|
||||
<input type="checkbox" v-model="rule.regex"><label>Regex</label>
|
||||
<input type="checkbox" v-model="rule.allow"><label>Allow</label>
|
||||
<input v-model="rule.regex" type="checkbox"><label>Regex</label>
|
||||
<input v-model="rule.allow" type="checkbox"><label>Allow</label>
|
||||
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
v-if="rule.regex"
|
||||
v-model="rule.regexp.raw"
|
||||
:placeholder="$t('settings.insertRegex')" />
|
||||
<input
|
||||
@keypress.enter.prevent
|
||||
type="text"
|
||||
:placeholder="$t('settings.insertRegex')"
|
||||
@keypress.enter.prevent
|
||||
>
|
||||
<input
|
||||
v-else
|
||||
v-model="rule.path"
|
||||
:placeholder="$t('settings.insertPath')" />
|
||||
type="text"
|
||||
:placeholder="$t('settings.insertPath')"
|
||||
@keypress.enter.prevent
|
||||
>
|
||||
|
||||
<button class="button button--red" @click="remove($event, index)">-</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button" @click="create" default="false">{{ $t('buttons.new') }}</button>
|
||||
<button class="button" default="false" @click="create">{{ $t('buttons.new') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'rules-textarea',
|
||||
name: 'RulesTextarea',
|
||||
props: ['rules'],
|
||||
methods: {
|
||||
remove (event, index) {
|
||||
remove(event, index) {
|
||||
event.preventDefault()
|
||||
let rules = [ ...this.rules ]
|
||||
const rules = [...this.rules]
|
||||
rules.splice(index, 1)
|
||||
this.$emit('update:rules', [ ...rules ])
|
||||
this.$emit('update:rules', [...rules])
|
||||
},
|
||||
create (event) {
|
||||
create(event) {
|
||||
event.preventDefault()
|
||||
|
||||
this.$emit('update:rules', [
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<select v-on:change="change" :value="theme">
|
||||
<select :value="theme" @change="change">
|
||||
<option value="">{{ $t('settings.themes.light') }}</option>
|
||||
<option value="dark">{{ $t('settings.themes.dark') }}</option>
|
||||
</select>
|
||||
@ -7,10 +7,10 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'themes',
|
||||
props: [ 'theme' ],
|
||||
name: 'Themes',
|
||||
props: ['theme'],
|
||||
methods: {
|
||||
change (event) {
|
||||
change(event) {
|
||||
this.$emit('update:theme', event.target.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,26 +2,26 @@
|
||||
<div>
|
||||
<p v-if="!isDefault">
|
||||
<label for="username">{{ $t('settings.username') }}</label>
|
||||
<input class="input input--block" type="text" v-model="user.username" id="username">
|
||||
<input id="username" v-model="user.username" class="input input--block" type="text">
|
||||
</p>
|
||||
|
||||
<p v-if="!isDefault">
|
||||
<label for="password">{{ $t('settings.password') }}</label>
|
||||
<input class="input input--block" type="password" :placeholder="passwordPlaceholder" v-model="user.password" id="password">
|
||||
<input id="password" v-model="user.password" class="input input--block" type="password" :placeholder="passwordPlaceholder">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="scope">{{ $t('settings.scope') }}</label>
|
||||
<input class="input input--block" type="text" v-model="user.scope" id="scope">
|
||||
<input id="scope" v-model="user.scope" class="input input--block" type="text">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="locale">{{ $t('settings.language') }}</label>
|
||||
<languages class="input input--block" id="locale" :locale.sync="user.locale"></languages>
|
||||
<languages id="locale" class="input input--block" :locale.sync="user.locale" />
|
||||
</p>
|
||||
|
||||
<p v-if="!isDefault">
|
||||
<input type="checkbox" :disabled="user.perm.admin" v-model="user.lockPassword"> {{ $t('settings.lockPassword') }}
|
||||
<input v-model="user.lockPassword" type="checkbox" :disabled="user.perm.admin"> {{ $t('settings.lockPassword') }}
|
||||
</p>
|
||||
|
||||
<permissions :perm.sync="user.perm" />
|
||||
@ -42,21 +42,21 @@ import Permissions from './Permissions'
|
||||
import Commands from './Commands'
|
||||
|
||||
export default {
|
||||
name: 'user',
|
||||
name: 'User',
|
||||
components: {
|
||||
Permissions,
|
||||
Languages,
|
||||
Rules,
|
||||
Commands
|
||||
},
|
||||
props: [ 'user', 'isNew', 'isDefault' ],
|
||||
props: ['user', 'isNew', 'isDefault'],
|
||||
computed: {
|
||||
passwordPlaceholder () {
|
||||
passwordPlaceholder() {
|
||||
return this.isNew ? '' : this.$t('settings.avoidChanges')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'user.perm.admin': function () {
|
||||
'user.perm.admin': function() {
|
||||
if (!this.user.perm.admin) return
|
||||
this.user.lockPassword = false
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#listing h2 {
|
||||
margin: 0 0 0 0.5em;
|
||||
font-size: .9em;
|
||||
font-size: 0.9em;
|
||||
color: rgba(0, 0, 0, 0.38);
|
||||
font-weight: 500;
|
||||
}
|
||||
@ -10,7 +10,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#listing>div {
|
||||
#listing > div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
@ -22,7 +22,7 @@
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
color: #6f6f6f;
|
||||
transition: .1s ease background, .1s ease opacity;
|
||||
transition: 0.1s ease background, 0.1s ease opacity;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -52,6 +52,13 @@
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#listing .item img {
|
||||
width: 4em;
|
||||
height: 4em;
|
||||
margin-right: 0.1em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.message {
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
@ -64,7 +71,7 @@
|
||||
|
||||
.message i {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: .2em;
|
||||
margin-bottom: 0.2em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -75,14 +82,14 @@
|
||||
|
||||
#listing.mosaic .item {
|
||||
width: calc(33% - 1em);
|
||||
margin: .5em;
|
||||
margin: 0.5em;
|
||||
padding: 0.5em;
|
||||
border-radius: 0.2em;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
#listing.mosaic .item:hover {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) !important;
|
||||
}
|
||||
|
||||
#listing.mosaic .header {
|
||||
@ -116,7 +123,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#listing .item[aria-selected=true] {
|
||||
#listing .item[aria-selected="true"] {
|
||||
background: var(--blue) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
@ -129,6 +136,11 @@
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
#listing.list .item div:first-of-type img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
#listing.list .item div:last-of-type {
|
||||
width: calc(100% - 3em);
|
||||
display: flex;
|
||||
@ -151,19 +163,19 @@
|
||||
#listing.list .header i {
|
||||
font-size: 1.5em;
|
||||
vertical-align: middle;
|
||||
margin-left: .2em;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
#listing.list .item.header {
|
||||
display: flex !important;
|
||||
background: #fafafa;
|
||||
z-index: 999;
|
||||
padding: .85em;
|
||||
padding: 0.85em;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#listing.list .item.header>div:first-child {
|
||||
#listing.list .item.header > div:first-child {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@ -175,7 +187,7 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#listing.list .item.header>div:first-child {
|
||||
#listing.list .item.header > div:first-child {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@ -193,7 +205,7 @@
|
||||
|
||||
#listing.list .header i {
|
||||
opacity: 0;
|
||||
transition: .1s ease all;
|
||||
transition: 0.1s ease all;
|
||||
}
|
||||
|
||||
#listing.list .header p:hover i,
|
||||
@ -215,7 +227,7 @@
|
||||
height: 4em;
|
||||
padding: 0.5em 0.5em 0.5em 1em;
|
||||
justify-content: space-between;
|
||||
transition: .2s ease bottom;
|
||||
transition: 0.2s ease bottom;
|
||||
}
|
||||
|
||||
#listing #multiple-selection.active {
|
||||
|
||||
@ -22,7 +22,7 @@ import zhTW from './zh-tw.json'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
export function detectLocale () {
|
||||
export function detectLocale() {
|
||||
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
|
||||
switch (true) {
|
||||
case /^ar.*/i.test(locale):
|
||||
|
||||
@ -9,7 +9,7 @@ import App from '@/App'
|
||||
|
||||
sync(store, router)
|
||||
|
||||
async function start () {
|
||||
async function start() {
|
||||
if (loginPage) {
|
||||
await validateLogin()
|
||||
} else {
|
||||
@ -17,7 +17,7 @@ async function start () {
|
||||
}
|
||||
|
||||
if (recaptcha) {
|
||||
await new Promise (resolve => {
|
||||
await new Promise(resolve => {
|
||||
const check = () => {
|
||||
if (typeof window.grecaptcha === 'undefined') {
|
||||
setTimeout(check, 100)
|
||||
@ -35,8 +35,8 @@ async function start () {
|
||||
store,
|
||||
router,
|
||||
i18n,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
components: { App },
|
||||
template: '<App/>'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@ const mutations = {
|
||||
state.plugins.push(value)
|
||||
},
|
||||
removeSelected: (state, value) => {
|
||||
let i = state.selected.indexOf(value)
|
||||
const i = state.selected.indexOf(value)
|
||||
if (i === -1) return
|
||||
state.selected.splice(i, 1)
|
||||
},
|
||||
@ -62,7 +62,7 @@ const mutations = {
|
||||
updateUser: (state, value) => {
|
||||
if (typeof value !== 'object') return
|
||||
|
||||
for (let field in value) {
|
||||
for (const field in value) {
|
||||
if (field === 'locale') {
|
||||
moment.locale(value[field])
|
||||
i18n.default.locale = value[field]
|
||||
|
||||
@ -3,7 +3,7 @@ import router from '@/router'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { baseURL } from '@/utils/constants'
|
||||
|
||||
export function parseToken (token) {
|
||||
export function parseToken(token) {
|
||||
const parts = token.split('.')
|
||||
|
||||
if (parts.length !== 3) {
|
||||
@ -21,7 +21,7 @@ export function parseToken (token) {
|
||||
store.commit('setUser', data.user)
|
||||
}
|
||||
|
||||
export async function validateLogin () {
|
||||
export async function validateLogin() {
|
||||
try {
|
||||
if (localStorage.getItem('jwt')) {
|
||||
await renew(localStorage.getItem('jwt'))
|
||||
@ -31,7 +31,7 @@ export async function validateLogin () {
|
||||
}
|
||||
}
|
||||
|
||||
export async function login (username, password, recaptcha) {
|
||||
export async function login(username, password, recaptcha) {
|
||||
const data = { username, password, recaptcha }
|
||||
|
||||
const res = await fetch(`${baseURL}/api/login`, {
|
||||
@ -51,11 +51,11 @@ export async function login (username, password, recaptcha) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function renew (jwt) {
|
||||
export async function renew(jwt) {
|
||||
const res = await fetch(`${baseURL}/api/renew`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Auth': jwt,
|
||||
'X-Auth': jwt
|
||||
}
|
||||
})
|
||||
|
||||
@ -68,7 +68,7 @@ export async function renew (jwt) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function signup (username, password) {
|
||||
export async function signup(username, password) {
|
||||
const data = { username, password }
|
||||
|
||||
const res = await fetch(`${baseURL}/api/signup`, {
|
||||
@ -84,9 +84,9 @@ export async function signup (username, password) {
|
||||
}
|
||||
}
|
||||
|
||||
export function logout () {
|
||||
export function logout() {
|
||||
store.commit('setJWT', '')
|
||||
store.commit('setUser', null)
|
||||
localStorage.setItem('jwt', null)
|
||||
router.push({path: '/login'})
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
function loading (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function loading(button) {
|
||||
const el = document.querySelector(`#${button}-button > i`)
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
@ -16,8 +16,8 @@ function loading (button) {
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function done (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function done(button) {
|
||||
const el = document.querySelector(`#${button}-button > i`)
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
@ -33,8 +33,8 @@ function done (button) {
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function success (button) {
|
||||
let el = document.querySelector(`#${button}-button > i`)
|
||||
function success(button) {
|
||||
const el = document.querySelector(`#${button}-button > i`)
|
||||
|
||||
if (el === undefined || el === null) {
|
||||
console.log('Error getting button ' + button) // eslint-disable-line
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export default function (name) {
|
||||
let re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
|
||||
export default function(name) {
|
||||
const re = new RegExp('(?:(?:^|.*;\\s*)' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$')
|
||||
return document.cookie.replace(re, '$1')
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
export default function getRule (rules) {
|
||||
export default function getRule(rules) {
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
rules[i] = rules[i].toLowerCase()
|
||||
}
|
||||
|
||||
let result = null
|
||||
let find = Array.prototype.find
|
||||
const find = Array.prototype.find
|
||||
|
||||
find.call(document.styleSheets, styleSheet => {
|
||||
result = find.call(styleSheet.cssRules, cssRule => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
function removeLastDir (url) {
|
||||
function removeLastDir(url) {
|
||||
var arr = url.split('/')
|
||||
if (arr.pop() === '') {
|
||||
arr.pop()
|
||||
@ -10,14 +10,14 @@ function removeLastDir (url) {
|
||||
// this code borrow from mozilla
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
|
||||
function encodeRFC5987ValueChars(str) {
|
||||
return encodeURIComponent(str).
|
||||
return encodeURIComponent(str)
|
||||
// Note that although RFC3986 reserves "!", RFC5987 does not,
|
||||
// so we do not need to escape it
|
||||
replace(/['()]/g, escape). // i.e., %27 %28 %29
|
||||
replace(/\*/g, '%2A').
|
||||
.replace(/['()]/g, escape) // i.e., %27 %28 %29
|
||||
.replace(/\*/g, '%2A')
|
||||
// The following are not required for percent-encoding per RFC5987,
|
||||
// so we can allow for a little better readability over the wire: |`^
|
||||
replace(/%(?:7C|60|5E)/g, unescape);
|
||||
.replace(/%(?:7C|60|5E)/g, unescape)
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@ -24,19 +24,19 @@ Vue.prototype.$showSuccess = (message) => {
|
||||
}
|
||||
|
||||
Vue.prototype.$showError = (error) => {
|
||||
let btns = [
|
||||
Noty.button(i18n.t('buttons.close'), '', function () {
|
||||
const btns = [
|
||||
Noty.button(i18n.t('buttons.close'), '', function() {
|
||||
n.close()
|
||||
})
|
||||
]
|
||||
|
||||
if (!disableExternal) {
|
||||
btns.unshift(Noty.button(i18n.t('buttons.reportIssue'), '', function () {
|
||||
btns.unshift(Noty.button(i18n.t('buttons.reportIssue'), '', function() {
|
||||
window.open('https://github.com/filebrowser/filebrowser/issues/new/choose')
|
||||
}))
|
||||
}
|
||||
|
||||
let n = new Noty(Object.assign({}, notyDefault, {
|
||||
const n = new Noty(Object.assign({}, notyDefault, {
|
||||
text: error.message || error,
|
||||
type: 'error',
|
||||
timeout: null,
|
||||
@ -47,7 +47,7 @@ Vue.prototype.$showError = (error) => {
|
||||
}
|
||||
|
||||
Vue.directive('focus', {
|
||||
inserted: function (el) {
|
||||
inserted: function(el) {
|
||||
el.focus()
|
||||
}
|
||||
})
|
||||
|
||||
@ -11,13 +11,13 @@
|
||||
</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>
|
||||
<not-found v-if="error.message === '404'" />
|
||||
<forbidden v-else-if="error.message === '403'" />
|
||||
<internal-error v-else />
|
||||
</div>
|
||||
<editor v-else-if="isEditor"></editor>
|
||||
<listing :class="{ multiple }" v-else-if="isListing"></listing>
|
||||
<preview v-else-if="isPreview"></preview>
|
||||
<editor v-else-if="isEditor" />
|
||||
<listing v-else-if="isListing" :class="{ multiple }" />
|
||||
<preview v-else-if="isPreview" />
|
||||
<div v-else>
|
||||
<h2 class="message">
|
||||
<span>{{ $t('files.loading') }}</span>
|
||||
@ -35,12 +35,12 @@ import Listing from '@/components/files/Listing'
|
||||
import { files as api } from '@/api'
|
||||
import { mapGetters, mapState, mapMutations } from 'vuex'
|
||||
|
||||
function clean (path) {
|
||||
function clean(path) {
|
||||
return path.endsWith('/') ? path.slice(0, -1) : path
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'files',
|
||||
name: 'Files',
|
||||
components: {
|
||||
Forbidden,
|
||||
NotFound,
|
||||
@ -63,11 +63,11 @@ export default {
|
||||
'multiple',
|
||||
'loading'
|
||||
]),
|
||||
isPreview () {
|
||||
isPreview() {
|
||||
return !this.loading && !this.isListing && !this.isEditor
|
||||
},
|
||||
breadcrumbs () {
|
||||
let parts = this.$route.path.split('/')
|
||||
breadcrumbs() {
|
||||
const parts = this.$route.path.split('/')
|
||||
|
||||
if (parts[0] === '') {
|
||||
parts.shift()
|
||||
@ -77,7 +77,7 @@ export default {
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
let breadcrumbs = []
|
||||
const breadcrumbs = []
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (i === 0) {
|
||||
@ -100,34 +100,34 @@ export default {
|
||||
return breadcrumbs
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
error: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'reload': function () {
|
||||
'reload': function() {
|
||||
this.fetchData()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.keyEvent)
|
||||
window.addEventListener('scroll', this.scroll)
|
||||
},
|
||||
beforeDestroy () {
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.keyEvent)
|
||||
window.removeEventListener('scroll', this.scroll)
|
||||
},
|
||||
destroyed () {
|
||||
destroyed() {
|
||||
this.$store.commit('updateRequest', {})
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'setLoading' ]),
|
||||
async fetchData () {
|
||||
...mapMutations(['setLoading']),
|
||||
async fetchData() {
|
||||
// Reset view information.
|
||||
this.$store.commit('setReload', false)
|
||||
this.$store.commit('resetSelected')
|
||||
@ -157,7 +157,7 @@ export default {
|
||||
this.setLoading(false)
|
||||
}
|
||||
},
|
||||
keyEvent (event) {
|
||||
keyEvent(event) {
|
||||
// Esc!
|
||||
if (event.keyCode === 27) {
|
||||
this.$store.commit('closeHovers')
|
||||
@ -212,7 +212,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
scroll () {
|
||||
scroll() {
|
||||
if (this.req.kind !== 'listing' || this.$store.state.user.viewMode === 'mosaic') return
|
||||
|
||||
let top = 112 - window.scrollY
|
||||
@ -223,10 +223,10 @@ export default {
|
||||
|
||||
document.querySelector('#listing.list .item.header').style.top = top + 'px'
|
||||
},
|
||||
openSidebar () {
|
||||
openSidebar() {
|
||||
this.$store.commit('showHover', 'sidebar')
|
||||
},
|
||||
openSearch () {
|
||||
openSearch() {
|
||||
this.$store.commit('showHover', 'search')
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="progress">
|
||||
<div v-bind:style="{ width: $store.state.progress + '%' }"></div>
|
||||
<div :style="{ width: $store.state.progress + '%' }" />
|
||||
</div>
|
||||
<site-header></site-header>
|
||||
<sidebar></sidebar>
|
||||
<site-header />
|
||||
<sidebar />
|
||||
<main>
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
<shell v-if="isLogged && user.perm.execute" />
|
||||
</main>
|
||||
<prompts></prompts>
|
||||
<prompts />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -21,7 +21,7 @@ import SiteHeader from '@/components/Header'
|
||||
import Shell from '@/components/Shell'
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
name: 'Layout',
|
||||
components: {
|
||||
Sidebar,
|
||||
SiteHeader,
|
||||
@ -29,11 +29,11 @@ export default {
|
||||
Shell
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([ 'isLogged' ]),
|
||||
...mapState([ 'user' ])
|
||||
...mapGetters(['isLogged']),
|
||||
...mapState(['user'])
|
||||
},
|
||||
watch: {
|
||||
'$route': function () {
|
||||
'$route': function() {
|
||||
this.$store.commit('resetSelected')
|
||||
this.$store.commit('multiple', false)
|
||||
if (this.$store.state.show !== 'success') this.$store.commit('closeHovers')
|
||||
|
||||
@ -5,14 +5,14 @@
|
||||
<h1>{{ name }}</h1>
|
||||
<div v-if="error !== ''" class="wrong">{{ error }}</div>
|
||||
|
||||
<input class="input input--block" type="text" v-model="username" :placeholder="$t('login.username')">
|
||||
<input class="input input--block" type="password" v-model="password" :placeholder="$t('login.password')">
|
||||
<input class="input input--block" v-if="createMode" type="password" v-model="passwordConfirm" :placeholder="$t('login.passwordConfirm')" />
|
||||
<input v-model="username" class="input input--block" type="text" :placeholder="$t('login.username')">
|
||||
<input v-model="password" class="input input--block" type="password" :placeholder="$t('login.password')">
|
||||
<input v-if="createMode" v-model="passwordConfirm" class="input input--block" type="password" :placeholder="$t('login.passwordConfirm')">
|
||||
|
||||
<div v-if="recaptcha" id="recaptcha"></div>
|
||||
<div v-if="recaptcha" id="recaptcha" />
|
||||
<input class="button button--block" type="submit" :value="createMode ? $t('login.signup') : $t('login.submit')">
|
||||
|
||||
<p @click="toggleMode" v-if="signup">{{ createMode ? $t('login.loginInstead') : $t('login.createAnAccount') }}</p>
|
||||
<p v-if="signup" @click="toggleMode">{{ createMode ? $t('login.loginInstead') : $t('login.createAnAccount') }}</p>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
@ -22,13 +22,8 @@ import * as auth from '@/utils/auth'
|
||||
import { name, logoURL, recaptcha, recaptchaKey, signup } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
computed: {
|
||||
signup: () => signup,
|
||||
name: () => name,
|
||||
logoURL: () => logoURL
|
||||
},
|
||||
data: function () {
|
||||
name: 'Login',
|
||||
data: function() {
|
||||
return {
|
||||
createMode: false,
|
||||
error: '',
|
||||
@ -38,7 +33,12 @@ export default {
|
||||
passwordConfirm: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
computed: {
|
||||
signup: () => signup,
|
||||
name: () => name,
|
||||
logoURL: () => logoURL
|
||||
},
|
||||
mounted() {
|
||||
if (!recaptcha) return
|
||||
|
||||
window.grecaptcha.render('recaptcha', {
|
||||
@ -46,10 +46,10 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
toggleMode () {
|
||||
toggleMode() {
|
||||
this.createMode = !this.createMode
|
||||
},
|
||||
async submit (event) {
|
||||
async submit(event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@ -83,7 +83,7 @@ export default {
|
||||
await auth.login(this.username, this.password, captcha)
|
||||
this.$router.push({ path: redirect })
|
||||
} catch (e) {
|
||||
if (e.message == 409) {
|
||||
if (e.message === 409) {
|
||||
this.error = this.$t('login.usernameTaken')
|
||||
} else {
|
||||
this.error = this.$t('login.wrongCredentials')
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<ul id="nav" v-if="user.perm.admin">
|
||||
<ul v-if="user.perm.admin" id="nav">
|
||||
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
|
||||
<li :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
|
||||
<li :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
|
||||
</ul>
|
||||
|
||||
<router-view></router-view>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
computed: mapState([ 'user' ])
|
||||
name: 'Settings',
|
||||
computed: mapState(['user'])
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div class="share" v-if="loaded">
|
||||
<div v-if="loaded" class="share">
|
||||
<a target="_blank" :href="link">
|
||||
<div class="share__box">
|
||||
<div class="share__box__download" v-if="file.isDir">{{ $t('download.downloadFolder') }}</div>
|
||||
<div class="share__box__download" v-else>{{ $t('download.downloadFile') }}</div>
|
||||
<div v-if="file.isDir" class="share__box__download">{{ $t('download.downloadFolder') }}</div>
|
||||
<div v-else class="share__box__download">{{ $t('download.downloadFile') }}</div>
|
||||
<div class="share__box__info">
|
||||
<svg v-if="file.isDir" 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="M0 0h24v24H0z" fill="none"/>
|
||||
<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" />
|
||||
</svg>
|
||||
<svg v-else 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="M0 0h24v24H0z" fill="none"/>
|
||||
<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" />
|
||||
</svg>
|
||||
<h1 class="share__box__title">{{ file.name }}</h1>
|
||||
<qrcode-vue :value="fullLink" size="200" level="M"></qrcode-vue>
|
||||
<qrcode-vue :value="fullLink" size="200" level="M" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -27,7 +27,7 @@ import { baseURL } from '@/utils/constants'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
|
||||
export default {
|
||||
name: 'share',
|
||||
name: 'Share',
|
||||
components: {
|
||||
QrcodeVue
|
||||
},
|
||||
@ -36,25 +36,25 @@ export default {
|
||||
notFound: false,
|
||||
file: null
|
||||
}),
|
||||
computed: {
|
||||
hash: function() {
|
||||
return this.$route.params.pathMatch
|
||||
},
|
||||
link: function() {
|
||||
return `${baseURL}/api/public/dl/${this.hash}/${encodeURI(this.file.name)}`
|
||||
},
|
||||
fullLink: function() {
|
||||
return window.location.origin + this.link
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData'
|
||||
},
|
||||
created: function () {
|
||||
created: function() {
|
||||
this.fetchData()
|
||||
},
|
||||
computed: {
|
||||
hash: function () {
|
||||
return this.$route.params.pathMatch
|
||||
},
|
||||
link: function () {
|
||||
return `${baseURL}/api/public/dl/${this.hash}/${encodeURI(this.file.name)}`
|
||||
},
|
||||
fullLink: function () {
|
||||
return window.location.origin + this.link
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchData: async function () {
|
||||
fetchData: async function() {
|
||||
try {
|
||||
this.file = await api.getHash(this.hash)
|
||||
this.loaded = true
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'forbidden'}
|
||||
export default { name: 'Forbidden' }
|
||||
</script>
|
||||
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'not-found'}
|
||||
export default { name: 'NotFound' }
|
||||
</script>
|
||||
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {name: 'internal-error'}
|
||||
export default { name: 'InternalError' }
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="dashboard" v-if="settings !== null">
|
||||
<div v-if="settings !== null" class="dashboard">
|
||||
<form class="card" @submit.prevent="save">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.globalSettings') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<p><input type="checkbox" v-model="settings.signup"> {{ $t('settings.allowSignup') }}</p>
|
||||
<p><input v-model="settings.signup" type="checkbox"> {{ $t('settings.allowSignup') }}</p>
|
||||
|
||||
<p><input type="checkbox" v-model="settings.createUserDir"> {{ $t('settings.createUserDir') }}</p>
|
||||
<p><input v-model="settings.createUserDir" type="checkbox"> {{ $t('settings.createUserDir') }}</p>
|
||||
|
||||
<h3>{{ $t('settings.rules') }}</h3>
|
||||
<p class="small">{{ $t('settings.globalRules') }}</p>
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
<h3>{{ $t('settings.executeOnShell') }}</h3>
|
||||
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
|
||||
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
|
||||
<input v-model="settings.shell" class="input input--block" type="text" placeholder="bash -c, cmd /c, ...">
|
||||
|
||||
<h3>{{ $t('settings.branding') }}</h3>
|
||||
|
||||
@ -25,23 +25,23 @@
|
||||
</i18n>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" v-model="settings.branding.disableExternal" id="branding-links" />
|
||||
<input id="branding-links" v-model="settings.branding.disableExternal" type="checkbox">
|
||||
{{ $t('settings.disableExternalLinks') }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="theme">{{ $t('settings.themes.title') }}</label>
|
||||
<themes class="input input--block" :theme.sync="settings.branding.theme" id="theme"></themes>
|
||||
<themes id="theme" class="input input--block" :theme.sync="settings.branding.theme" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="branding-name">{{ $t('settings.instanceName') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.name" id="branding-name" />
|
||||
<input id="branding-name" v-model="settings.branding.name" class="input input--block" type="text">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="branding-files">{{ $t('settings.brandingDirectoryPath') }}</label>
|
||||
<input class="input input--block" type="text" v-model="settings.branding.files" id="branding-files" />
|
||||
<input id="branding-files" v-model="settings.branding.files" class="input input--block" type="text">
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="card-content">
|
||||
<p class="small">{{ $t('settings.defaultUserDescription') }}</p>
|
||||
|
||||
<user-form :isNew="false" :isDefault="true" :user.sync="settings.defaults" />
|
||||
<user-form :is-new="false" :is-default="true" :user.sync="settings.defaults" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@ -86,7 +86,7 @@
|
||||
<i class="material-icons">arrow_drop_down</i>
|
||||
</label>
|
||||
<div class="collapse">
|
||||
<textarea class="input input--block input--textarea" v-model.trim="command.value"></textarea>
|
||||
<textarea v-model.trim="command.value" class="input input--block input--textarea" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -106,25 +106,25 @@ import Rules from '@/components/settings/Rules'
|
||||
import Themes from '@/components/settings/Themes'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
name: 'Settings',
|
||||
components: {
|
||||
Themes,
|
||||
UserForm,
|
||||
Rules
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
originalSettings: null,
|
||||
settings: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ])
|
||||
...mapState(['user'])
|
||||
},
|
||||
async created () {
|
||||
async created() {
|
||||
try {
|
||||
const original = await api.get()
|
||||
let settings = { ...original, commands: [] }
|
||||
const settings = { ...original, commands: [] }
|
||||
|
||||
for (const key in original.commands) {
|
||||
settings.commands.push({
|
||||
@ -142,9 +142,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
capitalize (name, where = '_') {
|
||||
capitalize(name, where = '_') {
|
||||
if (where === 'caps') where = /(?=[A-Z])/
|
||||
let splitted = name.split(where)
|
||||
const splitted = name.split(where)
|
||||
name = ''
|
||||
|
||||
for (let i = 0; i < splitted.length; i++) {
|
||||
@ -153,8 +153,8 @@ export default {
|
||||
|
||||
return name.slice(0, -1)
|
||||
},
|
||||
async save () {
|
||||
let settings = {
|
||||
async save() {
|
||||
const settings = {
|
||||
...this.settings,
|
||||
shell: this.settings.shell.trim().split(' ').filter(s => s !== ''),
|
||||
commands: {}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
<div class="card-content">
|
||||
<h3>{{ $t('settings.language') }}</h3>
|
||||
<languages class="input input--block" :locale.sync="locale"></languages>
|
||||
<languages class="input input--block" :locale.sync="locale" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@ -15,14 +15,14 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="card" v-if="!user.lockPassword" @submit="updatePassword">
|
||||
<form v-if="!user.lockPassword" class="card" @submit="updatePassword">
|
||||
<div class="card-title">
|
||||
<h2>{{ $t('settings.changePassword') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" v-model="password" name="password">
|
||||
<input :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" v-model="passwordConf" name="password">
|
||||
<input v-model="password" :class="passwordClass" type="password" :placeholder="$t('settings.newPassword')" name="password">
|
||||
<input v-model="passwordConf" :class="passwordClass" type="password" :placeholder="$t('settings.newPasswordConfirm')" name="password">
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
@ -38,11 +38,11 @@ import { users as api } from '@/api'
|
||||
import Languages from '@/components/settings/Languages'
|
||||
|
||||
export default {
|
||||
name: 'settings',
|
||||
name: 'Settings',
|
||||
components: {
|
||||
Languages
|
||||
},
|
||||
data: function () {
|
||||
data: function() {
|
||||
return {
|
||||
password: '',
|
||||
passwordConf: '',
|
||||
@ -50,8 +50,8 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState([ 'user' ]),
|
||||
passwordClass () {
|
||||
...mapState(['user']),
|
||||
passwordClass() {
|
||||
const baseClass = 'input input--block'
|
||||
|
||||
if (this.password === '' && this.passwordConf === '') {
|
||||
@ -65,12 +65,12 @@ export default {
|
||||
return `${baseClass} input--red`
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.locale = this.user.locale
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'updateUser' ]),
|
||||
async updatePassword (event) {
|
||||
...mapMutations(['updateUser']),
|
||||
async updatePassword(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (this.password !== this.passwordConf || this.password === '') {
|
||||
@ -86,7 +86,7 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
async updateSettings (event) {
|
||||
async updateSettings(event) {
|
||||
event.preventDefault()
|
||||
|
||||
try {
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<form v-if="loaded" @submit="save" class="card">
|
||||
<form v-if="loaded" class="card" @submit="save">
|
||||
<div class="card-title">
|
||||
<h2 v-if="user.id === 0">{{ $t('settings.newUser') }}</h2>
|
||||
<h2 v-else>{{ $t('settings.user') }} {{ user.username }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-content">
|
||||
<user-form :user.sync="user" :isDefault="false" :isNew="isNew" />
|
||||
<user-form :user.sync="user" :is-default="false" :is-new="isNew" />
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button
|
||||
v-if="!isNew"
|
||||
@click.prevent="deletePrompt"
|
||||
type="button"
|
||||
class="button button--flat button--red"
|
||||
:aria-label="$t('buttons.delete')"
|
||||
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
|
||||
:title="$t('buttons.delete')"
|
||||
@click.prevent="deletePrompt"
|
||||
>{{ $t('buttons.delete') }}</button>
|
||||
<input
|
||||
class="button button--flat"
|
||||
type="submit"
|
||||
:value="$t('buttons.save')">
|
||||
:value="$t('buttons.save')"
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -31,15 +33,19 @@
|
||||
</div>
|
||||
|
||||
<div class="card-action">
|
||||
<button class="button button--flat button--grey"
|
||||
@click="closeHovers"
|
||||
<button
|
||||
v-focus
|
||||
class="button button--flat button--grey"
|
||||
:aria-label="$t('buttons.cancel')"
|
||||
:title="$t('buttons.cancel')">
|
||||
:title="$t('buttons.cancel')"
|
||||
@click="closeHovers"
|
||||
>
|
||||
{{ $t('buttons.cancel') }}
|
||||
</button>
|
||||
<button class="button button--flat"
|
||||
@click="deleteUser">
|
||||
<button
|
||||
class="button button--flat"
|
||||
@click="deleteUser"
|
||||
>
|
||||
{{ $t('buttons.delete') }}
|
||||
</button>
|
||||
</div>
|
||||
@ -54,7 +60,7 @@ import UserForm from '@/components/settings/UserForm'
|
||||
import deepClone from 'lodash.clonedeep'
|
||||
|
||||
export default {
|
||||
name: 'user',
|
||||
name: 'User',
|
||||
components: {
|
||||
UserForm
|
||||
},
|
||||
@ -65,27 +71,27 @@ export default {
|
||||
loaded: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
computed: {
|
||||
isNew () {
|
||||
isNew() {
|
||||
return this.$route.path === '/settings/users/new'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route': 'fetchData',
|
||||
'user.perm.admin': function () {
|
||||
'user.perm.admin': function() {
|
||||
if (!this.user.perm.admin) return
|
||||
this.user.lockPassword = false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([ 'closeHovers', 'showHover', 'setUser' ]),
|
||||
async fetchData () {
|
||||
...mapMutations(['closeHovers', 'showHover', 'setUser']),
|
||||
async fetchData() {
|
||||
try {
|
||||
if (this.isNew) {
|
||||
let { defaults } = await settings.get()
|
||||
const { defaults } = await settings.get()
|
||||
this.user = {
|
||||
...defaults,
|
||||
username: '',
|
||||
@ -104,10 +110,10 @@ export default {
|
||||
this.$router.push({ path: '/settings/users/new' })
|
||||
}
|
||||
},
|
||||
deletePrompt () {
|
||||
deletePrompt() {
|
||||
this.showHover('deleteUser')
|
||||
},
|
||||
async deleteUser (event) {
|
||||
async deleteUser(event) {
|
||||
event.preventDefault()
|
||||
|
||||
try {
|
||||
@ -118,9 +124,9 @@ export default {
|
||||
this.$showError(e)
|
||||
}
|
||||
},
|
||||
async save (event) {
|
||||
async save(event) {
|
||||
event.preventDefault()
|
||||
let user = {
|
||||
const user = {
|
||||
...this.originalUser,
|
||||
...this.user
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<th>{{ $t('settings.username') }}</th>
|
||||
<th>{{ $t('settings.admin') }}</th>
|
||||
<th>{{ $t('settings.scope') }}</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
@ -31,13 +31,13 @@
|
||||
import { users as api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'users',
|
||||
data: function () {
|
||||
name: 'Users',
|
||||
data: function() {
|
||||
return {
|
||||
users: []
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
async created() {
|
||||
try {
|
||||
this.users = await api.getAll()
|
||||
} catch (e) {
|
||||
|
||||
2
go.mod
2
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/caddyserver/caddy v1.0.3
|
||||
github.com/daaku/go.zipexe v1.0.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/mux v1.7.3
|
||||
@ -29,6 +30,7 @@ require (
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
go.etcd.io/bbolt v1.3.3
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect
|
||||
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
|
||||
5
go.sum
5
go.sum
@ -43,6 +43,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
@ -239,6 +241,9 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8=
|
||||
|
||||
@ -59,6 +59,7 @@ func NewHandler(store *storage.Storage, server *settings.Server) (http.Handler,
|
||||
api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT")
|
||||
|
||||
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
|
||||
api.PathPrefix("/thumbnail").Handler(monkey(thumbnailHandler, "/api/thumbnail")).Methods("GET")
|
||||
api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET")
|
||||
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")
|
||||
|
||||
|
||||
92
http/thumbnail.go
Normal file
92
http/thumbnail.go
Normal file
@ -0,0 +1,92 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/files"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var thumbnailHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
if !d.user.Perm.Download {
|
||||
return http.StatusAccepted, nil
|
||||
}
|
||||
|
||||
file, err := files.NewFileInfo(files.FileOptions{
|
||||
Fs: d.user.Fs,
|
||||
Path: r.URL.Path,
|
||||
Modify: d.user.Perm.Modify,
|
||||
Expand: true,
|
||||
Checker: d,
|
||||
})
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
if file.IsDir || file.Type != "image" {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
return thumbnailFileHandler(w, r, file)
|
||||
})
|
||||
|
||||
func thumbnailFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo) (int, error) {
|
||||
fd, err := file.Fs.Open(file.Path)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
if r.URL.Query().Get("inline") == "true" {
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
} else {
|
||||
// As per RFC6266 section 4.3
|
||||
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(file.Name))
|
||||
}
|
||||
|
||||
srcImg, err := imaging.Decode(fd, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
dstImg := fitResizeImage(srcImg)
|
||||
w.Header().Add("Content-Type", mime.TypeByExtension(file.Extension))
|
||||
err = func() error {
|
||||
switch file.Extension {
|
||||
case ".jpg", ".jpeg":
|
||||
return jpeg.Encode(w, dstImg, nil)
|
||||
case ".png":
|
||||
return png.Encode(w, dstImg)
|
||||
case ".gif":
|
||||
return gif.Encode(w, dstImg, nil)
|
||||
default:
|
||||
return errors.ErrNotExist
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
const maxSize = 1080
|
||||
|
||||
func fitResizeImage(srcImage image.Image) image.Image {
|
||||
width := srcImage.Bounds().Dx()
|
||||
height := srcImage.Bounds().Dy()
|
||||
if width > maxSize && width > height {
|
||||
width = maxSize
|
||||
height = 0
|
||||
} else if height > maxSize && height > width {
|
||||
width = 0
|
||||
height = maxSize
|
||||
} else {
|
||||
return srcImage
|
||||
}
|
||||
return imaging.Resize(srcImage, width, height, imaging.NearestNeighbor)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user