this.printPopup = null;
this.searchTerm = '';
- this.searchResults = {};
+ this.searchResults = null;
this.firstIndex = null;
// We link to index.php to avoid redirect which breaks back button
// Should be overriden (before init) by custom implmentations.
- this.logoURL = 'http://www.archive.org/index.php';
+ this.logoURL = 'http://openlibrary.org';
// Base URL for UI images - should be overriden (before init) by
// custom implementations.
// embed/share ui
// info ui
};
+
+ // Text-to-Speech params
+ this.ttsPlaying = false;
+ this.ttsIndex = null; //leaf index
+ this.ttsPosition = -1; //chunk (paragraph) number
+ this.ttsBuffering = false;
+ this.ttsPoller = null;
+ this.ttsFormat = null;
return this;
};
$("#BookReader").append("<div id='BRcontainer'></div>");
$("#BRcontainer").append("<div id='BRpageview'></div>");
-
- this.initNavbar();
- this.bindNavigationHandlers();
-
+
// Autohide nav after showing for awhile
var self = this;
if (this.uiAutoHide) {
// Enact other parts of initial params
this.updateFromParams(params);
+
+ // We init the nav bar after the params processing so that the nav slider knows where
+ // it should start (doesn't jump after init)
+ this.initNavbar();
+ this.bindNavigationHandlers();
// Start AJAX request for OL data
- this.getOpenLibraryJSON(this.gotOpenLibraryRecord);
-
-}
-
-// XXXmang
-BookReader.prototype.gotOpenLibraryRecord = function(olObject) {
- // console.log(olObject);
- if (olObject) {
- if (olObject['table_of_contents']) {
- console.log('xxx updating table of contents');
- br.updateTOC(olObject['table_of_contents']); // XXX
- }
- }
-}
-
-BookReader.prototype.updateTOC = function(tocEntries) {
- this.removeChapters();
- for (var i = 0; i < tocEntries.length; i++) {
- this.addChapterFromEntry(tocEntries[i]);
- }
-}
-
-/*
- * Example table of contents entry - this format is defined by Open Library
- * {
- * "pagenum": "17",
- * "level": 1,
- * "label": "CHAPTER I",
- * "type": {"key": "/type/toc_item"},
- * "title": "THE COUNTRY AND THE MISSION"
- * }
- */
-BookReader.prototype.addChapterFromEntry = function(tocEntryObject) {
- console.log(tocEntryObject);
- var pageIndex = this.getPageIndex(tocEntryObject['pagenum']);
- // Only add if we know where it is
- if (pageIndex) {
- this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex);
+ if (this.getOpenLibraryRecord) {
+ this.getOpenLibraryRecord(this.gotOpenLibraryRecord);
}
}
br.zoom(-1);
}
});
-
}
BookReader.prototype.setClickHandler2UP = function( element, data, handler) {
leafTop += height +10;
leafBottom += 10;
}
-
+
+ // Based of the pages displayed in the view we set the current index
+ // $$$ we should consider the page in the center of the view to be the current one
var firstIndexToDraw = indicesToDisplay[0];
- this.firstIndex = firstIndexToDraw;
+ if (firstIndexToDraw != this.firstIndex) {
+ this.willChangeToIndex(firstIndexToDraw);
+ }
+ this.firstIndex = firstIndexToDraw;
// Update hash, but only if we're currently displaying a leaf
// Hack that fixes #365790
// console.log('current ' + currentIndex);
// console.log('least visible ' + leastVisible + ' most visible ' + mostVisible);
if (currentIndex < leastVisible) {
+ this.willChangeToIndex(leastVisible);
this.setCurrentIndex(leastVisible);
} else if (currentIndex > mostVisible) {
+ this.willChangeToIndex(mostVisible);
this.setCurrentIndex(mostVisible);
}
switch (this.mode) {
case this.constMode1up:
case this.constMode2up:
- this.resizePageView1up();
+ this.resizePageView1up(); // $$$ necessary in non-1up mode?
break;
case this.constModeThumb:
this.prepareThumbnailView( this.currentIndex() );
}
}
+// Resize the current one page view
BookReader.prototype.resizePageView1up = function() {
var i;
var viewHeight = 0;
var oldScrollTop = $('#BRcontainer').attr('scrollTop');
var oldScrollLeft = $('#BRcontainer').attr('scrollLeft');
+
var oldPageViewHeight = $('#BRpageview').height();
var oldPageViewWidth = $('#BRpageview').width();
- var oldCenterY = this.centerY1up();
- var oldCenterX = this.centerX1up();
-
- if (0 != oldPageViewHeight) {
- var scrollRatio = oldCenterY / oldPageViewHeight;
+ // May have come here after preparing the view, in which case the scrollTop and view height are not set
+
+ var scrollRatio = 0;
+ if (oldScrollTop > 0) {
+ // We have scrolled - implies view has been set up
+ var oldCenterY = this.centerY1up();
+ var oldCenterX = this.centerX1up();
+ scrollRatio = oldCenterY / oldPageViewHeight;
} else {
- var scrollRatio = 0;
+ // Have not scrolled, e.g. because in new container
+
+ // We set the scroll ratio so that the current index will still be considered the
+ // current index in drawLeafsOnePage after we create the new view container
+
+ // Make sure this will count as current page after resize
+ // console.log('fudging for index ' + this.currentIndex() + ' (page ' + this.getPageNum(this.currentIndex()) + ')');
+ var fudgeFactor = (this.getPageHeight(this.currentIndex()) / this.reduce) * 0.6;
+ var oldLeafTop = this.onePageGetPageTop(this.currentIndex()) + fudgeFactor;
+ var oldViewDimensions = this.onePageCalculateViewDimensions(this.reduce, this.padding);
+ scrollRatio = oldLeafTop / oldViewDimensions.height;
}
// Recalculate 1up reduction factors
this.reduce = reductionFactor.reduce;
}
- for (i=0; i<this.numLeafs; i++) {
- viewHeight += parseInt(this._getPageHeight(i)/this.reduce) + this.padding;
- var width = parseInt(this._getPageWidth(i)/this.reduce);
- if (width>viewWidth) viewWidth=width;
- }
- $('#BRpageview').height(viewHeight);
- $('#BRpageview').width(viewWidth);
+ var viewDimensions = this.onePageCalculateViewDimensions(this.reduce, this.padding);
+ $('#BRpageview').height(viewDimensions.height);
+ $('#BRpageview').width(viewDimensions.width);
- var newCenterY = scrollRatio*viewHeight;
+ var newCenterY = scrollRatio*viewDimensions.height;
var newTop = Math.max(0, Math.floor( newCenterY - $('#BRcontainer').height()/2 ));
$('#BRcontainer').attr('scrollTop', newTop);
this.updateSearchHilites();
}
+// Calculate the dimensions for a one page view with images at the given reduce and padding
+BookReader.prototype.onePageCalculateViewDimensions = function(reduce, padding) {
+ var viewWidth = 0;
+ var viewHeight = 0;
+ for (i=0; i<this.numLeafs; i++) {
+ viewHeight += parseInt(this._getPageHeight(i)/this.reduce) + this.padding;
+ var width = parseInt(this._getPageWidth(i)/this.reduce);
+ if (width>viewWidth) viewWidth=width;
+ }
+ return { width: viewWidth, height: viewHeight }
+}
// centerX1up()
//______________________________________________________________________________
//______________________________________________________________________________
BookReader.prototype.jumpToIndex = function(index, pageX, pageY) {
+ this.willChangeToIndex(index);
+
+ this.ttsStop();
+
if (this.constMode2up == this.mode) {
this.autoStop();
}
} else {
// 1up
- var i;
- var leafTop = 0;
- var leafLeft = 0;
- var h;
- for (i=0; i<index; i++) {
- h = parseInt(this._getPageHeight(i)/this.reduce);
- leafTop += h + this.padding;
- }
+ var leafTop = this.onePageGetPageTop(index);
if (pageY) {
//console.log('pageY ' + pageY);
}
}
-
// switchMode()
//______________________________________________________________________________
BookReader.prototype.switchMode = function(mode) {
- //console.log(' asked to switch to mode ' + mode + ' from ' + this.mode);
-
if (mode == this.mode) {
return;
}
}
this.autoStop();
+ this.ttsStop();
this.removeSearchHilites();
this.mode = mode;
this.reduce = this.quantizeReduce(this.reduce, this.onePage.reductionFactors);
this.prepareOnePageView();
} else if (3 == mode) {
- $('button.thumb').hide();
- $('button.twopg').show();
this.reduce = this.quantizeReduce(this.reduce, this.reductionFactors);
this.prepareThumbnailView();
} else {
});
$("#BRcontainer").append("<div id='BRpageview'></div>");
-
+
// Attaches to first child - child must be present
$('#BRcontainer').dragscrollable();
this.bindGestures($('#BRcontainer'));
$(this.leafEdgeL).bind('click', this, function(e) {
e.data.autoStop();
+ e.data.ttsStop();
var jumpIndex = e.data.jumpIndexForLeftEdgePageX(e.pageX);
e.data.jumpToIndex(jumpIndex);
});
$(this.leafEdgeR).bind('click', this, function(e) {
e.data.autoStop();
+ e.data.ttsStop();
var jumpIndex = e.data.jumpIndexForRightEdgePageX(e.pageX);
e.data.jumpToIndex(jumpIndex);
});
return (this.getMedianPageSize().height + 0.0) / ($('#BRcontainer').attr('clientHeight') - this.padding * 2); // make sure a little of adjacent pages show
}
+// Returns where the top of the page with given index should be in one page view
+BookReader.prototype.onePageGetPageTop = function(index)
+{
+ var i;
+ var leafTop = 0;
+ var leafLeft = 0;
+ var h;
+ for (i=0; i<index; i++) {
+ h = parseInt(this._getPageHeight(i)/this.reduce);
+ leafTop += h + this.padding;
+ }
+ return leafTop;
+}
+
BookReader.prototype.getMedianPageSize = function() {
if (this._medianPageSize) {
return this._medianPageSize;
}
//if (index<0) return;
+ this.willChangeToIndex(index);
+
var previousIndices = this.getSpreadIndices(index);
if (previousIndices[0] < this.firstDisplayableIndex() || previousIndices[1] < this.firstDisplayableIndex()) {
BookReader.prototype.flipFwdToIndex = function(index) {
if (this.animating) return;
-
+
if (null != this.leafEdgeTmp) {
alert('error: leafEdgeTmp should be null!');
return;
}
if (index > this.lastDisplayableIndex()) return;
+ this.willChangeToIndex(index);
+
this.animating = true;
var nextIndices = this.getSpreadIndices(index);
}
}
+/*
+ * Put handlers here for when we will navigate to a new page
+ */
+BookReader.prototype.willChangeToIndex = function(index)
+{
+ // Update navbar position icon - leads page change animation
+ this.updateNavIndex(index);
+
+}
+
// flipRightToLeft(nextL, nextR, gutter)
// $$$ better not to have to pass gutter in
//______________________________________________________________________________
this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexL],
{ self: this },
function(e) {
+ e.data.self.ttsStop();
e.data.self.left();
e.preventDefault();
}
this.setClickHandler2UP( this.prefetchedImgs[this.twoPage.currentIndexR],
{ self: this },
function(e) {
+ e.data.self.ttsStop();
e.data.self.right();
e.preventDefault();
}
if (index < 0 || index > (this.numLeafs - 1) ) {
// Facing page at beginning or end, or beyond
$(img).css({
- 'background-color': 'transparent'
+ 'background-color': '#efefef'
});
}
img.src = pageURI;
// search()
//______________________________________________________________________________
BookReader.prototype.search = function(term) {
- term = term.replace(/\//g, ' '); // strip slashes
+ //console.log('search called with term=' + term);
+ var url = 'http://'+this.server.replace(/:.+/, ''); //remove the port and userdir
+ url += '/~edward/inside_jsonp.php?item_id='+this.bookId;
+ url += '&doc='+this.subPrefix; //TODO: test with subitem
+ url += '&path='+this.bookPath.replace(new RegExp('/'+this.subPrefix+'$'), ''); //remove subPrefix from end of path
+ url += '&q='+escape(term);
+ //console.log('search url='+url);
+
+ term = term.replace(/\//g, ' '); // strip slashes, since this goes in the url
this.searchTerm = term;
- $('#BookReaderSearchScript').remove();
- var script = document.createElement("script");
- script.setAttribute('id', 'BookReaderSearchScript');
- script.setAttribute("type", "text/javascript");
- script.setAttribute("src", 'http://'+this.server+'/BookReader/flipbook_search_br.php?url='+escape(this.bookPath + '_djvu.xml')+'&term='+term+'&format=XML&callback=br.BRSearchCallback');
- document.getElementsByTagName('head')[0].appendChild(script);
- $('#BookReaderSearchBox').val(term);
- $('#BookReaderSearchResults').html('Searching...');
+
+ this.removeSearchResults();
+ this.showProgressPopup('<img id="searchmarker" src="'+this.imagesBaseURL + 'marker_srch-on.png'+'"> Search results will appear below...');
+ this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:'BRSearchCallback'});
}
// BRSearchCallback()
//______________________________________________________________________________
-BookReader.prototype.BRSearchCallback = function(txt) {
- //alert(txt);
- if (jQuery.browser.msie) {
- var dom=new ActiveXObject("Microsoft.XMLDOM");
- dom.async="false";
- dom.loadXML(txt);
- } else {
- var parser = new DOMParser();
- var dom = parser.parseFromString(txt, "text/xml");
- }
-
- $('#BookReaderSearchResults').empty();
- $('#BookReaderSearchResults').append('<ul>');
-
- for (var key in this.searchResults) {
- if (null != this.searchResults[key].div) {
- $(this.searchResults[key].div).remove();
- }
- delete this.searchResults[key];
+// Unfortunately, we can't pass 'br.searchCallback' to our search service,
+// because it can't handle the '.'
+function BRSearchCallback(results) {
+ //console.log('got ' + results.matches.length + ' results');
+ br.removeSearchResults();
+ br.searchResults = results;
+ //console.log(br.searchResults);
+
+ if (0 == results.matches.length) {
+ $(br.popup).text('No matches were found.');
+ setTimeout(function(){
+ $(br.popup).fadeOut('slow', function() {
+ br.removeProgressPopup();
+ })
+ },1000);
+ return;
}
- var pages = dom.getElementsByTagName('PAGE');
-
- if (0 == pages.length) {
- // $$$ it would be nice to echo the (sanitized) search result here
- $('#BookReaderSearchResults').append('<li>No search results found</li>');
- } else {
- for (var i = 0; i < pages.length; i++){
- //console.log(pages[i].getAttribute('file').substr(1) +'-'+ parseInt(pages[i].getAttribute('file').substr(1), 10));
-
-
- var re = new RegExp (/_(\d{4})\.djvu/);
- var reMatch = re.exec(pages[i].getAttribute('file'));
- var index = parseInt(reMatch[1], 10);
- //var index = parseInt(pages[i].getAttribute('file').substr(1), 10);
-
- var children = pages[i].childNodes;
- var context = '';
- for (var j=0; j<children.length; j++) {
- //console.log(j + ' - ' + children[j].nodeName);
- //console.log(children[j].firstChild.nodeValue);
- if ('CONTEXT' == children[j].nodeName) {
- context += children[j].firstChild.nodeValue;
- } else if ('WORD' == children[j].nodeName) {
- context += '<b>'+children[j].firstChild.nodeValue+'</b>';
-
- var index = this.leafNumToIndex(index);
- if (null != index) {
- //coordinates are [left, bottom, right, top, [baseline]]
- //we'll skip baseline for now...
- var coords = children[j].getAttribute('coords').split(',',4);
- if (4 == coords.length) {
- this.searchResults[index] = {'l':parseInt(coords[0]), 'b':parseInt(coords[1]), 'r':parseInt(coords[2]), 't':parseInt(coords[3]), 'div':null};
- }
- }
- }
- }
- var pageName = this.getPageName(index);
- var middleX = (this.searchResults[index].l + this.searchResults[index].r) >> 1;
- var middleY = (this.searchResults[index].t + this.searchResults[index].b) >> 1;
- //TODO: remove hardcoded instance name
- $('#BookReaderSearchResults').append('<li><b><a href="javascript:br.jumpToIndex('+index+','+middleX+','+middleY+');">' + pageName + '</a></b> - ' + context + '</li>');
- }
+ var i;
+ for (i=0; i<results.matches.length; i++) {
+ br.addSearchResult(results.matches[i].text, br.leafNumToIndex(results.matches[i].par[0].page));
}
- $('#BookReaderSearchResults').append('</ul>');
-
- // $$$ update again for case of loading search URL in new browser window (search box may not have been ready yet)
- $('#BookReaderSearchBox').val(this.searchTerm);
-
- this.updateSearchHilites();
+ br.updateSearchHilites();
+ br.removeProgressPopup();
}
+
// updateSearchHilites()
//______________________________________________________________________________
BookReader.prototype.updateSearchHilites = function() {
// showSearchHilites1UP()
//______________________________________________________________________________
BookReader.prototype.updateSearchHilites1UP = function() {
-
- for (var key in this.searchResults) {
-
- if (jQuery.inArray(parseInt(key), this.displayedIndices) >= 0) {
- var result = this.searchResults[key];
- if (null == result.div) {
- result.div = document.createElement('div');
- $(result.div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+key);
- //console.log('appending ' + key);
- }
- $(result.div).css({
- width: (result.r-result.l)/this.reduce + 'px',
- height: (result.b-result.t)/this.reduce + 'px',
- left: (result.l)/this.reduce + 'px',
- top: (result.t)/this.reduce +'px'
- });
-
- } else {
- //console.log(key + ' not displayed');
- this.searchResults[key].div=null;
+ var results = this.searchResults;
+ if (null == results) return;
+ var i, j;
+ for (i=0; i<results.matches.length; i++) {
+ //console.log(results.matches[i].par[0]);
+ for (j=0; j<results.matches[i].par[0].boxes.length; j++) {
+ var box = results.matches[i].par[0].boxes[j];
+ var pageIndex = this.leafNumToIndex(box.page);
+ if (jQuery.inArray(pageIndex, this.displayedIndices) >= 0) {
+ if (null == box.div) {
+ //create a div for the search highlight, and stash it in the box object
+ box.div = document.createElement('div');
+ $(box.div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+pageIndex);
+ }
+ $(box.div).css({
+ width: (box.r-box.l)/this.reduce + 'px',
+ height: (box.b-box.t)/this.reduce + 'px',
+ left: (box.l)/this.reduce + 'px',
+ top: (box.t)/this.reduce +'px'
+ });
+ } else {
+ if (null != box.div) {
+ //console.log('removing search highlight div');
+ $(box.div).remove();
+ box.div=null;
+ }
+ }
}
}
+
}
+
// twoPageGutter()
//______________________________________________________________________________
// Returns the position of the gutter (line between the page images)
});
}
-// showSearchHilites2UP()
+// showSearchHilites2UPNew()
//______________________________________________________________________________
BookReader.prototype.updateSearchHilites2UP = function() {
-
- for (var key in this.searchResults) {
- key = parseInt(key, 10);
- if (jQuery.inArray(key, this.displayedIndices) >= 0) {
- var result = this.searchResults[key];
- if (null == result.div) {
- result.div = document.createElement('div');
- $(result.div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview');
- //console.log('appending ' + key);
- }
-
- // We calculate the reduction factor for the specific page because it can be different
- // for each page in the spread
- var height = this._getPageHeight(key);
- var width = this._getPageWidth(key)
- var reduce = this.twoPage.height/height;
- var scaledW = parseInt(width*reduce);
-
- var gutter = this.twoPageGutter();
- var pageL;
- if ('L' == this.getPageSide(key)) {
- pageL = gutter-scaledW;
+ //console.log('updateSearchHilites2UP results = ' + this.searchResults);
+ var results = this.searchResults;
+ if (null == results) return;
+ var i, j;
+ for (i=0; i<results.matches.length; i++) {
+ //console.log(results.matches[i].par[0]);
+ for (j=0; j<results.matches[i].par[0].boxes.length; j++) {
+ var box = results.matches[i].par[0].boxes[j];
+ var pageIndex = this.leafNumToIndex(box.page);
+ if (jQuery.inArray(pageIndex, this.displayedIndices) >= 0) {
+ if (null == box.div) {
+ //create a div for the search highlight, and stash it in the box object
+ box.div = document.createElement('div');
+ $(box.div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview');
+ //console.log('appending new div');
+ }
+ this.setHilightCss2UP(box.div, pageIndex, box.l, box.r, box.t, box.b);
} else {
- pageL = gutter;
+ if (null != box.div) {
+ //console.log('removing search highlight div');
+ $(box.div).remove();
+ box.div=null;
+ }
}
- var pageT = this.twoPageTop();
-
- $(result.div).css({
- width: (result.r-result.l)*reduce + 'px',
- height: (result.b-result.t)*reduce + 'px',
- left: pageL+(result.l)*reduce + 'px',
- top: pageT+(result.t)*reduce +'px'
- });
-
- } else {
- //console.log(key + ' not displayed');
- if (null != this.searchResults[key].div) {
- //console.log('removing ' + key);
- $(this.searchResults[key].div).remove();
- }
- this.searchResults[key].div=null;
}
}
+
+}
+
+// setHilightCss2UP()
+//______________________________________________________________________________
+//position calculation shared between search and text-to-speech functions
+BookReader.prototype.setHilightCss2UP = function(div, index, left, right, top, bottom) {
+
+ // We calculate the reduction factor for the specific page because it can be different
+ // for each page in the spread
+ var height = this._getPageHeight(index);
+ var width = this._getPageWidth(index)
+ var reduce = this.twoPage.height/height;
+ var scaledW = parseInt(width*reduce);
+
+ var gutter = this.twoPageGutter();
+ var pageL;
+ if ('L' == this.getPageSide(index)) {
+ pageL = gutter-scaledW;
+ } else {
+ pageL = gutter;
+ }
+ var pageT = this.twoPageTop();
+
+ $(div).css({
+ width: (right-left)*reduce + 'px',
+ height: (bottom-top)*reduce + 'px',
+ left: pageL+left*reduce + 'px',
+ top: pageT+top*reduce +'px'
+ });
}
// removeSearchHilites()
//______________________________________________________________________________
BookReader.prototype.removeSearchHilites = function() {
- for (var key in this.searchResults) {
- if (null != this.searchResults[key].div) {
- $(this.searchResults[key].div).remove();
- this.searchResults[key].div=null;
- }
- }
+ var results = this.searchResults;
+ if (null == results) return;
+ var i, j;
+ for (i=0; i<results.matches.length; i++) {
+ for (j=0; j<results.matches[i].par[0].boxes.length; j++) {
+ var box = results.matches[i].par[0].boxes[j];
+ if (null != box.div) {
+ $(box.div).remove();
+ box.div=null;
+ }
+ }
+ }
}
+
// printPage
//______________________________________________________________________________
BookReader.prototype.printPage = function() {
// showEmbedCode()
//______________________________________________________________________________
BookReader.prototype.showEmbedCode = function() {
+ if (null != this.embedPopup) { // check if already showing
+ return;
+ }
+ this.autoStop();
+ this.ttsStop();
+
this.embedPopup = document.createElement("div");
$(this.embedPopup).css({
position: 'absolute',
// $$$ should make this work inside the BookReader div (self-contained), rather than after
$('#BookReader').after(
- '<div id="BRnav"><div id="BRnavpos"><div id="pager"></div><div id="BRnavline"></div></div></div>'
+ '<div id="BRnav">'
+ + '<div id="BRpage">' // Page turn buttons
+ + '<button class="BRicon onepg"></button>'
+ + '<button class="BRicon twopg"></button>'
+ + '<button class="BRicon thumb"></button>'
+ + '<button class="BRicon fit"></button>'
+ + '<button class="BRicon zoom_in"></button>'
+ + '<button class="BRicon zoom_out"></button>'
+ + '<button class="BRicon book_left"></button>'
+ + '<button class="BRicon book_right"></button>'
+ + '</div>'
+ + '<div id="BRnavpos">' // Page slider and nav line
+ //+ '<div id="BRfiller"></div>'
+ + '<div id="BRpager"></div>' // Page slider
+ + '<div id="BRnavline">' // Nav line with e.g. chapter markers
+ + '<div class="BRnavend" id="BRnavleft"></div>'
+ + '<div class="BRnavend" id="BRnavright"></div>'
+ + '</div>'
+ + '</div>'
+ + '<div id="BRnavCntlBtm" class="BRnavCntl BRdn"></div>'
+ + '</div>'
);
/*
-<!-- LOAD SEARCH RESULTS FIRST SO CHAPTER INDICATORS CAN APPEAR IN FRONT -->
- <div class="search" style="left:80%;" title="Search result">
- <div class="query">The Kingdom of the Future, for instance, though interesting in a Caley Robinson way, with its cold, mystical colour relieved by touches of warm reddish browns, and its big draped figures, was a composition in the past, and did not stimulate the <strong><a href="">emotional</a></strong> powers of the observer with a suggestion of coming ages or a prophecy of progress. <span>Page 26</span></div>
- </div>
-
- <div class="search" style="left:22%;" title="Search result">
- <div class="query">A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 27</span></div>
- </div>
-
- <div class="search" style="left:75%;" title="Search result">
- <div class="query">A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 27</span></div>
- </div>
-
- <div class="chapter" style="left:1%;">
- <div class="title">I. The Minotaur <span>|</span> Page 1</div>
- </div>
-
- <div class="chapter" style="left:17%;">
- <div class="title">II. The Griffon <span>|</span> Page 44</div>
- </div>
-
- <div class="chapter" style="left:30%;">
- <div class="title">III. The Firedrake <span>|</span> Page 129</div>
- </div>
-
- <div class="chapter" style="left:67.5%;">
- <div class="title">V. The Pegasus <span>|</span> Page 201</div>
- </div>
-
- <div class="chapter" style="left:90%;">
- <div class="title">VI. The Goblin <span>|</span> Page 255</div>
- </div>
-
<div class="searchChap" style="left:49%;" title="Search result">
<div class="query">
A related distinction is between the emotion and the results of the emotion, principally behaviors and <strong><a href="">emotional</a></strong> expressions. People often behave in certain ways as a direct result of their <strong><a href="">emotional</a></strong> state, such as crying, fighting or fleeing. <span>Page 163</span>
<div class="queryChap">IV. The Witch <span>|</span> Page 163</div>
</div>
</div>
-
- </div>
-</div>
*/
/* $$$mang search results and chapters should automatically coalesce
});
});
*/
+ var self = this;
+ $('#BRpager').slider({
+ animate: true,
+ min: 0,
+ max: this.numLeafs - 1,
+ value: this.currentIndex()
+ })
+ .bind('slide', function(event, ui){
+ self.updateNavPageNum(ui.value);
+ $("#pagenum").show();
+ return true;
+ })
+ .bind('slidechange', function(event, ui) {
+ $("#pagenum").hide();
- // XXXmang testing
- this.addSearchResult('hi there', '25', 22);
- $("#pager").draggable({axis:'x',containment:'parent'});
+ // recursion prevention for jumpToIndex
+ if ( $(this).data('swallowchange') ) {
+ $(this).data('swallowchange', false);
+ } else {
+ self.jumpToIndex(ui.value);
+ }
+ return true;
+ })
+ .hover(function() {
+ $("#pagenum").show();
+ },function(){
+ // XXXmang not triggering on iPad - probably due to touch event translation layer
+ $("#pagenum").hide();
+ }
+ );
+
+ //append icon to handle
+ var handleHelper = $('#BRpager .ui-slider-handle')
+ // $$$mang update logic for setting the page number label -- use page numbers if available
+ .append('<div id="pagenum"><span class="currentpage"></span></div>');
+ //.wrap('<div class="ui-handle-helper-parent"></div>').parent(); // XXXmang is this used for hiding the tooltip?
+
+ // $$$mang, why are these set both here and in bindNavigationHandlers?
+ $('.BRicon.book_left').bind('click', function() {
+ self.ttsStop();
+ self.left();
+ });
+ $('.BRicon.book_right').bind('click', function() {
+ self.ttsStop();
+ self.right();
+ });
+
+ this.updateNavPageNum(this.currentIndex());
+
+ $("#BRzoombtn").draggable({axis:'y',containment:'parent'});
+
+ //XXXmang remove once done testing
+ //this.addSearchResult("There is a place where the <strong>sidewalk</strong> ends And before the street begins, And there the grass grows soft and white, And there the sun burns crimson bright,And there the moon-bird rests from his flight To cool in the peppermint wind.", "20", 31);
+ //this.addSearchResult("There is a place where the <strong>sidewalk</strong> BEGINS And there the moon-bird rests from his flight To cool in the peppermint wind.", "60", 71);
+
+}
+
+BookReader.prototype.updateNavPageNum = function(index) {
+ var pageNum = this.getPageNum(index);
+ var pageStr;
+ if (pageNum[0] == 'n') { // funny index
+ pageStr = index + ' / ' + this.numLeafs;
+ } else {
+ pageStr = 'Page ' + pageNum;
+ }
+
+ $('#pagenum .currentpage').text(pageStr);
+}
+
+/*
+ * Update the nav bar display - does not cause navigation.
+ */
+BookReader.prototype.updateNavIndex = function(index) {
+ // We want to update the value, but normally moving the slider
+ // triggers jumpToIndex which triggers this method
+ $('#BRpager').data('swallowchange', true).slider('value', index);
}
-BookReader.prototype.addSearchResult = function(queryString, pageNumber, pageIndex) {
+BookReader.prototype.addSearchResult = function(queryString, pageIndex) {
+ var pageNumber = this.getPageNum(pageIndex);
var uiStringSearch = "Search result"; // i18n
var uiStringPage = "Page"; // i18n
- var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs - 1);
+ var pageDisplayString = '';
+ if (pageNumber) {
+ pageDisplayString = uiStringPage + ' ' + pageNumber;
+ }
- // $$$mang add click-through to page
- $('<div class="search" style="left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">'
+ var re = new RegExp('{{{(.+?)}}}', 'g');
+ queryString = queryString.replace(re, '<a href="#" onclick="br.jumpToIndex('+pageIndex+'); return false;">$1</a>')
+
+ var marker = $('<div class="search" style="top:'+(-$('#BRcontainer').height())+'px; left:' + percentThrough + ';" title="' + uiStringSearch + '"><div class="query">'
+ queryString + '<span>' + uiStringPage + ' ' + pageNumber + '</span></div>')
- .appendTo('#BRnavpos').bt({
+ .data({'self': this, 'pageIndex': pageIndex })
+ .appendTo('#BRnavline').bt({
contentSelector: '$(this).find(".query")',
- trigger: 'click',
+ trigger: 'hover',
closeWhenOthersOpen: true,
cssStyles: {
- padding: '10px 10px 15px',
+ padding: '12px 14px',
backgroundColor: '#fff',
- border: '3px solid #e2dcc5',
- borderBottom: 'none',
+ border: '4px solid #e2dcc5',
fontFamily: '"Lucida Grande","Arial",sans-serif',
- fontSize: '12px',
- lineHeight: '18px',
+ fontSize: '13px',
+ //lineHeight: '18px',
color: '#615132'
},
shrinkToFit: false,
padding: 0,
spikeGirth: 0,
spikeLength: 0,
- overlap: '10px',
+ overlap: '22px',
overlay: false,
killTitle: false,
textzIndex: 9999,
positions: ['top'],
fill: 'white',
windowMargin: 10,
- strokeWidth: 3,
- strokeStyle: '#e2dcc5',
+ strokeWidth: 0,
cornerRadius: 0,
centerPointX: 0,
centerPointY: 0,
shadow: false
})
- .hover(function(){
- $(this).addClass('front');
- },function(){
- $(this).removeClass('front');
- }
- );
+ .hover( function() {
+ $(this).addClass('front');
+ }, function() {
+ $(this).removeClass('front');
+ }
+ )
+ .bind('click', function() {
+ $(this).data('self').jumpToIndex($(this).data('pageIndex'));
+ });
+
+ $(marker).animate({top:'-25px'}, 'slow');
+
}
BookReader.prototype.removeSearchResults = function() {
+ this.removeSearchHilites(); //be sure to set all box.divs to null
$('#BRnavpos .search').remove();
}
BookReader.prototype.addChapter = function(chapterTitle, pageNumber, pageIndex) {
var uiStringPage = 'Page'; // i18n
- var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs);
+ var percentThrough = BookReader.util.cssPercentage(pageIndex, this.numLeafs - 1);
$('<div class="chapter" style="left:' + percentThrough + ';"><div class="title">'
+ chapterTitle + '<span>|</span> ' + uiStringPage + ' ' + pageNumber + '</div></div>')
- .appendTo('#BRnavpos')
+ .appendTo('#BRnavline')
.data({'self': this, 'pageIndex': pageIndex })
.bt({
contentSelector: '$(this).find(".title")',
trigger: 'hover',
closeWhenOthersOpen: true,
cssStyles: {
+ padding: '12px 14px',
backgroundColor: '#000',
- border: '2px solid #e2dcc5',
- borderBottom: 'none',
- padding: '5px 10px',
+ border: '4px solid #e2dcc5',
+ //borderBottom: 'none',
fontFamily: '"Arial", sans-serif',
- fontSize: '11px',
+ fontSize: '12px',
fontWeight: '700',
color: '#fff',
whiteSpace: 'nowrap'
padding: 0,
spikeGirth: 0,
spikeLength: 0,
- overlap: '16px',
+ overlap: '21px',
overlay: false,
killTitle: true,
textzIndex: 9999,
});
}
+/*
+ * Remove all chapters.
+ */
BookReader.prototype.removeChapters = function() {
$('#BRnavpos .chapter').remove();
}
-BookReader.prototype.initToolbar = function(mode, ui) {
-
- // $$$mang should be contained within the BookReader div instead of body
- $("body").append("<div id='BRtoolbar'>"
- + "<span id='BRtoolbarbuttons' style='float:right;'>"
- + "<button class='BRicon bookmark modal'></button>"
- + "<button class='BRicon link modal'></button>"
- + "<button class='BRicon embed modal'></button>"
- + "<button class='BRicon read modal'></button>"
- + "<button class='BRicon full'></button>"
-// + "<div class='BRtoolbarmode2' style='display: none'><button class='BRicon book_leftmost'></button><button class='BRicon book_left'></button><button class='BRicon book_right'></button><button class='BRicon book_rightmost'></button></div>"
-// + "<div class='BRtoolbarmode1' style='display: none'><button class='BRicon book_top'></button><button class='BRicon book_up'></button> <button class='BRicon book_down'></button><button class='BRicon book_bottom'></button></div>"
-// + "<div class='BRtoolbarmode3' style='display: none'><button class='BRicon book_top'></button><button class='BRicon book_up'></button> <button class='BRicon book_down'></button><button class='BRicon book_bottom'></button></div>"
-// + "<button class='BRicon play'></button><button class='BRicon pause' style='display: none'></button>"
- + "</span>"
-
- + "<span>"
- + "<a class='logo' href='" + this.logoURL + "'></a>"
- + "<button class='BRicon glass'></button>"
- /* XXXmang integrate search */
- + "<form method='get' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>"
- + "<button class='BRicon fit'></button>"
- + "<button class='BRicon thumb' onclick='br.switchMode(3); return false;'></button>"
- + "<button class='BRicon twopg' onclick='br.switchMode(2); return false;'></button>"
- + "</span>"
-
- + "</div>");
-
- this.updateToolbarZoom(this.reduce); // Pretty format
-
- if (ui == "embed" || ui == "touch") {
- $("#BookReader a.logo").attr("target","_blank");
+/*
+ * Update the table of contents based on array of TOC entries.
+ */
+BookReader.prototype.updateTOC = function(tocEntries) {
+ this.removeChapters();
+ for (var i = 0; i < tocEntries.length; i++) {
+ this.addChapterFromEntry(tocEntries[i]);
}
+}
- // $$$ turn this into a member variable
- var jToolbar = $('#BRtoolbar'); // j prefix indicates jQuery object
-
- // We build in mode 2
- jToolbar.append();
-
- this.bindToolbarNavHandlers(jToolbar);
-
- // Setup tooltips -- later we could load these from a file for i18n
- var titles = { '.logo': 'Go to Archive.org',
- '.zoom_in': 'Zoom in',
- '.zoom_out': 'Zoom out',
- '.onepg': 'One-page view',
- '.twopg': 'Two-page view',
- '.thumb': 'Thumbnail view',
- '.print': 'Print this page',
- '.embed': 'Embed BookReader',
- '.link': 'Link to this book (and page)',
- '.bookmark': 'Bookmark this page',
+/*
+ * Example table of contents entry - this format is defined by Open Library
+ * {
+ * "pagenum": "17",
+ * "level": 1,
+ * "label": "CHAPTER I",
+ * "type": {"key": "/type/toc_item"},
+ * "title": "THE COUNTRY AND THE MISSION"
+ * }
+ */
+BookReader.prototype.addChapterFromEntry = function(tocEntryObject) {
+ var pageIndex = this.getPageIndex(tocEntryObject['pagenum']);
+ // Only add if we know where it is
+ if (pageIndex) {
+ this.addChapter(tocEntryObject['title'], tocEntryObject['pagenum'], pageIndex);
+ }
+ $('.chapter').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ $('.search').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+ $('.searchChap').each(function(){
+ $(this).hover(function(){
+ $(this).addClass('front');
+ },function(){
+ $(this).removeClass('front');
+ });
+ });
+}
+
+BookReader.prototype.initToolbar = function(mode, ui) {
+
+ // $$$mang should be contained within the BookReader div instead of body
+ var readIcon = ''
+ if (!navigator.userAgent.match(/mobile/i)) {
+ readIcon = "<button class='BRicon read modal'></button>";
+ }
+
+ $("body").append(
+ "<div id='BRtoolbar'>"
+ + "<span id='BRtoolbarbuttons'>"
+ /* XXXmang integrate search */
+ + "<form action='javascript:' id='booksearch'><input type='search' id='textSrch' name='textSrch' val='' placeholder='Search inside'/><button type='submit' id='btnSrch' name='btnSrch'>GO</button></form>"
+ // XXXmang icons incorrect or handlers wrong
+ + "<button class='BRicon info'></button>"
+ + "<button class='BRicon share'></button>"
+ + readIcon
+ + "<button class='BRicon full'></button>"
+ + "</span>"
+ + "<span><a class='logo' href='" + this.logoURL + "'></a></span>"
+ + "<span id='BRreturn'><span>Back to</span><a href='" + this.bookUrl + "'>" + this.bookTitle + "</a></span>"
+ + "</div>"
+ /*
+ + "<div id='BRzoomer'>"
+ + "<div id='BRzoompos'>"
+ + "<button class='BRicon zoom_out'></button>"
+ + "<div id='BRzoomcontrol'>"
+ + "<div id='BRzoomstrip'></div>"
+ + "<div id='BRzoombtn'></div>"
+ + "</div>"
+ + "<button class='BRicon zoom_in'></button>"
+ + "</div>"
+ + "</div>"
+ */
+ );
+
+ this.updateToolbarZoom(this.reduce); // Pretty format
+
+ if (ui == "embed" || ui == "touch") {
+ $("#BookReader a.logo").attr("target","_blank");
+ }
+
+ // $$$ turn this into a member variable
+ var jToolbar = $('#BRtoolbar'); // j prefix indicates jQuery object
+
+ // We build in mode 2
+ jToolbar.append();
+
+ // Navigation handlers will be bound after all UI is in place -- makes moving icons between
+ // the toolbar and nav bar easier
+
+ // Setup tooltips -- later we could load these from a file for i18n
+ var titles = { '.logo': 'Go to Archive.org',
+ '.zoom_in': 'Zoom in',
+ '.zoom_out': 'Zoom out',
+ '.onepg': 'One-page view',
+ '.twopg': 'Two-page view',
+ '.thumb': 'Thumbnail view',
+ '.print': 'Print this page',
+ '.embed': 'Embed BookReader',
+ '.link': 'Link to this book (and page)',
+ '.bookmark': 'Bookmark this page',
'.read': 'Allow BookReader to read this aloud',
'.full': 'Show fullscreen',
'.book_left': 'Flip left',
}
}
-// bindToolbarNavHandlers
+// updateToolbarZoom(reduce)
+//______________________________________________________________________________
+// Update the displayed zoom factor based on reduction factor
+BookReader.prototype.updateToolbarZoom = function(reduce) {
+ var value;
+ var autofit = null;
+
+ // $$$ TODO preserve zoom/fit for each mode
+ if (this.mode == this.constMode2up) {
+ autofit = this.twoPage.autofit;
+ } else {
+ autofit = this.onePage.autofit;
+ }
+
+ if (autofit) {
+ value = autofit.slice(0,1).toUpperCase() + autofit.slice(1);
+ } else {
+ value = (100 / reduce).toFixed(2);
+ // Strip trailing zeroes and decimal if all zeroes
+ value = value.replace(/0+$/,'');
+ value = value.replace(/\.$/,'');
+ value += '%';
+ }
+ $('#BRzoom').text(value);
+}
+
+// bindNavigationHandlers
//______________________________________________________________________________
-// Binds the toolbar handlers
-BookReader.prototype.bindToolbarNavHandlers = function(jToolbar) {
+// Bind navigation handlers
+BookReader.prototype.bindNavigationHandlers = function() {
var self = this; // closure
+ jIcons = $('.BRicon');
+
+ jIcons.filter('.onepg').bind('click', function(e) {
+ self.switchMode(self.constMode1up);
+ });
+
+ jIcons.filter('.twopg').bind('click', function(e) {
+ self.switchMode(self.constMode2up);
+ });
- jToolbar.find('.book_left').click(function(e) {
+ jIcons.filter('.thumb').bind('click', function(e) {
+ self.switchMode(self.constModeThumb);
+ });
+
+ jIcons.filter('.fit').bind('fit', function(e) {
+ // XXXmang implement autofit zoom
+ });
+
+ jIcons.filter('.book_left').click(function(e) {
self.left();
return false;
});
- jToolbar.find('.book_right').click(function(e) {
+ jIcons.filter('.book_right').click(function(e) {
self.right();
return false;
});
- jToolbar.find('.book_up').bind('click', function(e) {
+ jIcons.filter('.book_up').bind('click', function(e) {
if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
self.scrollUp();
} else {
return false;
});
- jToolbar.find('.book_down').bind('click', function(e) {
+ jIcons.filter('.book_down').bind('click', function(e) {
if ($.inArray(self.mode, [self.constMode1up, self.constModeThumb]) >= 0) {
self.scrollDown();
} else {
return false;
});
- jToolbar.find('.print').click(function(e) {
+ jIcons.filter('.print').click(function(e) {
self.printPage();
return false;
});
- jToolbar.find('.embed').click(function(e) {
+ jIcons.filter('.embed').click(function(e) {
self.showEmbedCode();
return false;
});
- jToolbar.find('.bookmark').click(function(e) {
+ jIcons.filter('.bookmark').click(function(e) {
self.showBookmarkCode();
return false;
});
- jToolbar.find('.play').click(function(e) {
+ jIcons.filter('.play').click(function(e) {
self.autoToggle();
return false;
});
- jToolbar.find('.pause').click(function(e) {
+ jIcons.filter('.pause').click(function(e) {
self.autoToggle();
return false;
});
- jToolbar.find('.book_top').click(function(e) {
+ jIcons.filter('.book_top').click(function(e) {
self.first();
return false;
});
- jToolbar.find('.book_bottom').click(function(e) {
+ jIcons.filter('.book_bottom').click(function(e) {
self.last();
return false;
});
- jToolbar.find('.book_leftmost').click(function(e) {
+ jIcons.filter('.book_leftmost').click(function(e) {
self.leftmost();
return false;
});
- jToolbar.find('.book_rightmost').click(function(e) {
+ jIcons.filter('.book_rightmost').click(function(e) {
self.rightmost();
return false;
});
-}
-
-// updateToolbarZoom(reduce)
-//______________________________________________________________________________
-// Update the displayed zoom factor based on reduction factor
-BookReader.prototype.updateToolbarZoom = function(reduce) {
- var value;
- var autofit = null;
- // $$$ TODO preserve zoom/fit for each mode
- if (this.mode == this.constMode2up) {
- autofit = this.twoPage.autofit;
- } else {
- autofit = this.onePage.autofit;
- }
+ jIcons.filter('.read').click(function(e) {
+ self.ttsToggle();
+ return false;
+ });
- if (autofit) {
- value = autofit.slice(0,1).toUpperCase() + autofit.slice(1);
- } else {
- value = (100 / reduce).toFixed(2);
- // Strip trailing zeroes and decimal if all zeroes
- value = value.replace(/0+$/,'');
- value = value.replace(/\.$/,'');
- value += '%';
- }
- $('#BRzoom').text(value);
-}
+ jIcons.filter('.zoom_in').bind('click', function() {
+ self.zoom(1);
+ return false;
+ });
+
+ jIcons.filter('.zoom_out').bind('click', function() {
+ self.zoom(-1);
+ return false;
+ });
+
+ // XXX fix integration
+ $('#booksearch').bind('submit', function() {
+ self.search($('#textSrch').val());
+ });
-// bindNavigationHandlers
-//______________________________________________________________________________
-// Bind navigation handlers
-BookReader.prototype.bindNavigationHandlers = function() {
$('#BookReader').die('mousemove.navigation').live('mousemove.navigation',
{ 'br': this },
this.navigationMousemoveHandler
// $$$ don't hardcode height
$('#BRtoolbar').animate({top:-60});
$('#BRnav').animate({bottom:-60});
+ //$('#BRzoomer').animate({right:-26});
}
}
if (!this.navigationIsVisible()) {
$('#BRtoolbar').animate({top:0});
$('#BRnav').animate({bottom:0});
+ //$('#BRzoomer').animate({right:0});
}
}
if (newHash != self.oldUserHash) { // Only process new user hash once
//console.log('url change detected ' + self.oldLocationHash + " -> " + newHash);
+ self.ttsStop();
+
// Queue change if animating
if (self.animating) {
self.autoStop();
// searchHighlightVisible
//________
// Returns true if a search highlight is currently being displayed
-BookReader.prototype.searchHighlightVisible = function() {
+BookReader.prototype.searchHighlightVisible = function() {
+ var results = this.searchResults;
+ if (null == results) return false;
+
if (this.constMode2up == this.mode) {
- if (this.searchResults[this.twoPage.currentIndexL]
- || this.searchResults[this.twoPage.currentIndexR]) {
- return true;
- }
- } else { // 1up
- if (this.searchResults[this.currentIndex()]) {
- return true;
+ var visiblePages = Array(this.twoPage.currentIndexL, this.twoPage.currentIndexR);
+ } else if (this.constMode1up == this.mode) {
+ var visiblePages = Array();
+ visiblePages[0] = this.currentIndex();
+ } else {
+ return false;
+ }
+
+ var i, j;
+ for (i=0; i<results.matches.length; i++) {
+ //console.log(results.matches[i].par[0]);
+ for (j=0; j<results.matches[i].par[0].boxes.length; j++) {
+ var box = results.matches[i].par[0].boxes[j];
+ var pageIndex = this.leafNumToIndex(box.page);
+ if (jQuery.inArray(pageIndex, visiblePages) >= 0) {
+ return true;
+ }
}
}
+
return false;
}
// Returns the page URI or transparent image if out of range
BookReader.prototype._getPageURI = function(index, reduce, rotate) {
if (index < 0 || index >= this.numLeafs) { // Synthesize page
- return this.imagesBaseURL + "/transparent.png";
+ return this.imagesBaseURL + "transparent.png";
}
if ('undefined' == typeof(reduce)) {
return this.getPageURI(index, reduce, rotate);
}
+/*
+ * Update based on received record from Open Library.
+ */
+BookReader.prototype.gotOpenLibraryRecord = function(self, olObject) {
+ // $$$ could refactor this so that 'this' is available
+ if (olObject) {
+ if (olObject['table_of_contents']) {
+ // XXX check here that TOC is valid
+ self.updateTOC(olObject['table_of_contents']);
+ }
+ }
+
+ // $$$mang cleanup
+ $('#BRreturn a').attr('href', 'http://openlibrary.org' + olObject.key);
-/////// Functions that can/should be overriden by third-parties
-
-// If your book has a record on Open Library you get some nice things for free
-BookReader.prototype.getOpenLibraryJSON = function(callback) {
- return null;
}
-
// Library functions
BookReader.util = {
disableSelect: function(jObject) {
// Given value and maximum, calculate a percentage suitable for CSS
cssPercentage: function(value, max) {
- return parseInt(((value + 0.0) / max) * 100) + '%';
+ return (((value + 0.0) / max) * 100) + '%';
},
notInArray: function(value, array) {
}
// The final property here must NOT have a comma after it - IE7
}
+
+
+// ttsToggle()
+//______________________________________________________________________________
+BookReader.prototype.ttsToggle = function () {
+ if (false == this.ttsPlaying) {
+ this.ttsPlaying = true;
+ this.showProgressPopup('Loading audio...');
+ if(soundManager.supported()) {
+ this.ttsStart();
+ } else {
+ soundManager.onready(function(oStatus) {
+ if (oStatus.success) {
+ this.ttsStart();
+ } else {
+ alert('Could not load soundManager2, possibly due to FlashBlock. Audio playback is disabled');
+ }
+ }, this);
+ }
+ } else {
+ this.ttsStop();
+ }
+}
+
+// ttsStart()
+//______________________________________________________________________________
+BookReader.prototype.ttsStart = function () {
+ if (soundManager.debugMode) console.log('starting readAloud');
+ if (this.constModeThumb == this.mode) this.switchMode(this.constMode1up);
+
+ //this.ttsPlaying = true; //set this in ttsToggle()
+ this.ttsIndex = this.currentIndex();
+ this.ttsFormat = 'mp3';
+ if ($.browser.mozilla) {
+ this.ttsFormat = 'ogg';
+ }
+ this.ttsGetText(this.ttsIndex, 'ttsStartCB');
+}
+
+// ttsStop()
+//______________________________________________________________________________
+BookReader.prototype.ttsStop = function () {
+ if (false == this.ttsPlaying) return;
+
+ if (soundManager.debugMode) console.log('stopping readaloud');
+ soundManager.stopAll();
+ soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition);
+ this.ttsRemoveHilites();
+ this.removeProgressPopup();
+
+ this.ttsPlaying = false;
+ this.ttsIndex = null; //leaf index
+ this.ttsPosition = -1; //chunk (paragraph) number
+ this.ttsBuffering = false;
+ this.ttsPoller = null;
+}
+
+// ttsGetText()
+//______________________________________________________________________________
+BookReader.prototype.ttsGetText = function(index, callback) {
+ var url = 'http://'+this.server+'/BookReader/BookReaderGetTextWrapper.php?path='+this.bookPath+'_djvu.xml&page='+index;
+ this.ttsAjax = $.ajax({url:url, dataType:'jsonp', jsonpCallback:callback});
+}
+
+// ttsStartCB(): text-to-speech callback
+//______________________________________________________________________________
+BookReader.prototype.ttsStartCB = function (data) {
+ if (soundManager.debugMode) console.log('ttsStartCB got data: ' + data);
+ this.ttsChunks = data;
+ this.ttsHilites = [];
+
+ //deal with the page being blank
+ if (0 == data.length) {
+ if (soundManager.debugMode) console.log('first page is blank!');
+ if(this.ttsAdvance(true)) {
+ this.ttsGetText(this.ttsIndex, 'ttsStartCB');
+ }
+ return;
+ }
+
+ this.showProgressPopup('Loading audio...');
+
+ ///// whileloading: broken on safari
+ ///// onload fires on safari, but *after* the sound starts playing..
+ this.ttsPosition = -1;
+ var snd = soundManager.createSound({
+ id: 'chunk'+this.ttsIndex+'-0',
+ //url: 'http://home.us.archive.org/~rkumar/arctic.ogg',
+ url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(data[0][0]) + '&format=.'+this.ttsFormat, //the .ogg is to trick SoundManager2 to use the HTML5 audio player
+ whileloading: function(){if (this.bytesLoaded == this.bytesTotal) this.br.removeProgressPopup();}, //onload never fires in FF...
+ onload: function(){this.br.removeProgressPopup();} //whileloading never fires in safari...
+ });
+ snd.br = this;
+ snd.load();
+
+ this.ttsNextChunk();
+}
+
+// showProgressPopup
+//______________________________________________________________________________
+BookReader.prototype.showProgressPopup = function(msg) {
+ if (soundManager.debugMode) console.log('showProgressPopup index='+this.ttsIndex+' pos='+this.ttsPosition);
+ if (this.popup) return;
+
+ this.popup = document.createElement("div");
+ $(this.popup).css({
+ top: ($('#BookReader').height()*0.5-100) + 'px',
+ left: ($('#BookReader').width()-300)*0.5 + 'px',
+ width: '300px',
+ border: '2px solid black'
+ }).attr('className', 'BRprogresspopup');
+
+ var bar = document.createElement("div");
+ $(bar).css({
+ height: '20px'
+ }).attr('className', 'BRprogressbar');
+ $(this.popup).append(bar);
+
+ if (msg) {
+ var msgdiv = document.createElement("div");
+ msgdiv.innerHTML = msg;
+ $(this.popup).append(msgdiv);
+ }
+
+ $(this.popup).appendTo('#BookReader');
+}
+
+// removeProgressPopup
+//______________________________________________________________________________
+BookReader.prototype.removeProgressPopup = function() {
+ $(this.popup).remove();
+ this.popup=null;
+}
+
+// ttsNextPageCB
+//______________________________________________________________________________
+BookReader.prototype.ttsNextPageCB = function (data) {
+ this.ttsNextChunks = data;
+ if (soundManager.debugMode) console.log('preloaded next chunks.. data is ' + data);
+
+ if (true == this.ttsBuffering) {
+ if (soundManager.debugMode) console.log('ttsNextPageCB: ttsBuffering is true');
+ this.ttsBuffering = false;
+ }
+}
+
+// ttsLoadChunk
+//______________________________________________________________________________
+BookReader.prototype.ttsLoadChunk = function (page, pos, string) {
+ var snd = soundManager.createSound({
+ id: 'chunk'+page+'-'+pos,
+ url: 'http://'+this.server+'/BookReader/BookReaderGetTTS.php?string=' + escape(string) + '&format=.'+this.ttsFormat //the .ogg is to trick SoundManager2 to use the HTML5 audio player
+ });
+ snd.br = this;
+ snd.load()
+}
+
+
+// ttsNextChunk()
+//______________________________________________________________________________
+// This function into two parts: ttsNextChunk gets run before page flip animation
+// and ttsNextChunkPhase2 get run after page flip animation.
+// If a page flip is necessary, ttsAdvance() will return false so Phase2 isn't
+// called. Instead, this.animationFinishedCallback is set, so that Phase2
+// continues after animation is finished.
+
+BookReader.prototype.ttsNextChunk = function () {
+ if (soundManager.debugMode) console.log('nextchunk pos=' + this.ttsPosition);
+
+ if (-1 != this.ttsPosition) {
+ soundManager.destroySound('chunk'+this.ttsIndex+'-'+this.ttsPosition);
+ }
+
+ this.ttsRemoveHilites(); //remove old hilights
+
+ var moreToPlay = this.ttsAdvance();
+
+ if (moreToPlay) {
+ this.ttsNextChunkPhase2();
+ }
+
+ //This function is called again when ttsPlay() has finished playback.
+ //If the next chunk of text has not yet finished loading, ttsPlay()
+ //will start polling until the next chunk is ready.
+}
+
+// ttsNextChunkPhase2()
+//______________________________________________________________________________
+// page flip animation has now completed
+BookReader.prototype.ttsNextChunkPhase2 = function () {
+ if (null == this.ttsChunks) {
+ alert('error: ttsChunks is null?'); //TODO
+ return;
+ }
+
+ if (0 == this.ttsChunks.length) {
+ if (soundManager.debugMode) console.log('ttsNextChunk2: ttsChunks.length is zero.. hacking...');
+ this.ttsStartCB(this.ttsChunks);
+ return;
+ }
+
+ if (soundManager.debugMode) console.log('next chunk is ' + this.ttsPosition);
+
+ //prefetch next page of text
+ if (0 == this.ttsPosition) {
+ if (this.ttsIndex<(this.numLeafs-1)) {
+ this.ttsGetText(this.ttsIndex+1, 'ttsNextPageCB');
+ }
+ }
+
+ this.ttsPrefetchAudio();
+
+ this.ttsPlay();
+}
+
+// ttsAdvance()
+//______________________________________________________________________________
+// 1. advance ttsPosition
+// 2. if necessary, advance ttsIndex, and copy ttsNextChunks to ttsChunks
+// 3. if necessary, flip to current page, or scroll so chunk is visible
+// 4. do something smart is ttsNextChunks has not yet finished preloading (TODO)
+// 5. stop playing at end of book
+
+BookReader.prototype.ttsAdvance = function (starting) {
+ this.ttsPosition++;
+
+ if (this.ttsPosition >= this.ttsChunks.length) {
+
+ if (this.ttsIndex == (this.numLeafs-1)) {
+ if (soundManager.debugMode) console.log('tts stop');
+ return false;
+ } else {
+ if ((null != this.ttsNextChunks) || (starting)) {
+ if (soundManager.debugMode) console.log('moving to next page!');
+ this.ttsIndex++;
+ this.ttsPosition = 0;
+ this.ttsChunks = this.ttsNextChunks;
+ this.ttsNextChunks = null;
+
+ //A page flip might be necessary. This code is confusing since
+ //ttsNextChunks might be null if we are starting on a blank page.
+ if (2 == this.mode) {
+ if ((this.ttsIndex != this.twoPage.currentIndexL) && (this.ttsIndex != this.twoPage.currentIndexR)) {
+ if (!starting) {
+ this.animationFinishedCallback = this.ttsNextChunkPhase2;
+ this.next();
+ return false;
+ } else {
+ this.next();
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ } else {
+ if (soundManager.debugMode) console.log('ttsAdvance: ttsNextChunks is null');
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// ttsPrefetchAudio()
+//______________________________________________________________________________
+BookReader.prototype.ttsPrefetchAudio = function () {
+
+ if(false != this.ttsBuffering) {
+ alert('TTS Error: prefetch() called while content still buffering!');
+ return;
+ }
+
+ //preload next chunk
+ var nextPos = this.ttsPosition+1;
+ if (nextPos < this.ttsChunks.length) {
+ this.ttsLoadChunk(this.ttsIndex, nextPos, this.ttsChunks[nextPos][0]);
+ } else {
+ //for a short page, preload might nt have yet returned..
+ if (soundManager.debugMode) console.log('preloading chunk 0 from next page, index='+(this.ttsIndex+1));
+ if (null != this.ttsNextChunks) {
+ if (0 != this.ttsNextChunks.length) {
+ this.ttsLoadChunk(this.ttsIndex+1, 0, this.ttsNextChunks[0][0]);
+ } else {
+ if (soundManager.debugMode) console.log('prefetchAudio(): ttsNextChunks is zero length!');
+ }
+ } else {
+ if (soundManager.debugMode) console.log('ttsNextChunks is null, not preloading next page');
+ this.ttsBuffering = true;
+ }
+ }
+
+}
+
+// ttsPlay()
+//______________________________________________________________________________
+BookReader.prototype.ttsPlay = function () {
+
+ var chunk = this.ttsChunks[this.ttsPosition];
+ if (soundManager.debugMode) {
+ console.log('ttsPlay position = ' + this.ttsPosition);
+ console.log('chunk = ' + chunk);
+ console.log(this.ttsChunks);
+ }
+
+ //add new hilights
+ if (2 == this.mode) {
+ this.ttsHilite2UP(chunk);
+ } else {
+ this.ttsHilite1UP(chunk);
+ }
+
+ this.ttsScrollToChunk(chunk);
+
+ //play current chunk
+ if (false == this.ttsBuffering) {
+ soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsNextChunk();}});
+ } else {
+ soundManager.play('chunk'+this.ttsIndex+'-'+this.ttsPosition,{onfinish:function(){br.ttsStartPolling();}});
+ }
+}
+
+// scrollToChunk()
+//______________________________________________________________________________
+BookReader.prototype.ttsScrollToChunk = function(chunk) {
+ if (this.constMode1up != this.mode) return;
+
+ var leafTop = 0;
+ var h;
+ var i;
+ for (i=0; i<this.ttsIndex; i++) {
+ h = parseInt(this._getPageHeight(i)/this.reduce);
+ leafTop += h + this.padding;
+ }
+
+ var chunkTop = chunk[1][3]; //coords are in l,b,r,t order
+ var chunkBot = chunk[chunk.length-1][1];
+
+ var topOfFirstChunk = leafTop + chunkTop/this.reduce;
+ var botOfLastChunk = leafTop + chunkBot/this.reduce;
+
+ if (soundManager.debugMode) console.log('leafTop = ' + leafTop + ' topOfFirstChunk = ' + topOfFirstChunk + ' botOfLastChunk = ' + botOfLastChunk);
+
+ var containerTop = $('#BRcontainer').attr('scrollTop');
+ var containerBot = containerTop + $('#BRcontainer').height();
+ if (soundManager.debugMode) console.log('containerTop = ' + containerTop + ' containerBot = ' + containerBot);
+
+ if ((topOfFirstChunk < containerTop) || (botOfLastChunk > containerBot)) {
+ //jumpToIndex scrolls so that chunkTop is centered.. we want chunkTop at the top
+ //this.jumpToIndex(this.ttsIndex, null, chunkTop);
+ $('#BRcontainer').animate({scrollTop: topOfFirstChunk},'fast');
+ }
+}
+
+// ttsHilite1UP()
+//______________________________________________________________________________
+BookReader.prototype.ttsHilite1UP = function(chunk) {
+ var i;
+ for (i=1; i<chunk.length; i++) {
+ //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
+ var l = chunk[i][0];
+ var b = chunk[i][1];
+ var r = chunk[i][2];
+ var t = chunk[i][3];
+
+ var div = document.createElement('div');
+ this.ttsHilites.push(div);
+ $(div).attr('className', 'BookReaderSearchHilite').appendTo('#pagediv'+this.ttsIndex);
+
+ $(div).css({
+ width: (r-l)/this.reduce + 'px',
+ height: (b-t)/this.reduce + 'px',
+ left: l/this.reduce + 'px',
+ top: t/this.reduce +'px'
+ });
+ }
+
+}
+
+// ttsHilite2UP()
+//______________________________________________________________________________
+BookReader.prototype.ttsHilite2UP = function (chunk) {
+ var i;
+ for (i=1; i<chunk.length; i++) {
+ //each rect is an array of l,b,r,t coords (djvu.xml ordering...)
+ var l = chunk[i][0];
+ var b = chunk[i][1];
+ var r = chunk[i][2];
+ var t = chunk[i][3];
+
+ var div = document.createElement('div');
+ this.ttsHilites.push(div);
+ $(div).attr('className', 'BookReaderSearchHilite').css('zIndex', 3).appendTo('#BRtwopageview');
+ this.setHilightCss2UP(div, this.ttsIndex, l, r, t, b);
+ }
+}
+
+// ttsRemoveHilites()
+//______________________________________________________________________________
+BookReader.prototype.ttsRemoveHilites = function (chunk) {
+ $(this.ttsHilites).remove();
+ this.ttsHilites = [];
+}
+
+// ttsStartPolling()
+//______________________________________________________________________________
+// Play of the current chunk has ended, but the next chunk has not yet been loaded.
+// We need to wait for the text for the next page to be loaded, so we can
+// load the next audio chunk
+BookReader.prototype.ttsStartPolling = function () {
+ if (soundManager.debugMode) console.log('Starting the TTS poller...');
+ var self = this;
+ this.ttsPoller=setInterval(function(){
+ if (self.ttsBuffering) {return;}
+
+ if (soundManager.debugMode) console.log('TTS buffering finished!');
+ clearInterval(self.ttsPoller);
+ self.ttsPoller = null;
+ self.ttsPrefetchAudio();
+ self.ttsNextChunk();
+ },500);
+}
+//FADING, ETC.
+ function changeArrow(){
+ setTimeout(function(){
+ $('#BRnavCntlBtm').removeClass('BRdn').addClass('BRup');
+ },3000);
+ };
+ $().ready(function(){
+ $('#BRtoolbar').animate({top:0},3000).animate({top:-40});
+ $('#BRnav').animate({bottom:0},3000).animate({bottom:-53});
+ changeArrow();
+ $('.BRnavCntl').animate({opacity:1},3000).animate({height:'43px'}).animate({opacity:1},1000).animate({opacity:.25},1000);
+ $('.BRnavCntl').click(
+ function(){
+ if ($('#BRnavCntlBtm').hasClass('BRdn')) {
+ $('#BRtoolbar').animate({top:-40});
+ $('#BRnav').animate({bottom:-53});
+ $('#BRnavCntlBtm').addClass('BRup').removeClass('BRdn');
+ $('.BRnavCntl').animate({height:'43px'}).animate({opacity:1},1000).animate({opacity:.25},1000);
+ } else {
+ $('#BRtoolbar').animate({top:0});
+ $('#BRnav').animate({bottom:0});
+ $('#BRnavCntlBtm').addClass('BRdn').removeClass('BRup');
+ $('.BRnavCntl').animate({opacity:1,height:'30px'});
+ };
+ }
+ );
+ $('#BRnavCntlBtm').mouseover(function(){
+ if ($(this).hasClass('BRup')) {
+ $('.BRnavCntl').animate({opacity:1},250);
+ };
+ });
+ $('#BRnavCntlBtm').mouseleave(function(){
+ if ($(this).hasClass('BRup')) {
+ $('.BRnavCntl').animate({opacity:.25},250);
+ };
+ });
+ });
+