diff options
| author | Pitu <[email protected]> | 2018-09-19 04:45:50 -0300 |
|---|---|---|
| committer | Pitu <[email protected]> | 2018-09-19 04:45:50 -0300 |
| commit | 430af8306b1ab17e59a6dabf8f65ab816d28695d (patch) | |
| tree | 975814e80919cc7b8c5d820080a30def32a371ea /src | |
| parent | Some adjustements to public album view (diff) | |
| download | host.fuwn.me-430af8306b1ab17e59a6dabf8f65ab816d28695d.tar.xz host.fuwn.me-430af8306b1ab17e59a6dabf8f65ab816d28695d.zip | |
Switch to Nuxt.js
Diffstat (limited to 'src')
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 Binary files differindex eedc960..eedc960 100644 --- a/src/site/public/images/logo.png +++ b/src/site/assets/images/logo.png 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); |