1
0
Fork 0
mirror of https://github.com/Luzifer/share.git synced 2024-12-20 18:41:17 +00:00

Re-vamp frontend files

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-05-12 23:51:56 +02:00
parent 89944d242f
commit 81060aba97
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
12 changed files with 9512 additions and 824 deletions

8980
bindata.go

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -6,77 +6,17 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/combine/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css,npm/bootstrap-vue@2.0.0-rc.19/dist/bootstrap-vue.min.css,npm/bootswatch@4.3.1/dist/darkly/bootstrap.min.css,npm/highlight.js@9.15.6/styles/androidstudio.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha256-LA89z+k9fjgMKQ/kq4OO2Mrf8VltYml/VES+Rg0fh20=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/4.0.0/darkly/bootstrap.min.css"
integrity="sha256-bhD4/DdCApNAYlOpvKssUa2hD0Du8xrgOvtY4w25shM=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css" <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous"> integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/androidstudio.min.css"
integrity="sha256-rEVnyeMfkKzmBwocdeeiqGOu1M/ttUHB7K0meKIwqvw=" crossorigin="anonymous" />
<title>Share</title> <title>Share</title>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div id="app"></div>
<a class="navbar-brand" href="#"><i class="fas fa-share-alt-square"></i>
<span class="apptitle">Share</span></a>
</nav>
<div class="container show-loading">
<h1>
<i class="fas fa-spinner fa-pulse" aria-hidden="true"></i>
</h1>
</div>
<div class="container show-generic">
<h1>
<i class="fas fa-cloud-download-alt"></i>
</h1>
<h2><a href="#" class="filelink-href filename">untitled</a></h2>
</div>
<div class="container show-image text-center">
<a href="#" class="filelink-href">
<img src="" class="img-fluid filelink-src">
</a>
</div>
<div class="container show-video">
<div class="embed-responsive embed-responsive-16by9">
<video controls>
</video>
</div>
</div>
<div class="container show-audio text-center">
<audio controls></audio>
</div>
<div class="container show-text">
<pre><code></code></pre>
</div>
<div class="container show-error">
<h1>
<i class="fas fa-exclamation-circle"></i>
</h1>
<h2 class="error"></h2>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.1/popper.min.js"
integrity="sha256-ST2MecrXrJaAsqmfpk9XRQITlDoyMmUtgKBEndDisSc=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha256-5+02zu5UULQkO7w1GIr6vftCgMfFdZcAHeDtFnKZsBs=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"
integrity="sha256-/BfiIkHlHoVihZdc6TFuj7MmJ0TWcWsMXkeDFwhi0zw=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/combine/npm/vue@2.6.10,npm/bootstrap-vue@2.0.0-rc.19/dist/bootstrap-vue.min.js,npm/axios@0.18.0/dist/axios.min.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
</body> </body>
</html> </html>

89
src/.eslintrc.js Normal file
View file

@ -0,0 +1,89 @@
// https://eslint.org/docs/user-guide/configuring
module.exports = {
'root': true,
'parserOptions': {
parser: 'babel-eslint',
sourceType: 'module',
},
'env': {
node: true,
},
'extends': [
/*
* https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
* consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
*/
'plugin:vue/essential',
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
'eslint:recommended',
],
// required to lint *.vue files
'plugins': ['vue'],
'globals': {
locale: true,
process: true,
version: true,
},
// add your custom rules here
'rules': {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { before: true, after: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'comma-dangle': ['error', 'always-multiline'], // Apply Contentflow rules
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'], // allow async-await
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { beforeColon: false, afterColon: true, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['warn'],
'newline-per-chained-call': ['error'],
'no-console': ['off'],
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
'no-else-return': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxEOF: 0, maxBOF: 0 }],
'no-multi-spaces': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-useless-return': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: true }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'semi': ['error', 'never'],
'space-before-blocks': ['error', 'always'],
'spaced-comment': ['warn', 'always'],
'space-infix-ops': ['error'],
'space-in-parens': ['error', 'never'],
'space-unary-ops': ['error', { words: true, nonwords: false }],
'switch-colon-spacing': ['error'],
'unicode-bom': ['error', 'never'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
}

View file

@ -1,120 +0,0 @@
import './style.scss'
let fileURL = null;
const MSG_NOT_FOUND = 'File not found';
const MSG_NOT_PERMITTED = 'Not allowed to access file';
const MSG_GENERIC_ERR = 'Something went wrong';
/* global $:false */
class Share {
init() {
$(window).bind('hashchange', (e) => {
this.hashLoad();
});
let appTitle = `Share @ ${window.location.host}`;
$('title,.apptitle').text(appTitle);
this.hashLoad();
}
embedFileInfo(file = '') {
if (file === '') {
this.handleErrorMessage(MSG_NOT_FOUND);
return;
}
fileURL = file;
$.ajax(file, {
method: 'HEAD',
success: (data, status, xhr) => {
this.handleEmbed(data, status, xhr);
},
error: (xhr, status) => {
this.handleError(xhr, status);
},
});
}
handleEmbed(data, status, xhr) {
let type = xhr.getResponseHeader('Content-Type');
$('.container').hide();
$('.filename').text(fileURL.substring(fileURL.lastIndexOf('/') + 1));
$('.filelink-href').attr('href', fileURL);
if (type.match(/^image\//)) {
$('.filelink-src').attr('src', fileURL);
$('.show-image').show();
return;
}
if (type.match(/^video\//)) {
let src = $('<source>');
src.attr('src', fileURL);
src.appendTo($('video'));
$('.show-video').show();
return;
}
if (type.match(/^audio\/(aac|mp3|mpeg)$/)) {
let src = $('<source>');
src.attr('src', fileURL);
src.appendTo($('audio'));
$('.show-audio').show();
return;
}
if (type.match(/^(text\/|application\/javascript)/)) {
$.ajax(fileURL, {
dataType: 'text',
method: 'GET',
success: (data) => {
$('code').text(data);
$('.show-text').show();
hljs.initHighlighting();
},
error: (xhr, status) => {
this.handleError(xhr, status);
},
});
return;
}
$('.show-generic').show();
}
handleError(xhr, status) {
let message = '';
switch (xhr.status) {
case 404:
message = MSG_NOT_FOUND;
break;
case 403:
message = MSG_NOT_PERMITTED;
break;
default:
message = MSG_GENERIC_ERR;
break;
}
this.handleErrorMessage(message);
}
handleErrorMessage(message) {
$('.error').text(message);
$('.container').hide();
$('.show-error').show();
}
hashLoad() {
let file = window.location.hash.substring(1);
this.embedFileInfo(file);
}
}
$(function() {
let share = new Share();
share.init();
});

174
src/app.vue Normal file
View file

@ -0,0 +1,174 @@
<template>
<div>
<b-navbar variant="primary" type="dark">
<b-navbar-brand href="#"><i class="fas fa-share-alt-square"></i> Share</b-navbar-brand>
</b-navbar>
<b-container class="mt-4">
<b-row>
<b-col>
<b-card v-if="loading">
<b-card-text class="text-center">
<h2><i class="fas fa-spinner fa-pulse"></i></h2>
{{ strings.loading }}
</b-card-text>
</b-card>
<template v-else>
<b-card v-if="error" bg-variant="danger" text-variant="white">
<b-card-text class="text-center">
<h2><i class="fas fa-exclamation-circle"></i></h2>
{{ error }}
</b-card-text>
</b-card>
<b-card v-else-if="fileType.startsWith('image/')">
<b-card-text class="text-center">
<a :href="path">
<b-img :src="path" fluid></b-img>
</a>
</b-card-text>
</b-card>
<b-card v-else-if="fileType.startsWith('video/')">
<b-embed type="video" :src="path" allowfullscreen controls></b-embed>
</b-card>
<b-card v-else-if="fileType.startsWith('audio/')">
<b-card-text class="text-center">
<audio :src="path" controls></audio>
</b-card-text>
</b-card>
<b-card v-else-if="fileType.startsWith('text/')">
<pre><code>{{ text }}</code></pre>
</b-card>
<b-card v-else>
<b-card-text class="text-center">
<h2><i class="fas fa-cloud-download-alt"></i></h2>
<b-button :href="path" variant="success">{{ fileName }}</b-button>
</b-card-text>
</b-card>
</template>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import hljs from 'highlight.js'
import rewrites from './mime-rewrite.js'
import strings from './strings.js'
export default {
name: 'app',
computed: {
strings() {
return strings
},
},
data() {
return {
error: null,
fileType: null,
fileName: '',
loading: true,
path: '',
text: '',
}
},
methods: {
hashChange() {
const hash = window.location.hash
if (hash.length > 0) {
this.path = hash.substring(1)
} else {
this.error = strings.file_not_found
this.loading = false
}
},
},
mounted() {
window.onhashchange = this.hashChange
this.hashChange()
},
watch: {
fileType(v) {
// Rewrite known file types not matching the expectations above
if (rewrites[v]) {
this.fileType = rewrites[v]
return
}
// Load text files directly and highlight them
if (v.startsWith('text/')) {
this.loading = true
axios.get(this.path)
.then(resp => {
this.text = resp.data
if (this.text.length < 200*1024 && v !== 'text/plain') {
// Only highlight up to 200k and not on text/plain
window.setTimeout(() => hljs.initHighlighting(), 100)
}
this.loading = false
})
.catch(err => console.log(err))
}
},
path() {
if (this.path.indexOf('://') >= 0) {
// Strictly disallow loading files having any protocol in them
this.error = strings.not_permitted
this.loading = false
return
}
axios.head(this.path)
.then(resp => {
let contentType = 'application/octet-stream'
if (resp && resp.headers && resp.headers['content-type']) {
contentType = resp.headers['content-type']
}
this.loading = false
this.fileType = contentType
})
.catch(err => {
switch (err.response.status) {
case 403:
this.error = strings.not_permitted
break
case 404:
this.error = strings.file_not_found
break
default:
this.error = `Something went wrong (Status ${err.response.status})`
}
this.loading = false
})
},
},
}
</script>
<style scoped>
audio {
width: 80%;
}
</style>

7
src/main.js Normal file
View file

@ -0,0 +1,7 @@
import app from './app.vue'
new Vue({
components: { app },
el: '#app',
render: c => c('app'),
})

3
src/mime-rewrite.js Normal file
View file

@ -0,0 +1,3 @@
export default {
'application/javascript': 'text/javascript',
}

854
src/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,11 @@
"babel-loader": "^7.1.5", "babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0", "babel-preset-env": "^1.7.0",
"css-loader": "^1.0.0", "css-loader": "^1.0.0",
"node-sass": "^4.9.2", "node-sass": "^4.12.0",
"sass-loader": "^7.0.3", "sass-loader": "^7.0.3",
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.16.2", "webpack": "^4.16.2",
"webpack-cli": "^3.1.0", "webpack-cli": "^3.1.0",
"webpack-dev-middleware": "^1.4.0", "webpack-dev-middleware": "^1.4.0",
@ -21,5 +23,7 @@
"build": "webpack -p" "build": "webpack -p"
}, },
"version": "0.0.1", "version": "0.0.1",
"dependencies": {} "dependencies": {
"highlight.js": "^9.15.6"
}
} }

5
src/strings.js Normal file
View file

@ -0,0 +1,5 @@
export default {
file_not_found: 'The requested file has not been found.',
loading: 'Loading file details...',
not_permitted: 'Access to this file was denied.',
}

View file

@ -1,12 +1,14 @@
const path = require('path') const path = require('path')
const webpack = require('webpack'); const webpack = require('webpack');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
function resolve(dir) { function resolve(dir) {
return path.join(__dirname, dir) return path.join(__dirname, dir)
} }
module.exports = { module.exports = {
entry: './app.js', entry: './main.js',
output: { output: {
filename: 'app.js', filename: 'app.js',
path: path.resolve(__dirname, '..', 'frontend') path: path.resolve(__dirname, '..', 'frontend')
@ -17,13 +19,16 @@ module.exports = {
'process.env': { 'process.env': {
'NODE_ENV': JSON.stringify('production') 'NODE_ENV': JSON.stringify('production')
} }
}) }),
new VueLoaderPlugin(),
], ],
optimization: { optimization: {
minimize: true minimize: true
}, },
module: { module: {
rules: [{ rules: [
{
test: /\.(s?)css$/, test: /\.(s?)css$/,
use: [ use: [
'style-loader', 'style-loader',
@ -31,6 +36,7 @@ module.exports = {
'sass-loader', 'sass-loader',
], ],
}, },
{ {
test: /\.js$/, test: /\.js$/,
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
@ -38,15 +44,17 @@ module.exports = {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: [ presets: [
['env', { ['env', { "targets": { "browsers": [">0.25%", "not ie 11", "not op_mini all"] } }]
"targets": {
"browsers": [">0.25%", "not ie 11", "not op_mini all"]
}
}]
] ]
} }
} }
}, },
{
test: /\.vue$/,
loader: 'vue-loader',
},
] ]
} }
} }