mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-09 09:52:54 +01:00
feature (download): Add a way to download a file in the IDE + fix - #34
This commit is contained in:
parent
76172f0239
commit
3e2714fb33
14 changed files with 208 additions and 98 deletions
|
|
@ -92,9 +92,11 @@ const Logout = (props) => {
|
||||||
|
|
||||||
const Saving = (props) => {
|
const Saving = (props) => {
|
||||||
return (
|
return (
|
||||||
<NgIf className="component_saving" cond={props.needSaving === true}>
|
<ReactCSSTransitionGroup transitionName="saving_indicator" transitionLeave={true} transitionEnter={true} transitionAppear={true} transitionLeaveTimeout={200} transitionEnterTimeout={500} transitionAppearTimeout={500}>
|
||||||
*
|
<NgIf key={props.needSaving} className="component_saving" cond={props.needSaving === true}>
|
||||||
</NgIf>
|
*
|
||||||
|
</NgIf>
|
||||||
|
</ReactCSSTransitionGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
span{display: block; padding: 7px 0;}
|
> span{display: block; padding: 7px 0;}
|
||||||
div, li{
|
div, li{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,9 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.component_saving{
|
||||||
|
padding-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.component_path-element{
|
.component_path-element{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -128,4 +131,27 @@ body.touch-yes{
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
transition: all 0.2s ease-out;
|
transition: all 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.saving_indicator-leave{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.saving_indicator-leave.saving_indicator-leave-active{
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saving_indicator-enter, .saving_indicator-appear{
|
||||||
|
transform-origin: center;
|
||||||
|
animation-name: bounce;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
@keyframes bounce {
|
||||||
|
0% { transform: scale(0); }
|
||||||
|
30% { transform: scale(1.5);}
|
||||||
|
100% { transform: scale(1);}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
export function screenHeight(){
|
export function screenHeight(){
|
||||||
const $breadcrumb = document.querySelector(".breadcrumb");
|
const $breadcrumb = document.querySelector(".component_breadcrumb");
|
||||||
|
const $menubar = document.querySelector(".component_menubar");
|
||||||
let size = document.body.clientHeight;
|
let size = document.body.clientHeight;
|
||||||
if($breadcrumb){
|
if($breadcrumb){ size -= $breadcrumb.clientHeight; }
|
||||||
size -= $breadcrumb.clientHeight;
|
if($menubar){ size -= $menubar.clientHeight; }
|
||||||
}
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,15 +101,27 @@ class FileSystem{
|
||||||
cat(path){
|
cat(path){
|
||||||
const url = '/api/files/cat?path='+prepare(path);
|
const url = '/api/files/cat?path='+prepare(path);
|
||||||
return http_get(url, 'raw')
|
return http_get(url, 'raw')
|
||||||
.then((res) => cache.put(cache.FILE_CONTENT, path, {result: res}))
|
.then((res) => {
|
||||||
|
if(is_binary(res) === false) cache.put(cache.FILE_CONTENT, path, {result: res});
|
||||||
|
return Promise.resolve(res);
|
||||||
|
})
|
||||||
.catch((res) => {
|
.catch((res) => {
|
||||||
return cache.get(cache.FILE_CONTENT, path)
|
return cache.get(cache.FILE_CONTENT, path)
|
||||||
.then((_res) => {
|
.then((_res) => {
|
||||||
if(!_res || !_res.result) return Promise.reject(_res);
|
if(!_res || !_res.result) return Promise.reject(res);
|
||||||
return Promise.resolve(_res.result);
|
return Promise.resolve(_res.result);
|
||||||
})
|
})
|
||||||
.catch(() => Promise.reject(res));
|
.catch(() => Promise.reject(res));
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if(is_binary(res) === true) return Promise.reject({code: 'BINARY_FILE'});
|
||||||
|
return Promise.resolve(res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function is_binary(str){
|
||||||
|
// Reference: https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character
|
||||||
|
return /\ufffd/.test(str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
url(path){
|
url(path){
|
||||||
const url = '/api/files/cat?path='+prepare(path);
|
const url = '/api/files/cat?path='+prepare(path);
|
||||||
|
|
@ -181,7 +193,10 @@ class FileSystem{
|
||||||
function update_from(){
|
function update_from(){
|
||||||
return cache.get(cache.FILE_PATH, dirname(from), false)
|
return cache.get(cache.FILE_PATH, dirname(from), false)
|
||||||
.then((res_from) => {
|
.then((res_from) => {
|
||||||
let _file = {name: basename(from), type: /\/$/.test(from) ? 'directory' : 'file'};
|
let _file = {
|
||||||
|
name: basename(from),
|
||||||
|
type: /\/$/.test(from) ? 'directory' : 'file'
|
||||||
|
};
|
||||||
res_from.results = res_from.results.map((file) => {
|
res_from.results = res_from.results.map((file) => {
|
||||||
if(file.name === basename(from)){
|
if(file.name === basename(from)){
|
||||||
file.name = basename(to);
|
file.name = basename(to);
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,10 @@ export class ViewerPage extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
path: props.match.url.replace('/view', ''),
|
path: props.match.url.replace('/view', ''),
|
||||||
|
url: null,
|
||||||
filename: Path.basename(props.match.url.replace('/view', '')) || 'untitled.dat',
|
filename: Path.basename(props.match.url.replace('/view', '')) || 'untitled.dat',
|
||||||
opener: null,
|
opener: null,
|
||||||
data: '',
|
content: null,
|
||||||
needSaving: false,
|
needSaving: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
|
@ -36,35 +37,38 @@ export class ViewerPage extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount(){
|
componentWillMount(){
|
||||||
this.setState({loading: null}, () => {
|
const metadata = () => {
|
||||||
window.setTimeout(() => {
|
return new Promise((done, err) => {
|
||||||
if(this.state.loading === null) this.setState({loading: true});
|
let app_opener = opener(this.state.path);
|
||||||
}, 500);
|
Files.url(this.state.path).then((url) => {
|
||||||
});
|
this.setState({
|
||||||
let app = opener(this.state.path);
|
url: url,
|
||||||
if(app === 'editor'){
|
opener: app_opener
|
||||||
Files.cat(this.state.path).then((content) => {
|
}, () => done(app_opener));
|
||||||
this.setState({data: content, loading: false, opener: app});
|
}).catch(err => {
|
||||||
}).catch(err => {
|
|
||||||
if(err && err.code === 'CANCELLED'){ return; }
|
|
||||||
if(err.code === 'BINARY_FILE'){
|
|
||||||
Files.url(this.state.path).then((url) => {
|
|
||||||
this.setState({data: url, loading: false, opener: 'download'});
|
|
||||||
}).catch(err => {
|
|
||||||
notify.send(err, 'error');
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
notify.send(err, 'error');
|
notify.send(err, 'error');
|
||||||
}
|
err(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}else{
|
};
|
||||||
Files.url(this.state.path).then((url) => {
|
const data_fetch = (app) => {
|
||||||
this.setState({data: url, loading: false, opener: app});
|
if(app === 'editor'){
|
||||||
}).catch(err => {
|
Files.cat(this.state.path).then((content) => {
|
||||||
if(err && err.code === 'CANCELLED'){ return; }
|
this.setState({content: content || "", loading: false});
|
||||||
notify.send(err, 'error');
|
}).catch(err => {
|
||||||
});
|
if(err && err.code === 'CANCELLED'){ return; }
|
||||||
}
|
if(err.code === 'BINARY_FILE'){
|
||||||
|
this.setState({opener: 'download', loading: false});
|
||||||
|
}else{
|
||||||
|
notify.send(err, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
this.setState({loading: false});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return metadata()
|
||||||
|
.then(data_fetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
@ -79,15 +83,16 @@ export class ViewerPage extends React.Component {
|
||||||
|
|
||||||
save(file){
|
save(file){
|
||||||
this.setState({isSaving: true});
|
this.setState({isSaving: true});
|
||||||
Files.save(this.state.path, file)
|
return Files.save(this.state.path, file)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setState({isSaving: false});
|
this.setState({isSaving: false, needSaving: false});
|
||||||
this.setState({needSaving: false});
|
return Promise.resolve();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if(err && err.code === 'CANCELLED'){ return; }
|
if(err && err.code === 'CANCELLED'){ return; }
|
||||||
this.setState({isSaving: false});
|
this.setState({isSaving: false});
|
||||||
notify.send(err, 'error');
|
notify.send(err, 'error');
|
||||||
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,23 +121,24 @@ export class ViewerPage extends React.Component {
|
||||||
<IDE needSaving={this.needSaving.bind(this)}
|
<IDE needSaving={this.needSaving.bind(this)}
|
||||||
isSaving={this.state.isSaving}
|
isSaving={this.state.isSaving}
|
||||||
onSave={this.save.bind(this)}
|
onSave={this.save.bind(this)}
|
||||||
content={this.state.data || ''}
|
content={this.state.content}
|
||||||
|
url={this.state.url}
|
||||||
filename={this.state.filename}/>
|
filename={this.state.filename}/>
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.opener === 'image'} style={style}>
|
<NgIf cond={this.state.opener === 'image'} style={style}>
|
||||||
<ImageViewer data={this.state.data} filename={this.state.filename} />
|
<ImageViewer data={this.state.url} filename={this.state.filename} />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.opener === 'pdf'} style={style}>
|
<NgIf cond={this.state.opener === 'pdf'} style={style}>
|
||||||
<PDFViewer data={this.state.data} filename={this.state.filename} />
|
<PDFViewer data={this.state.url} filename={this.state.filename} />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.opener === 'video'} style={style}>
|
<NgIf cond={this.state.opener === 'video'} style={style}>
|
||||||
<VideoPlayer data={this.state.data} filename={this.state.filename} />
|
<VideoPlayer data={this.state.url} filename={this.state.filename} />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.opener === 'audio'} style={style}>
|
<NgIf cond={this.state.opener === 'audio'} style={style}>
|
||||||
<AudioPlayer data={this.state.data} filename={this.state.filename} />
|
<AudioPlayer data={this.state.url} filename={this.state.filename} />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.opener === 'download'} style={style}>
|
<NgIf cond={this.state.opener === 'download'} style={style}>
|
||||||
<FileDownloader data={this.state.data} filename={this.state.filename} />
|
<FileDownloader data={this.state.url} filename={this.state.filename} />
|
||||||
</NgIf>
|
</NgIf>
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.state.loading === true}>
|
<NgIf cond={this.state.loading === true}>
|
||||||
|
|
|
||||||
|
|
@ -74,13 +74,6 @@ export class Editor extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
CodeMirror.commands.save = () => {
|
CodeMirror.commands.save = () => {
|
||||||
let elt = editor.getWrapperElement();
|
|
||||||
elt.style.background = "rgba(0,0,0,0.1)";
|
|
||||||
elt.style.transition = "";
|
|
||||||
window.setTimeout(function() {
|
|
||||||
elt.style.transition = "background 0.5s ease-out";
|
|
||||||
elt.style.background = "";
|
|
||||||
}, 200);
|
|
||||||
this.props.onSave && this.props.onSave();
|
this.props.onSave && this.props.onSave();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
|
|
||||||
import { NgIf, Fab, Icon } from '../../components/';
|
import { NgIf, Fab, Icon } from '../../components/';
|
||||||
import { Editor } from './editor';
|
import { Editor } from './editor';
|
||||||
|
import { MenuBar } from './menubar';
|
||||||
|
|
||||||
import './ide.scss';
|
import './ide.scss';
|
||||||
|
|
||||||
|
|
@ -10,13 +11,14 @@ export class IDE extends React.Component {
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
contentToSave: props.content
|
contentToSave: props.content,
|
||||||
|
needSaving: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentUpdate(text){
|
onContentUpdate(text){
|
||||||
this.props.needSaving(true);
|
this.props.needSaving(true);
|
||||||
this.setState({contentToSave: text});
|
this.setState({contentToSave: text, needSaving: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
save(){
|
save(){
|
||||||
|
|
@ -28,22 +30,26 @@ export class IDE extends React.Component {
|
||||||
// https://stackoverflow.com/questions/33821631/alternative-for-file-constructor-for-safari
|
// https://stackoverflow.com/questions/33821631/alternative-for-file-constructor-for-safari
|
||||||
file = blob;
|
file = blob;
|
||||||
}
|
}
|
||||||
this.props.onSave(file);
|
this.props.onSave(file)
|
||||||
|
.then(() => this.setState({needSaving: false}));
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
return (
|
return (
|
||||||
<div style={{height: '100%'}}>
|
<div style={{height: '100%'}}>
|
||||||
<Editor onSave={this.save.bind(this)} filename={this.props.filename} content={this.props.content} onChange={this.onContentUpdate.bind(this)} />
|
<MenuBar title={this.props.filename} download={this.props.url} />
|
||||||
|
<Editor onSave={this.save.bind(this)} filename={this.props.filename} content={this.props.content} onChange={this.onContentUpdate.bind(this)} />
|
||||||
|
|
||||||
<ReactCSSTransitionGroup transitionName="fab" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={25000}>
|
<ReactCSSTransitionGroup transitionName="fab" transitionLeave={true} transitionEnter={true} transitionAppear={true} transitionAppearTimeout="300" transitonEnterTimeout="300" transitionLeaveTimeout="200">
|
||||||
|
<NgIf key={this.state.needSaving} cond={this.state.needSaving}>
|
||||||
<NgIf cond={!this.props.isSaving}>
|
<NgIf cond={!this.props.isSaving}>
|
||||||
<Fab onClick={this.save.bind(this)}><Icon name="save" style={{height: '100%', width: '100%'}}/></Fab>
|
<Fab onClick={this.save.bind(this)}><Icon name="save" style={{height: '100%', width: '100%'}}/></Fab>
|
||||||
</NgIf>
|
</NgIf>
|
||||||
<NgIf cond={this.props.isSaving}>
|
<NgIf cond={this.props.isSaving}>
|
||||||
<Fab><Icon name="loading_white" style={{height: '100%', width: '100%'}}/></Fab>
|
<Fab><Icon name="loading_white" style={{height: '100%', width: '100%'}}/></Fab>
|
||||||
</NgIf>
|
</NgIf>
|
||||||
</ReactCSSTransitionGroup>
|
</NgIf>
|
||||||
|
</ReactCSSTransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
.fab-appear{
|
.fab-appear, .fab-enter{
|
||||||
opacity: 0;
|
opacity: 0.5;
|
||||||
|
transform: translateX(70px);
|
||||||
}
|
}
|
||||||
.fab-appear.fab-appear-active{
|
.fab-appear.fab-appear-active, .fab-enter.fab-enter-active{
|
||||||
transition: all 0.2s ease-out;
|
transition: all 0.2s ease-out;
|
||||||
|
transition-delay: 0.1s;
|
||||||
|
transform: translateX(0px);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.fab-leave{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.fab-leave.fab-leave-active{
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,71 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||||
|
|
||||||
import { Container, NgIf, Icon } from '../../components/';
|
import { Container, NgIf, Icon } from '../../components/';
|
||||||
|
import './menubar.scss';
|
||||||
|
|
||||||
export class MenuBar extends React.Component{
|
|
||||||
|
export const MenuBar = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="component_menubar">
|
||||||
|
<Container>
|
||||||
|
<ReactCSSTransitionGroup transitionName="menubar" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={150}>
|
||||||
|
<DownloadButton link={props.download} name={props.title} />
|
||||||
|
<span style={{letterSpacing: '0.3px'}}>{props.title}</span>
|
||||||
|
</ReactCSSTransitionGroup>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DownloadButton extends React.Component {
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {loading: false, id: null}
|
this.state = {
|
||||||
|
loading: false,
|
||||||
|
id: null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onDownloadRequest(){
|
onDownloadRequest(){
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true
|
||||||
id: window.setInterval(function(){
|
});
|
||||||
if(document.cookie){
|
|
||||||
this.setState({loading: false})
|
// This my friend is a dirty hack aiming to detect when we the download effectively start
|
||||||
window.clearInterval(this.state.id);
|
// so that we can display a spinner instead of having a user clicking the download button
|
||||||
}
|
// 10 times. It works by sniffing a cookie in our session that will get destroy when
|
||||||
}.bind(this), 80)
|
// the server actually send a response
|
||||||
})
|
document.cookie = "download=yes; path=/; max-age=120;";
|
||||||
|
this.state.id = window.setInterval(() => {
|
||||||
|
if(/download=yes/.test(document.cookie) === false){
|
||||||
|
window.clearInterval(this.state.id);
|
||||||
|
this.setState({loading: false});
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(){
|
componentWillUnmount(){
|
||||||
window.clearInterval(this.state.id)
|
window.clearInterval(this.state.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
return (
|
return (
|
||||||
<div style={{background: '#313538', color: '#f1f1f1', boxShadow: 'rgba(0, 0, 0, 0.14) 2px 2px 2px 0px'}}>
|
<div style={{float: 'right', height: '1em'}}>
|
||||||
<Container style={{padding: '9px 0', textAlign: 'center', color: '#f1f1f1', fontSize: '0.9em'}}>
|
<NgIf cond={!this.state.loading} style={{display: 'inline'}}>
|
||||||
<NgIf cond={this.props.hasOwnProperty('download')} style={{float: 'right', height: '1em'}}>
|
<a href={this.props.link} download={this.props.name} onClick={this.onDownloadRequest.bind(this)}>
|
||||||
<NgIf cond={!this.state.loading} style={{display: 'inline'}}>
|
<Icon name="download" style={{width: '15px', height: '15px'}} />
|
||||||
<a href={this.props.download} download={this.props.title} onClick={this.onDownloadRequest.bind(this)}>
|
</a>
|
||||||
<Icon name="download" style={{width: '15px', height: '15px'}} />
|
</NgIf>
|
||||||
</a>
|
<NgIf cond={this.state.loading} style={{display: 'inline'}}>
|
||||||
</NgIf>
|
<Icon name="loading" style={{width: '15px', height: '15px'}} />
|
||||||
<NgIf cond={this.state.loading} style={{display: 'inline'}}>
|
</NgIf>
|
||||||
<Icon name="loading" style={{width: '15px', height: '15px'}} />
|
|
||||||
</NgIf>
|
|
||||||
</NgIf>
|
|
||||||
<span style={{letterSpacing: '0.3px'}}>{this.props.title}</span>
|
|
||||||
</Container>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DownloadButton.PropTypes = {
|
||||||
|
link: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
|
||||||
24
client/pages/viewerpage/menubar.scss
Normal file
24
client/pages/viewerpage/menubar.scss
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
.component_menubar{
|
||||||
|
background: #313538;
|
||||||
|
color: #f1f1f1;
|
||||||
|
border-bottom: 1px solid var(--color);
|
||||||
|
|
||||||
|
.component_container{
|
||||||
|
padding: 8px 0;
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.menubar-appear{
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
.menubar-appear.menubar-appear-active{
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0px);
|
||||||
|
transition: all 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ app.get('/ls', function(req, res){
|
||||||
// get a file content
|
// get a file content
|
||||||
app.get('/cat', function(req, res){
|
app.get('/cat', function(req, res){
|
||||||
let path = pathBuilder(req);
|
let path = pathBuilder(req);
|
||||||
res.cookie('download', path, { maxAge: 1000 });
|
res.clearCookie("download");
|
||||||
if(path){
|
if(path){
|
||||||
Files.cat(path, req.cookies.auth, res)
|
Files.cat(path, req.cookies.auth, res)
|
||||||
.then(function(stream){
|
.then(function(stream){
|
||||||
|
|
@ -148,5 +148,5 @@ app.get('/touch', function(req, res){
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
||||||
function pathBuilder(req){
|
function pathBuilder(req){
|
||||||
return path.join(req.cookies.auth.payload.path, decodeURIComponent(req.query.path));
|
return path.join(req.cookies.auth.payload.path || '', decodeURIComponent(req.query.path) || '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ app.post('/', function(req, res){
|
||||||
if(Buffer.byteLength(cookie, 'utf-8') > 4096){
|
if(Buffer.byteLength(cookie, 'utf-8') > 4096){
|
||||||
res.send({status: 'error', message: 'we can\'t authenticate you', })
|
res.send({status: 'error', message: 'we can\'t authenticate you', })
|
||||||
}else{
|
}else{
|
||||||
res.cookie('auth', crypto.encrypt(persist), { maxAge: 365*24*60*60*1000, httpOnly: true });
|
res.cookie('auth', crypto.encrypt(persist), { maxAge: 365*24*60*60*1000, httpOnly: true, path: "/api/" });
|
||||||
res.send({status: 'ok'});
|
res.send({status: 'ok'});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -43,7 +43,11 @@ app.post('/', function(req, res){
|
||||||
});
|
});
|
||||||
|
|
||||||
app.delete('/', function(req, res){
|
app.delete('/', function(req, res){
|
||||||
res.clearCookie("auth");
|
res.clearCookie("auth", {path: "/api/"});
|
||||||
|
|
||||||
|
// TODO in May 2019: remove the line below which was inserted to mitigate a cookie migration issue.
|
||||||
|
res.clearCookie("auth"); // the issue was a change in the cookie path which would have make
|
||||||
|
// impossible for an existing user to logout
|
||||||
res.send({status: 'ok'})
|
res.send({status: 'ok'})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ var app = require('./bootstrap'),
|
||||||
sessionRouter = require('./ctrl/session');
|
sessionRouter = require('./ctrl/session');
|
||||||
|
|
||||||
|
|
||||||
app.get('/ping', function(req, res){ res.send('pong')})
|
app.get('/api/ping', function(req, res){ res.send('pong')})
|
||||||
app.use('/api/files', filesRouter)
|
app.use('/api/files', filesRouter)
|
||||||
app.use('/api/session', sessionRouter);
|
app.use('/api/session', sessionRouter);
|
||||||
app.use('/', express.static(__dirname + '/public/'))
|
app.use('/', express.static(__dirname + '/public/'))
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ function smartCacheStrategy(request){
|
||||||
.catch(function(err){
|
.catch(function(err){
|
||||||
return fetchAndCache(request);
|
return fetchAndCache(request);
|
||||||
});
|
});
|
||||||
});
|
}).catch(() => return request);
|
||||||
|
|
||||||
|
|
||||||
function fetchAndCache(_request){
|
function fetchAndCache(_request){
|
||||||
|
|
@ -110,7 +110,7 @@ function smartCacheStrategy(request){
|
||||||
cache.put(_request, responseClone);
|
cache.put(_request, responseClone);
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
});
|
}).catch(() => return _request);
|
||||||
}
|
}
|
||||||
function nil(e){}
|
function nil(e){}
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +128,7 @@ function networkFirstStrategy(request){
|
||||||
network(request.clone && request.clone() || request)
|
network(request.clone && request.clone() || request)
|
||||||
.then(done)
|
.then(done)
|
||||||
.catch(error);
|
.catch(error);
|
||||||
});
|
}).catch(() => return request);
|
||||||
|
|
||||||
function network(request){
|
function network(request){
|
||||||
return fetch(request)
|
return fetch(request)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue