Browse Source

a little push

master
manetta 2 years ago
commit
266d5fc1cd
  1. 9
      Makefile
  2. 12
      README.md
  3. 4
      config.py
  4. 138
      feedmode.py
  5. 17
      requirements.txt
  6. 76
      static/main.css
  7. 31061
      static/paged.js
  8. 31107
      static/paged.polyfill.js
  9. 180
      static/pagedjs.css
  10. 13
      static/select.css
  11. 44
      templates/base.html
  12. 9
      templates/html.html
  13. 5
      templates/iframe.html
  14. 10
      templates/multifeeder-template.html
  15. 5
      templates/pad.html
  16. 36
      templates/pandoc-template.html
  17. 30
      templates/pdf.html
  18. 21
      templates/selection-template.html

9
Makefile

@ -0,0 +1,9 @@
default: run
setup:
@python3 -m venv .venv
@.venv/bin/pip install -r requirements.txt
run:
@.venv/bin/python feedmode.py

12
README.md

@ -0,0 +1,12 @@
# feedmode
Multifeeder/RSS feeds → selection + CSS + template → PDF (very beta)
## Use feedmode locally
`make setup` (sets up a virtual environment and install the requirements)
`make run` (runs the Flask application)
Open the application at <http://localhost:5001>.

4
config.py

@ -0,0 +1,4 @@
class Config(object):
PORTNUMBER = 5001
PAD_API_URL = 'https://pad.vvvvvvaria.org/api/1.2.15/'
PAD_API_KEY = '<insert API key here>'

138
feedmode.py

@ -0,0 +1,138 @@
import flask
from flask import request, redirect
from urllib.request import urlopen
from urllib.parse import urlencode
import json
import os
import pypandoc
from jinja2 import Template
APP = flask.Flask(__name__)
APP.config.from_object("config.Config")
# ---
def get_pad_content(pad):
arguments = {
'padID' : pad,
'apikey' : APP.config['PAD_API_KEY']
}
api_call = 'getText'
response = json.load(urlopen(f"{ APP.config['PAD_API_URL'] }{ api_call }", data=urlencode(arguments).encode()))
content = response['data']['text']
return content
def update_pad_contents():
# download the stylesheet + template pad
css = get_pad_content('feedmode.css')
template = get_pad_content('feedmode.template')
# !!! this breaks the whole idea that this application can be shared by multiple projects at the same time
# !!! but py_pandoc needs to run with files........ hmmm
with open('templates/pandoc-template.html', 'w') as f:
f.write(template)
return css, template
def get_multifeeder_feed(query):
feed_json = json.load(urlopen(query))
multifeeder_template = open('templates/multifeeder-template.html').read()
jinja_template = Template(multifeeder_template)
feed_html = jinja_template.render(response=feed_json)
return feed_html, feed_json
def load_query():
query = open('static/query.txt').read().strip() # !!! needs fixing
return query
# ---
@APP.route('/', methods=['GET'])
def index():
return redirect("/preview/", code=302)
@APP.route('/update/', methods=['POST'])
def update():
query = request.form['query']
with open('static/query.txt', 'w') as out:
out.write(query)
return redirect("/select/", code=302)
@APP.route('/select/', methods=['GET'])
def select():
# get feed contents
# render selection template with checkboxes
query = load_query()
x, feed_json = get_multifeeder_feed(query)
return flask.render_template('selection-template.html', feed=feed_json, query=query)
@APP.route('/preview/', methods=['GET'])
def preview():
# update pad contents
x, template_content = update_pad_contents()
# get feed contents
query = load_query()
feed_html, x = get_multifeeder_feed(query)
# render multifeeder feed in template
template = Template(template_content)
html = template.render(feed=feed_html, mode="screen")
return flask.render_template('html.html', html=html, query=query)
@APP.route('/stylesheet/', methods=['GET'])
def stylesheet():
ext = '.css'
query = load_query()
return flask.render_template('pad.html', name="feedmode", ext=ext, query=query)
@APP.route('/template/', methods=['GET'])
def template():
ext = '.template'
query = load_query()
return flask.render_template('pad.html', name="feedmode", ext=ext, query=query)
@APP.route('/pdf/')
def pdf():
query = load_query()
return flask.render_template('pdf.html', query=query)
# //////////////
# rendered resources (not saved as a file on the server)
@APP.route('/print.css')
def css():
css, x = update_pad_contents()
return css, 200, {'Content-Type': 'text/css; charset=utf-8'}
@APP.route('/pandoc-template.html')
def pandoc_template():
x, template = update_pad_contents()
return template, 200, {'Content-Type': 'text/html; charset=utf-8'}
@APP.route('/pagedjs.html')
def pagedjs():
# update pad contents
x, template = update_pad_contents()
# get feed contents
query = "https://multi.vvvvvvaria.org/API/latest/50" # !!! needs fixing
feed_html, feed_json = get_multifeeder_feed(query)
# render multifeeder feed in template
jinja_template = Template(template)
html = jinja_template.render(feed=feed_html, mode="print")
return html, 200, {'Content-Type': 'text/html; charset=utf-8'}
# //////////////
if __name__ == '__main__':
APP.debug=True
APP.run(host="0.0.0.0", port=f'{ APP.config["PORTNUMBER"] }', threaded=True)

17
requirements.txt

@ -0,0 +1,17 @@
click==8.0.3
Flask==2.0.2
importlib-metadata==4.10.1
itsdangerous==2.0.1
Jinja2==3.0.3
Markdown==3.3.6
MarkupSafe==2.0.1
pandoc==2.0.1
pkg-resources==0.0.0
plumbum==1.7.2
ply==3.11
pypandoc==1.7.2
typing-extensions==4.0.1
urllib3==1.26.8
Werkzeug==2.0.2
zipp==3.7.0

76
static/main.css

@ -0,0 +1,76 @@
body{
min-width: 900px;
}
/* GENERAL RULES */
/* main title element that says "in feedmode" */
h1 em.feedmode{
color: darkorchid;
}
/* navigation */
div#nav{
position: fixed;
width: calc(100% - 1em);
top: 0;
left: 0;
margin: 0;
padding: 0 0.5em;
}
div#nav h1{
position: absolute;
width: auto;
line-height: 0;
margin: 0.75em 15px;
float: left;
font-size: 24px;
}
div#nav div#buttons{
margin: 0.5em 15px;
float: right;
}
div#nav input{
min-width: 300px;
}
div#nav input#html{
min-width: unset;
}
div#nav form#update{
float: left;
margin: 0.5em 15px 0 300px;
}
/* iframe rules */
iframe{
width: 100%;
height: 100%;
border: none;
}
/* main content area */
div#wrapper{
/* pages with an iframe are on fixed mode */
position: fixed;
top: 50px;
left: 25px;
width: calc(100vw - 25px - 25px);
height: calc(100vh - 50px - 25px);
}
div#wrapper.scroll{
/* the HTML page is on scroll mode */
position: relative;
top: 0;
left: 0;
margin: 5em 0 0 0;
}
/* Z-INDEX */
div#wrapper,
div.pagedjs_pages{
z-index: 1;
}
div#nav{
z-index: 11;
}

31061
static/paged.js

File diff suppressed because it is too large

31107
static/paged.polyfill.js

File diff suppressed because it is too large

180
static/pagedjs.css

@ -0,0 +1,180 @@
/* CSS for Paged.js interface – v0.2 */
/* Change the look */
:root {
--color-background: whitesmoke;
--color-pageSheet: #cfcfcf;
--color-pageBox: violet;
--color-paper: white;
--color-marginBox: transparent;
--pagedjs-crop-color: black;
--pagedjs-crop-shadow: white;
--pagedjs-crop-stroke: 1px;
}
/* To define how the book look on the screen: */
@media screen {
body {
background-color: var(--color-background);
}
.pagedjs_pages {
display: flex;
width: calc(var(--pagedjs-width) * 2);
flex: 0;
flex-wrap: wrap;
margin: 0 auto;
}
.pagedjs_page {
background-color: var(--color-paper);
box-shadow: 0 0 0 1px var(--color-pageSheet);
margin: 0;
flex-shrink: 0;
flex-grow: 0;
margin-top: 10mm;
}
.pagedjs_first_page {
margin-left: var(--pagedjs-width);
}
.pagedjs_page:last-of-type {
margin-bottom: 10mm;
}
.pagedjs_pagebox{
box-shadow: 0 0 0 1px var(--color-pageBox);
}
.pagedjs_left_page{
z-index: 20;
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop {
border-color: transparent;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: 0;
}
.pagedjs_right_page{
z-index: 10;
position: relative;
left: calc(var(--pagedjs-bleed-left)*-1);
}
/* show the margin-box */
.pagedjs_margin-top-left-corner-holder,
.pagedjs_margin-top,
.pagedjs_margin-top-left,
.pagedjs_margin-top-center,
.pagedjs_margin-top-right,
.pagedjs_margin-top-right-corner-holder,
.pagedjs_margin-bottom-left-corner-holder,
.pagedjs_margin-bottom,
.pagedjs_margin-bottom-left,
.pagedjs_margin-bottom-center,
.pagedjs_margin-bottom-right,
.pagedjs_margin-bottom-right-corner-holder,
.pagedjs_margin-right,
.pagedjs_margin-right-top,
.pagedjs_margin-right-middle,
.pagedjs_margin-right-bottom,
.pagedjs_margin-left,
.pagedjs_margin-left-top,
.pagedjs_margin-left-middle,
.pagedjs_margin-left-bottom {
box-shadow: 0 0 0 1px inset var(--color-marginBox);
}
/* uncomment this part for recto/verso book : ------------------------------------ */
/*
.pagedjs_pages {
flex-direction: column;
width: 100%;
}
.pagedjs_first_page {
margin-left: 0;
}
.pagedjs_page {
margin: 0 auto;
margin-top: 10mm;
}
.pagedjs_left_page{
width: calc(var(--pagedjs-bleed-left) + var(--pagedjs-pagebox-width) + var(--pagedjs-bleed-left))!important;
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-crop{
border-color: var(--pagedjs-crop-color);
}
.pagedjs_left_page .pagedjs_bleed-right .pagedjs_marks-middle{
width: var(--pagedjs-cross-size)!important;
}
.pagedjs_right_page{
left: 0;
}
*/
/*--------------------------------------------------------------------------------------*/
/* uncomment this par to see the baseline : -------------------------------------------*/
/*
.pagedjs_pagebox {
--pagedjs-baseline: 22px;
--pagedjs-baseline-position: 5px;
--pagedjs-baseline-color: cyan;
background: linear-gradient(transparent 0%, transparent calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) calc(var(--pagedjs-baseline) - 1px), var(--pagedjs-baseline-color) var(--pagedjs-baseline)), transparent;
background-size: 100% var(--pagedjs-baseline);
background-repeat: repeat-y;
background-position-y: var(--pagedjs-baseline-position);
} */
/*--------------------------------------------------------------------------------------*/
}
/* Marks (to delete when merge in paged.js) */
.pagedjs_marks-crop{
z-index: 999999999999;
}
.pagedjs_bleed-top .pagedjs_marks-crop,
.pagedjs_bleed-bottom .pagedjs_marks-crop{
box-shadow: 1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-top .pagedjs_marks-crop:last-child,
.pagedjs_bleed-bottom .pagedjs_marks-crop:last-child{
box-shadow: -1px 0px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop,
.pagedjs_bleed-right .pagedjs_marks-crop{
box-shadow: 0px 1px 0px 0px var(--pagedjs-crop-shadow);
}
.pagedjs_bleed-left .pagedjs_marks-crop:last-child,
.pagedjs_bleed-right .pagedjs_marks-crop:last-child{
box-shadow: 0px -1px 0px 0px var(--pagedjs-crop-shadow);
}

13
static/select.css

@ -0,0 +1,13 @@
div.post{
width: 250px;
border: 1px solid magenta;
padding: 1em;
margin: 0.5em;
max-height: 400px;
float: left;
overflow-y: scroll;
font-size: 12px;
}
div.post img{
max-width: 100%;
}

44
templates/base.html

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="utf-8" />
<title>feedmode</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
{% block head %}
{% endblock %}
</head>
<body>
<div id="wrapper" class="{% block pagetype %}{% endblock %}">
{% block content %}
{% endblock %}
</div>
</body>
<script>
window.addEventListener('load', function () {
// Insert the nav buttons, after the page is loaded
const nav = document.createElement('div');
nav.id = 'nav';
nav.innerHTML = `
<h1><em class="feedmode">feedmode</em><sup>(very beta)</sup></h1>
<form id="update" action="/update/" method="post">
<input type="text" name="query" value="{% if query %}{{ query }}{% else %}https://multi.vvvvvvaria.org/API/latest/50{% endif %}">
<input id="html" type="submit" value="update">
</form>
<div id="buttons">
<a href="/select/"><button>select</button></a>
<a href="/preview/"><button>preview</button></a>
<a href="/pdf/"><button>pdf</button></a>
<a href="/stylesheet/"><button>stylesheet</button></a>: <input type="text" name="pad" value="https://pad.vvvvvvaria.org/feedmode.css">
<a href="/template/"><button>template</button></a>: <input type="text" name="pad" value="https://pad.vvvvvvaria.org/feedmode.template">
</div>
`;
document.body.insertBefore(nav, document.body.firstChild);
})
</script>
{% block footer %}
{% endblock %}
</html>

9
templates/html.html

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block pagetype %}
scroll
{% endblock %}
{% block content %}
{{ html | safe }}
{% endblock %}

5
templates/iframe.html

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<iframe src="{{ url }}"></iframe>
{% endblock %}

10
templates/multifeeder-template.html

@ -0,0 +1,10 @@
{% for post in response %}
<div class="post">
<!-- <input type="checkbox" name="check" value="{{ loop.index }}"><br><br> -->
<h1>{{ post.title }}</h1>
<!-- <div><small>{{ post.author }}</small></div> -->
<div><small>{{ post.published }}</small></div>
<div><small><a href="{{ post.link }}">{{ post.link }}</a></small></div>
<div>{{ post.summary }}</div>
</div>
{% endfor %}

5
templates/pad.html

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<iframe src="https://pad.vvvvvvaria.org/{{ name }}{{ ext }}"></iframe>
{% endblock %}

36
templates/pandoc-template.html

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if mode == "print" %}
<script src="/static/paged.js" type="text/javascript"></script>
<script src="/static/paged.polyfill.js" type="text/javascript"></script>
<link href="/static/pagedjs.css" rel="stylesheet" type="text/css" media="screen">
{% endif %}
<link href="/print.css" rel="stylesheet" type="text/css" media="{{ mode }}">
<title>The Everydaily</title>
</head>
<body>
<section id="cover">
<h1 id="title">The Everydaily</h1>
</section>
<section id="header">
<div>This is the header...</div>
</section>
<section id="feed">
{{ feed }}
</section>
<section id="footer">
<div>Anything in the footer here...</div>
</section>
</body>
</html>

30
templates/pdf.html

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<iframe id="pdf" name="pdf" src="/pagedjs.html"></iframe>
{% endblock %}
{% block footer %}
<script>
function printPage(){
window.frames["pdf"].focus();
window.frames["pdf"].print();
}
window.addEventListener('load', function () {
// Load the main.css again, to load the stylesheet for the nav
var cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = '/static/main.css';
var head = document.getElementsByTagName('head')[0];
head.insertBefore(cssLink, head.firstChild);
// Insert the SAVE button
const nav = document.getElementById('buttons');
const save = '<a href="#"><button id="save" onClick="printPage()">save</button></a>';
nav.innerHTML = nav.innerHTML + save;
})
</script>
{% endblock %}

21
templates/selection-template.html

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block head %}
<link rel="stylesheet" type="text/css" href="/static/select.css">
{% endblock %}
{% block pagetype %}
scroll
{% endblock %}
{% block content %}
{% for post in feed %}
<div class="post">
<input type="checkbox" name="check" value="{{ loop.index }}"><br><br>
<h1>{{ post.title }}</h1>
<div><small>{{ post.published }}</small></div>
<div><small><a href="{{ post.link }}">{{ post.link }}</a></small></div>
<div>{{ post.summary | safe }}</div>
</div>
{% endfor %}
{% endblock %}
Loading…
Cancel
Save