mirror of
https://github.com/Luzifer/past3.git
synced 2024-12-22 20:31:16 +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