Merge pull request #1026 from ramiresviana/fixes

This commit is contained in:
Oleg Lobanov 2020-07-17 17:41:17 +02:00 committed by GitHub
commit 1790df2090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 489 additions and 144 deletions

View File

@ -2,6 +2,7 @@ package fileutils
import (
"io"
"os"
"path/filepath"
"github.com/spf13/afero"
@ -25,7 +26,7 @@ func CopyFile(fs afero.Fs, source, dest string) error {
}
// Create the destination file.
dst, err := fs.Create(dest)
dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}

View File

@ -1,7 +1,7 @@
:root {
--background: #121212;
--surfacePrimary: #171819;
--surfaceSecondary: #212528;
--background: #141D24;
--surfacePrimary: #20292F;
--surfaceSecondary: #3A4147;
--divider: rgba(255, 255, 255, 0.12);
--icon: #ffffff;
--textPrimary: rgba(255, 255, 255, 0.87);
@ -16,7 +16,7 @@ body {
#loading {
background: var(--background);
}
#loading .spinner div {
#loading .spinner div, #previewer .loading .spinner div {
background: var(--icon);
}
@ -30,25 +30,34 @@ header {
#search #input {
background: var(--surfaceSecondary);
border-color: var(--surfacePrimary);
}
#search.active #input,
#search.active .boxes {
#search #input input::placeholder {
color: var(--textSecondary);
}
#search.active #input {
background: var(--surfacePrimary);
}
#search.active input {
color: var(--textPrimary);
}
#search.active #result {
#search #result {
background: var(--background);
color: var(--textPrimary);
}
#search.active .boxes h3 {
#search .boxes {
background: var(--surfaceSecondary);
}
#search .boxes h3 {
color: var(--textPrimary);
}
.action {
color: var(--textPrimary) !important;
}
.action:hover {
background-color: rgba(255, 255, 255, .1);
}
.action i {
color: var(--icon) !important;
}
@ -93,6 +102,10 @@ nav > div {
background: var(--background);
}
.message {
color: var(--textPrimary);
}
.card {
background: var(--surfacePrimary);
color: var(--textPrimary);
@ -106,9 +119,23 @@ nav > div {
.dashboard p label {
color: var(--textPrimary);
}
.card#share ul li input,
.card#share ul li select,
.input {
background: var(--surfaceSecondary);
color: var(--textPrimary);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.input:hover,
.input:focus {
border-color: rgba(255, 255, 255, 0.15);
}
.input--red {
background: #73302D;
}
.input--green {
background: #147A41;
}
.dashboard #nav li,
@ -119,10 +146,27 @@ nav > div {
color: var(--textPrimary);
}
table th {
color: var(--textSecondary);
}
.file-list li:hover {
background: var(--surfaceSecondary);
}
.file-list li:before {
color: var(--textSecondary);
}
.file-list li[aria-selected=true]:before {
color: var(--icon);
}
.shell {
background: var(--surfacePrimary);
color: var(--textPrimary);
}
.shell__result {
border-top: 1px solid var(--divider);
}
#editor-container {
background: var(--background);
@ -146,3 +190,11 @@ nav > div {
background: var(--surfaceSecondary) !important;
}
}
.share__box, .share__box__download {
background: var(--surfaceSecondary) !important;
color: var(--textPrimary);
}
.share__box__download {
border-bottom-color: var(--divider);
}

View File

@ -94,9 +94,6 @@ export async function post (url, content = '', overwrite = false, onupload) {
request.upload.onprogress = onupload
}
// Send a message to user before closing the tab during file upload
window.onbeforeunload = () => "Files are being uploaded."
request.onload = () => {
if (request.status === 200) {
resolve(request.responseText)
@ -112,29 +109,28 @@ export async function post (url, content = '', overwrite = false, onupload) {
}
request.send(content)
// Upload is done no more message before closing the tab
}).finally(() => { window.onbeforeunload = null })
})
}
function moveCopy (items, copy = false) {
function moveCopy (items, copy = false, overwrite = false, rename = false) {
let promises = []
for (let item of items) {
const from = removePrefix(item.from)
const to = encodeURIComponent(removePrefix(item.to))
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}`
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
promises.push(resourceAction(url, 'PATCH'))
}
return Promise.all(promises)
}
export function move (items) {
return moveCopy(items)
export function move (items, overwrite = false, rename = false) {
return moveCopy(items, false, overwrite, rename)
}
export function copy (items) {
return moveCopy(items, true)
export function copy (items, overwrite = false, rename = false) {
return moveCopy(items, true, overwrite, rename)
}
export async function checksum (url, algo) {

View File

@ -10,10 +10,12 @@
@mouseup="mouseUp"
@wheel="wheelMove"
>
<img :src="src" class="image-ex-img" ref="imgex" @load="setCenter">
<img :src="src" class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad">
</div>
</template>
<script>
import throttle from 'lodash.throttle'
export default {
props: {
src: String,
@ -50,7 +52,12 @@ export default {
inDrag: false,
lastTouchDistance: 0,
moveDisabled: false,
disabledTimer: null
disabledTimer: null,
imageLoaded: false,
position: {
center: { x: 0, y: 0 },
relative: { x: 0, y: 0 }
}
}
},
mounted() {
@ -63,24 +70,47 @@ export default {
if (getComputedStyle(container).height === "0px") {
container.style.height = "100%"
}
window.addEventListener('resize', this.onResize)
},
beforeDestroy () {
window.removeEventListener('resize', this.onResize)
document.removeEventListener('mouseup', this.onMouseUp)
},
methods: {
onLoad() {
let img = this.$refs.imgex
this.imageLoaded = true
if (img === undefined) {
return
}
img.classList.remove('image-ex-img-center')
this.setCenter()
img.classList.add('image-ex-img-ready')
document.addEventListener('mouseup', this.onMouseUp)
},
onMouseUp() {
this.inDrag = false
},
onResize: throttle(function() {
if (this.imageLoaded) {
this.setCenter()
this.doMove(this.position.relative.x, this.position.relative.y)
}
}, 100),
setCenter() {
let container = this.$refs.container
let img = this.$refs.imgex
let rate = Math.min(
container.clientWidth / img.clientWidth,
container.clientHeight / img.clientHeight
)
if (!this.autofill && rate > 1) {
rate = 1
}
// height will be auto set
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 )
this.position.center.x = Math.floor((container.clientWidth - img.clientWidth) / 2)
this.position.center.y = Math.floor((container.clientHeight - img.clientHeight) / 2)
img.style.left = this.position.center.x + 'px'
img.style.top = this.position.center.y + 'px'
},
mousedownStart(event) {
this.lastX = null
@ -159,8 +189,22 @@ export default {
},
doMove(x, y) {
let style = this.$refs.imgex.style
style.left = `${this.pxStringToNumber(style.left) + x}px`
style.top = `${this.pxStringToNumber(style.top) + y}px`
let posX = this.pxStringToNumber(style.left) + x
let posY = this.pxStringToNumber(style.top) + y
style.left = posX + 'px'
style.top = posY + 'px'
this.position.relative.x = Math.abs(this.position.center.x - posX)
this.position.relative.y = Math.abs(this.position.center.y - posY)
if (posX < this.position.center.x) {
this.position.relative.x = this.position.relative.x * -1
}
if (posY < this.position.center.y) {
this.position.relative.y = this.position.relative.y * -1
}
},
wheelMove(event) {
this.scale += (event.wheelDeltaY / 100) * this.zoomStep
@ -185,9 +229,20 @@ export default {
}
.image-ex-img {
position: absolute;
}
.image-ex-img-center {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
transition: none;
}
.image-ex-img-ready {
left: 0;
top: 0;
position: absolute;
transition: transform 0.1s ease;
}
</style>

View File

@ -248,7 +248,8 @@ export default {
this.$store.commit('updateClipboard', {
key: key,
items: items
items: items,
path: this.$route.path
})
},
paste (event) {
@ -261,23 +262,56 @@ export default {
for (let 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 })
items.push({ from, to, name: item.name })
}
if (items.length === 0) {
return
}
if (this.$store.state.clipboard.key === 'x') {
api.move(items).then(() => {
let action = (overwrite, rename) => {
api.copy(items, overwrite, rename).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
if (this.$store.state.clipboard.key === 'x') {
action = (overwrite, rename) => {
api.move(items, overwrite, rename).then(() => {
this.$store.commit('resetClipboard')
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
}
if (this.$store.state.clipboard.path == this.$route.path) {
action(false, true)
return
}
api.copy(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
let conflict = upload.checkConflict(items, this.req.items)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
},
resizeEvent () {
// Update the columns size based on the window width.

View File

@ -36,6 +36,7 @@ import { mapMutations, mapGetters, mapState } from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import { files as api } from '@/api'
import * as upload from '@/utils/upload'
export default {
name: 'item',
@ -110,26 +111,61 @@ export default {
el.style.opacity = 1
},
drop: function (event) {
drop: async function (event) {
if (!this.canDrop) return
event.preventDefault()
if (this.selectedCount === 0) return
let el = event.target
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
el = el.parentElement
}
}
let items = []
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
to: this.url + this.req.items[i].name
to: this.url + this.req.items[i].name,
name: this.req.items[i].name
})
}
api.move(items)
.then(() => {
let base = el.querySelector('.name').innerHTML + '/'
let path = this.$route.path + base
let baseItems = (await api.fetch(path)).items
let action = (overwrite, rename) => {
api.move(items, overwrite, rename).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
}
let conflict = upload.checkConflict(items, baseItems)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
.catch(this.$showError)
return
}
action(overwrite, rename)
},
click: function (event) {
if (this.selectedCount !== 0) event.preventDefault()

View File

@ -5,10 +5,22 @@
<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>
<div class="title">
<span>{{ this.name }}</span>
</div>
<rename-button :disabled="loading" v-if="user.perm.rename"></rename-button>
<delete-button :disabled="loading" v-if="user.perm.delete"></delete-button>
<download-button :disabled="loading" v-if="user.perm.download"></download-button>
<info-button :disabled="loading"></info-button>
</div>
<div class="loading" v-if="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
@ -18,6 +30,7 @@
<i class="material-icons">chevron_right</i>
</button>
<template v-if="!loading">
<div class="preview">
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
@ -37,6 +50,7 @@
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
</a>
</div>
</template>
</div>
</template>
@ -72,11 +86,12 @@ export default {
previousLink: '',
nextLink: '',
listing: null,
name: '',
subtitles: []
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt']),
...mapState(['req', 'user', 'oldReq', 'jwt', 'loading']),
hasPrevious () {
return (this.previousLink !== '')
},
@ -96,30 +111,24 @@ export default {
return `${this.previewUrl}&inline=true`
}
},
watch: {
$route: function () {
this.updatePreview()
}
},
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`)
}
try {
if (this.oldReq.items) {
this.updateLinks(this.oldReq.items)
} else {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.updateLinks(res.items)
}
} catch (e) {
this.$showError(e)
}
this.$store.commit('setPreviewMode', true)
this.listing = this.oldReq.items
this.updatePreview()
},
beforeDestroy () {
window.removeEventListener('keyup', this.key)
this.$store.commit('setPreviewMode', false)
},
methods: {
back () {
this.$store.commit('setPreviewMode', false)
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
@ -138,22 +147,42 @@ export default {
if (this.hasPrevious) this.prev()
}
},
updateLinks (items) {
for (let i = 0; i < items.length; i++) {
if (items[i].name !== this.req.name) {
async updatePreview () {
if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`)
}
let dirs = this.$route.fullPath.split("/")
this.name = decodeURIComponent(dirs[dirs.length - 1])
if (!this.listing) {
try {
const path = url.removeLastDir(this.$route.path)
const res = await api.fetch(path)
this.listing = res.items
} catch (e) {
this.$showError(e)
}
}
this.previousLink = ''
this.nextLink = ''
for (let i = 0; i < this.listing.length; i++) {
if (this.listing[i].name !== this.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(items[j].type)) {
this.previousLink = items[j].url
if (mediaTypes.includes(this.listing[j].type)) {
this.previousLink = this.listing[j].url
break
}
}
for (let j = i + 1; j < items.length; j++) {
if (mediaTypes.includes(items[j].type)) {
this.nextLink = items[j].url
for (let j = i + 1; j < this.listing.length; j++) {
if (mediaTypes.includes(this.listing[j].type)) {
this.nextLink = this.listing[j].url
break
}
}

View File

@ -16,7 +16,6 @@
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button class="button button--flat"
@click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div>
@ -28,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default {
name: 'copy',
@ -42,25 +42,66 @@ export default {
methods: {
copy: async function (event) {
event.preventDefault()
buttons.loading('copy')
let items = []
// Create a new promise for each file.
for (let item of this.selected) {
items.push({
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name)
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name
})
}
try {
await api.copy(items)
let action = async (overwrite, rename) => {
buttons.loading('copy')
await api.copy(items, overwrite, rename).then(() => {
buttons.success('copy')
if (this.$route.path === this.dest) {
this.$store.commit('setReload', true)
return
}
this.$router.push({ path: this.dest })
} catch (e) {
}).catch((e) => {
buttons.done('copy')
this.$showError(e)
})
}
if (this.$route.path === this.dest) {
this.$store.commit('closeHovers')
action(false, true)
return
}
let dstItems = (await api.fetch(this.dest)).items
let conflict = upload.checkConflict(items, dstItems)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
}
}
}

View File

@ -41,19 +41,7 @@ export default {
}
},
mounted () {
// If we're showing this on a listing,
// we can use the current request object
// to fill the move options.
if (this.req.kind === 'listing') {
this.fillOptions(this.req)
return
}
// Otherwise, we must be on a preview or editor
// so we fetch the data from the previous directory.
files.fetch(url.removeLastDir(this.$route.path))
.then(this.fillOptions)
.catch(this.$showError)
},
methods: {
fillOptions (req) {

View File

@ -27,6 +27,7 @@ import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import * as upload from '@/utils/upload'
export default {
name: 'move',
@ -41,26 +42,51 @@ export default {
methods: {
move: async function (event) {
event.preventDefault()
buttons.loading('move')
let items = []
for (let item of this.selected) {
items.push({
from: this.req.items[item].url,
to: this.dest + encodeURIComponent(this.req.items[item].name)
to: this.dest + encodeURIComponent(this.req.items[item].name),
name: this.req.items[item].name
})
}
try {
api.move(items)
let action = async (overwrite, rename) => {
buttons.loading('move')
await api.move(items, overwrite, rename).then(() => {
buttons.success('move')
this.$router.push({ path: this.dest })
} catch (e) {
}).catch((e) => {
buttons.done('move')
this.$showError(e)
})
}
let dstItems = (await api.fetch(this.dest)).items
let conflict = upload.checkConflict(items, dstItems)
let overwrite = false
let rename = false
if (conflict) {
this.$store.commit('showHover', {
prompt: 'replace-rename',
confirm: (event, option) => {
overwrite = option == 'overwrite'
rename = option == 'rename'
event.preventDefault()
this.$store.commit('closeHovers')
action(overwrite, rename)
}
})
return
}
action(overwrite, rename)
}
}
}

View File

@ -16,6 +16,7 @@ import Copy from './Copy'
import NewFile from './NewFile'
import NewDir from './NewDir'
import Replace from './Replace'
import ReplaceRename from './ReplaceRename'
import Share from './Share'
import Upload from './Upload'
import { mapState } from 'vuex'
@ -35,6 +36,7 @@ export default {
NewDir,
Help,
Replace,
ReplaceRename,
Upload
},
data: function () {
@ -87,6 +89,7 @@ export default {
'newDir',
'download',
'replace',
'replace-rename',
'share',
'upload'
].indexOf(this.show) >= 0;

View File

@ -0,0 +1,35 @@
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.replace') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.replaceMessage') }}</p>
</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')">{{ $t('buttons.cancel') }}</button>
<button class="button button--flat button--blue"
@click="(event) => showConfirm(event, 'rename')"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
<button class="button button--flat button--red"
@click="(event) => showConfirm(event, 'overwrite')"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')">{{ $t('buttons.replace') }}</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'replace-rename',
computed: mapState(['showConfirm'])
}
</script>

View File

@ -25,8 +25,8 @@
background: var(--red);
}
.button--red:hover {
background: var(--dark-red);
.button--blue {
background: var(--blue);
}
.button--flat {

View File

@ -125,8 +125,13 @@
height: 3.7em;
}
#previewer .action:first-of-type {
#previewer .bar .title {
margin-right: auto;
padding: 0 1em;
line-height: 2.7em;
overflow: hidden;
word-break: break-word;
color: #fff;
}
#previewer .action i {
@ -219,6 +224,11 @@
font-size: 1.2em;
}
#previewer .loading {
height: 100%;
width: 100%;
}
#editor-container #editor {
height: calc(100vh - 8.2em);
}

View File

@ -23,7 +23,8 @@ const state = {
show: null,
showShell: false,
showMessage: null,
showConfirm: null
showConfirm: null,
previewMode: false
}
export default new Vuex.Store({

View File

@ -37,6 +37,11 @@ const mutations = {
}
}
const beforeUnload = (event) => {
event.preventDefault()
event.returnValue = ''
}
const actions = {
upload: (context, item) => {
let uploadsCount = Object.keys(context.state.uploads).length;
@ -45,6 +50,7 @@ const actions = {
let isUploadsEmpty = uploadsCount == 0
if (isQueueEmpty && isUploadsEmpty) {
window.addEventListener('beforeunload', beforeUnload)
buttons.loading('upload')
}
@ -67,6 +73,7 @@ const actions = {
let canProcess = isBellowLimit && !isQueueEmpty
if (isFinished) {
window.removeEventListener('beforeunload', beforeUnload)
buttons.success('upload')
context.commit('reset')
context.commit('setReload', true, { root: true })

View File

@ -78,10 +78,14 @@ const mutations = {
updateClipboard: (state, value) => {
state.clipboard.key = value.key
state.clipboard.items = value.items
state.clipboard.path = value.path
},
resetClipboard: (state) => {
state.clipboard.key = ''
state.clipboard.items = []
},
setPreviewMode(state, value) {
state.previewMode = value
}
}

View File

@ -6,10 +6,7 @@ export function checkConflict(files, items) {
items = []
}
let folder_upload = false
if (files[0].fullPath !== undefined) {
folder_upload = true
}
let folder_upload = files[0].fullPath !== undefined
let conflict = false
for (let i = 0; i < files.length; i++) {
@ -69,7 +66,7 @@ export function scanFiles(dt) {
const dir = {
isDir: true,
size: 0,
path: `${directory}${entry.name}`
fullPath: `${directory}${entry.name}`
}
contents.push(dir)
@ -112,7 +109,7 @@ export function handleFiles(files, path, overwrite = false) {
if (file.isDir) {
itemPath = path
let folders = file.path.split("/")
let folders = file.fullPath.split("/")
for (let i = 0; i < folders.length; i++) {
let folder = folders[i]

View File

@ -10,14 +10,15 @@
<router-link :to="link.url">{{ link.name }}</router-link>
</span>
</div>
<div v-if="error">
<not-found v-if="error.message === '404'"></not-found>
<forbidden v-else-if="error.message === '403'"></forbidden>
<internal-error v-else></internal-error>
</div>
<preview v-else-if="isPreview"></preview>
<editor v-else-if="isEditor"></editor>
<listing :class="{ multiple }" v-else-if="isListing"></listing>
<preview v-else-if="isPreview"></preview>
<div v-else>
<h2 class="message">
<span>{{ $t('files.loading') }}</span>
@ -65,7 +66,7 @@ export default {
'show'
]),
isPreview () {
return !this.loading && !this.isListing && !this.isEditor
return !this.loading && !this.isListing && !this.isEditor || this.loading && this.$store.state.previewMode
},
breadcrumbs () {
let parts = this.$route.path.split('/')

View File

@ -127,6 +127,10 @@ var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Reques
return nil
}, action, r.URL.Path, "", d.user)
if err != nil {
_ = d.user.Fs.RemoveAll(r.URL.Path)
}
return errToStatus(err), err
})
@ -144,6 +148,31 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
return http.StatusForbidden, nil
}
override := r.URL.Query().Get("override") == "true"
rename := r.URL.Query().Get("rename") == "true"
if !override && !rename {
if _, err = d.user.Fs.Stat(dst); err == nil {
return http.StatusConflict, nil
}
}
if rename {
counter := 1
dir, name := filepath.Split(dst)
ext := filepath.Ext(name)
base := strings.TrimSuffix(name, ext)
for {
if _, err = d.user.Fs.Stat(dst); err != nil {
break
}
new := fmt.Sprintf("%s(%d)%s", base, counter, ext)
dst = filepath.Join(dir, new)
counter++
}
}
err = d.RunHook(func() error {
switch action {
// TODO: use enum