import changes from lepa
This commit is contained in:
parent
378f37c9b9
commit
ef640752cc
18
filter_registry.py
Normal file
18
filter_registry.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# This is an attempt to create a customizable way to
|
||||
# add filters to the content coming from the etherpad
|
||||
# get_text api endpoint
|
||||
# in pad_filters.py do this:
|
||||
#
|
||||
# import re
|
||||
# from filter_registry import register_filter
|
||||
|
||||
# @register_filter
|
||||
# def remove_star_before_img(text):
|
||||
# return re.sub(r'\*\s*!?\[\]\(', '
|
||||
|
||||
pad_content_filters = []
|
||||
|
||||
def register_filter(func):
|
||||
print(f"Registering filter: {func.__name__}")
|
||||
pad_content_filters.append(func)
|
||||
return func
|
||||
84
octomode.py
84
octomode.py
|
|
@ -3,6 +3,7 @@ import json
|
|||
from flask import Flask, request, render_template, redirect, url_for
|
||||
from urllib.request import urlopen
|
||||
from urllib.parse import urlencode
|
||||
import html
|
||||
|
||||
# To sanitize Flask input fields
|
||||
from markupsafe import Markup, escape
|
||||
|
|
@ -14,6 +15,11 @@ import pypandoc
|
|||
# To read the Markdown metadat
|
||||
import markdown
|
||||
|
||||
|
||||
from filter_registry import pad_content_filters
|
||||
import pad_filters
|
||||
|
||||
|
||||
APP = Flask(__name__)
|
||||
APP.config.from_pyfile('settings.py')
|
||||
|
||||
|
|
@ -40,6 +46,11 @@ def get_pad_content(pad_name, ext=""):
|
|||
response = json.load(urlopen(f"{ APP.config['PAD_API_URL'] }/{ api_call }", data=urlencode(arguments).encode()))
|
||||
|
||||
content = response['data']['text']
|
||||
# print("before: " + content)
|
||||
print( "GET PAD CONTENT" )
|
||||
for f in pad_content_filters:
|
||||
content = f(content)
|
||||
# print("after: " + content)
|
||||
return content
|
||||
|
||||
def all_pads():
|
||||
|
|
@ -95,6 +106,25 @@ def get_md_metadata(md_pad_content):
|
|||
|
||||
return metadata
|
||||
|
||||
def get_app_root():
|
||||
if APP.config['APPLICATION_ROOT'] == '/':
|
||||
app_root = ''
|
||||
elif APP.config['APPLICATION_ROOT'].endswith('/'):
|
||||
app_root = APP.config['APPLICATION_ROOT'][:-1]
|
||||
else:
|
||||
app_root = APP.config['APPLICATION_ROOT']
|
||||
return app_root
|
||||
|
||||
|
||||
# def apply_cover(html_str, cover):
|
||||
# import html
|
||||
# html_str = str(html_str)
|
||||
# html_str = html_str.replace('class="cover"', "class='cover' style='background-image: url("+ cover +")'")
|
||||
# return Markup(html_str)
|
||||
|
||||
def get_meta(metadata, key, default=None):
|
||||
return metadata.get(key, [default])[0]
|
||||
|
||||
# ---
|
||||
|
||||
@APP.route('/', methods=['GET', 'POST'])
|
||||
|
|
@ -110,7 +140,7 @@ def index():
|
|||
create_pad_on_first_run(name, ext)
|
||||
return redirect(url_for("pad", name=name))
|
||||
else:
|
||||
return render_template('start.html', pad_url=APP.config['PAD_URL'])
|
||||
return render_template('start.html', home_pad_url=APP.config['HOME_PAD_URL'])
|
||||
|
||||
@APP.route('/<name>/')
|
||||
def main(name):
|
||||
|
|
@ -128,28 +158,22 @@ def stylesheet(name):
|
|||
|
||||
@APP.route('/<name>/html/')
|
||||
def html(name):
|
||||
# only here we need application root to make all the URLs work.....
|
||||
if APP.config['APPLICATION_ROOT'] == '/':
|
||||
app_root = ''
|
||||
elif APP.config['APPLICATION_ROOT'].endswith('/'):
|
||||
app_root = APP.config['APPLICATION_ROOT'][:-1]
|
||||
else:
|
||||
app_root = APP.config['APPLICATION_ROOT']
|
||||
app_root = get_app_root()
|
||||
url = f"{ app_root }/{ name }/preview.html"
|
||||
return render_template('iframe.html', url=url, name=name.strip(), pad_url=APP.config['PAD_URL'])
|
||||
|
||||
@APP.route('/<name>/pdf/')
|
||||
def pdf(name):
|
||||
# only here we need application root to make all the URLs work.....
|
||||
if APP.config['APPLICATION_ROOT'] == '/':
|
||||
app_root = ''
|
||||
elif APP.config['APPLICATION_ROOT'].endswith('/'):
|
||||
app_root = APP.config['APPLICATION_ROOT'][:-1]
|
||||
else:
|
||||
app_root = APP.config['APPLICATION_ROOT']
|
||||
app_root = get_app_root()
|
||||
url = f"{ app_root }/{name}/pagedjs.html"
|
||||
return render_template('pdf.html', url=url, name=name.strip(), pad_url=APP.config['PAD_URL'])
|
||||
|
||||
@APP.route('/<name>/impose/')
|
||||
def impose(name):
|
||||
app_root = get_app_root()
|
||||
url = f"{ app_root }/{name}/imposed.html"
|
||||
return render_template('pdf.html', url=url, name=name.strip(), pad_url=APP.config['PAD_URL'])
|
||||
|
||||
# //////////////////
|
||||
# RENDERED RESOURCES
|
||||
# //////////////////
|
||||
|
|
@ -168,12 +192,8 @@ def preview(name):
|
|||
md_pad_content = get_pad_content(name, ext='.md')
|
||||
html = md_to_html(md_pad_content)
|
||||
metadata = get_md_metadata(md_pad_content)
|
||||
if metadata:
|
||||
lang = metadata['language'][0]
|
||||
title = metadata['title'][0]
|
||||
else:
|
||||
lang = "en"
|
||||
title = "No title"
|
||||
lang = get_meta(metadata, 'language', 'en')
|
||||
title = get_meta(metadata, 'title', 'Untitled')
|
||||
|
||||
return render_template('preview.html', name=name.strip(), pad_content=html, lang=lang, title=title)
|
||||
|
||||
|
|
@ -183,10 +203,26 @@ def pagedjs(name):
|
|||
md_pad_content = get_pad_content(name, ext='.md')
|
||||
html = md_to_html(md_pad_content)
|
||||
metadata = get_md_metadata(md_pad_content)
|
||||
lang = metadata['language'][0]
|
||||
title = metadata['title'][0]
|
||||
lang = get_meta(metadata, 'language', 'en')
|
||||
title = get_meta(metadata, 'title', 'Untitled')
|
||||
cover = get_meta(metadata, 'cover', None)
|
||||
|
||||
return render_template('pagedjs.html', name=name.strip(), pad_content=html, lang=lang, title=title)
|
||||
impose = False #request.args.get("impose") == "true"
|
||||
|
||||
return render_template('pagedjs.html', name=name.strip(), pad_content=html, lang=lang, title=title, cover=cover, impose=impose)
|
||||
|
||||
@APP.route('/<name>/imposed.html')
|
||||
def imposed(name):
|
||||
# TO GENERATE THE PAGED.JS WEBPAGE
|
||||
md_pad_content = get_pad_content(name, ext='.md')
|
||||
html = md_to_html(md_pad_content)
|
||||
metadata = get_md_metadata(md_pad_content)
|
||||
lang = get_meta(metadata, 'language', 'en')
|
||||
title = get_meta(metadata, 'title', 'Untitled')
|
||||
|
||||
impose = True #request.args.get("impose") == "true"
|
||||
|
||||
return render_template('pagedjs.html', name=name.strip(), pad_content=html, lang=lang, title=title, impose=impose)
|
||||
|
||||
# //////////////////
|
||||
|
||||
|
|
|
|||
15
pad_filters.py
Normal file
15
pad_filters.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import re
|
||||
from filter_registry import register_filter
|
||||
|
||||
@register_filter
|
||||
def remove_star_before_img(text):
|
||||
# regex version
|
||||
return re.sub(r'\*\s*!?\[\]\(', '
|
||||
|
||||
# @register_filter
|
||||
# def remove_star_before_img(text):
|
||||
# # print(">>> filter running")
|
||||
# # print(">>> before:", repr(text))
|
||||
# text = text.replace('*
|
||||
# # print(">>> after:", repr(text))
|
||||
# return text
|
||||
|
|
@ -9,7 +9,8 @@ APPLICATION_ROOT = os.environ.get('OCTOMODE_APPLICATION_ROOT', '/')
|
|||
PORTNUMBER = int(os.environ.get('OCTOMODE_PORTNUMBER', 5001))
|
||||
PAD_URL = os.environ.get('OCTOMODE_PAD_URL', 'https://pad.vvvvvvaria.org')
|
||||
PAD_API_URL = os.environ.get('OCTOMODE_PAD_API_URL', 'https://pad.vvvvvvaria.org/api/1.2.15')
|
||||
PAD_API_KEY = os.environ.get('OCTOMODE_PAD_API_KEY', '')
|
||||
PAD_API_KEY = "97e3bf6a626eaaa3426833db3d936f3a"
|
||||
#os.environ.get('OCTOMODE_PAD_API_KEY', '')
|
||||
|
||||
# Check if API key is provided
|
||||
if not PAD_API_KEY or PAD_API_KEY == "XXX":
|
||||
|
|
|
|||
55
static/footnotes.js
Normal file
55
static/footnotes.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Adds a custom css property that switches footnote style
|
||||
between endnodes (default for Pandoc) and footnotes
|
||||
modified from: https://pagedjs.org/plugins/endnotes-to-footnotes/
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
const endNoteCalloutsQuery = ".footnote-ref";
|
||||
|
||||
// the hook
|
||||
class endToFootNotes extends Paged.Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.notestyle = 'endnotes';
|
||||
}
|
||||
|
||||
onDeclaration(declaration, dItem, dList, rule) {
|
||||
if (declaration.property == "--paged-note-style") {
|
||||
if (declaration.value.value.includes("footnote")) {
|
||||
this.notestyle = 'footnotes';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeParsed(content) {
|
||||
if (this.notestyle !== 'footnotes') return;
|
||||
|
||||
// Clean up previously injected clones (in case of re-render).
|
||||
content.querySelectorAll('.pagedjs-footnote-clone').forEach((n) => n.remove());
|
||||
|
||||
// Optional: hide the original footnote list; we will clone notes inline.
|
||||
const originalList = content.querySelector('.footnotes');
|
||||
if (originalList) {
|
||||
originalList.classList.add('pagedjs-footnotes-hidden');
|
||||
}
|
||||
|
||||
const callouts = content.querySelectorAll(endNoteCalloutsQuery);
|
||||
callouts.forEach((callout) => {
|
||||
if (!callout.hash) return;
|
||||
const note = content.querySelector(callout.hash);
|
||||
if (!note) {
|
||||
console.warn(`No footnote found for ${callout.hash}`);
|
||||
return;
|
||||
}
|
||||
// Clone the note content and float it as a footnote.
|
||||
const clone = document.createElement('span');
|
||||
clone.classList.add('pagedjs-footnote-clone');
|
||||
clone.innerHTML = note.innerHTML;
|
||||
callout.insertAdjacentElement('afterend', clone);
|
||||
});
|
||||
}
|
||||
}
|
||||
Paged.registerHandlers(endToFootNotes);
|
||||
428
static/imposition.js
Normal file
428
static/imposition.js
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
// Imposition for booklet(s)
|
||||
//
|
||||
// This script re-arrange the pages of your document in order to make an imposed sheet layouts for printing.
|
||||
// Two pages per sheet, double-sided
|
||||
// modified from: https://gitlab.com/pagedjs-plugins/booklet-imposition/-/blob/master/imposition.js?ref_type=heads
|
||||
|
||||
class Booklet extends Paged.Handler {
|
||||
constructor(chunker, polisher, caller) {
|
||||
super(chunker, polisher, caller);
|
||||
this.pagedbooklet;
|
||||
this.sourceSize;
|
||||
this.pageStart;
|
||||
this.pageEnd;
|
||||
}
|
||||
onAtPage(node, item, list) { }
|
||||
onDeclaration(declaration, dItem, dList, rule) {
|
||||
if (declaration.property == "--paged-layout") {
|
||||
if (declaration.value.value.includes("booklet")) {
|
||||
console.log('BOOKLET');
|
||||
// console.log(declaration.property, declaration, rule);
|
||||
this.pagedbooklet = true;
|
||||
let valuesBooklet = declaration.value.value.trim().split(/\s+/);
|
||||
let index = valuesBooklet.indexOf("booklet");
|
||||
/* Set first page of the imposition */
|
||||
const rawStart = valuesBooklet[index + 1];
|
||||
if (rawStart) {
|
||||
const parsedStart = parseInt(rawStart, 10);
|
||||
this.pageStart = Number.isNaN(parsedStart) ? 1 : parsedStart;
|
||||
} else {
|
||||
this.pageStart = 1;
|
||||
}
|
||||
|
||||
/* Set last page of the imposition */
|
||||
const rawEnd = valuesBooklet[index + 2];
|
||||
if (rawEnd) {
|
||||
const parsedEnd = parseInt(rawEnd, 10);
|
||||
this.pageEnd = Number.isNaN(parsedEnd) ? undefined : parsedEnd;
|
||||
} else {
|
||||
this.pageEnd = undefined;
|
||||
}
|
||||
console.log(`START: ${this.pageStart} END: ${this.pageEnd}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
afterRendered(pages) {
|
||||
|
||||
|
||||
/* Verify this.pageEnd */
|
||||
if (!this.pageEnd) {
|
||||
let allPagesBefore = document.querySelectorAll(".pagedjs_page").length;
|
||||
this.pageEnd = allPagesBefore;
|
||||
}
|
||||
|
||||
/* Verify this.pageStart */
|
||||
if (this.pageStart == 0) {
|
||||
this.pageStart = 1;
|
||||
} else if (this.pageStart % 2 == 0) {
|
||||
this.pageStart = this.pageStart - 1;
|
||||
}
|
||||
|
||||
|
||||
/* Launch when printing */
|
||||
//window.addEventListener("beforeprint", (evenement) => {
|
||||
const reorder = (pages) => {
|
||||
let containerPages = document.querySelector(".pagedjs_pages");
|
||||
|
||||
|
||||
/* Delete pages we don't want*/
|
||||
pages.forEach(page => {
|
||||
let id = parseInt(page.id.replace('page-', ''));
|
||||
if (id < this.pageStart || id > this.pageEnd) {
|
||||
let pageSelect = document.querySelector('#' + page.id);
|
||||
pageSelect.remove();
|
||||
}
|
||||
});
|
||||
|
||||
/* Reset page counter */
|
||||
let reset = parseInt(this.pageStart) - 1;
|
||||
containerPages.style.counterReset = "page " + reset;
|
||||
|
||||
|
||||
let format = document.querySelector(".pagedjs_page");
|
||||
|
||||
/* Width of page without bleed, extract the first number of calc() function */
|
||||
let width = getCSSCustomProp("--pagedjs-width", format);
|
||||
let numbers = width
|
||||
.match(/[0-9]+/g)
|
||||
.map(function (n) {
|
||||
return + (n);
|
||||
});
|
||||
width = parseInt(numbers[0]);
|
||||
|
||||
/* Height of page with bleed, addition of all the numbers of calc() function*/
|
||||
let height = getCSSCustomProp("--pagedjs-height", format);
|
||||
numbers = height
|
||||
.match(/[0-9]+/g)
|
||||
.map(function (n) {
|
||||
return + (n);
|
||||
});
|
||||
const reducer = (previousValue, currentValue) => previousValue + currentValue;
|
||||
height = numbers.reduce(reducer);
|
||||
|
||||
/* Bleed of the page */
|
||||
let bleed = getCSSCustomProp("--pagedjs-bleed-top", format);
|
||||
let bleedNum = parseInt(bleed);
|
||||
|
||||
/* Spread and half-spread*/
|
||||
let spread = width * 2 + bleedNum * 2;
|
||||
|
||||
let spreadHalf = width + bleedNum;
|
||||
|
||||
// Add CSS to have pages in spread
|
||||
//
|
||||
// - change size of the page when printing (actually, sheet size)
|
||||
// - flex properties
|
||||
// - delete bleeds inside spread */
|
||||
|
||||
var newSize =
|
||||
`@media print{
|
||||
@page{
|
||||
size: ${spread}mm ${height}mm;
|
||||
}
|
||||
.pagedjs_pages {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@media screen{
|
||||
.pagedjs_pages{
|
||||
max-width: calc(var(--pagedjs-width) * 2);
|
||||
}
|
||||
}
|
||||
.pagedjs_pages {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
transform: none !important;
|
||||
height: 100% !important;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.pagedjs_page {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
height: 100% !important;
|
||||
|
||||
}
|
||||
|
||||
.pagedjs_sheet {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
height: 100% !important;
|
||||
}
|
||||
body{
|
||||
--pagedjs-bleed-right-left: 0mm;
|
||||
}
|
||||
.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_right_page,
|
||||
.pagedjs_right_page .pagedjs_sheet{
|
||||
width: calc(var(--pagedjs-bleed-right-right) + var(--pagedjs-pagebox-width))!important;
|
||||
}
|
||||
|
||||
.pagedjs_right_page .pagedjs_sheet{
|
||||
grid-template-columns: [bleed-left] var(--pagedjs-bleed-right-left) [sheet-center] 1fr [bleed-right] var(--pagedjs-bleed-right-right);
|
||||
}
|
||||
.pagedjs_right_page .pagedjs_bleed-left{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagedjs_right_page .pagedjs_bleed-top .pagedjs_marks-crop:nth-child(1),
|
||||
.pagedjs_right_page .pagedjs_bleed-bottom .pagedjs_marks-crop:nth-child(1){
|
||||
width: 0!important;
|
||||
}
|
||||
.pagedjs_first_page {
|
||||
margin-left: 0;
|
||||
}
|
||||
body{
|
||||
margin: 0
|
||||
}
|
||||
.pagedjs_page:nth-of-type(even){
|
||||
break-after: always;
|
||||
}
|
||||
.pagedjs_page,
|
||||
.pagedjs_sheet{
|
||||
width: ${spreadHalf - 0.1}mm!important;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// Add style for the arrangement of the pages
|
||||
|
||||
if (this.pagedbooklet == true) {
|
||||
let style = document.createElement("style");
|
||||
style.textContent = newSize;
|
||||
document
|
||||
.head
|
||||
.appendChild(style);
|
||||
|
||||
var number_of_pages = document.getElementsByClassName("pagedjs_page").length;
|
||||
var pages_array = [];
|
||||
|
||||
// If the page count isn't a multiple of 4, we need to pad the array with blank
|
||||
// pages so we have the correct number of pages for a booklet.
|
||||
//
|
||||
// ex. [1, 2, 3, 4, 5, 6, 7, 8, 9, blank, blank, blank]
|
||||
|
||||
let modulo = number_of_pages % 4;
|
||||
let additional_pages = 0;
|
||||
if (modulo != 0) {
|
||||
additional_pages = 4 - modulo;
|
||||
}
|
||||
|
||||
for (i = 0; i < additional_pages; i++) {
|
||||
let added_page = document.createElement("div");
|
||||
added_page
|
||||
.classList
|
||||
.add("pagedjs_page", "added");
|
||||
added_page.id = `page-${this.pageEnd + i + 1}`;
|
||||
added_page.innerHTML = `
|
||||
<div class="pagedjs_sheet">
|
||||
<div class="pagedjs_bleed pagedjs_bleed-top">
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
<div class="pagedjs_marks-middle">
|
||||
<div class="pagedjs_marks-cross"></div>
|
||||
</div>
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
</div>
|
||||
<div class="pagedjs_bleed pagedjs_bleed-bottom">
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
<div class="pagedjs_marks-middle">
|
||||
<div class="pagedjs_marks-cross"></div>
|
||||
</div> <div class="pagedjs_marks-crop"></div>
|
||||
</div>
|
||||
<div class="pagedjs_bleed pagedjs_bleed-left">
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
<div class="pagedjs_marks-middle">
|
||||
<div class="pagedjs_marks-cross"></div>
|
||||
</div> <div class="pagedjs_marks-crop"></div>
|
||||
</div>
|
||||
<div class="pagedjs_bleed pagedjs_bleed-right">
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
<div class="pagedjs_marks-middle">
|
||||
<div class="pagedjs_marks-cross"></div>
|
||||
</div>
|
||||
<div class="pagedjs_marks-crop"></div>
|
||||
</div>
|
||||
<div class="pagedjs_pagebox">
|
||||
<div class="pagedjs_margin-top-left-corner-holder">
|
||||
<div class="pagedjs_margin pagedjs_margin-top-left-corner"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-top">
|
||||
<div class="pagedjs_margin pagedjs_margin-top-left"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-top-center"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-top-right"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-top-right-corner-holder">
|
||||
<div class="pagedjs_margin pagedjs_margin-top-right-corner"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-right">
|
||||
<div class="pagedjs_margin pagedjs_margin-right-top"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-right-middle"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-right-bottom"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-left">
|
||||
<div class="pagedjs_margin pagedjs_margin-left-top"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-left-middle"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-left-bottom"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-bottom-left-corner-holder">
|
||||
<div class="pagedjs_margin pagedjs_margin-bottom-left-corner"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-bottom" style="grid-template-columns: 0px 1fr 0px;">
|
||||
<div class="pagedjs_margin pagedjs_margin-bottom-left"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-bottom-center hasContent"><div class="pagedjs_margin-content"></div></div>
|
||||
<div class="pagedjs_margin pagedjs_margin-bottom-right"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_margin-bottom-right-corner-holder">
|
||||
<div class="pagedjs_margin pagedjs_margin-bottom-right-corner"><div class="pagedjs_margin-content"></div></div>
|
||||
</div>
|
||||
<div class="pagedjs_area">
|
||||
<div class="pagedjs_page_content"><div>
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document
|
||||
.querySelector(".pagedjs_pages")
|
||||
.appendChild(added_page);
|
||||
}
|
||||
|
||||
// Push each page in the array
|
||||
|
||||
for (var i = number_of_pages + additional_pages; i >= 1; i--) {
|
||||
pages_array.push(i);
|
||||
}
|
||||
|
||||
// get the last page here, and add a 'pagedjs_last_page' class
|
||||
// it's mixing functionality a bit, but i don't want to iterate
|
||||
// a second time over the pages
|
||||
|
||||
const pagesList = Array.from(document.querySelectorAll('.pagedjs_page'));
|
||||
pagesList.forEach(p => p.classList.remove('pagedjs_last_page'));
|
||||
const last = pagesList.sort((a, b) => (parseInt(a.style.order || 0) - parseInt(b.style.order || 0))).pop();
|
||||
console.log("last", last);
|
||||
if (last) last.classList.add('pagedjs_last_page');
|
||||
|
||||
|
||||
// Split the array in half
|
||||
//
|
||||
// ex. [1, 2, 3, 4, 5, 6], [7, 8, 9, blank, blank, blank]
|
||||
|
||||
var split_start = pages_array.length / 2;
|
||||
|
||||
var split_end = pages_array.length;
|
||||
|
||||
var first_array = pages_array.slice(0, split_start);
|
||||
var second_array = pages_array.slice(split_start, split_end);
|
||||
|
||||
// Reverse the second half of the array. This is the beginning of the back half
|
||||
// of the booklet (from the center fold, back to the outside last page)
|
||||
//
|
||||
// ex. [blank, blank, blank, 9, 8, 7]
|
||||
|
||||
var second_array_reversed = second_array.reverse();
|
||||
|
||||
// Zip the two arrays together in groups of 2 These will end up being each '2-up
|
||||
// side' of the final document So, the sub-array at index zero will be the first
|
||||
// side of physical page one and index 1 will be the back side. However, they
|
||||
// won't yet be in the proper order.
|
||||
//
|
||||
// ex. [[1, blank], [2, blank], [3, blank], [4, 9], [5, 8], [6, 7]]
|
||||
|
||||
var page_groups = [];
|
||||
for (var i = 0; i < first_array.length; i++) {
|
||||
page_groups[i] = [
|
||||
first_array[i], second_array_reversed[i]
|
||||
];
|
||||
}
|
||||
|
||||
// We need to reverse every other sub-array starting with the first side. This
|
||||
// is the final step of aligning our booklet pages in the order with which the
|
||||
// booklet gets printed and bound.
|
||||
//
|
||||
// ex. [[blank, 1], [2, blank], [blank, 3], [4, 9], [8, 5], [6, 7]] final_groups
|
||||
// = page_groups.each_with_index { |group, index| group.reverse! if (index %
|
||||
// 2).zero? }
|
||||
var final_groups = [];
|
||||
for (var i = 0; i < page_groups.length; i++) {
|
||||
var group = page_groups[i];
|
||||
if (i % 2 != 0) {
|
||||
final_groups[i] = page_groups[i].reverse();
|
||||
} else {
|
||||
final_groups[i] = page_groups[i];
|
||||
}
|
||||
}
|
||||
console.log("Final Imposition Order: " + final_groups);
|
||||
|
||||
var allPages = document.querySelectorAll(".pagedjs_page");
|
||||
|
||||
var final_flat = final_groups.flat();
|
||||
|
||||
final_flat.forEach((folio, i) => {
|
||||
folio = folio + reset;
|
||||
document
|
||||
.querySelector(`#page-${folio}`)
|
||||
.style
|
||||
.order = i;
|
||||
});
|
||||
}
|
||||
|
||||
}; // before print
|
||||
reorder(pages);
|
||||
}
|
||||
}
|
||||
Paged
|
||||
.registerHandlers(Booklet);
|
||||
|
||||
/**
|
||||
* Pass in an element and its CSS Custom Property that you want the value of.
|
||||
* Optionally, you can determine what datatype you get back.
|
||||
*
|
||||
* @param {String} propKey
|
||||
* @param {HTMLELement} element=document.documentElement
|
||||
* @param {String} castAs='string'
|
||||
* @returns {*}
|
||||
*/
|
||||
const getCSSCustomProp = (
|
||||
propKey,
|
||||
element = document.documentElement,
|
||||
castAs = "string"
|
||||
) => {
|
||||
let response = getComputedStyle(element).getPropertyValue(propKey);
|
||||
|
||||
// Tidy up the string if there's something to work with
|
||||
if (response.length) {
|
||||
response = response
|
||||
.replace(/\'|"/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Convert the response into a whatever type we wanted
|
||||
switch (castAs) {
|
||||
case "number":
|
||||
case "int":
|
||||
return parseInt(response, 10);
|
||||
case "float":
|
||||
return parseFloat(response, 10);
|
||||
case "boolean":
|
||||
case "bool":
|
||||
return response === "true" || response === "1";
|
||||
}
|
||||
|
||||
// Return the string response by default
|
||||
return response;
|
||||
};
|
||||
|
|
@ -98,3 +98,19 @@ div.pagedjs_pages{
|
|||
div#nav{
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
|
||||
.home_pad_iframe {
|
||||
height: 75vh;
|
||||
margin: 5rem;
|
||||
width: calc(100vw - 13rem);
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.home_pad_iframe {
|
||||
margin: 1rem;
|
||||
width: 90vw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,3 +178,14 @@
|
|||
.pagedjs_bleed-right .pagedjs_marks-crop:last-child{
|
||||
box-shadow: 0px -1px 0px 0px var(--pagedjs-crop-shadow);
|
||||
}
|
||||
|
||||
/* Footnotes: clone definitions inline and float them to the footer */
|
||||
/* .pagedjs-footnote-clone {
|
||||
float: footnote;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.pagedjs-footnotes-hidden {
|
||||
display: none;
|
||||
} */
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ window.addEventListener('load', function () {
|
|||
|
||||
<a href="{{ url_for('html', name=name) }}"><button>html</button></a>
|
||||
|
||||
<a href="{{ url_for('pdf', name=name) }}"><button>pdf</button></a>
|
||||
<a href="{{ url_for('pdf', name=name) }}"><button>layout</button></a>
|
||||
|
||||
<a href="{{ url_for('impose', name=name) }}"><button>impose</button></a>
|
||||
</div>`;
|
||||
|
||||
document.body.insertBefore(nav, document.body.firstChild);
|
||||
|
|
|
|||
|
|
@ -2,34 +2,126 @@
|
|||
|
||||
@page{
|
||||
size: A5;
|
||||
margin: 10mm 20mm 25mm 20mm;
|
||||
margin: 10mm 15mm 20mm 15mm;
|
||||
|
||||
@bottom-center{
|
||||
content: counter(page);
|
||||
font-family: monospace;
|
||||
font-family: abordage;
|
||||
font-size: 150%;
|
||||
}
|
||||
}
|
||||
|
||||
@page :first {
|
||||
@bottom-center {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: abordage;
|
||||
src: url(https://chatty-pub-files.hackersanddesigners.nl/files/2/99/nMPuQi6bsXWzzHQcKzByuHnk/abordage-regular.woff);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Director-Regular;
|
||||
src: url(https://chatty-pub-files.hackersanddesigners.nl/files/2/d2/T7cPNlPHYfJD7uGerSbUl2zH/Director-Regular.otf);
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: Latitude;
|
||||
src: url(https://chatty-pub-files.hackersanddesigners.nl/files/2/f8/52Q5ce2-rtPtoRRQpZrwp0X_/Latitude-Regular.otf);
|
||||
}
|
||||
|
||||
body{
|
||||
font-size: 12px;
|
||||
font-family: Latitude;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
color: #822b01;
|
||||
--paged-layout: booklet;
|
||||
}
|
||||
|
||||
/* ------------------------------------ cover */
|
||||
|
||||
@page:first{
|
||||
background-color: #f3c6ff;
|
||||
color: #822b01;
|
||||
@page:first {
|
||||
color: white;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
filter: hue-rotate(17deg);
|
||||
}
|
||||
section#cover{
|
||||
|
||||
.cover{
|
||||
break-after: always;
|
||||
font-family: abordage;
|
||||
margin-top: 50px;
|
||||
padding: 15px;
|
||||
background: #822b01;
|
||||
background-clip: border-box;
|
||||
|
||||
}
|
||||
section#cover h1#title{
|
||||
font-size: 300%;
|
||||
|
||||
.cover h1#title {
|
||||
font-family: abordage;
|
||||
font-size: 500%;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
.cover h2 {
|
||||
font-family: abordage;
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
/* ------------------------------------ main */
|
||||
|
||||
section#main pre{
|
||||
color: magenta;
|
||||
|
||||
h1 {
|
||||
font-family: abordage;
|
||||
font-size: 500%;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
section#main pre{
|
||||
color: black;
|
||||
}
|
||||
|
||||
.main h2 {
|
||||
font-family: abordage;
|
||||
font-size: 180%;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
blockquote{
|
||||
margin-right: 0;
|
||||
font-family: abordage;
|
||||
font-size: 140%;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
/*
|
||||
Images are always grayscale in the main content and
|
||||
are alone on a page
|
||||
*/
|
||||
.main img {
|
||||
display: block;
|
||||
filter: grayscale(100%);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
page-break-after: always;
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
/*
|
||||
*** alone on a line in markdown will be turned into a <hr>
|
||||
we use this a way to force a page break, and hide the hr itself.
|
||||
*/
|
||||
|
||||
hr {
|
||||
break-after: page;
|
||||
border: none;
|
||||
margin: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,56 +5,58 @@ language: en
|
|||
|
||||
<!--
|
||||
|
||||
|
|
||||
__ __ _|_ __ _ _ _ __ __| _
|
||||
/ \_/ | / \_/ |/ |/ | / \_/ | |/
|
||||
\__/ \___/|_/\__/ | | |_/\__/ \_/|_/|__/
|
||||
This document is opened in octomode.
|
||||
|
||||
Octomode is a collective editing space for PDF making that uses Etherpad, Paged.js, and Flask. It was first developed by Varia, building on the knowledge and practices of many designers and developers who work with and contribute to open-source and web-to-print approaches to making publications.
|
||||
|
||||
Please keep in mind:
|
||||
|
||||
|
||||
This document is opened in octomode.
|
||||
* This instance of octomode is hosted on the server of Hackers & Designer's and is subject to H&D's code of conduct: https://hackersanddesigners.nl/code-of-conduct
|
||||
|
||||
pad : all materials for the PDF are collected here (written in Markdown)
|
||||
stylesheet : all CSS rules for the PDF are collected here (written in CSS)
|
||||
html : render the structure of the lay out as a HTML (with PyPandoc)
|
||||
[note] this view does not render any styling!
|
||||
pdf : render the lay out as a PDF (with Paged.js)
|
||||
* The pads are not indexed by search engines, but anyone who knows the URL can read and change them.
|
||||
|
||||
https://git.vvvvvvaria.org/cc/octomode
|
||||
* The contents of the pads are not encrypted, meaning that they are not private.
|
||||
|
||||
* We make our own backups, meaning the the contents of all pads sit on H&D's harddrives potentially indefinitely.
|
||||
|
||||
* We might not be able to respond to pad retrieval requests.
|
||||
|
||||
* H&D is a small collective with changing energies and availabilities, which might affect the availability of the pads
|
||||
|
||||
* If you rely on the content of these pads, please remember to make your own backups.
|
||||
|
||||
|
||||
Octomode is for everyone who enjoys working side by side and looks for ways to escape, divest, and boycott big tech companies that create unaffordable, inaccessible black box software and are complicit in the genocidal war on Palestine.
|
||||
|
||||
How it works:
|
||||
pad: all content for the PDF are collected here (written in Markdown)
|
||||
stylesheet: all CSS rules for the PDF are collected here (written in CSS)
|
||||
html: render the structure of the layout as a HTML (with PyPandoc)
|
||||
layout: render the layout as a PDF, single pages (with Paged.js)
|
||||
imposition: sort pages so they can be printed double sided, folded in the middle an stapled.
|
||||
#MD?: markdown cheatsheet
|
||||
https://git.vvvvvvaria.org/varia/octomode
|
||||
|
||||
-->
|
||||
|
||||
<!--
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
This pad is hosted by CC (creative crowds), a server that we share for doing
|
||||
collective research on the entanglements of tools, cultures and infrastructure.
|
||||
::: cover
|
||||
|
||||
How can this server be available AND unstable, public AND being paid for,
|
||||
free to be used AND situated, a production environment AND in transformation?
|
||||
# Title
|
||||
## Author
|
||||
|
||||
While surfing the contradictions, we are formulting collective guidelines for
|
||||
engaging with this server, which you can find at: https://cc.practices.tools
|
||||
-------------------------------------------------------------------------------
|
||||
Everything else that goes on the cover
|
||||
|
||||
-->
|
||||
|
||||
<section id="cover">
|
||||
# *in octomode* { #title }
|
||||
:::
|
||||
|
||||
|
||||
::: main
|
||||
|
||||
</section>
|
||||
# Title
|
||||
## Author
|
||||
|
||||
<section id="main">
|
||||
Octomode is a collective editing space for PDF making, using Etherpad, Paged.js and Flask.
|
||||
Everything that goes into the zine
|
||||
|
||||
Inspired by the multi-centered, tentacular cognition capabilities of the octopus, we imagined a space in which the artificial boundaries of writing and design can be crossed; where writing, editing and designing can be done in one environment simultaneously, allowing the format to influence the matter and vice-versa.
|
||||
:::
|
||||
|
||||
```
|
||||
Edit this text in the PAD view.
|
||||
Edit the styling in the STYLESHEET view.
|
||||
Preview the page in the HTML view.
|
||||
Render it on pages in the PDF view.
|
||||
```
|
||||
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,22 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="{{ url_for('static', filename='paged.polyfill.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url_for('static', filename='footnotes.js') }}" type="text/javascript"></script>
|
||||
{% if impose %}
|
||||
<script src="{{ url_for('static', filename='imposition.js') }}" type="text/javascript" id="imposition_js"></script>
|
||||
<style>body{
|
||||
--paged-layout: booklet;
|
||||
}</style>
|
||||
{% endif %}
|
||||
{% if cover %}
|
||||
<style>
|
||||
.pagedjs_first_page {
|
||||
background-image: url({{cover}});
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
<link href="{{ url_for('static', filename='pagedjs.css') }}" rel="stylesheet" type="text/css" media="screen">
|
||||
<!-- [LOCAL HACK] mb: added /octomode/ below to make the stylesheet link work again. -->
|
||||
<link href="/octomode/{{ name }}/stylesheet.css" rel="stylesheet" type="text/css" media="print">
|
||||
<link href="{{ url_for('css', name=name) }}" rel="stylesheet" type="text/css" media="print">
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<title>hallo</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<iframe id="pdf" name="pdf" src="{{ url }}"></iframe>
|
||||
{% endblock %}
|
||||
|
|
@ -20,9 +24,12 @@ window.addEventListener('load', function () {
|
|||
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>';
|
||||
// insert the IMPOSE button
|
||||
// const impose = '<a href="#"><button id="impose" onClick="impose()">impose</button></a>';
|
||||
|
||||
// Insert the SAVE button
|
||||
const save = '<a href="#"><button id="save" onClick="printPage()" style="background-color: #66ee66;">save</button></a>';
|
||||
nav.innerHTML = nav.innerHTML + save;
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- [LOCAL HACK] mb: added /octomode/ below to make the stylesheet link work again. -->
|
||||
<link href="/octomode/{{ name }}/stylesheet.css" rel="stylesheet" type="text/css" media="screen">
|
||||
<link href="{{ url_for('css', name=name) }}" rel="stylesheet" type="text/css" media="screen">
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
<meta charset="utf-8" />
|
||||
<title>octomode</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
<body class="start-page">
|
||||
<form action="{{ url_for('index') }}" method="POST">
|
||||
<h1><input type="submit" value="open"> <input type="text" name="name"> <em class="octomode">in octomode</em></h1>
|
||||
</form>
|
||||
<iframe class="home_pad_iframe" src="{{ home_pad_url }}"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in a new issue