change new folder and file permissions #190

progresses on sharing #192

Progresses on #192

Progresses on #192

Little API update

Build assets


Former-commit-id: 68e70132ea857eb65638c0496c030be1c181ed1c [formerly d67b74280b7f12c3e20de6abe31fcfc26e8f43ef] [formerly 8fe91e003c9616da23f0e673ad4bb89d792a41c8 [formerly 868434360592aa0280e0d631840750d53a564cd3]]
Former-commit-id: 7d22ff468e580601d0c3e0921734b587b92484f8 [formerly 55f9d830636f9bbf15e0453d1ee7de6ee5d5191e]
Former-commit-id: ad411a5979521dda9ea9683d86e4c8ae7b3c9e6f
This commit is contained in:
Henrique Dias
2017-08-11 09:33:47 +01:00
parent 25a86a9382
commit 8d715bb433
38 changed files with 736 additions and 67 deletions

View File

@@ -29,6 +29,7 @@
<!-- Menu that shows on listing AND mobile when there are files selected -->
<div id="file-selection" v-if="isMobile && req.kind === 'listing'">
<span v-if="selectedCount > 0">{{ selectedCount }} selected</span>
<share-button v-show="showRenameButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showMoveButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
@@ -38,6 +39,7 @@
<!-- 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="showRenameButton"></share-button>
<rename-button v-show="showRenameButton"></rename-button>
<copy-button v-show="showMoveButton"></copy-button>
<move-button v-show="showMoveButton"></move-button>
@@ -74,6 +76,7 @@ import SwitchButton from './buttons/SwitchView'
import MoveButton from './buttons/Move'
import CopyButton from './buttons/Copy'
import ScheduleButton from './buttons/Schedule'
import ShareButton from './buttons/Share'
import {mapGetters, mapState} from 'vuex'
import * as api from '@/utils/api'
import buttons from '@/utils/buttons'
@@ -84,6 +87,7 @@ export default {
Search,
InfoButton,
DeleteButton,
ShareButton,
RenameButton,
DownloadButton,
CopyButton,

View File

@@ -82,7 +82,7 @@
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'search',

View File

@@ -8,7 +8,7 @@
<script>
import {mapGetters, mapState} from 'vuex'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'download-button',

View File

@@ -0,0 +1,17 @@
<template>
<button @click="show" :aria-label="$t('buttons.share')" :title="$t('buttons.share')" class="action">
<i class="material-icons">share</i>
<span>{{ $t('buttons.share') }}</span>
</button>
</template>
<script>
export default {
name: 'share-button',
methods: {
show (event) {
this.$store.commit('showHover', 'share')
}
}
}
</script>

View File

@@ -11,7 +11,7 @@
<script>
import { mapState } from 'vuex'
import CodeMirror from '@/utils/codemirror'
import api from '@/utils/api'
import * as api from '@/utils/api'
import buttons from '@/utils/buttons'
export default {
@@ -129,7 +129,7 @@ export default {
api.put(this.$route.path, content, regenerate, this.schedule)
.then(() => {
buttons.done(button)
buttons.success(button)
this.$store.commit('setSchedule', '')
})
.catch(error => {

View File

@@ -91,7 +91,7 @@
import {mapState} from 'vuex'
import Item from './ListingItem'
import css from '@/utils/css'
import api from '@/utils/api'
import * as api from '@/utils/api'
import buttons from '@/utils/buttons'
export default {
@@ -325,7 +325,7 @@ export default {
Promise.all(promises)
.then(() => {
buttons.done('upload')
buttons.success('upload')
this.$store.commit('setReload', true)
})
.catch(error => {

View File

@@ -33,7 +33,7 @@
import { mapMutations, mapGetters, mapState } from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'item',

View File

@@ -38,7 +38,7 @@
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'

View File

@@ -21,7 +21,7 @@
<script>
import { mapState } from 'vuex'
import FileList from './FileList'
import api from '@/utils/api'
import * as api from '@/utils/api'
import buttons from '@/utils/buttons'
export default {
@@ -51,7 +51,7 @@ export default {
// Execute the promises.
api.copy(items)
.then(() => {
buttons.done('copy')
buttons.success('copy')
this.$router.push({ path: this.dest })
})
.catch(error => {

View File

@@ -17,7 +17,7 @@
<script>
import {mapGetters, mapMutations, mapState} from 'vuex'
import api from '@/utils/api'
import { remove } from '@/utils/api'
import url from '@/utils/url'
import buttons from '@/utils/buttons'
@@ -36,9 +36,9 @@ export default {
// If we are not on a listing, delete the current
// opened file.
if (this.req.kind !== 'listing') {
api.delete(this.$route.path)
remove(this.$route.path)
.then(() => {
buttons.done('delete')
buttons.success('delete')
this.$router.push({ path: url.removeLastDir(this.$route.path) + '/' })
})
.catch(error => {
@@ -59,12 +59,12 @@ export default {
let promises = []
for (let index of this.selected) {
promises.push(api.delete(this.req.items[index].url))
promises.push(remove(this.req.items[index].url))
}
Promise.all(promises)
.then(() => {
buttons.done('delete')
buttons.success('delete')
this.$store.commit('setReload', true)
})
.catch(error => {

View File

@@ -13,7 +13,7 @@
<script>
import {mapGetters, mapState} from 'vuex'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'download',

View File

@@ -19,7 +19,7 @@
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'file-list',

View File

@@ -34,7 +34,7 @@
import {mapState, mapGetters} from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'info',

View File

@@ -21,7 +21,7 @@
<script>
import { mapState } from 'vuex'
import FileList from './FileList'
import api from '@/utils/api'
import * as api from '@/utils/api'
import buttons from '@/utils/buttons'
export default {
@@ -51,7 +51,7 @@ export default {
// Execute the promises.
api.move(items)
.then(() => {
buttons.done('move')
buttons.success('move')
this.$router.push({ path: this.dest })
})
.catch(error => {

View File

@@ -18,7 +18,7 @@
<script>
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'new-dir',

View File

@@ -18,7 +18,7 @@
<script>
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'new-file',

View File

@@ -14,6 +14,7 @@
<replace v-else-if="showReplace"></replace>
<schedule v-else-if="show === 'schedule'"></schedule>
<new-archetype v-else-if="show === 'new-archetype'"></new-archetype>
<share v-else-if="show === 'share'"></share>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
@@ -33,9 +34,10 @@ import NewDir from './NewDir'
import NewArchetype from './NewArchetype'
import Replace from './Replace'
import Schedule from './Schedule'
import Share from './Share'
import { mapState } from 'vuex'
import buttons from '@/utils/buttons'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'prompts',
@@ -50,6 +52,7 @@ export default {
Success,
Move,
Copy,
Share,
NewFile,
NewDir,
Help,

View File

@@ -20,7 +20,7 @@
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'rename',

View File

@@ -0,0 +1,153 @@
<template>
<div class="prompt" id="share">
<h3>{{ $t('buttons.share') }}</h3>
<p></p>
<ul>
<li v-if="!hasPermanent">
<a @click="getPermalink" :aria-label="$t('buttons.permalink')">{{ $t('buttons.permalink') }}</a>
</li>
<li v-for="link in links" :key="link.hash">
<a :href="buildLink(link.hash)" target="_blank">
<template v-if="link.expires">{{ humanTime(link.expireDate) }}</template>
<template v-else>{{ $t('permanent') }}</template>
</a>
<button class="action"
@click="deleteLink($event, link)"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
<button class="action copy"
:data-clipboard-text="buildLink(link.hash)"
:aria-label="$t('buttons.copyToClipboard')"
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
</li>
<li>
<input autofocus
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"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
</li>
</ul>
<div>
<button class="cancel"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { getShare, deleteShare, share } from '@/utils/api'
import moment from 'moment'
import Clipboard from 'clipboard'
export default {
name: 'share',
data: function () {
return {
time: '',
unit: 'hours',
hasPermanent: false,
links: [],
clip: null
}
},
computed: {
...mapState([ 'baseURL', 'req', 'selected', 'selectedCount' ]),
url () {
// Get the current name of the file we are editing.
if (this.req.kind !== 'listing') {
return this.$route.path
}
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return
}
return this.req.items[this.selected[0]].url
}
},
beforeMount () {
getShare(this.url)
.then(links => {
this.links = links
this.sort()
for (let link of this.links) {
if (!link.expires) {
this.hasPermanent = true
break
}
}
})
.catch(error => {
if (error === 404) return
this.showError(error)
})
},
mounted () {
this.clip = new Clipboard('.copy')
},
methods: {
...mapMutations([ 'showError' ]),
submit: function (event) {
if (!this.time) return
share(this.url, this.time, this.unit)
.then(result => { this.links.push(result); this.sort() })
.catch(error => { this.showError(error) })
},
getPermalink (event) {
share(this.url)
.then(result => {
this.links.push(result)
this.sort()
this.hasPermanent = true
})
.catch(error => { this.showError(error) })
},
deleteLink (event, link) {
event.preventDefault()
deleteShare(link.hash)
.then(() => {
if (!link.expires) this.hasPermanent = false
this.links = this.links.filter(item => item.hash !== link.hash)
})
.catch(error => { this.showError(error) })
},
humanTime (time) {
return moment(time).fromNow()
},
buildLink (hash) {
return `${window.location.origin}${this.baseURL}/share/${hash}`
},
sort () {
this.links = this.links.sort((a, b) => {
if (!a.expires) return -1
if (!b.expires) return 1
return new Date(a.expireDate) - new Date(b.expireDate)
})
}
}
}
</script>

View File

@@ -61,7 +61,7 @@
background: #fff;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
width: 95%;
max-width: 18em;
max-width: 20em;
}
#file-selection .action {
border-radius: 50%;

View File

@@ -177,3 +177,32 @@
opacity: 1;
}
}
.prompt#share ul {
list-style: none;
padding: 0;
margin: 0;
}
.prompt#share ul li {
display: flex;
justify-content: space-between;
align-items: center;
}
.prompt#share ul li a {
color: #2196F3;
cursor: pointer;
margin-right: auto;
}
.prompt#share ul li .action i {
font-size: 1em;
}
.prompt#share ul li input,
.prompt#share ul li select {
padding: .2em;
margin-right: .5em;
border: 1px solid #dadada;
}

View File

@@ -1,8 +1,10 @@
permanent: Permanent
buttons:
cancel: Cancel
close: Close
copy: Copy
copyFile: Copy file
copyToClipboard: Copy to clipboard
create: Create
delete: Delete
download: Download
@@ -20,6 +22,7 @@ buttons:
save: Save
search: Search
select: Select
share: Share
publish: Publish
selectMultiple: Select multiple
schedule: Schedule
@@ -27,6 +30,7 @@ buttons:
toggleSidebar: Toggle sidebar
update: Update
upload: Upload
permalink: Get Permanent Link
errors:
forbidden: You're not welcome here.
internal: Something really went wrong.
@@ -183,3 +187,9 @@ languages:
en: English
pt: Portuguese
zhCN: Chinese (Simplified)
time:
unit: Time Unit
seconds: Seconds
minutes: Minutes
hours: Hours
days: Days

View File

@@ -1,8 +1,10 @@
permanent: Permanente
buttons:
cancel: Cancelar
close: Fechar
copy: Copiar
copyFile: Copiar ficheiro
copyToClipboard: Copiar
create: Criar
delete: Eliminar
download: Descarregar
@@ -19,6 +21,7 @@ buttons:
replace: Substituir
reportIssue: Reportar Erro
save: Guardar
share: Partilhar
schedule: Agendar
search: Pesquisar
select: Selecionar
@@ -27,6 +30,7 @@ buttons:
toggleSidebar: Alternar barra lateral
update: Atualizar
upload: Enviar
permalink: Obter link permanente
errors:
forbidden: Tu não és bem-vindo aqui.
internal: Algo correu bastante mal.
@@ -186,3 +190,9 @@ sidebar:
servedWith: Servido com
settings: Configurações
siteSettings: Configurações do Site
time:
unit: Unidades de Tempo
seconds: Segundos
minutes: Minutos
hours: Horas
days: Dias

View File

@@ -1,4 +1,5 @@
import i18n from '@/i18n'
import moment from 'moment'
const mutations = {
closeHovers: state => {
@@ -26,6 +27,7 @@ const mutations = {
setLoading: (state, value) => { state.loading = value },
setReload: (state, value) => { state.reload = value },
setUser: (state, value) => {
moment.locale(value.locale)
i18n.locale = value.locale
state.user = value
},

View File

@@ -35,7 +35,7 @@ export function fetch (url) {
})
}
export function rm (url) {
export function remove (url) {
url = removePrefix(url)
return new Promise((resolve, reject) => {
@@ -383,25 +383,69 @@ export function deleteUser (id) {
})
}
export default {
removePrefix,
delete: rm,
fetch,
checksum,
move,
put,
copy,
post,
command,
search,
download,
// other things
getSettings,
updateSettings,
// User things
newUser,
getUser,
getUsers,
updateUser,
deleteUser
// SHARE
export function getShare (url) {
url = removePrefix(url)
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('GET', `${store.state.baseURL}/api/share${url}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
resolve(JSON.parse(request.responseText))
} else {
reject(request.status)
}
}
request.onerror = (error) => reject(error)
request.send()
})
}
export function deleteShare (hash) {
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('DELETE', `${store.state.baseURL}/api/share/${hash}`, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
resolve()
} else {
reject(request.status)
}
}
request.onerror = (error) => reject(error)
request.send()
})
}
export function share (url, expires = '', unit = 'hours') {
url = removePrefix(url)
url = `${store.state.baseURL}/api/share${url}`
if (expires !== '') {
url += `?expires=${expires}&unit=${unit}`
}
return new Promise((resolve, reject) => {
let request = new window.XMLHttpRequest()
request.open('POST', url, true)
request.setRequestHeader('Authorization', `Bearer ${store.state.jwt}`)
request.onload = () => {
if (request.status === 200) {
resolve(JSON.parse(request.responseText))
} else {
reject(request.responseStatus)
}
}
request.onerror = (error) => reject(error)
request.send()
})
}

View File

@@ -16,7 +16,7 @@ function loading (button) {
}, 100)
}
function done (button, success = true) {
function done (button) {
let el = document.querySelector(`#${button}-button > i`)
if (el === undefined || el === null) {
@@ -33,7 +33,34 @@ function done (button, success = true) {
}, 100)
}
function success (button) {
let el = document.querySelector(`#${button}-button > i`)
if (el === undefined || el === null) {
console.log('Error getting button ' + button)
return
}
el.style.opacity = 0
setTimeout(() => {
el.classList.remove('spin')
el.innerHTML = 'done'
el.style.opacity = 1
setTimeout(() => {
el.style.opacity = 0
setTimeout(() => {
el.innerHTML = el.dataset.icon
el.style.opacity = 1
}, 100)
}, 500)
}, 100)
}
export default {
loading,
done
done,
success
}

View File

@@ -33,7 +33,7 @@ import InternalError from './errors/500'
import Preview from '@/components/files/Preview'
import Listing from '@/components/files/Listing'
import Editor from '@/components/files/Editor'
import api from '@/utils/api'
import * as api from '@/utils/api'
import { mapGetters, mapState, mapMutations } from 'vuex'
export default {

View File

@@ -31,7 +31,7 @@
</template>
<script>
import api from '@/utils/api'
import * as api from '@/utils/api'
export default {
name: 'users',