styling and template refactoring

master
mknurs 2022-08-12 19:51:55 +02:00
parent 4e2337540c
commit 2d90e979a1
27 changed files with 654 additions and 540 deletions

4
app.py
View File

@ -29,7 +29,7 @@ def create_app(test_config=None):
session.rollback()
return render_template('500.html'), 500
@app.route('/deck/index', methods=["GET", "POST"])
@app.route('/deck', methods=["GET", "POST"])
def deck():
dbsession = get_session()
if not 'user_id' in session:
@ -117,7 +117,7 @@ def create_app(test_config=None):
#raise Exception("Ne najdem naslednje karte")
# Prikaži obrazec
return render_template("deck/index.html", username=username, card=show_card)
return render_template("deck.html", username=username, card=show_card)
@app.route("/share_button", methods=["GET", "POST"])

View File

@ -53,7 +53,7 @@ def register():
flash(error)
return render_template('auth/register.html')
return render_template('register.html')
@bp.route('/login', methods=('GET', 'POST'))
@ -79,7 +79,7 @@ def login():
return redirect(url_for("menu.index")) #TODO ne dela
flash(error)
return render_template('auth/login.html')
return render_template('login.html')
@bp.before_app_request

View File

@ -45,7 +45,7 @@ def index():
user_settings = get_settings(user_id)
if user_settings['max_new'] == "0" and user_settings['max_due'] == "0":
flash("Error: Attempted to make deck with 0 cards.")
return render_template("menu/menu.html")
return render_template("menu.html")
deck = probabilistic_deck_generator(user_id, int(user_settings['max_new']), int(user_settings['max_due']))
cards_by_id = get_deck(deck)
@ -71,8 +71,8 @@ def index():
return render_template("settings.html", username=username, user_id=user_id, settings=settings)
elif action == "instructions":
return render_template("instructions.html", username=username, user_id=user_id)
elif action == "about":
return render_template("about.html", username=username, user_id=user_id)
# elif action == "about":
# return render_template("about.html", username=username, user_id=user_id)
return render_template("menu/menu.html", username=username, deck_status=deck_status)
return render_template("menu.html", username=username, deck_status=deck_status)

View File

@ -0,0 +1,50 @@
// select all `<input>` elements with the class `dropzone-input` and operate on each of them
document.querySelectorAll(".dropzone-input").forEach((inputEl) => {
// select the element's (closest) parent with the class name `dropzone` (the element with the actual "dropzone" functionality)
const dropZoneEl = inputEl.closest(".dropzone");
// select the element's (closest) parent with the class name `dropzone-form` (the container of all "dropzone" functionality and the form action, in our case this is the same element)
const dropZoneFormEl = inputEl.closest(".dropzone-form");
// select the info text element inside the container
const dropZoneTextEl = dropZoneFormEl.querySelector(".dropzone-txt")
// add a `click` event listener on the dropzone to trigger the input's click event
dropZoneEl.addEventListener("click", (e) => {
inputEl.click();
});
// add a `change` event listener
inputEl.addEventListener("change", (e) => {
e.preventDefault();
console.log("change")
if (inputEl.files.length) {
// change info text
dropZoneTextEl.innerHTML = "Uploading file ...";
// submit form
dropZoneFormEl.submit();
}
});
// add to class list when we drag over the element
dropZoneEl.addEventListener("dragover", (e) => {
e.preventDefault();
dropZoneEl.classList.add("dropzone--over");
});
// remove from class list when we leave or stop dragging
["dragleave", "dragend"].forEach((type) => {
dropZoneEl.addEventListener(type, (e) => {
e.preventDefault();
dropZoneEl.classList.remove("dropzone--over");
});
});
dropZoneEl.addEventListener("drop", (e) => {
e.preventDefault();
if (e.dataTransfer.files.length) {
inputEl.files = e.dataTransfer.files;
dropZoneTextEl.innerHTML = "Uploading file ...";
dropZoneFormEl.submit();
}
dropZoneEl.classList.remove("dropzone--over");
});
});

View File

@ -1,173 +1,310 @@
/* variables */
:root {
--txt-width: 60ch;
--foreground: #333333;
--background: #eeeeee;
--dimm: 0.7;
--green: #00d090;
--orange: #f08010;
--yellow: #f0c060;
--red: #f05030;
--blue: #4080a0;
}
/* normalize */
* {
box-sizing: border-box;
}
input {
background: none;
border: none;
border-bottom: solid var(--foreground) 1px;
font-size: 1rem;
}
input:focus {
outline: none;
}
button,
input[type^=submit] {
cursor: pointer;
border: none;
background: none;
font-size: 1rem;
}
.yes,
.green{
color: var(--green);
}
.maybe,
.yellow {
color: var(--yellow);
}
.no,
.orange {
color: var(--orange);
}
.delete,
.red {
color: var(--red);
}
.share,
.blue {
color: var(--blue);
}
html {
font-family: sans-serif;
background: #eee;
padding: 1rem;
}
body {
max-width: 960px;
margin: 0 auto;
background: white;
}
h1, h2, h3, h4, h5, h6 {
font-family: serif;
color: #000000;
margin: 1rem 0;
}
a {
color: #377ba8;
}
hr {
border: none;
border-top: 1px solid lightgray;
}
nav {
background: D9D2C6;
display: flex;
align-items: center;
padding: 0 0.5rem;
}
nav h1 {
flex: auto;
margin: 0;
}
nav h1 a {
text-decoration: none;
padding: 0.25rem 0.5rem;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav ul li a, nav ul li span, header .action {
display: block;
padding: 0.5rem;
}
.content {
padding: 0 1rem 1rem;
}
.content > header {
border-bottom: 1px solid lightgray;
display: flex;
align-items: flex-end;
}
.content > header h1 {
flex: auto;
margin: 1rem 0 0.25rem 0;
}
.flash {
margin: 1em 0;
padding: 1em;
background: #cae6f6;
border: 1px solid #377ba8;
}
.post > header {
display: flex;
align-items: flex-end;
font-size: 0.85em;
}
.post > header > div:first-of-type {
flex: auto;
}
.post > header h1 {
font-size: 1.5em;
margin-bottom: 0;
}
.post .about {
color: slategray;
font-style: italic;
}
.post .body {
white-space: pre-line;
}
.content:last-child {
margin-bottom: 0;
}
.content form {
margin: 1em 0;
display: flex;
flex-direction: column;
}
.content label {
font-weight: bold;
margin-bottom: 0.5em;
}
.content input, .content textarea {
margin-bottom: 1em;
}
.content textarea {
min-height: 12em;
resize: vertical;
}
input.danger {
color: #cc2f2e;
}
input[type=submit] {
align-self: start;
min-width: 10em;
}
padding: 0;
margin: 0;
font-family: sans-serif;
/* font-size: clamp(16px, calc(1vw + 1vh + 0.5vmin), 22px); */
font-size: calc(0.1rem + 1vw + 1vh + 0.5vmin);
line-height: 1.25;
color: var(--foreground);
background-color: var(--background);
}
#pdf-doc {
width: 100%;
height: calc(100vh - 332px);
}
/* layout */
body {
margin: 0;
height: 100vh;
display: grid;
grid-template-rows: auto 1fr auto;
}
.drop-zone {
max-width: 200px;
height: 200px;
padding: 25px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family: "Quicksand", sans-serif;
font-weight: 500;
font-size: 20px;
cursor: pointer;
color: #cccccc;
border: 4px dashed #009578;
border-radius: 10px;
}
.drop-zone--over {
border-style: solid;
}
.drop-zone__input {
display: none;
}
body>* {
padding: 0.5rem;
background-color: var(--background);
}
button {
border-radius: 0px;
width: fill-available;
width: -webkit-fill-available;
width: -moz-available;
height: 70px;
font-size: 130%
}
/* header */
body>header {
border-bottom: thin solid var(--foreground);
display: grid;
grid-template-columns: 1fr auto;
grid-template-areas:
"title navigation";
}
body>header>h1,
body>header>nav {
display: flex;
gap: 0.5rem;
}
body>header>h1 {
grid-area: title;
}
body>header>nav {
grid-area: navigation;
}
body>header * {
margin: 0;
font-size: 1rem;
font-weight: normal;
text-decoration: none;
color: var(--foreground);
}
/* main */
body>main {
overflow: scroll;
}
/* footer */
body>footer {
border-top: thin solid var(--foreground);
}
body>footer * {
margin: 0;
font-size: 0.5rem;
}
/* deck */
#deck article {
max-height: 100%;
display: grid;
grid-template-rows: auto 1fr auto;
}
#deck article>main {
overflow: scroll;
}
/* matches */
#matches table {
width: 100%;
}
#matches table tr {
display: grid;
grid-template-columns: 1fr 1fr auto;
border-bottom: thin solid var(--foreground);
}
#matches table tr td:last-child {
text-align: right;
}
/* upload */
#upload form {
height: 100%;
display: grid;
grid-template-rows: 1fr auto;
}
#upload input[type^=file] {
display: none;
}
.dropzone {
cursor: pointer;
}
/* settings */
/* TODO: arrows */
#settings form {
display: grid;
grid-template-columns: auto auto;
grid-gap: 0.5rem;
}
#settings form input[type=submit] {
grid-column: 1 / -1;
}
/* instructions */
#instructions article h1,
#instructions article h2,
#instructions article h3,
#instructions article h4,
#instructions article h5,
#instructions article h6 {
font-size: 1rem;
font-weight: normal;
border-bottom: thin solid var(--foreground);
}
/* login, register */
#login>main,
#register>main {
place-self: center;
}
#login form {
display: grid;
grid-template-areas:
"user-label user-input"
"pass-label pass-input"
"login login";
grid-gap: 0.5rem;
align-items: center;
}
#register form {
display: grid;
grid-template-areas:
"user-label user-input"
"pass-label pass-input"
"mail-label mail-input"
"login login";
grid-gap: 0.5rem;
align-items: center;
}
#login form>input[type=text],
#login form>input[type=password],
#register form>input[type=text],
#register form>input[type=password],
#register form>input[type=email] {
}
#login form>input:focus,
#register form>input:focus {
outline: none;
}
#login form>input[type^="submit"],
#register form>input[type^="submit"] {
grid-area: login;
place-self: center;
align-self: center;
}
/* menu */
#menu form {
display: grid;
grid-template-columns: auto;
grid-gap: 0.5rem;
justify-items: start;
}
#content>form {
display: grid;
grid-template-rows: 1fr;
}
/* flash */
.flashes {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: none;
max-width: var(--txt-width);
list-style: none;
}
.flashes li {
animation: 3s 2s forwards fadeout;
}
@keyframes fadeout {
from {
opacity: 1;
visibility: visible;
}
to {
opacity: 0;
visibility: hidden;
}
}
#buttonContainer {
display: grid;
grid-auto-flow: column;
grid-template-columns: repeat(5, 1fr);
}
#buttonContainer>button:hover {
/* background-color: black!important; */
}
button[value^="Yes"] {
background-color: var(--green);
}
button[value^="No"] {
background-color: var(--orange);
}
button[value^="Maybe"] {
background-color: var(--yellow);
}
button[value^="Delete"] {
background-color: var(--red);
}
button[value^="Share"] {
background-color: var(--blue);
}

View File

@ -1,16 +0,0 @@
{% extends 'base.html' %}
{% block header %}
<h1>About</h1>
{% endblock %}
{% block content %}
<p>Contentmatcher is a <a href="https://gia.kompot.si/about.php">General Intelligence Agency of Ljubljana</a> prototype.</p>
<p>You can find the its code <a href="https://git.kompot.si/gia/contentmatcher">here</a>.</p>
<p>Contentmatcher is hosted by <a href="https://kompot.si/wiki/doku.php">kompot</a>, a librehosters community from Ljubljana.</p>
If you have any questions, feedback or if you would like contribute, please contact us at <a href="mailto:gia@kompot.si">gia@kompot.si</a>.
{% endblock %}

View File

@ -1,15 +0,0 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}

View File

@ -1,17 +0,0 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<label for="email">Email</label>
<input type="email" name="email" id="email" required>
<input type="submit" value="Register">
</form>
{% endblock %}

View File

@ -1,24 +0,0 @@
<!doctype html>
<title>{% block title %}{% endblock %} - contentmatcher </title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<nav>
<h1><a href="{{ url_for('menu.index') }}">contentmatcher</a></h1>
<ul>
{% if username %}
<li><span>{{username}}</span>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</ul>
</nav>
<section class="content">
<header>
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>

View File

@ -0,0 +1,97 @@
{% extends 'partials/base.html' %}
{% block name %}deck{% endblock %}
{% block content %}
<main>
<article>
<header>
<span>Download: <a href="{{ card['item_location'] }}">{{ card['title'] }}</a></span>
</header>
<main id="pdf-doc">
</main>
<footer>
<form method="post">
<input type="hidden" name="card_id" value="{{ card['id'] }}">
<!-- <span id="buttonContainer" style="display: flex; justify-content: space-around;"> -->
<span id="buttonContainer">
<button type="submit" name="rate" value="Yes" accesskey="1">Yes</button> <!--ti keyi so alt+shit+key...-->
<button type="submit" name="rate" value="Maybe" accesskey="2">Maybe</button>
<button type="submit" name="rate" value="No" accesskey="3">No</button>
<button type="submit" name="rate" value="Delete" accesskey="d">Delete</button>
<button type="submit" name="share" value="Share" accesskey="s">Share</button>
</span>
</form>
</footer>
</article>
</main>
<script type="text/javascript" src="{{ url_for('static', filename='js/pdf.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/pdf.worker.js') }}"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function (){
var currPage = 1; //Pages are 1-based not 0-based
var numPages = 0;
var thePDF = null;
//This is where you start
var doc = pdfjsLib.getDocument('{{ card["item_location"] }}');
doc.promise.then(function(pdf) {
//Set PDFJS global object (so we can easily access in our page functions
thePDF = pdf;
//How many pages it has
numPages = pdf.numPages;
//Start with first page
pdf.getPage( 1 ).then( handlePages );
});
function handlePages(page)
{
//This gives us the page's dimensions at full scale
var scale = 1.5;
var viewport = page.getViewport({ scale: scale, });
//We'll create a canvas for each page to draw it on
var canvas = document.createElement( "canvas" );
canvas.style.display = "block";
var context = canvas.getContext('2d');
//Add it to the web page
document.getElementById('pdf-doc').appendChild(canvas);
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = "100%";
canvas.style.height = Math.floor(canvas.style.width * viewport.width / viewport.height);
var transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
var renderContext = {
canvasContext: context,
viewport: viewport,
transform: transform
};
//Draw it on the canvas
page.render(renderContext);
//Move to next page
currPage++;
if ( thePDF !== null && currPage <= Math.min(numPages, 10) )
{
thePDF.getPage( currPage ).then( handlePages );
}
}
});
</script>
{% endblock %}

View File

@ -1,95 +0,0 @@
{% extends 'base.html' %}
{% block header %}
{% endblock %}
{% block content %}
<article class="post">
<header>
<p>Download: <a href="{{ card['item_location'] }}">{{ card['title'] }}</a></p>
</header>
<div style="background-color: blue; height: fit-content;" id="pdf-doc"></div>
</article>
<div style="position: fixed; bottom:0%; left:0%; width:100%">
<form method="post" style="max-width: 960px; margin: auto; padding: 0 1rem 1rem;">
<input type="hidden" name="card_id" value="{{ card['id'] }}">
<span style="display: flex; justify-content: space-around;">
<button style="background-color: #736B1E;" id="rbutton" type="submit" name="rate" value="Yes" accesskey="1">Yes</button> <!--ti keyi so alt+shit+key...-->
<button style="background-color: #ED8008;" id="rbutton" type="submit" name="rate" value="Maybe" accesskey="2">Maybe</button>
<button style="background-color: #ED3f1C;" id="rbutton" type="submit" name="rate" value="No" accesskey="3">No</button>
<button style="background-color: #BF1B1B;" id="rbutton" type="submit" name="rate" value="Delete" accesskey="d">Delete</button>
<button style="background-color: #D9D2C6;" type="submit" name="share" value="share" accesskey="s">Share</button>
</span>
</form>
</div>
<script type="text/javascript" src="{{ url_for('static', filename='js/pdf.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/pdf.worker.js') }}"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function (){
var currPage = 1; //Pages are 1-based not 0-based
var numPages = 0;
var thePDF = null;
//This is where you start
var doc = pdfjsLib.getDocument('{{ card["item_location"] }}');
doc.promise.then(function(pdf) {
//Set PDFJS global object (so we can easily access in our page functions
thePDF = pdf;
//How many pages it has
numPages = pdf.numPages;
//Start with first page
pdf.getPage( 1 ).then( handlePages );
});
function handlePages(page)
{
//This gives us the page's dimensions at full scale
var scale = 1.5;
var viewport = page.getViewport({ scale: scale, });
//We'll create a canvas for each page to draw it on
var canvas = document.createElement( "canvas" );
canvas.style.display = "block";
var context = canvas.getContext('2d');
//Add it to the web page
document.getElementById('pdf-doc').appendChild(canvas);
// Support HiDPI-screens.
var outputScale = window.devicePixelRatio || 1;
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = "100%";
canvas.style.height = Math.floor(canvas.style.width * viewport.width / viewport.height);
var transform = outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
var renderContext = {
canvasContext: context,
viewport: viewport,
transform: transform
};
//Draw it on the canvas
page.render(renderContext);
//Move to next page
currPage++;
if ( thePDF !== null && currPage <= Math.min(numPages, 10) )
{
thePDF.getPage( currPage ).then( handlePages );
}
}
});
</script>
{% endblock %}

View File

@ -1,6 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<h1>Error: No cards foud</h1>
<p>No cards were found in your collection. Consider <a href="{{ url_for('upload.index') }}">uploading</a> some.</p>
{% endblock %}

View File

@ -1,59 +1,40 @@
{% extends 'base.html' %}
{% extends 'partials/base.html' %}
{% block name %}instructions{% endblock %}
{% block header %}
<h1>Instructions</h1>
{% endblock %}
{% block content %}
<p>
Contentmatcher is a <a href="https://gia.kompot.si/about.php">GIA</a> prototype that tries to help you engage with your content and share it with other users. It assumes that you have a heap of content that you would like to reconsider. This heap can take many forms such vast libraries of PDFs on your computer or a long lists of bookmarks.
</p>
<p>
The app will help you get through this content step by step and continue to show you your content again based on the interest you express as well present you with shared content. If you and at least one more person express a high interest in an <strong>item</strong> a match will appear on your <a href="{{ url_for('matches.index') }}">matches</a> page. There you can send them an email to talk about the <strong>item</strong> your interested in.
</p>
<p>
<h2>Some definitions:</h2>
<ul>
<li>
An <strong>item</strong> is a unit of content (like a PDF you upload).
</li>
<li>
<strong>Items</strong> are private by default but they can be shared.
</li>
<li>
A <strong>collection</strong> made up of all your private items and the items shared with you.
</li>
<li>
When you start a new session Contentmatcher will create a <strong>deck</strong>, which is a small part of the <strong>collection</strong>.
</li>
</ul>
</p>
<main>
<article>
<p>Contentmatcher is a <a href="https://gia.kompot.si/about.php">GIA</a> prototype that tries to help you engage with your content and share it with other users. It assumes that you have a heap of content that you would like to reconsider. This heap can take many forms such vast libraries of PDFs on your computer or a long lists of bookmarks.</p>
<h2>Importing your content</h2>
<p>
For now the only way to import content is to <a href='{{ url_for('upload.index')}}'>upload</a> your PDFs. We're figuring out how to add different lists of links like browser bookmarks, liked tweets and "watch later" youtube videos.
</p>
<p>The app will help you get through this content step by step and continue to show you your content again based on the interest you express as well present you with shared content. If you and at least one more person express a high interest in an <em>item</em> a match will appear on your <a href="{{ url_for('matches.index') }}">matches</a> page. There you can send them an email to talk about the <em>item</em> your interested in.</p>
<h2>Sessions</h2>
<p>
Going through sessions is the main activity in using Contentmatcher.
A session consists of rating and potentially shareing items in a <strong>deck</strong>. You can adjust the size of your <strong>deck</strong> in the settings.
<h2>Some definitions:</h2>
In sessions you rate <strong>items</strong> with <mark style="color: black; background-color: #736B1E;">yes</mark>, <mark style="color: black; background-color: #ED8008;">maybe</mark>, <mark style="color: black; background-color: #ED3f1C;">no</mark> and <mark style="color: black; background-color: #BF1B1B;">delete</mark> buttons. It's weird because there is no question, maybe think of a question like: "Are you really interested in this <strong>item</strong>?"
</p>
<p>
There is also the <mark style="color: black; background-color: #D9D2C6;">share</mark> button. This will create a new <strong>item</strong> with this content for all other users, so it will appear in their decks from now on as well.
</p>
<ul>
<li>An <em>item</em> is a unit of content (like a PDF you upload).</li>
<li><em>Items</em> are private by default but they can be shared.</li>
<li>A <em>collection</em> made up of all your private items and the items shared with you.</li>
<li>When you start a new session Contentmatcher will create a <em>deck</em>, which is a small part of the <em>collection</em>.</li>
</ul>
<h2>Matches</h2>
<p>
If you and at least one other user rate an item with a <mark style="color: black; background-color: #736B1E;">yes</mark> then a match will appear on your <a href="{{ url_for('match') }}">matches</a> page. There you can send a email to talk.
</p>
<h2>Importing your content</h2>
<h2>Feedback</h2>
<p>
If you have any questions, feedback, ideas or if you would like contribute, please contact us at <a href="mailto:gia@kompot.si">gia@kompot.si</a>.
</p>
<p>For now the only way to import content is to <a href='{{ url_for('upload.index')}}'>upload</a> your PDFs. We're figuring out how to add different lists of links like browser bookmarks, liked tweets and "watch later" youtube videos.</p>
<h2>Sessions</h2>
<p>Going through sessions is the main activity in using Contentmatcher. A session consists of rating and potentially shareing items in a <em>deck</em>. You can adjust the size of your <em>deck</em> in the settings. In sessions you rate <em>items</em> with <em class="yes">yes</em>, <em class="maybe">maybe</em>, <em class="no">no</em> and <em class="delete">delete</em> buttons. It's weird because there is no question, maybe think of a question like: "Are you really interested in this <em>item</em>?"</p>
<p>There is also the <em class="share">share</em> button. This will create a new <em>item</em> with this content for all other users, so it will appear in their decks from now on as well.</p>
<h2>Matches</h2>
<p>If you and at least one other user rate an item with a <em class="yes">yes</em> then a match will appear on your <a href="{{ url_for('match') }}">matches</a> page. There you can send an email to talk.</p>
<h2>Feedback</h2>
<p>If you have any questions, feedback, ideas or if you would like contribute, please contact us at <a href="mailto:gia@kompot.si">gia@kompot.si</a>.</p>
</article>
</main>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'partials/base.html' %}
{% block name %}login{% endblock %}
{% block content %}
<main>
<form method="post">
<label for="username">username:</label>
<input name="username" required>
<label for="password">password:</label>
<input type="password" name="password" required>
<input type="submit" value="log in">
</form>
</main>
{% endblock %}

View File

@ -1,48 +1,29 @@
{% extends 'base.html' %}
{% block header %}
<head>Matches</head>
{% endblock %}
{% block content %}
{% if list_of_matches %}
<table>
<style>
table {
border-collapse: collapse;
table-layout: fixed;
width: 90%;
}
table td {
border: solid 1px #666;
width: felx;
word-wrap: break-word;
}
</style>
<tr>
<th>Title</th>
<th>Users</th>
<th>Action</th>
</tr>
{% for match in list_of_matches %}
<TR>
<TD>
{{ match[0]['title'] }}
</TD>
<TD>
{% for card in match %}
{{ names_by_ids[card['owner_id']] }}
{% endfor %}
</TD>
<TD>
<a href="mailto:{% for card in match %}{{ emails_by_ids[card['owner_id']] }};{% endfor %}">send email</a>
</TD>
</TR>
{% endfor %}
</table>
{% extends 'partials/base.html' %}
{% block name %}matches{% endblock %}
{% block content %}
<main>
{% if list_of_matches %}
<table>
{% for match in list_of_matches %}
<TR>
<TD>
{{ match[0]['title'] }}
</TD>
<TD>
{% for card in match %}
{{ names_by_ids[card['owner_id']] }}
{% endfor %}
</TD>
<TD>
<a href="mailto:{% for card in match %}{{ emails_by_ids[card['owner_id']] }};{% endfor %}">send email</a>
</TD>
</TR>
{% endfor %}
</table>
{% else %}
<p>You have no matches at the moment</p>
<p>You have no matches at the moment.</p>
{% endif %}
{% endblock %}
</main>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'partials/base.html' %}
{% block name %}menu{% endblock %}
{% block content %}
<main>
<form method="post">
{% if deck_status == "old" %}
<button type="submit" name="menu" value="new_session">Continiue Session</button>
{% else %}
<button type="submit" name="menu" value="new_session">New Session</button>
{% endif %}
<button type="submit" name="menu" value="matches">Matches</button>
<button type="submit" name="menu" value="upload">Upload</button>
<button type="submit" name="menu" value="settings">Settings</button>
<button type="submit" name="menu" value="instructions">Instructions</button>
<!-- <button type="submit" name="menu" value="about">About</button> -->
</form>
</main>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html' %}
{% block header %}
<h1>Menu</h1>
{% endblock %}
{% block content %}
<form method="post">
{% if deck_status == "old" %}
<button type="submit" name="menu" value="new_session">Continiue Session</button> <br />
{% else %}
<button type="submit" name="menu" value="new_session">New Session</button> <br />
{% endif %}
<button type="submit" name="menu" value="matches">Matches</button> <br />
<button type="submit" name="menu" value="upload">Upload</button> <br />
<button type="submit" name="menu" value="settings">Settings</button> <br />
<button type="submit" name="menu" value="instructions">Instructions</button> <br />
<button type="submit" name="menu" value="about">About</button>
</form>
{% endblock %}

View File

@ -1,66 +0,0 @@
{% extends 'base.html' %}
{% block header %}
<head>
<title>Drag and Drop File Upload</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
</head>
{% endblock %}
{% block content %}
</body>
<form id="form" method="post" enctype="multipart/form-data" action="/upload/uploader">
<div class="drop-zone">
<span id="zone-txt" class="drop-zone__prompt">Drop file here or click to upload</span>
<input type="file" name="file" class="drop-zone__input" runat="server" accept=".pdf" multiple >
</div>
</form>
<script type="text/javascript">
document.querySelectorAll(".drop-zone__input").forEach((inputElement) => {
const dropZoneElement = inputElement.closest(".drop-zone");
dropZoneElement.addEventListener("click", (e) => {
inputElement.click();
});
inputElement.addEventListener("change", (e) => {
e.preventDefault();
console.log(inputElement.files);
if (inputElement.files.length) {
console.log(inputElement)
console.log("oddajam form", e);
document.getElementById('zone-txt').innerHTML = "Uploading file...";
document.getElementById("form").submit();
}
});
dropZoneElement.addEventListener("dragover", (e) => {
e.preventDefault();
dropZoneElement.classList.add("drop-zone--over");
});
["dragleave", "dragend"].forEach((type) => {
dropZoneElement.addEventListener(type, (e) => {
dropZoneElement.classList.remove("drop-zone--over");
});
});
dropZoneElement.addEventListener("drop", (e) => {
e.preventDefault();
if (e.dataTransfer.files.length) {
inputElement.files = e.dataTransfer.files;
console.log("oddajam form", e);
document.getElementById('zone-txt').innerHTML = "Uploading file...";
document.getElementById("form").submit();
}
dropZoneElement.classList.remove("drop-zone--over");
});
});
</script>
<body>
{% endblock %}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>contentmatcher</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body id="{% block name %}{% endblock %}">
{% block header %}
{% include 'partials/header.html' %}
{% endblock %}
{% block content %}{% endblock %}
{% block footer %}
{% include 'partials/footer.html' %}
{% endblock %}
{% include 'partials/flash.html' %}
</body>
</html>

View File

@ -0,0 +1,9 @@
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}

View File

@ -0,0 +1,3 @@
<footer>
<p>Contentmatcher is a <a href="https://gia.kompot.si/about.php">General Intelligence Agency of Ljubljana</a> prototype. You can find its code <a href="https://git.kompot.si/gia/contentmatcher">here</a>. Contentmatcher is hosted by <a href="https://kompot.si/wiki/doku.php">kompot</a>, a librehosters community from Ljubljana. If you have any questions, feedback or if you would like contribute, please contact us at <a href="mailto:gia@kompot.si">gia@kompot.si</a>.</p>
</footer>

View File

@ -0,0 +1,12 @@
<header>
<h1><a href="{{ url_for('menu.index') }}">contentmatcher</a></h1>
<nav>
{% if username %}
<span>logged in as <em>{{username}}</em></span>
<a href="{{ url_for('auth.logout') }}">log out</a>
{% else %}
<a href="{{ url_for('auth.register') }}">register</a>
<a href="{{ url_for('auth.login') }}">log in</a>
{% endif %}
</nav>
</header>

View File

@ -0,0 +1,11 @@
<nav>
{% if username %}
<span>logged in as <em>{{username}}</em></span>
<a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
<a href="{{ url_for('auth.register') }}">Register</a>
<a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}
</nav>

View File

@ -0,0 +1,17 @@
{% extends 'partials/base.html' %}
{% block name %}register{% endblock %}
{% block content %}
<main>
<form method="post">
<label for="username">username:</label>
<input name="username" required>
<label for="password">password:</label>
<input type="password" name="password" required>
<label for="email">email:</label>
<input type="email" name="email" required>
<input type="submit" value="register">
</form>
</main>
{% endblock %}

View File

@ -1,10 +1,9 @@
{% extends 'base.html' %}
quantity
{% block header %}
<head>Settings</head>
{% endblock %}
{% extends 'partials/base.html' %}
{% block name %}settings{% endblock %}
{% block content %}
<main>
<form method="post" action={{ url_for('settings.save_settings') }}>
<label for="quantity">Maximum new cards per session</label>
<input type="number" id="quantity" name="max_new" value= '{{ settings['max_new'] }}' min="0" required="required">
@ -17,4 +16,5 @@ quantity
-->
<input type="submit" value="Save">
</form>
{% endblock %}
</main>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'partials/base.html' %}
{% block name %}upload{% endblock %}
{% block content %}
<main>
<form class="dropzone dropzone-form" method="post" enctype="multipart/form-data" action="/upload/uploader">
<span id="dropzone-txt" class="dropzone-txt">Drop file here or click to upload.</span>
<input class="dropzone-input" type="file" name="file" runat="server" accept=".pdf" multiple >
</form>
</main>
<script src="{{ url_for('static', filename='js/dropzone.js') }}"></script>
{% endblock %}

View File

@ -23,7 +23,7 @@ nc.login(CONFIG['NC_USER'],CONFIG['NC_PASSWORD'])
@bp.route("/", methods=["GET", "POST"])
def index():
username = session["username"]
return render_template("menu/upload.html", username=username)
return render_template("upload.html", username=username)
@bp.route('/uploader', methods = ('GET', 'POST'))
@ -40,7 +40,7 @@ def upload_file():
# Is there really a file?
if not filename:
flash('There is no file. Try again?')
return render_template("menu/upload.html", username=username)
return render_template("upload.html", username=username)
#prevent duplicate filenames
print(filename)
@ -69,9 +69,9 @@ def upload_file():
dbsession.close()
else:
flash("Please insert a PDF file, support for other formats comming soon...")
#return render_template("menu/upload.html", user_id=user_id, username=username)
#return render_template("upload.html", user_id=user_id, username=username)
os.remove(path)
return render_template("menu/upload.html", user_id=user_id, username=username)
return render_template("upload.html", user_id=user_id, username=username)