aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPitu <[email protected]>2018-09-19 04:45:50 -0300
committerPitu <[email protected]>2018-09-19 04:45:50 -0300
commit430af8306b1ab17e59a6dabf8f65ab816d28695d (patch)
tree975814e80919cc7b8c5d820080a30def32a371ea /src
parentSome adjustements to public album view (diff)
downloadhost.fuwn.me-430af8306b1ab17e59a6dabf8f65ab816d28695d.tar.xz
host.fuwn.me-430af8306b1ab17e59a6dabf8f65ab816d28695d.zip
Switch to Nuxt.js
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/files/uploadPOST_Multer.js.bak380
-rw-r--r--src/site/App.vue208
-rw-r--r--src/site/assets/images/logo.png (renamed from src/site/public/images/logo.png)bin45536 -> 45536 bytes
-rw-r--r--src/site/assets/styles/_colors.scss (renamed from src/site/styles/_colors.scss)0
-rw-r--r--src/site/assets/styles/dropzone.scss (renamed from src/site/styles/dropzone.scss)0
-rw-r--r--src/site/assets/styles/icons.min.css (renamed from src/site/styles/icons.min.css)0
-rw-r--r--src/site/assets/styles/style.scss (renamed from src/site/styles/style.scss)0
-rw-r--r--src/site/components/grid/Grid.vue17
-rw-r--r--src/site/components/grid/waterfall/Waterfall.vue9
-rw-r--r--src/site/components/grid/waterfall/WaterfallItem.vue2
-rw-r--r--src/site/components/grid/waterfall/old/waterfall-slot.vue76
-rw-r--r--src/site/components/grid/waterfall/old/waterfall.vue442
-rw-r--r--src/site/components/home/links/Links.vue4
-rw-r--r--src/site/components/logo/Logo.vue4
-rw-r--r--src/site/components/navbar/Navbar.vue8
-rw-r--r--src/site/components/sidebar/Sidebar.vue18
-rw-r--r--src/site/components/uploader/Uploader.vue26
-rw-r--r--src/site/index.html14
-rw-r--r--src/site/index.js49
-rw-r--r--src/site/layouts/default.vue114
-rw-r--r--src/site/layouts/error.vue (renamed from src/site/views/NotFound.vue)6
-rw-r--r--src/site/pages/a/_identifier.vue (renamed from src/site/views/PublicAlbum.vue)58
-rw-r--r--src/site/pages/dashboard/albums.vue (renamed from src/site/views/dashboard/Albums.vue)24
-rw-r--r--src/site/pages/dashboard/index.vue (renamed from src/site/views/dashboard/Uploads.vue)17
-rw-r--r--src/site/pages/dashboard/settings.vue (renamed from src/site/views/dashboard/Settings.vue)17
-rw-r--r--src/site/pages/index.vue (renamed from src/site/views/Home.vue)47
-rw-r--r--src/site/pages/login.vue (renamed from src/site/views/Auth/Login.vue)10
-rw-r--r--src/site/pages/register.vue (renamed from src/site/views/Auth/Register.vue)17
-rw-r--r--src/site/plugins/buefy.js4
-rw-r--r--src/site/plugins/v-clipboard.js4
-rw-r--r--src/site/plugins/vue-analytics.js12
-rw-r--r--src/site/plugins/vue-axios.js6
-rw-r--r--src/site/plugins/vue-isyourpasswordsafe.js7
-rw-r--r--src/site/plugins/vue-timeago.js8
-rw-r--r--src/site/router/index.js21
-rw-r--r--src/site/store/index.js23
-rw-r--r--src/site/views/dashboard/Album.vue172
-rw-r--r--src/start.js86
38 files changed, 780 insertions, 1130 deletions
diff --git a/src/api/routes/files/uploadPOST_Multer.js.bak b/src/api/routes/files/uploadPOST_Multer.js.bak
new file mode 100644
index 0000000..d6e6436
--- /dev/null
+++ b/src/api/routes/files/uploadPOST_Multer.js.bak
@@ -0,0 +1,380 @@
+const Route = require('../../structures/Route');
+const config = require('../../../../config');
+const path = require('path');
+const multer = require('multer');
+const Util = require('../../utils/Util');
+const db = require('knex')(config.server.database);
+const moment = require('moment');
+const log = require('../../utils/Log');
+const jetpack = require('fs-jetpack');
+const Busboy = require('busboy');
+const fs = require('fs');
+// WE SHOULD ALSO STRIP EXIF UNLESS THE USER SPECIFIED THEY WANT IT.
+// https://github.com/WeebDev/lolisafe/issues/110
+class uploadPOST extends Route {
+ constructor() {
+ super('/upload', 'post', { bypassAuth: true });
+ }
+
+ async run(req, res) {
+ const user = Util.isAuthorized(req);
+ if (!user && !config.uploads.allowAnonymousUploads) return res.status(401).json({ message: 'Not authorized to use this resource' });
+
+ /*
+ const albumId = req.body.albumId || req.headers.albumId;
+ if (this.albumId && !this.user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
+ if (this.albumId && this.user) {
+ const album = await db.table('albums').where({ id: this.albumId, userId: this.user.id }).first();
+ if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
+ }
+ */
+ return this.uploadFile(req, res, user);
+ }
+
+ async processFile(req, res, user, file) {
+ /*
+ Check if the user is trying to upload to an album
+ */
+ const albumId = req.body.albumId || req.headers.albumId;
+ if (albumId && !user) return res.status(401).json({ message: 'Only registered users can upload files to an album' });
+ if (albumId && user) {
+ const album = await db.table('albums').where({ id: albumId, userId: user.id }).first();
+ if (!album) return res.status(401).json({ message: 'Album doesn\'t exist or it doesn\'t belong to the user' });
+ }
+
+ let upload = file.data;
+ /*
+ If it's a chunked upload but this is not the last part of the chunk, just green light.
+ Otherwise, put the file together and process it
+ */
+ if (file.body.uuid) {
+ if (file.body.chunkindex < file.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if
+ /*
+ We got a chunk that is not the last part, send smoke signal that we received it.
+ */
+ return res.json({ message: 'Successfully uploaded chunk' });
+ } else {
+ /*
+ Seems we finally got the last part of a chunk upload
+ */
+ const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder);
+ const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', file.body.uuid);
+ const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' });
+ const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'));
+
+ const tempFile = {
+ filename: Util.getUniqueFilename(originalname),
+ originalname,
+ size: file.body.totalfilesize
+ };
+
+ for (const chunkFile of chunkFiles) {
+ try {
+ const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop
+ await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ upload = tempFile;
+ }
+ }
+
+ console.log(upload);
+ const hash = await Util.getFileHash(upload.filename); // eslint-disable-line no-await-in-loop
+ const exists = await db.table('files') // eslint-disable-line no-await-in-loop
+ .where(function() {
+ if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this
+ else this.where('userId', user.id); // eslint-disable-line no-invalid-this
+ })
+ .where({
+ hash,
+ size: upload.size
+ })
+ .first();
+
+ if (exists) {
+ res.json({
+ message: 'Successfully uploaded file',
+ name: exists.name,
+ size: exists.size,
+ url: `${config.filesServeLocation}/${exists.name}`
+ });
+
+ return Util.deleteFile(upload.filename);
+ }
+
+ const now = moment.utc().toDate();
+ try {
+ await db.table('files').insert({
+ userId: user ? user.id : null,
+ name: upload.filename,
+ original: upload.originalname,
+ type: upload.mimetype || '',
+ size: upload.size,
+ hash,
+ ip: req.ip,
+ albumId: albumId ? albumId : null,
+ createdAt: now,
+ editedAt: now
+ });
+ } catch (error) {
+ log.error('There was an error saving the file to the database');
+ console.log(error);
+ return res.status(500).json({ message: 'There was an error uploading the file.' });
+ }
+
+ res.json({
+ message: 'Successfully uploaded file',
+ name: upload.filename,
+ size: upload.size,
+ url: `${config.filesServeLocation}/${upload.filename}`
+ });
+
+ if (albumId) {
+ try {
+ db.table('albums').where('id', albumId).update('editedAt', now);
+ } catch (error) {
+ log.error('There was an error updating editedAt on an album');
+ console.error(error);
+ }
+ }
+
+ // return Util.generateThumbnail(file.filename);
+ }
+
+ uploadFile(req, res, user) {
+ const busboy = new Busboy({
+ headers: req.headers,
+ limits: {
+ fileSize: config.uploads.uploadMaxSize * (1000 * 1000),
+ files: 1
+ }
+ });
+
+ const fileToUpload = {
+ data: {},
+ body: {}
+ };
+
+ /*
+ Note: For this to work on every case, whoever is uploading a chunk
+ should really send the body first and the file last. Otherwise lolisafe
+ may not catch the field on time and the chunk may end up being saved
+ as a standalone file, completely broken.
+ */
+ busboy.on('field', (fieldname, val) => {
+ if (/^dz/.test(fieldname)) {
+ fileToUpload.body[fieldname.substring(2)] = val;
+ } else {
+ fileToUpload.body[fieldname] = val;
+ }
+ });
+
+ /*
+ Hey ther's a file! Let's upload it.
+ */
+ busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
+ let name, saveTo;
+
+ /*
+ Let check whether the file is part of a chunk upload or if it's a standalone one.
+ If the former, we should store them separately and join all the pieces after we
+ receive the last one.
+ */
+ if (!fileToUpload.body.uuid) {
+ name = Util.getUniqueFilename(filename);
+ if (!name) return res.status(500).json({ message: 'There was a problem allocating a filename for your upload' });
+ saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, name);
+ } else {
+ name = `${filename}.${fileToUpload.body.chunkindex}`;
+ const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid);
+ jetpack.dir(chunkDir);
+ saveTo = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', fileToUpload.body.uuid, name);
+ }
+
+ /*
+ Let's save some metadata for the db.
+ */
+ fileToUpload.data = { filename: name, originalname: filename, encoding, mimetype };
+ const stream = fs.createWriteStream(saveTo);
+
+ file.on('data', data => {
+ fileToUpload.data.size = data.length;
+ });
+
+ /*
+ The file that is being uploaded is bigger than the limit specified on the config file
+ and thus we should close the stream and delete the file.
+ */
+ file.on('limit', () => {
+ file.unpipe(stream);
+ stream.end();
+ jetpack.removeAsync(saveTo);
+ res.status(400).json({ message: 'The file is too big.' });
+ });
+
+ file.pipe(stream);
+ });
+
+ busboy.on('error', err => {
+ log.error('There was an error uploading a file');
+ console.error(err);
+ return res.status(500).json({ message: 'There was an error uploading the file.' });
+ });
+
+ busboy.on('finish', () => this.processFile(req, res, user, fileToUpload));
+ req.pipe(busboy);
+
+ // return req.pipe(busboy);
+
+ /*
+ return upload(this.req, this.res, async err => {
+ if (err) {
+ log.error('There was an error uploading a file');
+ console.error(err);
+ return this.res.status(500).json({ message: 'There was an error uploading the file.' });
+ }
+
+ log.info('---');
+ console.log(this.req.file);
+ log.info('---');
+
+ let file = this.req.file;
+ if (this.req.body.uuid) {
+ // If it's a chunked upload but this is not the last part of the chunk, just green light.
+ // Otherwise, put the file together and process it
+ if (this.req.body.chunkindex < this.req.body.totalchunkcount - 1) { // eslint-disable-line no-lonely-if
+ log.info('Hey this is a chunk, sweet.');
+ return this.res.json({ message: 'Successfully uploaded chunk' });
+ } else {
+ log.info('Hey this is the last part of a chunk, sweet.');
+
+ const uploadsDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder);
+ const chunkedFileDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', this.req.body.uuid);
+ const chunkFiles = await jetpack.findAsync(chunkedFileDir, { matching: '*' });
+ const originalname = chunkFiles[0].substring(0, chunkFiles[0].lastIndexOf('.'));
+
+ const tempFile = {
+ filename: Util.getUniqueFilename(originalname),
+ originalname,
+ size: this.req.body.totalfilesize
+ };
+
+ for (const chunkFile of chunkFiles) {
+ try {
+ const data = await jetpack.readAsync(chunkFile, 'buffer'); // eslint-disable-line no-await-in-loop
+ await jetpack.appendAsync(path.join(uploadsDir, tempFile.filename), data); // eslint-disable-line no-await-in-loop
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ file = tempFile;
+ }
+ }
+
+ const { user } = this;
+ // console.log(file);
+ if (!file.filename) return log.error('This file doesnt have a filename!');
+ // console.log(file);
+ const hash = await Util.getFileHash(file.filename); // eslint-disable-line no-await-in-loop
+ const exists = await db.table('files') // eslint-disable-line no-await-in-loop
+ .where(function() {
+ if (!user) this.whereNull('userId'); // eslint-disable-line no-invalid-this
+ else this.where('userId', user.id); // eslint-disable-line no-invalid-this
+ })
+ .where({
+ hash,
+ size: file.size
+ })
+ .first();
+
+ if (exists) {
+ this.res.json({
+ message: 'Successfully uploaded file',
+ name: exists.name,
+ size: exists.size,
+ url: `${config.filesServeLocation}/${exists.name}`
+ });
+
+ return Util.deleteFile(file.filename);
+ }
+
+ const now = moment.utc().toDate();
+ try {
+ await db.table('files').insert({
+ userId: this.user ? this.user.id : null,
+ name: file.filename,
+ original: file.originalname,
+ type: file.mimetype || '',
+ size: file.size,
+ hash,
+ ip: this.req.ip,
+ albumId: this.albumId ? this.albumId : null,
+ createdAt: now,
+ editedAt: now
+ });
+ } catch (error) {
+ log.error('There was an error saving the file to the database');
+ console.log(error);
+ return this.res.status(500).json({ message: 'There was an error uploading the file.' });
+ }
+
+ this.res.json({
+ message: 'Successfully uploaded file',
+ name: file.filename,
+ size: file.size,
+ url: `${config.filesServeLocation}/${file.filename}`
+ });
+
+ if (this.albumId) {
+ try {
+ db.table('albums').where('id', this.albumId).update('editedAt', now);
+ } catch (error) {
+ log.error('There was an error updating editedAt on an album');
+ console.error(error);
+ }
+ }
+
+ // return Util.generateThumbnail(file.filename);
+ });
+ */
+ }
+}
+
+/*
+const upload = multer({
+ limits: config.uploads.uploadMaxSize,
+ fileFilter(req, file, cb) {
+ const ext = path.extname(file.originalname).toLowerCase();
+ if (Util.isExtensionBlocked(ext)) return cb('This file extension is not allowed');
+
+ // Remove those pesky dz prefixes. Thanks to BobbyWibowo.
+ for (const key in req.body) {
+ if (!/^dz/.test(key)) continue;
+ req.body[key.replace(/^dz/, '')] = req.body[key];
+ delete req.body[key];
+ }
+
+ return cb(null, true);
+ },
+ storage: multer.diskStorage({
+ destination(req, file, cb) {
+ if (!req.body.uuid) return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder));
+ // Hey, we have chunks
+
+ const chunkDir = path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder, 'chunks', req.body.uuid);
+ jetpack.dir(chunkDir);
+ return cb(null, chunkDir);
+ return cb(null, path.join(__dirname, '..', '..', '..', '..', config.uploads.uploadFolder));
+ },
+ filename(req, file, cb) {
+ // if (req.body.uuid) return cb(null, `${file.originalname}.${req.body.chunkindex}`);
+ const filename = Util.getUniqueFilename(file.originalname);
+ // if (!filename) return cb('Could not allocate a unique file name');
+ return cb(null, filename);
+ }
+ })
+}).single('file');
+*/
+module.exports = uploadPOST;
diff --git a/src/site/App.vue b/src/site/App.vue
deleted file mode 100644
index f0cd9d6..0000000
--- a/src/site/App.vue
+++ /dev/null
@@ -1,208 +0,0 @@
-<template>
- <div id="app"
- @dragover="isDrag = true"
- @dragend="isDrag = false"
- @dragleave="isDrag = false"
- @drop="isDrag = false">
- <router-view :key="$route.fullPath"/>
-
- <!--
- <div v-if="!ready"
- id="loading">
- <div class="background"/>
- <Loading class="square"/>
- </div>
- -->
- <div v-if="false"
- id="drag-overlay">
- <div class="background"/>
- <div class="drop">
- Drop your files here
- </div>
- </div>
- </div>
-</template>
-
-<script>
-import Vue from 'vue';
-import Fuse from 'fuse.js';
-import Logo from './components/logo/Logo.vue';
-import Loading from './components/loading/CubeShadow.vue';
-
-const protectedRoutes = [
- '/dashboard',
- '/dashboard/albums',
- '/dashboard/settings'
-];
-
-export default {
- components: {
- Loading,
- Logo
- },
- data() {
- return {
- pageTitle: '',
- ready: false,
- isDrag: false
- };
- },
- computed: {
- user() {
- return this.$store.state.user;
- },
- loggedIn() {
- return this.$store.state.loggedIn;
- },
- config() {
- return this.$store.state.config;
- }
- },
- mounted() {
- console.log(`%c Running lolisafe %c v${this.config.version} %c`, 'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff', 'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff', 'background:transparent');
- this.$store.commit('config', Vue.prototype.$config);
- this.ready = true;
- },
- metaInfo() { // eslint-disable-line complexity
- return {
- title: this.pageTitle || 'A small safe worth protecting.',
- titleTemplate: '%s | lolisafe',
- link: [
- { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Nunito:300,400,600,700', body: true },
- // { rel: 'stylesheet', href: 'https://cdn.materialdesignicons.com/2.1.99/css/materialdesignicons.min.css', body: true },
-
- { rel: 'apple-touch-icon', sizes: '180x180', href: '/public/images/icons/apple-touch-icon.png' },
- { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/public/images/icons/favicon-32x32.png' },
- { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/public/images/icons/favicon-16x16.png' },
- { rel: 'manifest', href: '/public/images/icons/manifest.json' },
- { rel: 'mask-icon', color: '#FF015B', href: '/public/images/icons/safari-pinned-tab.svg' },
- { rel: 'shortcut icon', href: '/public/images/icons/favicon.ico' },
- { rel: 'chrome-webstore-item', href: 'https://chrome.google.com/webstore/detail/lolisafe-uploader/enkkmplljfjppcdaancckgilmgoiofnj' },
- { type: 'application/json+oembed', href: '/public/oembed.json' }
- ],
- meta: [
- { vmid: 'theme-color', name: 'theme-color', content: '#30a9ed' },
-
- { vmid: 'description', name: 'description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
- { vmid: 'keywords', name: 'keywords', content: 'lolisafe, file, upload, uploader, vue, node, open source, free' },
-
- { vmid: 'apple-mobile-web-app-title', name: 'apple-mobile-web-app-title', content: 'lolisafe' },
- { vmid: 'application-name', name: 'application-name', content: 'lolisafe' },
- { vmid: 'msapplication-config', name: 'msapplication-config', content: '/public/images/icons/browserconfig.xml' },
-
- { vmid: 'twitter:card', name: 'twitter:card', content: 'summary_large_image' },
- { vmid: 'twitter:site', name: 'twitter:site', content: '@its_pitu' },
- { vmid: 'twitter:creator', name: 'twitter:creator', content: '@its_pitu' },
- { vmid: 'twitter:title', name: 'twitter:title', content: `lolisafe` },
- { vmid: 'twitter:description', name: 'twitter:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
- { vmid: 'twitter:image', name: 'twitter:image', content: '/public/images/share.jpg' },
-
- { vmid: 'og:url', property: 'og:url', content: 'https://lolisafe.moe' },
- { vmid: 'og:type', property: 'og:type', content: 'website' },
- { vmid: 'og:title', property: 'og:title', content: `lolisafe` },
- { vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
- { vmid: 'og:image', property: 'og:image', content: '/public/images/share.jpg' },
- { vmid: 'og:image:secure_url', property: 'og:image:secure_url', content: '/public/images/share.jpg' },
- { vmid: 'og:site_name', property: 'og:site_name', content: 'lolisafe' }
- ]
- };
- },
- created() {
- /*
- Register our global handles
- */
- const App = this; // eslint-disable-line consistent-this
- this.$store.commit('config', Vue.prototype.$config);
- Vue.prototype.$search = function(term, list, options) {
- return new Promise(resolve => {
- const run = new Fuse(list, options);
- const results = run.search(term);
- return resolve(results);
- });
- };
-
- Vue.prototype.$onPromiseError = function(error, logout = false) {
- App.processCatch(error, logout);
- };
-
- Vue.prototype.$showToast = function(text, error, duration) {
- App.showToast(text, error, duration);
- };
-
- Vue.prototype.$logOut = function() {
- App.$store.commit('user', null);
- App.$store.commit('loggedIn', false);
- App.$store.commit('token', null);
- };
-
- this.$router.beforeEach((to, from, next) => {
- if (this.$store.state.loggedIn) return next();
- if (process.browser) {
- if (localStorage && localStorage.getItem('ls-token')) return this.tryToLogin(next, `/login?redirect=${to.path}`);
- }
-
- for (const match of to.matched) {
- if (protectedRoutes.includes(match.path)) {
- if (this.$store.state.loggedIn === false) return next(`/login?redirect=${to.path}`);
- }
- }
-
- return next();
- });
- if (process.browser) this.tryToLogin();
- },
- methods: {
- showToast(text, error, duration) {
- this.$toast.open({
- duration: duration || 2500,
- message: text,
- position: 'is-bottom',
- type: error ? 'is-danger' : 'is-success'
- });
- },
- processCatch(error, logout) {
- if (error.response && error.response.data && error.response.data.message) {
- this.showToast(error.response.data.message, true, 5000);
- if (error.response.status === 429) return;
- if (error.response.status === 502) return;
- if (logout) {
- this.$logOut();
- setTimeout(() => this.$router.push('/'), 3000);
- }
- } else {
- console.error(error);
- this.showToast('Something went wrong, please check the console :(', true, 5000);
- }
- },
- tryToLogin(next, destination) {
- if (process.browser) this.$store.commit('token', localStorage.getItem('ls-token'));
- this.axios.get(`${this.$config.baseURL}/verify`).then(res => {
- this.$store.commit('user', res.data.user);
- this.$store.commit('loggedIn', true);
- if (next) return next();
- return null;
- }).catch(error => {
- if (error.response && error.response.status === 520) return;
- if (error.response && error.response.status === 429) {
- setTimeout(() => {
- this.tryToLogin(next, destination);
- }, 1000);
- return next(false);
- } else {
- this.$store.commit('user', null);
- this.$store.commit('loggedIn', false);
- this.$store.commit('token', null);
- if (next && destination) return next(destination);
- if (next) return next('/');
- return null;
- }
- });
- }
- }
-};
-</script>
-
-<style lang="scss">
- @import "./styles/style.scss";
- @import "./styles/icons.min.css";
-</style>
diff --git a/src/site/public/images/logo.png b/src/site/assets/images/logo.png
index eedc960..eedc960 100644
--- a/src/site/public/images/logo.png
+++ b/src/site/assets/images/logo.png
Binary files differ
diff --git a/src/site/styles/_colors.scss b/src/site/assets/styles/_colors.scss
index e03c747..e03c747 100644
--- a/src/site/styles/_colors.scss
+++ b/src/site/assets/styles/_colors.scss
diff --git a/src/site/styles/dropzone.scss b/src/site/assets/styles/dropzone.scss
index 2d68659..2d68659 100644
--- a/src/site/styles/dropzone.scss
+++ b/src/site/assets/styles/dropzone.scss
diff --git a/src/site/styles/icons.min.css b/src/site/assets/styles/icons.min.css
index c550e0a..c550e0a 100644
--- a/src/site/styles/icons.min.css
+++ b/src/site/assets/styles/icons.min.css
diff --git a/src/site/styles/style.scss b/src/site/assets/styles/style.scss
index 8394ec6..8394ec6 100644
--- a/src/site/styles/style.scss
+++ b/src/site/assets/styles/style.scss
diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue
index 50e626b..615d68f 100644
--- a/src/site/components/grid/Grid.vue
+++ b/src/site/components/grid/Grid.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/_colors.scss';
+ @import '~/assets/styles/_colors.scss';
.item-move {
transition: all .25s cubic-bezier(.55,0,.1,1);
-webkit-transition: all .25s cubic-bezier(.55,0,.1,1);
@@ -99,25 +99,25 @@
position="is-top">
<a :href="`${item.url}`"
target="_blank">
- <i class="icon-web-code"/>
+ <i class="icon-web-code" />
</a>
</b-tooltip>
<b-tooltip label="Albums"
position="is-top">
<a @click="manageAlbums(item)">
- <i class="icon-interface-window"/>
+ <i class="icon-interface-window" />
</a>
</b-tooltip>
<b-tooltip label="Tags"
position="is-top">
<a @click="manageTags(item)">
- <i class="icon-ecommerce-tag-c"/>
+ <i class="icon-ecommerce-tag-c" />
</a>
</b-tooltip>
<b-tooltip label="Delete"
position="is-top">
<a @click="deleteFile(item, index)">
- <i class="icon-editorial-trash-a-l"/>
+ <i class="icon-editorial-trash-a-l" />
</a>
</b-tooltip>
</div>
@@ -155,6 +155,11 @@ export default {
data() {
return { showWaterfall: true };
},
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
methods: {
deleteFile(file, index) {
this.$dialog.confirm({
@@ -165,7 +170,7 @@ export default {
hasIcon: true,
onConfirm: async () => {
try {
- const response = await this.axios.delete(`${this.$config.baseURL}/file/${file.id}`);
+ const response = await this.axios.delete(`${this.config.baseURL}/file/${file.id}`);
this.showWaterfall = false;
this.files.splice(index, 1);
this.$nextTick(() => {
diff --git a/src/site/components/grid/waterfall/Waterfall.vue b/src/site/components/grid/waterfall/Waterfall.vue
index 9827075..8631ea5 100644
--- a/src/site/components/grid/waterfall/Waterfall.vue
+++ b/src/site/components/grid/waterfall/Waterfall.vue
@@ -5,20 +5,19 @@
</style>
<template>
<div class="waterfall">
- <slot/>
+ <slot />
</div>
</template>
<script>
// import {quickSort, getMinIndex, _, sum} from './util'
const quickSort = (arr, type) => {
- let left = [];
- let right = [];
- let povis;
+ const left = [];
+ const right = [];
if (arr.length <= 1) {
return arr;
}
- povis = arr[0];
+ const povis = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i][type] < povis[type]) {
left.push(arr[i]);
diff --git a/src/site/components/grid/waterfall/WaterfallItem.vue b/src/site/components/grid/waterfall/WaterfallItem.vue
index 597cca6..a02ea1f 100644
--- a/src/site/components/grid/waterfall/WaterfallItem.vue
+++ b/src/site/components/grid/waterfall/WaterfallItem.vue
@@ -5,7 +5,7 @@
</style>
<template>
<div class="waterfall-item">
- <slot/>
+ <slot />
</div>
</template>
<script>
diff --git a/src/site/components/grid/waterfall/old/waterfall-slot.vue b/src/site/components/grid/waterfall/old/waterfall-slot.vue
deleted file mode 100644
index 07ca268..0000000
--- a/src/site/components/grid/waterfall/old/waterfall-slot.vue
+++ /dev/null
@@ -1,76 +0,0 @@
-<template>
- <div class="vue-waterfall-slot" v-show="isShow">
- <slot></slot>
- </div>
-</template>
-
-<style>
-.vue-waterfall-slot {
- position: absolute;
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-</style>
-
-<script>
-
-export default {
- data: () => ({
- isShow: false
- }),
- props: {
- width: {
- required: true,
- validator: (val) => val >= 0
- },
- height: {
- required: true,
- validator: (val) => val >= 0
- },
- order: {
- default: 0
- },
- moveClass: {
- default: ''
- }
- },
- methods: {
- notify () {
- this.$parent.$emit('reflow', this)
- },
- getMeta () {
- return {
- vm: this,
- node: this.$el,
- order: this.order,
- width: this.width,
- height: this.height,
- moveClass: this.moveClass
- }
- }
- },
- created () {
- this.rect = {
- top: 0,
- left: 0,
- width: 0,
- height: 0
- }
- this.$watch(() => (
- this.width,
- this.height
- ), this.notify)
- },
- mounted () {
- this.$parent.$once('reflowed', () => {
- this.isShow = true
- })
- this.notify()
- },
- destroyed () {
- this.notify()
- }
-}
-
-</script>
diff --git a/src/site/components/grid/waterfall/old/waterfall.vue b/src/site/components/grid/waterfall/old/waterfall.vue
deleted file mode 100644
index 84e3c98..0000000
--- a/src/site/components/grid/waterfall/old/waterfall.vue
+++ /dev/null
@@ -1,442 +0,0 @@
-<template>
- <div class="vue-waterfall" :style="style">
- <slot></slot>
- </div>
-</template>
-
-<style>
-.vue-waterfall {
- position: relative;
- /*overflow: hidden; cause clientWidth = 0 in IE if height not bigger than 0 */
-}
-</style>
-
-<script>
-
-const MOVE_CLASS_PROP = '_wfMoveClass'
-
-export default {
- props: {
- autoResize: {
- default: true
- },
- interval: {
- default: 200,
- validator: (val) => val >= 0
- },
- align: {
- default: 'left',
- validator: (val) => ~['left', 'right', 'center'].indexOf(val)
- },
- line: {
- default: 'v',
- validator: (val) => ~['v', 'h'].indexOf(val)
- },
- lineGap: {
- required: true,
- validator: (val) => val >= 0
- },
- minLineGap: {
- validator: (val) => val >= 0
- },
- maxLineGap: {
- validator: (val) => val >= 0
- },
- singleMaxWidth: {
- validator: (val) => val >= 0
- },
- fixedHeight: {
- default: false
- },
- grow: {
- validator: (val) => val instanceof Array
- },
- watch: {
- default: () => ({})
- }
- },
- data: () => ({
- style: {
- height: '',
- overflow: ''
- },
- token: null
- }),
- methods: {
- reflowHandler,
- autoResizeHandler,
- reflow
- },
- created () {
- this.virtualRects = []
- this.$on('reflow', () => {
- this.reflowHandler()
- })
- this.$watch(() => (
- this.align,
- this.line,
- this.lineGap,
- this.minLineGap,
- this.maxLineGap,
- this.singleMaxWidth,
- this.fixedHeight,
- this.watch
- ), this.reflowHandler)
- this.$watch('grow', this.reflowHandler)
- },
- mounted () {
- this.$watch('autoResize', this.autoResizeHandler)
- on(this.$el, getTransitionEndEvent(), tidyUpAnimations, true)
- this.autoResizeHandler(this.autoResize)
- },
- beforeDestroy () {
- this.autoResizeHandler(false)
- off(this.$el, getTransitionEndEvent(), tidyUpAnimations, true)
- }
-}
-
-function autoResizeHandler (autoResize) {
- if (autoResize === false || !this.autoResize) {
- off(window, 'resize', this.reflowHandler, false)
- } else {
- on(window, 'resize', this.reflowHandler, false)
- }
-}
-
-function tidyUpAnimations (event) {
- let node = event.target
- let moveClass = node[MOVE_CLASS_PROP]
- if (moveClass) {
- removeClass(node, moveClass)
- }
-}
-
-function reflowHandler () {
- clearTimeout(this.token)
- this.token = setTimeout(this.reflow, this.interval)
-}
-
-function reflow () {
- if (!this.$el) { return }
- let width = this.$el.clientWidth
- let metas = this.$children.map((slot) => slot.getMeta())
- metas.sort((a, b) => a.order - b.order)
- this.virtualRects = metas.map(() => ({}))
- calculate(this, metas, this.virtualRects)
- setTimeout(() => {
- if (isScrollBarVisibilityChange(this.$el, width)) {
- calculate(this, metas, this.virtualRects)
- }
- this.style.overflow = 'hidden'
- render(this.virtualRects, metas)
- this.$emit('reflowed', this)
- }, 0)
-}
-
-function isScrollBarVisibilityChange (el, lastClientWidth) {
- return lastClientWidth !== el.clientWidth
-}
-
-function calculate (vm, metas, styles) {
- let options = getOptions(vm)
- let processor = vm.line === 'h' ? horizontalLineProcessor : verticalLineProcessor
- processor.calculate(vm, options, metas, styles)
-}
-
-function getOptions (vm) {
- const maxLineGap = vm.maxLineGap ? +vm.maxLineGap : vm.lineGap
- return {
- align: ~['left', 'right', 'center'].indexOf(vm.align) ? vm.align : 'left',
- line: ~['v', 'h'].indexOf(vm.line) ? vm.line : 'v',
- lineGap: +vm.lineGap,
- minLineGap: vm.minLineGap ? +vm.minLineGap : vm.lineGap,
- maxLineGap: maxLineGap,
- singleMaxWidth: Math.max(vm.singleMaxWidth || 0, maxLineGap),
- fixedHeight: !!vm.fixedHeight,
- grow: vm.grow && vm.grow.map(val => +val)
- }
-}
-
-var verticalLineProcessor = (() => {
-
- function calculate (vm, options, metas, rects) {
- let width = vm.$el.clientWidth
- let grow = options.grow
- let strategy = grow
- ? getRowStrategyWithGrow(width, grow)
- : getRowStrategy(width, options)
- let tops = getArrayFillWith(0, strategy.count)
- metas.forEach((meta, index) => {
- let offset = tops.reduce((last, top, i) => top < tops[last] ? i : last, 0)
- let width = strategy.width[offset % strategy.count]
- let rect = rects[index]
- rect.top = tops[offset]
- rect.left = strategy.left + (offset ? sum(strategy.width.slice(0, offset)) : 0)
- rect.width = width
- rect.height = meta.height * (options.fixedHeight ? 1 : width / meta.width)
- tops[offset] = tops[offset] + rect.height
- })
- vm.style.height = Math.max.apply(Math, tops) + 'px'
- }
-
- function getRowStrategy (width, options) {
- let count = width / options.lineGap
- let slotWidth
- if (options.singleMaxWidth >= width) {
- count = 1
- slotWidth = Math.max(width, options.minLineGap)
- } else {
- let maxContentWidth = options.maxLineGap * ~~count
- let minGreedyContentWidth = options.minLineGap * ~~(count + 1)
- let canFit = maxContentWidth >= width
- let canFitGreedy = minGreedyContentWidth <= width
- if (canFit && canFitGreedy) {
- count = Math.round(count)
- slotWidth = width / count
- } else if (canFit) {
- count = ~~count
- slotWidth = width / count
- } else if (canFitGreedy) {
- count = ~~(count + 1)
- slotWidth = width / count
- } else {
- count = ~~count
- slotWidth = options.maxLineGap
- }
- if (count === 1) {
- slotWidth = Math.min(width, options.singleMaxWidth)
- slotWidth = Math.max(slotWidth, options.minLineGap)
- }
- }
- return {
- width: getArrayFillWith(slotWidth, count),
- count: count,
- left: getLeft(width, slotWidth * count, options.align)
- }
- }
-
- function getRowStrategyWithGrow (width, grow) {
- let total = sum(grow)
- return {
- width: grow.map(val => width * val / total),
- count: grow.length,
- left: 0
- }
- }
-
- return {
- calculate
- }
-
-})()
-
-var horizontalLineProcessor = (() => {
-
- function calculate (vm, options, metas, rects) {
- let width = vm.$el.clientWidth
- let total = metas.length
- let top = 0
- let offset = 0
- while (offset < total) {
- let strategy = getRowStrategy(width, options, metas, offset)
- for (let i = 0, left = 0, meta, rect; i < strategy.count; i++) {
- meta = metas[offset + i]
- rect = rects[offset + i]
- rect.top = top
- rect.left = strategy.left + left
- rect.width = meta.width * strategy.height / meta.height
- rect.height = strategy.height
- left += rect.width
- }
- offset += strategy.count
- top += strategy.height
- }
- vm.style.height = top + 'px'
- }
-
- function getRowStrategy (width, options, metas, offset) {
- let greedyCount = getGreedyCount(width, options.lineGap, metas, offset)
- let lazyCount = Math.max(greedyCount - 1, 1)
- let greedySize = getContentSize(width, options, metas, offset, greedyCount)
- let lazySize = getContentSize(width, options, metas, offset, lazyCount)
- let finalSize = chooseFinalSize(lazySize, greedySize, width)
- let height = finalSize.height
- let fitContentWidth = finalSize.width
- if (finalSize.count === 1) {
- fitContentWidth = Math.min(options.singleMaxWidth, width)
- height = metas[offset].height * fitContentWidth / metas[offset].width
- }
- return {
- left: getLeft(width, fitContentWidth, options.align),
- count: finalSize.count,
- height: height
- }
- }
-
- function getGreedyCount (rowWidth, rowHeight, metas, offset) {
- let count = 0
- for (let i = offset, width = 0; i < metas.length && width <= rowWidth; i++) {
- width += metas[i].width * rowHeight / metas[i].height
- count++
- }
- return count
- }
-
- function getContentSize (rowWidth, options, metas, offset, count) {
- let originWidth = 0
- for (let i = count - 1; i >= 0; i--) {
- let meta = metas[offset + i]
- originWidth += meta.width * options.lineGap / meta.height
- }
- let fitHeight = options.lineGap * rowWidth / originWidth
- let canFit = (fitHeight <= options.maxLineGap && fitHeight >= options.minLineGap)
- if (canFit) {
- return {
- cost: Math.abs(options.lineGap - fitHeight),
- count: count,
- width: rowWidth,
- height: fitHeight
- }
- } else {
- let height = originWidth > rowWidth ? options.minLineGap : options.maxLineGap
- return {
- cost: Infinity,
- count: count,
- width: originWidth * height / options.lineGap,
- height: height
- }
- }
- }
-
- function chooseFinalSize (lazySize, greedySize, rowWidth) {
- if (lazySize.cost === Infinity && greedySize.cost === Infinity) {
- return greedySize.width < rowWidth ? greedySize : lazySize
- } else {
- return greedySize.cost >= lazySize.cost ? lazySize : greedySize
- }
- }
-
- return {
- calculate
- }
-
-})()
-
-function getLeft (width, contentWidth, align) {
- switch (align) {
- case 'right':
- return width - contentWidth
- case 'center':
- return (width - contentWidth) / 2
- default:
- return 0
- }
-}
-
-function sum (arr) {
- return arr.reduce((sum, val) => sum + val)
-}
-
-function render (rects, metas) {
- let metasNeedToMoveByTransform = metas.filter((meta) => meta.moveClass)
- let firstRects = getRects(metasNeedToMoveByTransform)
- applyRects(rects, metas)
- let lastRects = getRects(metasNeedToMoveByTransform)
- metasNeedToMoveByTransform.forEach((meta, i) => {
- meta.node[MOVE_CLASS_PROP] = meta.moveClass
- setTransform(meta.node, firstRects[i], lastRects[i])
- })
- document.body.clientWidth // forced reflow
- metasNeedToMoveByTransform.forEach((meta) => {
- addClass(meta.node, meta.moveClass)
- clearTransform(meta.node)
- })
-}
-
-function getRects (metas) {
- return metas.map((meta) => meta.vm.rect)
-}
-
-function applyRects (rects, metas) {
- rects.forEach((rect, i) => {
- let style = metas[i].node.style
- metas[i].vm.rect = rect
- for (let prop in rect) {
- style[prop] = rect[prop] + 'px'
- }
- })
-}
-
-function setTransform (node, firstRect, lastRect) {
- let dx = firstRect.left - lastRect.left
- let dy = firstRect.top - lastRect.top
- let sw = firstRect.width / lastRect.width
- let sh = firstRect.height / lastRect.height
- node.style.transform =
- node.style.WebkitTransform = `translate(${dx}px,${dy}px) scale(${sw},${sh})`
- node.style.transitionDuration = '0s'
-}
-
-function clearTransform (node) {
- node.style.transform = node.style.WebkitTransform = ''
- node.style.transitionDuration = ''
-}
-
-function getTransitionEndEvent () {
- let isWebkitTrans =
- window.ontransitionend === undefined &&
- window.onwebkittransitionend !== undefined
- let transitionEndEvent = isWebkitTrans
- ? 'webkitTransitionEnd'
- : 'transitionend'
- return transitionEndEvent
-}
-
-/**
- * util
- */
-
-function getArrayFillWith (item, count) {
- let getter = (typeof item === 'function') ? () => item() : () => item
- let arr = []
- for (let i = 0; i < count; i++) {
- arr[i] = getter()
- }
- return arr
-}
-
-function addClass (elem, name) {
- if (!hasClass(elem, name)) {
- let cur = attr(elem, 'class').trim()
- let res = (cur + ' ' + name).trim()
- attr(elem, 'class', res)
- }
-}
-
-function removeClass (elem, name) {
- let reg = new RegExp('\\s*\\b' + name + '\\b\\s*', 'g')
- let res = attr(elem, 'class').replace(reg, ' ').trim()
- attr(elem, 'class', res)
-}
-
-function hasClass (elem, name) {
- return (new RegExp('\\b' + name + '\\b')).test(attr(elem, 'class'))
-}
-
-function attr (elem, name, value) {
- if (typeof value !== 'undefined') {
- elem.setAttribute(name, value)
- } else {
- return elem.getAttribute(name) || ''
- }
-}
-
-function on (elem, type, listener, useCapture = false) {
- elem.addEventListener(type, listener, useCapture)
-}
-
-function off (elem, type, listener, useCapture = false) {
- elem.removeEventListener(type, listener, useCapture)
-}
-
-</script>
diff --git a/src/site/components/home/links/Links.vue b/src/site/components/home/links/Links.vue
index ba1e493..0a5f218 100644
--- a/src/site/components/home/links/Links.vue
+++ b/src/site/components/home/links/Links.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../../styles/_colors.scss';
+ @import '~/assets/styles/_colors.scss';
.links {
margin-bottom: 3em;
align-items: stretch;
@@ -96,5 +96,5 @@
</div>
</template>
<script>
-export default {}
+export default {};
</script>
diff --git a/src/site/components/logo/Logo.vue b/src/site/components/logo/Logo.vue
index 2452b58..b083cb4 100644
--- a/src/site/components/logo/Logo.vue
+++ b/src/site/components/logo/Logo.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/_colors.scss';
+ @import '~/assets/styles/_colors.scss';
#logo {
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
@@ -50,7 +50,7 @@
<template>
<p id="logo">
- <img src="../../public/images/logo.png">
+ <img src="~/assets/images/logo.png">
</p>
</template>
diff --git a/src/site/components/navbar/Navbar.vue b/src/site/components/navbar/Navbar.vue
index 108c150..5b422ae 100644
--- a/src/site/components/navbar/Navbar.vue
+++ b/src/site/components/navbar/Navbar.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
nav.navbar {
background: transparent;
box-shadow: none;
@@ -47,7 +47,7 @@
<div class="navbar-brand">
<router-link to="/"
class="navbar-item no-active">
- <i class="icon-ecommerce-safebox"/> {{ config.serviceName }}
+ <i class="icon-ecommerce-safebox" /> {{ config.serviceName }}
</router-link>
<!--
@@ -78,12 +78,12 @@
<router-link v-if="!loggedIn"
class="navbar-item"
- to="/login"><i class="hidden"/>Login</router-link>
+ to="/login"><i class="hidden" />Login</router-link>
<router-link v-else
to="/dashboard"
class="navbar-item no-active"
- exact><i class="hidden"/>Dashboard</router-link>
+ exact><i class="hidden" />Dashboard</router-link>
</div>
</nav>
</template>
diff --git a/src/site/components/sidebar/Sidebar.vue b/src/site/components/sidebar/Sidebar.vue
index 861ebea..77d0cdd 100644
--- a/src/site/components/sidebar/Sidebar.vue
+++ b/src/site/components/sidebar/Sidebar.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
.dashboard-menu {
a {
display: block;
@@ -25,19 +25,17 @@
</style>
<template>
<div class="dashboard-menu">
- <router-link to="/"><i class="icon-ecommerce-safebox"/>lolisafe</router-link>
+ <router-link to="/"><i class="icon-ecommerce-safebox" />lolisafe</router-link>
<hr>
- <a><i class="icon-interface-cloud-upload"/>Upload files</a>
+ <a><i class="icon-interface-cloud-upload" />Upload files</a>
<hr>
- <router-link to="/dashboard"><i class="icon-com-pictures"/>Files</router-link>
- <router-link to="/dashboard/albums"><i class="icon-interface-window"/>Albums</router-link>
- <router-link to="/dashboard/tags"><i class="icon-ecommerce-tag-c"/>Tags</router-link>
+ <router-link to="/dashboard"><i class="icon-com-pictures" />Files</router-link>
+ <router-link to="/dashboard/albums"><i class="icon-interface-window" />Albums</router-link>
+ <router-link to="/dashboard/tags"><i class="icon-ecommerce-tag-c" />Tags</router-link>
<hr>
- <router-link to="/dashboard/settings"><i class="icon-setting-gear-a"/>Settings</router-link>
+ <router-link to="/dashboard/settings"><i class="icon-setting-gear-a" />Settings</router-link>
</div>
</template>
<script>
-export default {
-
-}
+export default {};
</script>
diff --git a/src/site/components/uploader/Uploader.vue b/src/site/components/uploader/Uploader.vue
index afafd55..bc157bb 100644
--- a/src/site/components/uploader/Uploader.vue
+++ b/src/site/components/uploader/Uploader.vue
@@ -8,8 +8,8 @@
expanded>
<option
v-for="album in albums"
- :value="album.id"
- :key="album.id">
+ :key="album.id"
+ :value="album.id">
{{ album.name }}
</option>
</b-select>
@@ -29,20 +29,20 @@
ref="template">
<div class="dz-preview dz-file-preview">
<div class="dz-details">
- <div class="dz-filename"><span data-dz-name/></div>
- <div class="dz-size"><span data-dz-size/></div>
+ <div class="dz-filename"><span data-dz-name /></div>
+ <div class="dz-size"><span data-dz-size /></div>
</div>
<div class="result">
<div class="copyLink">
<b-tooltip label="Copy link">
- <i class="icon-web-code"/>
+ <i class="icon-web-code" />
</b-tooltip>
</div>
<div class="openLink">
<b-tooltip label="Open file">
<a class="link"
target="_blank">
- <i class="icon-web-url"/>
+ <i class="icon-web-url" />
</a>
</b-tooltip>
</div>
@@ -51,14 +51,14 @@
<div>
<span>
<span class="error-message"
- data-dz-errormessage/>
- <i class="icon-web-warning"/>
+ data-dz-errormessage />
+ <i class="icon-web-warning" />
</span>
</div>
</div>
<div class="dz-progress">
<span class="dz-upload"
- data-dz-uploadprogress/>
+ data-dz-uploadprogress />
</div>
<!--
<div class="dz-error-message"><span data-dz-errormessage/></div>
@@ -72,7 +72,7 @@
<script>
import Dropzone from 'nuxt-dropzone';
-import '../../styles/dropzone.scss';
+import '~/assets/styles/dropzone.scss';
export default {
components: { Dropzone },
@@ -107,7 +107,7 @@ export default {
},
mounted() {
this.dropzoneOptions = {
- url: `${this.$config.baseURL}/upload`,
+ url: `${this.config.baseURL}/upload`,
autoProcessQueue: true,
addRemoveLinks: false,
parallelUploads: 5,
@@ -135,7 +135,7 @@ export default {
*/
async getAlbums() {
try {
- const response = await this.axios.get(`${this.$config.baseURL}/albums/dropdown`);
+ const response = await this.axios.get(`${this.config.baseURL}/albums/dropdown`);
this.albums = response.data.albums;
this.updateDropzoneConfig();
} catch (error) {
@@ -218,7 +218,7 @@ export default {
}
</style>
<style lang="scss">
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
.filepond--panel-root {
background: transparent;
border: 2px solid #2c3340;
diff --git a/src/site/index.html b/src/site/index.html
deleted file mode 100644
index a912ed9..0000000
--- a/src/site/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui" />
- <!--ream-head-placeholder-->
- <!--ream-styles-placeholder-->
- </head>
- <body>
- <!--ream-app-placeholder-->
- <!--ream-scripts-placeholder-->
- </body>
-</html>
diff --git a/src/site/index.js b/src/site/index.js
deleted file mode 100644
index 7a48ce2..0000000
--- a/src/site/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vue from 'vue';
-
-import VueMeta from 'vue-meta';
-import axios from 'axios';
-import VueAxios from 'vue-axios';
-import Buefy from 'buefy';
-import VueTimeago from 'vue-timeago';
-import VueLazyload from 'vue-lazyload';
-import VueAnalytics from 'vue-analytics';
-import Clipboard from 'v-clipboard';
-import VueIsYourPasswordSafe from 'vue-isyourpasswordsafe';
-
-import router from './router';
-import store from './store';
-
-const isProduction = process.env.NODE_ENV === 'production';
-
-Vue.use(VueMeta);
-Vue.use(VueLazyload);
-Vue.use(VueAnalytics, {
- id: 'UA-000000000-0',
- debug: {
- enabled: !isProduction,
- sendHitTask: isProduction
- }
-});
-Vue.use(VueIsYourPasswordSafe, {
- minLength: 6,
- maxLength: 64
-});
-Vue.use(VueAxios, axios);
-Vue.use(Buefy);
-Vue.use(VueTimeago, {
- name: 'timeago',
- locale: 'en-US',
- locales: { 'en-US': require('vue-timeago/locales/en-US.json') }
-});
-Vue.use(Clipboard);
-
-Vue.axios.defaults.headers.common.Accept = 'application/vnd.lolisafe.json';
-Vue.prototype.$config = require('./config');
-
-export default () => {
- return {
- root: () => import('./App.vue'),
- router,
- store
- };
-};
diff --git a/src/site/layouts/default.vue b/src/site/layouts/default.vue
new file mode 100644
index 0000000..cd7efe5
--- /dev/null
+++ b/src/site/layouts/default.vue
@@ -0,0 +1,114 @@
+<template>
+ <nuxt />
+</template>
+<script>
+import Vue from 'vue';
+import Fuse from 'fuse.js';
+
+const protectedRoutes = [
+ '/dashboard',
+ '/dashboard/albums',
+ '/dashboard/settings'
+];
+
+export default {
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
+ mounted() {
+ console.log(`%c lolisafe %c v${this.config.version} %c`, 'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff', 'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff', 'background:transparent');
+ },
+ created() {
+ Vue.prototype.$search = (term, list, options) => {
+ return new Promise(resolve => {
+ const run = new Fuse(list, options);
+ const results = run.search(term);
+ return resolve(results);
+ });
+ };
+
+ Vue.prototype.$onPromiseError = (error, logout = false) => {
+ this.processCatch(error, logout);
+ };
+
+ Vue.prototype.$showToast = (text, error, duration) => {
+ this.showToast(text, error, duration);
+ };
+
+ Vue.prototype.$logOut = () => {
+ this.$store.commit('user', null);
+ this.$store.commit('loggedIn', false);
+ this.$store.commit('token', null);
+ };
+
+ this.$router.beforeEach((to, from, next) => {
+ if (this.$store.state.loggedIn) return next();
+ if (process.browser) {
+ if (localStorage && localStorage.getItem('lolisafe-token')) return this.tryToLogin(next, `/login?redirect=${to.path}`);
+ }
+
+ for (const match of to.matched) {
+ if (protectedRoutes.includes(match.path)) {
+ if (this.$store.state.loggedIn === false) return next(`/login?redirect=${to.path}`);
+ }
+ }
+
+ return next();
+ });
+ if (process.browser) this.tryToLogin();
+ },
+ methods: {
+ showToast(text, error, duration) {
+ this.$toast.open({
+ duration: duration || 2500,
+ message: text,
+ position: 'is-bottom',
+ type: error ? 'is-danger' : 'is-success'
+ });
+ },
+ processCatch(error, logout) {
+ if (error.response && error.response.data && error.response.data.message) {
+ this.showToast(error.response.data.message, true, 5000);
+ if (error.response.status === 429) return;
+ if (error.response.status === 502) return;
+ if (logout) {
+ this.$logOut();
+ setTimeout(() => this.$router.push('/'), 3000);
+ }
+ } else {
+ console.error(error);
+ this.showToast('Something went wrong, please check the console :(', true, 5000);
+ }
+ },
+ tryToLogin(next, destination) {
+ if (process.browser) this.$store.commit('token', localStorage.getItem('lolisafe-token'));
+ this.axios.get(`${this.config.baseURL}/verify`).then(res => {
+ this.$store.commit('user', res.data.user);
+ this.$store.commit('loggedIn', true);
+ if (next) return next();
+ return null;
+ }).catch(error => {
+ if (error.response && error.response.status === 520) return;
+ if (error.response && error.response.status === 429) {
+ setTimeout(() => {
+ this.tryToLogin(next, destination);
+ }, 1000);
+ return next(false);
+ }
+ this.$store.commit('user', null);
+ this.$store.commit('loggedIn', false);
+ this.$store.commit('token', null);
+ if (next && destination) return next(destination);
+ if (next) return next('/');
+ return null;
+ });
+ }
+ }
+};
+</script>
+<style lang="scss">
+ @import "~/assets/styles/style.scss";
+ @import "~assets/styles/icons.min.css";
+</style>
diff --git a/src/site/views/NotFound.vue b/src/site/layouts/error.vue
index 6f4d7c9..597653b 100644
--- a/src/site/views/NotFound.vue
+++ b/src/site/layouts/error.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
-@import "../styles/_colors.scss";
+@import "~/assets/styles/_colors.scss";
h2 {
font-weight: 100;
color: $textColor;
@@ -10,7 +10,7 @@
<template>
<section class="hero is-fullheight">
- <Navbar :isWhite="true"/>
+ <Navbar :isWhite="true" />
<div class="hero-body">
<div class="container">
<h2>404エラ</h2>
@@ -20,7 +20,7 @@
</template>
<script>
-import Navbar from '../components/navbar/Navbar.vue';
+import Navbar from '~/components/navbar/Navbar.vue';
export default {
components: { Navbar },
diff --git a/src/site/views/PublicAlbum.vue b/src/site/pages/a/_identifier.vue
index 6dc6cc3..624c835 100644
--- a/src/site/views/PublicAlbum.vue
+++ b/src/site/pages/a/_identifier.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
section { background-color: $backgroundLight1 !important; }
section.hero div.hero-body.align-top {
@@ -14,7 +14,7 @@
}
</style>
<style lang="scss">
- @import '../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
</style>
<template>
@@ -49,24 +49,25 @@
</template>
<script>
-import Grid from '../components/grid/Grid.vue';
-import Loading from '../components/loading/CubeShadow.vue';
+import Grid from '~/components/grid/Grid.vue';
+import Loading from '~/components/loading/CubeShadow.vue';
import axios from 'axios';
-import config from '../config.js';
+import config from '~/config.js';
export default {
components: { Grid, Loading },
- async getInitialData({ route, store }) {
+ async asyncData({ params, error }) {
try {
- const res = await axios.get(`${config.baseURL}/album/${route.params.identifier}`);
- const downloadLink = res.data.downloadEnabled ? `${config.baseURL}/album/${route.params.identifier}/zip` : null;
+ const res = await axios.get(`${config.baseURL}/album/${params.identifier}`);
+ const downloadLink = res.data.downloadEnabled ? `${config.baseURL}/album/${params.identifier}/zip` : null;
return {
name: res.data.name,
downloadEnabled: res.data.downloadEnabled,
files: res.data.files,
downloadLink
};
- } catch (error) {
+ } catch (err) {
+ /*
return {
name: null,
downloadEnabled: false,
@@ -74,13 +75,20 @@ export default {
downloadLink: null,
error: error.response.status
};
+ */
+ error({ statusCode: 404, message: 'Post not found' });
}
},
data() {
return {};
},
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
metaInfo() {
- if (!this.files) {
+ if (this.files) {
return {
title: `${this.name ? this.name : ''}`,
meta: [
@@ -98,31 +106,31 @@ export default {
{ vmid: 'og:image:secure_url', property: 'og:image:secure_url', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` }
]
};
- } else {
- return {
- title: `${this.name ? this.name : ''}`,
- meta: [
- { vmid: 'theme-color', name: 'theme-color', content: '#30a9ed' },
- { vmid: 'twitter:card', name: 'twitter:card', content: 'summary' },
- { vmid: 'twitter:title', name: 'twitter:title', content: 'lolisafe' },
- { vmid: 'twitter:description', name: 'twitter:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
- { vmid: 'og:url', property: 'og:url', content: `${config.URL}/a/${this.$route.params.identifier}` },
- { vmid: 'og:title', property: 'og:title', content: 'lolisafe' },
- { vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' }
- ]
- };
}
+ return {
+ title: `${this.name ? this.name : ''}`,
+ meta: [
+ { vmid: 'theme-color', name: 'theme-color', content: '#30a9ed' },
+ { vmid: 'twitter:card', name: 'twitter:card', content: 'summary' },
+ { vmid: 'twitter:title', name: 'twitter:title', content: 'lolisafe' },
+ { vmid: 'twitter:description', name: 'twitter:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
+ { vmid: 'og:url', property: 'og:url', content: `${config.URL}/a/${this.$route.params.identifier}` },
+ { vmid: 'og:title', property: 'og:title', content: 'lolisafe' },
+ { vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' }
+ ]
+ };
},
mounted() {
+ /*
if (this.error) {
if (this.error === 404) {
this.$toast.open('Album not found', true, 3000);
setTimeout(() => this.$router.push('/404'), 3000);
return;
- } else {
- this.$toast.open(`Error code ${this.error}`, true, 3000);
}
+ this.$toast.open(`Error code ${this.error}`, true, 3000);
}
+ */
this.$ga.page({
page: `/a/${this.$route.params.identifier}`,
title: `Album | ${this.name}`,
diff --git a/src/site/views/dashboard/Albums.vue b/src/site/pages/dashboard/albums.vue
index 9dc883c..f8c0e36 100644
--- a/src/site/views/dashboard/Albums.vue
+++ b/src/site/pages/dashboard/albums.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
section { background-color: $backgroundLight1 !important; }
section.hero div.hero-body {
align-items: baseline;
@@ -118,7 +118,7 @@
div.column > h2.subtitle { padding-top: 1px; }
</style>
<style lang="scss">
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
.b-table {
.table-wrapper {
@@ -147,7 +147,7 @@
<b-input v-model="newAlbumName"
placeholder="Album name..."
type="text"
- @keyup.enter.native="createAlbum"/>
+ @keyup.enter.native="createAlbum" />
<p class="control">
<button class="button is-primary"
@click="createAlbum">Create album</button>
@@ -227,14 +227,14 @@
label="Allow download"
centered>
<b-switch v-model="props.row.enableDownload"
- @input="linkOptionsChanged(props.row)"/>
+ @input="linkOptionsChanged(props.row)" />
</b-table-column>
<b-table-column field="enabled"
label="Enabled"
centered>
<b-switch v-model="props.row.enabled"
- @input="linkOptionsChanged(props.row)"/>
+ @input="linkOptionsChanged(props.row)" />
</b-table-column>
<!--
@@ -252,7 +252,7 @@
</template>
<template slot="empty">
<div class="has-text-centered">
- <i class="icon-misc-mood-sad"/>
+ <i class="icon-misc-mood-sad" />
</div>
<div class="has-text-centered">
Nothing here
@@ -281,12 +281,10 @@
<script>
import Sidebar from '../../components/sidebar/Sidebar.vue';
-import Grid from '../../components/grid/Grid.vue';
export default {
components: {
- Sidebar,
- Grid
+ Sidebar
},
data() {
return {
@@ -313,7 +311,7 @@ export default {
methods: {
async linkOptionsChanged(link) {
try {
- const response = await this.axios.post(`${this.$config.baseURL}/album/link/edit`,
+ const response = await this.axios.post(`${this.config.baseURL}/album/link/edit`,
{
identifier: link.identifier,
enableDownload: link.enableDownload,
@@ -327,7 +325,7 @@ export default {
async createLink(album) {
album.isCreatingLink = true;
try {
- const response = await this.axios.post(`${this.$config.baseURL}/album/link/new`,
+ const response = await this.axios.post(`${this.config.baseURL}/album/link/new`,
{ albumId: album.id });
this.$toast.open(response.data.message);
album.links.push({
@@ -346,7 +344,7 @@ export default {
async createAlbum() {
if (!this.newAlbumName || this.newAlbumName === '') return;
try {
- const response = await this.axios.post(`${this.$config.baseURL}/album/new`,
+ const response = await this.axios.post(`${this.config.baseURL}/album/new`,
{ name: this.newAlbumName });
this.newAlbumName = null;
this.$toast.open(response.data.message);
@@ -358,7 +356,7 @@ export default {
},
async getAlbums() {
try {
- const response = await this.axios.get(`${this.$config.baseURL}/albums/mini`);
+ const response = await this.axios.get(`${this.config.baseURL}/albums/mini`);
for (const album of response.data.albums) {
album.isDetailsOpen = false;
}
diff --git a/src/site/views/dashboard/Uploads.vue b/src/site/pages/dashboard/index.vue
index 86b7399..0d89aaf 100644
--- a/src/site/views/dashboard/Uploads.vue
+++ b/src/site/pages/dashboard/index.vue
@@ -1,12 +1,12 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
section { background-color: $backgroundLight1 !important; }
section.hero div.hero-body {
align-items: baseline;
}
</style>
<style lang="scss">
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
</style>
@@ -25,7 +25,7 @@
<hr>
-->
<Grid v-if="files.length"
- :files="files"/>
+ :files="files" />
</div>
</div>
</div>
@@ -34,8 +34,8 @@
</template>
<script>
-import Sidebar from '../../components/sidebar/Sidebar.vue';
-import Grid from '../../components/grid/Grid.vue';
+import Sidebar from '~/components/sidebar/Sidebar.vue';
+import Grid from '~/components/grid/Grid.vue';
export default {
components: {
@@ -45,6 +45,11 @@ export default {
data() {
return { files: [] };
},
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
metaInfo() {
return { title: 'Uploads' };
},
@@ -59,7 +64,7 @@ export default {
methods: {
async getFiles() {
try {
- const response = await this.axios.get(`${this.$config.baseURL}/files`);
+ const response = await this.axios.get(`${this.config.baseURL}/files`);
this.files = response.data.files;
console.log(this.files);
} catch (error) {
diff --git a/src/site/views/dashboard/Settings.vue b/src/site/pages/dashboard/settings.vue
index 1a3ab68..d6c6189 100644
--- a/src/site/views/dashboard/Settings.vue
+++ b/src/site/pages/dashboard/settings.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
section { background-color: $backgroundLight1 !important; }
section.hero div.hero-body {
align-items: baseline;
@@ -10,7 +10,7 @@
}
</style>
<style lang="scss">
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
</style>
@@ -20,7 +20,7 @@
<div class="container">
<div class="columns">
<div class="column is-narrow">
- <Sidebar/>
+ <Sidebar />
</div>
<div class="column">
<!--
@@ -45,18 +45,11 @@
</template>
<script>
-import Sidebar from '../../components/sidebar/Sidebar.vue';
-import Grid from '../../components/grid/Grid.vue';
-// import Waterfall from '../../components/waterfall/Waterfall.vue';
-// import WaterfallItem from '../../components/waterfall/WaterfallItem.vue';
+import Sidebar from '~/components/sidebar/Sidebar.vue';
export default {
components: {
- Sidebar,
- Grid
- // Waterfall,
- // WaterfallSlot
- // WaterfallItem
+ Sidebar
},
data() {
return {
diff --git a/src/site/views/Home.vue b/src/site/pages/index.vue
index 4158f0e..7342b97 100644
--- a/src/site/views/Home.vue
+++ b/src/site/pages/index.vue
@@ -1,30 +1,29 @@
<style lang="scss" scoped>
- @import "../styles/_colors.scss";
+ @import "~/assets/styles/_colors.scss";
div.home {
color: $textColor;
- // background-color: #1e2430;
- }
- .columns {
- .column {
- &.centered {
- display: flex;
- align-items: center;
+ .columns {
+ .column {
+ &.centered {
+ display: flex;
+ align-items: center;
+ }
}
}
- }
- h4 {
- color: $textColorHighlight;
- margin-bottom: 1em;
- }
+ h4 {
+ color: $textColorHighlight;
+ margin-bottom: 1em;
+ }
- p {
- font-size: 1.25em;
- font-weight: 600;
- line-height: 1.5;
+ p {
+ font-size: 1.25em;
+ font-weight: 600;
+ line-height: 1.5;
- strong {
- color: $textColorHighlight;
+ strong {
+ color: $textColorHighlight;
+ }
}
}
</style>
@@ -32,7 +31,7 @@
<template>
<div class="home">
<section class="hero is-fullheight has-text-centered">
- <Navbar :isWhite="true"/>
+ <Navbar :isWhite="true" />
<div class="hero-body">
<div class="container">
<div class="columns">
@@ -64,10 +63,10 @@
</template>
<script>
-import Navbar from '../components/navbar/Navbar.vue';
-import Logo from '../components/logo/Logo.vue';
-import Uploader from '../components/uploader/Uploader.vue';
-import Links from '../components/home/links/Links.vue';
+import Navbar from '~/components/navbar/Navbar.vue';
+import Logo from '~/components/logo/Logo.vue';
+import Uploader from '~/components/uploader/Uploader.vue';
+import Links from '~/components/home/links/Links.vue';
export default {
name: 'Home',
diff --git a/src/site/views/Auth/Login.vue b/src/site/pages/login.vue
index 405d354..e4a1c9d 100644
--- a/src/site/views/Auth/Login.vue
+++ b/src/site/pages/login.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
</style>
<template>
@@ -20,14 +20,14 @@
<b-input v-model="username"
type="text"
placeholder="Username"
- @keyup.enter.native="login"/>
+ @keyup.enter.native="login" />
</b-field>
<b-field>
<b-input v-model="password"
type="password"
placeholder="Password"
password-reveal
- @keyup.enter.native="login"/>
+ @keyup.enter.native="login" />
</b-field>
<p class="control has-addons is-pulled-right">
@@ -70,7 +70,7 @@
</template>
<script>
-import Navbar from '../../components/navbar/Navbar.vue';
+import Navbar from '~/components/navbar/Navbar.vue';
export default {
name: 'Login',
@@ -107,7 +107,7 @@ export default {
return;
}
this.isLoading = true;
- this.axios.post(`${this.$config.baseURL}/auth/login`, {
+ this.axios.post(`${this.config.baseURL}/auth/login`, {
username: this.username,
password: this.password
}).then(res => {
diff --git a/src/site/views/Auth/Register.vue b/src/site/pages/register.vue
index 93f349f..c331f36 100644
--- a/src/site/views/Auth/Register.vue
+++ b/src/site/pages/register.vue
@@ -1,5 +1,5 @@
<style lang="scss" scoped>
- @import '../../styles/colors.scss';
+ @import '~/assets/styles/_colors.scss';
</style>
<template>
@@ -19,20 +19,20 @@
<b-field>
<b-input v-model="username"
type="text"
- placeholder="Username"/>
+ placeholder="Username" />
</b-field>
<b-field>
<b-input v-model="password"
type="password"
placeholder="Password"
- password-reveal/>
+ password-reveal />
</b-field>
<b-field>
<b-input v-model="rePassword"
type="password"
placeholder="Re-type Password"
password-reveal
- @keyup.enter.native="register"/>
+ @keyup.enter.native="register" />
</b-field>
<p class="control has-addons is-pulled-right">
@@ -50,7 +50,7 @@
</template>
<script>
-import Navbar from '../../components/navbar/Navbar.vue';
+import Navbar from '~/components/navbar/Navbar.vue';
export default {
name: 'Register',
@@ -63,6 +63,11 @@ export default {
isLoading: false
};
},
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
metaInfo() {
return { title: 'Register' };
},
@@ -81,7 +86,7 @@ export default {
return;
}
this.isLoading = true;
- this.axios.post(`${this.$config.baseURL}/auth/register`, {
+ this.axios.post(`${this.config.baseURL}/auth/register`, {
username: this.username,
password: this.password
}).then(response => {
diff --git a/src/site/plugins/buefy.js b/src/site/plugins/buefy.js
new file mode 100644
index 0000000..58bf28f
--- /dev/null
+++ b/src/site/plugins/buefy.js
@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Buefy from 'buefy';
+
+Vue.use(Buefy);
diff --git a/src/site/plugins/v-clipboard.js b/src/site/plugins/v-clipboard.js
new file mode 100644
index 0000000..f1f6b53
--- /dev/null
+++ b/src/site/plugins/v-clipboard.js
@@ -0,0 +1,4 @@
+import Vue from 'vue';
+import Clipboard from 'v-clipboard';
+
+Vue.use(Clipboard);
diff --git a/src/site/plugins/vue-analytics.js b/src/site/plugins/vue-analytics.js
new file mode 100644
index 0000000..79a216b
--- /dev/null
+++ b/src/site/plugins/vue-analytics.js
@@ -0,0 +1,12 @@
+import Vue from 'vue';
+import VueAnalytics from 'vue-analytics';
+
+const isProduction = process.env.NODE_ENV === 'production';
+
+Vue.use(VueAnalytics, {
+ id: 'UA-000000000-0',
+ debug: {
+ enabled: !isProduction,
+ sendHitTask: isProduction
+ }
+});
diff --git a/src/site/plugins/vue-axios.js b/src/site/plugins/vue-axios.js
new file mode 100644
index 0000000..41f026f
--- /dev/null
+++ b/src/site/plugins/vue-axios.js
@@ -0,0 +1,6 @@
+import Vue from 'vue';
+import axios from 'axios';
+import VueAxios from 'vue-axios';
+
+Vue.use(VueAxios, axios);
+Vue.axios.defaults.headers.common.Accept = 'application/vnd.lolisafe.json';
diff --git a/src/site/plugins/vue-isyourpasswordsafe.js b/src/site/plugins/vue-isyourpasswordsafe.js
new file mode 100644
index 0000000..6172ca0
--- /dev/null
+++ b/src/site/plugins/vue-isyourpasswordsafe.js
@@ -0,0 +1,7 @@
+import Vue from 'vue';
+import VueIsYourPasswordSafe from 'vue-isyourpasswordsafe';
+
+Vue.use(VueIsYourPasswordSafe, {
+ minLength: 6,
+ maxLength: 64
+});
diff --git a/src/site/plugins/vue-timeago.js b/src/site/plugins/vue-timeago.js
new file mode 100644
index 0000000..28f3c6d
--- /dev/null
+++ b/src/site/plugins/vue-timeago.js
@@ -0,0 +1,8 @@
+import Vue from 'vue';
+import VueTimeago from 'vue-timeago';
+
+Vue.use(VueTimeago, {
+ name: 'timeago',
+ locale: 'en-US',
+ locales: { 'en-US': require('vue-timeago/locales/en-US.json') }
+});
diff --git a/src/site/router/index.js b/src/site/router/index.js
deleted file mode 100644
index 79053b9..0000000
--- a/src/site/router/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import Vue from 'vue';
-import Router from 'vue-router';
-
-Vue.use(Router);
-
-const router = new Router({
- mode: 'history',
- routes: [
- { path: '/', component: () => import('../views/Home.vue') },
- { path: '/login', component: () => import('../views/Auth/Login.vue') },
- { path: '/register', component: () => import('../views/Auth/Register.vue') },
- { path: '/dashboard', component: () => import('../views/Dashboard/Uploads.vue') },
- { path: '/dashboard/albums', component: () => import('../views/Dashboard/Albums.vue') },
- { path: '/dashboard/settings', component: () => import('../views/Dashboard/Settings.vue') },
- { path: '/a/:identifier', component: () => import('../views/PublicAlbum.vue'), props: true },
- { path: '/404', component: () => import('../views/NotFound.vue') },
- { path: '*', component: () => import('../views/NotFound.vue') }
- ]
-});
-
-export default router;
diff --git a/src/site/store/index.js b/src/site/store/index.js
index 3977093..a681fbe 100644
--- a/src/site/store/index.js
+++ b/src/site/store/index.js
@@ -1,8 +1,6 @@
import Vue from 'vue';
import Vuex from 'vuex';
-Vue.use(Vuex);
-
const state = {
loggedIn: false,
user: {},
@@ -18,19 +16,19 @@ const mutations = {
user(state, payload) {
if (!payload) {
state.user = {};
- localStorage.removeItem('ls-user');
+ localStorage.removeItem('lolisafe-user');
return;
}
- localStorage.setItem('ls-user', JSON.stringify(payload));
+ localStorage.setItem('lolisafe-user', JSON.stringify(payload));
state.user = payload;
},
token(state, payload) {
if (!payload) {
- localStorage.removeItem('ls-token');
+ localStorage.removeItem('lolisafe-token');
state.token = null;
return;
}
- localStorage.setItem('ls-token', payload);
+ localStorage.setItem('lolisafe-token', payload);
setAuthorizationHeader(payload);
state.token = payload;
},
@@ -39,13 +37,22 @@ const mutations = {
}
};
+const actions = {
+ nuxtServerInit({ commit }, { req }) {
+ const config = require('~/config.js');
+ commit('config', config);
+ }
+};
+
const setAuthorizationHeader = payload => {
+ console.log('hihi');
Vue.axios.defaults.headers.common.Authorization = payload ? `Bearer ${payload}` : '';
};
-const store = new Vuex.Store({
+const store = () => new Vuex.Store({
state,
- mutations
+ mutations,
+ actions
});
export default store;
diff --git a/src/site/views/dashboard/Album.vue b/src/site/views/dashboard/Album.vue
deleted file mode 100644
index f067e4d..0000000
--- a/src/site/views/dashboard/Album.vue
+++ /dev/null
@@ -1,172 +0,0 @@
-<style lang="scss" scoped>
- @import '../../styles/colors.scss';
- section { background-color: $backgroundLight1 !important; }
- section.hero div.hero-body {
- align-items: baseline;
- }
-
- div.view-container {
- padding: 2rem;
- }
-
- div.album {
- display: flex;
- margin-bottom: 10px;
-
- div.thumb {
- width: 64px;
- height: 64px;
- -webkit-box-shadow: $boxShadowLight;
- box-shadow: $boxShadowLight;
- }
-
- div.info {
- margin-left: 15px;
- h4 {
- font-size: 1.5rem;
- a {
- color: $defaultTextColor;
- font-weight: 400;
- &:hover { text-decoration: underline; }
- }
- }
- span { display: block; }
- span:nth-child(3) {
- font-size: 0.9rem;
- }
- }
-
- div.latest {
- flex-grow: 1;
- justify-content: flex-end;
- display: flex;
- margin-left: 15px;
-
- div.more {
- width: 64px;
- height: 64px;
- background: white;
- display: flex;
- align-items: center;
- padding: 10px;
- text-align: center;
- a {
- line-height: 1rem;
- color: $defaultTextColor;
- &:hover { text-decoration: underline; }
- }
- }
- }
- }
-</style>
-<style lang="scss">
- @import '../../styles/colors.scss';
-</style>
-
-
-<template>
- <section class="hero is-fullheight">
- <div class="hero-body">
- <div class="container">
- <div class="columns">
- <div class="column is-narrow">
- <Sidebar/>
- </div>
- <div class="column">
-
- <h1 class="title">{{ albumName }}</h1>
- <hr>
-
- <div class="view-container">
- <div v-for="album in albums"
- :key="album.id"
- class="album">
- <div class="thumb">
- <figure class="image is-64x64 thumb">
- <img src="../../assets/images/blank.png">
- </figure>
- </div>
- <div class="info">
- <h4>
- <router-link :to="`/dashboard/albums/${album.id}`">{{ album.name }}</router-link>
- </h4>
- <span>Updated <timeago :since="album.editedAt" /></span>
- <span>{{ album.fileCount || 0 }} files</span>
- </div>
- <div class="latest">
- <div v-for="file of album.files"
- :key="file.id"
- class="thumb">
- <figure class="image is-64x64">
- <a :href="file.url"
- target="_blank">
- <img :src="file.thumbSquare">
- </a>
- </figure>
- </div>
- <div v-if="album.fileCount > 5"
- class="thumb more no-background">
- <router-link :to="`/dashboard/albums/${album.id}`">{{ album.fileCount - 5 }}+ more</router-link>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </section>
-</template>
-
-<script>
-import Sidebar from '../../components/sidebar/Sidebar.vue';
-import Grid from '../../components/grid/Grid.vue';
-
-export default {
- components: {
- Sidebar,
- Grid
- },
- data() {
- return {
- albums: [],
- newAlbumName: null
- };
- },
- metaInfo() {
- return { title: 'Uploads' };
- },
- mounted() {
- this.getAlbums();
- this.$ga.page({
- page: '/dashboard/albums',
- title: 'Albums',
- location: window.location.href
- });
- },
- methods: {
- async createAlbum() {
- if (!this.newAlbumName || this.newAlbumName === '') return;
- try {
- const response = await this.axios.post(`${this.$config.baseURL}/album/new`,
- { name: this.newAlbumName });
- this.newAlbumName = null;
- this.$toast.open(response.data.message);
- this.getAlbums();
- return;
- } catch (error) {
- this.$onPromiseError(error);
- }
- },
- async getAlbums() {
- try {
- const response = await this.axios.get(`${this.$config.baseURL}/albums/mini`);
- this.albums = response.data.albums;
- console.log(this.albums);
- } catch (error) {
- console.error(error);
- }
- }
- }
-};
-</script>
diff --git a/src/start.js b/src/start.js
index 705da12..f516ad6 100644
--- a/src/start.js
+++ b/src/start.js
@@ -1,23 +1,103 @@
const Backend = require('./api/structures/Server');
const express = require('express');
const compression = require('compression');
-const ream = require('ream');
+// const ream = require('ream');
const config = require('../config');
const path = require('path');
const log = require('./api/utils/Log');
const dev = process.env.NODE_ENV !== 'production';
const oneliner = require('one-liner');
const jetpack = require('fs-jetpack');
+// const { Nuxt, Builder } = require('nuxt-edge');
+// const nuxtConfig = require('./nuxt/nuxt.config.js');
function startProduction() {
startAPI();
- startSite();
+ // startSite();
+ // startNuxt();
}
function startAPI() {
+ writeFrontendConfig();
new Backend().start();
}
+async function startNuxt() {
+ /*
+ Make sure the frontend has enough data to prepare the service
+ */
+ writeFrontendConfig();
+
+ /*
+ Starting Nuxt's custom server powered by express
+ */
+
+ const app = express();
+
+ /*
+ Instantiate Nuxt.js
+ */
+ nuxtConfig.dev = true;
+ const nuxt = new Nuxt(nuxtConfig);
+
+ /*
+ Start the server or build it if we're on dev mode
+ */
+
+ if (nuxtConfig.dev) {
+ try {
+ await new Builder(nuxt).build();
+ } catch (error) {
+ log.error(error);
+ process.exit(1);
+ }
+ }
+
+ /*
+ Render every route with Nuxt.js
+ */
+ app.use(nuxt.render);
+
+ /*
+ Start the server and listen to the configured port
+ */
+ app.listen(config.server.ports.frontend, '127.0.0.1');
+ log.info(`> Frontend ready and listening on port ${config.server.ports.frontend}`);
+
+ /*
+ Starting Nuxt's custom server powered by express
+ */
+ /*
+ const app = express();
+ app.set('port', config.server.ports.frontend);
+
+ // Configure dev enviroment
+ nuxtConfig.dev = dev;
+
+ // Init Nuxt.js
+ const nuxt = new Nuxt(nuxtConfig);
+
+ // Build only in dev mode
+ if (nuxtConfig.dev) {
+ const builder = new Builder(nuxt);
+ await builder.build();
+ }
+
+ // Give nuxt middleware to express
+ app.use(nuxt.render);
+
+ if (config.serveFilesWithNode) {
+ app.use('/', express.static(`./${config.uploads.uploadFolder}`));
+ }
+
+ // Listen the server
+ app.listen(config.server.ports.frontend, '127.0.0.1');
+ app.on('renderer-ready', () => log.info(`> Frontend ready and listening on port ${config.server.ports.frontend}`));
+ // log.success(`> Frontend ready and listening on port ${config.server.ports.frontend}`);
+ // console.log(`Server listening on http://${host}:${port}`); // eslint-disable-line no-console
+ */
+}
+
function startSite() {
/*
Make sure the frontend has enough data to prepare the service
@@ -74,5 +154,5 @@ function writeFrontendConfig() {
const args = process.argv[2];
if (!args) startProduction();
else if (args === 'api') startAPI();
-else if (args === 'site') startSite();
+else if (args === 'site') startNuxt();
else process.exit(0);