Commit 56441266 authored by Fred Chasen's avatar Fred Chasen

init

parent 601a6ff8
{
"presets": [["@babel/preset-env", {
"useBuiltIns": "usage"
}]],
"plugins": ["@babel/plugin-transform-runtime"]
}
lib
dist
node_modules
# marginalia
# Marginalia
## Demo
A demo is hosted at http://marginalia.press
## Local
To run locally, clone the repo then run the following:
`npm install`
`npm start`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Marginalia - Pagedjs Epub Viewer</title>
<meta content="width=device-width, initial-scale=1" name="viewport">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.1/jszip.min.js"></script>
<!-- <script src="https://unpkg.com/epubjs@0.4.2/dist/epub.min.js"></script> -->
<script src="node_modules/pagedjs/dist/paged.js"></script>
<script src="dist/main.js"></script>
<link rel="stylesheet" type="text/css" href="styles/main.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<a id="opener">
<i class="material-icons">menu</i>
</a>
<div id="navigation">
<div id="header">
<a id="closer">
<i class="material-icons">close</i>
</a>
</div>
<h1 id="title">...</h1>
<image id="cover" width="150px"/>
<h2 id="author">...</h2>
<ul id="toc"></ul>
</div>
<div id="main">
<div id="controls">
<header id="logo">
<h1>Marginalia Press</h1>
<p>Apply CSS Paged Media print styles for any ePub</p>
</header>
<form id="form" class="box" method="post" action="" enctype="multipart/form-data">
<div class="box__input">
<i class="material-icons box__icon">file_download</i>
<input class="box__file" type="file" name="files[]" id="input" />
<label for="file"><a id="choose"><strong>Choose an ePub</strong></a><span class="box__dragndrop"> or drag it here</span>.</label>
</div>
<div class="box__uploading">Uploading&hellip;</div>
<div class="box__success">Done!</div>
<div class="box__error">Error! <span></span>.</div>
</form>
</div>
<div id="viewer"></div>
<a id="prev" href="#prev" class="arrow">
<i class="material-icons">chevron_left</i>
</a>
<a id="next" href="#next" class="arrow">
<i class="material-icons">chevron_right</i>
</a>
</div>
</body>
</html>
This diff is collapsed.
{
"name": "marginalia-press",
"version": "0.0.1",
"description": "Epub Previewer for Pagedjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "./node_modules/.bin/webpack-dev-server",
"compile": "./node_modules/.bin/babel src/ -d lib/",
"watch": "./node_modules/.bin/babel src/ -w -d lib/",
"build": "./node_modules/.bin/webpack",
"build-watch": "./node_modules/.bin/webpack -w",
"copy": "cp -R src/styles/ dist/styles/",
"prepare": "npm run compile && npm run build"
},
"repository": {
"type": "git",
"url": "https://gitlab.pagedmedia.org/tools/previewer.git"
},
"author": "fchasen@gmail.com",
"license": "MIT",
"dependencies": {
"@babel/polyfill": "^7.2.5",
"@babel/runtime": "^7.3.4",
"epubjs": "^0.4.2",
"jszip": "^3.1.1",
"pagedjs": "^0.1.31"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.3.4",
"@babel/plugin-transform-runtime": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"babel-loader": "^8.0.5",
"css-loader": "^2.1.1",
"http-server": "^0.11.1",
"style-loader": "^0.23.1",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1"
}
}
import ready from './ready';
import Reader from './reader';
ready.then(async function () {
let params = URLSearchParams && new URLSearchParams(document.location.search.substring(1));
let manifest = params && params.get("manifest") && decodeURIComponent(params.get("manifest"));
let reader = new Reader(manifest);
});
import ePub from 'epubjs';
import 'jszip';
class Manifest {
constructor() {
}
async open(bookData) {
this.book = await ePub(bookData, {replacements: true} );
return this.process(this.book);
}
async process(book) {
this.metadata(book.metadata);
this.cover(book.cover);
this.toc(book.toc);
this.stylesheets = this.styles(book.resources);
return this.contents(book.readingOrder || book.spine);
}
metadata(meta) {
var $title = document.getElementById("title");
var $author = document.getElementById("author");
$title.textContent = meta.title;
$author.textContent = meta.creator;
}
cover(cover) {
var $cover = document.getElementById("cover");
if (cover) {
$cover.src = cover;
} else {
$cover.style.display = "none";
}
}
toc(toc=[]) {
console.log(toc);
let nav = document.getElementById("toc");
let docfrag = document.createDocumentFragment();
toc.forEach((chapter) => {
this.tocItem(chapter, docfrag);
});
nav.appendChild(docfrag);
}
tocItem(chapter, parent) {
let item = document.createElement("li");
let link = document.createElement("a");
link.id = "chap-" + chapter.id;
link.textContent = chapter.title;
link.href = "#" + encodeURIComponent(chapter.href);
item.appendChild(link);
parent.appendChild(item);
link.onclick = function(){
let nav = document.getElementById("toc");
nav.classList.remove("open");
};
if (chapter.subitems && chapter.subitems.length) {
var ul = document.createElement("ul");
item.appendChild(ul);
chapter.subitems.forEach(function(subchapter) {
this.tocItem(subchapter, ul);
});
}
// link.onclick = function(){
// var url = link.getAttribute("href");
// console.log(url)
// rendition.display(url);
// return false;
// };
}
async contents(spine) {
let text = "";
let pattern = /<body[^>]*>((.|[\n\r])*)<\/body>/im;
let main = /<main[^>]*>((.|[\n\r])*)<\/main>/im;
let links = /<link[^>]*rel\s*=\s*["']\s*stylesheet\s*["'][^>]*>/im;
for (let section of spine) {
let href = section.href;
let styles = "";
let html = await fetch(href)
.then((response) => {
return response.text();
}).then((t) => {
let matches = pattern.exec(t);
styles = links.exec(t);
return matches && matches.length && matches[1];
}).then((t) => {
let matches = main.exec(t);
return matches && matches.length ? matches[1] : t;
});
let id = encodeURIComponent(section.href);
text += "<article id='"+id+"' class='marginalia-section'>";
// if (styles) {
// for (let i = 0; i < styles.length; i++) {
// text += styles[i];
// }
// }
text += html + "</article>";
}
return text;
}
styles(resources) {
return resources.filter((r) => {
return r.type === "text/css";
});
}
}
export default Manifest;
import Uploader from './uploader';
import Manifest from './manifest';
class Reader {
constructor(manifest) {
this.uploader = new Uploader(this.uploaded.bind(this));
if (manifest) {
this.process(manifest);
}
}
showUi() {
let nav = document.getElementById("navigation");
let opener = document.getElementById("opener");
opener.style.visibility = "hidden";
opener.addEventListener("click", this.open, false);
let closer = document.getElementById("closer");
closer.addEventListener("click", this.close, false);
opener.style.visibility = "visible";
}
open() {
let nav = document.getElementById("navigation");
nav.classList.add("open");
}
close() {
let nav = document.getElementById("navigation");
nav.classList.remove("open");
}
hideInput() {
let form = document.getElementById("form");
form.style.display = "none";
let logo = document.getElementById("logo");
logo.style.display = "none";
}
async uploaded(bookData) {
this.hideInput();
this.showUi();
this.manifest = new Manifest();
let text = await this.manifest.open(bookData);
this.render(text);
let opener = document.getElementById("opener");
opener.style.visibility = "visible";
}
async process(manifestUrl) {
this.hideInput();
this.showUi();
let manifestJson = await fetch(manifestUrl)
.then((response) => {
return response.json();
});
// absolute links
manifestJson.readingOrder.forEach((item) => {
item.href = new URL(item.href, manifestUrl).toString();
})
this.manifest = new Manifest();
let text = await this.manifest.process(manifestJson);
this.render(text);
}
async render(text) {
this.clear();
this.chunker = new Paged.Chunker()
this.polisher = new Paged.Polisher();
Paged.initializeHandlers(this.chunker, this.polisher);
let viewer = document.querySelector("#viewer");
this.chunker.on("rendering", () => {
this.resizer();
});
window.addEventListener("resize", this.resizer.bind(this), false);
await this.addStyles(this.manifest.stylesheets);
return this.chunker.flow(text, viewer);
}
resizer() {
let pages = document.querySelector(".pagedjs_pages");
let scale = ((window.innerWidth ) / pages.offsetWidth) * .75;
let style = document.createElement('style');
style.setAttribute("media", "screen");
document.head.appendChild(style);
let sheet = style.sheet;
if (scale < 1) {
sheet.insertRule(`#viewer .pagedjs_pages { transform: scale(${scale}); margin-left: ${(window.innerWidth / 2) - ((pages.offsetWidth/ 2) ) * scale }px }`, sheet.cssRules.length);
} else {
pages.style.transform = "none";
pages.style.marginLeft = "0";
}
}
addStyles(stylesArray) {
let toAdd = ["styles/epub.css"];
for (let style of stylesArray) {
toAdd.push(style.href);
}
return this.polisher.add(...toAdd);
}
clear() {
let pages = document.querySelectorAll(".section");
for (var i = 0; i < pages.length; i++) {
pages[i].remove();
}
}
destroy() {
}
}
export default Reader;
export default new Promise(function(resolve, reject){
if (document.readyState === "interactive" || document.readyState === "complete") {
resolve(document.readyState);
return;
}
document.onreadystatechange = function ($) {
if (document.readyState === "interactive") {
resolve(document.readyState);
}
}
});
class Renderer {
constructor(manifest) {
}
addStyles(polisher, resources) {
let head = document.querySelector("head");
let stylesArray = resources.filter((r) => {
return r.type === "text/css";
});
let toAdd = ["styles/epub.css"];
for (let style of stylesArray) {
// let link = document.createElement("link");
// link.rel = "stylesheet";
// link.type = "text/css";
// link.href = style.href;
// head.appendChild(link);
toAdd.push(style.href);
}
return polisher.add(...toAdd) //.apply(styles, toAdd);
}
clear() {
let pages = document.querySelectorAll(".section");
for (var i = 0; i < pages.length; i++) {
pages[i].remove();
}
}
}
export default Renderer;
class Uploader {
constructor(onUpload) {
this.onUpload = onUpload;
this.form = document.getElementById("form");
this.form.addEventListener("dragenter", this.dragenter.bind(this), false);
this.form.addEventListener("dragover", this.dragover.bind(this), false);
this.form.addEventListener("dragleave", this.dragleave.bind(this), false);
this.form.addEventListener("drop", this.drop.bind(this), false);
this.inputListener();
}
dragenter(e) {
e.stopPropagation();
e.preventDefault();
this.form.classList.add("is-dragover");
}
dragover(e) {
e.stopPropagation();
e.preventDefault();
}
dragleave(e) {
e.stopPropagation();
e.preventDefault();
this.form.classList.remove("is-dragover");
}
drop(e) {
e.stopPropagation();
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
this.upload(files[0]);
}
inputListener() {
let inputElement = document.getElementById("input");
let choose = document.getElementById("choose");
choose.addEventListener("click", () => {
inputElement.click();
}, false);
inputElement.addEventListener('change', (e) => {
var file = e.target.files[0];
this.upload(file);
this.form.style.display = "none";
});
}
upload(file) {
if (window.FileReader) {
var reader = new FileReader();
reader.onload = this.result.bind(this);
reader.readAsArrayBuffer(file);
}
}
result(e) {
this.onUpload(e.target.result);
}
destroy() {
}
}
export default Uploader;
@page {
size: 8.5in 11in;
margin: 20mm 25mm;
/* marks: crop; */
@footnote {
margin: 0.6em 0 0 0;
padding: 0.3em 0 0 0;
max-height: 10em;
}
@top-center {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(booktitle, first-except);
}
}
@page :left {
margin: 20mm 40mm 20mm 30mm;
@top-left {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(page-number, first-except);
letter-spacing: 0.1em;
margin-left: -1em;
font-size: 0.9em;
}
@bottom-left {
content: counter(page);
}
}
@page :right {
margin: 20mm 30mm 20mm 40mm;
@top-right {
vertical-align: bottom;
padding-bottom: 10mm;
content: string(page-number, first-except);
letter-spacing: 0.08em;
margin-right: -1em;
font-size: 0.9em;
}
@bottom-right {
content: counter(page);
text-align: right;
width: 100%;
}
@top-center{
content: string(booktitle, first-except);
}
}
@page cover {
@top-center{
content: none;
}
}
@page:first {
margin: 0;
@top-left{ content: none; }
@top-center{ content: none; }
@top-right{ content: none; }
@bottom-left{ content: none; }
}
@page:blank {
@top-left-corner{ content: none; }
@top-left{ content: none; }
@top-center{ content: none; }
@top-right{ content: none; }
@top-right-corner{ content: none; }
@right-top{ content: none; }
@right-middle{ content: none; }
@right-bottom{ content: none; }
@bottom-right-corner{ content: none; }
@bottom-right{ content: none; }
@bottom-center{ content: none; }
@bottom-left{ content: none; }
@bottom-left-corner{ content: none; }
@left-bottom{ content: none; }
@left-middle{ content: none; }
@left-top{ content: none; }
}
@font-face {
font-family: 'Stix';
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Stix';
font-weight: bold;
font-style: normal;
}
@font-face {
font-family:'Stix';
font-weight: normal;
font-style: italic;
}
@font-face {
font-family:'Stix';
font-weight: bold;
font-style: italic;
}
article.marginalia-section:nth-child(1) h1, article.marginalia-section:nth-child(1) .title {
string-set: booktitle content(text);
}
article.marginalia-section {
break-before: right;
break-after: always;
}
body {
font-size: 1em;
line-height: 1.33em;
font-family: 'Stix', serif;
font-variant-numeric: oldstyle-nums;
}
p {
padding: 0;
margin: 0;
text-align: justify;
}
header + p:first-line {
text-transform: lowercase !important;
font-variant: small-caps;
font-size: 1.1em;
}
p + p {
text-indent: 1.33em;
}
.decoration-rw {
text-align: center;
padding-top: 1em;
padding-bottom: 1em;
padding-left: 0em;
padding-right: 0em;
}
.copyright-rw {
font-size: .8em;
line-height: 1em;
text-align: left;
padding-top: 2em;
}
.copyright-rw p {
margin-bottom: 1em;
text-indent: 0;
}
.dedication-rw p {
text-align: center;
text-indent: 0;
}
.Dedication-rw {
margin-top: 3em;
}
.leading-line-rw {
height: 1.33em;
}
.fp-rw {
text-indent: 0;
}
h1 {
font-size: 1.5em;
line-height: 1.33em;
text-align: center;
padding-bottom: 0em;
text-align: center;
text-transform: uppercase;
font-weight: normal;
letter-spacing: 4px;
}
h2 {
text-align: center;
font-size: 1.33em;
line-height: 1.2em;
text-align: center;
padding-bottom: 0em;
text-align: center;