mirror of
https://github.com/Luzifer/past3.git
synced 2024-11-14 02:32:41 +00:00
Initial version
This commit is contained in:
parent
7cf6b81c07
commit
c86cc33de5
7 changed files with 393 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
public
|
17
Makefile
Normal file
17
Makefile
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
default: generate
|
||||||
|
|
||||||
|
generate: script index-page
|
||||||
|
|
||||||
|
script: public
|
||||||
|
coffee --bare --compile --output public app.coffee
|
||||||
|
python -m jsmin public/app.js > public/app.min.js
|
||||||
|
mv public/app.min.js public/app.js
|
||||||
|
|
||||||
|
index-page: public
|
||||||
|
./generate.py > public/index.html
|
||||||
|
|
||||||
|
public:
|
||||||
|
mkdir public
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf public
|
161
app.coffee
Normal file
161
app.coffee
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
$ ->
|
||||||
|
$('#signin').modal
|
||||||
|
backdrop: 'static'
|
||||||
|
keyboard: false
|
||||||
|
|
||||||
|
AWS.config.region = window.past3_config.region
|
||||||
|
|
||||||
|
CodeMirror.modeURL = 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.22.2/mode/%N/%N.js'
|
||||||
|
|
||||||
|
window.editor = CodeMirror.fromTextArea document.getElementById('editor'),
|
||||||
|
extraKeys:
|
||||||
|
Tab: (cm) -> cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "))
|
||||||
|
lineNumbers: true
|
||||||
|
viewportMargin: 25
|
||||||
|
|
||||||
|
window.editor.setSize null, '100%'
|
||||||
|
|
||||||
|
$('#filename').bind 'input', filenameInput
|
||||||
|
|
||||||
|
$('#newFile').bind 'click', () ->
|
||||||
|
$('#filename').val('')
|
||||||
|
$('#file-url').val('n/a')
|
||||||
|
window.editor.setValue('')
|
||||||
|
$('.file-list-item').removeClass 'active'
|
||||||
|
return false
|
||||||
|
|
||||||
|
$('#saveFile').bind 'click', () ->
|
||||||
|
filename = $('#filename').val()
|
||||||
|
window.editor.save()
|
||||||
|
content = $('#editor').val()
|
||||||
|
saveFile(filename, content)
|
||||||
|
|
||||||
|
$('#file-url').bind 'click', () ->
|
||||||
|
$(this).select()
|
||||||
|
|
||||||
|
signinCallback = (authResult) ->
|
||||||
|
if authResult.Zi.id_token
|
||||||
|
|
||||||
|
AWS.config.credentials = new AWS.CognitoIdentityCredentials
|
||||||
|
IdentityPoolId: window.past3_config.identity_pool_id
|
||||||
|
Logins:
|
||||||
|
'accounts.google.com': authResult.Zi.id_token
|
||||||
|
|
||||||
|
AWS.config.credentials.get () ->
|
||||||
|
$('#signin').modal 'hide'
|
||||||
|
listFiles()
|
||||||
|
|
||||||
|
renderButton = () ->
|
||||||
|
gapi.signin2.render 'signInButton',
|
||||||
|
'scope': 'profile email',
|
||||||
|
'width': 240,
|
||||||
|
'height': 30,
|
||||||
|
'longtitle': true,
|
||||||
|
'theme': 'dark',
|
||||||
|
'onsuccess': signinCallback,
|
||||||
|
|
||||||
|
getFilePrefix = () ->
|
||||||
|
s3 = new AWS.S3()
|
||||||
|
"#{s3.config.credentials.identityId}/"
|
||||||
|
|
||||||
|
getFile = (filename) ->
|
||||||
|
s3 = new AWS.S3()
|
||||||
|
s3.getObject({
|
||||||
|
Bucket: window.past3_config.bucket
|
||||||
|
Key: getFilePrefix() + filename
|
||||||
|
}, loadFileIntoEditor)
|
||||||
|
$('#filename').val filename
|
||||||
|
$('#filename').trigger 'input'
|
||||||
|
|
||||||
|
listFiles = () ->
|
||||||
|
s3 = new AWS.S3()
|
||||||
|
s3.listObjects({
|
||||||
|
Bucket: window.past3_config.bucket
|
||||||
|
Prefix: getFilePrefix()
|
||||||
|
}, loadFileList)
|
||||||
|
|
||||||
|
saveFile = (filename, content) ->
|
||||||
|
mime = getMimeType filename
|
||||||
|
s3 = new AWS.S3()
|
||||||
|
s3.putObject({
|
||||||
|
ACL: window.past3_config.acl
|
||||||
|
Body: content
|
||||||
|
Bucket: window.past3_config.bucket
|
||||||
|
ContentType: mime.mime
|
||||||
|
Key: getFilePrefix() + filename
|
||||||
|
}, saveFileCallback)
|
||||||
|
|
||||||
|
loadFileIntoEditor = (err, data) ->
|
||||||
|
if err
|
||||||
|
error err
|
||||||
|
return
|
||||||
|
|
||||||
|
window.editor.setValue(String(data.Body))
|
||||||
|
|
||||||
|
loadFileList = (err, data) ->
|
||||||
|
if err
|
||||||
|
error err
|
||||||
|
return
|
||||||
|
|
||||||
|
$('.file-list-item').remove()
|
||||||
|
for obj in data.Contents
|
||||||
|
key = obj.Key.replace getFilePrefix(), ''
|
||||||
|
|
||||||
|
li = $("<a href='#' class='list-group-item file-list-item'><span class='badge'></span> #{key}</a>")
|
||||||
|
li.find('.badge').text formatDate(obj.LastModified)
|
||||||
|
li.data 'file', key
|
||||||
|
|
||||||
|
if key == $('#filename').val()
|
||||||
|
li.addClass 'active'
|
||||||
|
|
||||||
|
li.appendTo $('#fileList')
|
||||||
|
li.bind 'click', openFileClick
|
||||||
|
|
||||||
|
saveFileCallback = (err, data) ->
|
||||||
|
if err
|
||||||
|
error err
|
||||||
|
return
|
||||||
|
|
||||||
|
listFiles()
|
||||||
|
|
||||||
|
formatDate = (src) ->
|
||||||
|
$.format.date src, 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
|
||||||
|
setEditorMime = (filename) ->
|
||||||
|
if window.mime_detect
|
||||||
|
window.clearTimeout window.mime_detect
|
||||||
|
|
||||||
|
window.mime_detect = window.setTimeout () ->
|
||||||
|
autoMime = getMimeType filename
|
||||||
|
window.editor.setOption 'mode', autoMime.mime
|
||||||
|
CodeMirror.autoLoadMode window.editor, autoMime.mode
|
||||||
|
, 500
|
||||||
|
|
||||||
|
getMimeType = (filename) ->
|
||||||
|
name_parts = filename.split('.')
|
||||||
|
ext = name_parts[name_parts.length - 1]
|
||||||
|
|
||||||
|
mime = CodeMirror.findModeByExtension ext
|
||||||
|
|
||||||
|
if mime == undefined
|
||||||
|
mime = CodeMirror.findModeByExtension 'txt'
|
||||||
|
|
||||||
|
return mime
|
||||||
|
|
||||||
|
openFileClick = () ->
|
||||||
|
getFile $(this).data('file')
|
||||||
|
|
||||||
|
$('.file-list-item').removeClass 'active'
|
||||||
|
$(this).addClass 'active'
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
filenameInput = () ->
|
||||||
|
setEditorMime $(this).val()
|
||||||
|
|
||||||
|
$('#file-url').val window.past3_config.base_url + getFilePrefix() + $(this).val()
|
||||||
|
|
||||||
|
error = (err) ->
|
||||||
|
ed = $('#errorDisplay')
|
||||||
|
ed.find('.alert').text err
|
||||||
|
ed.show()
|
24
config.yml
Normal file
24
config.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
# Canned ACL for files stored into S3. One of "private, public-read,
|
||||||
|
# public-read-write, authenticated-read, aws-exec-read, bucket-owner-read,
|
||||||
|
# bucket-owner-full-control"
|
||||||
|
acl: public-read
|
||||||
|
|
||||||
|
# Base URL for the uploaded files, either S3 download URL or CloudFront
|
||||||
|
# distribution URL to which the file path is appended for display in the
|
||||||
|
# interface
|
||||||
|
base_url: https://s3-eu-west-1.amazonaws.com/past3/
|
||||||
|
|
||||||
|
# Name of the Bucket used to store the files in. The bucket needs to be
|
||||||
|
# whitelisted for Cognito authenticated users
|
||||||
|
bucket: past3
|
||||||
|
|
||||||
|
# Id of the Cognito federated identity pool to be used to store users in
|
||||||
|
identity_pool_id: 'eu-west-1:8605f42e-f1e2-4c71-a796-a96ed9e79930'
|
||||||
|
|
||||||
|
# AWS region of the bucket
|
||||||
|
region: eu-west-1
|
||||||
|
|
||||||
|
# Client ID of the oAuth2 application created in the Google Cloud console
|
||||||
|
google_client_id: 693734536874-s0quna7oa2msnt1up4vubi4sh3uaucud.apps.googleusercontent.com
|
11
generate.py
Executable file
11
generate.py
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
config = yaml.load(open('config.yml', 'r').read())
|
||||||
|
|
||||||
|
env = Environment(loader=FileSystemLoader('./'))
|
||||||
|
template = env.get_template('index.html')
|
||||||
|
print(template.render(config))
|
||||||
|
|
176
index.html
Normal file
176
index.html
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
||||||
|
<title>PaS(t)3 - S3 file editor</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
|
||||||
|
integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.22.2/codemirror.min.css"
|
||||||
|
integrity="sha256-B4lcRQIA/hXjqRxuZHImRuHmb0IT1kscrY9mYJ7FsMs=" crossorigin="anonymous" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#signInButton { margin: 20px 164px 0; }
|
||||||
|
.modal-body { text-align: center; }
|
||||||
|
#errorDisplay { display: none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"
|
||||||
|
integrity="sha256-3Jy/GbSLrg0o9y5Z5n1uw0qxZECH7C6OQpVBgNFYa0g="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"
|
||||||
|
integrity="sha256-g6iAfvZp+nDQ2TdTR/VVKJf3bGro4ub5fvWSWVRi2NE="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!-- Sign-In with Google -->
|
||||||
|
<meta name="google-signin-client_id" content="{{ google_client_id }}">
|
||||||
|
<!-- Configure PaS(t)3 -->
|
||||||
|
<script>
|
||||||
|
window.past3_config = {
|
||||||
|
acl: '{{ acl }}',
|
||||||
|
base_url: '{{ base_url }}',
|
||||||
|
bucket: '{{ bucket }}',
|
||||||
|
identity_pool_id: '{{ identity_pool_id }}',
|
||||||
|
region: '{{ region }}',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- NAV -->
|
||||||
|
<nav class="navbar navbar-default">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<!-- Brand and toggle get grouped for better mobile display -->
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="#">Pa<em>S(t)<strong>3</strong></em></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||||
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
</ul>
|
||||||
|
</div><!-- /.navbar-collapse -->
|
||||||
|
</div><!-- /.container-fluid -->
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="list-group" id="fileList">
|
||||||
|
<!--
|
||||||
|
<a href="#" class="list-group-item active">
|
||||||
|
<span class="badge">2006-01-02 15:04:05</span>
|
||||||
|
README.md
|
||||||
|
</a>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-addon">Filename</div>
|
||||||
|
<input type="text" class="form-control" id="filename" placeholder="untitled">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /.col-md-10 -->
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<button class="btn btn-default col-md-12" id="newFile"><i class="fa fa-file-o"></i> New File</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-addon">File-URL</div>
|
||||||
|
<input type="text" class="form-control" id="file-url" placeholder="n/a" readonly>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /.col-md-10 -->
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<button class="btn btn-success col-md-12" id="saveFile"><i class="fa fa-save"></i> Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" id="errorDisplay">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="alert alert-danger"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<textarea style="height:100%;width:100%;" id="editor"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /.col-md-9 -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" tabindex="-1" role="dialog" id="signin">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">You need to sign in!</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
To get access to your files you need to sign in with your Google Account.
|
||||||
|
</p>
|
||||||
|
<div id="signInButton"></div>
|
||||||
|
</div>
|
||||||
|
</div><!-- /.modal-content -->
|
||||||
|
</div><!-- /.modal-dialog -->
|
||||||
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
<a href="https://github.com/Luzifer/past3" class="github-corner" aria-label="View source on Github"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#70B7FD; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"
|
||||||
|
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dateFormat/1.0/jquery.dateFormat.min.js"
|
||||||
|
integrity="sha256-YVu3IT7nGTfxru7MQiv/TgOnffsbPuvXHRXuw1KzxWc=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"
|
||||||
|
integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/aws-sdk/2.7.21/aws-sdk.min.js"
|
||||||
|
integrity="sha256-hfu3qznG/BXuwrZTPWpq3eDm+eh6aao1eQMMSXgGYNg=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.22.2/codemirror.min.js"
|
||||||
|
integrity="sha256-pWigXb2fd8JQ4vx4hmK0s9m7u3z219t7tjcnmn76yd8=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.22.2/addon/mode/loadmode.min.js"
|
||||||
|
integrity="sha256-bUg7jjJLHxp1tOgz4DdCaa/G3AQTx1RXhDHaz8y9bDA=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.22.2/mode/meta.min.js"
|
||||||
|
integrity="sha256-Q7TtpA3J4bkPZpylepA+IKKsUn0lfkek+EPrddqC1Lo=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script src="https://apis.google.com/js/platform.js?onload=renderButton" async defer></script>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Jinja2==2.8
|
||||||
|
jsmin==2.2.1
|
||||||
|
PyYAML==3.12
|
Loading…
Reference in a new issue