Merge
This commit is contained in:
		
						commit
						ac8763f4f5
					
				
					 11 changed files with 7691 additions and 8 deletions
				
			
		|  | @ -65,6 +65,7 @@ class JSONRenderer | |||
|     public static function getFullBookContentArray ($book) { | ||||
|         global $config; | ||||
|         $out = self::getBookContentArray ($book); | ||||
|         $database = GetUrlParam (DB); | ||||
| 
 | ||||
|         $out ["coverurl"] = Data::getLink ($book, "jpg", "image/jpeg", Link::OPDS_IMAGE_TYPE, "cover.jpg", NULL)->hrefXhtml (); | ||||
|         $out ["thumbnailurl"] = Data::getLink ($book, "jpg", "image/jpeg", Link::OPDS_THUMBNAIL_TYPE, "cover.jpg", NULL, NULL, $config['cops_html_thumbnail_height'] * 2)->hrefXhtml (); | ||||
|  | @ -72,10 +73,13 @@ class JSONRenderer | |||
|         $out ["datas"] = array (); | ||||
|         $dataKindle = $book->GetMostInterestingDataToSendToKindle (); | ||||
|         foreach ($book->getDatas() as $data) { | ||||
|             $tab = array ("id" => $data->id, "format" => $data->format, "url" => $data->getHtmlLink (), "mail" => 0); | ||||
|             $tab = array ("id" => $data->id, "format" => $data->format, "url" => $data->getHtmlLink (), "mail" => 0, "readerUrl" => ""); | ||||
|             if (!empty ($config['cops_mail_configuration']) && !is_null ($dataKindle) && $data->id == $dataKindle->id) { | ||||
|                 $tab ["mail"] = 1; | ||||
|             } | ||||
|             if ($data->format == "EPUB") { | ||||
|                 $tab ["readerUrl"] = "epubreader.php?data={$data->id}&db={$database}"; | ||||
|             } | ||||
|             array_push ($out ["datas"], $tab); | ||||
|         } | ||||
|         $out ["authors"] = array (); | ||||
|  |  | |||
							
								
								
									
										74
									
								
								epubfs.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								epubfs.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | |||
| <?php | ||||
| 
 | ||||
| require_once ("config.php"); | ||||
| require_once ("base.php"); | ||||
| require_once ("book.php"); | ||||
| require_once ("resources/php-epub-meta/epub.php"); | ||||
| 
 | ||||
| function notFound () { | ||||
|     header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); | ||||
|     header("Status: 404 Not Found"); | ||||
| 
 | ||||
|     $_SERVER['REDIRECT_STATUS'] = 404; | ||||
| } | ||||
| 
 | ||||
| $idData = getURLParam ("data", NULL); | ||||
| $add = "data=$idData&"; | ||||
| if (!is_null (GetUrlParam (DB))) $add .= DB . "=" . GetUrlParam (DB) . "&"; | ||||
| $myBook = Book::getBookByDataId($idData); | ||||
| 
 | ||||
| $book = new EPub ($myBook->getFilePath ("EPUB", $idData)); | ||||
| 
 | ||||
| $book->initSpineComponent (); | ||||
| 
 | ||||
| if (!isset ($_GET["comp"])) { | ||||
|     notFound (); | ||||
|     return; | ||||
| } | ||||
| 
 | ||||
| $component = $_GET["comp"]; | ||||
| 
 | ||||
| try { | ||||
|     $data = $book->component ($component); | ||||
|     $directory = dirname ($component); | ||||
|      | ||||
|     $callback = function ($m) use ($book, $component, $add) { | ||||
|         $method = $m[1]; | ||||
|         $path = $m[2]; | ||||
|         $end = ""; | ||||
|         if (preg_match ("/^src:/", $method)) { | ||||
|             $end = ")"; | ||||
|         } | ||||
|         if (preg_match ("/^#/", $path)) { | ||||
|             return "{$method}'{$path}'{$end}"; | ||||
|         } | ||||
|         $hash = ""; | ||||
|         if (preg_match ("/^(.+)#(.+)$/", $path, $matches)) { | ||||
|             $path = $matches [1]; | ||||
|             $hash = "#" . $matches [2]; | ||||
|         } | ||||
|         $comp = $book->getComponentName ($component, $path); | ||||
|         if (!$comp) return "{$method}'#'{$end}"; | ||||
|         $out = "{$method}'epubfs.php?{$add}comp={$comp}{$hash}'{$end}"; | ||||
|         if ($end) { | ||||
|             return $out; | ||||
|         } | ||||
|         return str_replace ("&", "&", $out); | ||||
|     }; | ||||
|      | ||||
|     $data = preg_replace_callback ("/(src=)[\"']([^:]*?)[\"']/", $callback, $data); | ||||
|     $data = preg_replace_callback ("/(href=)[\"']([^:]*?)[\"']/", $callback, $data); | ||||
|     $data = preg_replace_callback ("/(\@import\s+)[\"'](.*?)[\"'];/", $callback, $data); | ||||
|     $data = preg_replace_callback ("/(src:\s*url\()(.*?)\)/", $callback, $data); | ||||
|      | ||||
|     $expires = 60*60*24*14; | ||||
|     header("Pragma: public"); | ||||
|     header("Cache-Control: maxage=".$expires); | ||||
|     header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT'); | ||||
|     header ("Content-Type: " . $book->componentContentType($component)); | ||||
|     echo $data; | ||||
| } | ||||
| catch (Exception $e) { | ||||
|     error_log ($e); | ||||
|     notFound (); | ||||
| } | ||||
							
								
								
									
										68
									
								
								epubreader.php
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								epubreader.php
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | ||||
| <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr"> | ||||
| <?php | ||||
| 
 | ||||
| require_once ("config.php"); | ||||
| require_once ("base.php"); | ||||
| require_once ("book.php"); | ||||
| require_once ("resources/php-epub-meta/epub.php"); | ||||
| 
 | ||||
| header ("Content-Type: text/html;charset=utf-8"); | ||||
| 
 | ||||
| $idData = getURLParam ("data", NULL); | ||||
| $add = "data=$idData&"; | ||||
| if (!is_null (GetUrlParam (DB))) $add .= DB . "=" . GetUrlParam (DB) . "&"; | ||||
| $myBook = Book::getBookByDataId($idData); | ||||
| 
 | ||||
| $book = new EPub ($myBook->getFilePath ("EPUB", $idData)); | ||||
| $book->initSpineComponent (); | ||||
| 
 | ||||
| ?>
 | ||||
| <head> | ||||
|     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||||
|     <meta http-equiv="imagetoolbar" content="no" /> | ||||
|     <meta name="viewport" content="width=device-width, height=device-height, user-scalable=no" /> | ||||
|     <title>COPS's Epub Reader</title> | ||||
|     <script type="text/javascript" src="<?php echo getUrlWithVersion("resources/monocle/scripts/monocore.js") ?>"></script> | ||||
|     <script type="text/javascript" src="<?php echo getUrlWithVersion("resources/monocle/scripts/monoctrl.js") ?>"></script> | ||||
|     <link rel="stylesheet" type="text/css" href="<?php echo getUrlWithVersion("resources/monocle/styles/monocore.css") ?>" media="screen" /> | ||||
|     <link rel="stylesheet" type="text/css" href="<?php echo getUrlWithVersion("resources/monocle/styles/monoctrl.css") ?>" media="screen" /> | ||||
|     <script type="text/javascript"> | ||||
|         Monocle.DEBUG = true;  | ||||
|         var bookData = { | ||||
|           getComponents: function () { | ||||
|             <?php echo "return [" . implode (", ", array_map (function ($comp) { return "'" . $comp . "'"; }, $book->components ())) . "];"; ?>
 | ||||
|           }, | ||||
|           getContents: function () { | ||||
|             <?php echo "return [" . implode (", ", array_map (function ($content) { return "{title: '" . $content["title"] . "', src: '". $content["src"] . "'}"; }, $book->contents ())) . "];"; ?>
 | ||||
|           }, | ||||
|           getComponent: function (componentId) { | ||||
|             return { url: "epubfs.php?<?php echo $add ?>comp="  + componentId }; | ||||
|           }, | ||||
|           getMetaData: function(key) { | ||||
|             return { | ||||
|               title: "<?php echo $myBook->title ?>", | ||||
|               creator: "Inventive Labs" | ||||
|             }[key]; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|     </script> | ||||
|     <script type="text/javascript" src="<?php echo getUrlWithVersion("styles/cops-monocle.js") ?>"></script> | ||||
|     <link rel="stylesheet" type="text/css" href="<?php echo getUrlWithVersion("styles/cops-monocle.css") ?>" media="screen" /> | ||||
| </head> | ||||
| <body> | ||||
|   <div id="readerBg"> | ||||
|       <div class="board"></div> | ||||
|       <div class="jacket"></div> | ||||
|       <div class="dummyPage"></div> | ||||
|       <div class="dummyPage"></div> | ||||
|       <div class="dummyPage"></div> | ||||
|       <div class="dummyPage"></div> | ||||
|       <div class="dummyPage"></div> | ||||
|   </div> | ||||
|   <div id="readerCntr"> | ||||
|       <div id="reader"></div> | ||||
|   </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										5641
									
								
								resources/monocle/scripts/monocore.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5641
									
								
								resources/monocle/scripts/monocore.js
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										985
									
								
								resources/monocle/scripts/monoctrl.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										985
									
								
								resources/monocle/scripts/monoctrl.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,985 @@ | |||
| Monocle.Controls.Contents = function (reader) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.Contents } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = { | ||||
|     reader: reader | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function createControlElements() { | ||||
|     var div = reader.dom.make('div', 'controls_contents_container'); | ||||
|     contentsForBook(div, reader.getBook()); | ||||
|     return div; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function contentsForBook(div, book) { | ||||
|     while (div.hasChildNodes()) { | ||||
|       div.removeChild(div.firstChild); | ||||
|     } | ||||
|     var list = div.dom.append('ol', 'controls_contents_list'); | ||||
| 
 | ||||
|     var contents = book.properties.contents; | ||||
|     for (var i = 0; i < contents.length; ++i) { | ||||
|       chapterBuilder(list, contents[i], 0); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function chapterBuilder(list, chp, padLvl) { | ||||
|     var index = list.childNodes.length; | ||||
|     var li = list.dom.append('li', 'controls_contents_chapter', index); | ||||
|     var span = li.dom.append( | ||||
|       'span', | ||||
|       'controls_contents_chapterTitle', | ||||
|       index, | ||||
|       { html: chp.title } | ||||
|     ); | ||||
|     span.style.paddingLeft = padLvl + "em"; | ||||
| 
 | ||||
|     var invoked = function () { | ||||
|       p.reader.skipToChapter(chp.src); | ||||
|       p.reader.hideControl(API); | ||||
|     } | ||||
| 
 | ||||
|     Monocle.Events.listenForTap(li, invoked, 'controls_contents_chapter_active'); | ||||
| 
 | ||||
|     if (chp.children) { | ||||
|       for (var i = 0; i < chp.children.length; ++i) { | ||||
|         chapterBuilder(list, chp.children[i], padLvl + 1); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   API.createControlElements = createControlElements; | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| ; | ||||
| Monocle.Controls.Magnifier = function (reader) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.Magnifier } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = { | ||||
|     buttons: [], | ||||
|     magnified: false | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function initialize() { | ||||
|     p.reader = reader; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function createControlElements(holder) { | ||||
|     var btn = holder.dom.make('div', 'controls_magnifier_button'); | ||||
|     btn.smallA = btn.dom.append('span', 'controls_magnifier_a', { text: 'A' }); | ||||
|     btn.largeA = btn.dom.append('span', 'controls_magnifier_A', { text: 'A' }); | ||||
|     p.buttons.push(btn); | ||||
|     Monocle.Events.listenForTap(btn, toggleMagnification); | ||||
|     return btn; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function toggleMagnification(evt) { | ||||
|     var opacities; | ||||
|     p.magnified = !p.magnified; | ||||
|     if (p.magnified) { | ||||
|       opacities = [0.3, 1]; | ||||
|       p.reader.formatting.setFontScale(k.MAGNIFICATION, true); | ||||
|     } else { | ||||
|       opacities = [1, 0.3]; | ||||
|       p.reader.formatting.setFontScale(null, true); | ||||
|     } | ||||
| 
 | ||||
|     for (var i = 0; i < p.buttons.length; i++) { | ||||
|       p.buttons[i].smallA.style.opacity = opacities[0]; | ||||
|       p.buttons[i].largeA.style.opacity = opacities[1]; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   API.createControlElements = createControlElements; | ||||
| 
 | ||||
|   initialize(); | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Monocle.Controls.Magnifier.MAGNIFICATION = 1.2; | ||||
| // A panel is an invisible column of interactivity. When contact occurs
 | ||||
| // (mousedown, touchstart), the panel expands to the full width of its
 | ||||
| // container, to catch all interaction events and prevent them from hitting
 | ||||
| // other things.
 | ||||
| //
 | ||||
| // Panels are used primarily to provide hit zones for page flipping
 | ||||
| // interactions, but you can do whatever you like with them.
 | ||||
| //
 | ||||
| // After instantiating a panel and adding it to the reader as a control,
 | ||||
| // you can call listenTo() with a hash of methods for any of 'start', 'move'
 | ||||
| // 'end' and 'cancel'.
 | ||||
| //
 | ||||
| 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 setDirection(dir) { | ||||
|     p.direction = dir; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   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](p.direction, evt.m.offsetX, evt.m.offsetY, API); | ||||
|     } | ||||
|     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; | ||||
|   API.setDirection = setDirection; | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Monocle.Controls.Panel.CLS = { | ||||
|   panel: 'panel', | ||||
|   expanded: 'controls_panel_expanded' | ||||
| } | ||||
| Monocle.Controls.Panel.DEFAULT_STYLES = { | ||||
|   position: 'absolute', | ||||
|   height: '100%' | ||||
| } | ||||
| ; | ||||
| Monocle.Controls.PlaceSaver = function (bookId) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.PlaceSaver } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = {} | ||||
| 
 | ||||
| 
 | ||||
|   function initialize() { | ||||
|     applyToBook(bookId); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function assignToReader(reader) { | ||||
|     p.reader = reader; | ||||
|     p.reader.listen('monocle:turn', savePlaceToCookie); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function applyToBook(bookId) { | ||||
|     p.bkTitle = bookId.toLowerCase().replace(/[^a-z0-9]/g, ''); | ||||
|     p.prefix = k.COOKIE_NAMESPACE + p.bkTitle + "."; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function setCookie(key, value, days) { | ||||
|     var expires = ""; | ||||
|     if (days) { | ||||
|       var d = new Date(); | ||||
|       d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); | ||||
|       expires = "; expires="+d.toGMTString(); | ||||
|     } | ||||
|     var path = "; path=/"; | ||||
|     document.cookie = p.prefix + key + "=" + value + expires + path; | ||||
|     return value; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function getCookie(key) { | ||||
|     if (!document.cookie) { | ||||
|       return null; | ||||
|     } | ||||
|     var regex = new RegExp(p.prefix + key + "=(.+?)(;|$)"); | ||||
|     var matches = document.cookie.match(regex); | ||||
|     if (matches) { | ||||
|       return matches[1]; | ||||
|     } else { | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function savePlaceToCookie() { | ||||
|     var place = p.reader.getPlace(); | ||||
|     setCookie( | ||||
|       "component", | ||||
|       encodeURIComponent(place.componentId()), | ||||
|       k.COOKIE_EXPIRES_IN_DAYS | ||||
|     ); | ||||
|     setCookie( | ||||
|       "percent", | ||||
|       place.percentageThrough(), | ||||
|       k.COOKIE_EXPIRES_IN_DAYS | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function savedPlace() { | ||||
|     var locus = { | ||||
|       componentId: getCookie('component'), | ||||
|       percent: getCookie('percent') | ||||
|     } | ||||
|     if (locus.componentId && locus.percent) { | ||||
|       locus.componentId = decodeURIComponent(locus.componentId); | ||||
|       locus.percent = parseFloat(locus.percent); | ||||
|       return locus; | ||||
|     } else { | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function restorePlace() { | ||||
|     var locus = savedPlace(); | ||||
|     if (locus) { | ||||
|       p.reader.moveTo(locus); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   API.assignToReader = assignToReader; | ||||
|   API.savedPlace = savedPlace; | ||||
|   API.restorePlace = restorePlace; | ||||
| 
 | ||||
|   initialize(); | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| Monocle.Controls.PlaceSaver.COOKIE_NAMESPACE = "monocle.controls.placesaver."; | ||||
| Monocle.Controls.PlaceSaver.COOKIE_EXPIRES_IN_DAYS = 7; // Set to 0 for session-based expiry.
 | ||||
| ; | ||||
| Monocle.Controls.Scrubber = function (reader) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.Scrubber } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = {} | ||||
| 
 | ||||
| 
 | ||||
|   function initialize() { | ||||
|     p.reader = reader; | ||||
|     p.reader.listen('monocle:turn', updateNeedles); | ||||
|     updateNeedles(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function pixelToPlace(x, cntr) { | ||||
|     if (!p.componentIds) { | ||||
|       p.componentIds = p.reader.getBook().properties.componentIds; | ||||
|       p.componentWidth = 100 / p.componentIds.length; | ||||
|     } | ||||
|     var pc = (x / cntr.offsetWidth) * 100; | ||||
|     var cmpt = p.componentIds[Math.floor(pc / p.componentWidth)]; | ||||
|     var cmptPc = ((pc % p.componentWidth) / p.componentWidth); | ||||
|     return { componentId: cmpt, percentageThrough: cmptPc }; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function placeToPixel(place, cntr) { | ||||
|     if (!p.componentIds) { | ||||
|       p.componentIds = p.reader.getBook().properties.componentIds; | ||||
|       p.componentWidth = 100 / p.componentIds.length; | ||||
|     } | ||||
|     var componentIndex = p.componentIds.indexOf(place.componentId()); | ||||
|     var pc = p.componentWidth * componentIndex; | ||||
|     pc += place.percentageThrough() * p.componentWidth; | ||||
|     return Math.round((pc / 100) * cntr.offsetWidth); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function updateNeedles() { | ||||
|     if (p.hidden || !p.reader.dom.find(k.CLS.container)) { | ||||
|       return; | ||||
|     } | ||||
|     var place = p.reader.getPlace(); | ||||
|     var x = placeToPixel(place, p.reader.dom.find(k.CLS.container)); | ||||
|     var needle, i = 0; | ||||
|     for (var i = 0, needle; needle = p.reader.dom.find(k.CLS.needle, i); ++i) { | ||||
|       setX(needle, x - needle.offsetWidth / 2); | ||||
|       p.reader.dom.find(k.CLS.trail, i).style.width = x + "px"; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function setX(node, x) { | ||||
|     var cntr = p.reader.dom.find(k.CLS.container); | ||||
|     x = Math.min(cntr.offsetWidth - node.offsetWidth, x); | ||||
|     x = Math.max(x, 0); | ||||
|     Monocle.Styles.setX(node, x); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function createControlElements(holder) { | ||||
|     var cntr = holder.dom.make('div', k.CLS.container); | ||||
|     var track = cntr.dom.append('div', k.CLS.track); | ||||
|     var needleTrail = cntr.dom.append('div', k.CLS.trail); | ||||
|     var needle = cntr.dom.append('div', k.CLS.needle); | ||||
|     var bubble = cntr.dom.append('div', k.CLS.bubble); | ||||
| 
 | ||||
|     var cntrListeners, bodyListeners; | ||||
| 
 | ||||
|     var moveEvt = function (evt, x) { | ||||
|       evt.preventDefault(); | ||||
|       x = (typeof x == "number") ? x : evt.m.registrantX; | ||||
|       var place = pixelToPlace(x, cntr); | ||||
|       setX(needle, x - needle.offsetWidth / 2); | ||||
|       var book = p.reader.getBook(); | ||||
|       var chps = book.chaptersForComponent(place.componentId); | ||||
|       var cmptIndex = p.componentIds.indexOf(place.componentId); | ||||
|       var chp = chps[Math.floor(chps.length * place.percentageThrough)]; | ||||
|       if (cmptIndex > -1 && book.properties.components[cmptIndex]) { | ||||
|         var actualPlace = Monocle.Place.FromPercentageThrough( | ||||
|           book.properties.components[cmptIndex], | ||||
|           place.percentageThrough | ||||
|         ); | ||||
|         chp = actualPlace.chapterInfo() || chp; | ||||
|       } | ||||
| 
 | ||||
|       if (chp) { | ||||
|         bubble.innerHTML = chp.title; | ||||
|       } | ||||
|       setX(bubble, x - bubble.offsetWidth / 2); | ||||
| 
 | ||||
|       p.lastX = x; | ||||
|       return place; | ||||
|     } | ||||
| 
 | ||||
|     var endEvt = function (evt) { | ||||
|       var place = moveEvt(evt, p.lastX); | ||||
|       p.reader.moveTo({ | ||||
|         percent: place.percentageThrough, | ||||
|         componentId: place.componentId | ||||
|       }); | ||||
|       Monocle.Events.deafenForContact(cntr, cntrListeners); | ||||
|       Monocle.Events.deafenForContact(document.body, bodyListeners); | ||||
|       bubble.style.display = "none"; | ||||
|     } | ||||
| 
 | ||||
|     var startFn = function (evt) { | ||||
|       bubble.style.display = "block"; | ||||
|       moveEvt(evt); | ||||
|       cntrListeners = Monocle.Events.listenForContact( | ||||
|         cntr, | ||||
|         { move: moveEvt } | ||||
|       ); | ||||
|       bodyListeners = Monocle.Events.listenForContact( | ||||
|         document.body, | ||||
|         { end: endEvt } | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     Monocle.Events.listenForContact(cntr, { start: startFn }); | ||||
| 
 | ||||
|     return cntr; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   API.createControlElements = createControlElements; | ||||
|   API.updateNeedles = updateNeedles; | ||||
| 
 | ||||
|   initialize(); | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| Monocle.Controls.Scrubber.CLS = { | ||||
|   container: 'controls_scrubber_container', | ||||
|   track: 'controls_scrubber_track', | ||||
|   needle: 'controls_scrubber_needle', | ||||
|   trail: 'controls_scrubber_trail', | ||||
|   bubble: 'controls_scrubber_bubble' | ||||
| } | ||||
| ; | ||||
| Monocle.Controls.Spinner = function (reader) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.Spinner } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = { | ||||
|     reader: reader, | ||||
|     divs: [], | ||||
|     repeaters: {}, | ||||
|     showForPages: [] | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function createControlElements(cntr) { | ||||
|     var anim = cntr.dom.make('div', 'controls_spinner_anim'); | ||||
|     anim.dom.append('div', 'controls_spinner_inner'); | ||||
|     p.divs.push(anim); | ||||
|     return anim; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function registerSpinEvent(startEvtType, stopEvtType) { | ||||
|     var label = startEvtType; | ||||
|     p.reader.listen(startEvtType, function (evt) { spin(label, evt) }); | ||||
|     p.reader.listen(stopEvtType, function (evt) { spun(label, evt) }); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Registers spin/spun event handlers for certain time-consuming events.
 | ||||
|   //
 | ||||
|   function listenForUsualDelays() { | ||||
|     registerSpinEvent('monocle:componentloading', 'monocle:componentloaded'); | ||||
|     registerSpinEvent('monocle:componentchanging', 'monocle:componentchange'); | ||||
|     registerSpinEvent('monocle:resizing', 'monocle:resize'); | ||||
|     registerSpinEvent('monocle:jumping', 'monocle:jump'); | ||||
|     registerSpinEvent('monocle:recalculating', 'monocle:recalculated'); | ||||
|     p.reader.listen('monocle:notfound', forceSpun); | ||||
|     p.reader.listen('monocle:componentfailed', forceSpun); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Displays the spinner. Both arguments are optional.
 | ||||
|   //
 | ||||
|   function spin(label, evt) { | ||||
|     label = label || k.GENERIC_LABEL; | ||||
|     p.repeaters[label] = true; | ||||
|     p.reader.showControl(API); | ||||
| 
 | ||||
|     // If the delay is on a page other than the page we've been assigned to,
 | ||||
|     // don't show the animation. p.global ensures that if an event affects
 | ||||
|     // all pages, the animation is always shown, even if other events in this
 | ||||
|     // spin cycle are page-specific.
 | ||||
|     var page = (evt && evt.m && evt.m.page) ? evt.m.page : null; | ||||
|     if (page && p.divs.length > 1) { | ||||
|       p.showForPages[page.m.pageIndex] = true; | ||||
|     } else { | ||||
|       p.global = true; | ||||
|       p.reader.dispatchEvent('monocle:modal:on'); | ||||
|     } | ||||
|     for (var i = 0; i < p.divs.length; ++i) { | ||||
|       var show = (p.global || p.showForPages[i]) ? true : false; | ||||
|       p.divs[i].dom[show ? 'removeClass' : 'addClass']('dormant'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Stops displaying the spinner. Both arguments are optional.
 | ||||
|   //
 | ||||
|   function spun(label, evt) { | ||||
|     label = label || k.GENERIC_LABEL; | ||||
|     p.repeaters[label] = false; | ||||
|     for (var l in p.repeaters) { | ||||
|       if (p.repeaters[l]) { return; } | ||||
|     } | ||||
|     forceSpun(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function forceSpun() { | ||||
|     if (p.global) { p.reader.dispatchEvent('monocle:modal:off'); } | ||||
|     p.global = false; | ||||
|     p.repeaters = {}; | ||||
|     p.showForPages = []; | ||||
|     for (var i = 0; i < p.divs.length; ++i) { | ||||
|       p.divs[i].dom.addClass('dormant'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   API.createControlElements = createControlElements; | ||||
|   API.registerSpinEvent = registerSpinEvent; | ||||
|   API.listenForUsualDelays = listenForUsualDelays; | ||||
|   API.spin = spin; | ||||
|   API.spun = spun; | ||||
|   API.forceSpun = forceSpun; | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| Monocle.Controls.Spinner.GENERIC_LABEL = "generic"; | ||||
| Monocle.Controls.Stencil = function (reader, behaviorClasses) { | ||||
| 
 | ||||
|   var API = { constructor: Monocle.Controls.Stencil } | ||||
|   var k = API.constants = API.constructor; | ||||
|   var p = API.properties = { | ||||
|     reader: reader, | ||||
|     behaviors: [], | ||||
|     components: {}, | ||||
|     masks: [] | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Create the stencil container and listen for draw/update events.
 | ||||
|   //
 | ||||
|   function createControlElements(holder) { | ||||
|     behaviorClasses = behaviorClasses || k.DEFAULT_BEHAVIORS; | ||||
|     for (var i = 0, ii = behaviorClasses.length; i < ii; ++i) { | ||||
|       addBehavior(behaviorClasses[i]); | ||||
|     } | ||||
|     p.container = holder.dom.make('div', k.CLS.container); | ||||
|     p.reader.listen('monocle:turning', hide); | ||||
|     p.reader.listen('monocle:turn:cancel', show); | ||||
|     p.reader.listen('monocle:turn', update); | ||||
|     p.reader.listen('monocle:stylesheetchange', update); | ||||
|     p.reader.listen('monocle:resize', update); | ||||
|     update(); | ||||
|     return p.container; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Pass this method an object that responds to 'findElements(doc)' with
 | ||||
|   // an array of DOM elements for that document, and to 'fitMask(elem, mask)'.
 | ||||
|   //
 | ||||
|   // After you have added all your behaviors this way, you would typically
 | ||||
|   // call update() to make them take effect immediately.
 | ||||
|   //
 | ||||
|   function addBehavior(bhvrClass) { | ||||
|     var bhvr = new bhvrClass(API); | ||||
|     if (typeof bhvr.findElements != 'function') { | ||||
|       console.warn('Missing "findElements" method for behavior: %o', bhvr); | ||||
|     } | ||||
|     if (typeof bhvr.fitMask != 'function') { | ||||
|       console.warn('Missing "fitMask" method for behavior: %o', bhvr); | ||||
|     } | ||||
|     p.behaviors.push(bhvr); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Resets any pre-calculated rectangles for the active component,
 | ||||
|   // recalculates them, and forces masks to be "drawn" (moved into the new
 | ||||
|   // rectangular locations).
 | ||||
|   //
 | ||||
|   function update() { | ||||
|     var visPages = p.reader.visiblePages(); | ||||
|     if (!visPages || !visPages.length) { return; } | ||||
|     var pageDiv = visPages[0]; | ||||
|     var cmptId = pageComponentId(pageDiv); | ||||
|     if (!cmptId) { return; } | ||||
|     p.components[cmptId] = null; | ||||
|     calculateRectangles(pageDiv); | ||||
|     draw(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function hide() { | ||||
|     p.container.style.display = 'none'; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function show() { | ||||
|     p.container.style.display = 'block'; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Removes any existing masks.
 | ||||
|   function clear() { | ||||
|     while (p.container.childNodes.length) { | ||||
|       p.container.removeChild(p.container.lastChild); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Aligns the stencil container to the shape of the page, then moves the
 | ||||
|   // masks to sit above any currently visible rectangles.
 | ||||
|   //
 | ||||
|   function draw() { | ||||
|     var pageDiv = p.reader.visiblePages()[0]; | ||||
|     var cmptId = pageComponentId(pageDiv); | ||||
|     if (!p.components[cmptId]) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Position the container.
 | ||||
|     alignToComponent(pageDiv); | ||||
| 
 | ||||
|     // Clear old masks.
 | ||||
|     clear(); | ||||
| 
 | ||||
|     // Layout the masks.
 | ||||
|     if (!p.disabled) { | ||||
|       show(); | ||||
|       var rects = p.components[cmptId]; | ||||
|       if (rects && rects.length) { | ||||
|         layoutRectangles(pageDiv, rects); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Iterate over all the <a> elements in the active component, and
 | ||||
|   // create an array of rectangular points corresponding to their positions.
 | ||||
|   //
 | ||||
|   function calculateRectangles(pageDiv) { | ||||
|     var cmptId = pageComponentId(pageDiv); | ||||
|     if (!p.components[cmptId]) { | ||||
|       p.components[cmptId] = []; | ||||
|     } else { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     var doc = pageDiv.m.activeFrame.contentDocument; | ||||
|     var offset = getOffset(pageDiv); | ||||
| 
 | ||||
|     for (var b = 0, bb = p.behaviors.length; b < bb; ++b) { | ||||
|       var bhvr = p.behaviors[b]; | ||||
|       var elems = bhvr.findElements(doc); | ||||
|       for (var i = 0; i < elems.length; ++i) { | ||||
|         var elem = elems[i]; | ||||
|         if (elem.getClientRects) { | ||||
|           var r = elem.getClientRects(); | ||||
|           for (var j = 0; j < r.length; j++) { | ||||
|             p.components[cmptId].push({ | ||||
|               element: elem, | ||||
|               behavior: bhvr, | ||||
|               left: Math.ceil(r[j].left + offset.l), | ||||
|               top: Math.ceil(r[j].top), | ||||
|               width: Math.floor(r[j].width), | ||||
|               height: Math.floor(r[j].height) | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return p.components[cmptId]; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Update location of visible rectangles - creating as required.
 | ||||
|   //
 | ||||
|   function layoutRectangles(pageDiv, rects) { | ||||
|     var offset = getOffset(pageDiv); | ||||
|     var visRects = []; | ||||
|     for (var i = 0; i < rects.length; ++i) { | ||||
|       if (rectVisible(rects[i], offset.l, offset.l + offset.w)) { | ||||
|         visRects.push(rects[i]); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     for (i = 0; i < visRects.length; ++i) { | ||||
|       var r = visRects[i]; | ||||
|       var cr = { | ||||
|         left: r.left - offset.l, | ||||
|         top: r.top, | ||||
|         width: r.width, | ||||
|         height: r.height | ||||
|       }; | ||||
|       var mask = createMask(r.element, r.behavior); | ||||
|       mask.dom.setStyles({ | ||||
|         display: 'block', | ||||
|         left: cr.left+"px", | ||||
|         top: cr.top+"px", | ||||
|         width: cr.width+"px", | ||||
|         height: cr.height+"px", | ||||
|         position: 'absolute' | ||||
|       }); | ||||
|       mask.stencilRect = cr; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Find the offset position in pixels from the left of the current page.
 | ||||
|   //
 | ||||
|   function getOffset(pageDiv) { | ||||
|     return { | ||||
|       l: pageDiv.m.offset || 0, | ||||
|       w: pageDiv.m.dimensions.properties.width | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Is this area presently on the screen?
 | ||||
|   //
 | ||||
|   function rectVisible(rect, l, r) { | ||||
|     return rect.left >= l && rect.left < r; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Returns the active component id for the given page, or the current
 | ||||
|   // page if no argument passed in.
 | ||||
|   //
 | ||||
|   function pageComponentId(pageDiv) { | ||||
|     pageDiv = pageDiv || p.reader.visiblePages()[0]; | ||||
|     if (!pageDiv.m.activeFrame.m.component) { return; } | ||||
|     return pageDiv.m.activeFrame.m.component.properties.id; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Positions the stencil container over the active frame.
 | ||||
|   //
 | ||||
|   function alignToComponent(pageDiv) { | ||||
|     cmpt = pageDiv.m.activeFrame.parentNode; | ||||
|     p.container.dom.setStyles({ | ||||
|       left: cmpt.offsetLeft+"px", | ||||
|       top: cmpt.offsetTop+"px" | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function createMask(element, bhvr) { | ||||
|     var mask = p.container.dom.append(bhvr.maskTagName || 'div', k.CLS.mask); | ||||
|     Monocle.Events.listenForContact(mask, { | ||||
|       start: function () { p.reader.dispatchEvent('monocle:magic:halt'); }, | ||||
|       move: function (evt) { evt.preventDefault(); }, | ||||
|       end: function () { p.reader.dispatchEvent('monocle:magic:init'); } | ||||
|     }); | ||||
|     bhvr.fitMask(element, mask); | ||||
|     return mask; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Make the active masks visible (by giving them a class -- override style
 | ||||
|   // in monoctrl.css).
 | ||||
|   //
 | ||||
|   function toggleHighlights() { | ||||
|     var cls = k.CLS.highlights; | ||||
|     if (p.container.dom.hasClass(cls)) { | ||||
|       p.container.dom.removeClass(cls); | ||||
|     } else { | ||||
|       p.container.dom.addClass(cls); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function disable() { | ||||
|     p.disabled = true; | ||||
|     draw(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function enable() { | ||||
|     p.disabled = false; | ||||
|     draw(); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function filterElement(elem, behavior) { | ||||
|     if (typeof behavior.filterElement == 'function') { | ||||
|       return behavior.filterElement(elem); | ||||
|     } | ||||
|     return elem; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   function maskAssigned(elem, mask, behavior) { | ||||
|     if (typeof behavior.maskAssigned == 'function') { | ||||
|       return behavior.maskAssigned(elem, mask); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   API.createControlElements = createControlElements; | ||||
|   API.addBehavior = addBehavior; | ||||
|   API.draw = draw; | ||||
|   API.update = update; | ||||
|   API.toggleHighlights = toggleHighlights; | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Monocle.Controls.Stencil.CLS = { | ||||
|   container: 'controls_stencil_container', | ||||
|   mask: 'controls_stencil_mask', | ||||
|   highlights: 'controls_stencil_highlighted' | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Monocle.Controls.Stencil.Links = function (stencil) { | ||||
|   var API = { constructor: Monocle.Controls.Stencil.Links } | ||||
| 
 | ||||
|   // Optionally specify the HTML tagname of the mask.
 | ||||
|   API.maskTagName = 'a'; | ||||
| 
 | ||||
|   // Returns an array of all the elements in the given doc that should
 | ||||
|   // be covered with a stencil mask for interactivity.
 | ||||
|   //
 | ||||
|   // (Hint: doc.querySelectorAll() is your friend.)
 | ||||
|   //
 | ||||
|   API.findElements = function (doc) { | ||||
|     return doc.querySelectorAll('a[href]'); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Return an element. It should usually be a child of the container element,
 | ||||
|   // with a className of the given maskClass. You set up the interactivity of
 | ||||
|   // the mask element here.
 | ||||
|   //
 | ||||
|   API.fitMask = function (link, mask) { | ||||
|     var hrefObject = deconstructHref(link); | ||||
| 
 | ||||
|     if (hrefObject.internal) { | ||||
|       mask.setAttribute('href', 'javascript:"Skip to chapter"'); | ||||
|       mask.onclick = function (evt) { | ||||
|         stencil.properties.reader.skipToChapter(hrefObject.internal); | ||||
|         evt.preventDefault(); | ||||
|         return false; | ||||
|       } | ||||
|     } else { | ||||
|       mask.setAttribute('href', hrefObject.external); | ||||
|       mask.setAttribute('target', '_blank'); | ||||
|       mask.onclick = function (evt) { return true; } | ||||
|     } | ||||
| 
 | ||||
|     link.onclick = function (evt) { | ||||
|       evt.preventDefault(); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // Returns an object with either:
 | ||||
|   //
 | ||||
|   // - an 'external' property -- an absolute URL with a protocol,
 | ||||
|   // host & etc, which should be treated as an external resource (eg,
 | ||||
|   // open in new window)
 | ||||
|   //
 | ||||
|   //   OR
 | ||||
|   //
 | ||||
|   // - an 'internal' property -- a relative URL (with optional hash anchor),
 | ||||
|   //  that is treated as a link to component in the book
 | ||||
|   //
 | ||||
|   // A weird but useful property of <a> tags is that while
 | ||||
|   // link.getAttribute('href') will return the actual string value of the
 | ||||
|   // attribute (eg, 'foo.html'), link.href will return the absolute URL (eg,
 | ||||
|   // 'http://example.com/monocles/foo.html').
 | ||||
|   //
 | ||||
|   function deconstructHref(elem) { | ||||
|     var loc = document.location; | ||||
|     var origin = loc.protocol+'//'+loc.host; | ||||
|     var href = elem.href; | ||||
|     var path = href.substring(origin.length); | ||||
|     var ext = { external: href }; | ||||
| 
 | ||||
|     // Anchor tags with 'target' attributes are always external URLs.
 | ||||
|     if (elem.getAttribute('target')) { | ||||
|       return ext; | ||||
|     } | ||||
|     // URLs with a different protocol or domain are always external.
 | ||||
|     //console.log("Domain test: %s <=> %s", origin, href);
 | ||||
|     if (href.indexOf(origin) != 0) { | ||||
|       return ext; | ||||
|     } | ||||
| 
 | ||||
|     // If it is in a sub-path of the current path, it's internal.
 | ||||
|     var topPath = loc.pathname.replace(/[^\/]*\.[^\/]+$/,''); | ||||
|     if (topPath[topPath.length - 1] != '/') { | ||||
|       topPath += '/'; | ||||
|     } | ||||
|     //console.log("Sub-path test: %s <=> %s", topPath, path);
 | ||||
|     if (path.indexOf(topPath) == 0) { | ||||
|       return { internal: path.substring(topPath.length) } | ||||
|     } | ||||
| 
 | ||||
|     // If it's a root-relative URL and it's in our list of component ids,
 | ||||
|     // it's internal.
 | ||||
|     var cmptIds = stencil.properties.reader.getBook().properties.componentIds; | ||||
|     for (var i = 0, ii = cmptIds.length; i < ii; ++i) { | ||||
|       //console.log("Component test: %s <=> %s", cmptIds[i], path);
 | ||||
|       if (path.indexOf(cmptIds[i]) == 0) { | ||||
|         return { internal: path } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     // Otherwise it's external.
 | ||||
|     return ext; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   return API; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Monocle.Controls.Stencil.DEFAULT_BEHAVIORS = [Monocle.Controls.Stencil.Links]; | ||||
							
								
								
									
										195
									
								
								resources/monocle/styles/monocore.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								resources/monocle/styles/monocore.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,195 @@ | |||
| /*=========================================================================== | ||||
| 
 | ||||
| This is a base-level Monocle stylesheet. It assumes no class-prefix has been | ||||
| given to the Reader during initialisation - if one has, you can copy and | ||||
| modify this stylesheet accordingly. | ||||
| 
 | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| /* The reader object that holds pretty much everything. | ||||
|  * (A direct child of the element passed to reader initialisation). */ | ||||
| 
 | ||||
| div.monelem_container { | ||||
|   background-color: black; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* The div that mimics a leaf of paper in a book. */ | ||||
| div.monelem_page { | ||||
|   background: white; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   bottom: 3px; | ||||
|   right: 5px; | ||||
|   border-right: 1px solid #999; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* The div within the page that determines page margins. */ | ||||
| div.monelem_sheaf { | ||||
|   top: 1em; | ||||
|   left: 1em; | ||||
|   bottom: 1em; | ||||
|   right: 1em; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* The iframe within the page that loads the content of the book. */ | ||||
| div.monelem_component { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* A panel that sits above the entire reader object, holding controls. */ | ||||
| div.monelem_overlay { | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* A full-size panel to display an announcement (iframe or div) */ | ||||
| div.monelem_billboard_container { | ||||
|   background: #FFF; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   z-index: 2000; | ||||
|   -webkit-transform: scale(0); | ||||
|   -moz-transform: scale(0); | ||||
|   transform: scale(0); | ||||
|   -webkit-transform-origin: -0 -0; | ||||
|   -moz-transform-origin: -0 -0; | ||||
|   transform-origin: -0 -0; | ||||
| } | ||||
| 
 | ||||
| .monelem_billboard_inner { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   border: none; | ||||
|   overflow: auto; | ||||
|   /*-webkit-overflow-scrolling: touch;*/ /* This is sexy, but crashy. */ | ||||
| } | ||||
| 
 | ||||
| div.monelem_billboard_inner { | ||||
|   min-width: 100%; | ||||
|   min-height: 100%; | ||||
|   text-align: center; | ||||
|   vertical-align: middle; | ||||
|   display: -webkit-box; | ||||
|   -webkit-box-pack: center; | ||||
|   -webkit-box-align: center; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| div.monelem_billboard_close { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   width: 50px; | ||||
|   height: 30px; | ||||
|   color: white; | ||||
|   background: #C00; | ||||
|   cursor: pointer; | ||||
|   border-bottom-left-radius: 4px; | ||||
|   text-shadow: 1px 1px 1px #900; | ||||
|   font: 9pt Helvetica Neue, Helvetica, sans-serif; | ||||
| } | ||||
| 
 | ||||
| div.monelem_billboard_close:after { | ||||
|   display: block; | ||||
|   content: 'Close'; | ||||
|   width: 100%; | ||||
|   line-height: 30px; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| div.monelem_book_fatality { | ||||
|   font-family: Helvetica Neue, Helvetica, sans-serif; | ||||
|   margin: 0 auto; | ||||
|   max-width: 75%; | ||||
| } | ||||
| 
 | ||||
| div.monelem_book_fatality p { | ||||
|   line-height: 1.4; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*=========================================================================== | ||||
|   PANELS | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| 
 | ||||
| .monelem_panels_imode_panel { | ||||
|   background: rgba(255,255,255,0.7); | ||||
|   opacity: 0; | ||||
| } | ||||
| 
 | ||||
| .monelem_panels_imode_backwardsPanel { | ||||
|   -webkit-box-shadow: 1px 1px 3px #777; | ||||
|   -moz-box-shadow: 1px 1px 3px #777; | ||||
|   box-shadow: 1px 1px 3px #777; | ||||
| } | ||||
| 
 | ||||
| .monelem_panels_imode_forwardsPanel { | ||||
|   -webkit-box-shadow: -1px 1px 3px #777; | ||||
|   -moz-box-shadow: -1px 1px 3px #777; | ||||
|   box-shadow: -1px 1px 3px #777; | ||||
| } | ||||
| 
 | ||||
| .monelem_panels_imode_centralPanel { | ||||
| } | ||||
| 
 | ||||
| .monelem_panels_imode_toggleIcon { | ||||
|   position: absolute; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
|   width: 50px; | ||||
|   height: 50px; | ||||
|   background-repeat: no-repeat; | ||||
|   background-position: center center; | ||||
| } | ||||
| 
 | ||||
| /* If you modify this you could significantly change the way panels work. */ | ||||
| div.monelem_controls_panel_expanded { | ||||
|   left: 0 !important; | ||||
|   width: 100% !important; | ||||
|   z-index: 1001 !important; | ||||
| } | ||||
| 
 | ||||
| /*=========================================================================== | ||||
|   Flippers | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| div.monelem_flippers_slider_wait { | ||||
|   position: absolute; | ||||
|   right: 0px; | ||||
|   top: 0px; | ||||
|   width: 92px; | ||||
|   height: 112px; | ||||
|   background-repeat: no-repeat; | ||||
|   -webkit-background-size: 100%; | ||||
|   -moz-background-size: 100%; | ||||
|   background-size: 100%; | ||||
| } | ||||
| 
 | ||||
| @media screen and (max-width: 640px) { | ||||
|   div.monelem_flippers_slider_wait { | ||||
|     width: 61px; | ||||
|     height: 75px; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*=========================================================================== | ||||
|   DATA URIs | ||||
| 
 | ||||
|   These are data-uri packed images, inlined for loading speed and simplicity. | ||||
|   Placed at the end of this file because they're visually noisy... | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| div.monelem_panels_imode_toggleIcon { | ||||
|   background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAaCAYAAABPY4eKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1%2B%2FAAAABV0RVh0Q3JlYXRpb24gVGltZQAzMC82LzEwBMfmVwAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAANYSURBVEiJtdZbiNVVFMfxj8cx85JkIGlqSESgOGA9WIQgGmTRUyRaYFJDnUWYGV2eyiCpkIbEKJI1UqYvUkmFDxFBgpghonajSDCM7hcxLSnt4ulh%2F2c4HufMTOH8Xs75%2F%2Ffa67v3%2Bu%2B91hphGJWZNUzCXJyKiHd6xxqNhhGDTB6NOViAyzARY3EaP%2BNL7MCBiPi9Ze4leBlTsR9jcCnuiYgDbeGZeV4F7EINe7EP3%2BJ49W4GrsZ8NPAGXouIk5k5F93YFhHPVT5H4kbcjaX1ev3kWfDMPB9P4ko8ERE7BopONWcOVmMc1uBRrG8Oc5Ptq1hdr9cPdrQMTMUWfBQRCweD9ioiPsQtmbkeu7G8P3ClsZSI98EzcxqeUsLXM1RwZs7ErRiJKXgQN2Tmzoj4qsV2Hn7BYcq369UaHIqI5yPizyGCx2MPfsRVOBoR6%2FA%2BNmXmqCbbm%2FAiMiJO9cEzcwEuwLODwMZk5oXVLYA6PouIF%2FC6cvBgI37D0mreStyJroh4r9df785XYGtEHG8Hfnjb1w08Xu2qq3regtOZuaka2whV5NZieWY%2BhkV4ICJ2N%2FusZeYMJQm8NdCuuxdPH4HENGzsXjx9REQcqRxvR2dEfNBrHxF7lHywGPXW7085cEvwZkScHAheaRz%2BwngcqyAnlEPan%2Fbh5oj4rr%2FBDlyOXUMA%2Fx%2F9oFytM5SZs3t6epbWlOtxeJjg%2BzEmMye3vF%2BCYx2YhdFnTTs3OoQT2JqZ3TiC2zETyzrwrnIwhkMTqwVsxW24GLsiYmWj0dCBo2gNy7nSRfgpIjZjM6WU1ut1lHt%2BGLOHCd6J79sN1pSkMSUzJwwD%2FBoD5I9aRHyiFIVFQ3D2j1KR%2Fh7MMDPnY1JE7GwLr3434N5BnI3GFRiFzuai0Ub34aWBDGr0pcKPM%2FPpqovpT11KoVinNAvXt1lkLTNXKFesXU1HUz3HI0plWqW0QGcoIjYoERpMy7AS17b2da06o43KzLF4RanRzwwx3%2FfOHYW7lL5ubUR83p9do9Ho%2B99fDzcZDynfdxPejog%2FBoCOxHW4AxOwKiK%2BaGc%2FILzJ6ULcXznciwM4qFSzCUob3Km0UCeU3W5v5%2B8%2FwZsWMQvzlN1Nq8C%2F4ht8qkRm72B%2B%2BoP%2FC0sEOftJmUbfAAAAAElFTkSuQmCC); | ||||
| } | ||||
| 
 | ||||
| div.monelem_flippers_slider_wait { | ||||
|   background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAABwCAMAAACkX%2BcnAAAB0VBMVEUAAACDg4OEhISFhYWGhoaHh4eIiIiJiIiJiYmKioqLi4uMjIyNjY2Ojo6Pj4%2BQkJCRkZGSkpKTk5OUlJSVlZWWlpaXl5eYmJiZmZmampqbm5ucnJycnJ2dnZ2dnZ6dnZ%2Benp6enp%2Bfn5%2Bfn6CgoKCgoKGhoKChoaGioqKjo6OkpKSlpaWmpaWmpqaoqKiqqqqrq6usrKytra2urq6wsLCxsbGzs7O0tLS0tLW1tbW1tba1tbe2tri4uLi4uLm4uLq6ury7u7y8vLy8vL28vL%2B9vb2%2Bvr6%2Bvr%2B%2Fv7%2B%2Fv8HAwMDAwMLAwMPBwcPCwsPExMTExMXFxcXGxsbHx8fIyMjJycrOztDOztHPz9DPz9HR0dTS0tTT09TT09XU1NbU1NfV1dfW1tjW1tnX19fX19rY2Nra2tva2tzd3eDe3t7f39%2Fh4eHi4uLl5enn5%2Bnp6ezp6e3q6u3q6u7r6%2B7r6%2B%2Fs7O%2Fs7PDt7fDt7fHu7vHu7vLv7%2B%2Fv7%2FLv7%2FPw8PDw8PPw8PTx8fTx8fXy8vXy8vbz8%2Fbz8%2Ff09Pf09Pj19fj19fn29vn39%2Fn39%2Fr4%2BPr4%2BPv5%2Bfv6%2Bvv6%2Bvz7%2B%2Fz7%2B%2F38%2FP39%2Ff39%2Ff7%2B%2Fv7%2B%2Fv%2F%2F%2F%2F%2BHSJEZAAAAAXRSTlMAQObYZgAAA5dJREFUaN61lk1uE0EQhd%2BrsQlREAgkFkQKLJByteQU3IIdd2OBYIFASFmAFLurWPT0uOfXme6aWUXy6PNL9XPXR3z6DSI93wQ0GkHjzweapM%2B%2Btn8SMAERPzKQQKN7IDRhD2APgkbumucvXp24T3s%2BH47H7%2F9U1AxmpvaDzV5IUMBfD0CbQXYPly93K%2BEiwneqphpMVc3e7p492zciQhGKNN2bX%2F42shJOEQFIQgAKgfgdpvFz7d58%2FPO4Fn5PiggBAUkAYhoUMJipwU5vhsfjWjhESMTsBChQVVMDYICadfjD4VAAFyGYZVcN7Vzar4iP6frkd5RuLjG7WlCFwdSy4ICtPlBAKJLNhYBq6HKf8IHrx4J7IQX5maqFLHeC3yrWwyEiFACSzlTVVFNuzQZTAG%2BrLoQwVT1kubvGF4wlVj2vi2isuvWrbiXJIUISYKwL5qpuWgbvXQHxSCeqbiXwvOrpClC1QdXViuAQUnpXgE1U%2FSb%2BUwVVF7JfdTWN2G4uFyiaeZz6oOpB1drzTF0sSw6ySdc5Y%2FZe1SPeCpPfS6p6yq4arK16V5eyAwWEp6oTEKpqewXEygBW9iMabzsAZjqoOkuTL227tjJvSg8UaG%2FGhW33obSK8d4dVj1eAV3VrXQsuBtXvd12XdWteCxg2nbobbuU2xQsHst42zHe6lllypOnbcdUeZ62HUzNoOXJz4vdpZXDz4rde5TDz4rdsQ6%2BLHZNxVjOip3VJD8ndjVtOSt2rEp%2BRuxCHXxZ7G6tCr4sdhUX1xPETmvhC2KndWNZFjtUjmVR7KRyLItiF2qTL4ndtdXCF8Tuqhq%2BIHaonfmi2Ek1fEHsQjV8YdtVt2VR7DzgM2J36QCfFbsbB%2Fi82MEBPit2HvBZsfMYy6zYuSSfq7oLfE7sLpzgk2J37QKfETt1gc%2BJnQ98Rux84NNiJ07wSbELTvBpsXOCT4rdRz%2F4WOzMCz4pdl7wKbGDG3xC7NzGMiV2jvCx2PnNfELsbvzgY7FrHOFjsXOEj7YdHeFjsfOF96sePOFjsXOED8XutSt8sO2uXOFDsfOFD6ruCx9U3Rc%2BEDt3eC52zvC%2B2DnD%2B2LnDe9V3RveEzt3eC527vBc7NzhudhtAe%2BuAH94VnV%2FeCZ2G8BzscMmUxdgi5lnYrcF%2FCR2wCZHSvftP9x2m8DTttsEnsRuK7hs8%2FPPxG4beCt2G8HbbbcNPG67reAUEfwHRePBMkvuZ4wAAAAASUVORK5CYII%3D); | ||||
| } | ||||
							
								
								
									
										169
									
								
								resources/monocle/styles/monoctrl.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								resources/monocle/styles/monoctrl.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,169 @@ | |||
| /*=========================================================================== | ||||
|   CONTROLS | ||||
| 
 | ||||
|   The standard Monocle stylesheet for the optional Monocle controls. See | ||||
|   comments for monocore.css, which apply here too. | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| /* Contents */ | ||||
| 
 | ||||
| div.monelem_controls_contents_container { | ||||
|   position: absolute; | ||||
|   width: 75%; | ||||
|   height: 75%; | ||||
|   left: 12.5%; | ||||
|   top: 12.5%; | ||||
|   background: #EEE; | ||||
|   border: 2px solid #F7F7F7; | ||||
|   border-radius: 9px; | ||||
|   overflow-y: auto; | ||||
|   -webkit-overflow-scrolling: touch; | ||||
|   -moz-border-radius: 9px; | ||||
|   -webkit-border-radius: 9px; | ||||
|   box-shadow: 1px 2px 6px rgba(0,0,0,0.5); | ||||
|   -moz-box-shadow: 1px 2px 6px rgba(0,0,0,0.5); | ||||
|   -webkit-box-shadow: 1px 2px 6px rgba(0,0,0,0.5); | ||||
| } | ||||
| 
 | ||||
| ol.monelem_controls_contents_list { | ||||
|   margin: 6px; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| li.monelem_controls_contents_chapter { | ||||
|   list-style: none; | ||||
|   line-height: 220%; | ||||
|   padding-left: 1em; | ||||
|   padding-right: 2em; | ||||
|   border-bottom: 2px groove #FEFEFE; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| li.monelem_controls_contents_chapter_active { | ||||
|   background: #999; | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| /* Magnifier */ | ||||
| 
 | ||||
| .monelem_controls_magnifier_button { | ||||
|   cursor: pointer; | ||||
|   color: #555; | ||||
|   position: absolute; | ||||
|   top: 2px; | ||||
|   right: 10px; | ||||
|   padding: 0 2px; | ||||
| } | ||||
| 
 | ||||
| .monelem_controls_magnifier_a { | ||||
|   font-size: 11px; | ||||
| } | ||||
| 
 | ||||
| .monelem_controls_magnifier_A { | ||||
|   font-size: 18px; | ||||
|   opacity: 0.3; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Spinner */ | ||||
| 
 | ||||
| .monelem_controls_spinner_anim { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background-color: white; | ||||
|   background-repeat: no-repeat; | ||||
|   background-position: center center; | ||||
| } | ||||
| .monelem_controls_spinner_anim.monelem_dormant { | ||||
|   width: 0; | ||||
|   height: 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Scrubber */ | ||||
| 
 | ||||
| div.monelem_controls_scrubber_container { | ||||
|   position: absolute; | ||||
|   left: 1em; | ||||
|   right: 1em; | ||||
|   bottom: 4px; | ||||
|   height: 30px; | ||||
|   background: rgba(255,255,255,0.8); | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_track { | ||||
|   margin-top: 10px; | ||||
|   height: 5px; | ||||
|   border: 1px solid #999; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_needle { | ||||
|   position: absolute; | ||||
|   width: 14px; | ||||
|   height: 14px; | ||||
|   top: 5px; | ||||
|   background: #CCC; | ||||
|   border: 1px solid #999; | ||||
|   border-radius: 8px; | ||||
|   -moz-border-radius: 8px; | ||||
|   -webkit-border-radius: 8px; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_trail { | ||||
|   position: absolute; | ||||
|   background: #DDD; | ||||
|   top: 11px; | ||||
|   left: 1px; | ||||
|   height: 5px; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_bubble { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
|   padding: 1em; | ||||
|   min-width: 20%; | ||||
|   max-width: 30%; | ||||
|   bottom: 2.5em; | ||||
|   background: rgba(0, 0, 0, 0.9); | ||||
|   color: #CCC; | ||||
|   font: bold 12px Lucida Grande, Tahoma, Helvetica, Arial, sans-serif; | ||||
|   white-space: nowrap; | ||||
|   text-overflow: ellipsis; | ||||
|   overflow: hidden; | ||||
|   border-radius: 10px; | ||||
|   -moz-border-radius: 10px; | ||||
|   -webkit-border-radius: 10px; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Stencil */ | ||||
| div.monelem_controls_stencil_container { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 0; | ||||
|   height: 0; | ||||
| } | ||||
| 
 | ||||
| .monelem_controls_stencil_mask { | ||||
|   display: block; | ||||
|   position: absolute; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_stencil_highlighted .monelem_controls_stencil_mask { | ||||
|   background: rgba(0,0,255,0.15); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /*=========================================================================== | ||||
|   DATA URIs | ||||
| 
 | ||||
|   These are data-uri packed images, inlined for loading speed and simplicity. | ||||
|   Placed at the end of this file because they're visually noisy... | ||||
| ---------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| div.monelem_controls_spinner_anim { | ||||
|   background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA0CAMAAAANBM47AAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M1cbXjNgAAABV0RVh0Q3JlYXRpb24gVGltZQAxNy81LzEwnOhoKAAAAE5QTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxKKmWQAAABp0Uk5TAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBl0wLilAAAC8klEQVQYGZXBB2LjOAADQFCimtVFEoD//9HLbrJxipzoZoBToYptUwV8V/Xrsc8RP6i7aduPXHI69mWIAR9UY6Is5rnCuTBsWXeLkijbTFOLf7okW6R8zxEnwphskfIrifJdW4u/RtlpbGLsdjoHfDNkSZTSNg192w3jchSJEtcawCRzDvgjLPINX1SbSSvNXcC7eNuLXpQuTFbp8CZkH/isyS68H0PAF+0iUzxoNni33HPAR51UxDHgRLObslLEw3TPFT7oKPqIeOImURs+WJ0CHlqKXgLOxL4NgyRqxbuqeMNDXURPOBNWSokquRRP+GeVOzwcLlpwJmx3WVJuY2ZRi1ezfOBhdNGGU52ZhrloBzqSucKLerdLxLtIKlc4Nd9LA6wuNTC5aAbQZzs3eFhE9Tg3mw2wqkQgHCZrTJK3iIcoasMTvXX0E30EAK2k+Wbrho8mky2eCLslSz3+2ERKucVHIZsbnqp2WWXEX60ossMnrakeP+jGocabg9SGzyaXHHDRpOIO/zRjDWCTNlzVsLjFm4bODapE33BZoke8mVy8oqXY4rLNXvFmEnXDKJYaly3SjlchkSOwiCngstFMeDXLE4CVygGX3e6FawUgzFIKANbiHHDZ7U4qL7c5SWzxYqFywGXjvVD3F3Zu8ccs5gqXzeYx7CTTWOOvnmTEZZu0ItSxrvAmZrrHZYme8dkhLbiqLkUDPlvMA1cNIiM+613Y4KJNSviiprTgmrrQM75arVzhkllUxFetqBlXVEXa8d0hMeKCxVSH73rRG37XidpxZlXRiN9UhYUtztRFVI+fhUPFE851KlSHn4TNxTueGU2yx3PVbipVeGpxIaeAJ2IynRv8YHEp3iNOjRRdGvxotGjONb7pD7M4RfyiK6ZclhYf1bdDprRW+FW9SZSUlqGtq1BVTTftRaKce1zS7bIpWyW/oK0i38tU4apupWyRsijKVhoj/o+6W45cJEoqaR+bgP8txH5a1nUZ2gq/+Q/51T5MhuG3fQAAAABJRU5ErkJggg==); | ||||
| } | ||||
|  | @ -74,7 +74,7 @@ class EPub { | |||
|         $spine = $this->xpath->query('//opf:spine')->item(0); | ||||
|         $tocid = $spine->getAttribute('toc'); | ||||
|         $tochref = $this->xpath->query("//opf:manifest/opf:item[@id='$tocid']")->item(0)->attr('href'); | ||||
|         $tocpath = dirname($this->meta).'/'.$tochref; | ||||
|         $tocpath = $this->getFullPath ($tochref); | ||||
|         // read epub toc
 | ||||
|         if (!$this->zip->FileExists($tocpath)) { | ||||
|             throw new Exception ("Unable to find " . $tocpath); | ||||
|  | @ -146,7 +146,7 @@ class EPub { | |||
|         $nodes = $this->xpath->query('//opf:spine/opf:itemref'); | ||||
|         foreach($nodes as $node){ | ||||
|             $idref =  $node->getAttribute('idref'); | ||||
|             $spine[] = $this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href'); | ||||
|             $spine[] = $this->encodeComponentName ($this->xpath->query("//opf:manifest/opf:item[@id='$idref']")->item(0)->getAttribute('href')); | ||||
|         } | ||||
|         return $spine; | ||||
|     } | ||||
|  | @ -155,22 +155,66 @@ class EPub { | |||
|      * Get the component content | ||||
|      */ | ||||
|     public function component($comp) { | ||||
|         $path = dirname($this->meta).'/'.$comp; | ||||
|         $path = $this->decodeComponentName ($comp); | ||||
|         $path = $this->getFullPath ($path); | ||||
|         if (!$this->zip->FileExists($path)) { | ||||
|             throw new Exception ("Unable to find " . $path); | ||||
|             throw new Exception ("Unable to find {$path} <{$comp}>"); | ||||
|         } | ||||
| 
 | ||||
|         $data = $this->zip->FileRead($path); | ||||
|         return $data; | ||||
|     } | ||||
| 
 | ||||
|     public function getComponentName ($comp, $elementPath) { | ||||
|         $path = $this->decodeComponentName ($comp); | ||||
|         $path = $this->getFullPath ($path, $elementPath); | ||||
|         if (!$this->zip->FileExists($path)) { | ||||
|             error_log ("Unable to find " . $path); | ||||
|             return false; | ||||
|         } | ||||
|         $ref = dirname('/'.$this->meta); | ||||
|         $ref = ltrim($ref,'\\'); | ||||
|         $ref = ltrim($ref,'/'); | ||||
|         if (strlen ($ref) > 0) { | ||||
|             $path = str_replace ($ref . "/", "", $path); | ||||
|         } | ||||
|         return $this->encodeComponentName ($path); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encode the component name (to replace / and -) | ||||
|      */ | ||||
|     private function encodeComponentName ($src) { | ||||
|         return str_replace (array ("/", "-"), | ||||
|                             array ("~SLASH~", "~DASH~"), | ||||
|                             $src); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Decode the component name (to replace / and -) | ||||
|      */ | ||||
|     private function decodeComponentName ($src) { | ||||
|         return str_replace (array ("~SLASH~", "~DASH~"), | ||||
|                             array ("/", "-"), | ||||
|                             $src); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component content type | ||||
|      */ | ||||
|     public function componentContentType($comp) { | ||||
|         $comp = $this->decodeComponentName ($comp); | ||||
|         return $this->xpath->query("//opf:manifest/opf:item[@href='$comp']")->item(0)->getAttribute('media-type'); | ||||
|     } | ||||
| 
 | ||||
|     private function getNavPointDetail ($node) { | ||||
|         $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; | ||||
|         $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); | ||||
|         $src = $this->decodeComponentName ($src); | ||||
|         return array("title" => $title, "src" => $src); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the Epub content (TOC) as an array | ||||
|      * | ||||
|  | @ -180,9 +224,12 @@ class EPub { | |||
|         $contents = array(); | ||||
|         $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); | ||||
|         foreach($nodes as $node){ | ||||
|             $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; | ||||
|             $src = $this->toc_xpath->query('x:content', $node)->item(0)->attr('src'); | ||||
|             $contents[] =  array("title" => $title, "src" => $src); | ||||
|             $contents[] = $this->getNavPointDetail ($node); | ||||
| 
 | ||||
|             $insidenodes = $this->toc_xpath->query('x:navPoint', $node); | ||||
|             foreach($insidenodes as $insidenode){ | ||||
|                 $contents[] = $this->getNavPointDetail ($insidenode); | ||||
|             } | ||||
|         } | ||||
|         return $contents; | ||||
|     } | ||||
|  | @ -543,6 +590,53 @@ class EPub { | |||
|         return $nodes->item(0); | ||||
|     } | ||||
| 
 | ||||
|     public function Combine($a, $b) | ||||
|     { | ||||
|         $isAbsolute = false; | ||||
|         if ($a[0] == "/") | ||||
|             $isAbsolute = true; | ||||
| 
 | ||||
|         if ($b[0] == "/") | ||||
|             throw new InvalidArgumentException("Second path part must not start with " . $m_Separator); | ||||
| 
 | ||||
|         $splittedA = split("/", $a); | ||||
|         $splittedB = split("/", $b); | ||||
| 
 | ||||
|         $pathParts = array(); | ||||
|         $mergedPath = array_merge($splittedA, $splittedB); | ||||
| 
 | ||||
|         foreach($mergedPath as $item) | ||||
|         { | ||||
|             if ($item == null || $item == "" || $item == ".") | ||||
|                 continue; | ||||
| 
 | ||||
|             if ($item == "..") | ||||
|             { | ||||
|                 array_pop($pathParts); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             array_push($pathParts, $item); | ||||
|         } | ||||
| 
 | ||||
|         $path = implode("/", $pathParts); | ||||
|         if ($isAbsolute) | ||||
|             return("/" . $path); | ||||
|         else | ||||
|             return($path); | ||||
|     } | ||||
| 
 | ||||
|     private function getFullPath ($file, $context = NULL) { | ||||
|         $path = dirname('/'.$this->meta).'/'.$file; | ||||
|         $path = ltrim($path,'\\'); | ||||
|         $path = ltrim($path,'/'); | ||||
|         if (!empty ($context)) { | ||||
|             $path = $this->combine (dirname ($path), $context); | ||||
|         } | ||||
|         //error_log ("FullPath : $path ($file / $context)");
 | ||||
|         return $path; | ||||
|     } | ||||
| 
 | ||||
|     public function updateForKepub () { | ||||
|         $item = $this->getCoverItem (); | ||||
|         if (!is_null ($item)) { | ||||
|  |  | |||
							
								
								
									
										273
									
								
								styles/cops-monocle.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								styles/cops-monocle.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,273 @@ | |||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   background: #000; | ||||
|   -webkit-user-select: none; | ||||
|   -webkit-text-size-adjust: none; | ||||
| } | ||||
| 
 | ||||
| #components { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| #reader, #readerBg { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| #reader div pre { | ||||
|   white-space: normal; | ||||
|   font: normal 100% serif; | ||||
| } | ||||
| 
 | ||||
| /* from smallest and outermost to largest and innermost */ | ||||
| .dummyPage { | ||||
|   position: absolute; | ||||
|   left: 0; | ||||
|   top: 20px; | ||||
|   bottom: 20px; | ||||
|   right: 15px; | ||||
|   background-color: #FCF6F0; | ||||
|   -webkit-box-shadow: 2px 2px 4px #754; | ||||
|   -moz-box-shadow: 2px 2px 4px #754; | ||||
|   box-shadow: 2px 2px 4px #754; | ||||
|   -webkit-border-top-left-radius: 26px 6px; | ||||
|   -webkit-border-bottom-left-radius: 26px 6px; | ||||
|   -moz-border-top-left-radius: 26px 6px; | ||||
|   -moz-border-bottom-left-radius: 26px 6px; | ||||
|   border-top-left-radius: 26px 6px; | ||||
|   border-bottom-left-radius: 26px 6px; | ||||
| } | ||||
| .dummyPage + .dummyPage { | ||||
|   top: 16px; | ||||
|   bottom: 16px; | ||||
|   right: 16px; | ||||
|   -webkit-box-shadow: 1px 0 2px #A99; | ||||
|   -moz-box-shadow: 1px 0 2px #A99; | ||||
|   box-shadow: 1px 0 2px #A99; | ||||
| } | ||||
| .dummyPage + .dummyPage + .dummyPage { | ||||
|   top: 13px; | ||||
|   bottom: 13px; | ||||
|   right: 18px; | ||||
|   background-color: #FFF9F4; | ||||
| } | ||||
| .dummyPage + .dummyPage + .dummyPage + .dummyPage { | ||||
|   top: 10px; | ||||
|   bottom: 10px; | ||||
|   right: 21px; | ||||
| } | ||||
| .dummyPage + .dummyPage + .dummyPage + .dummyPage + .dummyPage { | ||||
|   top: 8px; | ||||
|   bottom: 8px; | ||||
|   right: 25px; | ||||
| } | ||||
| .jacket { | ||||
|   position: absolute; | ||||
|   top: 1px; | ||||
|   bottom: 1px; | ||||
|   right: 3px; | ||||
|   left: 65%; | ||||
|   -webkit-box-shadow: -3px 0 3px #311; | ||||
|   -moz-box-shadow: -3px 0 3px #311; | ||||
|   box-shadow: -3px 0 3px #311; | ||||
|   -webkit-border-top-right-radius: 3px; | ||||
|   -webkit-border-bottom-right-radius: 3px; | ||||
|   -moz-border-top-right-radius: 3px; | ||||
|   -moz-border-bottom-right-radius: 3px; | ||||
|   border-top-right-radius: 3px; | ||||
|   border-bottom-right-radius: 3px; | ||||
|   background-color: #F7F7F7; | ||||
|   background: -webkit-linear-gradient(0deg, #DDD, #FFF); | ||||
|   background: -moz-linear-gradient(0deg , #DDD, #FFF); | ||||
|   background: linear-gradient(90deg, #DDD, #FFF); | ||||
| } | ||||
| .board { | ||||
|   position: absolute; | ||||
|   top: 1px; | ||||
|   bottom: 1px; | ||||
|   width: 90%; | ||||
|   background-color: #974; | ||||
|   border: 1px solid #852; | ||||
| } | ||||
| 
 | ||||
| .runner { | ||||
|   color: #542; | ||||
|   text-transform: uppercase; | ||||
|   font-size: 82%; | ||||
| } | ||||
| 
 | ||||
| .pageNumber, .bookTitle, .chapterTitle { | ||||
|   padding: 3% 2%; | ||||
| } | ||||
| 
 | ||||
| .bookTitle, .chapterTitle { | ||||
|   position: absolute; | ||||
|   top: 1%; | ||||
|   left: 6%; | ||||
|   cursor: pointer; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .chapterTitle { | ||||
|   top: auto; | ||||
|   bottom: 1%; | ||||
|   right: 20%; | ||||
| } | ||||
| 
 | ||||
| .pageNumber { | ||||
|   position: absolute; | ||||
|   bottom: 1%; | ||||
|   right: 8%; | ||||
|   padding-right: 0; | ||||
| } | ||||
| 
 | ||||
| #readerBg { | ||||
|   background-color: #000; | ||||
| } | ||||
| 
 | ||||
| #toc ul.root { | ||||
|   position: absolute; | ||||
|   top: 50px; | ||||
|   left: 8%; | ||||
|   max-height: 75%; | ||||
|   max-width: 80%; | ||||
|   background: #E0D3C0; | ||||
|   -webkit-box-shadow: 1px 2px 2px #652; | ||||
|   -moz-box-shadow: 1px 2px 2px #652; | ||||
|   -webkit-border-radius: 10px; | ||||
|   -moz-border-radius: 10px; | ||||
|   border-radius: 10px; | ||||
|   overflow-y: auto; | ||||
|   color: #432; | ||||
|   font: 11pt Georgia, serif; | ||||
|   text-shadow: 1px 1px #EEE6D0; | ||||
|   border: 1px solid #EED; | ||||
|   z-index: 9; | ||||
| } | ||||
| 
 | ||||
| .tocArrow { | ||||
|   position: absolute; | ||||
|   top: 40px; | ||||
|   left: 16%; | ||||
|   width: 20px; | ||||
|   height: 20px; | ||||
|   -webkit-transform: rotateZ(45deg); | ||||
|   background: #E0D3C0; | ||||
|   z-index: 8; | ||||
| } | ||||
| 
 | ||||
| #toc li { | ||||
|   list-style: none; | ||||
|   line-height: 220%; | ||||
|   padding-left: 1em; | ||||
|   padding-right: 2em; | ||||
|   border-bottom: 2px groove #FFF6E9; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| #toc li span { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| #toc ul li:last-child { | ||||
|   border-bottom: none; | ||||
| } | ||||
| 
 | ||||
| #toc ul { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| #toc ul.root { | ||||
|   border-top: none; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /* Overrides to core elements */ | ||||
| 
 | ||||
| div.monelem_container { | ||||
|   background: none; | ||||
|   width: auto; | ||||
| } | ||||
| 
 | ||||
| div.monelem_page { | ||||
|   top: 6px; | ||||
|   bottom: 6px; | ||||
|   right: 4px; | ||||
|   border-color: #CBA; | ||||
|   outline: none; | ||||
|   -webkit-box-shadow: 1px 0 1px #CBA; | ||||
|   -moz-box-shadow: 1px 0 1px #CBA; | ||||
|   box-shadow: 1px 0 2px #CBA; | ||||
|   -webkit-border-top-left-radius: 26px 4px; | ||||
|   -webkit-border-bottom-left-radius: 26px 4px; | ||||
|   -moz-border-top-left-radius: 26px 4px; | ||||
|   -moz-border-bottom-left-radius: 26px 4px; | ||||
|   border-top-left-radius: 26px 4px; | ||||
|   border-bottom-left-radius: 26px 4px; | ||||
|   background-color: #FFFFFE; | ||||
|   background-image: -webkit-linear-gradient(0deg, #EDEAE8 0px, #FFFFFE 24px); | ||||
|   background-image: -moz-linear-gradient(0deg, #EDEAE8 0px, #FFFFFE 24px); | ||||
|   background-image: linear-gradient(90deg, #EDEAE8 0px, #FFFFFE 24px); | ||||
| } | ||||
| 
 | ||||
| div.monelem_sheaf { | ||||
|   left: 6%; | ||||
|   right: 8%; | ||||
|   top: 8%; | ||||
|   bottom: 8%; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Overriding magnifier button display */ | ||||
| 
 | ||||
| div.monelem_controls_magnifier_button { | ||||
|   color: #632; | ||||
|   padding: 2%; | ||||
|   top: 1%; | ||||
|   right: 6%; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Overriding table of contents display */ | ||||
| 
 | ||||
| div.monelem_controls_contents_container { | ||||
|   background: #E0D3C0; | ||||
|   border: 1px solid #EED; | ||||
|   font: 11pt Georgia, serif; | ||||
|   color: #432; | ||||
|   text-shadow: 1px 1px #FFF6E0; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_contents_chapter { | ||||
|   border-bottom: 2px groove #FFF6E9; | ||||
| } | ||||
| 
 | ||||
| li.monelem_controls_contents_chapter_active { | ||||
|   text-shadow: -1px -1px #876; | ||||
|   background: #BA9; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Overriding the scrubber display */ | ||||
| div.monelem_controls_scrubber_container { | ||||
|   left: 5.5%; | ||||
|   right: 9%; | ||||
|   bottom: 2%; | ||||
|   background: #FFFEFC; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_track { | ||||
|   border-color: #432; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_needle { | ||||
|   border-color: #432; | ||||
|   background: #E0D3C0; | ||||
| } | ||||
| 
 | ||||
| div.monelem_controls_scrubber_trail { | ||||
|   background: #E0D3C0; | ||||
| } | ||||
							
								
								
									
										177
									
								
								styles/cops-monocle.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								styles/cops-monocle.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,177 @@ | |||
| Monocle.DEBUG = true; | ||||
| 
 | ||||
| (function () { | ||||
| 
 | ||||
|   Monocle.Styles.container.right = "24px"; | ||||
| 
 | ||||
|   // Initialize the reader element.
 | ||||
|   Monocle.Events.listen( | ||||
|     window, | ||||
|     'load', | ||||
|     function () { | ||||
|       var readerOptions = {}; | ||||
| 
 | ||||
|       /* PLACE SAVER */ | ||||
|       var bkTitle = bookData.getMetaData('title'); | ||||
|       var placeSaver = new Monocle.Controls.PlaceSaver(bkTitle); | ||||
|       readerOptions.place = placeSaver.savedPlace(); | ||||
|       readerOptions.panels = Monocle.Panels.Marginal; | ||||
|       readerOptions.stylesheet = "body { " + | ||||
|         "color: #210;" + | ||||
|         "font-family: Palatino, Georgia, serif;" + | ||||
|       "}"; | ||||
| 
 | ||||
|       /* Initialize the reader */ | ||||
|       window.reader = Monocle.Reader( | ||||
|         'reader', | ||||
|         bookData, | ||||
|         readerOptions, | ||||
|         function(reader) { | ||||
|           reader.addControl(placeSaver, 'invisible'); | ||||
| 
 | ||||
|           /* SPINNER */ | ||||
|           var spinner = Monocle.Controls.Spinner(reader); | ||||
|           reader.addControl(spinner, 'page', { hidden: true }); | ||||
|           spinner.listenForUsualDelays('reader'); | ||||
| 
 | ||||
|           /* Because the 'reader' element changes size on window resize, | ||||
|            * we should notify it of this event. */ | ||||
|           Monocle.Events.listen( | ||||
|             window, | ||||
|             'resize', | ||||
|             function () { window.reader.resized() } | ||||
|           ); | ||||
|            | ||||
|           Monocle.Events.listen(window.top.document, 'keyup', function(evt) { | ||||
|             var eventCharCode = evt.charCode || evt.keyCode; | ||||
|             var dir = null; | ||||
|             var flipper = reader.Flipper; | ||||
|             if (eventCharCode == 33 || eventCharCode == 37) { // Page down or Left arrow
 | ||||
|               dir = -1; | ||||
|             } else if (eventCharCode == 34 || eventCharCode == 39 ) { // Page down or Right arrow
 | ||||
|               dir = 1; | ||||
|             } | ||||
|             if (dir) { | ||||
|               reader.moveTo({ direction: dir }); | ||||
|               evt.preventDefault(); | ||||
|             } | ||||
|           }); | ||||
| 
 | ||||
|           /* MAGNIFIER CONTROL */ | ||||
|           var magnifier = new Monocle.Controls.Magnifier(reader); | ||||
|           reader.addControl(magnifier, 'page'); | ||||
| 
 | ||||
|           /* The stencil activates internal links */ | ||||
|           var stencil = new Monocle.Controls.Stencil(reader); | ||||
|           reader.addControl(stencil); | ||||
|           //stencil.toggleHighlights();
 | ||||
| 
 | ||||
|           /* BOOK TITLE RUNNING HEAD */ | ||||
|           var bookTitle = {} | ||||
|           bookTitle.contentsMenu = Monocle.Controls.Contents(reader); | ||||
|           reader.addControl(bookTitle.contentsMenu, 'popover', { hidden: true }); | ||||
|           bookTitle.createControlElements = function () { | ||||
|             var cntr = document.createElement('div'); | ||||
|             cntr.className = "bookTitle"; | ||||
|             var runner = document.createElement('div'); | ||||
|             runner.className = "runner"; | ||||
|             runner.innerHTML = reader.getBook().getMetaData('title'); | ||||
|             cntr.appendChild(runner); | ||||
| 
 | ||||
|             Monocle.Events.listenForContact( | ||||
|               cntr, | ||||
|               { | ||||
|                 start: function (evt) { | ||||
|                   if (evt.preventDefault) { | ||||
|                     evt.stopPropagation(); | ||||
|                     evt.preventDefault(); | ||||
|                   } else { | ||||
|                     evt.returnValue = false; | ||||
|                   } | ||||
|                   reader.showControl(bookTitle.contentsMenu); | ||||
|                 } | ||||
|               } | ||||
|             ); | ||||
| 
 | ||||
|             return cntr; | ||||
|           } | ||||
|           reader.addControl(bookTitle, 'page'); | ||||
| 
 | ||||
| 
 | ||||
|           /* CHAPTER TITLE RUNNING HEAD */ | ||||
|           var chapterTitle = { | ||||
|             runners: [], | ||||
|             createControlElements: function (page) { | ||||
|               var cntr = document.createElement('div'); | ||||
|               cntr.className = "chapterTitle"; | ||||
|               var runner = document.createElement('div'); | ||||
|               runner.className = "runner"; | ||||
|               cntr.appendChild(runner); | ||||
|               this.runners.push(runner); | ||||
|               this.update(page); | ||||
|               return cntr; | ||||
|             }, | ||||
|             update: function (page) { | ||||
|               var place = reader.getPlace(page); | ||||
|               if (place) { | ||||
|                 this.runners[page.m.pageIndex].innerHTML = place.chapterTitle(); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           reader.addControl(chapterTitle, 'page'); | ||||
|           reader.listen( | ||||
|             'monocle:pagechange', | ||||
|             function (evt) { chapterTitle.update(evt.m.page); } | ||||
|           ); | ||||
| 
 | ||||
| 
 | ||||
|           /* PAGE NUMBER RUNNING HEAD */ | ||||
|           var pageNumber = { | ||||
|             runners: [], | ||||
|             createControlElements: function (page) { | ||||
|               var cntr = document.createElement('div'); | ||||
|               cntr.className = "pageNumber"; | ||||
|               var runner = document.createElement('div'); | ||||
|               runner.className = "runner"; | ||||
|               cntr.appendChild(runner); | ||||
|               this.runners.push(runner); | ||||
|               this.update(page, page.m.place.pageNumber()); | ||||
|               return cntr; | ||||
|             }, | ||||
|             update: function (page, pageNumber) { | ||||
|               if (pageNumber) { | ||||
|                 this.runners[page.m.pageIndex].innerHTML = pageNumber; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           reader.addControl(pageNumber, 'page'); | ||||
|           reader.listen( | ||||
|             'monocle:pagechange', | ||||
|             function (evt) { | ||||
|               pageNumber.update(evt.m.page, evt.m.pageNumber); | ||||
|             } | ||||
|           ); | ||||
| 
 | ||||
|           /* Scrubber */ | ||||
|           var scrubber = new Monocle.Controls.Scrubber(reader); | ||||
|           reader.addControl(scrubber, 'popover', { hidden: true }); | ||||
|           var showFn = function (evt) { | ||||
|             evt.stopPropagation(); | ||||
|             reader.showControl(scrubber); | ||||
|             scrubber.updateNeedles(); | ||||
|           } | ||||
|           for (var i = 0; i < chapterTitle.runners.length; ++i) { | ||||
|             Monocle.Events.listenForContact( | ||||
|               chapterTitle.runners[i].parentNode, | ||||
|               { start: showFn } | ||||
|             ); | ||||
|             Monocle.Events.listenForContact( | ||||
|               pageNumber.runners[i].parentNode, | ||||
|               { start: showFn } | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|       ); | ||||
|     } | ||||
|   ); | ||||
| })(); | ||||
|  | @ -12,6 +12,9 @@ | |||
|         {{? data.mail == 1}} | ||||
|         <a id="mailButton" title="Mail" href="empty.php" onclick="sendToMailAddress (this, {{=data.id}}); return false;"><i class="icon-envelope icon-large"></i></a> | ||||
|         {{?}} | ||||
|         {{? data.readerUrl != ""}} | ||||
|         <a title="Reader" href="{{=data.readerUrl}}" target="blank"><i class="icon-eye-open icon-large"></i></a> | ||||
|         {{?}} | ||||
|         <br /> | ||||
|         {{~}} | ||||
|     </h2> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue