mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-04 15:09:16 +02:00
Add methods to mark a paragraph by cfi (#604)
* Add highlight to contents * Add marks and pass data to highlights * Add marks example * Only add pane when highlight is added * Include marks in babel processing
This commit is contained in:
parent
f0856ea78c
commit
32672359f5
7 changed files with 206 additions and 133 deletions
|
@ -9,77 +9,9 @@
|
|||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.1/jszip.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-classapplier.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="examples.css">
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #333;
|
||||
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
width: 900px;
|
||||
/*width: 80%;*/
|
||||
height: 600px;
|
||||
background: white;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
border-radius: 5px;
|
||||
|
||||
padding: 20px 40px;
|
||||
position: relative;
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
#frame {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#viewer iframe {
|
||||
background: white;
|
||||
}
|
||||
|
||||
#prev {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
#next {
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -32px;
|
||||
font-size: 64px;
|
||||
color: #E2E2E2;
|
||||
font-family: arial, sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.arrow:hover {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.arrow:active {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#toc {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: yellow;
|
||||
}
|
||||
|
@ -90,51 +22,39 @@
|
|||
}
|
||||
|
||||
#highlights {
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
}
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#highlights li {
|
||||
list-style: none;
|
||||
list-style: none;
|
||||
margin-bottom: 20px;
|
||||
border-top: 1px solid #E2E2E2;
|
||||
padding: 10px;
|
||||
border-top: 1px solid #E2E2E2;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#highlights a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
#viewer:after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
border-right: 1px #000 solid;
|
||||
height: 90%;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
margin-left: -1px;
|
||||
top: 5%;
|
||||
opacity: .15;
|
||||
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
|
||||
content: "";
|
||||
}
|
||||
#viewer.spreads {
|
||||
top: 0;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="frame">
|
||||
<div id="viewer"></div>
|
||||
<div id="prev" class="arrow">‹</div>
|
||||
<div id="next" class="arrow">›</div>
|
||||
<div id="viewer" class="spreads"></div>
|
||||
<a id="prev" href="#prev" class="arrow">‹</a>
|
||||
<a id="next" href="#next" class="arrow">›</a>
|
||||
</div>
|
||||
<div id="extras">
|
||||
<ul id="highlights"></ul>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
// Load the opf
|
||||
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
|
||||
|
@ -183,36 +103,24 @@
|
|||
// console.log(location);
|
||||
});
|
||||
|
||||
var applier;
|
||||
|
||||
displayed.then(function(renderer){
|
||||
// wait till something has been rendered to add applier
|
||||
applier = rangy.createClassApplier("annotator-hl");
|
||||
});
|
||||
|
||||
// Apply a class to selected text
|
||||
rendition.on("selected", function(cfiRange) {
|
||||
// Get the dom range of the selected text
|
||||
var range = rendition.getRange(cfiRange);
|
||||
// Create an empty Rangy range
|
||||
var rr = rangy.createRange();
|
||||
// Set that range to equal the dom range
|
||||
rr.setStart(range.startContainer, range.startOffset);
|
||||
rr.setEnd(range.endContainer, range.endOffset);
|
||||
// Add the class to that range
|
||||
applier.applyToRange(rr);
|
||||
// Clear the selection
|
||||
window.getSelection().removeAllRanges();
|
||||
rendition.on("selected", function(cfiRange, contents) {
|
||||
contents.highlight(cfiRange, {}, (e) => {
|
||||
console.log("highlight clicked", e.target);
|
||||
});
|
||||
contents.window.getSelection().removeAllRanges();
|
||||
|
||||
});
|
||||
|
||||
// Add a yellow background to text with our highlight class
|
||||
rendition.hooks.render.register(function (view) {
|
||||
var highlightColor = [
|
||||
['.annotator-hl', ['background-color', 'yellow']]
|
||||
];
|
||||
view.addStylesheetRules(highlightColor);
|
||||
})
|
||||
this.rendition.themes.default({
|
||||
'::selection': {
|
||||
'background': 'rgba(255,255,0, 0.3)'
|
||||
},
|
||||
'.epubjs-hl' : {
|
||||
'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
|
||||
}
|
||||
});
|
||||
|
||||
// Illustration of how to get text from a saved cfiRange
|
||||
var highlights = document.getElementById('highlights');
|
||||
|
|
101
examples/marks.html
Normal file
101
examples/marks.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>EPUB.js Pagination Example</title>
|
||||
|
||||
<script src="../dist/epub.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.1/jszip.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="examples.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="title"></div>
|
||||
<div id="viewer" class="spreads"></div>
|
||||
<a id="prev" href="#prev" class="arrow">‹</a>
|
||||
<a id="next" href="#next" class="arrow">›</a>
|
||||
|
||||
<script>
|
||||
// Load the opf
|
||||
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
|
||||
|
||||
var rendition = book.renderTo("viewer", {
|
||||
width: "100%",
|
||||
height: 600
|
||||
});
|
||||
|
||||
var displayed = rendition.display(6);
|
||||
|
||||
// Navigation loaded
|
||||
book.loaded.navigation.then(function(toc){
|
||||
// console.log(toc);
|
||||
});
|
||||
|
||||
var next = document.getElementById("next");
|
||||
next.addEventListener("click", function(){
|
||||
rendition.next();
|
||||
}, false);
|
||||
|
||||
var prev = document.getElementById("prev");
|
||||
prev.addEventListener("click", function(){
|
||||
rendition.prev();
|
||||
}, false);
|
||||
|
||||
var keyListener = function(e){
|
||||
|
||||
// Left Key
|
||||
if ((e.keyCode || e.which) == 37) {
|
||||
rendition.prev();
|
||||
}
|
||||
|
||||
// Right Key
|
||||
if ((e.keyCode || e.which) == 39) {
|
||||
rendition.next();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
rendition.on("keyup", keyListener);
|
||||
document.addEventListener("keyup", keyListener, false);
|
||||
|
||||
rendition.on("locationChanged", function(location){
|
||||
// console.log(location);
|
||||
});
|
||||
|
||||
|
||||
// Apply a class to selected text
|
||||
rendition.on("selected", function(cfiRange, contents) {
|
||||
|
||||
contents.mark(cfiRange, {'something' : true}, (e) => {
|
||||
console.log("mark clicked", e.target);
|
||||
});
|
||||
|
||||
contents.window.getSelection().removeAllRanges();
|
||||
|
||||
});
|
||||
|
||||
this.rendition.themes.default({
|
||||
'p': {
|
||||
'padding': '0 35px',
|
||||
'position': 'relative'
|
||||
},
|
||||
'[ref="epubjs-mk"]::before' : {
|
||||
'content': '""',
|
||||
'background': 'url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=") no-repeat',
|
||||
'display': 'block',
|
||||
'right' : '0',
|
||||
'position' : 'absolute',
|
||||
'width': '30px',
|
||||
'height': '30px',
|
||||
'cursor': 'pointer'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -58,7 +58,7 @@ module.exports = function(config) {
|
|||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
exclude: /node_modules\/(?!(marks)\/).*/,
|
||||
loader: "babel-loader",
|
||||
query: {
|
||||
presets: ['es2015'],
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
"version": "0.3.25",
|
||||
"description": "Parse and Render Epubs",
|
||||
"main": "lib/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"module": "src/index.js",
|
||||
"repository": "https://github.com/futurepress/epub.js",
|
||||
"directories": {
|
||||
|
@ -73,6 +72,7 @@
|
|||
"dependencies": {
|
||||
"event-emitter": "^0.3.4",
|
||||
"jszip": "^3.1.3",
|
||||
"marks": "github:fchasen/marks",
|
||||
"path-webpack": "^0.0.3",
|
||||
"stream-browserify": "^2.0.1",
|
||||
"xmldom": "^0.1.27"
|
||||
|
|
|
@ -3,6 +3,7 @@ import {isNumber, prefixed} from "./utils/core";
|
|||
import EpubCFI from "./epubcfi";
|
||||
import Mapping from "./mapping";
|
||||
import {replaceLinks} from "./utils/replacements";
|
||||
import { Pane, Highlight, Underline } from "marks";
|
||||
|
||||
// Dom events to listen for
|
||||
const EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
|
||||
|
@ -24,6 +25,8 @@ class Contents {
|
|||
|
||||
this.cfiBase = cfiBase || "";
|
||||
|
||||
this.pane = undefined;
|
||||
|
||||
this.listeners();
|
||||
}
|
||||
|
||||
|
@ -307,6 +310,8 @@ class Contents {
|
|||
height: height
|
||||
};
|
||||
|
||||
this.pane && this.pane.render();
|
||||
|
||||
this.emit("resize", this._size);
|
||||
}
|
||||
|
||||
|
@ -464,7 +469,8 @@ class Contents {
|
|||
if ( !ready && (!this.readyState || this.readyState == "complete") ) {
|
||||
ready = true;
|
||||
// Let apply
|
||||
setTimeout(function(){
|
||||
setTimeout(() => {
|
||||
this.pane && this.pane.render();
|
||||
resolve(true);
|
||||
}, 1);
|
||||
}
|
||||
|
@ -525,6 +531,7 @@ class Contents {
|
|||
styleSheet.insertRule(`${selector}{${result}}`, styleSheet.cssRules.length);
|
||||
});
|
||||
}
|
||||
this.pane && this.pane.render();
|
||||
}
|
||||
|
||||
addScript(src) {
|
||||
|
@ -625,7 +632,7 @@ class Contents {
|
|||
this.selectionEndTimeout = setTimeout(function() {
|
||||
var selection = this.window.getSelection();
|
||||
this.triggerSelectedEvent(selection);
|
||||
}.bind(this), 500);
|
||||
}.bind(this), 250);
|
||||
}
|
||||
|
||||
triggerSelectedEvent(selection){
|
||||
|
@ -747,6 +754,63 @@ class Contents {
|
|||
});
|
||||
}
|
||||
|
||||
highlight(cfiRange, data={}, cb) {
|
||||
let range = this.range(cfiRange);
|
||||
|
||||
data["epubcfi"] = cfiRange;
|
||||
|
||||
if (!this.pane) {
|
||||
this.pane = new Pane(this.content, this.document.body);
|
||||
}
|
||||
|
||||
let m = new Highlight(range, "epubjs-hl", data, {'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'});
|
||||
let h = this.pane.addMark(m);
|
||||
if (cb) {
|
||||
h.element.addEventListener("click", cb);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
underline(cfiRange, data={}, cb) {
|
||||
let range = this.range(cfiRange);
|
||||
|
||||
data["epubcfi"] = cfiRange;
|
||||
|
||||
if (!this.pane) {
|
||||
this.pane = new Pane(this.content, this.document.body);
|
||||
}
|
||||
|
||||
let m = new Underline(range, "epubjs-ul", data, {'stroke': 'black', 'stroke-opacity': '0.3', 'mix-blend-mode': 'multiply'});
|
||||
let h = this.pane.addMark(m);
|
||||
if (cb) {
|
||||
h.element.addEventListener("click", cb);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
mark(cfiRange, data={}, cb) {
|
||||
let range = this.range(cfiRange);
|
||||
|
||||
let container = range.commonAncestorContainer;
|
||||
let parent = (container.nodeType === 1) ? container : container.parentNode;
|
||||
|
||||
parent.setAttribute("ref", "epubjs-mk");
|
||||
|
||||
parent.dataset["epubcfi"] = cfiRange;
|
||||
|
||||
if (data) {
|
||||
Object.entries(data).forEach(([key, val]) => {
|
||||
parent.dataset[key] = val;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (cb) {
|
||||
parent.addEventListener("click", cb);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Stop observing
|
||||
if(this.observer) {
|
||||
|
|
|
@ -310,7 +310,7 @@ class Rendition {
|
|||
*/
|
||||
afterDisplayed(view){
|
||||
this.hooks.content.trigger(view.contents, this);
|
||||
this.emit("rendered", view.section);
|
||||
this.emit("rendered", view.section, view);
|
||||
// this.reportLocation();
|
||||
}
|
||||
|
||||
|
@ -550,11 +550,11 @@ class Rendition {
|
|||
passEvents(contents){
|
||||
var listenedEvents = Contents.listenedEvents;
|
||||
|
||||
listenedEvents.forEach(function(e){
|
||||
contents.on(e, this.triggerViewEvent.bind(this));
|
||||
}.bind(this));
|
||||
listenedEvents.forEach((e) => {
|
||||
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
|
||||
});
|
||||
|
||||
contents.on("selected", this.triggerSelectedEvent.bind(this));
|
||||
contents.on("selected", (e) => this.triggerSelectedEvent(e, contents));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -562,8 +562,8 @@ class Rendition {
|
|||
* @private
|
||||
* @param {event} e
|
||||
*/
|
||||
triggerViewEvent(e){
|
||||
this.emit(e.type, e);
|
||||
triggerViewEvent(e, contents){
|
||||
this.emit(e.type, e, contents);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -571,8 +571,8 @@ class Rendition {
|
|||
* @private
|
||||
* @param {EpubCFI} cfirange
|
||||
*/
|
||||
triggerSelectedEvent(cfirange){
|
||||
this.emit("selected", cfirange);
|
||||
triggerSelectedEvent(cfirange, contents){
|
||||
this.emit("selected", cfirange, contents);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,7 +44,7 @@ module.exports = {
|
|||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
exclude: /node_modules\/(?!(marks)\/).*/,
|
||||
loader: "babel-loader",
|
||||
query: LEGACY ? {
|
||||
presets: ['es2015'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue