mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-04 15:09:16 +02:00
add incomplete epubcfi update
This commit is contained in:
parent
57df816060
commit
05f1b92094
9 changed files with 1006 additions and 1798 deletions
2
books
2
books
|
@ -1 +1 @@
|
||||||
Subproject commit e9790315c2510315e270a7a4c4921825e9918039
|
Subproject commit 8d6c46ef23ca637d89e66b18b2146ccef93c1ac4
|
2008
dist/epub.js
vendored
2008
dist/epub.js
vendored
File diff suppressed because it is too large
Load diff
2
dist/epub.js.map
vendored
2
dist/epub.js.map
vendored
File diff suppressed because one or more lines are too long
|
@ -126,7 +126,7 @@
|
||||||
height: 600
|
height: 600
|
||||||
});
|
});
|
||||||
|
|
||||||
var displayed = rendition.display();
|
var displayed = rendition.display(3);
|
||||||
|
|
||||||
|
|
||||||
displayed.then(function(renderer){
|
displayed.then(function(renderer){
|
||||||
|
@ -165,6 +165,9 @@
|
||||||
rendition.on("keyup", keyListener);
|
rendition.on("keyup", keyListener);
|
||||||
document.addEventListener("keyup", keyListener, false);
|
document.addEventListener("keyup", keyListener, false);
|
||||||
|
|
||||||
|
rendition.on("selected", function(range) {
|
||||||
|
console.log("selected", range);
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -43,9 +43,9 @@ gulp.task('minify', ['bundle'], function(){
|
||||||
return gulp.src('dist/epub.js')
|
return gulp.src('dist/epub.js')
|
||||||
.pipe(plumber({ errorHandler: onError }))
|
.pipe(plumber({ errorHandler: onError }))
|
||||||
.pipe(rename('epub.min.js'))
|
.pipe(rename('epub.min.js'))
|
||||||
.pipe(sourcemaps.init({loadMaps: true}))
|
// .pipe(sourcemaps.init({loadMaps: true}))
|
||||||
.pipe(uglify(uglifyOptions))
|
.pipe(uglify(uglifyOptions))
|
||||||
.pipe(sourcemaps.write('./'))
|
// .pipe(sourcemaps.write('./'))
|
||||||
.pipe(size({ showFiles: true }))
|
.pipe(size({ showFiles: true }))
|
||||||
.pipe(gulp.dest('dist'));
|
.pipe(gulp.dest('dist'));
|
||||||
});
|
});
|
||||||
|
@ -61,7 +61,7 @@ gulp.task('serve', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
gulp.task('default', ['lint', 'build']);
|
gulp.task('default', ['lint', 'bundle']);
|
||||||
|
|
||||||
|
|
||||||
function bundle(file, watch) {
|
function bundle(file, watch) {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"author": "fchasen@gmail.com",
|
"author": "fchasen@gmail.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"browserify": "^12.0.1",
|
||||||
"colors": "^0.6.2",
|
"colors": "^0.6.2",
|
||||||
"connect": "^3.0.1",
|
"connect": "^3.0.1",
|
||||||
"express": "^4.5.1",
|
"express": "^4.5.1",
|
||||||
|
|
762
src/epubcfi.js
762
src/epubcfi.js
|
@ -1,86 +1,248 @@
|
||||||
var URI = require('urijs');
|
var URI = require('urijs');
|
||||||
var core = require('./core');
|
var core = require('./core');
|
||||||
|
|
||||||
function EpubCFI(cfiStr){
|
/**
|
||||||
if(cfiStr) return this.parse(cfiStr);
|
EPUB CFI spec: http://www.idpf.org/epub/linking/cfi/epub-cfi.html
|
||||||
|
|
||||||
|
Implements:
|
||||||
|
- Character Offset: epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)
|
||||||
|
- Simple Ranges : epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)
|
||||||
|
|
||||||
|
Does Not Implement:
|
||||||
|
- Temporal Offset (~)
|
||||||
|
- Spatial Offset (@)
|
||||||
|
- Temporal-Spatial Offset (~ + @)
|
||||||
|
- Text Location Assertion ([)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function EpubCFI(cfiFrom, base, options){
|
||||||
|
var type;
|
||||||
|
this.options = {
|
||||||
|
ignoreClass: 'annotator-hl'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.str = '';
|
||||||
|
|
||||||
|
this.base = {};
|
||||||
|
this.spinePos = 0; // For compatibility
|
||||||
|
|
||||||
|
this.range = false; // true || false;
|
||||||
|
|
||||||
|
this.path = {};
|
||||||
|
this.start = null;
|
||||||
|
this.end = null;
|
||||||
|
|
||||||
|
// Allow instantiation without the 'new' keyword
|
||||||
|
if (!(this instanceof EpubCFI)) {
|
||||||
|
return new URI(cfiFrom, base, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find options
|
||||||
|
for (var i = 1, length = arguments.length; i < length; i++) {
|
||||||
|
if(typeof arguments[i] === 'object' && (arguments[i].ignoreClass)) {
|
||||||
|
core.extend(this.options, arguments[i]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Object that includes:
|
||||||
|
{
|
||||||
|
spineNodeIndex: <int>
|
||||||
|
index: <int>
|
||||||
|
idref: <string:optional>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if(typeof base === 'string') {
|
||||||
|
this.base = this.parseComponent(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
type = this.checkType(cfiFrom);
|
||||||
|
|
||||||
|
|
||||||
|
if(type === 'string') {
|
||||||
|
this.str = cfiFrom;
|
||||||
|
return core.extend(this, this.parse(cfiFrom));
|
||||||
|
}
|
||||||
|
else if(type === 'range') {
|
||||||
|
this.fromRange(cfiFrom);
|
||||||
|
} else if(type === 'node') {
|
||||||
|
this.fromNode(cfiFrom);
|
||||||
|
} else if(type === 'EpubCFI') {
|
||||||
|
return cfiFrom;
|
||||||
|
} else {
|
||||||
|
throw new TypeError('not a valid argument for EpubCFI');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.generateChapterComponent = function(_spineNodeIndex, _pos, id) {
|
EpubCFI.prototype.checkType = function(cfi) {
|
||||||
var pos = parseInt(_pos),
|
// is a cfi string, should be wrapped with "epubcfi()"
|
||||||
spineNodeIndex = _spineNodeIndex + 1,
|
if (typeof cfi === 'string' &&
|
||||||
cfi = '/'+spineNodeIndex+'/';
|
cfi.indexOf("epubcfi(") === 0 &&
|
||||||
|
cfi[cfi.length-1] === ")") {
|
||||||
|
return 'string';
|
||||||
|
// Is a range object
|
||||||
|
} else (typeof cfi === 'object' && cfi.startContainer && cfi.startOffset){
|
||||||
|
return 'range'
|
||||||
|
} else ((typeof cfi === 'object') && cfi.nodeType){ // || typeof cfi === 'function'
|
||||||
|
return 'node'
|
||||||
|
} else (typeof cfi === 'object' && cfi instanceof EpubCFI){
|
||||||
|
return 'EpubCFI'
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
cfi += (pos + 1) * 2;
|
EpubCFI.prototype.parse = function(cfiStr) {
|
||||||
|
var cfi = {
|
||||||
|
spinePos: -1,
|
||||||
|
range: false,
|
||||||
|
base: {},
|
||||||
|
path: {},
|
||||||
|
start: null
|
||||||
|
end: null
|
||||||
|
};
|
||||||
|
var baseComponent, pathComponent, range;
|
||||||
|
|
||||||
if(id) cfi += "[" + id + "]";
|
|
||||||
|
|
||||||
//cfi += "!";
|
if(typeof cfiStr !== "string") {
|
||||||
|
return {spinePos: -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cfiStr.indexOf("epubcfi(") === 0 && cfiStr[cfiStr.length-1] === ")") {
|
||||||
|
// Remove intial epubcfi( and ending )
|
||||||
|
cfiStr = cfiStr.slice(8, cfiStr.length-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
baseComponent = this.getChapterComponent(cfiStr);
|
||||||
|
|
||||||
|
// Make sure this is a valid cfi or return
|
||||||
|
if(!baseComponent) {
|
||||||
|
return {spinePos: -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
cfi.base = this.parseComponent(baseComponent);
|
||||||
|
|
||||||
|
pathComponent = this.getPathComponent(cfiStr);
|
||||||
|
cfi.path = this.parseComponent(pathComponent);
|
||||||
|
|
||||||
|
range = this.getRange(cfiStr);
|
||||||
|
|
||||||
|
if(range) {
|
||||||
|
cfi.range = true;
|
||||||
|
cfi.start = this.parseComponent(range[0]);
|
||||||
|
cfi.end = this.parseComponent(range[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get spine node position
|
||||||
|
// cfi.spineSegment = cfi.base.steps[1];
|
||||||
|
|
||||||
|
// Chapter segment is always the second one
|
||||||
|
cfi.spinePos = cfi.base.steps[2];
|
||||||
|
|
||||||
return cfi;
|
return cfi;
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.generatePathComponent = function(steps) {
|
EpubCFI.prototype.parseComponent = function(componentString){
|
||||||
var parts = [];
|
var component = {
|
||||||
|
steps: [],
|
||||||
|
terminal: null
|
||||||
|
};
|
||||||
|
|
||||||
steps.forEach(function(part){
|
var parts = componentString.split(':');
|
||||||
var segment = '';
|
var steps = parts[0].split('/');
|
||||||
segment += (part.index + 1) * 2;
|
var terminal;
|
||||||
|
|
||||||
if(part.id) {
|
if(parts.length > 1) {
|
||||||
segment += "[" + part.id + "]";
|
terminal = parts[1];
|
||||||
}
|
component.terminal = this.parseTerminal(terminal);
|
||||||
|
}
|
||||||
|
|
||||||
parts.push(segment);
|
component.steps = steps.map(function(step){
|
||||||
});
|
return this.parseStep(part);
|
||||||
|
}.bind(this));
|
||||||
return parts.join('/');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.generateCfiFromElement = function(element, chapter) {
|
EpubCFI.prototype.parseStep = function(stepString){
|
||||||
var steps = this.pathTo(element);
|
var type, num, index, has_brackets, id;
|
||||||
var path = this.generatePathComponent(steps);
|
|
||||||
if(!path.length) {
|
has_brackets = part.match(/\[(.*)\]/);
|
||||||
// Start of Chapter
|
if(has_brackets && has_brackets[1]){
|
||||||
return "epubcfi(" + chapter + "!/4/)";
|
id = has_brackets[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
//-- Check if step is a text node or element
|
||||||
|
num = parseInt(stepString);
|
||||||
|
|
||||||
|
if(isNaN(num)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(num % 2 === 0) { // Even = is an element
|
||||||
|
type = "element"
|
||||||
|
index = num / 2 - 1;
|
||||||
} else {
|
} else {
|
||||||
// First Text Node
|
type = "text";
|
||||||
return "epubcfi(" + chapter + "!" + path + "/1:0)";
|
index = (num - 1 ) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type" : type,
|
||||||
|
'index' : index,
|
||||||
|
'id' : id || null
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.pathTo = function(node) {
|
EpubCFI.prototype.parseTerminal = function(termialString){
|
||||||
var stack = [],
|
var characterOffset, textLocationAssertion;
|
||||||
children;
|
var assertion = charecterOffsetComponent.match(/\[(.*)\]/);
|
||||||
|
|
||||||
while(node && node.parentNode !== null && node.parentNode.nodeType != 9) {
|
if(assertion && assertion[1]){
|
||||||
children = node.parentNode.children;
|
characterOffset = parseInt(charecterOffsetComponent.split('[')[0]);
|
||||||
|
textLocationAssertion = assertion[1];
|
||||||
stack.unshift({
|
} else {
|
||||||
'id' : node.id,
|
characterOffset = parseInt(charecterOffsetComponent);
|
||||||
// 'classList' : node.classList,
|
|
||||||
'tagName' : node.tagName,
|
|
||||||
'index' : children ? Array.prototype.indexOf.call(children, node) : 0
|
|
||||||
});
|
|
||||||
|
|
||||||
node = node.parentNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack;
|
return {
|
||||||
|
'offset': characterOffset,
|
||||||
|
'assertion': textLocationAssertion
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.getChapterComponent = function(cfiStr) {
|
EpubCFI.prototype.getChapterComponent = function(cfiStr) {
|
||||||
|
|
||||||
var splitStr = cfiStr.split("!");
|
var indirection = cfiStr.split("!");
|
||||||
|
|
||||||
return splitStr[0];
|
return indirection[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.getPathComponent = function(cfiStr) {
|
EpubCFI.prototype.getPathComponent = function(cfiStr) {
|
||||||
|
|
||||||
var splitStr = cfiStr.split("!");
|
var indirection = cfiStr.split("!");
|
||||||
var pathComponent = splitStr[1] ? splitStr[1].split(":") : '';
|
|
||||||
|
|
||||||
return pathComponent[0];
|
if(indirection[1]) {
|
||||||
|
ranges = indirection[1].split(',');
|
||||||
|
return ranges[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
EpubCFI.prototype.getRange = function(cfiStr) {
|
||||||
|
|
||||||
|
var ranges = cfiStr.split(",");
|
||||||
|
|
||||||
|
if(ranges.length === 3){
|
||||||
|
return [
|
||||||
|
ranges[1],
|
||||||
|
ranges[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.getCharecterOffsetComponent = function(cfiStr) {
|
EpubCFI.prototype.getCharecterOffsetComponent = function(cfiStr) {
|
||||||
|
@ -88,486 +250,64 @@ EpubCFI.prototype.getCharecterOffsetComponent = function(cfiStr) {
|
||||||
return splitStr[1] || '';
|
return splitStr[1] || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EpubCFI.prototype.joinSteps = function(steps) {
|
||||||
|
return steps.map(function(part){
|
||||||
|
var segment = '';
|
||||||
|
|
||||||
EpubCFI.prototype.parse = function(cfiStr) {
|
if(part.type === 'element') {
|
||||||
var cfi = {},
|
segment += (part.index + 1) * 2;
|
||||||
chapSegment,
|
|
||||||
chapterComponent,
|
|
||||||
pathComponent,
|
|
||||||
charecterOffsetComponent,
|
|
||||||
assertion,
|
|
||||||
chapId,
|
|
||||||
path,
|
|
||||||
end,
|
|
||||||
endInt,
|
|
||||||
text,
|
|
||||||
parseStep = function(part){
|
|
||||||
var type, index, has_brackets, id;
|
|
||||||
|
|
||||||
type = "element";
|
|
||||||
index = parseInt(part) / 2 - 1;
|
|
||||||
has_brackets = part.match(/\[(.*)\]/);
|
|
||||||
if(has_brackets && has_brackets[1]){
|
|
||||||
id = has_brackets[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"type" : type,
|
|
||||||
'index' : index,
|
|
||||||
'id' : id || false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if(typeof cfiStr !== "string") {
|
|
||||||
return {spinePos: -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
cfi.str = cfiStr;
|
|
||||||
|
|
||||||
if(cfiStr.indexOf("epubcfi(") === 0 && cfiStr[cfiStr.length-1] === ")") {
|
|
||||||
// Remove intial epubcfi( and ending )
|
|
||||||
cfiStr = cfiStr.slice(8, cfiStr.length-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
chapterComponent = this.getChapterComponent(cfiStr);
|
|
||||||
pathComponent = this.getPathComponent(cfiStr) || '';
|
|
||||||
charecterOffsetComponent = this.getCharecterOffsetComponent(cfiStr);
|
|
||||||
// Make sure this is a valid cfi or return
|
|
||||||
if(!chapterComponent) {
|
|
||||||
return {spinePos: -1};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chapter segment is always the second one
|
|
||||||
chapSegment = chapterComponent.split("/")[2] || '';
|
|
||||||
if(!chapSegment) return {spinePos:-1};
|
|
||||||
|
|
||||||
cfi.spinePos = (parseInt(chapSegment) / 2 - 1 ) || 0;
|
|
||||||
|
|
||||||
chapId = chapSegment.match(/\[(.*)\]/);
|
|
||||||
|
|
||||||
cfi.spineId = chapId ? chapId[1] : false;
|
|
||||||
|
|
||||||
if(pathComponent.indexOf(',') != -1) {
|
|
||||||
// Handle ranges -- not supported yet
|
|
||||||
console.warn("CFI Ranges are not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
path = pathComponent.split('/');
|
|
||||||
end = path.pop();
|
|
||||||
|
|
||||||
cfi.steps = [];
|
|
||||||
|
|
||||||
path.forEach(function(part){
|
|
||||||
var step;
|
|
||||||
|
|
||||||
if(part) {
|
|
||||||
step = parseStep(part);
|
|
||||||
cfi.steps.push(step);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//-- Check if END is a text node or element
|
|
||||||
endInt = parseInt(end);
|
|
||||||
if(!isNaN(endInt)) {
|
|
||||||
|
|
||||||
if(endInt % 2 === 0) { // Even = is an element
|
|
||||||
cfi.steps.push(parseStep(end));
|
|
||||||
} else {
|
|
||||||
cfi.steps.push({
|
|
||||||
"type" : "text",
|
|
||||||
'index' : (endInt - 1 ) / 2
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
if(part.type === 'text') {
|
||||||
|
segment += 1 + (2 * part.index); // TODO: double check that this is odd
|
||||||
assertion = charecterOffsetComponent.match(/\[(.*)\]/);
|
|
||||||
if(assertion && assertion[1]){
|
|
||||||
cfi.characterOffset = parseInt(charecterOffsetComponent.split('[')[0]);
|
|
||||||
// We arent handling these assertions yet
|
|
||||||
cfi.textLocationAssertion = assertion[1];
|
|
||||||
} else {
|
|
||||||
cfi.characterOffset = parseInt(charecterOffsetComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfi;
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.addMarker = function(cfi, _doc, _marker) {
|
|
||||||
var doc = _doc || document;
|
|
||||||
var marker = _marker || this.createMarker(doc);
|
|
||||||
var parent;
|
|
||||||
var lastStep;
|
|
||||||
var text;
|
|
||||||
var split;
|
|
||||||
|
|
||||||
if(typeof cfi === 'string') {
|
|
||||||
cfi = this.parse(cfi);
|
|
||||||
}
|
|
||||||
// Get the terminal step
|
|
||||||
lastStep = cfi.steps[cfi.steps.length-1];
|
|
||||||
|
|
||||||
// check spinePos
|
|
||||||
if(cfi.spinePos === -1) {
|
|
||||||
// Not a valid CFI
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the CFI elements parent
|
|
||||||
parent = this.findParent(cfi, doc);
|
|
||||||
|
|
||||||
if(!parent) {
|
|
||||||
// CFI didn't return an element
|
|
||||||
// Maybe it isnt in the current chapter?
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(lastStep && lastStep.type === "text") {
|
|
||||||
text = parent.childNodes[lastStep.index];
|
|
||||||
if(cfi.characterOffset){
|
|
||||||
split = text.splitText(cfi.characterOffset);
|
|
||||||
marker.classList.add("EPUBJS-CFI-SPLIT");
|
|
||||||
parent.insertBefore(marker, split);
|
|
||||||
} else {
|
|
||||||
parent.insertBefore(marker, text);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
parent.insertBefore(marker, parent.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
return marker;
|
if(part.id) {
|
||||||
};
|
segment += "[" + part.id + "]";
|
||||||
|
|
||||||
EpubCFI.prototype.createMarker = function(_doc) {
|
|
||||||
var doc = _doc || document;
|
|
||||||
var element = doc.createElement('span');
|
|
||||||
element.id = "EPUBJS-CFI-MARKER:"+ core.uuid();
|
|
||||||
element.classList.add("EPUBJS-CFI-MARKER");
|
|
||||||
|
|
||||||
return element;
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.removeMarker = function(marker, _doc) {
|
|
||||||
var doc = _doc || document;
|
|
||||||
// var id = marker.id;
|
|
||||||
|
|
||||||
// Cleanup textnodes if they were split
|
|
||||||
if(marker.classList.contains("EPUBJS-CFI-SPLIT")){
|
|
||||||
nextSib = marker.nextSibling;
|
|
||||||
prevSib = marker.previousSibling;
|
|
||||||
if(nextSib &&
|
|
||||||
prevSib &&
|
|
||||||
nextSib.nodeType === 3 &&
|
|
||||||
prevSib.nodeType === 3){
|
|
||||||
|
|
||||||
prevSib.textContent += nextSib.textContent;
|
|
||||||
marker.parentNode.removeChild(nextSib);
|
|
||||||
}
|
}
|
||||||
marker.parentNode.removeChild(marker);
|
|
||||||
} else if(marker.classList.contains("EPUBJS-CFI-MARKER")) {
|
return segment;
|
||||||
// Remove only elements added as markers
|
|
||||||
marker.parentNode.removeChild(marker);
|
}).join('/');
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.findParent = function(cfi, _doc) {
|
EpubCFI.prototype.segmentString = function(segment) {
|
||||||
var doc = _doc || document,
|
var segmentString = '';
|
||||||
element = doc.getElementsByTagName('html')[0],
|
|
||||||
children = Array.prototype.slice.call(element.children),
|
|
||||||
num, index, part, sections,
|
|
||||||
text, textBegin, textEnd;
|
|
||||||
|
|
||||||
if(typeof cfi === 'string') {
|
segmentString += this.joinSteps(segment.steps);
|
||||||
cfi = this.parse(cfi);
|
|
||||||
|
if(segment.terminal && segment.terminal.offset){
|
||||||
|
segmentString += ':' + segment.terminal.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
sections = cfi.steps.slice(0); // Clone steps array
|
if(segment.terminal && segment.terminal.assertion){
|
||||||
if(!sections.length) {
|
segmentString += '[' + segment.terminal.assertion + ']';
|
||||||
return doc.getElementsByTagName('body')[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(sections && sections.length > 0) {
|
return segmentString;
|
||||||
part = sections.shift();
|
|
||||||
// Find textNodes Parent
|
|
||||||
if(part.type === "text") {
|
|
||||||
text = element.childNodes[part.index];
|
|
||||||
element = text.parentNode || element;
|
|
||||||
// Find element by id if present
|
|
||||||
} else if(part.id){
|
|
||||||
element = doc.getElementById(part.id);
|
|
||||||
// Find element in parent
|
|
||||||
}else{
|
|
||||||
element = children[part.index];
|
|
||||||
}
|
|
||||||
// Element can't be found
|
|
||||||
if(!element || typeof element === "undefined") {
|
|
||||||
console.error("No Element For", part, cfi.str);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Get current element children and continue through steps
|
|
||||||
children = Array.prototype.slice.call(element.children);
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
|
EpubCFI.prototype.toString = function() {
|
||||||
if(typeof cfiOne === 'string') {
|
var cfiString = 'epubcfi(';
|
||||||
cfiOne = new EpubCFI(cfiOne);
|
|
||||||
}
|
cfiString += this.joinSteps(this.base);
|
||||||
if(typeof cfiTwo === 'string') {
|
|
||||||
cfiTwo = new EpubCFI(cfiTwo);
|
cfiString += '!';
|
||||||
}
|
cfiString += this.segmentString(this.path);
|
||||||
// Compare Spine Positions
|
|
||||||
if(cfiOne.spinePos > cfiTwo.spinePos) {
|
// Add Range, if present
|
||||||
return 1;
|
if(this.start) {
|
||||||
}
|
cfiString += ',';
|
||||||
if(cfiOne.spinePos < cfiTwo.spinePos) {
|
cfiString + this.segmentString(this.start);
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.end) {
|
||||||
// Compare Each Step in the First item
|
cfiString += ',';
|
||||||
for (var i = 0; i < cfiOne.steps.length; i++) {
|
cfiString + this.segmentString(this.end);
|
||||||
if(!cfiTwo.steps[i]) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(cfiOne.steps[i].index > cfiTwo.steps[i].index) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(cfiOne.steps[i].index < cfiTwo.steps[i].index) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Otherwise continue checking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All steps in First present in Second
|
cfiString += ")";
|
||||||
if(cfiOne.steps.length < cfiTwo.steps.length) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare the charecter offset of the text node
|
return cfiString;
|
||||||
if(cfiOne.characterOffset > cfiTwo.characterOffset) {
|
};
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(cfiOne.characterOffset < cfiTwo.characterOffset) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CFI's are equal
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateCfiFromHref = function(href, book) {
|
|
||||||
var uri = URI(href);
|
|
||||||
var path = uri.path();
|
|
||||||
var fragment = uri.fragment();
|
|
||||||
var spinePos = book.spineIndexByURL[path];
|
|
||||||
var loaded;
|
|
||||||
var deferred = new RSVP.defer();
|
|
||||||
var epubcfi = new EpubCFI();
|
|
||||||
var spineItem;
|
|
||||||
|
|
||||||
if(typeof spinePos !== "undefined"){
|
|
||||||
spineItem = book.spine[spinePos];
|
|
||||||
loaded = book.loadXml(spineItem.url);
|
|
||||||
loaded.then(function(doc){
|
|
||||||
var element = doc.getElementById(fragment);
|
|
||||||
var cfi;
|
|
||||||
cfi = epubcfi.generateCfiFromElement(element, spineItem.cfiBase);
|
|
||||||
deferred.resolve(cfi);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateCfiFromTextNode = function(anchor, offset, base) {
|
|
||||||
var parent = anchor.parentNode;
|
|
||||||
var steps = this.pathTo(parent);
|
|
||||||
var path = this.generatePathComponent(steps);
|
|
||||||
var index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, anchor));
|
|
||||||
|
|
||||||
var ignoreClass = 'annotator-hl';
|
|
||||||
var needsIgnoring = parent.classList.contains(ignoreClass);
|
|
||||||
var sibling = parent.previousSibling;
|
|
||||||
|
|
||||||
if (!needsIgnoring) {
|
|
||||||
parent = parent.parentNode;
|
|
||||||
if (sibling.nodeType === Node.TEXT_NODE) {
|
|
||||||
// If the previous sibling is a text node, join the offset
|
|
||||||
offset = sibling.textContent.length + offset
|
|
||||||
index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, sibling));
|
|
||||||
} else {
|
|
||||||
// Otherwise just ignore the node by getting the path to its parent
|
|
||||||
index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, anchor.parentNode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "epubcfi(" + base + "!" + path + "/"+index+":"+(offset || 0)+")";
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateCfiFromRangeAnchor = function(range, base) {
|
|
||||||
var anchor = range.anchorNode;
|
|
||||||
var offset = range.anchorOffset;
|
|
||||||
return this.generateCfiFromTextNode(anchor, offset, base);
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateCfiFromRange = function(range, base) {
|
|
||||||
var start, startElement, startSteps, startPath, startOffset, startIndex;
|
|
||||||
var end, endElement, endSteps, endPath, endOffset, endIndex;
|
|
||||||
|
|
||||||
start = range.startContainer;
|
|
||||||
|
|
||||||
if(start.nodeType === 3) { // text node
|
|
||||||
startElement = start.parentNode;
|
|
||||||
//startIndex = 1 + (2 * Array.prototype.indexOf.call(startElement.childNodes, start));
|
|
||||||
startIndex = 1 + (2 * core.indexOfTextNode(start));
|
|
||||||
startSteps = this.pathTo(startElement);
|
|
||||||
} else if(range.collapsed) {
|
|
||||||
return this.generateCfiFromElement(start, base); // single element
|
|
||||||
} else {
|
|
||||||
startSteps = this.pathTo(start);
|
|
||||||
}
|
|
||||||
|
|
||||||
startPath = this.generatePathComponent(startSteps);
|
|
||||||
startOffset = range.startOffset;
|
|
||||||
|
|
||||||
if(!range.collapsed) {
|
|
||||||
end = range.endContainer;
|
|
||||||
|
|
||||||
if(end.nodeType === 3) { // text node
|
|
||||||
endElement = end.parentNode;
|
|
||||||
// endIndex = 1 + (2 * Array.prototype.indexOf.call(endElement.childNodes, end));
|
|
||||||
endIndex = 1 + (2 * core.indexOfTextNode(end));
|
|
||||||
|
|
||||||
endSteps = this.pathTo(endElement);
|
|
||||||
} else {
|
|
||||||
endSteps = this.pathTo(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
endPath = this.generatePathComponent(endSteps);
|
|
||||||
endOffset = range.endOffset;
|
|
||||||
|
|
||||||
// Remove steps present in startPath
|
|
||||||
endPath = endPath.replace(startPath, '');
|
|
||||||
|
|
||||||
if (endPath.length) {
|
|
||||||
endPath = endPath + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "epubcfi(" + base + "!" + startPath + "/" + startIndex + ":" + startOffset + "," + endPath + endIndex + ":" + endOffset + ")";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "epubcfi(" + base + "!" + startPath + "/"+ startIndex +":"+ startOffset +")";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateXpathFromSteps = function(steps) {
|
|
||||||
var xpath = [".", "*"];
|
|
||||||
|
|
||||||
steps.forEach(function(step){
|
|
||||||
var position = step.index + 1;
|
|
||||||
|
|
||||||
if(step.id){
|
|
||||||
xpath.push("*[position()=" + position + " and @id='" + step.id + "']");
|
|
||||||
} else if(step.type === "text") {
|
|
||||||
xpath.push("text()[" + position + "]");
|
|
||||||
} else {
|
|
||||||
xpath.push("*[" + position + "]");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return xpath.join("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateQueryFromSteps = function(steps) {
|
|
||||||
var query = ["html"];
|
|
||||||
|
|
||||||
steps.forEach(function(step){
|
|
||||||
var position = step.index + 1;
|
|
||||||
|
|
||||||
if(step.id){
|
|
||||||
query.push("#" + step.id);
|
|
||||||
} else if(step.type === "text") {
|
|
||||||
// unsupported in querySelector
|
|
||||||
// query.push("text()[" + position + "]");
|
|
||||||
} else {
|
|
||||||
query.push("*:nth-child(" + position + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return query.join(">");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
EpubCFI.prototype.generateRangeFromCfi = function(cfi, _doc) {
|
|
||||||
var doc = _doc || document;
|
|
||||||
var range = doc.createRange();
|
|
||||||
var lastStep;
|
|
||||||
var xpath;
|
|
||||||
var startContainer;
|
|
||||||
var textLength;
|
|
||||||
var query;
|
|
||||||
var startContainerParent;
|
|
||||||
|
|
||||||
if(typeof cfi === 'string') {
|
|
||||||
cfi = this.parse(cfi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check spinePos
|
|
||||||
if(cfi.spinePos === -1) {
|
|
||||||
// Not a valid CFI
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the terminal step
|
|
||||||
lastStep = cfi.steps[cfi.steps.length-1];
|
|
||||||
|
|
||||||
if(typeof document.evaluate != 'undefined') {
|
|
||||||
xpath = this.generateXpathFromSteps(cfi.steps);
|
|
||||||
startContainer = doc.evaluate(xpath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
||||||
} else {
|
|
||||||
// Get the query string
|
|
||||||
query = this.generateQueryFromSteps(cfi.steps);
|
|
||||||
// Find the containing element
|
|
||||||
startContainerParent = doc.querySelector(query);
|
|
||||||
// Find the text node within that element
|
|
||||||
if(startContainerParent && lastStep.type == "text") {
|
|
||||||
startContainer = startContainerParent.childNodes[lastStep.index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!startContainer) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(startContainer && cfi.characterOffset >= 0) {
|
|
||||||
textLength = startContainer.length;
|
|
||||||
|
|
||||||
if(cfi.characterOffset < textLength) {
|
|
||||||
range.setStart(startContainer, cfi.characterOffset);
|
|
||||||
range.setEnd(startContainer, textLength );
|
|
||||||
} else {
|
|
||||||
console.debug("offset greater than length:", cfi.characterOffset, textLength);
|
|
||||||
range.setStart(startContainer, textLength - 1 );
|
|
||||||
range.setEnd(startContainer, textLength );
|
|
||||||
}
|
|
||||||
} else if(startContainer) {
|
|
||||||
range.selectNode(startContainer);
|
|
||||||
}
|
|
||||||
// doc.defaultView.getSelection().addRange(range);
|
|
||||||
return range;
|
|
||||||
};
|
|
||||||
|
|
||||||
EpubCFI.prototype.isCfiString = function(target) {
|
|
||||||
if(typeof target === "string" &&
|
|
||||||
target.indexOf("epubcfi(") === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = EpubCFI;
|
|
|
@ -627,12 +627,19 @@ Rendition.prototype.passViewEvents = function(view){
|
||||||
view.listenedEvents.forEach(function(e){
|
view.listenedEvents.forEach(function(e){
|
||||||
view.on(e, this.triggerViewEvent.bind(this));
|
view.on(e, this.triggerViewEvent.bind(this));
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
|
view.on("selected", this.triggerSelectedEvent.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
Rendition.prototype.triggerViewEvent = function(e){
|
Rendition.prototype.triggerViewEvent = function(e){
|
||||||
this.trigger(e.type, e);
|
this.trigger(e.type, e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Rendition.prototype.triggerSelectedEvent = function(cfirange){
|
||||||
|
console.log(cfirange);
|
||||||
|
this.trigger("selected", cfirange);
|
||||||
|
};
|
||||||
|
|
||||||
Rendition.prototype.replacements = function(){
|
Rendition.prototype.replacements = function(){
|
||||||
// Wait for loading
|
// Wait for loading
|
||||||
return this.q.enqueue(function () {
|
return this.q.enqueue(function () {
|
||||||
|
|
11
src/view.js
11
src/view.js
|
@ -744,11 +744,18 @@ View.prototype.onSelectionChange = function(e){
|
||||||
clearTimeout(this.selectionEndTimeout);
|
clearTimeout(this.selectionEndTimeout);
|
||||||
}
|
}
|
||||||
this.selectionEndTimeout = setTimeout(function() {
|
this.selectionEndTimeout = setTimeout(function() {
|
||||||
this.selectedRange = this.window.getSelection();
|
var selection = this.window.getSelection();
|
||||||
this.trigger("selected", this.selectedRange);
|
this.triggerSelectedEvent(selection);
|
||||||
}.bind(this), 500);
|
}.bind(this), 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
View.prototype.triggerSelectedEvent = function(selection){
|
||||||
|
var range = selection.getRangeAt(0);
|
||||||
|
console.log(range);
|
||||||
|
var cfirange = this.section.cfiFromRange(range);
|
||||||
|
this.trigger("selected", cfirange);
|
||||||
|
};
|
||||||
|
|
||||||
RSVP.EventTarget.mixin(View.prototype);
|
RSVP.EventTarget.mixin(View.prototype);
|
||||||
|
|
||||||
module.exports = View;
|
module.exports = View;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue