Showing preview only (339K chars total). Download the full file or copy to clipboard to get everything.
Repository: filebrowser/frontend
Branch: master
Commit: d45d7f92fb16
Files: 109
Total size: 296.9 KB
Directory structure:
gitextract_pd9iypza/
├── .circleci/
│ └── config.yml
├── .gitignore
├── .tx/
│ └── config
├── README.md
├── babel.config.js
├── package.json
├── public/
│ ├── img/
│ │ └── icons/
│ │ └── browserconfig.xml
│ ├── index.html
│ └── manifest.json
├── src/
│ ├── App.vue
│ ├── api/
│ │ ├── commands.js
│ │ ├── files.js
│ │ ├── index.js
│ │ ├── search.js
│ │ ├── settings.js
│ │ ├── share.js
│ │ ├── users.js
│ │ └── utils.js
│ ├── components/
│ │ ├── Header.vue
│ │ ├── Search.vue
│ │ ├── Shell.vue
│ │ ├── Sidebar.vue
│ │ ├── buttons/
│ │ │ ├── Copy.vue
│ │ │ ├── Delete.vue
│ │ │ ├── Download.vue
│ │ │ ├── Info.vue
│ │ │ ├── Move.vue
│ │ │ ├── Rename.vue
│ │ │ ├── Share.vue
│ │ │ ├── Shell.vue
│ │ │ ├── SwitchView.vue
│ │ │ └── Upload.vue
│ │ ├── files/
│ │ │ ├── Editor.vue
│ │ │ ├── Listing.vue
│ │ │ ├── ListingItem.vue
│ │ │ └── Preview.vue
│ │ ├── prompts/
│ │ │ ├── Copy.vue
│ │ │ ├── Delete.vue
│ │ │ ├── Download.vue
│ │ │ ├── FileList.vue
│ │ │ ├── Help.vue
│ │ │ ├── Info.vue
│ │ │ ├── Move.vue
│ │ │ ├── NewDir.vue
│ │ │ ├── NewFile.vue
│ │ │ ├── Prompts.vue
│ │ │ ├── Rename.vue
│ │ │ ├── Replace.vue
│ │ │ └── Share.vue
│ │ └── settings/
│ │ ├── Commands.vue
│ │ ├── Languages.vue
│ │ ├── Permissions.vue
│ │ ├── Rules.vue
│ │ └── UserForm.vue
│ ├── css/
│ │ ├── _buttons.css
│ │ ├── _inputs.css
│ │ ├── _share.css
│ │ ├── _shell.css
│ │ ├── _variables.css
│ │ ├── base.css
│ │ ├── dashboard.css
│ │ ├── fonts.css
│ │ ├── header.css
│ │ ├── listing.css
│ │ ├── login.css
│ │ ├── mobile.css
│ │ └── styles.css
│ ├── i18n/
│ │ ├── ar.json
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── index.js
│ │ ├── is.json
│ │ ├── it.json
│ │ ├── ja.json
│ │ ├── ko.json
│ │ ├── pl.json
│ │ ├── pt-br.json
│ │ ├── pt.json
│ │ ├── ro.json
│ │ ├── ru.json
│ │ ├── zh-cn.json
│ │ └── zh-tw.json
│ ├── main.js
│ ├── router/
│ │ └── index.js
│ ├── store/
│ │ ├── getters.js
│ │ ├── index.js
│ │ └── mutations.js
│ ├── utils/
│ │ ├── auth.js
│ │ ├── buttons.js
│ │ ├── constants.js
│ │ ├── cookie.js
│ │ ├── css.js
│ │ ├── url.js
│ │ └── vue.js
│ └── views/
│ ├── Files.vue
│ ├── Layout.vue
│ ├── Login.vue
│ ├── Settings.vue
│ ├── Share.vue
│ ├── errors/
│ │ ├── 403.vue
│ │ ├── 404.vue
│ │ └── 500.vue
│ └── settings/
│ ├── Global.vue
│ ├── Profile.vue
│ ├── User.vue
│ └── Users.vue
└── vue.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
build:
docker:
- image: circleci/node
steps:
- checkout
- run: npm install
- run: npm run lint
- run: npm run build
workflows:
version: 2
build:
jobs:
- build
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
================================================
FILE: .tx/config
================================================
[main]
host = https://www.transifex.com
lang_map = pt_BR: pt-br, zh_CN: zh-cn, zh_HK: zh-hk, zh_TW: zh-tw
[file-browser.file-browser]
file_filter = src/i18n/<lang>.json
minimum_perc = 50
source_file = src/i18n/en.json
source_lang = en
type = KEYVALUEJSON
================================================
FILE: README.md
================================================
# File Browser Front-end
[](https://circleci.com/gh/filebrowser/frontend)
[]()
[](https://github.com/RichardLitt/standard-readme)
[](http://webchat.freenode.net/?channels=%23filebrowser)
> This is an example file with default selections.
## Install
```
npm install filebrowser-frontend
```
## Usage
This package is not prepared to be used by other projects than [File Browser](https://github.com/filebrowser/filebrowser) itself.
## Contribute
Check the [community repository](https://github.com/filebrowser/community) for more information.
## License
[Apache 2.0](./LICENSE) File Browser Contributors
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: [
'@vue/app'
]
}
================================================
FILE: package.json
================================================
{
"name": "filebrowser-frontend",
"version": "2.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"watch": "vue-cli-service build --watch",
"lint": "vue-cli-service lint --fix"
},
"dependencies": {
"ace-builds": "^1.4.4",
"clipboard": "^2.0.4",
"js-base64": "^2.5.1",
"lodash.clonedeep": "^4.5.0",
"material-design-icons": "^3.0.1",
"moment": "^2.24.0",
"normalize.css": "^8.0.1",
"noty": "^3.2.0-beta",
"qrcode.vue": "^1.6.1",
"vue": "^2.6.10",
"vue-i18n": "^8.11.2",
"vue-router": "^3.0.6",
"vuex": "^3.1.1",
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.7.0",
"@vue/cli-plugin-eslint": "^3.7.0",
"@vue/cli-service": "^3.7.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.2.2",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
================================================
FILE: public/img/icons/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#455a64</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
[{[ if .ReCaptcha -]}]
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
<link rel="icon" type="image/png" sizes="32x32" href="/[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" href="/[{[ .StaticURL ]}]/manifest.json">
<meta name="theme-color" content="#2979ff">
<!-- Add to home screen for Safari on iOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="assets">
<link rel="apple-touch-icon" href="/[{[ .StaticURL ]}]/img/icons/apple-touch-icon-152x152.png">
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="/[{[ .StaticURL ]}]/img/icons/msapplication-icon-144x144.png">
<meta name="msapplication-TileColor" content="#2979ff">
<!-- Inject Some Variables -->
<script>window.FileBrowser = JSON.parse(`[{[ .Json ]}]`)</script>
<style>
#loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
z-index: 9999;
transition: .1s ease opacity;
-webkit-transition: .1s ease opacity;
}
#loading.done {
opacity: 0;
}
.spinner {
width: 70px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div id="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
[{[ if .CSS -]}]
<link rel="stylesheet" href="/[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
</body>
</html>
================================================
FILE: public/manifest.json
================================================
{
"name": "File Browser",
"short_name": "File Browser",
"icons": [
{
"src": "./img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./static/img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "./",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#455a64"
}
================================================
FILE: src/App.vue
================================================
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
mounted () {
const loading = document.getElementById('loading')
loading.classList.add('done')
setTimeout(function () {
loading.parentNode.removeChild(loading)
}, 200)
}
}
</script>
<style>
@import './css/styles.css';
</style>
================================================
FILE: src/api/commands.js
================================================
import { removePrefix } from './utils'
import { baseURL } from '@/utils/constants'
import store from '@/store'
const ssl = (window.location.protocol === 'https:')
const protocol = (ssl ? 'wss:' : 'ws:')
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)
conn.onopen = () => conn.send(command)
conn.onmessage = onmessage
conn.onclose = onclose
}
================================================
FILE: src/api/files.js
================================================
import { fetchURL, removePrefix } from './utils'
import { baseURL } from '@/utils/constants'
import store from '@/store'
export async function fetch (url) {
url = removePrefix(url)
const res = await fetchURL(`/api/resources${url}`, {})
if (res.status === 200) {
let data = await res.json()
data.url = `/files${url}`
if (data.isDir) {
if (!data.url.endsWith('/')) data.url += '/'
data.items = data.items.map((item, index) => {
item.index = index
item.url = `${data.url}${encodeURIComponent(item.name)}`
if (item.isDir) {
item.url += '/'
}
return item
})
}
return data
} else {
throw new Error(res.status)
}
}
async function resourceAction (url, method, content) {
url = removePrefix(url)
let opts = { method }
if (content) {
opts.body = content
}
const res = await fetchURL(`/api/resources${url}`, opts)
if (res.status !== 200) {
throw new Error(res.responseText)
} else {
return res
}
}
export async function remove (url) {
return resourceAction(url, 'DELETE')
}
export async function put (url, content = '') {
return resourceAction(url, 'PUT', content)
}
export function download (format, ...files) {
let url = `${baseURL}/api/raw`
if (files.length === 1) {
url += removePrefix(files[0]) + '?'
} else {
let arg = ''
for (let file of files) {
arg += removePrefix(file) + ','
}
arg = arg.substring(0, arg.length - 1)
arg = encodeURIComponent(arg)
url += `/?files=${arg}&`
}
if (format !== null) {
url += `algo=${format}&`
}
url += `auth=${store.state.jwt}`
window.open(url)
}
export async function post (url, content = '', overwrite = false, onupload) {
url = removePrefix(url)
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest()
request.open('POST', `${baseURL}/api/resources${url}?override=${overwrite}`, true)
request.setRequestHeader('X-Auth', store.state.jwt)
if (typeof onupload === 'function') {
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)
} else if (request.status === 409) {
reject(request.status)
} else {
reject(request.responseText)
}
}
request.onerror = (error) => {
reject(error)
}
request.send(content)
// Upload is done no more message before closing the tab
}).finally(() => { window.onbeforeunload = null })
}
function moveCopy (items, copy = 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}`
promises.push(resourceAction(url, 'PATCH'))
}
return Promise.all(promises)
}
export function move (items) {
return moveCopy(items)
}
export function copy (items) {
return moveCopy(items, true)
}
export async function checksum (url, algo) {
const data = await resourceAction(`${url}?checksum=${algo}`, 'GET')
return (await data.json()).checksums[algo]
}
================================================
FILE: src/api/index.js
================================================
import * as files from './files'
import * as share from './share'
import * as users from './users'
import * as settings from './settings'
import search from './search'
import commands from './commands'
export {
files,
share,
users,
settings,
commands,
search
}
================================================
FILE: src/api/search.js
================================================
import { fetchJSON, removePrefix } from './utils'
export default async function search (url, query) {
url = removePrefix(url)
query = encodeURIComponent(query)
return fetchJSON(`/api/search${url}?query=${query}`, {})
}
================================================
FILE: src/api/settings.js
================================================
import { fetchURL, fetchJSON } from './utils'
export function get () {
return fetchJSON(`/api/settings`, {})
}
export async function update (settings) {
const res = await fetchURL(`/api/settings`, {
method: 'PUT',
body: JSON.stringify(settings)
})
if (res.status !== 200) {
throw new Error(res.status)
}
}
================================================
FILE: src/api/share.js
================================================
import { fetchURL, fetchJSON, removePrefix } from './utils'
export async function getHash(hash) {
return fetchJSON(`/api/public/share/${hash}`)
}
export async function get(url) {
url = removePrefix(url)
return fetchJSON(`/api/share${url}`)
}
export async function remove(hash) {
const res = await fetchURL(`/api/share/${hash}`, {
method: 'DELETE'
})
if (res.status !== 200) {
throw new Error(res.status)
}
}
export async function create(url, expires = '', unit = 'hours') {
url = removePrefix(url)
url = `/api/share${url}`
if (expires !== '') {
url += `?expires=${expires}&unit=${unit}`
}
return fetchJSON(url, {
method: 'POST'
})
}
================================================
FILE: src/api/users.js
================================================
import { fetchURL, fetchJSON } from './utils'
export async function getAll () {
return fetchJSON(`/api/users`, {})
}
export async function get (id) {
return fetchJSON(`/api/users/${id}`, {})
}
export async function create (user) {
const res = await fetchURL(`/api/users`, {
method: 'POST',
body: JSON.stringify({
what: 'user',
which: [],
data: user
})
})
if (res.status === 201) {
return res.headers.get('Location')
} else {
throw new Error(res.status)
}
}
export async function update (user, which = ['all']) {
const res = await fetchURL(`/api/users/${user.id}`, {
method: 'PUT',
body: JSON.stringify({
what: 'user',
which: which,
data: user
})
})
if (res.status !== 200) {
throw new Error(res.status)
}
}
export async function remove (id) {
const res = await fetchURL(`/api/users/${id}`, {
method: 'DELETE'
})
if (res.status !== 200) {
throw new Error(res.status)
}
}
================================================
FILE: src/api/utils.js
================================================
import store from '@/store'
import { renew } from '@/utils/auth'
import { baseURL } from '@/utils/constants'
export async function fetchURL (url, opts) {
opts = opts || {}
opts.headers = opts.headers || {}
let { headers, ...rest } = opts
const res = await fetch(`${baseURL}${url}`, {
headers: {
'X-Auth': store.state.jwt,
...headers
},
...rest
})
if (res.headers.get('X-Renew-Token') === 'true') {
await renew(store.state.jwt)
}
return res
}
export async function fetchJSON (url, opts) {
const res = await fetchURL(url, opts)
if (res.status === 200) {
return res.json()
} else {
throw new Error(res.status)
}
}
export function removePrefix (url) {
if (url.startsWith('/files')) {
url = url.slice(6)
}
if (url === '') url = '/'
if (url[0] !== '/') url = '/' + url
return url
}
================================================
FILE: src/components/Header.vue
================================================
<template>
<header>
<div>
<button @click="openSidebar" :aria-label="$t('buttons.toggleSidebar')" :title="$t('buttons.toggleSidebar')" class="action">
<i class="material-icons">menu</i>
</button>
<img :src="logoURL" alt="File Browser">
<search v-if="isLogged"></search>
</div>
<div>
<template v-if="isLogged">
<button @click="openSearch" :aria-label="$t('buttons.search')" :title="$t('buttons.search')" class="search-button action">
<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">
<i class="material-icons">save</i>
</button>
<button @click="openMore" id="more" :aria-label="$t('buttons.more')" :title="$t('buttons.more')" class="action">
<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">
<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>
</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>
</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>
<button v-show="isListing" @click="openSelect" :aria-label="$t('buttons.selectMultiple')" :title="$t('buttons.selectMultiple')" class="action">
<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>
</header>
</template>
<script>
import Search from './Search'
import InfoButton from './buttons/Info'
import DeleteButton from './buttons/Delete'
import RenameButton from './buttons/Rename'
import UploadButton from './buttons/Upload'
import DownloadButton from './buttons/Download'
import SwitchButton from './buttons/SwitchView'
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 { logoURL } from '@/utils/constants'
import * as api from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'header-layout',
components: {
Search,
InfoButton,
DeleteButton,
ShareButton,
RenameButton,
DownloadButton,
CopyButton,
UploadButton,
SwitchButton,
MoveButton,
ShellButton
},
data: function () {
return {
width: window.innerWidth,
pluginData: {
api,
buttons,
'store': this.$store,
'router': this.$router
}
}
},
created () {
window.addEventListener('resize', () => {
this.width = window.innerWidth
})
},
computed: {
...mapGetters([
'selectedCount',
'isFiles',
'isEditor',
'isListing',
'isLogged'
]),
...mapState([
'req',
'user',
'loading',
'reload',
'multiple'
]),
logoURL: () => logoURL,
isMobile () {
return this.width <= 736
},
showUpload () {
return this.isListing && this.user.perm.create
},
showSaveButton () {
return this.isEditor && this.user.perm.modify
},
showDownloadButton () {
return this.isFiles && this.user.perm.download
},
showDeleteButton () {
return this.isFiles && (this.isListing
? (this.selectedCount !== 0 && this.user.perm.delete)
: this.user.perm.delete)
},
showRenameButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.rename)
: this.user.perm.rename)
},
showShareButton () {
return this.isFiles && (this.isListing
? (this.selectedCount === 1 && this.user.perm.share)
: this.user.perm.share)
},
showMoveButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.rename)
: this.user.perm.rename)
},
showCopyButton () {
return this.isFiles && (this.isListing
? (this.selectedCount > 0 && this.user.perm.create)
: this.user.perm.create)
},
showMore () {
return this.isFiles && this.$store.state.show === 'more'
},
showOverlay () {
return this.showMore
}
},
methods: {
openSidebar () {
this.$store.commit('showHover', 'sidebar')
},
openMore () {
this.$store.commit('showHover', 'more')
},
openSearch () {
this.$store.commit('showHover', 'search')
},
openSelect () {
this.$store.commit('multiple', true)
this.resetPrompts()
},
resetPrompts () {
this.$store.commit('closeHovers')
}
}
}
</script>
================================================
FILE: src/components/Search.vue
================================================
<template>
<div id="search" @click="open" v-bind:class="{ active , ongoing }">
<div id="input">
<button
v-if="active"
class="action"
@click="close"
:aria-label="$t('buttons.close')"
:title="$t('buttons.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"
:aria-label="$t('search.search')"
:placeholder="$t('search.search')"
>
</div>
<div id="result" ref="result">
<div>
<template v-if="isEmpty">
<p>{{ text }}</p>
<template v-if="value.length === 0">
<div class="boxes">
<h3>{{ $t('search.types') }}</h3>
<div>
<div
tabindex="0"
v-for="(v,k) in boxes"
:key="k"
role="button"
@click="init('type:'+k)"
:aria-label="$t('search.'+v.label)"
>
<i class="material-icons">{{v.icon}}</i>
<p>{{ $t('search.'+v.label) }}</p>
</div>
</div>
</div>
</template>
</template>
<ul v-show="results.length > 0">
<li v-for="(s,k) in filteredResults" :key="k">
<router-link @click.native="close" :to="'./' + s.path">
<i v-if="s.dir" class="material-icons">folder</i>
<i v-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span>
</router-link>
</li>
</ul>
</div>
<p id="renew">
<i class="material-icons spin">autorenew</i>
</p>
</div>
</div>
</template>
<script>
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" }
}
export default {
name: "search",
data: function() {
return {
value: "",
active: false,
ongoing: false,
results: [],
reload: false,
resultsCount: 50,
scrollable: null
}
},
watch: {
show (val, old) {
this.active = val === "search"
if (old === "search" && !this.active) {
if (this.reload) {
this.setReload(true)
}
document.body.style.overflow = "auto"
this.reset()
this.value = ''
this.active = false
this.$refs.input.blur()
} else if (this.active) {
this.reload = false
this.$refs.input.focus()
document.body.style.overflow = "hidden"
}
},
value () {
if (this.results.length) {
this.reset()
}
}
},
computed: {
...mapState(["user", "show"]),
...mapGetters(["isListing"]),
boxes() {
return boxes
},
isEmpty() {
return this.results.length === 0
},
text() {
if (this.ongoing) {
return ""
}
return this.value === '' ? this.$t("search.typeToSearch") : this.$t("search.pressToSearch")
},
filteredResults () {
return this.results.slice(0, this.resultsCount)
}
},
mounted() {
window.addEventListener("keydown", event => {
if (event.keyCode === 27) {
this.closeHovers()
}
})
this.$refs.result.addEventListener('scroll', event => {
if (event.target.offsetHeight + event.target.scrollTop >= event.target.scrollHeight - 100) {
this.resultsCount += 50
}
})
},
methods: {
...mapMutations(["showHover", "closeHovers", "setReload"]),
open() {
this.showHover("search")
},
close(event) {
event.stopPropagation()
event.preventDefault()
this.closeHovers()
},
keyup(event) {
if (event.keyCode === 27) {
this.close(event)
return
}
this.results.length = 0
},
init (string) {
this.value = `${string} `
this.$refs.input.focus()
},
reset () {
this.ongoing = false
this.resultsCount = 50
this.results = []
},
async submit(event) {
event.preventDefault()
if (this.value === '') {
return
}
let path = this.$route.path
if (!this.isListing) {
path = url.removeLastDir(path) + "/"
}
this.ongoing = true
this.results = await search(path, this.value)
this.ongoing = false
}
}
}
</script>
================================================
FILE: src/components/Shell.vue
================================================
<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 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__prompt"><i class="material-icons">chevron_right</i></div>
<pre
tabindex="0"
ref="input"
class="shell__text"
contenteditable="true"
@keydown.prevent.38="historyUp"
@keydown.prevent.40="historyDown"
@keypress.prevent.enter="submit" />
</div>
</div>
</template>
<script>
import { mapMutations, mapState, mapGetters } from 'vuex'
import { commands } from '@/api'
export default {
name: 'shell',
computed: {
...mapState([ 'user', 'showShell' ]),
...mapGetters([ 'isFiles', 'isLogged' ]),
path: function () {
if (this.isFiles) {
return this.$route.path
}
return ''
}
},
data: () => ({
content: [],
history: [],
historyPos: 0,
canInput: true
}),
methods: {
...mapMutations([ 'toggleShell' ]),
scroll: function () {
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight
},
focus: function () {
this.$refs.input.focus()
},
historyUp () {
if (this.historyPos > 0) {
this.$refs.input.innerText = this.history[--this.historyPos]
this.focus()
}
},
historyDown () {
if (this.historyPos >= 0 && this.historyPos < this.history.length - 1) {
this.$refs.input.innerText = this.history[++this.historyPos]
this.focus()
} else {
this.historyPos = this.history.length
this.$refs.input.innerText = ''
}
},
submit: function (event) {
const cmd = event.target.innerText.trim()
if (cmd === '') {
return
}
if (cmd === 'clear') {
this.content = []
event.target.innerHTML = ''
return
}
if (cmd === 'exit') {
event.target.innerHTML = ''
this.toggleShell()
return
}
this.canInput = false
event.target.innerHTML = ''
let results = {
text: `${cmd}\n\n`
}
this.history.push(cmd)
this.historyPos = this.history.length
this.content.push(results)
commands(
this.path,
cmd,
event => {
results.text += `${event.data}\n`
this.scroll()
},
() => {
results.text = results.text.trimEnd()
this.canInput = true
this.$refs.input.focus()
this.scroll()
}
)
}
}
}
</script>
================================================
FILE: src/components/Sidebar.vue
================================================
<template>
<nav :class="{active}">
<template v-if="isLogged">
<router-link class="action" to="/files/" :aria-label="$t('sidebar.myFiles')" :title="$t('sidebar.myFiles')">
<i class="material-icons">folder</i>
<span>{{ $t('sidebar.myFiles') }}</span>
</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')">
<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')">
<i class="material-icons">note_add</i>
<span>{{ $t('sidebar.newFile') }}</span>
</button>
</div>
<div>
<router-link class="action" to="/settings" :aria-label="$t('sidebar.settings')" :title="$t('sidebar.settings')">
<i class="material-icons">settings_applications</i>
<span>{{ $t('sidebar.settings') }}</span>
</router-link>
<button v-if="!noAuth" @click="logout" class="action" id="logout" :aria-label="$t('sidebar.logout')" :title="$t('sidebar.logout')">
<i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.logout') }}</span>
</button>
</div>
</template>
<template v-else>
<router-link class="action" to="/login" :aria-label="$t('sidebar.login')" :title="$t('sidebar.login')">
<i class="material-icons">exit_to_app</i>
<span>{{ $t('sidebar.login') }}</span>
</router-link>
<router-link v-if="signup" class="action" to="/login" :aria-label="$t('sidebar.signup')" :title="$t('sidebar.signup')">
<i class="material-icons">person_add</i>
<span>{{ $t('sidebar.signup') }}</span>
</router-link>
</template>
<p class="credits">
<span>
<span v-if="disableExternal">File Browser</span>
<a v-else rel="noopener noreferrer" target="_blank" href="https://github.com/filebrowser/filebrowser">File Browser</a>
<span> {{ version }}</span>
</span>
<span><a @click="help">{{ $t('sidebar.help') }}</a></span>
</p>
</nav>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import * as auth from '@/utils/auth'
import { version, signup, disableExternal, noAuth } from '@/utils/constants'
export default {
name: 'sidebar',
computed: {
...mapState([ 'user' ]),
...mapGetters([ 'isLogged' ]),
active () {
return this.$store.state.show === 'sidebar'
},
signup: () => signup,
version: () => version,
disableExternal: () => disableExternal,
noAuth: () => noAuth
},
methods: {
help () {
this.$store.commit('showHover', 'help')
},
logout: auth.logout
}
}
</script>
================================================
FILE: src/components/buttons/Copy.vue
================================================
<template>
<button @click="show" :aria-label="$t('buttons.copy')" :title="$t('buttons.copy')" class="action" id="copy-button">
<i class="material-icons">content_copy</i>
<span>{{ $t('buttons.copyFile') }}</span>
</button>
</template>
<script>
export default {
name: 'copy-button',
methods: {
show: function () {
this.$store.commit('showHover', 'copy')
}
}
}
</script>
================================================
FILE: src/components/buttons/Delete.vue
================================================
<template>
<button @click="show" :aria-label="$t('buttons.delete')" :title="$t('buttons.delete')" class="action" id="delete-button">
<i class="material-icons">delete</i>
<span>{{ $t('buttons.delete') }}</span>
</button>
</template>
<script>
export default {
name: 'delete-button',
methods: {
show: function () {
this.$store.commit('showHover', 'delete')
}
}
}
</script>
================================================
FILE: src/components/buttons/Download.vue
================================================
<template>
<button @click="download" :aria-label="$t('buttons.download')" :title="$t('buttons.download')" id="download-button" class="action">
<i class="material-icons">file_download</i>
<span>{{ $t('buttons.download') }}</span>
<span v-if="selectedCount > 0" class="counter">{{ selectedCount }}</span>
</button>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import { files as api } from '@/api'
export default {
name: 'download-button',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['isListing', 'selectedCount'])
},
methods: {
download: function () {
if (!this.isListing) {
api.download(null, this.$route.path)
return
}
if (this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir) {
api.download(null, this.req.items[this.selected[0]].url)
return
}
this.$store.commit('showHover', 'download')
}
}
}
</script>
================================================
FILE: src/components/buttons/Info.vue
================================================
<template>
<button :title="$t('buttons.info')" :aria-label="$t('buttons.info')" class="action" @click="show">
<i class="material-icons">info</i>
<span>{{ $t('buttons.info') }}</span>
</button>
</template>
<script>
export default {
name: 'info-button',
methods: {
show: function () {
this.$store.commit('showHover', 'info')
}
}
}
</script>
================================================
FILE: src/components/buttons/Move.vue
================================================
<template>
<button @click="show" :aria-label="$t('buttons.move')" :title="$t('buttons.move')" class="action" id="move-button">
<i class="material-icons">forward</i>
<span>{{ $t('buttons.moveFile') }}</span>
</button>
</template>
<script>
export default {
name: 'move-button',
methods: {
show: function () {
this.$store.commit('showHover', 'move')
}
}
}
</script>
================================================
FILE: src/components/buttons/Rename.vue
================================================
<template>
<button @click="show" :aria-label="$t('buttons.rename')" :title="$t('buttons.rename')" class="action" id="rename-button">
<i class="material-icons">mode_edit</i>
<span>{{ $t('buttons.rename') }}</span>
</button>
</template>
<script>
export default {
name: 'rename-button',
methods: {
show: function () {
this.$store.commit('showHover', 'rename')
}
}
}
</script>
================================================
FILE: src/components/buttons/Share.vue
================================================
<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 () {
this.$store.commit('showHover', 'share')
}
}
}
</script>
================================================
FILE: src/components/buttons/Shell.vue
================================================
<template>
<button @click="show" :aria-label="$t('buttons.shell')" :title="$t('buttons.shell')" class="action">
<i class="material-icons">code</i>
<span>{{ $t('buttons.shell') }}</span>
</button>
</template>
<script>
export default {
name: 'shell-button',
methods: {
show: function () {
this.$store.commit('toggleShell')
}
}
}
</script>
================================================
FILE: src/components/buttons/SwitchView.vue
================================================
<template>
<button @click="change" :aria-label="$t('buttons.switchView')" :title="$t('buttons.switchView')" class="action" id="switch-view-button">
<i class="material-icons">{{ icon }}</i>
<span>{{ $t('buttons.switchView') }}</span>
</button>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import { users as api } from '@/api'
export default {
name: 'switch-button',
computed: {
...mapState(['user']),
icon: function () {
if (this.user.viewMode === 'mosaic') return 'view_list'
return 'view_module'
}
},
methods: {
...mapMutations([ 'updateUser', 'closeHovers' ]),
change: async function () {
this.closeHovers()
const data = {
id: this.user.id,
viewMode: (this.icon === 'view_list') ? 'list' : 'mosaic'
}
try {
await api.update(data, ['viewMode'])
this.updateUser(data)
} catch (e) {
this.$showError(e)
}
}
}
}
</script>
================================================
FILE: src/components/buttons/Upload.vue
================================================
<template>
<button @click="upload" :aria-label="$t('buttons.upload')" :title="$t('buttons.upload')" class="action" id="upload-button">
<i class="material-icons">file_upload</i>
<span>{{ $t('buttons.upload') }}</span>
</button>
</template>
<script>
export default {
name: 'upload-button',
methods: {
upload: function () {
document.getElementById('upload-input').click()
}
}
}
</script>
================================================
FILE: src/components/files/Editor.vue
================================================
<template>
<form id="editor"></form>
</template>
<script>
import { mapState } from 'vuex'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
import ace from 'ace-builds/src-min-noconflict/ace.js'
import modelist from 'ace-builds/src-min-noconflict/ext-modelist.js'
import 'ace-builds/webpack-resolver'
export default {
name: 'editor',
computed: {
...mapState(['req'])
},
data: function () {
return {
content: null,
editor: null
}
},
created () {
window.addEventListener('keydown', this.keyEvent)
document.getElementById('save-button').addEventListener('click', this.save)
},
beforeDestroy () {
window.removeEventListener('keydown', this.keyEvent)
document.getElementById('save-button').removeEventListener('click', this.save)
},
mounted: function () {
if (this.req.content === undefined || this.req.content === null) {
this.req.content = ''
}
this.editor = ace.edit('editor', {
maxLines: Infinity,
minLines: 20,
value: this.req.content,
showPrintMargin: false,
readOnly: this.req.type === 'textImmutable',
theme: 'ace/theme/chrome',
mode: modelist.getModeForPath(this.req.name).mode
})
},
methods: {
keyEvent (event) {
if (!event.ctrlKey && !event.metaKey) {
return
}
if (String.fromCharCode(event.which).toLowerCase() !== 's') {
return
}
event.preventDefault()
this.save()
},
async save () {
const button = 'save'
buttons.loading('save')
try {
await api.put(this.$route.path, this.editor.getValue())
buttons.success(button)
} catch (e) {
buttons.done(button)
this.$showError(e)
}
}
}
}
</script>
================================================
FILE: src/components/files/Listing.vue
================================================
<template>
<div v-if="(req.numDirs + req.numFiles) == 0">
<h2 class="message">
<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>
</div>
<div v-else id="listing"
:class="user.viewMode"
@dragenter="dragEnter"
@dragend="dragEnd">
<div>
<div class="item header">
<div></div>
<div>
<p :class="{ active: nameSorted }" class="name"
role="button"
tabindex="0"
@click="sort('name')"
:title="$t('files.sortByName')"
:aria-label="$t('files.sortByName')">
<span>{{ $t('files.name') }}</span>
<i class="material-icons">{{ nameIcon }}</i>
</p>
<p :class="{ active: sizeSorted }" class="size"
role="button"
tabindex="0"
@click="sort('size')"
:title="$t('files.sortBySize')"
:aria-label="$t('files.sortBySize')">
<span>{{ $t('files.size') }}</span>
<i class="material-icons">{{ sizeIcon }}</i>
</p>
<p :class="{ active: modifiedSorted }" class="modified"
role="button"
tabindex="0"
@click="sort('modified')"
:title="$t('files.sortByLastModified')"
:aria-label="$t('files.sortByLastModified')">
<span>{{ $t('files.lastModified') }}</span>
<i class="material-icons">{{ modifiedIcon }}</i>
</p>
</div>
</div>
</div>
<h2 v-if="req.numDirs > 0">{{ $t('files.folders') }}</h2>
<div v-if="req.numDirs > 0">
<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>
</div>
<h2 v-if="req.numFiles > 0">{{ $t('files.files') }}</h2>
<div v-if="req.numFiles > 0">
<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>
</div>
<input style="display:none" type="file" id="upload-input" @change="uploadInput($event)" multiple>
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
<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">
<i class="material-icons">clear</i>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import Item from './ListingItem'
import css from '@/utils/css'
import { users, files as api } from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'listing',
components: { Item },
data: function () {
return {
show: 50
}
},
computed: {
...mapState(['req', 'selected', 'user']),
nameSorted () {
return (this.req.sorting.by === 'name')
},
sizeSorted () {
return (this.req.sorting.by === 'size')
},
modifiedSorted () {
return (this.req.sorting.by === 'modified')
},
ascOrdered () {
return this.req.sorting.asc
},
items () {
const dirs = []
const files = []
this.req.items.forEach((item) => {
if (item.isDir) {
dirs.push(item)
} else {
files.push(item)
}
})
return { dirs, files }
},
dirs () {
return this.items.dirs.slice(0, this.show)
},
files () {
let show = this.show - this.items.dirs.length
if (show < 0) show = 0
return this.items.files.slice(0, show)
},
nameIcon () {
if (this.nameSorted && !this.ascOrdered) {
return 'arrow_upward'
}
return 'arrow_downward'
},
sizeIcon () {
if (this.sizeSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
},
modifiedIcon () {
if (this.modifiedSorted && this.ascOrdered) {
return 'arrow_downward'
}
return 'arrow_upward'
}
},
mounted: function () {
// Check the columns size for the first time.
this.resizeEvent()
// Add the needed event listeners to the window and document.
window.addEventListener('keydown', this.keyEvent)
window.addEventListener('resize', this.resizeEvent)
window.addEventListener('scroll', this.scrollEvent)
document.addEventListener('dragover', this.preventDefault)
document.addEventListener('drop', this.drop)
},
beforeDestroy () {
// Remove event listeners before destroying this page.
window.removeEventListener('keydown', this.keyEvent)
window.removeEventListener('resize', this.resizeEvent)
window.removeEventListener('scroll', this.scrollEvent)
document.removeEventListener('dragover', this.preventDefault)
document.removeEventListener('drop', this.drop)
},
methods: {
...mapMutations([ 'updateUser' ]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)))
},
keyEvent (event) {
if (!event.ctrlKey && !event.metaKey) {
return
}
let key = String.fromCharCode(event.which).toLowerCase()
switch (key) {
case 'f':
event.preventDefault()
this.$store.commit('showHover', 'search')
break
case 'c':
case 'x':
this.copyCut(event, key)
break
case 'v':
this.paste(event)
break
}
},
preventDefault (event) {
// Wrapper around prevent default.
event.preventDefault()
},
copyCut (event, key) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
name: encodeURIComponent(this.req.items[i].name)
})
}
if (items.length == 0) {
return
}
this.$store.commit('updateClipboard', {
key: key,
items: items
})
},
paste (event) {
if (event.target.tagName.toLowerCase() === 'input') {
return
}
let items = []
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 })
}
if (items.length === 0) {
return
}
if (this.$store.state.clipboard.key === 'x') {
api.move(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
return
}
api.copy(items).then(() => {
this.$store.commit('setReload', true)
}).catch(this.$showError)
},
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'])
if (columns === 0) columns = 1
items.style.width = `calc(${100 / columns}% - 1em)`
},
scrollEvent () {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
this.show += 50
}
},
dragEnter () {
// When the user starts dragging an item, put every
// file on the listing with 50% opacity.
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 0.5
})
},
dragEnd () {
this.resetOpacity()
},
drop: function (event) {
event.preventDefault()
this.resetOpacity()
let dt = event.dataTransfer
let files = dt.files
let el = event.target
if (files.length <= 0) return
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains('item')) {
el = el.parentElement
}
}
let base = ''
if (el !== null && el.classList.contains('item') && el.dataset.dir === 'true') {
base = el.querySelector('.name').innerHTML + '/'
}
if (base !== '') {
api.fetch(this.$route.path + base)
.then(req => {
this.checkConflict(files, req.items, base)
})
.catch(this.$showError)
return
}
this.checkConflict(files, this.req.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) {
return (element.name === this)
}, files[i].name)
if (res >= 0) {
conflict = true
break
}
}
if (!conflict) {
this.handleFiles(files, base)
return
}
this.$store.commit('showHover', {
prompt: 'replace',
confirm: (event) => {
event.preventDefault()
this.$store.commit('closeHovers')
this.handleFiles(files, base, true)
}
})
},
uploadInput (event) {
this.checkConflict(event.currentTarget.files, this.req.items, '')
},
resetOpacity () {
let items = document.getElementsByClassName('item')
Array.from(items).forEach(file => {
file.style.opacity = 1
})
},
handleFiles (files, base, overwrite = false) {
buttons.loading('upload')
let promises = []
let progress = new Array(files.length).fill(0)
let onupload = (id) => (event) => {
progress[id] = (event.loaded / event.total) * 100
let sum = 0
for (let i = 0; i < progress.length; i++) {
sum += progress[i]
}
this.$store.commit('setProgress', Math.ceil(sum / progress.length))
}
for (let i = 0; i < files.length; i++) {
let file = files[i]
promises.push(api.post(this.$route.path + base + file.name, file, overwrite, onupload(i)))
}
let finish = () => {
buttons.success('upload')
this.$store.commit('setProgress', 0)
}
Promise.all(promises)
.then(() => {
finish()
this.$store.commit('setReload', true)
})
.catch(error => {
finish()
this.$showError(error)
})
return false
},
async sort (by) {
let asc = false
if (by === 'name') {
if (this.nameIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'size') {
if (this.sizeIcon === 'arrow_upward') {
asc = true
}
} else if (by === 'modified') {
if (this.modifiedIcon === 'arrow_upward') {
asc = true
}
}
try {
await users.update({ id: this.user.id, sorting: { by, asc } }, ['sorting'])
} catch (e) {
this.$showError(e)
}
this.$store.commit('setReload', true)
}
}
}
</script>
================================================
FILE: src/components/files/ListingItem.vue
================================================
<template>
<div class="item"
role="button"
tabindex="0"
draggable="true"
@dragstart="dragStart"
@dragover="dragOver"
@drop="drop"
@click="click"
@dblclick="open"
@touchstart="touchstart"
:data-dir="isDir"
:aria-label="name"
:aria-selected="isSelected">
<div>
<i class="material-icons">{{ icon }}</i>
</div>
<div>
<p class="name">{{ name }}</p>
<p v-if="isDir" class="size" data-order="-1">—</p>
<p v-else class="size" :data-order="humanSize()">{{ humanSize() }}</p>
<p class="modified">
<time :datetime="modified">{{ humanTime() }}</time>
</p>
</div>
</div>
</template>
<script>
import { mapMutations, mapGetters, mapState } from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import { files as api } from '@/api'
export default {
name: 'item',
data: function () {
return {
touches: 0
}
},
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'],
computed: {
...mapState(['selected', 'req']),
...mapGetters(['selectedCount']),
isSelected () {
return (this.selected.indexOf(this.index) !== -1)
},
icon () {
if (this.isDir) return 'folder'
if (this.type === 'image') return 'insert_photo'
if (this.type === 'audio') return 'volume_up'
if (this.type === 'video') return 'movie'
return 'insert_drive_file'
},
canDrop () {
if (!this.isDir) return false
for (let i of this.selected) {
if (this.req.items[i].url === this.url) {
return false
}
}
return true
}
},
methods: {
...mapMutations(['addSelected', 'removeSelected', 'resetSelected']),
humanSize: function () {
return filesize(this.size)
},
humanTime: function () {
return moment(this.modified).fromNow()
},
dragStart: function () {
if (this.selectedCount === 0) {
this.addSelected(this.index)
return
}
if (!this.isSelected) {
this.resetSelected()
this.addSelected(this.index)
}
},
dragOver: function (event) {
if (!this.canDrop) return
event.preventDefault()
let el = event.target
for (let i = 0; i < 5; i++) {
if (!el.classList.contains('item')) {
el = el.parentElement
}
}
el.style.opacity = 1
},
drop: function (event) {
if (!this.canDrop) return
event.preventDefault()
if (this.selectedCount === 0) return
let items = []
for (let i of this.selected) {
items.push({
from: this.req.items[i].url,
to: this.url + this.req.items[i].name
})
}
api.move(items)
.then(() => {
this.$store.commit('setReload', true)
})
.catch(this.$showError)
},
click: function (event) {
if (this.selectedCount !== 0) event.preventDefault()
if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index)
return
}
if (event.shiftKey && this.selected.length === 1) {
let fi = 0
let la = 0
if (this.index > this.selected[0]) {
fi = this.selected[0] + 1
la = this.index
} else {
fi = this.index
la = this.selected[0] - 1
}
for (; fi <= la; fi++) {
this.addSelected(fi)
}
return
}
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
this.addSelected(this.index)
},
touchstart () {
setTimeout(() => {
this.touches = 0
}, 300)
this.touches++
if (this.touches > 1) {
this.open()
}
},
open: function () {
this.$router.push({path: this.url})
}
}
}
</script>
================================================
FILE: src/components/files/Preview.vue
================================================
<template>
<div id="previewer">
<div class="bar">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close">
<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>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')">
<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')">
<i class="material-icons">chevron_right</i>
</button>
<div class="preview">
<img v-if="req.type == 'image'" :src="raw">
<audio v-else-if="req.type == 'audio'" :src="raw" autoplay controls></audio>
<video v-else-if="req.type == 'video'" :src="raw" autoplay controls>
<track
kind="captions"
v-for="(sub, index) in subtitles"
:key="index"
: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>
and watch it with your favorite video player!
</video>
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object>
<a v-else-if="req.type == 'blob'" :href="download">
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2>
</a>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { baseURL } from '@/utils/constants'
import { files as api } from '@/api'
import InfoButton from '@/components/buttons/Info'
import DeleteButton from '@/components/buttons/Delete'
import RenameButton from '@/components/buttons/Rename'
import DownloadButton from '@/components/buttons/Download'
const mediaTypes = [
"image",
"video",
"audio",
"blob"
]
export default {
name: 'preview',
components: {
InfoButton,
DeleteButton,
RenameButton,
DownloadButton
},
data: function () {
return {
previousLink: '',
nextLink: '',
listing: null,
subtitles: []
}
},
computed: {
...mapState(['req', 'user', 'oldReq', 'jwt']),
hasPrevious () {
return (this.previousLink !== '')
},
hasNext () {
return (this.nextLink !== '')
},
download () {
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`
},
raw () {
return `${this.download}&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`)
}
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)
}
},
beforeDestroy () {
window.removeEventListener('keyup', this.key)
},
methods: {
back () {
let uri = url.removeLastDir(this.$route.path) + '/'
this.$router.push({ path: uri })
},
prev () {
this.$router.push({ path: this.previousLink })
},
next () {
this.$router.push({ path: this.nextLink })
},
key (event) {
event.preventDefault()
if (event.which === 13 || event.which === 39) { // right arrow
if (this.hasNext) this.next()
} else if (event.which === 37) { // left arrow
if (this.hasPrevious) this.prev()
}
},
updateLinks (items) {
for (let i = 0; i < items.length; i++) {
if (items[i].name !== this.req.name) {
continue
}
for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(items[j].type)) {
this.previousLink = items[j].url
break
}
}
for (let j = i + 1; j < items.length; j++) {
if (mediaTypes.includes(items[j].type)) {
this.nextLink = items[j].url
break
}
}
return
}
}
}
}
</script>
================================================
FILE: src/components/prompts/Copy.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.copy') }}</h2>
</div>
<div class="card-content">
<p>{{ $t('prompts.copyMessage') }}</p>
<file-list @update:selected="val => dest = val"></file-list>
</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"
@click="copy"
:disabled="$route.path === dest"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'copy',
components: { FileList },
data: function () {
return {
current: window.location.pathname,
dest: null
}
},
computed: mapState(['req', 'selected']),
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)
})
}
try {
await api.copy(items)
buttons.success('copy')
this.$router.push({ path: this.dest })
} catch (e) {
buttons.done('copy')
this.$showError(e)
}
}
}
}
</script>
================================================
FILE: src/components/prompts/Delete.vue
================================================
<template>
<div class="card floating">
<div class="card-content">
<p v-if="req.kind !== 'listing'">{{ $t('prompts.deleteMessageSingle') }}</p>
<p v-else>{{ $t('prompts.deleteMessageMultiple', { count: selectedCount}) }}</p>
</div>
<div class="card-action">
<button @click="$store.commit('closeHovers')"
class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
<button @click="submit"
class="button button--flat button--red"
:aria-label="$t('buttons.delete')"
:title="$t('buttons.delete')">{{ $t('buttons.delete') }}</button>
</div>
</div>
</template>
<script>
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',
computed: {
...mapGetters(['isListing', 'selectedCount']),
...mapState(['req', 'selected'])
},
methods: {
...mapMutations(['closeHovers']),
submit: async function () {
this.closeHovers()
buttons.loading('delete')
try {
if (!this.isListing) {
await api.remove(this.$route.path)
buttons.success('delete')
this.$router.push({ path: url.removeLastDir(this.$route.path) + '/' })
return
}
if (this.selectedCount === 0) {
return
}
let promises = []
for (let index of this.selected) {
promises.push(api.remove(this.req.items[index].url))
}
await Promise.all(promises)
buttons.success('delete')
this.$store.commit('setReload', true)
} catch (e) {
buttons.done('delete')
this.$showError(e)
if (this.isListing) this.$store.commit('setReload', true)
}
}
}
}
</script>
================================================
FILE: src/components/prompts/Download.vue
================================================
<template>
<div class="card floating" id="download">
<div class="card-title">
<h2>{{ $t('prompts.download') }}</h2>
</div>
<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>
</div>
</div>
</template>
<script>
import {mapGetters, mapState} from 'vuex'
import { files as api } from '@/api'
export default {
name: 'download',
computed: {
...mapState(['selected', 'req']),
...mapGetters(['selectedCount'])
},
methods: {
download: function (format) {
if (this.selectedCount === 0) {
api.download(format, this.$route.path)
} else {
let files = []
for (let i of this.selected) {
files.push(this.req.items[i].url)
}
api.download(format, ...files)
}
this.$store.commit('closeHovers')
}
}
}
</script>
================================================
FILE: src/components/prompts/FileList.vue
================================================
<template>
<div>
<ul class="file-list">
<li @click="select"
@touchstart="touchstart"
@dblclick="next"
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>
</ul>
<p>{{ $t('prompts.currentlyNavigating') }} <code>{{ nav }}</code>.</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
import url from '@/utils/url'
import { files } from '@/api'
export default {
name: 'file-list',
data: function () {
return {
items: [],
touches: {
id: '',
count: 0
},
selected: null,
current: window.location.pathname
}
},
computed: {
...mapState([ 'req' ]),
nav () {
return decodeURIComponent(this.current)
}
},
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) {
// Sets the current path and resets
// the current items.
this.current = req.url
this.items = []
this.$emit('update:selected', this.current)
// If the path isn't the root path,
// show a button to navigate to the previous
// directory.
if (req.url !== '/files/') {
this.items.push({
name: '..',
url: url.removeLastDir(req.url) + '/'
})
}
// If this folder is empty, finish here.
if (req.items === null) return
// Otherwise we add every directory to the
// move options.
for (let item of req.items) {
if (!item.isDir) continue
this.items.push({
name: item.name,
url: item.url
})
}
},
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
files.fetch(uri)
.then(this.fillOptions)
.catch(this.$showError)
},
touchstart (event) {
let url = event.currentTarget.dataset.url
// In 300 milliseconds, we shall reset the count.
setTimeout(() => {
this.touches.count = 0
}, 300)
// If the element the user is touching
// is different from the last one he touched,
// reset the count.
if (this.touches.id !== url) {
this.touches.id = url
this.touches.count = 1
return
}
this.touches.count++
// If there is more than one touch already,
// open the next screen.
if (this.touches.count > 1) {
this.next(event)
}
},
select: function (event) {
// If the element is already selected, unselect it.
if (this.selected === event.currentTarget.dataset.url) {
this.selected = null
this.$emit('update:selected', this.current)
return
}
// Otherwise select the element.
this.selected = event.currentTarget.dataset.url
this.$emit('update:selected', this.selected)
}
}
}
</script>
================================================
FILE: src/components/prompts/Help.vue
================================================
<template>
<div class="card floating help">
<div class="card-title">
<h2>{{ $t('help.help') }}</h2>
</div>
<div class="card-content">
<ul>
<li><strong>F1</strong> - {{ $t('help.f1') }}</li>
<li><strong>F2</strong> - {{ $t('help.f2') }}</li>
<li><strong>DEL</strong> - {{ $t('help.del') }}</li>
<li><strong>ESC</strong> - {{ $t('help.esc') }}</li>
<li><strong>CTRL + S</strong> - {{ $t('help.ctrl.s') }}</li>
<li><strong>CTRL + F</strong> - {{ $t('help.ctrl.f') }}</li>
<li><strong>CTRL + Click</strong> - {{ $t('help.ctrl.click') }}</li>
<li><strong>Click</strong> - {{ $t('help.click') }}</li>
<li><strong>Double click</strong> - {{ $t('help.doubleClick') }}</li>
</ul>
</div>
<div class="card-action">
<button type="submit"
@click="$store.commit('closeHovers')"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
</div>
</div>
</template>
<script>
export default { name: 'help' }
</script>
================================================
FILE: src/components/prompts/Info.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.fileInfo') }}</h2>
</div>
<div class="card-content">
<p v-if="selected.length > 1">{{ $t('prompts.filesSelected', { count: selected.length }) }}</p>
<p 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"><strong>{{ $t('prompts.lastModified') }}:</strong> {{ humanTime }}</p>
<template v-if="dir && selected.length === 0">
<p><strong>{{ $t('prompts.numberFiles') }}:</strong> {{ req.numFiles }}</p>
<p><strong>{{ $t('prompts.numberDirs') }}:</strong> {{ req.numDirs }}</p>
</template>
<template v-if="!dir">
<p><strong>MD5: </strong><code><a @click="checksum($event, 'md5')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA1: </strong><code><a @click="checksum($event, 'sha1')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA256: </strong><code><a @click="checksum($event, 'sha256')">{{ $t('prompts.show') }}</a></code></p>
<p><strong>SHA512: </strong><code><a @click="checksum($event, 'sha512')">{{ $t('prompts.show') }}</a></code></p>
</template>
</div>
<div class="card-action">
<button type="submit"
@click="$store.commit('closeHovers')"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')">{{ $t('buttons.ok') }}</button>
</div>
</div>
</template>
<script>
import {mapState, mapGetters} from 'vuex'
import filesize from 'filesize'
import moment from 'moment'
import { files as api } from '@/api'
export default {
name: 'info',
computed: {
...mapState(['req', 'selected']),
...mapGetters(['selectedCount', 'isListing']),
humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) {
return filesize(this.req.size)
}
let sum = 0
for (let selected of this.selected) {
sum += this.req.items[selected].size
}
return filesize(sum)
},
humanTime: function () {
if (this.selectedCount === 0) {
return moment(this.req.modified).fromNow()
}
return moment(this.req.items[this.selected[0]]).fromNow()
},
name: function () {
return this.selectedCount === 0 ? this.req.name : this.req.items[this.selected[0]].name
},
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) {
event.preventDefault()
let link
if (this.selectedCount) {
link = this.req.items[this.selected[0]].url
} else {
link = this.$route.path
}
try {
const hash = await api.checksum(link, algo)
event.target.innerHTML = hash
} catch (e) {
this.$showError(e)
}
}
}
}
</script>
================================================
FILE: src/components/prompts/Move.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.move') }}</h2>
</div>
<div class="card-content">
<file-list @update:selected="val => dest = val"></file-list>
</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"
@click="move"
:disabled="$route.path === dest"
:aria-label="$t('buttons.move')"
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import FileList from './FileList'
import { files as api } from '@/api'
import buttons from '@/utils/buttons'
export default {
name: 'move',
components: { FileList },
data: function () {
return {
current: window.location.pathname,
dest: null
}
},
computed: mapState(['req', 'selected']),
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)
})
}
try {
api.move(items)
buttons.success('move')
this.$router.push({ path: this.dest })
} catch (e) {
buttons.done('move')
this.$showError(e)
}
event.preventDefault()
}
}
}
</script>
================================================
FILE: src/components/prompts/NewDir.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.newDir') }}</h2>
</div>
<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>
</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"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"
@click="submit"
>{{ $t('buttons.create') }}</button>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { files as api } from '@/api'
import url from '@/utils/url'
export default {
name: 'new-dir',
data: function() {
return {
name: ''
};
},
computed: {
...mapGetters([ 'isFiles', 'isListing' ])
},
methods: {
submit: async function(event) {
event.preventDefault()
if (this.new === '') return
// Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + '/' : '/'
if (!this.isListing) {
uri = url.removeLastDir(uri) + '/'
}
uri += encodeURIComponent(this.name) + '/'
uri = uri.replace('//', '/')
try {
await api.post(uri)
this.$router.push({ path: uri })
} catch (e) {
this.$showError(e)
}
this.$store.commit('closeHovers')
}
}
};
</script>
================================================
FILE: src/components/prompts/NewFile.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.newFile') }}</h2>
</div>
<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">
</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"
@click="submit"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"
>{{ $t('buttons.create') }}</button>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { files as api } from '@/api'
import url from '@/utils/url'
export default {
name: 'new-file',
data: function() {
return {
name: ''
};
},
computed: {
...mapGetters([ 'isFiles', 'isListing' ])
},
methods: {
submit: async function(event) {
event.preventDefault()
if (this.new === '') return
// Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + '/' : '/'
if (!this.isListing) {
uri = url.removeLastDir(uri) + '/'
}
uri += encodeURIComponent(this.name)
uri = uri.replace('//', '/')
try {
await api.post(uri)
this.$router.push({ path: uri })
} catch (e) {
this.$showError(e)
}
this.$store.commit('closeHovers')
}
}
};
</script>
================================================
FILE: src/components/prompts/Prompts.vue
================================================
<template>
<div>
<help v-if="showHelp" ></help>
<download v-else-if="showDownload"></download>
<new-file v-else-if="showNewFile"></new-file>
<new-dir v-else-if="showNewDir"></new-dir>
<rename v-else-if="showRename"></rename>
<delete v-else-if="showDelete"></delete>
<info v-else-if="showInfo"></info>
<move v-else-if="showMove"></move>
<copy v-else-if="showCopy"></copy>
<replace v-else-if="showReplace"></replace>
<share v-else-if="show === 'share'"></share>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
<script>
import Help from './Help'
import Info from './Info'
import Delete from './Delete'
import Rename from './Rename'
import Download from './Download'
import Move from './Move'
import Copy from './Copy'
import NewFile from './NewFile'
import NewDir from './NewDir'
import Replace from './Replace'
import Share from './Share'
import { mapState } from 'vuex'
import buttons from '@/utils/buttons'
export default {
name: 'prompts',
components: {
Info,
Delete,
Rename,
Download,
Move,
Copy,
Share,
NewFile,
NewDir,
Help,
Replace
},
data: function () {
return {
pluginData: {
buttons,
'store': this.$store,
'router': this.$router
}
}
},
computed: {
...mapState(['show', 'plugins']),
showInfo: function () { return this.show === 'info' },
showHelp: function () { return this.show === 'help' },
showDelete: function () { return this.show === 'delete' },
showRename: function () { return this.show === 'rename' },
showMove: function () { return this.show === 'move' },
showCopy: function () { return this.show === 'copy' },
showNewFile: function () { return this.show === 'newFile' },
showNewDir: function () { return this.show === 'newDir' },
showDownload: function () { return this.show === 'download' },
showReplace: function () { return this.show === 'replace' },
showOverlay: function () {
return (this.show !== null && this.show !== 'search' && this.show !== 'more')
}
},
methods: {
resetPrompts () {
this.$store.commit('closeHovers')
}
}
}
</script>
================================================
FILE: src/components/prompts/Rename.vue
================================================
<template>
<div class="card floating">
<div class="card-title">
<h2>{{ $t('prompts.rename') }}</h2>
</div>
<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">
</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 @click="submit"
class="button button--flat"
type="submit"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')">{{ $t('buttons.rename') }}</button>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import url from '@/utils/url'
import { files as api } from '@/api'
export default {
name: 'rename',
data: function () {
return {
name: ''
}
},
created () {
this.name = this.oldName()
},
computed: {
...mapState(['req', 'selected', 'selectedCount']),
...mapGetters(['isListing'])
},
methods: {
cancel: function () {
this.$store.commit('closeHovers')
},
oldName: function () {
if (!this.isListing) {
return this.req.name
}
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return
}
return this.req.items[this.selected[0]].name
},
submit: async function () {
let oldLink = ''
let newLink = ''
if (!this.isListing) {
oldLink = this.req.url
} else {
oldLink = this.req.items[this.selected[0]].url
}
newLink = url.removeLastDir(oldLink) + '/' + encodeURIComponent(this.name)
try {
await api.move([{ from: oldLink, to: newLink }])
if (!this.isListing) {
this.$router.push({ path: newLink })
return
}
this.$store.commit('setReload', true)
} catch (e) {
this.$showError(e)
}
this.$store.commit('closeHovers')
}
}
}
</script>
================================================
FILE: src/components/prompts/Replace.vue
================================================
<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--red"
@click="showConfirm"
: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',
computed: mapState(['showConfirm'])
}
</script>
================================================
FILE: src/components/prompts/Share.vue
================================================
<template>
<div class="card floating" id="share">
<div class="card-title">
<h2>{{ $t('buttons.share') }}</h2>
</div>
<div class="card-content">
<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.expire !== 0">{{ humanTime(link.expire) }}</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-clipboard"
: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 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"
:aria-label="$t('buttons.create')"
:title="$t('buttons.create')"><i class="material-icons">add</i></button>
</li>
</ul>
</div>
<div class="card-action">
<button class="button button--flat"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')">{{ $t('buttons.close') }}</button>
</div>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import { share as api } from '@/api'
import { baseURL } from '@/utils/constants'
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([ 'req', 'selected', 'selectedCount' ]),
...mapGetters([ 'isListing' ]),
url () {
if (!this.isListing) {
return this.$route.path
}
if (this.selectedCount === 0 || this.selectedCount > 1) {
// This shouldn't happen.
return
}
return this.req.items[this.selected[0]].url
}
},
async beforeMount () {
try {
const links = await api.get(this.url)
this.links = links
this.sort()
for (let link of this.links) {
if (link.expire === 0) {
this.hasPermanent = true
break
}
}
} catch (e) {
this.$showError(e)
}
},
mounted () {
this.clip = new Clipboard('.copy-clipboard')
this.clip.on('success', () => {
this.$showSuccess(this.$t('success.linkCopied'))
})
},
beforeDestroy () {
this.clip.destroy()
},
methods: {
submit: async function () {
if (!this.time) return
try {
const res = await api.create(this.url, this.time, this.unit)
this.links.push(res)
this.sort()
} catch (e) {
this.$showError(e)
}
},
getPermalink: async function () {
try {
const res = await api.create(this.url)
this.links.push(res)
this.sort()
this.hasPermanent = true
} catch (e) {
this.$showError(e)
}
},
deleteLink: async function (event, link) {
event.preventDefault()
try {
await api.remove(link.hash)
if (link.expire === 0) this.hasPermanent = false
this.links = this.links.filter(item => item.hash !== link.hash)
} catch (e) {
this.$showError(e)
}
},
humanTime (time) {
return moment(time * 1000).fromNow()
},
buildLink (hash) {
return `${window.location.origin}${baseURL}/share/${hash}`
},
sort () {
this.links = this.links.sort((a, b) => {
if (a.expire === 0) return -1
if (b.expire === 0) return 1
return new Date(a.expire) - new Date(b.expire)
})
}
}
}
</script>
================================================
FILE: src/components/settings/Commands.vue
================================================
<template>
<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">
</div>
</template>
<script>
export default {
name: 'permissions',
props: ['commands'],
computed: {
raw: {
get () {
return this.commands.join(' ')
},
set (value) {
this.$emit('update:commands', value.split(' '))
}
}
}
}
</script>
================================================
FILE: src/components/settings/Languages.vue
================================================
<template>
<select v-on:change="change" :value="locale">
<option value="ar">{{ $t('languages.ar') }}</option>
<option value="de">{{ $t('languages.de') }}</option>
<option value="en">{{ $t('languages.en') }}</option>
<option value="es">{{ $t('languages.es') }}</option>
<option value="fr">{{ $t('languages.fr') }}</option>
<option value="it">{{ $t('languages.it') }}</option>
<option value="ja">{{ $t('languages.ja') }}</option>
<option value="ko">{{ $t('languages.ko') }}</option>
<option value="pl">{{ $t('languages.pl') }}</option>
<option value="pt-br">{{ $t('languages.ptBR') }}</option>
<option value="pt">{{ $t('languages.pt') }}</option>
<option value="ru">{{ $t('languages.ru') }}</option>
<option value="zh-cn">{{ $t('languages.zhCN') }}</option>
<option value="zh-tw">{{ $t('languages.zhTW') }}</option>
</select>
</template>
<script>
export default {
name: 'languages',
props: [ 'locale' ],
methods: {
change (event) {
this.$emit('update:locale', event.target.value)
}
}
}
</script>
================================================
FILE: src/components/settings/Permissions.vue
================================================
<template>
<div>
<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 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>
</div>
</template>
<script>
export default {
name: 'permissions',
props: ['perm'],
computed: {
admin: {
get () {
return this.perm.admin
},
set (value) {
if (value) {
for (const key in this.perm) {
this.perm[key] = true
}
}
this.perm.admin = value
}
}
}
}
</script>
================================================
FILE: src/components/settings/Rules.vue
================================================
<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
@keypress.enter.prevent
type="text"
v-if="rule.regex"
v-model="rule.regexp.raw"
:placeholder="$t('settings.insertRegex')" />
<input
@keypress.enter.prevent
type="text"
v-else
v-model="rule.path"
:placeholder="$t('settings.insertPath')" />
<button class="button button--red" @click="remove($event, index)">-</button>
</div>
<div>
<button class="button" @click="create" default="false">{{ $t('buttons.new') }}</button>
</div>
</form>
</template>
<script>
export default {
name: 'rules-textarea',
props: ['rules'],
methods: {
remove (event, index) {
event.preventDefault()
let rules = [ ...this.rules ]
rules.splice(index, 1)
this.$emit('update:rules', [ ...rules ])
},
create (event) {
event.preventDefault()
this.$emit('update:rules', [
...this.rules,
{
allow: true,
path: '',
regex: false,
regexp: {
raw: ''
}
}
])
}
}
}
</script>
================================================
FILE: src/components/settings/UserForm.vue
================================================
<template>
<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">
</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">
</p>
<p>
<label for="scope">{{ $t('settings.scope') }}</label>
<input class="input input--block" type="text" v-model="user.scope" id="scope">
</p>
<p>
<label for="locale">{{ $t('settings.language') }}</label>
<languages class="input input--block" id="locale" :locale.sync="user.locale"></languages>
</p>
<p v-if="!isDefault">
<input type="checkbox" :disabled="user.perm.admin" v-model="user.lockPassword"> {{ $t('settings.lockPassword') }}
</p>
<permissions :perm.sync="user.perm" />
<commands :commands.sync="user.commands" />
<div v-if="!isDefault">
<h3>{{ $t('settings.rules') }}</h3>
<p class="small">{{ $t('settings.rulesHelp') }}</p>
<rules :rules.sync="user.rules" />
</div>
</div>
</template>
<script>
import Languages from './Languages'
import Rules from './Rules'
import Permissions from './Permissions'
import Commands from './Commands'
export default {
name: 'user',
components: {
Permissions,
Languages,
Rules,
Commands
},
props: [ 'user', 'isNew', 'isDefault' ],
computed: {
passwordPlaceholder () {
return this.isNew ? '' : this.$t('settings.avoidChanges')
}
},
watch: {
'user.perm.admin': function () {
if (!this.user.perm.admin) return
this.user.lockPassword = false
}
}
}
</script>
================================================
FILE: src/css/_buttons.css
================================================
.button {
outline: 0;
border: 0;
padding: .5em 1em;
border-radius: .1em;
cursor: pointer;
background: var(--blue);
color: white;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.05);
transition: .1s ease all;
}
.button:hover {
background-color: var(--dark-blue);
}
.button--block {
margin: 0 0 0.5em;
display: block;
width: 100%;
}
.button--red {
background: var(--red);
}
.button--red:hover {
background: var(--dark-red);
}
.button--flat {
color: var(--dark-blue);
background: transparent;
box-shadow: 0 0 0;
border: 0;
text-transform: uppercase;
}
.button--flat:hover {
background: var(--moon-grey);
}
.button--flat.button--red {
color: var(--dark-red);
}
.button--flat.button--grey {
color: #6f6f6f;
}
.button[disabled] {
opacity: .5;
cursor: not-allowed;
}
================================================
FILE: src/css/_inputs.css
================================================
.input {
border-radius: .1em;
padding: .5em 1em;
background: white;
border: 1px solid rgba(0, 0, 0, 0.1);
transition: .2s ease all;
color: #333;
margin: 0;
}
.input:hover,
.input:focus {
border-color: rgba(0, 0, 0, 0.2);
}
.input--block {
margin-bottom: .5em;
display: block;
width: 100%;
}
.input--textarea {
line-height: 1.15;
font-family: monospace;
min-height: 10em;
resize: vertical;
}
.input--red {
background: #fcd0cd;
}
.input--green {
background: #c9f2da;
}
================================================
FILE: src/css/_share.css
================================================
.share__box {
text-align: center;
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;
background: #fff;
display: block;
border-radius: 0.2em;
width: 90%;
max-width: 25em;
margin: 6em auto;
}
.share__box__download {
width: 100%;
padding: 1em;
cursor: pointer;
background: #ffffff;
color: rgba(0, 0, 0, 0.5);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.share__box__info {
padding: 2em 3em;
}
.share__box__title {
margin-top: .2em;
overflow: hidden;
text-overflow: ellipsis;
}
================================================
FILE: src/css/_shell.css
================================================
.shell {
position: fixed;
bottom: 0;
left: 0;
height: 25em;
max-height: calc(100% - 4em);
background: white;
color: #212121;
z-index: 9999;
width: 100%;
font-family: monospace;
overflow: auto;
font-size: 1rem;
cursor: text;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transition: .2s ease transform;
}
.shell__result {
display: flex;
padding: 0.5em;
align-items: flex-start;
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
.shell--hidden {
transform: translateY(105%);
}
.shell__result--hidden {
opacity: 0;
}
.shell__text,
.shell__prompt,
.shell__prompt i {
font-size: inherit;
}
.shell__prompt {
width: 1.2rem;
}
.shell__prompt i {
color: var(--blue);
}
.shell__text {
margin: 0;
font-family: inherit;
white-space: pre-wrap;
width: 100%;
}
================================================
FILE: src/css/_variables.css
================================================
:root {
--blue: #2196f3;
--dark-blue: #1E88E5;
--red: #F44336;
--dark-red: #D32F2F;
--moon-grey: #f2f2f2;
}
================================================
FILE: src/css/base.css
================================================
body {
font-family: 'Roboto', sans-serif;
padding-top: 4em;
background-color: #fafafa;
color: #333333;
}
* {
box-sizing: border-box;
}
*,
*:hover,
*:active,
*:focus {
outline: 0
}
a {
text-decoration: none;
}
img {
max-width: 100%;
}
audio,
video {
width: 100%;
}
.mobile-only {
display: none !important;
}
.container {
width: 95%;
max-width: 960px;
margin: 1em auto 0;
}
i.spin {
animation: 1s spin linear infinite;
}
#app {
transition: .2s ease padding;
}
#app.multiple {
padding-bottom: 4em;
}
nav {
width: 16em;
position: fixed;
top: 4em;
left: 0;
}
nav .action {
width: 100%;
display: block;
border-radius: 0;
font-size: 1.1em;
padding: .5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
nav>div {
border-top: 1px solid rgba(0, 0, 0, 0.05);
}
nav .action>* {
vertical-align: middle;
}
main {
min-height: 1em;
margin: 0 1em 1em auto;
width: calc(100% - 19em);
}
#breadcrumbs {
height: 3em;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
#breadcrumbs span,
#breadcrumbs {
display: flex;
align-items: center;
color: #6f6f6f;
}
#breadcrumbs a {
color: inherit;
transition: .1s ease-in;
border-radius: .125em;
}
#breadcrumbs a:hover {
background-color: rgba(0,0,0, 0.05);
}
#breadcrumbs span a {
padding: .2em;
}
#progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
z-index: 9999999999;
}
#progress div {
height: 100%;
background-color: #40c4ff;
width: 0;
transition: .2s ease width;
}
================================================
FILE: src/css/dashboard.css
================================================
.dashboard {
max-width: 600px;
margin: 1em 0;
}
a {
color: inherit
}
.dashboard p label {
margin-bottom: .2em;
display: block;
font-size: .8em;
font-weight: 500;
color: rgba(0, 0, 0, 0.57);
}
li code,
p code {
background: rgba(0, 0, 0, 0.05);
padding: .1em;
border-radius: .2em;
}
.small {
font-size: .8em;
line-height: 1.5;
}
.dashboard #nav {
list-style: none;
display: flex;
color: rgb(84, 110, 122);
font-weight: 500;
margin: 0 0 1em;
font-size: .8em;
text-align: center;
justify-content: space-between;
padding: 0;
}
.dashboard #nav li {
width: 100%;
padding: 0 0 1em;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
}
.dashboard #nav li.active {
border-color: var(--blue)
}
.dashboard #nav i {
font-size: 1em;
vertical-align: middle;
}
table {
border-collapse: collapse;
width: 100%;
}
table tr {
border-bottom: 1px solid #ccc;
}
table tr:last-child {
border: 0;
}
table th {
font-weight: 500;
color: #757575;
text-align: left;
}
table th,
table td {
padding: .5em 0;
}
table td.small {
width: 1em;
}
table tr>*:first-child {
padding-left: 1em;
}
table tr>*:last-child {
padding-right: 1em;
}
.card {
position: relative;
margin: .5rem 0 1rem 0;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
}
.card.floating {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 99999;
max-width: 25em;
width: 90%;
max-height: 95%;
z-index: 99999;
animation: .1s show forwards;
}
.card>*>*:first-child {
margin-top: 0;
}
.card>*>*:last-child {
margin-bottom: 0;
}
.card .card-title {
padding: 1.5em 1em 1em;
display: flex;
}
.card .card-title>*:first-child {
margin-right: auto;
}
.card>div {
padding: 1em 1em;
}
.card>div:first-child {
padding-top: 1.5em;
}
.card>div:last-child {
padding-bottom: 1.5em;
}
.card .card-title * {
margin: 0;
}
.card .card-action {
text-align: right;
}
.card .card-content.full {
padding-bottom: 0;
}
.card h2 {
font-weight: 500;
}
.card h3 {
color: rgba(0, 0, 0, 0.53);
font-size: 1em;
font-weight: 500;
margin: 2em 0 1em;
}
.card-content table {
margin: 0 -1em;
width: calc(100% + 2em);
}
.card code {
word-wrap: break-word;
}
.card#download {
max-width: 15em;
}
.card#share ul {
list-style: none;
padding: 0;
margin: 0;
}
.card#share ul li {
display: flex;
justify-content: space-between;
align-items: center;
}
.card#share ul li a {
color: var(--blue);
cursor: pointer;
margin-right: auto;
}
.card#share ul li .action i {
font-size: 1em;
}
.card#share ul li input,
.card#share ul li select {
padding: .2em;
margin-right: .5em;
border: 1px solid #dadada;
}
.card#share .action.copy-clipboard::after {
content: 'Copied!';
position: absolute;
left: -25%;
width: 150%;
font-size: .6em;
text-align: center;
background: #44a6f5;
color: #fff;
padding: .5em .2em;
border-radius: .4em;
top: -2em;
transition: .1s ease opacity;
opacity: 0;
}
.card#share .action.copy-clipboard.active::after {
opacity: 1;
}
.overlay {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 9999;
animation: .1s show forwards;
}
/* * * * * * * * * * * * * * * *
* PROMPT - MOVE *
* * * * * * * * * * * * * * * */
.file-list {
max-height: 50vh;
overflow: auto;
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
.file-list li {
width: 100%;
user-select: none;
border-radius: .2em;
padding: .3em;
}
.file-list li[aria-selected=true] {
background: var(--blue) !important;
color: #fff !important;
transition: .1s ease all;
}
.file-list li:hover {
background-color: #e9eaeb;
cursor: pointer;
}
.file-list li:before {
content: "folder";
color: #6f6f6f;
vertical-align: middle;
line-height: 1.4;
font-family: 'Material Icons';
font-size: 1.75em;
margin-right: .25em;
}
.file-list li[aria-selected=true]:before {
color: white;
}
.help {
max-width: 24em;
}
.help ul {
padding: 0;
margin: 1em 0;
list-style: none;
}
@keyframes show {
0% {
display: none;
opacity: 0;
}
1% {
display: block;
opacity: 0;
}
100% {
display: block;
opacity: 1;
}
}
.collapsible {
border-top: 1px solid rgba(0,0,0,0.1);
}
.collapsible:last-of-type {
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.collapsible > input {
display: none;
}
.collapsible > label {
padding: 1em 0;
cursor: pointer;
border-right: 0;
border-left: 0;
display: flex;
justify-content: space-between;
}
.collapsible > label * {
margin: 0;
color: rgba(0,0,0,0.57);
}
.collapsible > label i {
transition: .2s ease transform;
user-select: none;
}
.collapsible .collapse {
max-height: 0;
overflow: hidden;
transition: .2s ease all;
}
.collapsible > input:checked ~ .collapse {
padding-top: 1em;
padding-bottom: 1em;
max-height: 20em;
}
.collapsible > input:checked ~ label i {
transform: rotate(180deg)
}
.card .collapsible {
width: calc(100% + 2em);
margin: 0 -1em;
}
.card .collapsible > label {
padding: 1em;
}
.card .collapsible .collapse {
padding: 0 1em;
}
================================================
FILE: src/css/fonts.css
================================================
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url(../assets/fonts/roboto/normal-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-cyrillic.woff2) format('woff2');
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-greek.woff2) format('woff2');
unicode-range: U+0370-03FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 500;
src: local('Roboto Medium'), local('Roboto-Medium'), url(../assets/fonts/roboto/medium-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
@import "~material-design-icons/iconfont/material-icons.css";
================================================
FILE: src/css/header.css
================================================
header {
z-index: 1000;
background-color: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 0;
display: flex;
}
header .overlay {
width: 0;
height: 0;
}
header a,
header a:hover {
color: inherit;
}
header>div:first-child>.action,
header img {
margin-right: 1em;
}
header img {
height: 2.5em;
}
header>div:first-child>.action {
display: none;
}
header>div {
display: flex;
width: 100%;
padding: 0.5em 0.5em 0.5em 1em;
align-items: center;
}
header .action span {
display: none;
}
header>div div {
vertical-align: middle;
position: relative;
}
header>div:last-child div {
display: flex;
}
header>div:first-child {
height: 4em;
}
header>div:last-child {
justify-content: flex-end;
}
header .search-button {
display: none;
}
#more {
display: none;
}
#search {
position: relative;
height: 100%;
width: 100%;
max-width: 25em;
}
#search.active {
position: fixed;
top: 0;
right: 0;
width: 100%;
max-width: 100%;
height: 100%;
z-index: 9999;
}
#search #input {
background-color: #f5f5f5;
display: flex;
padding: 0.75em;
border-radius: 0.3em;
transition: .1s ease all;
align-items: center;
z-index: 2;
}
#search.active #input {
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
background-color: #fff;
height: 4em;
}
#search.active>div {
border-radius: 0 !important;
}
#search.active i,
#search.active input {
color: #212121;
}
#search #input>.action,
#search #input>i {
margin-right: 0.3em;
user-select: none;
}
#search input {
width: 100%;
border: 0;
background-color: transparent;
padding: 0;
}
#search #result {
visibility: visible;
max-height: none;
background-color: #f8f8f8;
text-align: left;
padding: 0;
color: rgba(0, 0, 0, 0.6);
height: 0;
transition: .1s ease height, .1s ease padding;
overflow-x: hidden;
overflow-y: auto;
z-index: 1;
}
#search #result>div>*:first-child {
margin-top: 0;
}
#search.active #result {
padding: .5em;
height: calc(100% - 4em);
}
#search ul {
padding: 0;
margin: 0;
list-style: none;
}
#search li {
margin-bottom: .5em;
}
#search #result>div {
max-width: 45em;
margin: 0 auto;
}
#search #result #renew {
width: 100%;
text-align: center;
display: none;
margin: 0;
max-width: none;
}
#search.ongoing #result #renew {
display: block;
}
#search.active #result i {
color: #ccc;
}
#search.active #result>p>i {
text-align: center;
margin: 0 auto;
display: table;
}
#search.active #result ul li a {
display: flex;
align-items: center;
padding: .3em 0;
}
#search.active #result ul li a i {
margin-right: .3em;
}
#search::-webkit-input-placeholder {
color: rgba(255, 255, 255, .5);
}
#search:-moz-placeholder {
opacity: 1;
color: rgba(255, 255, 255, .5);
}
#search::-moz-placeholder {
opacity: 1;
color: rgba(255, 255, 255, .5);
}
#search:-ms-input-placeholder {
color: rgba(255, 255, 255, .5);
}
#search .boxes {
border: 1px solid rgba(0, 0, 0, 0.075);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
background: #fff;
margin: 1em 0;
}
#search .boxes h3 {
margin: 0;
font-weight: 500;
font-size: 1em;
color: #212121;
padding: .5em;
}
#search .boxes>div {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-right: -1em;
margin-bottom: -1em;
}
#search .boxes>div>div {
background: var(--blue);
color: #fff;
text-align: center;
width: 10em;
padding: 1em;
cursor: pointer;
margin-bottom: 1em;
margin-right: 1em;
flex-grow: 1;
}
#search .boxes p {
margin: 1em 0 0;
}
#search .boxes i {
color: #fff !important;
font-size: 3.5em;
}
================================================
FILE: src/css/listing.css
================================================
#listing h2 {
margin: 0 0 0 0.5em;
font-size: .9em;
color: rgba(0, 0, 0, 0.38);
font-weight: 500;
}
#listing .item div:last-of-type * {
text-overflow: ellipsis;
overflow: hidden;
}
#listing>div {
display: flex;
padding: 0;
flex-wrap: wrap;
justify-content: flex-start;
position: relative;
}
#listing .item {
background-color: #fff;
position: relative;
display: flex;
flex-wrap: nowrap;
color: #6f6f6f;
transition: .1s ease background, .1s ease opacity;
align-items: center;
cursor: pointer;
}
#listing .item div:last-of-type {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
#listing .item p {
margin: 0;
}
#listing .item .size,
#listing .item .modified {
font-size: 0.9em;
}
#listing .item .name {
font-weight: bold;
}
#listing .item i {
font-size: 4em;
margin-right: 0.1em;
vertical-align: bottom;
}
.message {
text-align: center;
font-size: 2em;
margin: 1em auto;
display: block !important;
width: 95%;
color: rgba(0, 0, 0, 0.3);
font-weight: 500;
}
.message i {
font-size: 2.5em;
margin-bottom: .2em;
display: block;
}
#listing.mosaic {
padding-top: 1em;
margin: 0 -0.5em;
}
#listing.mosaic .item {
width: calc(33% - 1em);
margin: .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);
}
#listing.mosaic .item:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
}
#listing.mosaic .header {
display: none;
}
#listing.mosaic .item div:first-of-type {
width: 5em;
}
#listing.mosaic .item div:last-of-type {
width: calc(100% - 5vw);
}
#listing.list {
flex-direction: column;
width: 100%;
max-width: 100%;
margin: 0;
}
#listing.list .item {
width: 100%;
margin: 0;
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 1em;
border-top: 0;
}
#listing.list h2 {
display: none;
}
#listing .item[aria-selected=true] {
background: var(--blue) !important;
color: #fff !important;
}
#listing.list .item div:first-of-type {
width: 3em;
}
#listing.list .item div:first-of-type i {
font-size: 2em;
}
#listing.list .item div:last-of-type {
width: calc(100% - 3em);
display: flex;
align-items: center;
}
#listing.list .item .name {
width: 50%;
}
#listing.list .item .size {
width: 25%;
}
#listing .item.header {
display: none !important;
background-color: #ccc;
}
#listing.list .header i {
font-size: 1.5em;
vertical-align: middle;
margin-left: .2em;
}
#listing.list .item.header {
display: flex !important;
background: #fafafa;
z-index: 999;
padding: .85em;
border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
#listing.list .item.header>div:first-child {
width: 0;
}
#listing.list .item.header .name {
margin-right: 3em;
}
#listing.list .header a {
color: inherit;
}
#listing.list .item.header>div:first-child {
width: 0;
}
#listing.list .name {
font-weight: normal;
}
#listing.list .item.header .name {
margin-right: 3em;
}
#listing.list .header span {
vertical-align: middle;
}
#listing.list .header i {
opacity: 0;
transition: .1s ease all;
}
#listing.list .header p:hover i,
#listing.list .header .active i {
opacity: 1;
}
#listing.list .item.header .active {
font-weight: bold;
}
@keyframes slidein {
from {
bottom: -4em;
}
to {
bottom: 0;
}
}
#listing #multiple-selection {
position: fixed;
bottom: -4em;
left: 0;
z-index: 99999;
width: 100%;
background-color: var(--blue);
height: 4em;
display: none;
padding: 0.5em 0.5em 0.5em 1em;
justify-content: space-between;
align-items: center;
transition: .2s ease bottom;
}
#listing #multiple-selection.active {
animation: slidein 0.2s forwards;
display: flex;
}
#listing #multiple-selection p,
#listing #multiple-selection i {
color: #fff;
}
================================================
FILE: src/css/login.css
================================================
#login {
background: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#login img {
width: 4em;
height: 4em;
margin: 0 auto;
display: block;
}
#login h1 {
text-align: center;
font-size: 2.5em;
margin: .4em 0 .67em;
}
#login form {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 16em;
width: 90%;
}
#login.recaptcha form {
min-width: 304px;
}
#login #recaptcha {
margin: .5em 0 0;
}
#login .wrong {
background: var(--red);
color: #fff;
padding: .5em;
text-align: center;
animation: .2s opac forwards;
}
@keyframes opac {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#login p {
cursor: pointer;
text-align: right;
color: var(--blue);
text-transform: lowercase;
font-weight: 500;
font-size: 0.9rem;
margin: .5rem 0;
}
================================================
FILE: src/css/mobile.css
================================================
@media (max-width: 1024px) {
nav {
width: 10em
}
}
@media (max-width: 1024px) {
main {
width: calc(100% - 13em)
}
}
@media (max-width: 736px) {
body {
padding-bottom: 5em;
}
#listing.list .item .size {
display: none;
}
#listing.list .item .name {
width: 60%;
}
#more {
display: inherit
}
header .overlay {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.1);
}
#dropdown {
position: fixed;
top: 1em;
right: 1em;
display: block;
background-color: #fff;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transform: scale(0);
transition: .1s ease-in-out transform;
transform-origin: top right;
z-index: 99999;
}
#dropdown > div {
display: block;
}
#dropdown.active {
transform: scale(1);
}
#dropdown .action {
display: flex;
align-items: center;
border-radius: 0;
width: 100%;
}
#dropdown .action span:not(.counter) {
display: inline-block;
padding: .4em;
}
#dropdown .counter {
left: 2.25em;
}
#file-selection {
position: fixed;
bottom: 1em;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
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: 20em;
}
#file-selection .action {
border-radius: 50%;
width: auto;
}
#file-selection > span {
display: inline-block;
margin-left: 1em;
color: #6f6f6f;
margin-right: auto;
}
nav {
top: 0;
z-index: 99999;
background: #fff;
height: 100%;
width: 16em;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
transition: .1s ease left;
left: -17em;
}
nav.active {
left: 0;
}
header .search-button,
header>div:first-child>.action {
display: inherit;
}
header img {
display: none;
}
#listing {
margin-bottom: 5em;
}
main {
margin: 0 1em;
width: calc(100% - 2em);
}
#search {
display: none;
}
#search.active {
display: block;
}
}
@media (max-width: 450px) {
#listing.list .item .modified {
display: none;
}
#listing.list .item .name {
width: 100%;
}
}
================================================
FILE: src/css/styles.css
================================================
@import "~normalize.css/normalize.css";
@import "~noty/lib/noty.css";
@import "~noty/lib/themes/mint.css";
@import "./_variables.css";
@import "./_buttons.css";
@import "./_inputs.css";
@import "./_shell.css";
@import "./_share.css";
@import "./fonts.css";
@import "./base.css";
@import "./header.css";
@import "./listing.css";
@import "./dashboard.css";
@import "./login.css";
.link {
color: var(--blue);
}
/* * * * * * * * * * * * * * * *
* ACTION *
* * * * * * * * * * * * * * * */
.action {
display: inline-block;
cursor: pointer;
transition: 0.2s ease all;
border: 0;
margin: 0;
color: #546E7A;
border-radius: 50%;
background: transparent;
padding: 0;
box-shadow: none;
vertical-align: middle;
text-align: left;
position: relative;
}
.action.disabled {
opacity: 0.2;
cursor: not-allowed;
}
.action i {
padding: 0.4em;
transition: .1s ease-in-out all;
border-radius: 50%;
}
.action:hover {
background-color: rgba(0, 0, 0, .1);
}
.action ul {
position: absolute;
top: 0;
color: #7d7d7d;
list-style: none;
margin: 0;
padding: 0;
flex-direction: column;
display: flex;
}
.action ul li {
line-height: 1;
padding: .7em;
transition: .1s ease background-color;
}
.action ul li:hover {
background-color: rgba(0, 0, 0, 0.04);
}
#click-overlay {
display: none;
position: fixed;
cursor: pointer;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
#click-overlay.active {
display: block;
}
.action .counter {
display: block;
position: absolute;
bottom: 0;
right: 0;
background: var(--blue);
color: #fff;
border-radius: 50%;
font-size: .75em;
width: 1.5em;
height: 1.5em;
text-align: center;
line-height: 1.25em;
border: 2px solid white;
}
/* PREVIEWER */
#previewer {
background-color: rgba(0, 0, 0, 0.9);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
overflow: hidden;
}
#previewer .bar {
width: 100%;
text-align: right;
display: flex;
padding: 0.5em;
height: 3.7em;
}
#previewer .action:first-of-type {
margin-right: auto;
}
#previewer .action i {
color: #fff;
}
#previewer .action:hover {
background-color: rgba(255, 255, 255, 0.3)
}
#previewer .action span {
display: none;
}
#previewer .preview {
margin: 2em auto 4em;
max-width: 80%;
text-align: center;
height: calc(100vh - 9.7em);
}
#previewer .preview pre {
text-align: left;
overflow: auto;
}
#previewer .preview pre,
#previewer .preview video,
#previewer .preview img {
max-height: 100%;
margin: 0;
}
#previewer .pdf {
width: 100%;
height: 100%;
}
#previewer h2.message {
color: rgba(255, 255, 255, 0.5)
}
#previewer>button {
margin: 0;
position: fixed;
top: 50%;
transform: translateY(-50%);
}
#previewer>button:first-of-type {
left: 0.5em;
}
#previewer>button:last-of-type {
right: 0.5em;
}
/* * * * * * * * * * * * * * * *
* PROMPT *
* * * * * * * * * * * * * * * */
.noty_buttons {
text-align: right;
padding: 0 10px 10px !important;
}
.noty_buttons button {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0,0,0,0.1);
box-shadow: 0 0 0 0;
font-size: 14px;
}
/* * * * * * * * * * * * * * * *
* FOOTER *
* * * * * * * * * * * * * * * */
.credits {
font-size: 0.6em;
margin: 3em 2.5em;
color: #a5a5a5;
}
.credits > span {
display: block;
margin: .3em 0;
}
.credits a,
.credits a:hover {
color: inherit;
cursor: pointer;
}
/* * * * * * * * * * * * * * * *
* ANIMATIONS *
* * * * * * * * * * * * * * * */
@keyframes spin {
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
/* * * * * * * * * * * * * * * *
* SETTINGS RULES *
* * * * * * * * * * * * * * * */
.rules > div {
display: flex;
align-items: center;
margin: .5rem 0;
}
.rules input[type="checkbox"] {
margin-right: .2rem;
}
.rules input[type="text"] {
border: 1px solid#ddd;
padding: .2rem;
}
.rules label {
margin-right: .5rem;
}
.rules button {
margin-left: auto;
}
.rules button.delete {
padding: .2rem .5rem;
margin-left: .5rem;
}
@import './mobile.css';
================================================
FILE: src/i18n/ar.json
================================================
{
"permanent": "دائم",
"buttons": {
"shell": "Toggle shell",
"cancel": "إلغاء",
"close": "إغلاق",
"copy": "نسخ",
"copyFile": "نسخ الملف",
"copyToClipboard": "نسخ الى الحافظة",
"create": "إنشاء",
"delete": "حذف",
"download": "تحميل",
"info": "معلومات",
"more": "المزيد",
"move": "نقل",
"moveFile": "نقل الملف",
"new": "جديد",
"next": "التالي",
"ok": "موافق",
"replace": "استبدال",
"previous": "السابق",
"rename": "إعادة تسمية",
"reportIssue": "إبلاغ عن مشكلة",
"save": "حفظ",
"search": "بحث",
"select": "تحديد",
"share": "مشاركة",
"publish": "نشر",
"selectMultiple": "تحديد متعدد",
"schedule": "جدولة",
"switchView": "تغيير العرض",
"toggleSidebar": "تبديل الشريط الجانبي",
"update": "تحديث",
"upload": "رفع",
"permalink": "الحصول على لنك دائم"
},
"success": {
"linkCopied": "تم نسخ الملف"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "لقد حدث خطأ ما.",
"notFound": "لا يمكن الوصول لهذا المحتوى."
},
"files": {
"folders": "المجلدات",
"files": "الملفات",
"body": "الصفحة",
"clear": "مسح",
"closePreview": "إغلاق العرض",
"home": "الصفحة الاولى",
"lastModified": "آخر تعديل",
"loading": "جاري التحميل...",
"lonely": "تبدو وحيدا هنا...",
"metadata": "بيانات تعريفية",
"multipleSelectionEnabled": "التحديد المتعدد مفعل",
"name": "الإسم",
"size": "الحجم",
"sortByName": "الترتيب بالإسم",
"sortBySize": "الترتيب بالحجم",
"sortByLastModified": "الترتيب بآخر تعديل"
},
"help": {
"click": "حدد الملف أو المجلد",
"ctrl": {
"click": "حدد أكثر من ملف او مجلد",
"f": "إبدأ البحث",
"s": "حمل الملف او المجلد في هذا المكان"
},
"del": "حذف البيانات المحددة",
"doubleClick": "فتح المجلد او الملف",
"esc": "مسح التحديد وإغلاق النافذة المنبثقة",
"f1": "هذه المعلومات",
"f2": "إعادة تسمية الملف",
"help": "مساعدة"
},
"login": {
"password": "كلمة المرور",
"passwordConfirm": "Password Confirmation",
"submit": "تسجيل دخول",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"username": "إسم المستخدم",
"wrongCredentials": "بيانات دخول خاطئة"
},
"prompts": {
"copy": "نسخ",
"copyMessage": "رجاء حدد المكان لنسخ ملفاتك فيه:",
"currentlyNavigating": "يتم الإنتقال حاليا إلى:",
"deleteMessageMultiple": "هل تريد بالتأكيد حذف {count} ملف؟",
"deleteMessageSingle": "هل تريد بالتأكيد حذف هذا الملف/المجلد؟",
"deleteTitle": "حذف الملفات",
"displayName": "الإسم:",
"download": "تحميل الملفات",
"downloadMessage": "حدد إمتداد الملف المراد تحميله.",
"error": "لقد حدث خطأ ما",
"fileInfo": "معلومات الملف",
"filesSelected": "تم تحديد {count} ملفات.",
"lastModified": "آخر تعديل",
"move": "نقل",
"moveMessage": "إختر مكان جديد للملفات أو المجلدات المراد نقلها:",
"newDir": "مجلد جديد",
"newDirMessage": "رجاء أدخل اسم المجلد الجديد.",
"newFile": "ملف جديد",
"newFileMessage": "رجاء ادخل اسم الملف الجديد.",
"numberDirs": "عدد المجلدات",
"numberFiles": "عدد الملفات",
"replace": "إستبدال",
"replaceMessage": "أحد الملفات التي تحاول رفعها يتعارض مع ملف موجود بنفس الإسم. هل تريد إستبدال الملف الموجود؟\n",
"rename": "إعادة تسمية",
"renameMessage": "إدراج اسم جديد لـ",
"show": "عرض",
"size": "الحجم",
"schedule": "جدولة",
"scheduleMessage": "أختر الوقت والتاريخ لجدولة نشر هذا المقال.",
"newArchetype": "إنشاء منشور من المنشور الأصلي. الملف سيتم انشاءه في مجلد المحتويات."
},
"settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "تنفيذ الأوامر",
"allowEdit": "تعديل، إعادة تسمية وحذف الملفات والمجلدات",
"allowNew": "إنشاء ملفات ومجلدات جديدة",
"allowPublish": "نشر مقالات وصفحات جديدة",
"avoidChanges": "(أتركه فارغاً إن لم ترد تغييره)",
"changePassword": "تغيير كلمة المرور",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "تم تحديث الأوامر",
"customStylesheet": "ستايل مخصص",
"examples": "أمثلة",
"globalSettings": "إعدادات عامة",
"language": "اللغة",
"lockPassword": "منع المستخدم من تغيير كلمة المرور",
"newPassword": "كلمة المرور الجديدة",
"newPasswordConfirm": "تأكيد كلمة المرور",
"newUser": "مستخدم جديد",
"password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور",
"permissions": "الصلاحيات",
"permissionsHelp": "يمكنك تعيين المستخدم كـ \"مدير\" أو تحديد الصلاحيات بشكل منفرد.\n إذا قمت بتحديد المستخدم كـ \"مدير\"، باقي الخيارات سيتم تحديدها تلقائياً.\n إدارة المستخدمين تبقى صلاحية فريدة للـ \"مدير\" فقط.\n",
"profileSettings": "إعدادات الحساب",
"ruleExample1": "منع الوصول إلى الملفات التي تبدأ بنقطة مثل (.git، و .gitignore) في كل مجلد.\n",
"ruleExample2": "منع الوصول إلى الملف المسمى Caddyfile في نطاق الجذر.",
"rules": "المجموعات",
"rulesHelp": "يمكنك هنا تحديد مجموعة من شروط السماح والمنع لهذا المستخدم. الملفات الممنوعة لن تظهر ضمن القائمة لهذا المستخدم ولن يستطيع الوصول لها. هنا ندعم الـ regex والـ relative path لنطاق المستخدمين.\n",
"scope": "نطاق",
"settingsUpdated": "تم تعديل الإعدادات",
"user": "المستخدم",
"userCommands": "الأوامر",
"userCommandsHelp": "الأوامر المتاحة لهذا المستخدم مفصولة فيما بينها بمسافة. مثال:\n",
"userCreated": "تم إنشاء المستخدم",
"userDeleted": "تم حذف المستخدم",
"userManagement": "إدارة المستخدمين",
"username": "إسم المستخدم",
"users": "المستخدمين",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "تم تعديل المستخدم",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new users.",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
},
"sidebar": {
"help": "مساعدة",
"login": "Login",
"signup": "Signup",
"logout": "تسجيل خروج",
"myFiles": "ملفاتي",
"newFile": "ملف جديد",
"newFolder": "مجلد جديد",
"settings": "الإعدادات",
"siteSettings": "إعدادات الموقع",
"hugoNew": "هيوجو جديد",
"preview": "معاينة"
},
"search": {
"images": "الصور",
"music": "الموسيقى",
"pdf": "PDF",
"types": "الأنواع",
"video": "فيديوهات",
"search": "البحث...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "وحدة الوقت",
"seconds": "ثواني",
"minutes": "دقائق",
"hours": "ساعات",
"days": "أيام"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
}
}
================================================
FILE: src/i18n/de.json
================================================
{
"permanent": "Permanent",
"buttons": {
"shell": "Kommandozeile ein/ausschalten",
"cancel": "Abbrechen",
"close": "Schließen",
"copy": "Kopieren",
"copyFile": "Kopiere Datei",
"copyToClipboard": "In Zwischenablage kopieren",
"create": "Neu",
"delete": "Löschen",
"download": "Downloaden",
"info": "Info",
"more": "mehr",
"move": "Verschieben",
"moveFile": "Verschiebe Datei",
"new": "Neu",
"next": "nächste",
"ok": "OK",
"replace": "Ersetzen",
"previous": "vorherige",
"rename": "umbenennen",
"reportIssue": "Fehler melden",
"save": "Speichern",
"search": "Suchen",
"select": "Auswählen",
"share": "Teilen",
"publish": "Veröffentlichen",
"selectMultiple": "Mehrfachauswahl",
"schedule": "Planung",
"switchView": "Ansicht wechseln",
"toggleSidebar": "Seitenleiste anzeigen",
"update": "Update",
"upload": "Upload",
"permalink": "permanenten Verweis anzeigen"
},
"success": {
"linkCopied": "Verweis wurde kopiert!"
},
"errors": {
"forbidden": "Sie haben keine Berechtigung dies abzurufen.",
"internal": "Etwas ist schief gelaufen.",
"notFound": "Dieser Ort kann nicht angezeigt werden."
},
"files": {
"folders": "Ordner",
"files": "Dateien",
"body": "Body",
"clear": "Clear",
"closePreview": "Vorschau schließen",
"home": "Home",
"lastModified": "zuletzt verändert",
"loading": "Lade...",
"lonely": "Hier scheint nichts zu sein...",
"metadata": "Metadaten",
"multipleSelectionEnabled": "Mehrfachauswahl ausgewählt",
"name": "Name",
"size": "Größe",
"sortByName": "Nach Namen sortieren",
"sortBySize": "Nach Größe sortieren",
"sortByLastModified": "Nach Änderungsdatum sortieren"
},
"help": {
"click": "wähle Datei oder Ordner",
"ctrl": {
"click": "markiere mehrere Dateien oder Ordner",
"f": "öffnet eine neue Suche",
"s": "speichert eine Datei oder einen Ordner am akutellen Ort"
},
"del": "löscht die ausgewählten Elemente",
"doubleClick": "öffnet eine Datei oder einen Ordner",
"esc": "Auswahl zurücksetzen und/oder Dialog schließen",
"f1": "diese Informationsseite",
"f2": "Datei umbenennen",
"help": "Hilfe"
},
"login": {
"password": "Passwort",
"passwordConfirm": "Passwort Bestätigung",
"submit": "Login",
"createAnAccount": "Account erstellen",
"loginInstead": "Account besteht bereits",
"passwordsDontMatch": "Passwörter stimmen nicht überein",
"usernameTaken": "Benutzername ist bereits vergeben",
"signup": "Registrieren",
"username": "Benutzername",
"wrongCredentials": "Falsche Zugangsdaten"
},
"prompts": {
"copy": "Kopieren",
"copyMessage": "Wählen Sie einen Zielort zum Kopieren:",
"currentlyNavigating": "Aktueller Ort:",
"deleteMessageMultiple": "Sind Sie sicher, dass Sie {count} Datei(en) löschen möchten?",
"deleteMessageSingle": "Sind Sie sicher, dass Sie diesen Ordner/diese Datei löschen möchten?",
"deleteTitle": "Lösche Dateien",
"displayName": "Display Name:",
"download": "Lade Dateien",
"downloadMessage": "Wählen Sie ein Format zum downloaden aus.",
"error": "Etwas ist schief gelaufen",
"fileInfo": "Dateiinformation",
"filesSelected": "{count} Dateien ausgewählt.",
"lastModified": "Zuletzt geändert",
"move": "Verschieben",
"moveMessage": "Wählen sie einen neuen Platz für ihre Datei(en)/Ordner:",
"newDir": "Neuer Ordner",
"newDirMessage": "Geben Sie den Namen des neuen Ordners an.",
"newFile": "Neue Datei",
"newFileMessage": "Geben Sie den Namen der neuen Datei an.",
"numberDirs": "Anzahl der Ordner",
"numberFiles": "Anzahl der Dateien",
"replace": "Ersetzen",
"replaceMessage": "Eine der Datei mit dem gleichen Namen, wie die Sie hochladen wollen, existiert bereits. Soll die vorhandene Datei ersetzt werden ?\n",
"rename": "Umbenennen",
"renameMessage": "Fügen Sie einen Namen ein für",
"show": "Anzeigen",
"size": "Größe",
"schedule": "Plan",
"scheduleMessage": "Wählen Sie ein Datum und eine Zeit für die Veröffentlichung dieses Beitrags.",
"newArchetype": "Erstelle neuen Beitrag auf dem Archetyp. Ihre Datei wird im Inhalteordner erstellt."
},
"settings": {
"instanceName": "Instanzname",
"brandingDirectoryPath": "Markenverzeichnispfad",
"documentation": "Dokumentation",
"branding": "Marke",
"disableExternalLinks": "Externe Links deaktivieren (außer Dokumentation)",
"brandingHelp": "Sie können das Erscheinungsbild ihres File Browser anpassen, in dem sie den Namen ändern, das Logo austauchsen oder eigene Stile definieren und sogar externe Links zu GitHub deaktivieren.\nUm mehr Informationen zum Anpassen an ihre Marke zu bekommen, gehen sie bitte zu {0}.",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Befehle ausführen",
"allowEdit": "Bearbeiten, Umbenennen und Löschen von Dateien oder Ordnern",
"allowNew": "Erstellen neuer Dateien und Ordner",
"allowPublish": "Veröffentlichen von neuen Beiträgen und Seiten",
"avoidChanges": "(leer lassen um Änderungen zu vermeiden)",
"changePassword": "Ändere das Passwort",
"commandRunner": "Befehlseingabe",
"commandRunnerHelp": "Hier könne sie Befehle eintragen die bei benannten Aktionen ausgeführt werden. Sie müssen pro Zeile jeweils einen eingeben. Die Umgebungsvariable {0} und {1} sind verfügbar, wobei {0} relative zu {1} ist. Für mehr Informationen über diese Funktion und die verfügbaren Umgebungsvariablen, lesen sie bitte das {2}.",
"commandsUpdated": "Befehle aktualisiert!",
"customStylesheet": "Individuelles Stylesheet",
"examples": "Beispiele",
"globalSettings": "Globale Einstellungen",
"language": "Sprache",
"lockPassword": "Verhindere, dass der Benutzer sein Passwort ändert",
"newPassword": "Ihr neues Passwort.",
"newPasswordConfirm": "Bestätigen Sie Ihr neues Passwort",
"newUser": "Neuer Benutzer",
"password": "Passwort",
"passwordUpdated": "Passwort aktualisiert!",
"permissions": "Berechtigungen",
"permissionsHelp": "Sie können einem Benutzer Administratorrechte einräumen oder die Berechtigunen individuell festlegen. Wenn Sie \"Administrator\" auswählen, werden alle anderen Rechte automatisch vergeben. Die Nutzerverwaltung kann nur durch einen Administrator erfolgen.\n",
"profileSettings": "Profileinstellungen",
"ruleExample1": "Verhindert den Zugang zu dot Dateien (dot Files, wie .git, .gitignore) in allen Ordnern\n",
"ruleExample2": "blockiert den Zugang auf Dateien mit dem Namen Caddyfile in der Wurzel/Basis des scopes.",
"rules": "Regeln",
"rulesHelp": "Hier können Sie erlaubte und verbotene Aktionen für einen einzelnen Benutzer festlegen. Bockierte Dateien werden nicht im Listing angezeigt und sind nicht erreichbar für den Nutzer. Wir unterstützen reguläre Ausdrücke (Regex) und Pfade die relativ zum Benutzerordner sind. \n",
"scope": "Scope",
"settingsUpdated": "Einstellungen aktualisiert!",
"user": "Benutzer",
"userCommands": "Befehle",
"userCommandsHelp": "Eine Liste, mit einem Leerzeichen als Trennung, mit den für diesen Nutzer verfügbaren Befehlen. Example:\n",
"userCreated": "Benutzer angelegt!",
"userDeleted": "Benutzer gelöscht!",
"userManagement": "Benutzerverwaltung",
"username": "Nutzername",
"users": "Nutzer",
"globalRules": "Das ist ein globales Set von Regeln die erlauben oder nicht erlauben. Die sind für alle Benutzer zutreffend. Es können spezielle Regeln in den Einstellungen der Benutzer definiert werden die diese übersteuern.",
"allowSignup": "Erlaube Benutzern sich zu registrieren",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Regex Ausdruck einfügen",
"insertPath": "Pfad einfügen",
"userUpdated": "Benutzer aktualisiert!",
"userDefaults": "Benutzer Standard Einstellungen",
"defaultUserDescription": "Das sind die Standard Einstellunge für Benutzer",
"executeOnShell": "In shell ausführen",
"executeOnShellDescription": "Es ist voreingestellt das der File Brower Befehle ausführt in dem er die Befehlsdatein direkt auf ruft. Wenn sie wollen das sie auf einer Kommandozeile (wo Bash oder PowerShell) laufen, könne sie das hier definieren mit allen bennötigten Argumenten und Optionen. Wenn gesetzt, wird das Kommando das ausgeführt werden soll als Parameter angehängt. Das gilt für Benuzerkommandos sowie auch für Ereignisse.",
"perm": {
"create": "Dateien und Ordner erstellen",
"delete": "Dateien und Ordner löschen",
"download": "Download",
"modify": "Dateien editieren",
"execute": "Befehle ausführen",
"rename": "Umbenennen oder Verschieben von Dateien oder Ordnern",
"share": "Datei teilen"
}
},
"sidebar": {
"help": "Hilfe",
"login": "Anmelden",
"signup": "Registrieren",
"logout": "Logout",
"myFiles": "Meine Dateien",
"newFile": "Neue Datei",
"newFolder": "Neuer Ordner",
"settings": "Einstellungen",
"siteSettings": "Seiteneinstellungen",
"hugoNew": "Hugo Neu",
"preview": "Vorschau"
},
"search": {
"images": "Bilder",
"music": "Musik",
"pdf": "PDF",
"types": "Typen",
"video": "Video",
"search": "Suche...",
"typeToSearch": "Tippe um zu suchen...",
"pressToSearch": "Drücken sie Enter um zu suchen..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Zeiteinheit",
"seconds": "Sekunden",
"minutes": "Minuten",
"hours": "Stunden",
"days": "Tage"
},
"download": {
"downloadFile": "Download Datei",
"downloadFolder": "Download Ordner"
}
}
================================================
FILE: src/i18n/en.json
================================================
{
"permanent": "Permanent",
"buttons": {
"shell": "Toggle shell",
"cancel": "Cancel",
"close": "Close",
"copy": "Copy",
"copyFile": "Copy file",
"copyToClipboard": "Copy to clipboard",
"create": "Create",
"delete": "Delete",
"download": "Download",
"info": "Info",
"more": "More",
"move": "Move",
"moveFile": "Move file",
"new": "New",
"next": "Next",
"ok": "OK",
"replace": "Replace",
"previous": "Previous",
"rename": "Rename",
"reportIssue": "Report Issue",
"save": "Save",
"search": "Search",
"select": "Select",
"share": "Share",
"publish": "Publish",
"selectMultiple": "Select multiple",
"schedule": "Schedule",
"switchView": "Switch view",
"toggleSidebar": "Toggle sidebar",
"update": "Update",
"upload": "Upload",
"permalink": "Get Permanent Link"
},
"success": {
"linkCopied": "Link copied!"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "Something really went wrong.",
"notFound": "This location can't be reached."
},
"files": {
"folders": "Folders",
"files": "Files",
"body": "Body",
"clear": "Clear",
"closePreview": "Close preview",
"home": "Home",
"lastModified": "Last modified",
"loading": "Loading...",
"lonely": "It feels lonely here...",
"metadata": "Metadata",
"multipleSelectionEnabled": "Multiple selection enabled",
"name": "Name",
"size": "Size",
"sortByName": "Sort by name",
"sortBySize": "Sort by size",
"sortByLastModified": "Sort by last modified"
},
"help": {
"click": "select file or directory",
"ctrl": {
"click": "select multiple files or directories",
"f": "opens search",
"s": "save a file or download the directory where you are"
},
"del": "delete selected items",
"doubleClick": "open a file or directory",
"esc": "clear selection and/or close the prompt",
"f1": "this information",
"f2": "rename file",
"help": "Help"
},
"login": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Login",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"username": "Username",
"wrongCredentials": "Wrong credentials"
},
"prompts": {
"copy": "Copy",
"copyMessage": "Choose the place to copy your files:",
"currentlyNavigating": "Currently navigating on:",
"deleteMessageMultiple": "Are you sure you want to delete {count} file(s)?",
"deleteMessageSingle": "Are you sure you want to delete this file/folder?",
"deleteTitle": "Delete files",
"displayName": "Display Name:",
"download": "Download files",
"downloadMessage": "Choose the format you want to download.",
"error": "Something went wrong",
"fileInfo": "File information",
"filesSelected": "{count} files selected.",
"lastModified": "Last Modified",
"move": "Move",
"moveMessage": "Choose new house for your file(s)/folder(s):",
"newDir": "New directory",
"newDirMessage": "Write the name of the new directory.",
"newFile": "New file",
"newFileMessage": "Write the name of the new file.",
"numberDirs": "Number of directories",
"numberFiles": "Number of files",
"replace": "Replace",
"replaceMessage": "One of the files you're trying to upload is conflicting because of its name. Do you wish to replace the existing one?\n",
"rename": "Rename",
"renameMessage": "Insert a new name for",
"show": "Show",
"size": "Size",
"schedule": "Schedule",
"scheduleMessage": "Pick a date and time to schedule the publication of this post.",
"newArchetype": "Create a new post based on an archetype. Your file will be created on content folder."
},
"settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin",
"administrator": "Administrator",
"allowCommands": "Execute commands",
"allowEdit": "Edit, rename and delete files or directories",
"allowNew": "Create new files and directories",
"allowPublish": "Publish new posts and pages",
"avoidChanges": "(leave blank to avoid changes)",
"changePassword": "Change Password",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commands updated!",
"customStylesheet": "Custom Stylesheet",
"examples": "Examples",
"globalSettings": "Global Settings",
"language": "Language",
"lockPassword": "Prevent the user from changing the password",
"newPassword": "Your new password",
"newPasswordConfirm": "Confirm your new password",
"newUser": "New User",
"password": "Password",
"passwordUpdated": "Password updated!",
"permissions": "Permissions",
"permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n",
"profileSettings": "Profile Settings",
"ruleExample1": "prevents the access to any dot file (such as .git, .gitignore) in every folder.\n",
"ruleExample2": "blocks the access to the file named Caddyfile on the root of the scope.",
"rules": "Rules",
"rulesHelp": "Here you can define a set of allow and disallow rules for this specific user. The blocked files won't show up in the listings and they wont be accessible to the user. We support regex and paths relative to the users scope.\n",
"scope": "Scope",
"settingsUpdated": "Settings updated!",
"user": "User",
"userCommands": "Commands",
"userCommandsHelp": "A space separated list with the available commands for this user. Example:\n",
"userCreated": "User created!",
"userDeleted": "User deleted!",
"userManagement": "User Management",
"username": "Username",
"users": "Users",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "User updated!",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new users.",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
},
"sidebar": {
"help": "Help",
"login": "Login",
"signup": "Signup",
"logout": "Logout",
"myFiles": "My files",
"newFile": "New file",
"newFolder": "New folder",
"settings": "Settings",
"siteSettings": "Site Settings",
"hugoNew": "Hugo New",
"preview": "Preview"
},
"search": {
"images": "Images",
"music": "Music",
"pdf": "PDF",
"types": "Types",
"video": "Video",
"search": "Search...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Time Unit",
"seconds": "Seconds",
"minutes": "Minutes",
"hours": "Hours",
"days": "Days"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
}
}
================================================
FILE: src/i18n/es.json
================================================
{
"permanent": "Permanente",
"buttons": {
"shell": "Presiona Enter para buscar...",
"cancel": "Cancelar",
"close": "Cerrar",
"copy": "Copiar",
"copyFile": "Copiar archivo",
"copyToClipboard": "Copiar al portapapeles",
"create": "Crear",
"delete": "Borrar",
"download": "Descargar",
"info": "Info",
"more": "Más",
"move": "Mover",
"moveFile": "Mover archivo",
"new": "Nuevo",
"next": "Siguiente",
"ok": "OK",
"replace": "Reemplazar",
"previous": "Anterior",
"rename": "Renombrar",
"reportIssue": "Reportar problema",
"save": "Guardar",
"search": "Buscar",
"select": "Seleccionar",
"share": "Compartir",
"publish": "Publicar",
"selectMultiple": "Selección múltiple",
"schedule": "Programar",
"switchView": "Cambiar vista",
"toggleSidebar": "Mostrar/Ocultar menú",
"update": "Actualizar",
"upload": "Subir",
"permalink": "Link permanente"
},
"success": {
"linkCopied": "¡Link copiado!"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "La verdad es que algo ha ido mal.",
"notFound": "No se puede acceder a este lugar."
},
"files": {
"folders": "Carpetas",
"files": "Archivos",
"body": "Cuerpo",
"clear": "Limpiar",
"closePreview": "Cerrar vista previa",
"home": "Inicio",
"lastModified": "Última modificación",
"loading": "Cargando...",
"lonely": "Uno se siente muy sólo aquí...",
"metadata": "Metadatos",
"multipleSelectionEnabled": "Selección múltiple activada",
"name": "Nombre",
"size": "Tamaño",
"sortByName": "Ordenar por nombre",
"sortBySize": "Ordenar por tamaño",
"sortByLastModified": "Ordenar por última modificación"
},
"help": {
"click": "seleccionar archivo o carpeta",
"ctrl": {
"click": "seleccionar múltiples archivos o carpetas",
"f": "abre la búsqueda",
"s": "guarda un archivo o lo descarga a la carpeta en la que estás"
},
"del": "elimina los items seleccionados",
"doubleClick": "abre un archivo o carpeta",
"esc": "limpia la selección y/o cierra la ventana",
"f1": "esta información",
"f2": "renombrar archivo",
"help": "Ayuda"
},
"login": {
"password": "Contraseña",
"passwordConfirm": "Confirmación de contraseña",
"submit": "Iniciar sesión",
"createAnAccount": "Crear una cuenta",
"loginInstead": "Usuario ya existente",
"passwordsDontMatch": "Las contraseñas no coinciden",
"usernameTaken": "Nombre usuario no disponible",
"signup": "Signup",
"username": "Usuario",
"wrongCredentials": "Usuario y/o contraseña incorrectos"
},
"prompts": {
"copy": "Copiar",
"copyMessage": "Elige el lugar donde quieres copiar tus archivos:",
"currentlyNavigating": "Actualmente estás en:",
"deleteMessageMultiple": "¿Estás seguro que quieres eliminar {count} archivo(s)?",
"deleteMessageSingle": "¿Estás seguro que quieres eliminar este archivo/carpeta?",
"deleteTitle": "Borrar archivos",
"displayName": "Nombre:",
"download": "Descargar archivos",
"downloadMessage": "Elige el formato de descarga.",
"error": "Algo ha fallado",
"fileInfo": "Información del archivo",
"filesSelected": "{count} archivos seleccionados.",
"lastModified": "Última modificación",
"move": "Mover",
"moveMessage": "Elige una nueva casa para tus archivo(s)/carpeta(s):",
"newDir": "Nueva carpeta",
"newDirMessage": "Escribe el nombre de la nueva carpeta.",
"newFile": "Nuevo archivo",
"newFileMessage": "Escribe el nombre del nuevo archivo.",
"numberDirs": "Número de carpetas",
"numberFiles": "Número de archivos",
"replace": "Reemplazar",
"replaceMessage": "Uno de los archivos ue intentas subir está creando conflicto por su nombre. ¿Quieres cambiar el nombre del ya existente?\n",
"rename": "Renombrar",
"renameMessage": "Escribe el nuevo nombre para",
"show": "Mostrar",
"size": "Tamaño",
"schedule": "Programar",
"scheduleMessage": "Elige una hora y fecha para programar la publicación de este post.",
"newArchetype": "Crea un nuevo post basado en un arquetipo. Tu archivo será creado en la carpeta de contenido."
},
"settings": {
"instanceName": "Nombre de la instancia",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentación",
"branding": "Branding",
"disableExternalLinks": "Deshabilitar enlaces externos (excepto documentación)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin",
"administrator": "Administrador",
"allowCommands": "Ejecutar comandos",
"allowEdit": "Editar, renombrar y borrar archivos o carpetas",
"allowNew": "Crear nuevos archivos y carpetas",
"allowPublish": "Publicar nuevos posts y páginas",
"avoidChanges": "(dejar en blanco para evitar cambios)",
"changePassword": "Cambiar contraseña",
"commandRunner": "Executor de comandos",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "¡Comandos actualizados!",
"customStylesheet": "Modificar hoja de estilos",
"examples": "Ejemplos",
"globalSettings": "Ajustes globales",
"language": "Idioma",
"lockPassword": "Evitar que el usuario cambie la contraseña",
"newPassword": "Tu nueva contraseña",
"newPasswordConfirm": "Confirma tu contraseña",
"newUser": "Nuevo usuario",
"password": "Contraseña",
"passwordUpdated": "¡Contraseña actualizada!",
"permissions": "Permisos",
"permissionsHelp": "Puedes nombrar al usuario como administrador o elegir los permisos individualmente. Si seleccionas \"Administrador\", todas las otras opciones serán activadas automáticamente. La administración de usuarios es un privilegio de administrador.\n",
"profileSettings": "Ajustes del perfil",
"ruleExample1": "previene el acceso a una extensión de archivo (Como .git) en cada carpeta.\n",
"ruleExample2": "bloquea el acceso al archivo llamado Caddyfile en la carpeta raíz.",
"rules": "Reglas",
"rulesHelp": "Aquí puedes definir un conjunto de reglas de permisos para este usuario específico. Los archivos bloqueados no se mostrarán en las listas y no serán accesibles por el usuario. Puedes utilizar regex y rutas relativas a la raíz del usuario.\n",
"scope": "Raíz",
"settingsUpdated": "¡Ajustes actualizados!",
"user": "Usuario",
"userCommands": "Comandos",
"userCommandsHelp": "Una lista separada por espacios con los comandos permitidos para este usuario. Ejemplo:\n",
"userCreated": "¡Usuario creado!",
"userDeleted": "¡Usuario eliminado!",
"userManagement": "Administración de usuarios",
"username": "Usuario",
"users": "Usuarios",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Permitir registro de usuarios",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Introducir expresión regular",
"insertPath": "Introduce la ruta",
"userUpdated": "¡Usuario actualizado!",
"userDefaults": "Configuración de usuario por defecto",
"defaultUserDescription": "Estas son las configuraciones por defecto para nuevos usuarios.",
"executeOnShell": "Ejecutar en la shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Crear ficheros y directorios",
"delete": "Eliminar ficheros y directorios",
"download": "Descargar",
"modify": "Editar ficheros",
"execute": "Executar comandos",
"rename": "Renombrar o mover ficheros y directorios",
"share": "Compartir ficheros"
}
},
"sidebar": {
"help": "Ayuda",
"login": "Login",
"signup": "Signup",
"logout": "Cerrar sesión",
"myFiles": "Mis archivos",
"newFile": "Nuevo archivo",
"newFolder": "Nueva carpeta",
"settings": "Ajustes",
"siteSettings": "Ajustes del sitio",
"hugoNew": "Nuevo Hugo",
"preview": "Vista previa"
},
"search": {
"images": "Images",
"music": "Música",
"pdf": "PDF",
"types": "Tipos",
"video": "Vídeo",
"search": "Buscar...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Unidad",
"seconds": "Segundos",
"minutes": "Minutos",
"hours": "Horas",
"days": "Días"
},
"download": {
"downloadFile": "Descargar fichero",
"downloadFolder": "Descargar directorio"
}
}
================================================
FILE: src/i18n/fr.json
================================================
{
"permanent": "Permanent",
"buttons": {
"shell": "Toggle shell",
"cancel": "Annuler",
"close": "Fermer",
"copy": "Copier",
"copyFile": "Copier le fichier",
"copyToClipboard": "Copier dans le presse-papier",
"create": "Créer",
"delete": "Supprimer",
"download": "Télécharger",
"info": "Info",
"more": "Plus",
"move": "Déplacer",
"moveFile": "Déplacer le fichier",
"new": "Nouveau",
"next": "Suivant",
"ok": "OK",
"replace": "Remplacer",
"previous": "Précédent",
"rename": "Renommer",
"reportIssue": "Rapport d'erreur",
"save": "Enregistrer",
"search": "Chercher",
"select": "Sélectionner",
"share": "Partager",
"publish": "Publier",
"selectMultiple": "Sélection multiple",
"schedule": "Fixer la date",
"switchView": "Changer le mode d'affichage",
"toggleSidebar": "Afficher/Masquer la barre latérale",
"update": "Mettre à jour",
"upload": "Importer",
"permalink": "Obtenir un lien permanent"
},
"success": {
"linkCopied": "Link copied!"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "Aïe ! Quelque chose s'est mal passé.",
"notFound": "Impossible d'accéder à cet emplacement."
},
"files": {
"folders": "Dossiers",
"files": "Fichiers",
"body": "Corps",
"clear": "Fermer",
"closePreview": "Fermer la prévisualisation",
"home": "Accueil",
"lastModified": "Dernière modification",
"loading": "Chargement...",
"lonely": "Il semble qu'il n'y ai rien par ici...",
"metadata": "Metadonnées",
"multipleSelectionEnabled": "Sélection multiple activée",
"name": "Nom",
"size": "Taille",
"sortByName": "Trier par nom",
"sortBySize": "Trier par taille",
"sortByLastModified": "Trier par date de dernière modification"
},
"help": {
"click": "Sélectionner un élément",
"ctrl": {
"click": "Sélectionner plusieurs éléments",
"f": "Ouvrir l'invité de recherche",
"s": "Télécharger l'élément actuel"
},
"del": "Supprimer les éléments sélectionnés",
"doubleClick": "Ouvrir un élément",
"esc": "Désélectionner et/ou fermer la boîte de dialogue",
"f1": "Ouvrir l'aide",
"f2": "Renommer le fichier",
"help": "Aide"
},
"login": {
"password": "Mot de passe",
"passwordConfirm": "Password Confirmation",
"submit": "Se connecter",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"username": "Utilisateur",
"wrongCredentials": "Identifiants incorrects !"
},
"prompts": {
"copy": "Copier",
"copyMessage": "Choisissez l'emplacement où copier la sélection :",
"currentlyNavigating": "Dossier courant :",
"deleteMessageMultiple": "Etes-vous sûr de vouloir supprimer ces {count} élément(s) ?",
"deleteMessageSingle": "Etes-vous sûr de vouloir supprimer cet élément ?",
"deleteTitle": "Supprimer",
"displayName": "Nom :",
"download": "Télécharger",
"downloadMessage": "Choisissez le format de téléchargement :",
"error": "Quelque chose s'est mal passé",
"fileInfo": "Informations",
"filesSelected": "{count} éléments sélectionnés",
"lastModified": "Dernière modification",
"move": "Déplacer",
"moveMessage": "Choisissez l'emplacement où déplacer la sélection :",
"newDir": "Nouveau dossier",
"newDirMessage": "Nom du nouveau dossier :",
"newFile": "Nouveau fichier",
"newFileMessage": "Nom du nouveau fichier :",
"numberDirs": "Nombre de dossiers",
"numberFiles": "Nombre de fichiers",
"replace": "Remplacer",
"replaceMessage": "Un des fichiers que vous êtes en train d'importer a le même nom qu'un autre déjà présent. Voulez-vous remplacer le fichier actuel par le nouveau ?\n",
"rename": "Renommer",
"renameMessage": "Nouveau nom pour",
"show": "Montrer",
"size": "Taille",
"schedule": "Fixer la date",
"scheduleMessage": "Choisissez une date pour planifier la publication de ce post",
"newArchetype": "Créer un nouveau post basé sur un archétype. Votre fichier sera créé dans le dossier de contenu."
},
"settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin",
"administrator": "Administrateur",
"allowCommands": "Exécuter des commandes",
"allowEdit": "Editer, renommer et supprimer des fichiers ou des dossiers",
"allowNew": "Créer de nouveaux fichiers et dossiers",
"allowPublish": "Publier de nouveaux posts et pages",
"avoidChanges": "(Laisser vide pour conserver l'actuel)",
"changePassword": "Modifier le mot de passe",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Commandes mises à jour !",
"customStylesheet": "Feuille de style personnalisée",
"examples": "Exemples",
"globalSettings": "Paramètres généraux",
"language": "Langue",
"lockPassword": "Prevent the user from changing the password",
"newPassword": "Votre nouveau mot de passe",
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
"newUser": "Nouvel Utilisateur",
"password": "Mot de passe",
"passwordUpdated": "Mot de passe mis à jour !",
"permissions": "Permissions",
"permissionsHelp": "Vous pouvez définir l'utilisateur comme étant un administrateur ou encore choisir les permissions individuellement. Si vous sélectionnez \"Administrateur\", toutes les autres options seront automatiquement activées. La gestion des utilisateurs est un privilège que seul l'administrateur possède.\n",
"profileSettings": "Paramètres du profil",
"ruleExample1": "Bloque l'accès à tous les fichiers commençant par un point (comme par exemple .git, .gitignore) dans tous les dossiers",
"ruleExample2": "Bloque l'accès au fichier nommé \"Caddyfile\" à la racine du dossier utilisateur",
"rules": "Règles",
"rulesHelp": "Vous pouvez définir ici un ensemble de règles pour cet utilisateur. Les fichiers bloqués ne seront pas affichés et ne seront pas accessibles par l'utilisateur. Les expressions régulières sont supportées et les chemins d'accès sont relatifs par rapport au dossier de l'utilisateur.\n",
"scope": "Portée du dossier utilisateur",
"settingsUpdated": "Les paramètres ont été mis à jour !",
"user": "Utilisateur",
"userCommands": "Commandes",
"userCommandsHelp": "Une liste séparée par des espaces des commandes permises pour l'utilisateur. Exemple :",
"userCreated": "Utilisateur créé !",
"userDeleted": "Utilisateur supprimé !",
"userManagement": "Gestion des utilisateurs",
"username": "Nom d'utilisateur",
"users": "Utilisateurs",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "Utilisateur mis à jour !",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new users.",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
},
"sidebar": {
"help": "Aide",
"login": "Login",
"signup": "Signup",
"logout": "Se déconnecter",
"myFiles": "Mes fichiers",
"newFile": "Nouveau fichier",
"newFolder": "Nouveau dossier",
"settings": "Paramètres",
"siteSettings": "Paramètres du site",
"hugoNew": "Nouveau Hugo",
"preview": "Prévisualiser"
},
"search": {
"images": "Images",
"music": "Musique",
"pdf": "PDF",
"types": "Types",
"video": "Video",
"search": "Recherche en cours...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Unité de temps",
"seconds": "Secondes",
"minutes": "Minutes",
"hours": "Heures",
"days": "Jours"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
}
}
================================================
FILE: src/i18n/index.js
================================================
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import ar from './ar.json'
import de from './de.json'
import en from './en.json'
import es from './es.json'
import fr from './fr.json'
import it from './it.json'
import ja from './ja.json'
import pl from './pl.json'
import ko from './ko.json'
import pt from './pt.json'
import ptBR from './pt-br.json'
import ru from './ru.json'
import zhCN from './zh-cn.json'
import zhTW from './zh-tw.json'
Vue.use(VueI18n)
export function detectLocale () {
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
switch (true) {
case /^en.*/i.test(locale):
locale = 'en'
break
case /^it.*/i.test(locale):
locale = 'it'
break
case /^fr.*/i.test(locale):
locale = 'fr'
break
case /^pt.*/i.test(locale):
locale = 'pt'
break
case /^pt-BR.*/i.test(locale):
locale = 'pt-br'
break
case /^ja.*/i.test(locale):
locale = 'ja'
break
case /^zh-CN/i.test(locale):
locale = 'zh-cn'
break
case /^zh-TW/i.test(locale):
locale = 'zh-tw'
break
case /^zh.*/i.test(locale):
locale = 'zh-cn'
break
case /^es.*/i.test(locale):
locale = 'es'
break
case /^de.*/i.test(locale):
locale = 'de'
break
case /^ru.*/i.test(locale):
locale = 'ru'
break
case /^pl.*/i.test(locale):
locale = 'pl'
break
case /^ko.*/i.test(locale):
locale = 'ko'
break
default:
locale = 'en'
}
return locale
}
const i18n = new VueI18n({
locale: detectLocale(),
fallbackLocale: 'en',
messages: {
'ar': ar,
'de': de,
'en': en,
'es': es,
'fr': fr,
'it': it,
'ja': ja,
'ko': ko,
'pl': pl,
'pt-br': ptBR,
'pt': pt,
'ru': ru,
'zh-cn': zhCN,
'zh-tw': zhTW
}
})
export default i18n
================================================
FILE: src/i18n/is.json
================================================
{
"permanent": "Varanlegt",
"buttons": {
"shell": "Sýna skipanaglugga",
"cancel": "Hætta við",
"close": "Loka",
"copy": "Afrita",
"copyFile": "Afrita skjal",
"copyToClipboard": "Afrita",
"create": "Búa til",
"delete": "Eyða",
"download": "Sækja",
"info": "Upplýsingar",
"more": "Meira",
"move": "Færa",
"moveFile": "Færa skjal",
"new": "Nýtt",
"next": "Næsta",
"ok": "OK",
"replace": "Skipta út",
"previous": "Fyrri",
"rename": "Endurnefna",
"reportIssue": "Tilkynna vandamál",
"save": "Vista",
"search": "Leita",
"select": "Velja",
"share": "Deila",
"publish": "Gefa út",
"selectMultiple": "Velja mörg",
"schedule": "Áætlun",
"switchView": "Skipta um útlit",
"toggleSidebar": "Sýna hliðarstiku",
"update": "Vista",
"upload": "Hlaða upp",
"permalink": "Sækja fastan hlekk"
},
"success": {
"linkCopied": "Hlekkur afritaður!"
},
"errors": {
"forbidden": "Þú hefur ekki aðgang að þessari síðu.",
"internal": "Eitthvað fór úrskeiðis.",
"notFound": "Ekki er hægt að opna þessa síðu."
},
"files": {
"folders": "Möppur",
"files": "Skjöl",
"body": "Meginmál",
"clear": "Hreinsa",
"closePreview": "Loka forskoðun",
"home": "Heim",
"lastModified": "Seinast breytt",
"loading": "Hleð...",
"lonely": "Ekkert hér...",
"metadata": "Meta-gögn",
"multipleSelectionEnabled": "Hægt að velja mörg skjöl/möppur",
"name": "Nafn",
"size": "Stærð",
"sortByName": "Flokka eftir nafni",
"sortBySize": "Flokka eftir stærð",
"sortByLastModified": "Flokka eftir Seinast breytt"
},
"help": {
"click": "velja skjal eða möppu",
"ctrl": {
"click": "velja mörg skjöl eða möppur",
"f": "opnar leitarstiku",
"s": "vista skjal eða sækja möppuna sem þú ert í"
},
"del": "eyða völdum skjölum",
"doubleClick": "opna skjal eða möppu",
"esc": "hreinsa val og/eða loka glugganum",
"f1": "þessar upplýsingar",
"f2": "endurnefna skjal",
"help": "Hjálp"
},
"login": {
"password": "Lykilorð",
"passwordConfirm": "Staðfesting lykilorðs",
"submit": "Innskráning",
"createAnAccount": "Búa til nýjan aðgang",
"loginInstead": "Þú ert þegar með aðgang",
"passwordsDontMatch": "Lykilorð eru mismunandi",
"usernameTaken": "Þetta norendanafn er þegar í notkun",
"signup": "Nýskráning",
"username": "Notendanafn",
"wrongCredentials": "Rangar notendaupplýsingar"
},
"prompts": {
"copy": "Afrita",
"copyMessage": "Veldu staðsetningu til að afrita gögn: ",
"currentlyNavigating": "Núverandi staðsetning:",
"deleteMessageMultiple": "Ertu viss um að þú viljir eyða {count} skjölum?",
"deleteMessageSingle": "Ertu viss um að þú viljir eyða þessu skjali/möppu?",
"deleteTitle": "Eyða skjölum",
"displayName": "Nafn: ",
"download": "Sækja skjöl",
"downloadMessage": "Veldu skrárgerð sem þú vilt sækja.",
"error": "Eitthvað fór úrskeiðis",
"fileInfo": "Upplýsingar um gögn",
"filesSelected": "{count} skjöl valin.",
"lastModified": "Seinast breytt",
"move": "Færa",
"moveMessage": "Velja nýtt hús fyrir skjöl/möppur:",
"newDir": "Ný mappa",
"newDirMessage": "Hvað á mappan að heita?",
"newFile": "Nýtt skjal",
"newFileMessage": "Hvað á skjalið að heita?",
"numberDirs": "Fjöldi mappa",
"numberFiles": "Fjöldi skjala",
"replace": "Skipta út",
"replaceMessage": "Eitt af skjölunum sem þú ert að reyna að hlaða upp hefur sama nafn og annað skjal. Viltu skipta nýja skjalinu út fyrir það gamla?\n",
"rename": "Endurnefna",
"renameMessage": "Settu inn nýtt nafn fyrir",
"show": "Sýna",
"size": "Stærð",
"schedule": "Áætlun",
"scheduleMessage": "Veldu dagsetningu og tíma fyrir áætlaða útgáfu. ",
"newArchetype": "Búðu til nýja færslu sem byggir á frumgerð. Skjalið verður búið til í content möppu. "
},
"settings": {
"instanceName": "Nafn tilviks",
"brandingDirectoryPath": "Mappa fyrir branding-skjöl",
"documentation": "leiðbeiningar",
"branding": "Útlit",
"disableExternalLinks": "Sýna ytri-hlekki (fyrir utan leiðbeiningar)",
"brandingHelp": "Þú getur breytt því hvernig File Browser lítur út með því að breyta nafninu, setja inn nýtt lógó, búa til þína eigin stíla og tekið út GitHub-hlekki. \nTil að lesa meira um custom-branding, kíktu á {0}.",
"admin": "Stjórnandi",
"administrator": "Stjórnandi",
"allowCommands": "Senda skipanir",
"allowEdit": "Breyta, endurnefna og eyða skjölum eða möppum",
"allowNew": "Búa til ný skjöl og möppur",
"allowPublish": "Gefa út nýjar færslur og síður",
"avoidChanges": "(engar breytingar ef ekkert er skrifað)",
"changePassword": "Breyta lykilorði",
"commandRunner": "Skipanagluggi",
"commandRunnerHelp": "Hér geturðu sett inn skipanir sem eru keyrðar eftir því sem þú tilgreinir. Skrifaðu eina skipun í hverja línu. Umhverfisbreyturnar {0} og {1} verða aðgengilegar ({0} miðast við {1}). Til að lesa meira og sjá lista yfir þær skipanir sem eru í boði, vinsamlegast lestu {2}. ",
"commandsUpdated": "Skipanastillingar vistaðar!",
"customStylesheet": "Custom Stylesheet",
"examples": "Dæmi",
"globalSettings": "Global stillingar",
"language": "Tungumál",
"lockPassword": "Koma í veg fyrir að notandi breyti lykilorðinu",
"newPassword": "Nýja lykilorðið þitt",
"newPasswordConfirm": "Staðfestu nýja lykilorðið",
"newUser": "Nýr notandi",
"password": "Lykilorð",
"passwordUpdated": "Lykilorð vistað!",
"permissions": "Heimildir",
"permissionsHelp": "Þú getur stillt notenda sem stjórnanda eða valið einstaklingsbundnar heimildir. Ef þú velur \"Stjórnandi\", þá verða allir aðrir valmöguleikar valdir sjálfrafa. Aðgangstýring notenda er á hendi stjórnenda. \n",
"profileSettings": "Stilla prófíl",
"ruleExample1": "kemur í veg fyrir aðgang að dot-skjali (t.d. .git, .gitignore) í öllum möppum. \n",
"ruleExample2": "kemur í veg fyrir aðgang að Caddyfile-skjalinu í root-möppu í sýn notandans. ",
"rules": "Reglur",
"rulesHelp": "Hér getur þú skilgreint hvaða reglur gilda um notandann. Skjölin sem hann hefur ekki aðgang að eru óaðgengileg og hann sér þau ekki. Stuðst er við reglulegar segðir og slóðir sem miðast við sýn notandans. ",
"scope": "Sýn notandans",
"settingsUpdated": "Stillingar vistaðar!",
"user": "Notandi",
"userCommands": "Skipanir",
"userCommandsHelp": "Listi þar sem gildum er skipt upp með bili og inniheldur tiltækar skipanir fyrir þennan notanda. Til dæmis:\n",
"userCreated": "Notandi stofnaður!",
"userDeleted": "Notanda eytt!",
"userManagement": "Notendastýring",
"username": "Notendanafn",
"users": "Notendur",
"globalRules": "Þetta eru sjálfgegnar aðgangsreglur. Þær gilda um alla notendur. Þú getur tilgreint sérstakar reglur í stillingum fyrir hvern notenda til að ógilda þessar reglur. ",
"allowSignup": "Leyfa nýjum notendum að skrá sig",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Setja inn reglulega segð",
"insertPath": "Settu inn slóð",
"userUpdated": "Notandastillingar vistaðar!",
"userDefaults": "Sjálfgefnar notendastillingar",
"defaultUserDescription": "Þetta eru sjálfgefnar stillingar fyrir nýja notendur.",
"executeOnShell": "Keyra í skel",
"executeOnShellDescription": "Sjálfgefnar stillingar File Browser eru að keyra skipanir beint með því að sækja binaries. Ef þú villt keyra skipanir í skel (t.d. í Bash eða PowerShell), þá geturðu skilgreint það hér með nauðsynlegum arguments og flags. Ef þetta er stillt, þá verður skipuninni bætt fyrir aftan sem argument. Þetta gildir bæði um skipanir notenda og event hooks.",
"perm": {
"create": "Búa til sköl og möppur",
"delete": "Eyða skjölum og möppum",
"download": "Sækja",
"modify": "Breyta skjölum",
"execute": "Keyra skipanir",
"rename": "Endurnefna eða færa skjöl og möppur",
"share": "Deila skjölum"
}
},
"sidebar": {
"help": "Hjálp",
"login": "Innskráning",
"signup": "Nýskráning",
"logout": "Útskráning",
"myFiles": "Gögnin mín",
"newFile": "Nýtt skjal",
"newFolder": "Ný mappa",
"settings": "Stillingar",
"siteSettings": "Stillingar síðu",
"hugoNew": "Hugo New",
"preview": "Sýnishorn"
},
"search": {
"images": "Myndir",
"music": "Tónlist",
"pdf": "PDF",
"types": "Skrárgerðir",
"video": "Myndbönd",
"search": "Leita...",
"typeToSearch": "Skrifaðu til að leita...",
"pressToSearch": "Ýttu á Enter til að leita..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Tímastilling",
"seconds": "Sekúndur",
"minutes": "Mínútur",
"hours": "Klukkutímar",
"days": "Dagar"
},
"download": {
"downloadFile": "Sækja skjal",
"downloadFolder": "Sækja möppu"
}
}
================================================
FILE: src/i18n/it.json
================================================
{
"permanent": "Permanente",
"buttons": {
"shell": "Toggle shell",
"cancel": "Annulla",
"close": "Chiudi",
"copy": "Copia",
"copyFile": "Copia file",
"copyToClipboard": "Copia negli appunti",
"create": "Crea",
"delete": "Elimina",
"download": "Scarica",
"info": "Informazioni",
"more": "Altro",
"move": "Sposta",
"moveFile": "Sposta file",
"new": "Nuovo",
"next": "Successivo",
"ok": "OK",
"replace": "Sostituisci",
"previous": "Precedente",
"rename": "Rinomina",
"reportIssue": "Segnala un problema",
"save": "Salva",
"search": "Cerca",
"select": "Seleziona",
"share": "Condividi",
"publish": "Publica",
"selectMultiple": "Seleziona molteplici",
"schedule": "Programma",
"switchView": "Cambia vista",
"toggleSidebar": "Mostra/nascondi la barra laterale",
"update": "Aggiorna",
"upload": "Carica",
"permalink": "Ottieni link permanente"
},
"success": {
"linkCopied": "Link copiato!"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "Qualcosa è andato veramente male.",
"notFound": "Questo percorso non può essere raggiunto."
},
"files": {
"folders": "Cartelle",
"files": "Files",
"body": "Contenuto",
"clear": "Cancella",
"closePreview": "Chiudi anteprima",
"home": "Home",
"lastModified": "Ultima modifica",
"loading": "Caricamento...",
"lonely": "Ci si sente soli qui...",
"metadata": "Metadata",
"multipleSelectionEnabled": "Selezione multipla attivata",
"name": "Nome",
"size": "Grandezza",
"sortByName": "Ordina per nome",
"sortBySize": "Ordina per dimensione",
"sortByLastModified": "Ordina per ultima modifica"
},
"help": {
"click": "seleziona un file o una cartella",
"ctrl": {
"click": "seleziona più file o cartelle",
"f": "apre la barra di ricerca",
"s": "salva un file o scarica la cartella in cui ci si trova"
},
"del": "elimina gli elementi selezionati",
"doubleClick": "apre un file o una cartella",
"esc": "annulla la selezione e/o chiude la finestra aperta",
"f1": "questo pannello",
"f2": "rinomina un file",
"help": "Aiuto"
},
"login": {
"password": "Password",
"passwordConfirm": "Password Confirmation",
"submit": "Entra",
"createAnAccount": "Create an account",
"loginInstead": "Already have an account",
"passwordsDontMatch": "Passwords don't match",
"usernameTaken": "Username already taken",
"signup": "Signup",
"username": "Nome utente",
"wrongCredentials": "Credenziali errate"
},
"prompts": {
"copy": "Copia",
"copyMessage": "Seleziona la cartella in cui copiare i file:",
"currentlyNavigating": "Attualmente navigando su:",
"deleteMessageMultiple": "Sei sicuro di voler eliminare {count} file(s)?",
"deleteMessageSingle": "Sei sicuro di voler eliminare questo file/cartella?",
"deleteTitle": "Elimina",
"displayName": "Nome Mostrato:",
"download": "Scarica files",
"downloadMessage": "Seleziona il formato che vuoi scaricare.",
"error": "Qualcosa è andato per il verso storto",
"fileInfo": "Informazioni sul file",
"filesSelected": "{count} file selezionati.",
"lastModified": "Ultima modifica",
"move": "Sposta",
"moveMessage": "Seleziona la nuova posizione per i tuoi file e/o cartella/e:",
"newDir": "Nuova cartella",
"newDirMessage": "Scrivi il nome della nuova cartella.",
"newFile": "Nuovo file",
"newFileMessage": "Scrivi il nome del nuovo file.",
"numberDirs": "Numero di cartelle",
"numberFiles": "Numero di files",
"replace": "Sostituisci",
"replaceMessage": "Uno dei file che stai cercando di caricare sta generando un conflitto per via del suo nome. Desideri sostituire il file già esistente?\n",
"rename": "Rinomina",
"renameMessage": "Inserisci un nuovo nome per",
"show": "Mostra",
"size": "Grandezza",
"schedule": "Pianifica",
"scheduleMessage": "Seleziona data e ora per programmare la pubbilicazione di questo post",
"newArchetype": "Crea un nuovo post basato su un modello. Il tuo file verrà creato nella cartella."
},
"settings": {
"instanceName": "Instance name",
"brandingDirectoryPath": "Branding directory path",
"documentation": "documentation",
"branding": "Branding",
"disableExternalLinks": "Disable external links (except documentation)",
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
"admin": "Admin",
"administrator": "Amministratore",
"allowCommands": "Esegui comandi",
"allowEdit": "Modifica, rinomina ed elimina file o cartelle",
"allowNew": "Crea nuovi files o cartelle",
"allowPublish": "Pubblica nuovi post e pagine",
"avoidChanges": "(lascia vuoto per evitare cambiamenti)",
"changePassword": "Modifica password",
"commandRunner": "Command runner",
"commandRunnerHelp": "Here you can set commands that are executed in the named events. You must write one per line. The environment variables {0} and {1} will be available, being {0} relative to {1}. For more information about this feature and the available environment variables, please read the {2}.",
"commandsUpdated": "Comandi aggiornati!",
"customStylesheet": "Folgio di stile personalizzato",
"examples": "Esempi",
"globalSettings": "Impostazioni Globali",
"language": "Lingua",
"lockPassword": "Impedisci all'utente di modificare la password",
"newPassword": "La tua nuova password",
"newPasswordConfirm": "Conferma la password",
"newUser": "Nuovo utente",
"password": "Password",
"passwordUpdated": "Password aggiornata!",
"permissions": "Permessi",
"permissionsHelp": "È possibile impostare l'utente come amministratore o scegliere i permessi singolarmente. Se si seleziona \"Amministratore\", tutte le altre opzioni saranno automaticamente assegnate. La gestione degli utenti rimane un privilegio di un amministratore.\n",
"profileSettings": "Impostazioni del profilo",
"ruleExample1": "Impedisci l'accesso a qualsiasi file avente come prefisso un punto\n (ad esempio .git, .gitignore) presente in ogni cartella.\n",
"ruleExample2": "blocca l'accesso al file denominato Caddyfile nella radice del campo di applicazione.",
"rules": "Regole",
"rulesHelp": "Qui è possibile definire una serie di regole e permessi per questo specifico utente. I file bloccati non appariranno negli elenchi e non saranno accessibili dagli utenti. all'utente. Sia regex che i percorsi relativi all'ambito di applicazione degli utenti sono supportati.\n",
"scope": "Scopo",
"settingsUpdated": "Impostazioni aggiornate!",
"user": "Utente",
"userCommands": "Comandi",
"userCommandsHelp": "Una lista separata dal spazi con i comandi disponibili per questo utente. Example:\n",
"userCreated": "Utente creato!",
"userDeleted": "Utente eliminato!",
"userManagement": "Gestione degli utenti",
"username": "Nome utente",
"users": "Utenti",
"globalRules": "This is a global set of allow and disallow rules. They apply to every user. You can define specific rules on each user's settings to override this ones.",
"allowSignup": "Allow users to signup",
"createUserDir": "Auto create user home dir while adding new user",
"insertRegex": "Insert regex expression",
"insertPath": "Insert the path",
"userUpdated": "Utente aggiornato!",
"userDefaults": "User default settings",
"defaultUserDescription": "This are the default settings for new users.",
"executeOnShell": "Execute on shell",
"executeOnShellDescription": "By default, File Browser executes the commands by calling their binaries directly. If you want to run them on a shell instead (such as Bash or PowerShell), you can define it here with the required arguments and flags. If set, the command you execute will be appended as an argument. This apply to both user commands and event hooks.",
"perm": {
"create": "Create files and directories",
"delete": "Delete files and directories",
"download": "Download",
"modify": "Edit files",
"execute": "Execute commands",
"rename": "Rename or move files and directories",
"share": "Share files"
}
},
"sidebar": {
"help": "Aiuto",
"login": "Login",
"signup": "Signup",
"logout": "Esci",
"myFiles": "I miei file",
"newFile": "Nuovo file",
"newFolder": "Nuova cartella",
"settings": "Impostazioni",
"siteSettings": "Impostaizoni del sito",
"hugoNew": "Hugo New",
"preview": "Anteprima"
},
"search": {
"images": "Immagini",
"music": "Musica",
"pdf": "PDF",
"types": "Tipi",
"video": "Video",
"search": "Cerca...",
"typeToSearch": "Type to search...",
"pressToSearch": "Press enter to search..."
},
"languages": {
"ar": "العربية",
"en": "English",
"it": "Italiano",
"fr": "Français",
"pt": "Português",
"ptBR": "Português (Brasil)",
"ja": "日本語",
"zhCN": "中文 (简体)",
"zhTW": "中文 (繁體)",
"es": "Español",
"de": "Deutsch",
"ru": "Русский",
"pl": "Polski",
"ko": "한국어"
},
"time": {
"unit": "Unità di tempo",
"seconds": "Secondi",
"minutes": "Minuti",
"hours": "Ore",
"days": "Giorni"
},
"download": {
"downloadFile": "Download File",
"downloadFolder": "Download Folder"
}
}
================================================
FILE: src/i18n/ja.json
================================================
{
"permanent": "永久",
"buttons": {
"shell": "Toggle shell",
"cancel": "キャンセル",
"close": "閉じる",
"copy": "コピー",
"copyFile": "ファイルをコピー",
"copyToClipboard": "クリップボードにコピー",
"create": "作成",
"delete": "削除",
"download": "ダウンロード",
"info": "情報",
"more": "More",
"move": "移動",
"moveFile": "ファイルを移動",
"new": "新規",
"next": "次",
"ok": "OK",
"replace": "置き換える",
"previous": "前",
"rename": "名前を変更",
"reportIssue": "問題を報告",
"save": "保存",
"search": "検索",
"select": "選択",
"share": "シェア",
"publish": "発表",
"selectMultiple": "複数選択",
"schedule": "スケジュール",
"switchView": "表示を切り替わる",
"toggleSidebar": "サイドバーを表示する",
"update": "更新",
"upload": "アップロード",
"permalink": "固定リンク"
},
"success": {
"linkCopied": "リンクがコピーされました!"
},
"errors": {
"forbidden": "You don't have permissions to access this.",
"internal": "内部エラーが発生しました。",
"notFound": "リソースが見つからなりませんでした。"
},
"files"
gitextract_pd9iypza/ ├── .circleci/ │ └── config.yml ├── .gitignore ├── .tx/ │ └── config ├── README.md ├── babel.config.js ├── package.json ├── public/ │ ├── img/ │ │ └── icons/ │ │ └── browserconfig.xml │ ├── index.html │ └── manifest.json ├── src/ │ ├── App.vue │ ├── api/ │ │ ├── commands.js │ │ ├── files.js │ │ ├── index.js │ │ ├── search.js │ │ ├── settings.js │ │ ├── share.js │ │ ├── users.js │ │ └── utils.js │ ├── components/ │ │ ├── Header.vue │ │ ├── Search.vue │ │ ├── Shell.vue │ │ ├── Sidebar.vue │ │ ├── buttons/ │ │ │ ├── Copy.vue │ │ │ ├── Delete.vue │ │ │ ├── Download.vue │ │ │ ├── Info.vue │ │ │ ├── Move.vue │ │ │ ├── Rename.vue │ │ │ ├── Share.vue │ │ │ ├── Shell.vue │ │ │ ├── SwitchView.vue │ │ │ └── Upload.vue │ │ ├── files/ │ │ │ ├── Editor.vue │ │ │ ├── Listing.vue │ │ │ ├── ListingItem.vue │ │ │ └── Preview.vue │ │ ├── prompts/ │ │ │ ├── Copy.vue │ │ │ ├── Delete.vue │ │ │ ├── Download.vue │ │ │ ├── FileList.vue │ │ │ ├── Help.vue │ │ │ ├── Info.vue │ │ │ ├── Move.vue │ │ │ ├── NewDir.vue │ │ │ ├── NewFile.vue │ │ │ ├── Prompts.vue │ │ │ ├── Rename.vue │ │ │ ├── Replace.vue │ │ │ └── Share.vue │ │ └── settings/ │ │ ├── Commands.vue │ │ ├── Languages.vue │ │ ├── Permissions.vue │ │ ├── Rules.vue │ │ └── UserForm.vue │ ├── css/ │ │ ├── _buttons.css │ │ ├── _inputs.css │ │ ├── _share.css │ │ ├── _shell.css │ │ ├── _variables.css │ │ ├── base.css │ │ ├── dashboard.css │ │ ├── fonts.css │ │ ├── header.css │ │ ├── listing.css │ │ ├── login.css │ │ ├── mobile.css │ │ └── styles.css │ ├── i18n/ │ │ ├── ar.json │ │ ├── de.json │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ ├── index.js │ │ ├── is.json │ │ ├── it.json │ │ ├── ja.json │ │ ├── ko.json │ │ ├── pl.json │ │ ├── pt-br.json │ │ ├── pt.json │ │ ├── ro.json │ │ ├── ru.json │ │ ├── zh-cn.json │ │ └── zh-tw.json │ ├── main.js │ ├── router/ │ │ └── index.js │ ├── store/ │ │ ├── getters.js │ │ ├── index.js │ │ └── mutations.js │ ├── utils/ │ │ ├── auth.js │ │ ├── buttons.js │ │ ├── constants.js │ │ ├── cookie.js │ │ ├── css.js │ │ ├── url.js │ │ └── vue.js │ └── views/ │ ├── Files.vue │ ├── Layout.vue │ ├── Login.vue │ ├── Settings.vue │ ├── Share.vue │ ├── errors/ │ │ ├── 403.vue │ │ ├── 404.vue │ │ └── 500.vue │ └── settings/ │ ├── Global.vue │ ├── Profile.vue │ ├── User.vue │ └── Users.vue └── vue.config.js
SYMBOL INDEX (39 symbols across 13 files)
FILE: src/api/commands.js
function command (line 8) | function command(url, command, onmessage, onclose) {
FILE: src/api/files.js
function fetch (line 5) | async function fetch (url) {
function resourceAction (line 34) | async function resourceAction (url, method, content) {
function remove (line 52) | async function remove (url) {
function put (line 56) | async function put (url, content = '') {
function download (line 60) | function download (format, ...files) {
function post (line 85) | async function post (url, content = '', overwrite = false, onupload) {
function moveCopy (line 119) | function moveCopy (items, copy = false) {
function move (line 132) | function move (items) {
function copy (line 136) | function copy (items) {
function checksum (line 140) | async function checksum (url, algo) {
FILE: src/api/search.js
function search (line 3) | async function search (url, query) {
FILE: src/api/settings.js
function get (line 3) | function get () {
function update (line 7) | async function update (settings) {
FILE: src/api/share.js
function getHash (line 3) | async function getHash(hash) {
function get (line 7) | async function get(url) {
function remove (line 12) | async function remove(hash) {
function create (line 22) | async function create(url, expires = '', unit = 'hours') {
FILE: src/api/users.js
function getAll (line 3) | async function getAll () {
function get (line 7) | async function get (id) {
function create (line 11) | async function create (user) {
function update (line 29) | async function update (user, which = ['all']) {
function remove (line 44) | async function remove (id) {
FILE: src/api/utils.js
function fetchURL (line 5) | async function fetchURL (url, opts) {
function fetchJSON (line 26) | async function fetchJSON (url, opts) {
function removePrefix (line 36) | function removePrefix (url) {
FILE: src/i18n/index.js
function detectLocale (line 21) | function detectLocale () {
FILE: src/main.js
function start (line 12) | async function start () {
FILE: src/utils/auth.js
function parseToken (line 6) | function parseToken (token) {
function validateLogin (line 24) | async function validateLogin () {
function login (line 34) | async function login (username, password, recaptcha) {
function renew (line 54) | async function renew (jwt) {
function signup (line 71) | async function signup (username, password) {
function logout (line 87) | function logout () {
FILE: src/utils/buttons.js
function loading (line 1) | function loading (button) {
function done (line 19) | function done (button) {
function success (line 36) | function success (button) {
FILE: src/utils/css.js
function getRule (line 1) | function getRule (rules) {
FILE: src/utils/url.js
function removeLastDir (line 1) | function removeLastDir (url) {
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (353K chars).
[
{
"path": ".circleci/config.yml",
"chars": 230,
"preview": "version: 2\njobs:\n build:\n docker:\n - image: circleci/node\n steps:\n - checkout\n - run: npm install\n"
},
{
"path": ".gitignore",
"chars": 214,
"preview": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn"
},
{
"path": ".tx/config",
"chars": 256,
"preview": "[main]\nhost = https://www.transifex.com\nlang_map = pt_BR: pt-br, zh_CN: zh-cn, zh_HK: zh-hk, zh_TW: zh-tw\n\n[file-browser"
},
{
"path": "README.md",
"chars": 1000,
"preview": "# File Browser Front-end\n\n[ {\n url = removePre"
},
{
"path": "src/api/settings.js",
"chars": 331,
"preview": "import { fetchURL, fetchJSON } from './utils'\n\nexport function get () {\n return fetchJSON(`/api/settings`, {})\n}\n\nexpor"
},
{
"path": "src/api/share.js",
"chars": 681,
"preview": "import { fetchURL, fetchJSON, removePrefix } from './utils'\n\nexport async function getHash(hash) {\n return fetchJSON(`/"
},
{
"path": "src/api/users.js",
"chars": 989,
"preview": "import { fetchURL, fetchJSON } from './utils'\n\nexport async function getAll () {\n return fetchJSON(`/api/users`, {})\n}\n"
},
{
"path": "src/api/utils.js",
"chars": 860,
"preview": "import store from '@/store'\nimport { renew } from '@/utils/auth'\nimport { baseURL } from '@/utils/constants'\n\nexport asy"
},
{
"path": "src/components/Header.vue",
"chars": 5947,
"preview": "<template>\n <header>\n <div>\n <button @click=\"openSidebar\" :aria-label=\"$t('buttons.toggleSidebar')\" :title=\"$t("
},
{
"path": "src/components/Search.vue",
"chars": 4801,
"preview": "<template>\n <div id=\"search\" @click=\"open\" v-bind:class=\"{ active , ongoing }\">\n <div id=\"input\">\n <button\n "
},
{
"path": "src/components/Shell.vue",
"chars": 2831,
"preview": "<template>\n <div @click=\"focus\" class=\"shell\" ref=\"scrollable\" :class=\"{ ['shell--hidden']: !showShell}\">\n <div v-fo"
},
{
"path": "src/components/Sidebar.vue",
"chars": 2934,
"preview": "<template>\n <nav :class=\"{active}\">\n <template v-if=\"isLogged\">\n <router-link class=\"action\" to=\"/files/\" :aria"
},
{
"path": "src/components/buttons/Copy.vue",
"chars": 401,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.copy')\" :title=\"$t('buttons.copy')\" class=\"action\" id=\"copy-"
},
{
"path": "src/components/buttons/Delete.vue",
"chars": 403,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.delete')\" :title=\"$t('buttons.delete')\" class=\"action\" id=\"d"
},
{
"path": "src/components/buttons/Download.vue",
"chars": 963,
"preview": "<template>\n <button @click=\"download\" :aria-label=\"$t('buttons.download')\" :title=\"$t('buttons.download')\" id=\"download"
},
{
"path": "src/components/buttons/Info.vue",
"chars": 372,
"preview": "<template>\n <button :title=\"$t('buttons.info')\" :aria-label=\"$t('buttons.info')\" class=\"action\" @click=\"show\">\n <i c"
},
{
"path": "src/components/buttons/Move.vue",
"chars": 396,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.move')\" :title=\"$t('buttons.move')\" class=\"action\" id=\"move-"
},
{
"path": "src/components/buttons/Rename.vue",
"chars": 406,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.rename')\" :title=\"$t('buttons.rename')\" class=\"action\" id=\"r"
},
{
"path": "src/components/buttons/Share.vue",
"chars": 368,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.share')\" :title=\"$t('buttons.share')\" class=\"action\">\n <i"
},
{
"path": "src/components/buttons/Shell.vue",
"chars": 370,
"preview": "<template>\n <button @click=\"show\" :aria-label=\"$t('buttons.shell')\" :title=\"$t('buttons.shell')\" class=\"action\">\n <i"
},
{
"path": "src/components/buttons/SwitchView.vue",
"chars": 979,
"preview": "<template>\n <button @click=\"change\" :aria-label=\"$t('buttons.switchView')\" :title=\"$t('buttons.switchView')\" class=\"act"
},
{
"path": "src/components/buttons/Upload.vue",
"chars": 418,
"preview": "<template>\n <button @click=\"upload\" :aria-label=\"$t('buttons.upload')\" :title=\"$t('buttons.upload')\" class=\"action\" id="
},
{
"path": "src/components/files/Editor.vue",
"chars": 1790,
"preview": "<template>\n <form id=\"editor\"></form>\n</template>\n\n<script>\nimport { mapState } from 'vuex'\nimport { files as api } fro"
},
{
"path": "src/components/files/Listing.vue",
"chars": 11550,
"preview": "<template>\n <div v-if=\"(req.numDirs + req.numFiles) == 0\">\n <h2 class=\"message\">\n <i class=\"material-icons\">sen"
},
{
"path": "src/components/files/ListingItem.vue",
"chars": 3855,
"preview": "<template>\n <div class=\"item\"\n role=\"button\"\n tabindex=\"0\"\n draggable=\"true\"\n @dragstart=\"dragStart\"\n @dragover=\"d"
},
{
"path": "src/components/files/Preview.vue",
"chars": 4523,
"preview": "<template>\n <div id=\"previewer\">\n <div class=\"bar\">\n <button @click=\"back\" class=\"action\" :title=\"$t('files.clo"
},
{
"path": "src/components/prompts/Copy.vue",
"chars": 1729,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.copy') }}</h2>\n </div>"
},
{
"path": "src/components/prompts/Delete.vue",
"chars": 1917,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-content\">\n <p v-if=\"req.kind !== 'listing'\">{{ $t('pr"
},
{
"path": "src/components/prompts/Download.vue",
"chars": 1501,
"preview": "<template>\n <div class=\"card floating\" id=\"download\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.download') "
},
{
"path": "src/components/prompts/FileList.vue",
"chars": 3504,
"preview": "<template>\n <div>\n <ul class=\"file-list\">\n <li @click=\"select\"\n @touchstart=\"touchstart\"\n @dblcli"
},
{
"path": "src/components/prompts/Help.vue",
"chars": 1118,
"preview": "<template>\n <div class=\"card floating help\">\n <div class=\"card-title\">\n <h2>{{ $t('help.help') }}</h2>\n </di"
},
{
"path": "src/components/prompts/Info.vue",
"chars": 3148,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.fileInfo') }}</h2>\n </"
},
{
"path": "src/components/prompts/Move.vue",
"chars": 1663,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.move') }}</h2>\n </div>"
},
{
"path": "src/components/prompts/NewDir.vue",
"chars": 1671,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.newDir') }}</h2>\n </di"
},
{
"path": "src/components/prompts/NewFile.vue",
"chars": 1668,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.newFile') }}</h2>\n </d"
},
{
"path": "src/components/prompts/Prompts.vue",
"chars": 2235,
"preview": "<template>\n <div>\n <help v-if=\"showHelp\" ></help>\n <download v-else-if=\"showDownload\"></download>\n <new-file v"
},
{
"path": "src/components/prompts/Rename.vue",
"chars": 2207,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.rename') }}</h2>\n </di"
},
{
"path": "src/components/prompts/Replace.vue",
"chars": 828,
"preview": "<template>\n <div class=\"card floating\">\n <div class=\"card-title\">\n <h2>{{ $t('prompts.replace') }}</h2>\n </d"
},
{
"path": "src/components/prompts/Share.vue",
"chars": 4628,
"preview": "<template>\n <div class=\"card floating\" id=\"share\">\n <div class=\"card-title\">\n <h2>{{ $t('buttons.share') }}</h2"
},
{
"path": "src/components/settings/Commands.vue",
"chars": 506,
"preview": "<template>\n <div>\n <h3>{{ $t('settings.userCommands') }}</h3>\n <p class=\"small\">{{ $t('settings.userCommandsHelp'"
},
{
"path": "src/components/settings/Languages.vue",
"chars": 1077,
"preview": "<template>\n <select v-on:change=\"change\" :value=\"locale\">\n <option value=\"ar\">{{ $t('languages.ar') }}</option>\n "
},
{
"path": "src/components/settings/Permissions.vue",
"chars": 1348,
"preview": "<template>\n <div>\n <h3>{{ $t('settings.permissions') }}</h3>\n <p class=\"small\">{{ $t('settings.permissionsHelp') "
},
{
"path": "src/components/settings/Rules.vue",
"chars": 1349,
"preview": "<template>\n <form class=\"rules small\">\n <div v-for=\"(rule, index) in rules\" :key=\"index\">\n <input type=\"checkbo"
},
{
"path": "src/components/settings/UserForm.vue",
"chars": 1801,
"preview": "<template>\n <div>\n <p v-if=\"!isDefault\">\n <label for=\"username\">{{ $t('settings.username') }}</label>\n <in"
},
{
"path": "src/css/_buttons.css",
"chars": 848,
"preview": ".button {\n outline: 0;\n border: 0;\n padding: .5em 1em;\n border-radius: .1em;\n cursor: pointer;\n background: var(--"
},
{
"path": "src/css/_inputs.css",
"chars": 506,
"preview": ".input {\n border-radius: .1em;\n padding: .5em 1em;\n background: white;\n border: 1px solid rgba(0, 0, 0, 0.1);\n tran"
},
{
"path": "src/css/_share.css",
"chars": 544,
"preview": ".share__box {\n text-align: center;\n box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 3px, rgba(0, 0, 0, 0.12) 0px 1px 2px;\n ba"
},
{
"path": "src/css/_shell.css",
"chars": 798,
"preview": ".shell {\n position: fixed;\n bottom: 0;\n left: 0;\n height: 25em;\n max-height: calc(100% - 4em);\n background: white;"
},
{
"path": "src/css/_variables.css",
"chars": 118,
"preview": ":root {\n --blue: #2196f3;\n --dark-blue: #1E88E5;\n --red: #F44336;\n --dark-red: #D32F2F;\n --moon-grey: #f2f2f2;\n}\n"
},
{
"path": "src/css/base.css",
"chars": 1555,
"preview": "body {\n font-family: 'Roboto', sans-serif;\n padding-top: 4em;\n background-color: #fafafa;\n color: #333333;\n}\n\n* {\n "
},
{
"path": "src/css/dashboard.css",
"chars": 5322,
"preview": ".dashboard {\n max-width: 600px;\n margin: 1em 0;\n}\n\na {\n color: inherit\n}\n\n.dashboard p label {\n margin-bottom: .2em;"
},
{
"path": "src/css/fonts.css",
"chars": 3850,
"preview": "@font-face {\n font-family: 'Roboto';\n font-style: normal;\n font-weight: 400;\n src: local('Roboto'), local('Roboto-Re"
},
{
"path": "src/css/header.css",
"chars": 3785,
"preview": "header {\n z-index: 1000;\n background-color: #fff;\n border-bottom: 1px solid rgba(0, 0, 0, 0.075);\n box-shadow: 0 0 5"
},
{
"path": "src/css/listing.css",
"chars": 3867,
"preview": "#listing h2 {\n margin: 0 0 0 0.5em;\n font-size: .9em;\n color: rgba(0, 0, 0, 0.38);\n font-weight: 500;\n}\n\n#listing .i"
},
{
"path": "src/css/login.css",
"chars": 857,
"preview": "#login {\n background: #fff;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n#login img {\n wi"
},
{
"path": "src/css/mobile.css",
"chars": 2221,
"preview": "@media (max-width: 1024px) {\n nav {\n width: 10em\n }\n}\n\n@media (max-width: 1024px) {\n main {\n width: calc(100% -"
},
{
"path": "src/css/styles.css",
"chars": 4211,
"preview": "@import \"~normalize.css/normalize.css\";\n@import \"~noty/lib/noty.css\";\n@import \"~noty/lib/themes/mint.css\";\n@import \"./_v"
},
{
"path": "src/i18n/ar.json",
"chars": 8894,
"preview": "{\n \"permanent\": \"دائم\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"إلغاء\",\n \"close\": \"إغلاق\",\n \"c"
},
{
"path": "src/i18n/de.json",
"chars": 10130,
"preview": "{\n \"permanent\": \"Permanent\",\n \"buttons\": {\n \"shell\": \"Kommandozeile ein/ausschalten\",\n \"cancel\": \"Abbrechen\",\n "
},
{
"path": "src/i18n/en.json",
"chars": 9212,
"preview": "{\n \"permanent\": \"Permanent\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Cancel\",\n \"close\": \"Close\",\n"
},
{
"path": "src/i18n/es.json",
"chars": 9766,
"preview": "{\n \"permanent\": \"Permanente\",\n \"buttons\": {\n \"shell\": \"Presiona Enter para buscar...\",\n \"cancel\": \"Cancelar\",\n "
},
{
"path": "src/i18n/fr.json",
"chars": 9996,
"preview": "{\n \"permanent\": \"Permanent\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Annuler\",\n \"close\": \"Fermer\""
},
{
"path": "src/i18n/index.js",
"chars": 1896,
"preview": "import Vue from 'vue'\nimport VueI18n from 'vue-i18n'\n\nimport ar from './ar.json'\nimport de from './de.json'\nimport en fr"
},
{
"path": "src/i18n/is.json",
"chars": 9301,
"preview": "{\n \"permanent\": \"Varanlegt\",\n \"buttons\": {\n \"shell\": \"Sýna skipanaglugga\",\n \"cancel\": \"Hætta við\",\n \"close\": "
},
{
"path": "src/i18n/it.json",
"chars": 9713,
"preview": "{\n \"permanent\": \"Permanente\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Annulla\",\n \"close\": \"Chiudi"
},
{
"path": "src/i18n/ja.json",
"chars": 7882,
"preview": "{\n \"permanent\": \"永久\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"キャンセル\",\n \"close\": \"閉じる\",\n \"copy\""
},
{
"path": "src/i18n/ko.json",
"chars": 6635,
"preview": "{\n \"permanent\": \"영구\",\n \"buttons\": {\n \"shell\": \"쉘 전환\",\n \"cancel\": \"취소\",\n \"close\": \"닫기\",\n \"copy\": \"복사\",\n "
},
{
"path": "src/i18n/pl.json",
"chars": 9463,
"preview": "{\n \"permanent\": \"Permanent\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Anuluj\",\n \"close\": \"Zamknij\""
},
{
"path": "src/i18n/pt-br.json",
"chars": 9444,
"preview": "{\n \"permanent\": \"Permanente\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Cancelar\",\n \"close\": \"Fecha"
},
{
"path": "src/i18n/pt.json",
"chars": 9891,
"preview": "{\n \"permanent\": \"Permanente\",\n \"buttons\": {\n \"shell\": \"Alternar shell\",\n \"cancel\": \"Cancelar\",\n \"close\": \"Fec"
},
{
"path": "src/i18n/ro.json",
"chars": 9576,
"preview": "{\n \"permanent\": \"Permanent\",\n \"buttons\": {\n \"shell\": \"Comută linia de comandă\",\n \"cancel\": \"Anulează\",\n \"clos"
},
{
"path": "src/i18n/ru.json",
"chars": 9188,
"preview": "{\n \"permanent\": \"Постоянный\",\n \"buttons\": {\n \"shell\": \"Toggle shell\",\n \"cancel\": \"Отмена\",\n \"close\": \"Закрыть"
},
{
"path": "src/i18n/zh-cn.json",
"chars": 6621,
"preview": "{\n \"permanent\": \"永久\",\n \"buttons\": {\n \"shell\": \"激活 shell\",\n \"cancel\": \"取消\",\n \"close\": \"关闭\",\n \"copy\": \"复制\",\n"
},
{
"path": "src/i18n/zh-tw.json",
"chars": 7104,
"preview": "{\n \"permanent\": \"永久\",\n \"buttons\": {\n \"shell\": \"切換 shell\",\n \"cancel\": \"取消\",\n \"close\": \"關閉\",\n \"copy\": \"複製\",\n"
},
{
"path": "src/main.js",
"chars": 803,
"preview": "import { sync } from 'vuex-router-sync'\nimport store from '@/store'\nimport router from '@/router'\nimport i18n from '@/i1"
},
{
"path": "src/router/index.js",
"chars": 3392,
"preview": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport Login from '@/views/Login'\nimport Layout from '@/views/Layo"
},
{
"path": "src/store/getters.js",
"chars": 389,
"preview": "const getters = {\n isLogged: state => state.user !== null,\n isFiles: state => !state.loading && state.route.name === '"
},
{
"path": "src/store/index.js",
"chars": 489,
"preview": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport mutations from './mutations'\nimport getters from './getters'\n\nVue.u"
},
{
"path": "src/store/mutations.js",
"chars": 2170,
"preview": "import * as i18n from '@/i18n'\nimport moment from 'moment'\n\nconst mutations = {\n closeHovers: state => {\n state.show"
},
{
"path": "src/utils/auth.js",
"chars": 1962,
"preview": "import store from '@/store'\nimport router from '@/router'\nimport { Base64 } from 'js-base64'\nimport { baseURL } from '@/"
},
{
"path": "src/utils/buttons.js",
"chars": 1337,
"preview": "function loading (button) {\n let el = document.querySelector(`#${button}-button > i`)\n\n if (el === undefined || el ==="
},
{
"path": "src/utils/constants.js",
"chars": 654,
"preview": "const name = window.FileBrowser.Name || 'File Browser'\nconst disableExternal = window.FileBrowser.DisableExternal\nconst "
},
{
"path": "src/utils/cookie.js",
"chars": 159,
"preview": "export default function (name) {\n let re = new RegExp('(?:(?:^|.*;\\\\s*)' + name + '\\\\s*\\\\=\\\\s*([^;]*).*$)|^.*$')\n retu"
},
{
"path": "src/utils/css.js",
"chars": 614,
"preview": "export default function getRule (rules) {\n for (let i = 0; i < rules.length; i++) {\n rules[i] = rules[i].toLowerCase"
},
{
"path": "src/utils/url.js",
"chars": 179,
"preview": "function removeLastDir (url) {\n var arr = url.split('/')\n if (arr.pop() === '') {\n arr.pop()\n }\n\n return arr.join"
},
{
"path": "src/utils/vue.js",
"chars": 1096,
"preview": "import Vue from 'vue'\nimport Noty from 'noty'\nimport i18n from '@/i18n'\nimport { disableExternal } from '@/utils/constan"
},
{
"path": "src/views/Files.vue",
"chars": 5840,
"preview": "<template>\n <div>\n <div id=\"breadcrumbs\">\n <router-link to=\"/files/\" :aria-label=\"$t('files.home')\" :title=\"$t("
},
{
"path": "src/views/Layout.vue",
"chars": 989,
"preview": "<template>\n <div>\n <div id=\"progress\">\n <div v-bind:style=\"{ width: $store.state.progress + '%' }\"></div>\n <"
},
{
"path": "src/views/Login.vue",
"chars": 2657,
"preview": "<template>\n <div id=\"login\" :class=\"{ recaptcha: recaptcha }\">\n <form @submit=\"submit\">\n <img :src=\"logoURL\" al"
},
{
"path": "src/views/Settings.vue",
"chars": 736,
"preview": "<template>\n <div class=\"dashboard\">\n <ul id=\"nav\" v-if=\"user.perm.admin\">\n <li :class=\"{ active: $route.path =="
},
{
"path": "src/views/Share.vue",
"chars": 2026,
"preview": "<template>\n <div class=\"share\" v-if=\"loaded\">\n <a target=\"_blank\" :href=\"link\">\n <div class=\"share__box\">\n "
},
{
"path": "src/views/errors/403.vue",
"chars": 221,
"preview": "<template>\n <div>\n <h2 class=\"message\">\n <i class=\"material-icons\">error</i>\n <span>{{ $t('errors.forbidde"
},
{
"path": "src/views/errors/404.vue",
"chars": 222,
"preview": "<template>\n <div>\n <h2 class=\"message\">\n <i class=\"material-icons\">gps_off</i>\n <span>{{ $t('errors.notFou"
},
{
"path": "src/views/errors/500.vue",
"chars": 233,
"preview": "<template>\n <div>\n <h2 class=\"message\">\n <i class=\"material-icons\">error_outline</i>\n <span>{{ $t('errors."
},
{
"path": "src/views/settings/Global.vue",
"chars": 5241,
"preview": "<template>\n <div class=\"dashboard\" v-if=\"settings !== null\">\n <form class=\"card\" @submit.prevent=\"save\">\n <div "
},
{
"path": "src/views/settings/Profile.vue",
"chars": 2757,
"preview": "<template>\n <div class=\"dashboard\">\n <form class=\"card\" @submit=\"updateSettings\">\n <div class=\"card-title\">\n "
},
{
"path": "src/views/settings/User.vue",
"chars": 3727,
"preview": "<template>\n <div>\n <form v-if=\"loaded\" @submit=\"save\" class=\"card\">\n <div class=\"card-title\">\n <h2 v-if="
},
{
"path": "src/views/settings/Users.vue",
"chars": 1210,
"preview": "<template>\n <div class=\"card\">\n <div class=\"card-title\">\n <h2>{{ $t('settings.users') }}</h2>\n <router-lin"
},
{
"path": "vue.config.js",
"chars": 80,
"preview": "module.exports = {\n runtimeCompiler: true,\n publicPath: '[{[ .StaticURL ]}]'\n}"
}
]
About this extraction
This page contains the full source code of the filebrowser/frontend GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (296.9 KB), approximately 90.3k tokens, and a symbol index with 39 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.