old project, new git. welcome to echo
This commit is contained in:
2
.deployment
Normal file
2
.deployment
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[config]
|
||||||
|
SCM_DO_BUILD_DURING_DEPLOYMENT=true
|
||||||
35
.vscode/settings.json
vendored
Normal file
35
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"appService.zipIgnorePattern": [
|
||||||
|
"__pycache__{,/**}",
|
||||||
|
"*.py[cod]",
|
||||||
|
"*$py.class",
|
||||||
|
".Python{,/**}",
|
||||||
|
"build{,/**}",
|
||||||
|
"develop-eggs{,/**}",
|
||||||
|
"dist{,/**}",
|
||||||
|
"downloads{,/**}",
|
||||||
|
"eggs{,/**}",
|
||||||
|
".eggs{,/**}",
|
||||||
|
"lib{,/**}",
|
||||||
|
"lib64{,/**}",
|
||||||
|
"parts{,/**}",
|
||||||
|
"sdist{,/**}",
|
||||||
|
"var{,/**}",
|
||||||
|
"wheels{,/**}",
|
||||||
|
"share/python-wheels{,/**}",
|
||||||
|
"*.egg-info{,/**}",
|
||||||
|
".installed.cfg",
|
||||||
|
"*.egg",
|
||||||
|
"MANIFEST",
|
||||||
|
".env{,/**}",
|
||||||
|
".venv{,/**}",
|
||||||
|
"env{,/**}",
|
||||||
|
"venv{,/**}",
|
||||||
|
"ENV{,/**}",
|
||||||
|
"env.bak{,/**}",
|
||||||
|
"venv.bak{,/**}",
|
||||||
|
".vscode{,/**}"
|
||||||
|
],
|
||||||
|
"appService.defaultWebAppToDeploy": "/subscriptions/9d8af010-9453-475c-94bf-57ddced940ff/resourceGroups/theEcho_group/providers/Microsoft.Web/sites/theEcho",
|
||||||
|
"appService.deploySubpath": "."
|
||||||
|
}
|
||||||
173
app.py
Normal file
173
app.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
from flask import Flask, Blueprint, render_template, redirect, url_for, request, flash
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin, LoginManager, login_user, login_required, logout_user, current_user
|
||||||
|
import datetime
|
||||||
|
import urllib.parse
|
||||||
|
import save_azure
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
params = urllib.parse.quote_plus("Driver={ODBC Driver 18 for SQL Server};Server=tcp:theecho.database.windows.net,1433;Database=echo;Uid=echoDB;Pwd=Mariposa2502mARIPOSA2502$$2502$$;Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;")
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
app.config['SECRET_KEY'] = 'alpha-echo-testing'
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = "mssql+pyodbc:///?odbc_connect=%s" % params
|
||||||
|
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
|
||||||
|
|
||||||
|
# app.config['SECRET_KEY'] = 'echo-alpha-testing'
|
||||||
|
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
handle = db.Column(db.String(25))
|
||||||
|
email = db.Column(db.String(100), unique=True)
|
||||||
|
password = db.Column(db.String(100))
|
||||||
|
name = db.Column(db.String(1000))
|
||||||
|
pp = db.Column(db.String(1000))
|
||||||
|
echos = db.relationship('Echo', backref='user', lazy=True)
|
||||||
|
|
||||||
|
class Echo(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
handle = db.Column(db.String(25))
|
||||||
|
name = db.Column(db.String(1000))
|
||||||
|
echo = db.Column(db.String(2000))
|
||||||
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
replies = db.relationship('Reply', backref='echo', lazy=True)
|
||||||
|
|
||||||
|
class Reply(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
handle = db.Column(db.String(25))
|
||||||
|
name = db.Column(db.String(1000))
|
||||||
|
reply = db.Column(db.String(2000))
|
||||||
|
date = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
|
||||||
|
echo_id = db.Column(db.Integer, db.ForeignKey('echo.id'), nullable=False)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
login_manager = LoginManager()
|
||||||
|
login_manager.login_view = 'login'
|
||||||
|
login_manager.init_app(app)
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(user_id):
|
||||||
|
# since the user_id is just the primary key of our user table, use it in the query for the user
|
||||||
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return render_template('index.html', echos=Echo.query.all(), replies=Reply.query.all())
|
||||||
|
|
||||||
|
@app.route('/profile')
|
||||||
|
@login_required
|
||||||
|
def profile():
|
||||||
|
return render_template('profile.html', name=current_user.name, echos=current_user.echos)
|
||||||
|
|
||||||
|
@app.route('/echo', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def echo():
|
||||||
|
echo = request.form.get('echo')
|
||||||
|
|
||||||
|
db.session.add(Echo(echo=echo, user_id=current_user.id, handle=current_user.handle, name=current_user.name))
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('profile'))
|
||||||
|
|
||||||
|
@app.route('/reply', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def reply():
|
||||||
|
reply = request.form.get('reply')
|
||||||
|
echo_id = request.form.get('echo_id')
|
||||||
|
|
||||||
|
db.session.add(Reply(reply=reply, echo_id=echo_id, handle=current_user.handle, name=current_user.name))
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/login')
|
||||||
|
def login():
|
||||||
|
return render_template('login.html')
|
||||||
|
|
||||||
|
@app.route('/login', methods=['POST'])
|
||||||
|
def login_post():
|
||||||
|
# login code goes here
|
||||||
|
email = request.form.get('email')
|
||||||
|
password = request.form.get('password')
|
||||||
|
remember = True if request.form.get('remember') else False
|
||||||
|
|
||||||
|
user = User.query.filter_by(email=email).first()
|
||||||
|
|
||||||
|
# check if the user actually exists
|
||||||
|
# take the user-supplied password, hash it, and compare it to the hashed password in the database
|
||||||
|
if not user or not (user.password, password):
|
||||||
|
flash('Please check your login details and try again.')
|
||||||
|
return redirect(url_for('login')) # if the user doesn't exist or password is wrong, reload the page
|
||||||
|
login_user(user, remember=remember)
|
||||||
|
return redirect(url_for('profile'))
|
||||||
|
|
||||||
|
@app.route('/signup')
|
||||||
|
def signup():
|
||||||
|
return render_template('signup.html')
|
||||||
|
|
||||||
|
@app.route('/signup', methods=['POST'])
|
||||||
|
def signup_post():
|
||||||
|
# code to validate and add user to database goes here
|
||||||
|
handle = request.form.get('handle')
|
||||||
|
email = request.form.get('email')
|
||||||
|
name = request.form.get('name')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
# email = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
|
||||||
|
# handle = User.query.filter_by(handle=handle).first() # if this returns a user, then the handle already exists in database
|
||||||
|
|
||||||
|
if User.query.filter_by(email=email).first() or User.query.filter_by(handle=handle).first(): # if a user is found, we want to redirect back to signup page so user can try again
|
||||||
|
flash('Email address already exists')
|
||||||
|
return redirect(url_for('signup'))
|
||||||
|
|
||||||
|
# create a new user with the form data. Hash the password so the plaintext version isn't saved.
|
||||||
|
new_user = User(handle=handle, email=email, name=name, password=password)
|
||||||
|
|
||||||
|
# add the new user to the database
|
||||||
|
db.session.add(new_user)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/<handle>')
|
||||||
|
@login_required
|
||||||
|
def user(handle):
|
||||||
|
return render_template('user.html', handle=handle , echos=Echo.query.filter_by(handle=handle).all())
|
||||||
|
|
||||||
|
# upload a profile picture
|
||||||
|
@app.route('/pp', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def pp():
|
||||||
|
pp = request.files['file']
|
||||||
|
dir = 'static/pp/'
|
||||||
|
if pp.filename.endswith('.jpg'):
|
||||||
|
pp.save(dir + current_user.handle + '.jpg')
|
||||||
|
filename = current_user.handle + '.jpg'
|
||||||
|
elif pp.filename.endswith('.png'):
|
||||||
|
pp.save(dir + current_user.handle + '.png')
|
||||||
|
filename = current_user.handle + '.png'
|
||||||
|
path = dir + filename
|
||||||
|
print(path)
|
||||||
|
db.session.add(User(pp=save_azure.save(path, filename), id=current_user.id))
|
||||||
|
|
||||||
|
return redirect(url_for('profile'))
|
||||||
|
|
||||||
|
# @app.route('/<handle>/<echo_id>')
|
||||||
|
# @login_required
|
||||||
|
# def echo_page(handle, echo_id):
|
||||||
|
# return render_template('echo.html', handle=handle, echo=Echo.query.get(echo_id), replies=Reply.query.filter_by(echo_id=echo_id).all())
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
@login_required
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# app.run(debug=True)
|
||||||
|
# # app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'privkey.pem'))
|
||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Flask
|
||||||
|
Flask_Login
|
||||||
|
flask_sqlalchemy
|
||||||
|
pyodbc
|
||||||
|
azure-storage-blob
|
||||||
|
azure-identity
|
||||||
21
save_azure.py
Normal file
21
save_azure.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from azure.identity import DefaultAzureCredential
|
||||||
|
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
|
||||||
|
|
||||||
|
|
||||||
|
def save(path, filename):
|
||||||
|
account_url = "https://theecho.blob.core.windows.net"
|
||||||
|
default_credential = DefaultAzureCredential()
|
||||||
|
|
||||||
|
# Create the BlobServiceClient object
|
||||||
|
blob_service_client = BlobServiceClient(account_url, credential=default_credential)
|
||||||
|
|
||||||
|
container_name = "ppics"
|
||||||
|
|
||||||
|
# Create a blob client using the local file name as the name for the blob
|
||||||
|
blob_client = blob_service_client.get_blob_client(container=container_name, blob=filename)
|
||||||
|
|
||||||
|
# Upload the created file
|
||||||
|
with open(file=path, mode="rb") as data:
|
||||||
|
blob_client.upload_blob(data)
|
||||||
|
url = account_url + "/" + container_name + "/" + filename
|
||||||
|
return url
|
||||||
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
BIN
static/icon.png
Normal file
BIN
static/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 MiB |
BIN
static/logo.png
Normal file
BIN
static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
0
static/pp/placeholder.jpg
Normal file
0
static/pp/placeholder.jpg
Normal file
5
static/robots.txt
Normal file
5
static/robots.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /generate
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
156
templates/base.html
Normal file
156
templates/base.html
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<!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">
|
||||||
|
<meta name="description" content="How far does your echo go?">
|
||||||
|
<title>Echo</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
|
<link rel="icon" href="{{ url_for('static', filename='robots.txt') }}">
|
||||||
|
<meta property="og:title" content="Echo" />
|
||||||
|
<meta property="og:description" content="How far does your echo go?" />
|
||||||
|
<meta property="og:url" content="{{ url_for('index') }}" />
|
||||||
|
<meta property="og:image" content="{{ url_for('static', filename='icon.png') }}" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="hero is-black is-fullheight">
|
||||||
|
<div class="hero-head">
|
||||||
|
<nav class="navbar is-fixed-top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="https://echo.bbrunson.com">
|
||||||
|
<img src="/static/icon.png" width="28" height="28">
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMenuHeroA">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="navbarMenuHeroA" class="navbar-menu">
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a href="{{ url_for('index') }}" class="navbar-item">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<a href="{{ url_for('profile') }}" class="navbar-item">
|
||||||
|
Profile
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if not current_user.is_authenticated %}
|
||||||
|
<a href="{{ url_for('login') }}" class="navbar-item">
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
<div class="buttons navbar-item">
|
||||||
|
<a class="button is-info" href="{{ url_for('signup') }}">
|
||||||
|
<strong>Signup!</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- <a href="{{ url_for('signup') }}" class="navbar-item">
|
||||||
|
Sign Up
|
||||||
|
</a> -->
|
||||||
|
{% endif %}
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<a href="{{ url_for('logout') }}" class="navbar-item">
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
<!-- <div class="buttons navbar-item">
|
||||||
|
<a class="button is-info" href="{{ url_for('profile') }}">
|
||||||
|
<strong>Generate!</strong>
|
||||||
|
</a>
|
||||||
|
</div> -->
|
||||||
|
{% endif %}
|
||||||
|
{% if not current_user.is_authenticated %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||||
|
|
||||||
|
// Add a click event on each of them
|
||||||
|
$navbarBurgers.forEach( el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
|
||||||
|
// Get the target from the "data-target" attribute
|
||||||
|
const target = el.dataset.target;
|
||||||
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
el.classList.toggle('is-active');
|
||||||
|
$target.classList.toggle('is-active');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Functions to open and close a modal
|
||||||
|
function openModal($el) {
|
||||||
|
$el.classList.add('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal($el) {
|
||||||
|
$el.classList.remove('is-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAllModals() {
|
||||||
|
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||||
|
closeModal($modal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a click event on buttons to open a specific modal
|
||||||
|
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||||
|
const modal = $trigger.dataset.target;
|
||||||
|
const $target = document.getElementById(modal);
|
||||||
|
|
||||||
|
$trigger.addEventListener('click', () => {
|
||||||
|
openModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a click event on various child elements to close the parent modal
|
||||||
|
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||||
|
const $target = $close.closest('.modal');
|
||||||
|
|
||||||
|
$close.addEventListener('click', () => {
|
||||||
|
closeModal($target);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a keyboard event to close all modals
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
const e = event || window.event;
|
||||||
|
|
||||||
|
if (e.keyCode === 27) { // Escape key
|
||||||
|
closeAllModals();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
<!-- <footer class="footer">
|
||||||
|
</footer> -->
|
||||||
|
|
||||||
|
</html>
|
||||||
112
templates/index.html
Normal file
112
templates/index.html
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1 class="title">
|
||||||
|
Echo
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
How far does your voice go?
|
||||||
|
</h2>
|
||||||
|
<body>
|
||||||
|
{% for echo in echos|reverse %}
|
||||||
|
<br>
|
||||||
|
<article class="media">
|
||||||
|
<figure class="media-left">
|
||||||
|
<p class="image is-64x64">
|
||||||
|
<img src="https://bulma.io/images/placeholders/128x128.png">
|
||||||
|
</p>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<strong>{{echo.name}}</strong> <small>@{{echo.handle}}</small> <small>{{echo.date}}</small>
|
||||||
|
<br>
|
||||||
|
{{echo.echo}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav class="level is-mobile">
|
||||||
|
<div class="level-left">
|
||||||
|
<a class="level-item">
|
||||||
|
<button class="js-modal-trigger" data-target="modal-js-example">
|
||||||
|
Reply
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-retweet"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-heart"></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% for reply in replies|reverse %}
|
||||||
|
<article class="media">
|
||||||
|
<figure class="media-left">
|
||||||
|
<p class="image is-64x64">
|
||||||
|
<img src="https://bulma.io/images/placeholders/128x128.png">
|
||||||
|
</p>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<strong>{{reply.name}}</strong> <small>@{{reply.handle}}</small> <small>{{reply.date}}</small>
|
||||||
|
<br>
|
||||||
|
{{reply.reply}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav class="level is-mobile">
|
||||||
|
<div class="level-left">
|
||||||
|
<a class="level-item">
|
||||||
|
<button class="js-modal-trigger" data-target="modal-js-example">
|
||||||
|
Reply
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-retweet"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-heart"></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<!-- <button class="delete"></button> -->
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<!-- <button class="delete"></button> -->
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
<!-- <div class="columns is-variable is-multiline is-centered is-1-mobile">
|
||||||
|
<img src="/static/images/example1.jpg">
|
||||||
|
<img src="/static/images/example2.jpg">
|
||||||
|
<img src="/static/images/example3.jpg">
|
||||||
|
<img src="/static/images/example4.jpg">
|
||||||
|
</div> -->
|
||||||
|
<div id="modal-js-example" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="box">
|
||||||
|
<p>Reply</p>
|
||||||
|
<form method="POST" action="/reply">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="text" name="reply" placeholder="Reply" autofocus="">
|
||||||
|
<input type="hidden" name="echo_id" value="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Reply</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="modal-close is-large" aria-label="close"></button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
36
templates/login.html
Normal file
36
templates/login.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<h3 class="title">Login</h3>
|
||||||
|
<div class="box">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ messages[0] }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form method="POST" action="/login">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="password" name="password" placeholder="Your Password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" name="remember">
|
||||||
|
Remember me
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
94
templates/profile.html
Normal file
94
templates/profile.html
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<body>
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<h1 class="title">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="box">
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ messages[0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
Welcome, {{ name }}!<br>
|
||||||
|
<h4>Upload your profile picture:</h4>
|
||||||
|
<form action="/pp" method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file">
|
||||||
|
<input type="submit" value="Upload">
|
||||||
|
</form>
|
||||||
|
<br><br><br>
|
||||||
|
<form method="POST" action="/echo">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="text" name="echo" placeholder="Echo" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Echo</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
</h1>
|
||||||
|
{% for echo in echos|reverse %}
|
||||||
|
<br>
|
||||||
|
<article class="media">
|
||||||
|
<figure class="media-left">
|
||||||
|
<p class="image is-64x64">
|
||||||
|
<img src="https://bulma.io/images/placeholders/128x128.png">
|
||||||
|
</p>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<strong>{{echo.name}}</strong> <small>@{{echo.handle}}</small> <small>{{echo.date}}</small>
|
||||||
|
<br>
|
||||||
|
{{echo.echo}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav class="level is-mobile">
|
||||||
|
<div class="level-left">
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-reply"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-retweet"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-heart"></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<!-- <button class="delete"></button> -->
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
const $buttons = Array.prototype.slice.call(document.querySelectorAll('.button'), 0);
|
||||||
|
|
||||||
|
// Add a click event on each of them
|
||||||
|
$buttons.forEach( el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
|
||||||
|
// Get the target from the "data-target" attribute
|
||||||
|
const target = el.dataset.target;
|
||||||
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
el.classList.toggle('is-loading');
|
||||||
|
$target.classList.toggle('is-loading');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</h1>
|
||||||
|
{% endblock %}
|
||||||
43
templates/signup.html
Normal file
43
templates/signup.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<h3 class="title">Sign Up</h3>
|
||||||
|
<div class="box">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ messages[0] }}. Go to <a href="{{ url_for('login') }}">login page</a>.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<form method="POST" action="/signup">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="handle" name="handle" placeholder="Handle" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="password" name="password" placeholder="Password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
89
templates/user.html
Normal file
89
templates/user.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<body>
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<h1 class="title">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="box">
|
||||||
|
<div class="notification is-danger">
|
||||||
|
{{ messages[0] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{{ handle }}'s echos!<br><br><br>
|
||||||
|
<!-- <br>
|
||||||
|
<form method="POST" action="/echo">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<input class="input is-large" type="text" name="echo" placeholder="Echo" autofocus="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="button is-block is-info is-large is-fullwidth">Echo</button>
|
||||||
|
</form> -->
|
||||||
|
<br>
|
||||||
|
</h1>
|
||||||
|
{% for echo in echos|reverse %}
|
||||||
|
<br>
|
||||||
|
<article class="media">
|
||||||
|
<figure class="media-left">
|
||||||
|
<p class="image is-64x64">
|
||||||
|
<img src="https://bulma.io/images/placeholders/128x128.png">
|
||||||
|
</p>
|
||||||
|
</figure>
|
||||||
|
<div class="media-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<strong>{{echo.name}}</strong> <small>@{{echo.handle}}</small> <small>{{echo.date}}</small>
|
||||||
|
<br>
|
||||||
|
{{echo.echo}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<nav class="level is-mobile">
|
||||||
|
<div class="level-left">
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-reply"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-retweet"></i></span>
|
||||||
|
</a>
|
||||||
|
<a class="level-item">
|
||||||
|
<span class="icon is-small"><i class="fas fa-heart"></i></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="media-right">
|
||||||
|
<!-- <button class="delete"></button> -->
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// Get all "navbar-burger" elements
|
||||||
|
const $buttons = Array.prototype.slice.call(document.querySelectorAll('.button'), 0);
|
||||||
|
|
||||||
|
// Add a click event on each of them
|
||||||
|
$buttons.forEach( el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
|
|
||||||
|
// Get the target from the "data-target" attribute
|
||||||
|
const target = el.dataset.target;
|
||||||
|
const $target = document.getElementById(target);
|
||||||
|
|
||||||
|
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||||
|
el.classList.toggle('is-loading');
|
||||||
|
$target.classList.toggle('is-loading');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</h1>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user