1
0
Fork 0
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:
Fred Chasen 2017-04-24 15:42:16 -04:00 committed by GitHub
parent f0856ea78c
commit 32672359f5
7 changed files with 206 additions and 133 deletions

View file

@ -9,77 +9,9 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.1/jszip.min.js"></script> <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> <link rel="stylesheet" type="text/css" href="examples.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-classapplier.js"></script>
<style type="text/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 { ::selection {
background: yellow; background: yellow;
} }
@ -90,51 +22,39 @@
} }
#highlights { #highlights {
list-style: none; list-style: none;
margin-left: 0; margin-left: 0;
padding: 0; padding: 0;
} }
#highlights li { #highlights li {
list-style: none; list-style: none;
margin-bottom: 20px; margin-bottom: 20px;
border-top: 1px solid #E2E2E2; border-top: 1px solid #E2E2E2;
padding: 10px; padding: 10px;
} }
#highlights a { #highlights a {
display: block; display: block;
} }
@media (min-width: 1000px) { #viewer.spreads {
#viewer:after { top: 0;
position: absolute; margin-top: 50px;
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: "";
}
} }
</style> </style>
</head> </head>
<body> <body>
<div id="frame"> <div id="frame">
<div id="viewer"></div> <div id="viewer" class="spreads"></div>
<div id="prev" class="arrow"></div> <a id="prev" href="#prev" class="arrow"></a>
<div id="next" class="arrow"></div> <a id="next" href="#next" class="arrow"></a>
</div> </div>
<div id="extras"> <div id="extras">
<ul id="highlights"></ul> <ul id="highlights"></ul>
</div> </div>
<script> <script>
// Load the opf // Load the opf
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf"); var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
@ -183,36 +103,24 @@
// console.log(location); // 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 // Apply a class to selected text
rendition.on("selected", function(cfiRange) { rendition.on("selected", function(cfiRange, contents) {
// Get the dom range of the selected text contents.highlight(cfiRange, {}, (e) => {
var range = rendition.getRange(cfiRange); console.log("highlight clicked", e.target);
// Create an empty Rangy range });
var rr = rangy.createRange(); contents.window.getSelection().removeAllRanges();
// 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();
}); });
// Add a yellow background to text with our highlight class this.rendition.themes.default({
rendition.hooks.render.register(function (view) { '::selection': {
var highlightColor = [ 'background': 'rgba(255,255,0, 0.3)'
['.annotator-hl', ['background-color', 'yellow']] },
]; '.epubjs-hl' : {
view.addStylesheetRules(highlightColor); 'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'
}) }
});
// Illustration of how to get text from a saved cfiRange // Illustration of how to get text from a saved cfiRange
var highlights = document.getElementById('highlights'); var highlights = document.getElementById('highlights');

101
examples/marks.html Normal file
View 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>

View file

@ -58,7 +58,7 @@ module.exports = function(config) {
loaders: [ loaders: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules\/(?!(marks)\/).*/,
loader: "babel-loader", loader: "babel-loader",
query: { query: {
presets: ['es2015'], presets: ['es2015'],

View file

@ -3,7 +3,6 @@
"version": "0.3.25", "version": "0.3.25",
"description": "Parse and Render Epubs", "description": "Parse and Render Epubs",
"main": "lib/index.js", "main": "lib/index.js",
"jsnext:main": "src/index.js",
"module": "src/index.js", "module": "src/index.js",
"repository": "https://github.com/futurepress/epub.js", "repository": "https://github.com/futurepress/epub.js",
"directories": { "directories": {
@ -73,6 +72,7 @@
"dependencies": { "dependencies": {
"event-emitter": "^0.3.4", "event-emitter": "^0.3.4",
"jszip": "^3.1.3", "jszip": "^3.1.3",
"marks": "github:fchasen/marks",
"path-webpack": "^0.0.3", "path-webpack": "^0.0.3",
"stream-browserify": "^2.0.1", "stream-browserify": "^2.0.1",
"xmldom": "^0.1.27" "xmldom": "^0.1.27"

View file

@ -3,6 +3,7 @@ import {isNumber, prefixed} from "./utils/core";
import EpubCFI from "./epubcfi"; import EpubCFI from "./epubcfi";
import Mapping from "./mapping"; import Mapping from "./mapping";
import {replaceLinks} from "./utils/replacements"; import {replaceLinks} from "./utils/replacements";
import { Pane, Highlight, Underline } from "marks";
// Dom events to listen for // Dom events to listen for
const EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"]; const EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
@ -24,6 +25,8 @@ class Contents {
this.cfiBase = cfiBase || ""; this.cfiBase = cfiBase || "";
this.pane = undefined;
this.listeners(); this.listeners();
} }
@ -307,6 +310,8 @@ class Contents {
height: height height: height
}; };
this.pane && this.pane.render();
this.emit("resize", this._size); this.emit("resize", this._size);
} }
@ -464,7 +469,8 @@ class Contents {
if ( !ready && (!this.readyState || this.readyState == "complete") ) { if ( !ready && (!this.readyState || this.readyState == "complete") ) {
ready = true; ready = true;
// Let apply // Let apply
setTimeout(function(){ setTimeout(() => {
this.pane && this.pane.render();
resolve(true); resolve(true);
}, 1); }, 1);
} }
@ -525,6 +531,7 @@ class Contents {
styleSheet.insertRule(`${selector}{${result}}`, styleSheet.cssRules.length); styleSheet.insertRule(`${selector}{${result}}`, styleSheet.cssRules.length);
}); });
} }
this.pane && this.pane.render();
} }
addScript(src) { addScript(src) {
@ -625,7 +632,7 @@ class Contents {
this.selectionEndTimeout = setTimeout(function() { this.selectionEndTimeout = setTimeout(function() {
var selection = this.window.getSelection(); var selection = this.window.getSelection();
this.triggerSelectedEvent(selection); this.triggerSelectedEvent(selection);
}.bind(this), 500); }.bind(this), 250);
} }
triggerSelectedEvent(selection){ 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() { destroy() {
// Stop observing // Stop observing
if(this.observer) { if(this.observer) {

View file

@ -310,7 +310,7 @@ class Rendition {
*/ */
afterDisplayed(view){ afterDisplayed(view){
this.hooks.content.trigger(view.contents, this); this.hooks.content.trigger(view.contents, this);
this.emit("rendered", view.section); this.emit("rendered", view.section, view);
// this.reportLocation(); // this.reportLocation();
} }
@ -550,11 +550,11 @@ class Rendition {
passEvents(contents){ passEvents(contents){
var listenedEvents = Contents.listenedEvents; var listenedEvents = Contents.listenedEvents;
listenedEvents.forEach(function(e){ listenedEvents.forEach((e) => {
contents.on(e, this.triggerViewEvent.bind(this)); contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
}.bind(this)); });
contents.on("selected", this.triggerSelectedEvent.bind(this)); contents.on("selected", (e) => this.triggerSelectedEvent(e, contents));
} }
/** /**
@ -562,8 +562,8 @@ class Rendition {
* @private * @private
* @param {event} e * @param {event} e
*/ */
triggerViewEvent(e){ triggerViewEvent(e, contents){
this.emit(e.type, e); this.emit(e.type, e, contents);
} }
/** /**
@ -571,8 +571,8 @@ class Rendition {
* @private * @private
* @param {EpubCFI} cfirange * @param {EpubCFI} cfirange
*/ */
triggerSelectedEvent(cfirange){ triggerSelectedEvent(cfirange, contents){
this.emit("selected", cfirange); this.emit("selected", cfirange, contents);
} }
/** /**

View file

@ -44,7 +44,7 @@ module.exports = {
loaders: [ loaders: [
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules\/(?!(marks)\/).*/,
loader: "babel-loader", loader: "babel-loader",
query: LEGACY ? { query: LEGACY ? {
presets: ['es2015'], presets: ['es2015'],