diff --git a/resources/content_server/monocle.js b/resources/content_server/monocle.js new file mode 100644 index 0000000000..c0642743f7 --- /dev/null +++ b/resources/content_server/monocle.js @@ -0,0 +1,3385 @@ +Monocle = { + VERSION: "1.0.0" +}; + + +Monocle.pieceLoaded = function (piece) { + if (typeof onMonoclePiece == 'function') { + onMonoclePiece(piece); + } +} + + +Monocle.defer = function (fn, time) { + if (fn && typeof fn == "function") { + return setTimeout(fn, time || 0); + } +} + + +Monocle.Browser = { engine: 'W3C' } + +Monocle.Browser.is = { + IE: (!!(window.attachEvent && navigator.userAgent.indexOf('Opera') === -1)) && + (Monocle.Browser.engine = "IE"), + Opera: navigator.userAgent.indexOf('Opera') > -1 && + (Monocle.Browser.engine = "Opera"), + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1 && + (Monocle.Browser.engine = "WebKit"), + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1 && + (Monocle.Browser.engine = "Gecko"), + MobileSafari: !!navigator.userAgent.match(/AppleWebKit.*Mobile/) +} // ... with thanks to PrototypeJS. + + +Monocle.Browser.on = { + iPhone: navigator.userAgent.indexOf("iPhone") != -1, + iPad: navigator.userAgent.indexOf("iPad") != -1, + BlackBerry: navigator.userAgent.indexOf("BlackBerry") != -1, + Android: navigator.userAgent.indexOf('Android') != -1, + Kindle3: navigator.userAgent.match(/Kindle\/3/) +} + + +if (Monocle.Browser.is.MobileSafari) { + (function () { + var ver = navigator.userAgent.match(/ OS ([\d_]+)/); + if (ver) { + Monocle.Browser.iOSVersion = ver[1].replace(/_/g, '.'); + } else { + console.warn("Unknown MobileSafari user agent: "+navigator.userAgent); + } + })(); +} +Monocle.Browser.iOSVersionBelow = function (strOrNum) { + return Monocle.Browser.iOSVersion && Monocle.Browser.iOSVersion < strOrNum; +} + + +Monocle.Browser.CSSProps = { + engines: ["W3C", "WebKit", "Gecko", "Opera", "IE", "Konqueror"], + prefixes: ["", "-webkit-", "-moz-", "-o-", "-ms-", "-khtml-"], + domprefixes: ["", "Webkit", "Moz", "O", "ms", "Khtml"], + guineapig: document.createElement('div') +} + + +Monocle.Browser.CSSProps.capStr = function (wd) { + return wd ? wd.charAt(0).toUpperCase() + wd.substr(1) : ""; +} + + +Monocle.Browser.CSSProps.toDOMProps = function (prop, prefix) { + var parts = prop.split('-'); + for (var i = parts.length; i > 0; --i) { + parts[i] = Monocle.Browser.CSSProps.capStr(parts[i]); + } + + if (typeof(prefix) != 'undefined' && prefix != null) { + if (prefix) { + parts[0] = Monocle.Browser.CSSProps.capStr(parts[0]); + return prefix+parts.join(''); + } else { + return parts.join(''); + } + } + + var props = [parts.join('')]; + parts[0] = Monocle.Browser.CSSProps.capStr(parts[0]); + for (i = 0; i < Monocle.Browser.CSSProps.prefixes.length; ++i) { + var pf = Monocle.Browser.CSSProps.domprefixes[i]; + if (!pf) { continue; } + props.push(pf+parts.join('')); + } + return props; +} + + +Monocle.Browser.CSSProps.toDOMProp = function (prop) { + return Monocle.Browser.CSSProps.toDOMProps( + prop, + Monocle.Browser.CSSProps.domprefixes[ + Monocle.Browser.CSSProps.engines.indexOf(Monocle.Browser.engine) + ] + ); +} + + +Monocle.Browser.CSSProps.isSupported = function (props) { + for (var i in props) { + if (Monocle.Browser.CSSProps.guineapig.style[props[i]] !== undefined) { + return true; + } + } + return false; +} // Thanks modernizr! + + +Monocle.Browser.CSSProps.isSupportedForAnyPrefix = function (prop) { + return Monocle.Browser.CSSProps.isSupported( + Monocle.Browser.CSSProps.toDOMProps(prop) + ); +} + + +Monocle.Browser.CSSProps.supportsMediaQuery = function (query) { + var gpid = "monocle_guineapig"; + var div = Monocle.Browser.CSSProps.guineapig; + div.id = gpid; + var st = document.createElement('style'); + st.textContent = query+'{#'+gpid+'{height:3px}}'; + (document.head || document.getElementsByTagName('head')[0]).appendChild(st); + document.documentElement.appendChild(div); + + var result = Monocle.Browser.CSSProps.guineapig.offsetHeight === 3; + + st.parentNode.removeChild(st); + div.parentNode.removeChild(div); + + return result; +} // Thanks modernizr! + + +Monocle.Browser.CSSProps.supportsMediaQueryProperty = function (prop) { + return Monocle.Browser.CSSProps.supportsMediaQuery( + '@media ('+Monocle.Browser.CSSProps.prefixes.join(prop+'),(')+'monocle__)' + ); +} + + + +Monocle.Browser.has = {} +Monocle.Browser.has.touch = ('ontouchstart' in window) || + Monocle.Browser.CSSProps.supportsMediaQueryProperty('touch-enabled'); +Monocle.Browser.has.columns = Monocle.Browser.CSSProps.isSupportedForAnyPrefix( + 'column-width' +); +Monocle.Browser.has.transform3d = Monocle.Browser.CSSProps.isSupported([ + 'perspectiveProperty', + 'WebkitPerspective', + 'MozPerspective', + 'OPerspective', + 'msPerspective' +]) && Monocle.Browser.CSSProps.supportsMediaQueryProperty('transform-3d'); +Monocle.Browser.has.iframeTouchBug = Monocle.Browser.iOSVersionBelow("4.2"); +Monocle.Browser.has.selectThruBug = Monocle.Browser.iOSVersionBelow("4.2"); +Monocle.Browser.has.mustScrollSheaf = Monocle.Browser.is.MobileSafari; +Monocle.Browser.has.iframeDoubleWidthBug = Monocle.Browser.has.mustScrollSheaf; +Monocle.Browser.has.floatColumnBug = Monocle.Browser.is.WebKit; + + +if (typeof window.console == "undefined") { + window.console = { + messages: [], + log: function (msg) { + this.messages.push(msg); + } + } +} + + +window.console.compatDir = function (obj) { + var stringify = function (o) { + var parts = []; + for (x in o) { + parts.push(x + ": " + o[x]); + } + return parts.join("; "); + } + + window.console.log(stringify(obj)); +} + + +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(elt /*, from*/) { + var len = this.length >>> 0; + + var from = Number(arguments[1]) || 0; + from = (from < 0) + ? Math.ceil(from) + : Math.floor(from); + if (from < 0) { + from += len; + } + + for (; from < len; from++) { + if (from in this && this[from] === elt) { + return from; + } + } + return -1; + }; +} + + +Monocle.pieceLoaded('compat'); +Monocle.Factory = function (element, label, index, reader) { + + var API = { constructor: Monocle.Factory }; + var k = API.constants = API.constructor; + var p = API.properties = { + element: element, + label: label, + index: index, + reader: reader, + prefix: reader.properties.classPrefix || '' + } + + + function initialize() { + var node = p.reader.properties.graph; + node[p.label] = node[p.label] || []; + if (typeof p.index == 'undefined' && node[p.label][p.index]) { + throw('Element already exists in graph: '+p.label+'['+p.index+']'); + } else { + p.index = p.index || node[p.label].length; + } + node[p.label][p.index] = p.element; + + addClass(p.label); + } + + + function find(oLabel, oIndex) { + if (!p.reader.properties.graph[oLabel]) { + return null; + } + return p.reader.properties.graph[oLabel][oIndex || 0]; + } + + + function claim(oElement, oLabel, oIndex) { + return oElement.dom = new Monocle.Factory( + oElement, + oLabel, + oIndex, + p.reader + ); + } + + + function make(tagName, oLabel, index_or_options, or_options) { + var oIndex, options; + if (arguments.length == 2) { + oIndex = 0; + options = {}; + } else if (arguments.length == 4) { + oIndex = arguments[2]; + options = arguments[3]; + } else if (arguments.length == 3) { + var lastArg = arguments[arguments.length - 1]; + if (typeof lastArg == "number") { + oIndex = lastArg; + options = {}; + } else { + oIndex = 0; + options = lastArg; + } + } + + var oElement = document.createElement(tagName); + claim(oElement, oLabel, oIndex); + if (options['class']) { + oElement.className += " "+p.prefix+options['class']; + } + if (options['html']) { + oElement.innerHTML = options['html']; + } + if (options['text']) { + oElement.appendChild(document.createTextNode(options['text'])); + } + + return oElement; + } + + + function append(tagName, oLabel, index_or_options, or_options) { + var oElement = make.apply(this, arguments); + p.element.appendChild(oElement); + return oElement; + } + + + function address() { + return [p.label, p.index, p.reader]; + } + + + function setStyles(rules) { + return Monocle.Styles.applyRules(p.element, rules); + } + + + function setBetaStyle(property, value) { + return Monocle.Styles.affix(p.element, property, value); + } + + + + function hasClass(name) { + name = p.prefix + name; + var klass = p.element.className; + if (!klass) { return false; } + if (klass == name) { return true; } + return new RegExp("(^|\\s)"+name+"(\\s|$)").test(klass); + } + + + function addClass(name) { + if (hasClass(name)) { return; } + var gap = p.element.className ? ' ' : ''; + return p.element.className += gap+p.prefix+name; + } + + + function removeClass(name) { + var reName = new RegExp("(^|\\s+)"+p.prefix+name+"(\\s+|$)"); + var reTrim = /^\s+|\s+$/g; + var klass = p.element.className; + p.element.className = klass.replace(reName, ' ').replace(reTrim, ''); + return p.element.className; + } + + + API.find = find; + API.claim = claim; + API.make = make; + API.append = append; + API.address = address; + + API.setStyles = setStyles; + API.setBetaStyle = setBetaStyle; + API.hasClass = hasClass; + API.addClass = addClass; + API.removeClass = removeClass; + + initialize(); + + return API; +} + +Monocle.pieceLoaded('factory'); +Monocle.Events = {} + + +Monocle.Events.listen = function (elem, evtType, fn, useCapture) { + if (elem.addEventListener) { + return elem.addEventListener(evtType, fn, useCapture || false); + } else if (elem.attachEvent) { + return elem.attachEvent('on'+evtType, fn); + } +} + + +Monocle.Events.deafen = function (elem, evtType, fn, useCapture) { + if (elem.removeEventListener) { + return elem.removeEventListener(evtType, fn, useCapture || false); + } else if (elem.detachEvent) { + try { + return elem.detachEvent('on'+evtType, fn); + } catch(e) {} + } +} + + +Monocle.Events.listenForContact = function (elem, fns, options) { + var listeners = {}; + + var cursorInfo = function (evt, ci) { + evt.m = { + pageX: ci.pageX, + pageY: ci.pageY + }; + + var target = evt.target || window.srcElement; + while (target.nodeType != 1 && target.parentNode) { + target = target.parentNode; + } + + var offset = offsetFor(evt, target); + evt.m.offsetX = offset[0]; + evt.m.offsetY = offset[1]; + + if (evt.currentTarget) { + offset = offsetFor(evt, evt.currentTarget); + evt.m.registrantX = offset[0]; + evt.m.registrantY = offset[1]; + } + + return evt; + } + + + var offsetFor = function (evt, elem) { + var r; + if (elem.getBoundingClientRect) { + var er = elem.getBoundingClientRect(); + var dr = document.body.getBoundingClientRect(); + r = { left: er.left - dr.left, top: er.top - dr.top }; + } else { + r = { left: elem.offsetLeft, top: elem.offsetTop } + while (elem = elem.parentNode) { + if (elem.offsetLeft || elem.offsetTop) { + r.left += elem.offsetLeft; + r.top += elem.offsetTop; + } + } + } + return [evt.m.pageX - r.left, evt.m.pageY - r.top]; + } + + + var capture = (options && options.useCapture) || false; + + if (!Monocle.Browser.has.touch) { + if (fns.start) { + listeners.mousedown = function (evt) { + if (evt.button != 0) { return; } + fns.start(cursorInfo(evt, evt)); + } + Monocle.Events.listen(elem, 'mousedown', listeners.mousedown, capture); + } + if (fns.move) { + listeners.mousemove = function (evt) { + fns.move(cursorInfo(evt, evt)); + } + Monocle.Events.listen(elem, 'mousemove', listeners.mousemove, capture); + } + if (fns.end) { + listeners.mouseup = function (evt) { + fns.end(cursorInfo(evt, evt)); + } + Monocle.Events.listen(elem, 'mouseup', listeners.mouseup, capture); + } + if (fns.cancel) { + listeners.mouseout = function (evt) { + obj = evt.relatedTarget || evt.fromElement; + while (obj && (obj = obj.parentNode)) { + if (obj == elem) { return; } + } + fns.cancel(cursorInfo(evt, evt)); + } + Monocle.Events.listen(elem, 'mouseout', listeners.mouseout, capture); + } + } else { + if (fns.start) { + listeners.start = function (evt) { + if (evt.touches.length > 1) { return; } + fns.start(cursorInfo(evt, evt.targetTouches[0])); + } + } + if (fns.move) { + listeners.move = function (evt) { + if (evt.touches.length > 1) { return; } + fns.move(cursorInfo(evt, evt.targetTouches[0])); + } + } + if (fns.end) { + listeners.end = function (evt) { + fns.end(cursorInfo(evt, evt.changedTouches[0])); + evt.preventDefault(); + } + } + if (fns.cancel) { + listeners.cancel = function (evt) { + fns.cancel(cursorInfo(evt, evt.changedTouches[0])); + } + } + + if (Monocle.Browser.has.iframeTouchBug) { + Monocle.Events.tMonitor = Monocle.Events.tMonitor || + new Monocle.Events.TouchMonitor(); + Monocle.Events.tMonitor.listen(elem, listeners, options); + } else { + for (etype in listeners) { + Monocle.Events.listen(elem, 'touch'+etype, listeners[etype], capture); + } + } + } + + return listeners; +} + + +Monocle.Events.deafenForContact = function (elem, listeners) { + var prefix = ""; + if (Monocle.Browser.has.touch) { + prefix = Monocle.Browser.has.iframeTouchBug ? "contact" : "touch"; + } + + for (evtType in listeners) { + Monocle.Events.deafen(elem, prefix + evtType, listeners[evtType]); + } +} + + +Monocle.Events.listenForTap = function (elem, fn) { + var startPos; + + if (Monocle.Browser.on.Kindle3) { + Monocle.Events.listen(elem, 'click', function () {}); + } + + var annulIfOutOfBounds = function (evt) { + if (evt.type.match(/^mouse/)) { + return; + } + if (Monocle.Browser.is.MobileSafari && Monocle.Browser.iOSVersion < "3.2") { + return; + } + if ( + evt.m.registrantX < 0 || evt.m.registrantX > elem.offsetWidth || + evt.m.registrantY < 0 || evt.m.registrantY > elem.offsetHeight + ) { + startPos = null; + } else { + evt.preventDefault(); + } + } + + return Monocle.Events.listenForContact( + elem, + { + start: function (evt) { + startPos = [evt.m.pageX, evt.m.pageY]; + evt.preventDefault(); + }, + move: annulIfOutOfBounds, + end: function (evt) { + annulIfOutOfBounds(evt); + if (startPos) { + evt.m.startOffset = startPos; + fn(evt); + } + }, + cancel: function (evt) { + startPos = null; + } + }, + { + useCapture: false + } + ); +} + + +Monocle.Events.deafenForTap = Monocle.Events.deafenForContact; + + +Monocle.Events.TouchMonitor = function () { + if (Monocle.Events == this) { + return new Monocle.Events.TouchMonitor(); + } + + var API = { constructor: Monocle.Events.TouchMonitor } + var k = API.constants = API.constructor; + var p = API.properties = { + touching: null, + edataPrev: null, + originator: null, + brokenModel_4_1: navigator.userAgent.match(/ OS 4_1/) + } + + + function listenOnIframe(iframe) { + if (iframe.contentDocument) { + enableTouchProxy(iframe.contentDocument); + iframe.contentDocument.isTouchFrame = true; + } + + if (p.brokenModel_4_1) { + enableTouchProxy(iframe); + } + } + + + function listen(element, fns, useCapture) { + for (etype in fns) { + Monocle.Events.listen(element, 'contact'+etype, fns[etype], useCapture); + } + enableTouchProxy(element, useCapture); + } + + + function enableTouchProxy(element, useCapture) { + if (element.monocleTouchProxy) { + return; + } + element.monocleTouchProxy = true; + + var fn = function (evt) { touchProxyHandler(element, evt) } + Monocle.Events.listen(element, "touchstart", fn, useCapture); + Monocle.Events.listen(element, "touchmove", fn, useCapture); + Monocle.Events.listen(element, "touchend", fn, useCapture); + Monocle.Events.listen(element, "touchcancel", fn, useCapture); + } + + + function touchProxyHandler(element, evt) { + var edata = { + start: evt.type == "touchstart", + move: evt.type == "touchmove", + end: evt.type == "touchend" || evt.type == "touchcancel", + time: new Date().getTime(), + frame: element.isTouchFrame + } + + if (!p.touching) { + p.originator = element; + } + + var target = element; + var touch = evt.touches[0] || evt.changedTouches[0]; + target = document.elementFromPoint(touch.screenX, touch.screenY); + + if (target) { + translateTouchEvent(element, target, evt, edata); + } + } + + + function translateTouchEvent(element, target, evt, edata) { + if ( + p.brokenModel_4_1 && + !edata.frame && + !p.touching && + edata.start && + p.edataPrev && + p.edataPrev.end && + (edata.time - p.edataPrev.time) < 30 + ) { + evt.preventDefault(); + return; + } + + if (!p.touching && !edata.end) { + return fireStart(evt, target, edata); + } + + if (edata.move && p.touching) { + return fireMove(evt, edata); + } + + if (p.brokenModel_4_1) { + if (p.touching && !edata.frame) { + return fireProvisionalEnd(evt, edata); + } + } else { + if (edata.end && p.touching) { + return fireProvisionalEnd(evt, edata); + } + } + + if ( + p.brokenModel_4_1 && + p.originator != element && + edata.frame && + edata.end + ) { + evt.preventDefault(); + return; + } + + if (edata.frame && edata.end && p.touching) { + return fireProvisionalEnd(evt, edata); + } + } + + + function fireStart(evt, target, edata) { + p.touching = target; + p.edataPrev = edata; + return fireTouchEvent(p.touching, 'start', evt); + } + + + function fireMove(evt, edata) { + clearProvisionalEnd(); + p.edataPrev = edata; + return fireTouchEvent(p.touching, 'move', evt); + } + + + function fireEnd(evt, edata) { + var result = fireTouchEvent(p.touching, 'end', evt); + p.edataPrev = edata; + p.touching = null; + return result; + } + + + function fireProvisionalEnd(evt, edata) { + clearProvisionalEnd(); + var mimicEvt = mimicTouchEvent(p.touching, 'end', evt); + p.edataPrev = edata; + + p.provisionalEnd = setTimeout( + function() { + if (p.touching) { + p.touching.dispatchEvent(mimicEvt); + p.touching = null; + } + }, + 30 + ); + } + + + function clearProvisionalEnd() { + if (p.provisionalEnd) { + clearTimeout(p.provisionalEnd); + p.provisionalEnd = null; + } + } + + + function mimicTouchEvent(target, newtype, evt) { + var cloneTouch = function (t) { + return document.createTouch( + document.defaultView, + target, + t.identifier, + t.screenX, + t.screenY, + t.screenX, + t.screenY + ); + } + + var findTouch = function (id) { + for (var i = 0; i < touches.all.length; ++i) { + if (touches.all[i].identifier == id) { + return touches.all[i]; + } + } + } + + var touches = { all: [], target: [], changed: [] }; + for (var i = 0; i < evt.touches.length; ++i) { + touches.all.push(cloneTouch(evt.touches[i])); + } + for (var i = 0; i < evt.targetTouches.length; ++i) { + touches.target.push( + findTouch(evt.targetTouches[i].identifier) || + cloneTouch(evt.targetTouches[i]) + ); + } + for (var i = 0; i < evt.changedTouches.length; ++i) { + touches.changed.push( + findTouch(evt.changedTouches[i].identifier) || + cloneTouch(evt.changedTouches[i]) + ); + } + + var mimicEvt = document.createEvent('TouchEvent'); + mimicEvt.initTouchEvent( + "contact"+newtype, + true, + true, + document.defaultView, + evt.detail, + evt.screenX, + evt.screenY, + evt.screenX, + evt.screenY, + evt.ctrlKey, + evt.altKey, + evt.shiftKey, + evt.metaKey, + document.createTouchList.apply(document, touches.all), + document.createTouchList.apply(document, touches.target), + document.createTouchList.apply(document, touches.changed), + evt.scale, + evt.rotation + ); + + return mimicEvt; + } + + + function fireTouchEvent(target, newtype, evt) { + var mimicEvt = mimicTouchEvent(target, newtype, evt); + var result = target.dispatchEvent(mimicEvt); + if (!result) { + evt.preventDefault(); + } + return result; + } + + + API.listen = listen; + API.listenOnIframe = listenOnIframe; + + return API; +} + + +Monocle.Events.listenOnIframe = function (frame) { + if (!Monocle.Browser.has.iframeTouchBug) { + return; + } + Monocle.Events.tMonitor = Monocle.Events.tMonitor || + new Monocle.Events.TouchMonitor(); + Monocle.Events.tMonitor.listenOnIframe(frame); +} + +Monocle.pieceLoaded('events'); +Monocle.Styles = { + applyRules: function (elem, rules) { + if (typeof rules != 'string') { + var parts = []; + for (var declaration in rules) { + parts.push(declaration+": "+rules[declaration]+";") + } + rules = parts.join(" "); + } + elem.style.cssText += ';'+rules; + return elem.style.cssText; + }, + + affix: function (elem, property, value) { + var target = elem.style ? elem.style : elem; + target[Monocle.Browser.CSSProps.toDOMProp(property)] = value; + }, + + setX: function (elem, x) { + var s = elem.style; + if (typeof x == "number") { x += "px"; } + if (Monocle.Browser.has.transform3d) { + s.webkitTransform = "translate3d("+x+", 0, 0)"; + } else { + s.webkitTransform = "translateX("+x+")"; + } + s.MozTransform = s.OTransform = s.transform = "translateX("+x+")"; + return x; + }, + + setY: function (elem, y) { + var s = elem.style; + if (typeof y == "number") { y += "px"; } + if (Monocle.Browser.has.transform3d) { + s.webkitTransform = "translate3d(0, "+y+", 0)"; + } else { + s.webkitTransform = "translateY("+y+")"; + } + s.MozTransform = s.OTransform = s.transform = "translateY("+y+")"; + return y; + } +} + + +Monocle.Styles.container = { + "position": "absolute", + "top": "0", + "left": "0", + "bottom": "0", + "right": "0" +} + +Monocle.Styles.page = { + "position": "absolute", + "z-index": "1", + "-webkit-user-select": "none", + "-moz-user-select": "none", + "user-select": "none", + "-webkit-transform": "translate3d(0,0,0)" + + /* + "background": "white", + "top": "0", + "left": "0", + "bottom": "0", + "right": "0" + */ +} + +Monocle.Styles.sheaf = { + "position": "absolute", + "overflow": "hidden" // Required by MobileSafari to constrain inner iFrame. + + /* + "top": "0", + "left": "0", + "bottom": "0", + "right": "0" + */ +} + +Monocle.Styles.component = { + "display": "block", + "width": "100%", + "height": "100%", + "border": "none", + "overflow": "hidden", + "-webkit-user-select": "none", + "-moz-user-select": "none", + "user-select": "none" +} + +Monocle.Styles.control = { + "z-index": "100", + "cursor": "pointer" +} + +Monocle.Styles.overlay = { + "position": "absolute", + "display": "none", + "width": "100%", + "height": "100%", + "z-index": "1000" +} + + + +Monocle.pieceLoaded('styles'); +Monocle.Reader = function (node, bookData, options, onLoadCallback) { + if (Monocle == this) { + return new Monocle.Reader(node, bookData, options, onLoadCallback); + } + + var API = { constructor: Monocle.Reader } + var k = API.constants = API.constructor; + var p = API.properties = { + initialized: false, + + book: null, + + graph: {}, + + pageStylesheets: [], + + systemId: (options ? options.systemId : null) || k.DEFAULT_SYSTEM_ID, + + classPrefix: k.DEFAULT_CLASS_PREFIX, + + controls: [], + + resizeTimer: null + } + + var dom; + + + function initialize(node, bookData, options, onLoadCallback) { + var box = typeof(node) == "string" ? document.getElementById(node) : node; + dom = API.dom = box.dom = new Monocle.Factory(box, 'box', 0, API); + + options = options || {} + + dispatchEvent("monocle:initializing"); + + var bk; + if (bookData) { + bk = new Monocle.Book(bookData); + } else { + bk = Monocle.Book.fromNodes([box.cloneNode(true)]); + } + box.innerHTML = ""; + + positionBox(); + + attachFlipper(options.flipper); + + createReaderElements(); + + p.defaultStyles = addPageStyles(k.DEFAULT_STYLE_RULES, false); + + primeFrames(options.primeURL, function () { + applyStyles(); + + listen('monocle:componentchange', persistPageStylesOnComponentChange); + + p.flipper.listenForInteraction(options.panels); + + setBook(bk, options.place, function () { + p.initialized = true; + if (onLoadCallback) { onLoadCallback(API); } + dispatchEvent("monocle:loaded"); + }); + }); + } + + + function positionBox() { + var currPosVal; + var box = dom.find('box'); + if (document.defaultView) { + var currStyle = document.defaultView.getComputedStyle(box, null); + currPosVal = currStyle.getPropertyValue('position'); + } else if (box.currentStyle) { + currPosVal = box.currentStyle.position + } + if (["absolute", "relative"].indexOf(currPosVal) == -1) { + box.style.position = "relative"; + } + } + + + function attachFlipper(flipperClass) { + if (!Monocle.Browser.has.columns) { + flipperClass = Monocle.Flippers[k.FLIPPER_LEGACY_CLASS]; + if (!flipperClass) { + return dom.append( + 'div', + 'abortMsg', + { 'class': k.abortMessage.CLASSNAME, 'html': k.abortMessage.TEXT } + ); + } + } else if (!flipperClass) { + flipperClass = Monocle.Flippers[k.FLIPPER_DEFAULT_CLASS]; + if (!flipperClass) { + throw("No flipper class"); + } + } + p.flipper = new flipperClass(API, null, p.readerOptions); + } + + + function createReaderElements() { + var cntr = dom.append('div', 'container'); + for (var i = 0; i < p.flipper.pageCount; ++i) { + var page = cntr.dom.append('div', 'page', i); + page.m = { reader: API, pageIndex: i, place: null } + page.m.sheafDiv = page.dom.append('div', 'sheaf', i); + page.m.activeFrame = page.m.sheafDiv.dom.append('iframe', 'component', i); + page.m.activeFrame.m = { 'pageDiv': page } + p.flipper.addPage(page); + Monocle.Events.listenOnIframe(page.m.activeFrame); + } + dom.append('div', 'overlay'); + dispatchEvent("monocle:loading"); + } + + + function primeFrames(url, callback) { + url = url || "about:blank"; + + var pageMax = p.flipper.pageCount; + var pageCount = 0; + + var cb = function (evt) { + var frame = evt.target || evt.srcElement; + Monocle.Events.deafen(frame, 'load', cb); + if (Monocle.Browser.is.WebKit) { + frame.contentDocument.documentElement.style.overflow = "hidden"; + } + if ((pageCount += 1) == pageMax) { + Monocle.defer(callback); + } + } + + for (var i = 0; i < pageMax; ++i) { + var page = dom.find('page', i); + page.m.activeFrame.style.visibility = "hidden"; + page.m.activeFrame.setAttribute('frameBorder', 0); + page.m.activeFrame.setAttribute('scrolling', 'no'); + Monocle.Events.listen(page.m.activeFrame, 'load', cb); + page.m.activeFrame.src = url; + } + } + + + function applyStyles() { + dom.find('container').dom.setStyles(Monocle.Styles.container); + for (var i = 0; i < p.flipper.pageCount; ++i) { + var page = dom.find('page', i); + page.dom.setStyles(Monocle.Styles.page); + dom.find('sheaf', i).dom.setStyles(Monocle.Styles.sheaf); + var cmpt = dom.find('component', i) + cmpt.dom.setStyles(Monocle.Styles.component); + Monocle.Styles.applyRules(cmpt.contentDocument.body, Monocle.Styles.body); + } + dom.find('overlay').dom.setStyles(Monocle.Styles.overlay); + dispatchEvent('monocle:styles'); + } + + + function setBook(bk, place, callback) { + p.book = bk; + var pageCount = 0; + if (typeof callback == 'function') { + var watcher = function (evt) { + if ((pageCount += 1) == p.flipper.pageCount) { + deafen('monocle:componentchange', watcher); + callback(); + } + } + listen('monocle:componentchange', watcher); + } + p.flipper.moveTo(place || { page: 1 }); + } + + + function getBook() { + return p.book; + } + + + function resized() { + if (!p.initialized) { + console.warn('Attempt to resize book before initialization.'); + } + if (!dispatchEvent("monocle:resizing", {}, true)) { + return; + } + clearTimeout(p.resizeTimer); + p.resizeTimer = setTimeout( + function () { + p.flipper.moveTo({ page: pageNumber() }); + dispatchEvent("monocle:resize"); + }, + k.durations.RESIZE_DELAY + ); + } + + + function pageNumber(pageDiv) { + var place = getPlace(pageDiv); + return place ? (place.pageNumber() || 1) : 1; + } + + + function getPlace(pageDiv) { + if (!p.initialized) { + console.warn('Attempt to access place before initialization.'); + } + return p.flipper.getPlace(pageDiv); + } + + + function moveTo(locus, callback) { + if (!p.initialized) { + console.warn('Attempt to move place before initialization.'); + } + var fn = callback; + if (!locus.direction) { + dispatchEvent('monocle:jumping', { locus: locus }); + fn = function () { + dispatchEvent('monocle:jump', { locus: locus }); + if (callback) { callback(); } + } + } + p.flipper.moveTo(locus, fn); + } + + + function skipToChapter(src) { + var locus = p.book.locusOfChapter(src); + if (locus) { + moveTo(locus); + return true; + } else { + dispatchEvent("monocle:notfound", { href: src }); + return false; + } + } + + + function addControl(ctrl, cType, options) { + for (var i = 0; i < p.controls.length; ++i) { + if (p.controls[i].control == ctrl) { + console.warn("Already added control: " + ctrl); + return; + } + } + + options = options || {}; + + var ctrlData = { + control: ctrl, + elements: [], + controlType: cType + } + p.controls.push(ctrlData); + + var ctrlElem; + var cntr = dom.find('container'), overlay = dom.find('overlay'); + if (!cType || cType == "standard") { + ctrlElem = ctrl.createControlElements(cntr); + cntr.appendChild(ctrlElem); + ctrlData.elements.push(ctrlElem); + } else if (cType == "page") { + for (var i = 0; i < p.flipper.pageCount; ++i) { + var page = dom.find('page', i); + var runner = ctrl.createControlElements(page); + page.appendChild(runner); + ctrlData.elements.push(runner); + } + } else if (cType == "modal" || cType == "popover") { + ctrlElem = ctrl.createControlElements(overlay); + overlay.appendChild(ctrlElem); + ctrlData.elements.push(ctrlElem); + ctrlData.usesOverlay = true; + } else if (cType == "invisible") { + if ( + typeof(ctrl.createControlElements) == "function" && + (ctrlElem = ctrl.createControlElements(cntr)) + ) { + cntr.appendChild(ctrlElem); + ctrlData.elements.push(ctrlElem); + } + } else { + console.warn("Unknown control type: " + cType); + } + + for (var i = 0; i < ctrlData.elements.length; ++i) { + Monocle.Styles.applyRules(ctrlData.elements[i], Monocle.Styles.control); + } + + if (options.hidden) { + hideControl(ctrl); + } else { + showControl(ctrl); + } + + if (typeof ctrl.assignToReader == 'function') { + ctrl.assignToReader(API); + } + + return ctrl; + } + + + function dataForControl(ctrl) { + for (var i = 0; i < p.controls.length; ++i) { + if (p.controls[i].control == ctrl) { + return p.controls[i]; + } + } + } + + + function hideControl(ctrl) { + var controlData = dataForControl(ctrl); + if (!controlData) { + console.warn("No data for control: " + ctrl); + return; + } + if (controlData.hidden) { + return; + } + for (var i = 0; i < controlData.elements.length; ++i) { + controlData.elements[i].style.display = "none"; + } + if (controlData.usesOverlay) { + var overlay = dom.find('overlay'); + overlay.style.display = "none"; + Monocle.Events.deafenForContact(overlay, overlay.listeners); + } + controlData.hidden = true; + if (ctrl.properties) { + ctrl.properties.hidden = true; + } + dispatchEvent('controlhide', ctrl, false); + } + + + function showControl(ctrl) { + var controlData = dataForControl(ctrl); + if (!controlData) { + console.warn("No data for control: " + ctrl); + return; + } + if (controlData.hidden == false) { + return; + } + for (var i = 0; i < controlData.elements.length; ++i) { + controlData.elements[i].style.display = "block"; + } + var overlay = dom.find('overlay'); + if (controlData.usesOverlay) { + overlay.style.display = "block"; + } + if (controlData.controlType == "popover") { + overlay.listeners = Monocle.Events.listenForContact( + overlay, + { + start: function (evt) { + obj = evt.target || window.event.srcElement; + do { + if (obj == controlData.elements[0]) { return true; } + } while (obj && (obj = obj.parentNode)); + hideControl(ctrl); + }, + move: function (evt) { + evt.preventDefault(); + } + } + ); + } + controlData.hidden = false; + if (ctrl.properties) { + ctrl.properties.hidden = false; + } + dispatchEvent('controlshow', ctrl, false); + } + + + function dispatchEvent(evtType, data, cancelable) { + if (!document.createEvent) { + return true; + } + var evt = document.createEvent("Events"); + evt.initEvent(evtType, false, cancelable || false); + evt.m = data; + try { + return dom.find('box').dispatchEvent(evt); + } catch(e) { + console.warn("Failed to dispatch event: " + evtType); + return false; + } + } + + + function listen(evtType, fn, useCapture) { + Monocle.Events.listen(dom.find('box'), evtType, fn, useCapture); + } + + + function deafen(evtType, fn) { + Monocle.Events.deafen(dom.find('box'), evtType, fn); + } + + + /* PAGE STYLESHEETS */ + + function addPageStyles(styleRules, restorePlace) { + return changingStylesheet(function () { + p.pageStylesheets.push(styleRules); + var sheetIndex = p.pageStylesheets.length - 1; + + for (var i = 0; i < p.flipper.pageCount; ++i) { + var doc = dom.find('component', i).contentDocument; + addPageStylesheet(doc, sheetIndex); + } + return sheetIndex; + }, restorePlace); + } + + + function updatePageStyles(sheetIndex, styleRules, restorePlace) { + return changingStylesheet(function () { + p.pageStylesheets[sheetIndex] = styleRules; + if (typeof styleRules.join == "function") { + styleRules = styleRules.join("\n"); + } + for (var i = 0; i < p.flipper.pageCount; ++i) { + var doc = dom.find('component', i).contentDocument; + var styleTag = doc.getElementById('monStylesheet'+sheetIndex); + if (!styleTag) { + console.warn('No such stylesheet: ' + sheetIndex); + return; + } + if (styleTag.styleSheet) { + styleTag.styleSheet.cssText = styleRules; + } else { + styleTag.replaceChild( + doc.createTextNode(styleRules), + styleTag.firstChild + ); + } + } + }, restorePlace); + } + + + function removePageStyles(sheetIndex, restorePlace) { + return changingStylesheet(function () { + p.pageStylesheets[sheetIndex] = null; + for (var i = 0; i < p.flipper.pageCount; ++i) { + var doc = dom.find('component', i).contentDocument; + var styleTag = doc.getElementById('monStylesheet'+sheetIndex); + styleTag.parentNode.removeChild(styleTag); + } + }, restorePlace); + } + + + function persistPageStylesOnComponentChange(evt) { + var doc = evt.m['document']; + doc.documentElement.id = p.systemId; + for (var i = 0; i < p.pageStylesheets.length; ++i) { + if (p.pageStylesheets[i]) { + addPageStylesheet(doc, i); + } + } + } + + + function changingStylesheet(callback, restorePlace) { + restorePlace = (restorePlace === false) ? false : true; + if (restorePlace) { + dispatchEvent("monocle:stylesheetchanging", {}); + } + var result = callback(); + if (restorePlace) { + p.flipper.moveTo({ page: pageNumber() }); + Monocle.defer( + function () { dispatchEvent("monocle:stylesheetchange", {}); } + ); + } + return result; + } + + + function addPageStylesheet(doc, sheetIndex) { + var styleRules = p.pageStylesheets[sheetIndex]; + + if (!styleRules) { + return; + } + + var head = doc.getElementsByTagName('head')[0]; + if (!head) { + if (!doc.documentElement) { return; } // FIXME: IE doesn't like docElem. + head = doc.createElement('head'); + doc.documentElement.appendChild(head); + } + + if (typeof styleRules.join == "function") { + styleRules = styleRules.join("\n"); + } + + var styleTag = doc.createElement('style'); + styleTag.type = 'text/css'; + styleTag.id = "monStylesheet"+sheetIndex; + if (styleTag.styleSheet) { + styleTag.styleSheet.cssText = styleRules; + } else { + styleTag.appendChild(doc.createTextNode(styleRules)); + } + + head.appendChild(styleTag); + + return styleTag; + } + + + function visiblePages() { + return p.flipper.visiblePages ? p.flipper.visiblePages() : [dom.find('page')]; + } + + + API.getBook = getBook; + API.getPlace = getPlace; + API.moveTo = moveTo; + API.skipToChapter = skipToChapter; + API.resized = resized; + API.addControl = addControl; + API.hideControl = hideControl; + API.showControl = showControl; + API.dispatchEvent = dispatchEvent; + API.listen = listen; + API.deafen = deafen; + API.addPageStyles = addPageStyles; + API.updatePageStyles = updatePageStyles; + API.removePageStyles = removePageStyles; + API.visiblePages = visiblePages; + + initialize(node, bookData, options, onLoadCallback); + + return API; +} + +Monocle.Reader.durations = { + RESIZE_DELAY: 100 +} +Monocle.Reader.abortMessage = { + CLASSNAME: "monocleAbortMessage", + TEXT: "Your browser does not support this technology." +} +Monocle.Reader.DEFAULT_SYSTEM_ID = 'RS:monocle' +Monocle.Reader.DEFAULT_CLASS_PREFIX = 'monelem_' +Monocle.Reader.FLIPPER_DEFAULT_CLASS = "Slider"; +Monocle.Reader.FLIPPER_LEGACY_CLASS = "Legacy"; +Monocle.Reader.DEFAULT_STYLE_RULES = [ + "html * {" + + "text-rendering: auto !important;" + + "word-wrap: break-word !important;" + + (Monocle.Browser.has.floatColumnBug ? "float: none !important;" : "") + + "}" + + "body {" + + "margin: 0 !important;" + + "padding: 0 !important;" + + "-webkit-text-size-adjust: none;" + + "}" + + "table, img {" + + "max-width: 100% !important;" + + "max-height: 90% !important;" + + "}" +] + + +Monocle.pieceLoaded('reader'); +/* BOOK */ + +/* The Book handles movement through the content by the reader page elements. + * + * It's responsible for instantiating components as they are required, + * and for calculating which component and page number to move to (based on + * requests from the Reader). + * + * It should set and know the place of each page element too. + * + */ +Monocle.Book = function (dataSource) { + if (Monocle == this) { return new Monocle.Book(dataSource); } + + var API = { constructor: Monocle.Book } + var k = API.constants = API.constructor; + var p = API.properties = { + dataSource: dataSource, + components: [], + chapters: {} // flat arrays of chapters per component + } + + + function initialize() { + p.componentIds = dataSource.getComponents(); + p.contents = dataSource.getContents(); + p.lastCIndex = p.componentIds.length - 1; + } + + + function pageNumberAt(pageDiv, locus) { + locus.load = false; + var currComponent = pageDiv.m.activeFrame ? + pageDiv.m.activeFrame.m.component : + null; + var component = null; + var cIndex = p.componentIds.indexOf(locus.componentId); + if (cIndex < 0 && !currComponent) { + locus.load = true; + locus.componentId = p.componentIds[0]; + return locus; + } else if (cIndex < 0) { + component = currComponent; + locus.componentId = pageDiv.m.activeFrame.m.component.properties.id; + cIndex = p.componentIds.indexOf(locus.componentId); + } else if (!p.components[cIndex] || p.components[cIndex] != currComponent) { + locus.load = true; + return locus; + } else { + component = currComponent; + } + + var result = { load: false, componentId: locus.componentId, page: 1 } + + var lastPageNum = { 'old': component.lastPageNumber() } + var changedDims = component.updateDimensions(pageDiv); + lastPageNum['new'] = component.lastPageNumber(); + + if (typeof(locus.page) == "number") { + result.page = locus.page; + } else if (typeof(locus.pagesBack) == "number") { + result.page = lastPageNum['new'] + locus.pagesBack; + } else if (typeof(locus.percent) == "number") { + var place = new Monocle.Place(); + place.setPlace(component, 1); + result.page = place.pageAtPercentageThrough(locus.percent); + } else if (typeof(locus.direction) == "number") { + if (!pageDiv.m.place) { + console.warn("Can't move in a direction if pageDiv has no place."); + } + result.page = pageDiv.m.place.pageNumber(); + result.page += locus.direction; + } else if (typeof(locus.anchor) == "string") { + result.page = component.pageForChapter(locus.anchor, pageDiv); + } else if (typeof(locus.position) == "string") { + if (locus.position == "start") { + result.page = 1; + } else if (locus.position == "end") { + result.page = lastPageNum['new']; + } + } else { + console.warn("Unrecognised locus: " + locus); + } + + if (changedDims && lastPageNum['old']) { + result.page = Math.round( + lastPageNum['new'] * (result.page / lastPageNum['old']) + ); + } + + if (result.page < 1) { + if (cIndex == 0) { + result.page = 1; + } else { + result.load = true; + result.componentId = p.componentIds[cIndex - 1]; + result.pagesBack = result.page; + result.page = null; + } + } else if (result.page > lastPageNum['new']) { + if (cIndex == p.lastCIndex) { + result.page = lastPageNum['new']; + } else { + result.load = true; + result.componentId = p.componentIds[cIndex + 1]; + result.page -= lastPageNum['new']; + } + } + + return result; + } + + + function setPageAt(pageDiv, locus) { + locus = pageNumberAt(pageDiv, locus); + if (!locus.load) { + var component = p.components[p.componentIds.indexOf(locus.componentId)]; + pageDiv.m.place = pageDiv.m.place || new Monocle.Place(); + pageDiv.m.place.setPlace(component, locus.page); + + var evtData = { + page: pageDiv, + locus: locus, + pageNumber: pageDiv.m.place.pageNumber(), + componentId: locus.componentId + } + pageDiv.m.reader.dispatchEvent("monocle:pagechange", evtData); + } + return locus; + } + + + function loadPageAt(pageDiv, locus, callback, progressCallback) { + var cIndex = p.componentIds.indexOf(locus.componentId); + if (!locus.load || cIndex < 0) { + locus = pageNumberAt(pageDiv, locus); + } + + if (!locus.load) { + callback(locus); + return; + } + + var findPageNumber = function () { + locus = setPageAt(pageDiv, locus); + if (locus.load) { + loadPageAt(pageDiv, locus, callback, progressCallback) + } else { + callback(locus); + } + } + + var pgFindPageNumber = function () { + progressCallback ? progressCallback(findPageNumber) : findPageNumber(); + } + + var applyComponent = function (component) { + component.applyTo(pageDiv, pgFindPageNumber); + } + + var pgApplyComponent = function (component) { + progressCallback ? + progressCallback(function () { applyComponent(component) }) : + applyComponent(component); + } + + loadComponent(cIndex, pgApplyComponent, pageDiv); + } + + + function setOrLoadPageAt(pageDiv, locus, callback, progressCallback) { + locus = setPageAt(pageDiv, locus); + if (locus.load) { + loadPageAt(pageDiv, locus, callback, progressCallback); + } else { + callback(locus); + } + } + + + function loadComponent(index, callback, pageDiv) { + if (p.components[index]) { + return callback(p.components[index]); + } + var cmptId = p.componentIds[index]; + if (pageDiv) { + var evtData = { 'page': pageDiv, 'component': cmptId, 'index': index }; + pageDiv.m.reader.dispatchEvent('monocle:componentloading', evtData); + } + var fn = function (cmptSource) { + if (pageDiv) { + evtData['source'] = cmptSource; + pageDiv.m.reader.dispatchEvent('monocle:componentloaded', evtData); + html = evtData['html']; + } + p.components[index] = new Monocle.Component( + API, + cmptId, + index, + chaptersForComponent(cmptId), + cmptSource + ); + callback(p.components[index]); + } + var cmptSource = p.dataSource.getComponent(cmptId, fn); + if (cmptSource && !p.components[index]) { + fn(cmptSource); + } + } + + + function chaptersForComponent(cmptId) { + if (p.chapters[cmptId]) { + return p.chapters[cmptId]; + } + p.chapters[cmptId] = []; + var matcher = new RegExp('^'+cmptId+"(\#(.+)|$)"); + var matches; + var recurser = function (chp) { + if (matches = chp.src.match(matcher)) { + p.chapters[cmptId].push({ + title: chp.title, + fragment: matches[2] || null + }); + } + if (chp.children) { + for (var i = 0; i < chp.children.length; ++i) { + recurser(chp.children[i]); + } + } + } + + for (var i = 0; i < p.contents.length; ++i) { + recurser(p.contents[i]); + } + return p.chapters[cmptId]; + } + + + function locusOfChapter(src) { + var matcher = new RegExp('^(.+?)(#(.*))?$'); + var matches = src.match(matcher); + if (!matches) { return null; } + var cmptId = componentIdMatching(matches[1]); + if (!cmptId) { return null; } + var locus = { componentId: cmptId } + matches[3] ? locus.anchor = matches[3] : locus.position = "start"; + return locus; + } + + + function componentIdMatching(str) { + for (var i = 0; i < p.componentIds.length; ++i) { + if (str.indexOf(p.componentIds[i]) > -1) { + return p.componentIds[i]; + } + } + return null; + } + + + API.getMetaData = dataSource.getMetaData; + API.pageNumberAt = pageNumberAt; + API.setPageAt = setPageAt; + API.loadPageAt = loadPageAt; + API.setOrLoadPageAt = setOrLoadPageAt; + API.chaptersForComponent = chaptersForComponent; + API.locusOfChapter = locusOfChapter; + + initialize(); + + return API; +} + + +Monocle.Book.fromNodes = function (nodes) { + var bookData = { + getComponents: function () { + return ['anonymous']; + }, + getContents: function () { + return []; + }, + getComponent: function (n) { + return { 'nodes': nodes }; + }, + getMetaData: function (key) { + } + } + + return new Monocle.Book(bookData); +} + +Monocle.pieceLoaded('book'); + +Monocle.Place = function () { + + var API = { constructor: Monocle.Place } + var k = API.constants = API.constructor; + var p = API.properties = { + component: null, + percent: null + } + + + function setPlace(cmpt, pageN) { + p.component = cmpt; + p.percent = pageN / cmpt.lastPageNumber(); + p.chapter = null; + } + + + function setPercentageThrough(cmpt, percent) { + p.component = cmpt; + p.percent = percent; + p.chapter = null; + } + + + function componentId() { + return p.component.properties.id; + } + + + function percentageThrough() { + return p.percent; + } + + + function pageAtPercentageThrough(pc) { + return Math.max(Math.round(p.component.lastPageNumber() * pc), 1); + } + + + function pageNumber() { + return pageAtPercentageThrough(p.percent); + } + + + function chapterInfo() { + if (p.chapter) { + return p.chapter; + } + return p.chapter = p.component.chapterForPage(pageNumber()); + } + + + function chapterTitle() { + var chp = chapterInfo(); + return chp ? chp.title : null; + } + + + function chapterSrc() { + var src = componentId(); + var cinfo = chapterInfo(); + if (cinfo && cinfo.fragment) { + src += "#" + cinfo.fragment; + } + return src; + } + + + function getLocus(options) { + options = options || {}; + var locus = { + page: pageNumber(), + componentId: componentId() + } + if (options.direction) { + locus.page += options.direction; + } + return locus; + } + + + function percentageOfBook() { + componentIds = p.component.properties.book.properties.componentIds; + componentSize = 1.0 / componentIds.length; + var pc = componentIds.indexOf(componentId()) * componentSize; + pc += componentSize * p.percent; + return pc; + } + + + function onFirstPageOfBook() { + return p.component.properties.index == 0 && pageNumber() == 1; + } + + + function onLastPageOfBook() { + return ( + p.component.properties.index == + p.component.properties.book.properties.lastCIndex && + pageNumber() == p.component.lastPageNumber() + ); + } + + + API.setPlace = setPlace; + API.setPercentageThrough = setPercentageThrough; + API.componentId = componentId; + API.percentageThrough = percentageThrough; + API.pageAtPercentageThrough = pageAtPercentageThrough; + API.pageNumber = pageNumber; + API.chapterInfo = chapterInfo; + API.chapterTitle = chapterTitle; + API.chapterSrc = chapterSrc; + API.getLocus = getLocus; + API.percentageOfBook = percentageOfBook; + API.onFirstPageOfBook = onFirstPageOfBook; + API.onLastPageOfBook = onLastPageOfBook; + + return API; +} + + +Monocle.Place.FromPageNumber = function (component, pageNumber) { + var place = new Monocle.Place(); + place.setPlace(component, pageNumber); + return place; +} + +Monocle.Place.FromPercentageThrough = function (component, percent) { + var place = new Monocle.Place(); + place.setPercentageThrough(component, percent); + return place; +} + +Monocle.pieceLoaded('place'); +/* COMPONENT */ + +Monocle.Component = function (book, id, index, chapters, source) { + + var API = { constructor: Monocle.Component } + var k = API.constants = API.constructor; + var p = API.properties = { + book: book, + + id: id, + + index: index, + + chapters: chapters, + + source: source + } + + + function applyTo(pageDiv, callback) { + var evtData = { 'page': pageDiv, 'source': p.source }; + pageDiv.m.reader.dispatchEvent('monocle:componentchanging', evtData); + + return loadFrame( + pageDiv, + function () { + setupFrame(pageDiv, pageDiv.m.activeFrame); + callback(pageDiv, API); + } + ); + } + + + function loadFrame(pageDiv, callback) { + var frame = pageDiv.m.activeFrame; + + frame.m.component = API; + + frame.style.visibility = "hidden"; + + + if (p.source.html || (typeof p.source == "string")) { // HTML + return loadFrameFromHTML(p.source.html || p.source, frame, callback); + } else if (p.source.url) { // URL + return loadFrameFromURL(p.source.url, frame, callback); + } else if (p.source.nodes) { // NODES + return loadFrameFromNodes(p.source.nodes, frame, callback); + } else if (p.source.doc) { // DOCUMENT + return loadFrameFromDocument(p.source.doc, frame, callback); + } + } + + + function loadFrameFromHTML(src, frame, callback) { + src = src.replace(/\s+/g, ' '); + + src = src.replace(/\'/g, '\\\''); + + + if (Monocle.Browser.is.Gecko) { + var doctypeFragment = "]*>"; + src = src.replace(new RegExp(doctypeFragment, 'm'), ''); + } + + src = "javascript: '" + src + "';"; + + frame.onload = function () { + frame.onload = null; + Monocle.defer(callback); + } + frame.src = src; + } + + + function loadFrameFromURL(url, frame, callback) { + frame.onload = function () { + frame.onload = null; + Monocle.defer(callback); + } + frame.contentWindow.location.replace(url); + } + + + function loadFrameFromNodes(nodes, frame, callback) { + var destDoc = frame.contentDocument; + destDoc.documentElement.innerHTML = ""; + var destHd = destDoc.createElement("head"); + var destBdy = destDoc.createElement("body"); + + for (var i = 0; i < nodes.length; ++i) { + var node = destDoc.importNode(nodes[i], true); + destBdy.appendChild(node); + } + + var oldHead = destDoc.getElementsByTagName('head')[0]; + if (oldHead) { + destDoc.documentElement.replaceChild(destHd, oldHead); + } else { + destDoc.documentElement.appendChild(destHd); + } + if (destDoc.body) { + destDoc.documentElement.replaceChild(destBdy, destDoc.body); + } else { + destDoc.documentElement.appendChild(destBdy); + } + + if (callback) { callback(); } + } + + + function loadFrameFromDocument(srcDoc, frame, callback) { + var destDoc = frame.contentDocument; + + var srcBases = srcDoc.getElementsByTagName('base'); + if (srcBases[0]) { + var head = destDoc.getElementsByTagName('head')[0]; + if (!head) { + try { + head = destDoc.createElement('head'); + if (destDoc.body) { + destDoc.insertBefore(head, destDoc.body); + } else { + destDoc.appendChild(head); + } + } catch (e) { + head = destDoc.body; + } + } + var bases = destDoc.getElementsByTagName('base'); + var base = bases[0] ? bases[0] : destDoc.createElement('base'); + base.setAttribute('href', srcBases[0].getAttribute('href')); + head.appendChild(base); + } + + destDoc.replaceChild( + destDoc.importNode(srcDoc.documentElement, true), + destDoc.documentElement + ); + + + Monocle.defer(callback); + } + + + function setupFrame(pageDiv, frame) { + Monocle.Events.listenOnIframe(frame); + + var evtData = { + 'page': pageDiv, + 'document': frame.contentDocument, + 'component': API + }; + pageDiv.m.reader.dispatchEvent('monocle:componentchange', evtData); + + var doc = frame.contentDocument; + var win = doc.defaultView; + var currStyle = win.getComputedStyle(doc.body, null); + var lh = parseFloat(currStyle.getPropertyValue('line-height')); + var fs = parseFloat(currStyle.getPropertyValue('font-size')); + doc.body.style.lineHeight = lh / fs; + + p.pageLength = pageDiv.m.dimensions.measure(); + frame.style.visibility = "visible"; + + locateChapters(pageDiv); + } + + + function updateDimensions(pageDiv) { + if (pageDiv.m.dimensions.hasChanged()) { + p.pageLength = pageDiv.m.dimensions.measure(); + return true; + } else { + return false; + } + } + + + function locateChapters(pageDiv) { + if (p.chapters[0] && typeof p.chapters[0].percent == "number") { + return; + } + for (var i = 0; i < p.chapters.length; ++i) { + var chp = p.chapters[i]; + chp.percent = 0; + if (chp.fragment) { + chp.percent = pageDiv.m.dimensions.percentageThroughOfId(chp.fragment); + } + } + return p.chapters; + } + + + function chapterForPage(pageN) { + var cand = null; + var percent = (pageN - 1) / p.pageLength; + for (var i = 0; i < p.chapters.length; ++i) { + if (percent >= p.chapters[i].percent) { + cand = p.chapters[i]; + } else { + return cand; + } + } + return cand; + } + + + function pageForChapter(fragment, pageDiv) { + if (!fragment) { + return 1; + } + var pc2pn = function (pc) { return Math.floor(pc * p.pageLength) + 1 } + for (var i = 0; i < p.chapters.length; ++i) { + if (p.chapters[i].fragment == fragment) { + return pc2pn(p.chapters[i].percent); + } + } + var percent = pageDiv.m.dimensions.percentageThroughOfId(fragment); + return pc2pn(percent); + } + + + function lastPageNumber() { + return p.pageLength; + } + + + API.applyTo = applyTo; + API.updateDimensions = updateDimensions; + API.chapterForPage = chapterForPage; + API.pageForChapter = pageForChapter; + API.lastPageNumber = lastPageNumber; + + return API; +} + +Monocle.pieceLoaded('component'); + +Monocle.Dimensions = {} +Monocle.Controls = {}; +Monocle.Flippers = {}; +Monocle.Panels = {}; + +Monocle.Controls.Panel = function () { + + var API = { constructor: Monocle.Controls.Panel } + var k = API.constants = API.constructor; + var p = API.properties = { + evtCallbacks: {} + } + + function createControlElements(cntr) { + p.div = cntr.dom.make('div', k.CLS.panel); + p.div.dom.setStyles(k.DEFAULT_STYLES); + Monocle.Events.listenForContact( + p.div, + { + 'start': start, + 'move': move, + 'end': end, + 'cancel': cancel + }, + { useCapture: false } + ); + return p.div; + } + + + function listenTo(evtCallbacks) { + p.evtCallbacks = evtCallbacks; + } + + + function deafen() { + p.evtCallbacks = {} + } + + + function start(evt) { + p.contact = true; + evt.m.offsetX += p.div.offsetLeft; + evt.m.offsetY += p.div.offsetTop; + expand(); + invoke('start', evt); + } + + + function move(evt) { + if (!p.contact) { + return; + } + invoke('move', evt); + } + + + function end(evt) { + if (!p.contact) { + return; + } + Monocle.Events.deafenForContact(p.div, p.listeners); + contract(); + p.contact = false; + invoke('end', evt); + } + + + function cancel(evt) { + if (!p.contact) { + return; + } + Monocle.Events.deafenForContact(p.div, p.listeners); + contract(); + p.contact = false; + invoke('cancel', evt); + } + + + function invoke(evtType, evt) { + if (p.evtCallbacks[evtType]) { + p.evtCallbacks[evtType](API, evt.m.offsetX, evt.m.offsetY); + } + evt.preventDefault(); + } + + + function expand() { + if (p.expanded) { + return; + } + p.div.dom.addClass(k.CLS.expanded); + p.expanded = true; + } + + + function contract(evt) { + if (!p.expanded) { + return; + } + p.div.dom.removeClass(k.CLS.expanded); + p.expanded = false; + } + + + API.createControlElements = createControlElements; + API.listenTo = listenTo; + API.deafen = deafen; + API.expand = expand; + API.contract = contract; + + return API; +} + + +Monocle.Controls.Panel.CLS = { + panel: 'panel', + expanded: 'controls_panel_expanded' +} +Monocle.Controls.Panel.DEFAULT_STYLES = { + position: 'absolute', + height: '100%' +} + + +Monocle.pieceLoaded('controls/panel'); +Monocle.Panels.TwoPane = function (flipper, evtCallbacks) { + + var API = { constructor: Monocle.Panels.TwoPane } + var k = API.constants = API.constructor; + var p = API.properties = {} + + + function initialize() { + p.panels = { + forwards: new Monocle.Controls.Panel(), + backwards: new Monocle.Controls.Panel() + } + + for (dir in p.panels) { + flipper.properties.reader.addControl(p.panels[dir]); + p.panels[dir].listenTo(evtCallbacks); + p.panels[dir].properties.direction = flipper.constants[dir.toUpperCase()]; + var style = { "width": k.WIDTH }; + style[(dir == "forwards" ? "right" : "left")] = 0; + p.panels[dir].properties.div.dom.setStyles(style); + } + } + + + initialize(); + + return API; +} + +Monocle.Panels.TwoPane.WIDTH = "50%"; + +Monocle.pieceLoaded('panels/twopane'); +Monocle.Dimensions.Vert = function (pageDiv) { + + var API = { constructor: Monocle.Dimensions.Vert } + var k = API.constants = API.constructor; + var p = API.properties = { + page: pageDiv, + reader: pageDiv.m.reader + } + + + function initialize() { + p.reader.listen('monocle:componentchange', componentChanged); + } + + + function hasChanged() { + return getBodyHeight() != p.bodyHeight || getPageHeight != p.pageHeight; + } + + + function measure() { + p.bodyHeight = getBodyHeight(); + p.pageHeight = getPageHeight(); + p.length = Math.ceil(p.bodyHeight / p.pageHeight); + return p.length; + } + + + function pages() { + return p.length; + } + + + function getBodyHeight() { + return p.page.m.activeFrame.contentDocument.body.scrollHeight; + } + + + function getPageHeight() { + return p.page.m.activeFrame.offsetHeight - k.GUTTER; + } + + + function percentageThroughOfId(id) { + var doc = p.page.m.activeFrame.contentDocument; + var target = doc.getElementById(id); + var offset = 0; + if (target.getBoundingClientRect) { + offset = target.getBoundingClientRect().top; + offset -= doc.body.getBoundingClientRect().top; + } else { + var oldScrollTop = doc.body.scrollTop; + target.scrollIntoView(); + offset = doc.body.scrollTop; + doc.body.scrollLeft = 0; + doc.body.scrollTop = oldScrollTop; + } + + var percent = offset / p.bodyHeight; + return percent; + } + + + function componentChanged(evt) { + if (evt.m['page'] != p.page) { return; } + var sheaf = p.page.m.sheafDiv; + var cmpt = p.page.m.activeFrame; + sheaf.dom.setStyles(k.SHEAF_STYLES); + cmpt.dom.setStyles(k.COMPONENT_STYLES); + var doc = evt.m['document']; + doc.documentElement.style.overflow = 'hidden'; + doc.body.style.marginRight = '10px !important'; + cmpt.contentWindow.scrollTo(0,0); + } + + + function locusToOffset(locus) { + return p.pageHeight * (locus.page - 1); + } + + + API.hasChanged = hasChanged; + API.measure = measure; + API.pages = pages; + API.percentageThroughOfId = percentageThroughOfId; + API.locusToOffset = locusToOffset; + + initialize(); + + return API; +} + +Monocle.Dimensions.Vert.GUTTER = 10; +Monocle.Flippers.Legacy = function (reader) { + + var API = { constructor: Monocle.Flippers.Legacy } + var k = API.constants = API.constructor; + var p = API.properties = { + pageCount: 1, + divs: {} + } + + + function initialize() { + p.reader = reader; + } + + + function addPage(pageDiv) { + pageDiv.m.dimensions = new Monocle.Dimensions.Vert(pageDiv); + } + + + function getPlace() { + return page().m.place; + } + + + function moveTo(locus, callback) { + var fn = frameToLocus; + if (typeof callback == "function") { + fn = function () { frameToLocus(); callback(); } + } + p.reader.getBook().setOrLoadPageAt(page(), locus, fn); + } + + + function listenForInteraction(panelClass) { + if (typeof panelClass != "function") { + panelClass = k.DEFAULT_PANELS_CLASS; + if (!panelClass) { + console.warn("Invalid panel class.") + } + } + p.panels = new panelClass(API, { 'end': turn }); + } + + + function page() { + return p.reader.dom.find('page'); + } + + + function turn(panel) { + var dir = panel.properties.direction; + var place = getPlace(); + if ( + (dir < 0 && place.onFirstPageOfBook()) || + (dir > 0 && place.onLastPageOfBook()) + ) { return; } + moveTo({ page: getPlace().pageNumber() + dir }); + } + + + function frameToLocus(locus) { + var cmpt = p.reader.dom.find('component'); + var win = cmpt.contentWindow; + var srcY = scrollPos(win); + var dims = page().m.dimensions; + var pageHeight = dims.properties.pageHeight; + var destY = dims.locusToOffset(locus); + + if (Math.abs(destY - srcY) > pageHeight) { + return win.scrollTo(0, destY); + } + + showIndicator(win, srcY < destY ? srcY + pageHeight : srcY); + Monocle.defer( + function () { smoothScroll(win, srcY, destY, 300, scrollingFinished); }, + 150 + ); + } + + + function scrollPos(win) { + if (win.pageYOffset) { + return win.pageYOffset; + } + if (win.document.documentElement && win.document.documentElement.scrollTop) { + return win.document.documentElement.scrollTop; + } + if (win.document.body.scrollTop) { + return win.document.body.scrollTop; + } + return 0; + } + + + function smoothScroll(win, currY, finalY, duration, callback) { + clearTimeout(win.smoothScrollInterval); + var stamp = (new Date()).getTime(); + var frameRate = 40; + var step = (finalY - currY) * (frameRate / duration); + var stepFn = function () { + var destY = currY + step; + if ( + (new Date()).getTime() - stamp > duration || + Math.abs(currY - finalY) < Math.abs((currY + step) - finalY) + ) { + clearTimeout(win.smoothScrollInterval); + win.scrollTo(0, finalY); + if (callback) { callback(); } + } else { + win.scrollTo(0, destY); + currY = destY; + } + } + win.smoothScrollInterval = setInterval(stepFn, frameRate); + } + + + function scrollingFinished() { + hideIndicator(page().m.activeFrame.contentWindow); + p.reader.dispatchEvent('monocle:turn'); + } + + + function showIndicator(win, pos) { + if (p.hideTO) { clearTimeout(p.hideTO); } + + var doc = win.document; + if (!doc.body.indicator) { + doc.body.indicator = createIndicator(doc); + doc.body.appendChild(doc.body.indicator); + } + doc.body.indicator.line.style.display = "block"; + doc.body.indicator.style.opacity = 1; + positionIndicator(pos); + } + + + function hideIndicator(win) { + var doc = win.document; + p.hideTO = Monocle.defer( + function () { + if (!doc.body.indicator) { + doc.body.indicator = createIndicator(doc); + doc.body.appendChild(doc.body.indicator); + } + var dims = page().m.dimensions; + positionIndicator( + dims.locusToOffset(getPlace().getLocus()) + dims.properties.pageHeight + ) + doc.body.indicator.line.style.display = "none"; + doc.body.indicator.style.opacity = 0.5; + }, + 600 + ); + } + + + function createIndicator(doc) { + var iBox = doc.createElement('div'); + doc.body.appendChild(iBox); + Monocle.Styles.applyRules(iBox, k.STYLES.iBox); + + iBox.arrow = doc.createElement('div'); + iBox.appendChild(iBox.arrow); + Monocle.Styles.applyRules(iBox.arrow, k.STYLES.arrow); + + iBox.line = doc.createElement('div'); + iBox.appendChild(iBox.line); + Monocle.Styles.applyRules(iBox.line, k.STYLES.line); + + return iBox; + } + + + function positionIndicator(y) { + var p = page(); + var doc = p.m.activeFrame.contentDocument; + var maxHeight = p.m.dimensions.properties.bodyHeight; + maxHeight -= doc.body.indicator.offsetHeight; + if (y > maxHeight) { + y = maxHeight; + } + doc.body.indicator.style.top = y + "px"; + } + + + API.pageCount = p.pageCount; + API.addPage = addPage; + API.getPlace = getPlace; + API.moveTo = moveTo; + API.listenForInteraction = listenForInteraction; + + initialize(); + + return API; +} + +Monocle.Flippers.Legacy.FORWARDS = 1; +Monocle.Flippers.Legacy.BACKWARDS = -1; +Monocle.Flippers.Legacy.DEFAULT_PANELS_CLASS = Monocle.Panels.TwoPane; + +Monocle.Flippers.Legacy.STYLES = { + iBox: { + 'position': 'absolute', + 'right': 0, + 'left': 0, + 'height': '10px' + }, + arrow: { + 'position': 'absolute', + 'right': 0, + 'height': '10px', + 'width': '10px', + 'background': '#333', + 'border-radius': '6px' + }, + line: { + 'width': '100%', + 'border-top': '2px dotted #333', + 'margin-top': '5px' + } +} + +Monocle.pieceLoaded('flippers/legacy'); +Monocle.Dimensions.Columns = function (pageDiv) { + + var API = { constructor: Monocle.Dimensions.Columns } + var k = API.constants = API.constructor; + var p = API.properties = { + page: pageDiv, + reader: pageDiv.m.reader, + dirty: true + } + + + function initialize() { + p.reader.listen('monocle:componentchange', componentChanged); + } + + + function hasChanged() { + if (p.dirty) { return true; } + var newMeasurements = rawMeasurements(); + return ( + (!p.measurements) || + (p.measurements.width != newMeasurements.width) || + (p.measurements.height != newMeasurements.height) || + (p.measurements.scrollWidth != newMeasurements.scrollWidth) || + (p.measurements.fontSize != newMeasurements.fontSize) + ); + } + + + function measure() { + setColumnWidth(); + p.measurements = rawMeasurements(); + + if ( + Monocle.Browser.has.iframeDoubleWidthBug && + p.measurements.scrollWidth == p.measurements.width * 2 + ) { + var doc = p.page.m.activeFrame.contentDocument; + var lc; + for (var i = doc.body.childNodes.length - 1; i >= 0; --i) { + lc = doc.body.childNodes[i]; + if (lc.getBoundingClientRect) { break; } + } + if (!lc || !lc.getBoundingClientRect) { + console.warn('Empty document for page['+p.page.m.pageIndex+']'); + p.measurements.scrollWidth = p.measurements.width; + } else if (lc.getBoundingClientRect().bottom > p.measurements.height) { + p.measurements.scrollWidth = p.measurements.width * 2; + } else { + p.measurements.scrollWidth = p.measurements.width; + } + } + + p.length = Math.ceil(p.measurements.scrollWidth / p.measurements.width); + p.dirty = false; + return p.length; + } + + + function pages() { + if (p.dirty) { + console.warn('Accessing pages() when dimensions are dirty.') + return 0; + } + return p.length; + } + + + function percentageThroughOfId(id) { + var doc = p.page.m.activeFrame.contentDocument; + var target = doc.getElementById(id); + if (!target) { + return 0; + } + var offset = 0; + if (target.getBoundingClientRect) { + offset = target.getBoundingClientRect().left; + offset -= doc.body.getBoundingClientRect().left; + } else { + var scroller = scrollerElement(); + var oldScrollLeft = scroller.scrollLeft; + target.scrollIntoView(); + offset = scroller.scrollLeft; + scroller.scrollTop = 0; + scroller.scrollLeft = oldScrollLeft; + } + + var percent = offset / p.measurements.scrollWidth; + return percent; + } + + + function componentChanged(evt) { + if (evt.m['page'] != p.page) { return; } + var doc = evt.m['document']; + Monocle.Styles.applyRules(doc.body, k.BODY_STYLES); + + if (Monocle.Browser.is.WebKit) { + doc.documentElement.style.overflow = 'hidden'; + } + p.dirty = true; + } + + + function setColumnWidth() { + var cw = p.page.m.sheafDiv.clientWidth; + var doc = p.page.m.activeFrame.contentDocument; + if (currBodyStyleValue('column-width') != cw+"px") { + Monocle.Styles.affix(doc.body, 'column-width', cw+"px"); + p.dirty = true; + } + } + + + function rawMeasurements() { + var sheaf = p.page.m.sheafDiv; + return { + width: sheaf.clientWidth, + height: sheaf.clientHeight, + scrollWidth: scrollerWidth(), + fontSize: currBodyStyleValue('font-size') + } + } + + + function scrollerElement() { + if (Monocle.Browser.has.mustScrollSheaf) { + return p.page.m.sheafDiv; + } else { + return p.page.m.activeFrame.contentDocument.body; + } + } + + + function scrollerWidth() { + var bdy = p.page.m.activeFrame.contentDocument.body; + if (Monocle.Browser.has.iframeDoubleWidthBug) { + if (Monocle.Browser.iOSVersion < "4.1") { + var hbw = bdy.scrollWidth / 2; + var sew = scrollerElement().scrollWidth; + return Math.max(sew, hbw); + } else { + bdy.scrollWidth; // Throw one away. Nuts. + var hbw = bdy.scrollWidth / 2; + return hbw; + } + } else if (Monocle.Browser.is.Gecko) { + var lc = bdy.lastChild; + while (lc && lc.nodeType != 1) { + lc = lc.previousSibling; + } + if (lc && lc.getBoundingClientRect) { + return lc.getBoundingClientRect().right; + } + } + return scrollerElement().scrollWidth; + } + + + function currBodyStyleValue(property) { + var win = p.page.m.activeFrame.contentWindow; + var doc = win.document; + if (!doc.body) { return null; } + var currStyle = win.getComputedStyle(doc.body, null); + return currStyle.getPropertyValue(property); + } + + + function locusToOffset(locus) { + return 0 - (p.measurements.width * (locus.page - 1)); + } + + + function translateToLocus(locus) { + var offset = locusToOffset(locus); + var bdy = p.page.m.activeFrame.contentDocument.body; + Monocle.Styles.affix(bdy, "transform", "translateX("+offset+"px)"); + return offset; + } + + + API.hasChanged = hasChanged; + API.measure = measure; + API.pages = pages; + API.percentageThroughOfId = percentageThroughOfId; + + API.locusToOffset = locusToOffset; + API.translateToLocus = translateToLocus; + + initialize(); + + return API; +} + + +Monocle.Dimensions.Columns.BODY_STYLES = { + "position": "absolute", + "height": "100%", + "-webkit-column-gap": "0", + "-webkit-column-fill": "auto", + "-moz-column-gap": "0", + "-moz-column-fill": "auto", + "column-gap": "0", + "column-fill": "auto" +} + +if (Monocle.Browser.has.iframeDoubleWidthBug) { + Monocle.Dimensions.Columns.BODY_STYLES["min-width"] = "200%"; +} else { + Monocle.Dimensions.Columns.BODY_STYLES["width"] = "100%"; +} +Monocle.Flippers.Slider = function (reader) { + if (Monocle.Flippers == this) { + return new Monocle.Flippers.Slider(reader); + } + + var API = { constructor: Monocle.Flippers.Slider } + var k = API.constants = API.constructor; + var p = API.properties = { + pageCount: 2, + activeIndex: 1, + turnData: {} + } + + + function initialize() { + p.reader = reader; + } + + + function addPage(pageDiv) { + pageDiv.m.dimensions = new Monocle.Dimensions.Columns(pageDiv); + } + + + function visiblePages() { + return [upperPage()]; + } + + + function listenForInteraction(panelClass) { + interactiveMode(true); + interactiveMode(false); + + if (typeof panelClass != "function") { + panelClass = k.DEFAULT_PANELS_CLASS; + if (!panelClass) { + console.warn("Invalid panel class.") + } + } + var q = function (action, panel, x) { + var dir = panel.properties.direction; + if (action == "lift") { + lift(dir, x); + } else if (action == "release") { + release(dir, x); + } + } + p.panels = new panelClass( + API, + { + 'start': function (panel, x) { q('lift', panel, x); }, + 'move': function (panel, x) { turning(panel.properties.direction, x); }, + 'end': function (panel, x) { q('release', panel, x); }, + 'cancel': function (panel, x) { q('release', panel, x); } + } + ); + } + + + function interactiveMode(bState) { + if (!Monocle.Browser.has.selectThruBug) { + return; + } + if (p.interactive = bState) { + if (p.activeIndex != 0) { + var place = getPlace(); + if (place) { + setPage( + p.reader.dom.find('page', 0), + place.getLocus(), + function () { + flipPages(); + prepareNextPage(); + } + ); + } else { + flipPages(); + } + } + } + } + + + function getPlace(pageDiv) { + pageDiv = pageDiv || upperPage(); + return pageDiv.m ? pageDiv.m.place : null; + } + + + function moveTo(locus, callback) { + var fn = function () { + prepareNextPage(announceTurn); + if (typeof callback == "function") { + callback(); + } + } + setPage(upperPage(), locus, fn); + } + + + function setPage(pageDiv, locus, callback) { + p.reader.getBook().setOrLoadPageAt( + pageDiv, + locus, + function (locus) { + pageDiv.m.dimensions.translateToLocus(locus); + if (callback) { callback(); } + } + ); + } + + + function upperPage() { + return p.reader.dom.find('page', p.activeIndex); + } + + + function lowerPage() { + return p.reader.dom.find('page', (p.activeIndex + 1) % 2); + } + + + function flipPages() { + upperPage().style.zIndex = 1; + lowerPage().style.zIndex = 2; + return p.activeIndex = (p.activeIndex + 1) % 2; + } + + + function lift(dir, boxPointX) { + if (p.turnData.lifting || p.turnData.releasing) { return; } + + p.turnData.points = { + start: boxPointX, + min: boxPointX, + max: boxPointX + } + p.turnData.lifting = true; + + if (dir == k.FORWARDS) { + if (getPlace().onLastPageOfBook()) { + resetTurnData(); + return; + } + onGoingForward(boxPointX); + } else if (dir == k.BACKWARDS) { + if (getPlace().onFirstPageOfBook()) { + resetTurnData(); + return; + } + onGoingBackward(boxPointX); + } else { + console.warn("Invalid direction: " + dir); + } + } + + + function turning(dir, boxPointX) { + if (!p.turnData.points) { return; } + if (p.turnData.lifting || p.turnData.releasing) { return; } + checkPoint(boxPointX); + slideToCursor(boxPointX, null, "0"); + } + + + function release(dir, boxPointX) { + if (!p.turnData.points) { + return; + } + if (p.turnData.lifting) { + p.turnData.releaseArgs = [dir, boxPointX]; + return; + } + if (p.turnData.releasing) { + return; + } + + checkPoint(boxPointX); + + p.turnData.releasing = true; + + if (dir == k.FORWARDS) { + if ( + p.turnData.points.tap || + p.turnData.points.start - boxPointX > 60 || + p.turnData.points.min >= boxPointX + ) { + slideOut(afterGoingForward); + } else { + slideIn(afterCancellingForward); + } + } else if (dir == k.BACKWARDS) { + if ( + p.turnData.points.tap || + boxPointX - p.turnData.points.start > 60 || + p.turnData.points.max <= boxPointX + ) { + slideIn(afterGoingBackward); + } else { + slideOut(afterCancellingBackward); + } + } else { + console.warn("Invalid direction: " + dir); + } + } + + + function checkPoint(boxPointX) { + p.turnData.points.min = Math.min(p.turnData.points.min, boxPointX); + p.turnData.points.max = Math.max(p.turnData.points.max, boxPointX); + p.turnData.points.tap = p.turnData.points.max - p.turnData.points.min < 10; + } + + + function onGoingForward(x) { + lifted(x); + } + + + function onGoingBackward(x) { + var lp = lowerPage(); + jumpOut(lp, // move lower page off-screen + function () { + flipPages(); // flip lower to upper + setPage( // set upper page to previous + lp, + getPlace(lowerPage()).getLocus({ direction: k.BACKWARDS }), + function () { lifted(x); } + ); + } + ); + } + + + function afterGoingForward() { + var up = upperPage(); + if (p.interactive) { + setPage( // set upper (off screen) to current + up, + getPlace().getLocus({ direction: k.FORWARDS }), + function () { + jumpIn(up, function () { prepareNextPage(announceTurn); }); + } + ); + } else { + flipPages(); + jumpIn(up, function () { prepareNextPage(announceTurn); }); + } + } + + + function afterGoingBackward() { + if (p.interactive) { + setPage( // set lower page to current + lowerPage(), + getPlace().getLocus(), + function () { + flipPages(); // flip lower to upper + prepareNextPage(announceTurn); + } + ); + } else { + announceTurn(); + } + } + + + function afterCancellingForward() { + resetTurnData(); + } + + + function afterCancellingBackward() { + flipPages(); // flip upper to lower + jumpIn( // move lower back onto screen + lowerPage(), + function () { prepareNextPage(resetTurnData); } + ); + } + + + function prepareNextPage(callback) { + setPage( + lowerPage(), + getPlace().getLocus({ direction: k.FORWARDS }), + callback + ); + } + + + function lifted(x) { + p.turnData.lifting = false; + var releaseArgs = p.turnData.releaseArgs; + if (releaseArgs) { + p.turnData.releaseArgs = null; + release(releaseArgs[0], releaseArgs[1]); + } else if (x) { + slideToCursor(x); + } + } + + + function announceTurn() { + p.reader.dispatchEvent('monocle:turn'); + resetTurnData(); + } + + + function resetTurnData() { + p.turnData = {}; + } + + + function setX(elem, x, options, callback) { + var duration; + + if (!options.duration) { + duration = 0; + } else { + duration = parseInt(options['duration']); + } + + if (typeof(x) == "number") { x = x + "px"; } + + if (typeof WebKitTransitionEvent != "undefined") { + if (duration) { + transition = '-webkit-transform'; + transition += ' ' + duration + "ms"; + transition += ' ' + (options['timing'] || 'linear'); + transition += ' ' + (options['delay'] || 0) + 'ms'; + } else { + transition = 'none'; + } + elem.style.webkitTransition = transition; + if (Monocle.Browser.has.transform3d) { + elem.style.webkitTransform = "translate3d("+x+",0,0)"; + } else { + elem.style.webkitTransform = "translateX("+x+")"; + } + + } else if (duration > 0) { + clearTimeout(elem.setXTransitionInterval) + + var stamp = (new Date()).getTime(); + var frameRate = 40; + var finalX = parseInt(x); + var currX = getX(elem); + var step = (finalX - currX) * (frameRate / duration); + var stepFn = function () { + var destX = currX + step; + if ( + (new Date()).getTime() - stamp > duration || + Math.abs(currX - finalX) <= Math.abs((currX + step) - finalX) + ) { + clearTimeout(elem.setXTransitionInterval) + Monocle.Styles.setX(elem, finalX); + if (elem.setXTCB) { + elem.setXTCB(); + } + } else { + Monocle.Styles.setX(elem, destX); + currX = destX; + } + } + + elem.setXTransitionInterval = setInterval(stepFn, frameRate); + } else { + Monocle.Styles.setX(elem, x); + } + + if (elem.setXTCB) { + Monocle.Events.deafen(elem, 'webkitTransitionEnd', elem.setXTCB); + elem.setXTCB = null; + } + + elem.setXTCB = function () { + if (callback) { callback(); } + } + + var sX = getX(elem); + if (!duration || sX == parseInt(x)) { + elem.setXTCB(); + } else { + Monocle.Events.listen(elem, 'webkitTransitionEnd', elem.setXTCB); + } + } + + + /* + function setX(elem, x, options, callback) { + var duration, transition; + + if (!Monocle.Browser.has.transitions) { + duration = 0; + } else if (!options.duration) { + duration = 0; + } else { + duration = parseInt(options['duration']); + } + + if (typeof(x) == "number") { x = x + "px"; } + + if (duration) { + transition = duration + "ms"; + transition += ' ' + (options['timing'] || 'linear'); + transition += ' ' + (options['delay'] || 0) + 'ms'; + } else { + transition = "none"; + } + + if (elem.setXTCB) { + Monocle.Events.deafen(elem, 'webkitTransitionEnd', elem.setXTCB); + Monocle.Events.deafen(elem, 'transitionend', elem.setXTCB); + elem.setXTCB = null; + } + + elem.setXTCB = function () { + if (callback) { callback(); } + } + + elem.dom.setBetaStyle('transition', transition); + if (Monocle.Browser.has.transform3d) { + elem.dom.setBetaStyle('transform', 'translate3d('+x+',0,0)'); + } else { + elem.dom.setBetaStyle('transform', 'translateX('+x+')'); + } + + if (!duration) { + elem.setXTCB(); + } else { + Monocle.Events.listen(elem, 'webkitTransitionEnd', elem.setXTCB); + Monocle.Events.listen(elem, 'transitionend', elem.setXTCB); + } + } + */ + + + function getX(elem) { + if (typeof WebKitCSSMatrix == "object") { + var matrix = window.getComputedStyle(elem).webkitTransform; + matrix = new WebKitCSSMatrix(matrix); + return matrix.m41; + } else { + var prop = elem.style.MozTransform; + if (!prop || prop == "") { return 0; } + return parseFloat((/translateX\((\-?.*)px\)/).exec(prop)[1]) || 0; + } + } + + + function jumpIn(pageDiv, callback) { + setX(pageDiv, 0, { duration: 1 }, callback); + } + + + function jumpOut(pageDiv, callback) { + setX(pageDiv, 0 - pageDiv.offsetWidth, { duration: 1 }, callback); + } + + + + function slideIn(callback) { + var slideOpts = { + duration: k.durations.SLIDE, + timing: 'ease-in' + }; + setX(upperPage(), 0, slideOpts, callback); + } + + + function slideOut(callback) { + var slideOpts = { + duration: k.durations.SLIDE, + timing: 'ease-in' + }; + setX(upperPage(), 0 - upperPage().offsetWidth, slideOpts, callback); + } + + + function slideToCursor(cursorX, callback, duration) { + setX( + upperPage(), + Math.min(0, cursorX - upperPage().offsetWidth), + { duration: duration || k.durations.FOLLOW_CURSOR }, + callback + ); + } + + + API.pageCount = p.pageCount; + API.addPage = addPage; + API.getPlace = getPlace; + API.moveTo = moveTo; + API.listenForInteraction = listenForInteraction; + + API.visiblePages = visiblePages; + API.interactiveMode = interactiveMode; + + initialize(); + + return API; +} + + +Monocle.Flippers.Slider.DEFAULT_PANELS_CLASS = Monocle.Panels.TwoPane; +Monocle.Flippers.Slider.FORWARDS = 1; +Monocle.Flippers.Slider.BACKWARDS = -1; +Monocle.Flippers.Slider.durations = { + SLIDE: 220, + FOLLOW_CURSOR: 100 +} + +Monocle.pieceLoaded('flippers/slider'); + +Monocle.pieceLoaded('monocle'); diff --git a/resources/recipes/elperiodico_catalan.recipe b/resources/recipes/elperiodico_catalan.recipe index e2bcb738b7..6b78f923cb 100644 --- a/resources/recipes/elperiodico_catalan.recipe +++ b/resources/recipes/elperiodico_catalan.recipe @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '30 October 2010, Jordi Balcells based on an earlier recipe by Darko Miletic ' ''' elperiodico.cat ''' @@ -12,8 +12,8 @@ from calibre.ebooks.BeautifulSoup import Tag class ElPeriodico_cat(BasicNewsRecipe): title = 'El Periodico de Catalunya' - __author__ = 'Darko Miletic' - description = 'Noticias desde Catalunya' + __author__ = 'Jordi Balcells/Darko Miletic' + description = 'Noticies des de Catalunya' publisher = 'elperiodico.cat' category = 'news, politics, Spain, Catalunya' oldest_article = 2 @@ -33,15 +33,25 @@ class ElPeriodico_cat(BasicNewsRecipe): html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - feeds = [(u"Tota l'edició", u'http://www.elperiodico.cat/rss.asp?id=46')] + feeds = [(u'Portada', u'http://www.elperiodico.cat/ca/rss/rss_portada.xml'), + (u'Internacional', u'http://www.elperiodico.cat/ca/rss/internacional/rss.xml'), + (u'Societat', u'http://www.elperiodico.cat/ca/rss/societat/rss.xml'), + (u'Ci\xe8ncia i tecnologia', u'http://www.elperiodico.cat/ca/rss/ciencia-i-tecnologia/rss.xml'), + (u'Esports', u'http://www.elperiodico.cat/ca/rss/esports/rss.xml'), + (u'Gent', u'http://www.elperiodico.cat/ca/rss/gent/rss.xml'), + (u'Opini\xf3', u'http://www.elperiodico.cat/ca/rss/opinio/rss.xml'), + (u'Pol\xedtica', u'http://www.elperiodico.cat/ca/rss/politica/rss.xml'), + (u'Barcelona', u'http://www.elperiodico.cat/ca/rss/barcelona/rss.xml'), + (u'Economia', u'http://www.elperiodico.cat/ca/rss/economia/rss.xml'), + (u'Cultura i espectacles', u'http://www.elperiodico.cat/ca/rss/cultura-i-espectacles/rss.xml'), + (u'Tele', u'http://www.elperiodico.cat/ca/rss/tele/rss.xml')] - keep_only_tags = [dict(name='div', attrs={'id':'noticia'})] + keep_only_tags = [dict(name='div', attrs={'class':'titularnoticia'}), + dict(name='div', attrs={'class':'noticia_completa'})] - remove_tags = [ - dict(name=['object','link','script']) - ,dict(name='ul',attrs={'class':'herramientasDeNoticia'}) - ,dict(name='div', attrs={'id':'inferiores'}) + remove_tags = [dict(name='div', attrs={'class':['opcionb','opcionb last','columna_noticia']}), + dict(name='span', attrs={'class':'opcionesnoticia'}) ] def print_version(self, url): diff --git a/resources/recipes/elperiodico_spanish.recipe b/resources/recipes/elperiodico_spanish.recipe index 073863fa15..d19adc5e58 100644 --- a/resources/recipes/elperiodico_spanish.recipe +++ b/resources/recipes/elperiodico_spanish.recipe @@ -2,17 +2,17 @@ # -*- coding: utf-8 -*- __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '30 October 2010, Jordi Balcells based on an earlier recipe by Darko Miletic ' ''' -elperiodico.com +elperiodico.cat ''' from calibre.web.feeds.news import BasicNewsRecipe from calibre.ebooks.BeautifulSoup import Tag -class ElPeriodico_esp(BasicNewsRecipe): +class ElPeriodico_cat(BasicNewsRecipe): title = 'El Periodico de Catalunya' - __author__ = 'Darko Miletic' + __author__ = 'Jordi Balcells/Darko Miletic' description = 'Noticias desde Catalunya' publisher = 'elperiodico.com' category = 'news, politics, Spain, Catalunya' @@ -33,15 +33,25 @@ class ElPeriodico_esp(BasicNewsRecipe): html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - feeds = [(u"Toda la edición", u'http://www.elperiodico.com/rss.asp?id=46')] + feeds = [(u'Portada', u'http://www.elperiodico.com/es/rss/rss_portada.xml'), + (u'Internacional', u'http://elperiodico.com/es/rss/internacional/rss.xml'), + (u'Sociedad', u'http://elperiodico.com/es/rss/sociedad/rss.xml'), + (u'Ciencia y Tecnolog\xeda', u'http://elperiodico.com/es/rss/ciencia-y-tecnologia/rss.xml'), + (u'Deportes', u'http://elperiodico.com/es/rss/deportes/rss.xml'), + (u'Gente', u'http://elperiodico.com/es/rss/gente/rss.xml'), + (u'Opini\xf3n', u'http://elperiodico.com/es/rss/opinion/rss.xml'), + (u'Pol\xedtica', u'http://elperiodico.com/es/rss/politica/rss.xml'), + (u'Barcelona', u'http://elperiodico.com/es/rss/barcelona/rss.xml'), + (u'Econom\xeda', u'http://elperiodico.com/es/rss/economia/rss.xml'), + (u'Cultura y espect\xe1culos', u'http://elperiodico.com/es/rss/cultura-y-espectaculos/rss.xml'), + (u'Tele', u'http://elperiodico.com/es/rss/cultura-y-espectaculos/rss.xml')] - keep_only_tags = [dict(name='div', attrs={'id':'noticia'})] + keep_only_tags = [dict(name='div', attrs={'class':'titularnoticia'}), + dict(name='div', attrs={'class':'noticia_completa'})] - remove_tags = [ - dict(name=['object','link','script']) - ,dict(name='ul',attrs={'class':'herramientasDeNoticia'}) - ,dict(name='div', attrs={'id':'inferiores'}) + remove_tags = [dict(name='div', attrs={'class':['opcionb','opcionb last','columna_noticia']}), + dict(name='span', attrs={'class':'opcionesnoticia'}) ] def print_version(self, url): diff --git a/resources/recipes/taggeschau_de.recipe b/resources/recipes/taggeschau_de.recipe new file mode 100644 index 0000000000..72677e3bc1 --- /dev/null +++ b/resources/recipes/taggeschau_de.recipe @@ -0,0 +1,24 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class Tagesschau(BasicNewsRecipe): + title = 'Tagesschau' + description = 'Nachrichten der ARD' + publisher = 'ARD' + language = 'de_DE' + + __author__ = 'Florian Andreas Pfaff' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + + feeds = [('Tagesschau', 'http://www.tagesschau.de/xml/rss2')] + + remove_tags = [ + dict(name='div', attrs={'class':['linksZumThema schmal','teaserBox','boxMoreLinks','directLinks','teaserBox boxtext','fPlayer','zitatBox breit flashaudio']}), + dict(name='div', + attrs={'id':['socialBookmarks','seitenanfang']}), + dict(name='ul', + attrs={'class':['directLinks','directLinks weltatlas']}), + dict(name='strong', attrs={'class':['boxTitle inv','inv']}) + ] + keep_only_tags = [dict(name='div', attrs={'id':'centerCol'})] diff --git a/setup/commands.py b/setup/commands.py index a67c3e8633..26af3d967a 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' __all__ = [ 'pot', 'translations', 'get_translations', 'iso639', - 'build', 'build_pdf2xml', + 'build', 'build_pdf2xml', 'server', 'gui', 'develop', 'install', 'resources', @@ -35,6 +35,9 @@ from setup.extensions import Build, BuildPDF2XML build = Build() build_pdf2xml = BuildPDF2XML() +from setup.server import Server +server = Server() + from setup.install import Develop, Install, Sdist develop = Develop() install = Install() diff --git a/setup/server.py b/setup/server.py new file mode 100644 index 0000000000..2103f4805a --- /dev/null +++ b/setup/server.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import subprocess, tempfile, os, time + +from setup import Command + +class Server(Command): + + description = 'Run the calibre server in development mode conveniently' + + MONOCLE_PATH = '../monocle' + + def rebuild_monocole(self): + subprocess.check_call(['sprocketize', '-C', self.MONOCLE_PATH, + '-I', 'src', 'src/monocle.js'], + stdout=open('resources/content_server/monocle.js', 'wb')) + + def launch_server(self, log): + self.rebuild_monocole() + p = subprocess.Popen(['calibre-server', '--develop'], + stderr=subprocess.STDOUT, stdout=log) + return p + + def run(self, opts): + tdir = tempfile.gettempdir() + logf = os.path.join(tdir, 'calibre-server.log') + log = open(logf, 'ab') + print 'Server log available at:', logf + + while True: + print 'Starting server...' + p = self.launch_server(log) + try: + raw_input('Press Enter to kill/restart server. Ctrl+C to quit: ') + except: + break + else: + while p.returncode is None: + p.terminate() + time.sleep(0.1) + p.kill() + print +