monocle-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/lib/monocle/rails.rb +8 -0
  8. data/lib/monocle/rails/version.rb +5 -0
  9. data/monocle-rails.gemspec +23 -0
  10. data/vendor/.DS_Store +0 -0
  11. data/vendor/assets/.DS_Store +0 -0
  12. data/vendor/assets/javascripts/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/compat/browser.js +120 -0
  14. data/vendor/assets/javascripts/compat/css.js +145 -0
  15. data/vendor/assets/javascripts/compat/env.js +463 -0
  16. data/vendor/assets/javascripts/compat/gala.js +469 -0
  17. data/vendor/assets/javascripts/compat/stubs.js +50 -0
  18. data/vendor/assets/javascripts/controls/contents.js +59 -0
  19. data/vendor/assets/javascripts/controls/magnifier.js +51 -0
  20. data/vendor/assets/javascripts/controls/panel.js +136 -0
  21. data/vendor/assets/javascripts/controls/placesaver.js +100 -0
  22. data/vendor/assets/javascripts/controls/scrubber.js +140 -0
  23. data/vendor/assets/javascripts/controls/spinner.js +99 -0
  24. data/vendor/assets/javascripts/controls/stencil.js +410 -0
  25. data/vendor/assets/javascripts/core/billboard.js +120 -0
  26. data/vendor/assets/javascripts/core/book.js +467 -0
  27. data/vendor/assets/javascripts/core/bookdata.js +59 -0
  28. data/vendor/assets/javascripts/core/component.js +413 -0
  29. data/vendor/assets/javascripts/core/events.js +56 -0
  30. data/vendor/assets/javascripts/core/factory.js +194 -0
  31. data/vendor/assets/javascripts/core/formatting.js +317 -0
  32. data/vendor/assets/javascripts/core/monocle.js +16 -0
  33. data/vendor/assets/javascripts/core/place.js +210 -0
  34. data/vendor/assets/javascripts/core/reader.js +683 -0
  35. data/vendor/assets/javascripts/core/selection.js +158 -0
  36. data/vendor/assets/javascripts/core/styles.js +155 -0
  37. data/vendor/assets/javascripts/dimensions/columns.js +218 -0
  38. data/vendor/assets/javascripts/flippers/instant.js +78 -0
  39. data/vendor/assets/javascripts/flippers/scroller.js +128 -0
  40. data/vendor/assets/javascripts/flippers/slider.js +469 -0
  41. data/vendor/assets/javascripts/monocore.js +27 -0
  42. data/vendor/assets/javascripts/monoctrl.js +1 -0
  43. data/vendor/assets/javascripts/panels/eink.js +61 -0
  44. data/vendor/assets/javascripts/panels/imode.js +180 -0
  45. data/vendor/assets/javascripts/panels/magic.js +297 -0
  46. data/vendor/assets/javascripts/panels/marginal.js +50 -0
  47. data/vendor/assets/javascripts/panels/twopane.js +34 -0
  48. data/vendor/assets/stylesheets/monocore.css +194 -0
  49. data/vendor/assets/stylesheets/monoctrl.css +168 -0
  50. metadata +129 -0
@@ -0,0 +1,16 @@
1
+ /*!
2
+ * Monocle - A silky, tactile browser-based ebook JavaScript library.
3
+ *
4
+ * Copyright 2012, Joseph Pearson
5
+ * Licensed under the MIT license.
6
+ */
7
+
8
+ Monocle = {
9
+ VERSION: "3.2.0"
10
+ };
11
+
12
+
13
+ Monocle.Dimensions = {};
14
+ Monocle.Controls = {};
15
+ Monocle.Flippers = {};
16
+ Monocle.Panels = {};
@@ -0,0 +1,210 @@
1
+ // PLACE
2
+
3
+ Monocle.Place = function () {
4
+
5
+ var API = { constructor: Monocle.Place }
6
+ var k = API.constants = API.constructor;
7
+ var p = API.properties = {
8
+ component: null,
9
+ percent: null
10
+ }
11
+
12
+
13
+ function setPlace(cmpt, pageN) {
14
+ p.component = cmpt;
15
+ p.percent = pageN / cmpt.lastPageNumber();
16
+ p.chapter = null;
17
+ }
18
+
19
+
20
+ function setPercentageThrough(cmpt, percent) {
21
+ p.component = cmpt;
22
+ p.percent = percent;
23
+ p.chapter = null;
24
+ }
25
+
26
+
27
+ function componentId() {
28
+ return p.component.properties.id;
29
+ }
30
+
31
+
32
+ // How far we are through the component at the "top of the page".
33
+ //
34
+ // 0 - start of book. 1.0 - end of book.
35
+ //
36
+ function percentAtTopOfPage() {
37
+ return p.percent - pageSizePercentage();
38
+ }
39
+
40
+
41
+ // How far we are through the component at the "bottom of the page".
42
+ //
43
+ function percentAtBottomOfPage() {
44
+ return p.percent;
45
+ }
46
+
47
+
48
+ function pageSizePercentage() {
49
+ return 1.0 / p.component.lastPageNumber();
50
+ }
51
+
52
+
53
+ // The page number at a given point (0: start, 1: end) within the component.
54
+ //
55
+ function pageAtPercentageThrough(percent) {
56
+ var pages = pagesInComponent();
57
+ if (typeof percent != 'number') { percent = 0; }
58
+ // We round after 4 decimal places because 25*0.8 = 7.000000000000001.
59
+ return Math.max(Math.ceil(Math.round(pages * percent * 1000) / 1000), 1);
60
+ }
61
+
62
+
63
+ // The page number of this point within the component.
64
+ //
65
+ function pageNumber() {
66
+ return pageAtPercentageThrough(p.percent);
67
+ }
68
+
69
+
70
+ function pagesInComponent() {
71
+ return p.component.lastPageNumber();
72
+ }
73
+
74
+
75
+ function chapterInfo() {
76
+ if (p.chapter) {
77
+ return p.chapter;
78
+ }
79
+ return p.chapter = p.component.chapterForPage(pageNumber()+1);
80
+ }
81
+
82
+
83
+ function chapterTitle() {
84
+ var chp = chapterInfo();
85
+ return chp ? chp.title : null;
86
+ }
87
+
88
+
89
+ function chapterSrc() {
90
+ var src = componentId();
91
+ var cinfo = chapterInfo();
92
+ if (cinfo && cinfo.fragment) {
93
+ src += "#" + cinfo.fragment;
94
+ }
95
+ return src;
96
+ }
97
+
98
+
99
+ function getLocus(options) {
100
+ options = options || {};
101
+ var locus = {
102
+ page: pageNumber(),
103
+ componentId: componentId()
104
+ }
105
+ if (options.direction) {
106
+ locus.page += options.direction;
107
+ } else {
108
+ locus.percent = percentAtBottomOfPage();
109
+ }
110
+ return locus;
111
+ }
112
+
113
+
114
+ // Returns how far this place is in the entire book (0 - start, 1.0 - end).
115
+ //
116
+ function percentageOfBook() {
117
+ var book = p.component.properties.book;
118
+ var componentIds = book.properties.componentIds;
119
+ var weights = book.componentWeights();
120
+ var cmptIndex = p.component.properties.index;
121
+ var pc = weights[cmptIndex] * p.percent;
122
+ for (var i = 0, ii = cmptIndex; i < ii; ++i) { pc += weights[i]; }
123
+
124
+ // Note: This is a decent estimation of current page number and total
125
+ // number of pages, but it's very approximate. Could be improved by storing
126
+ // the page counts of all components accessed (since the dimensions of the
127
+ // reader last changed), and averaging the result across them. (You
128
+ // probably want to ignore calcs for components < 2 or 3 pages long, too.
129
+ // The bigger the component, the more accurate the calculation.)
130
+ //
131
+ // var bkPages = p.component.lastPageNumber() / weights[cmptIndex];
132
+ // console.log('Page: '+ Math.floor(pc*bkPages)+ ' of '+ Math.floor(bkPages));
133
+
134
+ return pc;
135
+ }
136
+
137
+
138
+ function onFirstPageOfBook() {
139
+ return p.component.properties.index === 0 && pageNumber() === 1;
140
+ }
141
+
142
+
143
+ function onLastPageOfBook() {
144
+ return (
145
+ p.component.properties.index ==
146
+ p.component.properties.book.properties.lastCIndex &&
147
+ pageNumber() == p.component.lastPageNumber()
148
+ );
149
+ }
150
+
151
+
152
+ API.setPlace = setPlace;
153
+ API.setPercentageThrough = setPercentageThrough;
154
+ API.componentId = componentId;
155
+ API.percentAtTopOfPage = percentAtTopOfPage;
156
+ API.percentAtBottomOfPage = percentAtBottomOfPage;
157
+ API.percentageThrough = percentAtBottomOfPage;
158
+ API.pageSizePercentage = pageSizePercentage;
159
+ API.pageAtPercentageThrough = pageAtPercentageThrough;
160
+ API.pageNumber = pageNumber;
161
+ API.pagesInComponent = pagesInComponent;
162
+ API.chapterInfo = chapterInfo;
163
+ API.chapterTitle = chapterTitle;
164
+ API.chapterSrc = chapterSrc;
165
+ API.getLocus = getLocus;
166
+ API.percentageOfBook = percentageOfBook;
167
+ API.onFirstPageOfBook = onFirstPageOfBook;
168
+ API.onLastPageOfBook = onLastPageOfBook;
169
+
170
+ return API;
171
+ }
172
+
173
+
174
+ Monocle.Place.FromPageNumber = function (component, pageNumber) {
175
+ var place = new Monocle.Place();
176
+ place.setPlace(component, pageNumber);
177
+ return place;
178
+ }
179
+
180
+
181
+ Monocle.Place.FromPercentageThrough = function (component, percent) {
182
+ var place = new Monocle.Place();
183
+ place.setPercentageThrough(component, percent);
184
+ return place;
185
+ }
186
+
187
+
188
+ // We can't create a place from a percentage of the book, because the
189
+ // component may not have been loaded yet. But we can get a locus.
190
+ //
191
+ Monocle.Place.percentOfBookToLocus = function (reader, percent) {
192
+ var book = reader.getBook();
193
+ var componentIds = book.properties.componentIds;
194
+ var weights = book.componentWeights();
195
+ var cmptIndex = 0, cmptWeight = 0;
196
+ percent = Math.min(percent, 0.99999);
197
+ while (percent >= 0) {
198
+ cmptWeight = weights[cmptIndex];
199
+ percent -= weights[cmptIndex];
200
+ if (percent >= 0) {
201
+ cmptIndex += 1;
202
+ if (cmptIndex >= weights.length) {
203
+ console.error('Unable to calculate locus from percentage: '+percent);
204
+ return;
205
+ }
206
+ }
207
+ }
208
+ var cmptPercent = (percent + cmptWeight) / cmptWeight;
209
+ return { componentId: componentIds[cmptIndex], percent: cmptPercent }
210
+ }
@@ -0,0 +1,683 @@
1
+ // READER
2
+ //
3
+ //
4
+ // The full DOM hierarchy created by Reader is:
5
+ //
6
+ // box
7
+ // -> container
8
+ // -> pages (the number of page elements is determined by the flipper)
9
+ // -> sheaf (basically just sets the margins)
10
+ // -> component (an iframe created by the current component)
11
+ // -> body (the document.body of the iframe)
12
+ // -> page controls
13
+ // -> standard controls
14
+ // -> overlay
15
+ // -> modal/popover controls
16
+ //
17
+ //
18
+ // Options:
19
+ //
20
+ // flipper: The class of page flipper to use.
21
+ //
22
+ // panels: The class of panels to use
23
+ //
24
+ // stylesheet: A string of CSS rules to apply to the contents of each
25
+ // component loaded into the reader.
26
+ //
27
+ // fontScale: a float to multiply against the default font-size of each
28
+ // element in each component.
29
+ //
30
+ // place: A book locus for the page to open to when the reader is
31
+ // initialized. (See comments at Book#pageNumberAt for more about
32
+ // the locus option).
33
+ //
34
+ // systemId: the id for root elements of components, defaults to "RS:monocle"
35
+ //
36
+ Monocle.Reader = function (node, bookData, options, onLoadCallback) {
37
+
38
+ var API = { constructor: Monocle.Reader }
39
+ var k = API.constants = API.constructor;
40
+ var p = API.properties = {
41
+ // Initialization-completed flag.
42
+ initialized: false,
43
+
44
+ // The active book.
45
+ book: null,
46
+
47
+ // DOM graph of factory-generated objects.
48
+ graph: {},
49
+
50
+ // Id applied to the HTML element of each component, can be used to scope
51
+ // CSS rules.
52
+ systemId: (options ? options.systemId : null) || k.DEFAULT_SYSTEM_ID,
53
+
54
+ // Prefix for classnames for any created element.
55
+ classPrefix: k.DEFAULT_CLASS_PREFIX,
56
+
57
+ // Registered control objects (see addControl). Hashes of the form:
58
+ // {
59
+ // control: <control instance>,
60
+ // elements: <array of topmost elements created by control>,
61
+ // controlType: <standard, page, modal, popover, invisible, etc>
62
+ // }
63
+ controls: [],
64
+
65
+ // After the reader has been resized, this resettable timer must expire
66
+ // the place is restored.
67
+ resizeTimer: null
68
+ }
69
+
70
+ var dom;
71
+
72
+
73
+ // Inspects the browser environment and kicks off preparing the container.
74
+ //
75
+ function initialize() {
76
+ options = options || {}
77
+
78
+ Monocle.Browser.survey(prepareBox);
79
+ }
80
+
81
+
82
+ // Sets up the container and internal elements.
83
+ //
84
+ function prepareBox() {
85
+ var box = node;
86
+ if (typeof box == "string") { box = document.getElementById(box); }
87
+ dom = API.dom = box.dom = new Monocle.Factory(box, 'box', 0, API);
88
+
89
+ API.billboard = new Monocle.Billboard(API);
90
+
91
+ if (!Monocle.Browser.env.isCompatible()) {
92
+ if (dispatchEvent("monocle:incompatible", {}, true)) {
93
+ fatalSystemMessage(k.COMPATIBILITY_INFO);
94
+ }
95
+ return;
96
+ }
97
+
98
+ dispatchEvent("monocle:initializing", API);
99
+
100
+ bookData = bookData || Monocle.bookDataFromNodes([box.cloneNode(true)]);
101
+ var bk = new Monocle.Book(bookData, options.preloadWindow || 1);
102
+
103
+ box.innerHTML = "";
104
+
105
+ // Make sure the box div is absolutely or relatively positioned.
106
+ positionBox();
107
+
108
+ // Attach the page-flipping gadget.
109
+ attachFlipper(options.flipper);
110
+
111
+ // Create the essential DOM elements.
112
+ createReaderElements();
113
+
114
+ // Create the selection object.
115
+ API.selection = new Monocle.Selection(API);
116
+
117
+ // Create the formatting object.
118
+ API.formatting = new Monocle.Formatting(
119
+ API,
120
+ options.stylesheet,
121
+ options.fontScale
122
+ );
123
+
124
+ listen('monocle:turn', onPageTurn);
125
+
126
+ primeFrames(options.primeURL, function () {
127
+ // Make the reader elements look pretty.
128
+ applyStyles();
129
+
130
+ p.flipper.listenForInteraction(options.panels);
131
+
132
+ setBook(bk, options.place, function () {
133
+ if (onLoadCallback) { onLoadCallback(API); }
134
+ dispatchEvent("monocle:loaded", API);
135
+ });
136
+ });
137
+ }
138
+
139
+
140
+ function positionBox() {
141
+ var currPosVal;
142
+ var box = dom.find('box');
143
+ if (document.defaultView) {
144
+ var currStyle = document.defaultView.getComputedStyle(box, null);
145
+ currPosVal = currStyle.getPropertyValue('position');
146
+ } else if (box.currentStyle) {
147
+ currPosVal = box.currentStyle.position
148
+ }
149
+ if (["absolute", "relative"].indexOf(currPosVal) == -1) {
150
+ box.style.position = "relative";
151
+ }
152
+ }
153
+
154
+
155
+ function attachFlipper(flipperClass) {
156
+ if (!flipperClass) {
157
+ if (Monocle.Browser.renders.slow) {
158
+ flipperClass = Monocle.Flippers.Instant;
159
+ } else {
160
+ flipperClass = Monocle.Flippers.Slider;
161
+ }
162
+ }
163
+
164
+ p.flipper = new flipperClass(API, null, p.readerOptions);
165
+ }
166
+
167
+
168
+ function createReaderElements() {
169
+ var cntr = dom.append('div', 'container');
170
+ for (var i = 0; i < p.flipper.pageCount; ++i) {
171
+ var page = cntr.dom.append('div', 'page', i);
172
+ page.style.visibility = "hidden";
173
+ page.m = { reader: API, pageIndex: i, place: null }
174
+ page.m.sheafDiv = page.dom.append('div', 'sheaf', i);
175
+ page.m.activeFrame = page.m.sheafDiv.dom.append('iframe', 'component', i);
176
+ page.m.activeFrame.m = { 'pageDiv': page };
177
+ page.m.activeFrame.setAttribute('frameBorder', 0);
178
+ page.m.activeFrame.setAttribute('scrolling', 'no');
179
+ p.flipper.addPage(page);
180
+ }
181
+ dom.append('div', 'overlay');
182
+ dispatchEvent("monocle:loading", API);
183
+ }
184
+
185
+
186
+ // Opens the frame to a particular URL (usually 'about:blank').
187
+ //
188
+ function primeFrames(url, callback) {
189
+ url = url || (Monocle.Browser.on.UIWebView ? "blank.html" : "about:blank");
190
+
191
+ var pageCount = 0;
192
+
193
+ var cb = function (evt) {
194
+ var frame = evt.target || evt.srcElement;
195
+ Monocle.Events.deafen(frame, 'load', cb);
196
+ dispatchEvent(
197
+ 'monocle:frameprimed',
198
+ { frame: frame, pageIndex: pageCount }
199
+ );
200
+ if ((pageCount += 1) == p.flipper.pageCount) {
201
+ Monocle.defer(callback);
202
+ }
203
+ }
204
+
205
+ forEachPage(function (page) {
206
+ Monocle.Events.listen(page.m.activeFrame, 'load', cb);
207
+ page.m.activeFrame.src = url;
208
+ });
209
+ }
210
+
211
+
212
+ function applyStyles() {
213
+ dom.find('container').dom.setStyles(Monocle.Styles.container);
214
+ forEachPage(function (page, i) {
215
+ page.dom.setStyles(Monocle.Styles.page);
216
+ dom.find('sheaf', i).dom.setStyles(Monocle.Styles.sheaf);
217
+ var cmpt = dom.find('component', i)
218
+ cmpt.dom.setStyles(Monocle.Styles.component);
219
+ });
220
+ lockFrameWidths();
221
+ dom.find('overlay').dom.setStyles(Monocle.Styles.overlay);
222
+ dispatchEvent('monocle:styles');
223
+ }
224
+
225
+
226
+ function lockingFrameWidths() {
227
+ if (!Monocle.Browser.env.relativeIframeExpands) { return; }
228
+ for (var i = 0, cmpt; cmpt = dom.find('component', i); ++i) {
229
+ cmpt.style.display = "none";
230
+ }
231
+ }
232
+
233
+
234
+ function lockFrameWidths() {
235
+ if (!Monocle.Browser.env.relativeIframeExpands) { return; }
236
+ for (var i = 0, cmpt; cmpt = dom.find('component', i); ++i) {
237
+ cmpt.style.width = cmpt.parentNode.offsetWidth+"px";
238
+ cmpt.style.display = "block";
239
+ }
240
+ }
241
+
242
+
243
+ // Apply the book, move to a particular place or just the first page, wait
244
+ // for everything to complete, then fire the callback.
245
+ //
246
+ function setBook(bk, place, callback) {
247
+ p.book = bk;
248
+ var pageCount = 0;
249
+ if (typeof callback == 'function') {
250
+ var watchers = {
251
+ 'monocle:componentchange': function (evt) {
252
+ dispatchEvent('monocle:firstcomponentchange', evt.m);
253
+ return (pageCount += 1) == p.flipper.pageCount;
254
+ },
255
+ 'monocle:componentfailed': function (evt) {
256
+ fatalSystemMessage(k.LOAD_FAILURE_INFO);
257
+ return true;
258
+ },
259
+ 'monocle:turn': function (evt) {
260
+ deafen('monocle:componentfailed', listener);
261
+ callback();
262
+ return true;
263
+ }
264
+ }
265
+ var listener = function (evt) {
266
+ if (watchers[evt.type](evt)) { deafen(evt.type, listener); }
267
+ }
268
+ for (var evtType in watchers) { listen(evtType, listener) }
269
+ }
270
+ p.flipper.moveTo(place || { page: 1 }, initialized);
271
+ }
272
+
273
+
274
+ function getBook() {
275
+ return p.book;
276
+ }
277
+
278
+
279
+ function initialized() {
280
+ p.initialized = true;
281
+ }
282
+
283
+
284
+ // Attempts to restore the place we were up to in the book before the
285
+ // reader was resized.
286
+ //
287
+ // The delay ensures that if we get multiple calls to this function in
288
+ // a short period, we don't do lots of expensive recalculations.
289
+ //
290
+ function resized() {
291
+ if (!p.initialized) {
292
+ console.warn('Attempt to resize book before initialization.');
293
+ }
294
+ lockingFrameWidths();
295
+ if (!dispatchEvent("monocle:resizing", {}, true)) {
296
+ return;
297
+ }
298
+ clearTimeout(p.resizeTimer);
299
+ p.resizeTimer = Monocle.defer(performResize, k.RESIZE_DELAY);
300
+ }
301
+
302
+
303
+ function performResize() {
304
+ lockFrameWidths();
305
+ recalculateDimensions(true, afterResized);
306
+ }
307
+
308
+
309
+ function afterResized() {
310
+ dispatchEvent('monocle:resize');
311
+ }
312
+
313
+
314
+ function recalculateDimensions(andRestorePlace, callback) {
315
+ if (!p.book) { return; }
316
+ if (p.onRecalculate) {
317
+ var oldFn = p.onRecalculate;
318
+ p.onRecalculate = function () {
319
+ oldFn();
320
+ if (typeof callback == 'function') { callback(); }
321
+ }
322
+ return;
323
+ }
324
+
325
+ dispatchEvent("monocle:recalculating");
326
+ p.onRecalculate = function () {
327
+ if (typeof callback == 'function') { callback(); }
328
+ p.onRecalculate = null;
329
+ dispatchEvent("monocle:recalculated");
330
+ }
331
+
332
+ var onComplete = function () { Monocle.defer(p.onRecalculate); }
333
+ var onInitiate = onComplete;
334
+ if (andRestorePlace !== false && p.lastLocus) {
335
+ onInitiate = function () {
336
+ p.flipper.moveTo(p.lastLocus, onComplete, false);
337
+ }
338
+ }
339
+
340
+ forEachPage(function (pageDiv) {
341
+ pageDiv.m.activeFrame.m.component.updateDimensions(pageDiv);
342
+ });
343
+
344
+ Monocle.defer(onInitiate);
345
+ }
346
+
347
+
348
+ function onPageTurn(evt) {
349
+ if (p.onRecalculate) {
350
+ } else {
351
+ var place = getPlace();
352
+ p.lastLocus = {
353
+ componentId: place.componentId(),
354
+ percent: place.percentageThrough()
355
+ }
356
+ dispatchEvent('monocle:position', { place: place });
357
+ }
358
+ }
359
+
360
+
361
+ // Returns the current page number in the book.
362
+ //
363
+ // The pageDiv argument is optional - typically defaults to whatever the
364
+ // flipper thinks is the "active" page.
365
+ //
366
+ function pageNumber(pageDiv) {
367
+ var place = getPlace(pageDiv);
368
+ return place ? (place.pageNumber() || 1) : 1;
369
+ }
370
+
371
+
372
+ // Returns the current "place" in the book -- ie, the page number, chapter
373
+ // title, etc.
374
+ //
375
+ // The pageDiv argument is optional - typically defaults to whatever the
376
+ // flipper thinks is the "active" page.
377
+ //
378
+ function getPlace(pageDiv) {
379
+ if (!p.initialized) {
380
+ console.warn('Attempt to access place before initialization.');
381
+ }
382
+ return p.flipper.getPlace(pageDiv);
383
+ }
384
+
385
+
386
+ // Moves the current page as specified by the locus. See
387
+ // Monocle.Book#pageNumberAt for documentation on the locus argument.
388
+ //
389
+ // The callback argument is optional.
390
+ //
391
+ function moveTo(locus, callback) {
392
+ if (!p.initialized) {
393
+ console.warn('Attempt to move place before initialization.');
394
+ }
395
+ if (!p.book.isValidLocus(locus)) {
396
+ dispatchEvent(
397
+ "monocle:notfound",
398
+ { href: locus ? locus.componentId : "anonymous" }
399
+ );
400
+ return false;
401
+ }
402
+ var fn = callback;
403
+ if (!locus.direction) {
404
+ dispatchEvent('monocle:turning');
405
+ dispatchEvent('monocle:jumping', { locus: locus });
406
+ fn = function () {
407
+ dispatchEvent('monocle:jump', { locus: locus });
408
+ if (callback) { callback(); }
409
+ }
410
+ }
411
+ p.flipper.moveTo(locus, fn);
412
+ return true;
413
+ }
414
+
415
+
416
+ // Moves to the relevant element in the relevant component.
417
+ //
418
+ function skipToChapter(src) {
419
+ var locus = p.book.locusOfChapter(src);
420
+ return moveTo(locus);
421
+ }
422
+
423
+
424
+ // Valid types:
425
+ // - standard (an overlay above the pages)
426
+ // - page (within the page)
427
+ // - modal (overlay where click-away does nothing, for a single control)
428
+ // - hud (overlay that multiple controls can share)
429
+ // - popover (overlay where click-away removes the ctrl elements)
430
+ // - invisible
431
+ //
432
+ // Options:
433
+ // - hidden -- creates and hides the ctrl elements;
434
+ // use showControl to show them
435
+ // - container -- specify an existing DOM element to contain the control.
436
+ //
437
+ function addControl(ctrl, cType, options) {
438
+ for (var i = 0; i < p.controls.length; ++i) {
439
+ if (p.controls[i].control == ctrl) {
440
+ console.warn("Already added control: %o", ctrl);
441
+ return;
442
+ }
443
+ }
444
+
445
+ options = options || {};
446
+
447
+ var ctrlData = { control: ctrl, elements: [], controlType: cType }
448
+ p.controls.push(ctrlData);
449
+
450
+ var addControlTo = function (cntr) {
451
+ if (cntr == 'container') {
452
+ cntr = options.container || dom.find('container');
453
+ if (typeof cntr == 'string') { cntr = document.getElementById(cntr); }
454
+ if (!cntr.dom) { dom.claim(cntr, 'controlContainer'); }
455
+ } else if (cntr == 'overlay') {
456
+ cntr = dom.find('overlay');
457
+ }
458
+ if (typeof ctrl.createControlElements != 'function') { return; }
459
+ var ctrlElem = ctrl.createControlElements(cntr);
460
+ if (!ctrlElem) { return; }
461
+ cntr.appendChild(ctrlElem);
462
+ ctrlData.elements.push(ctrlElem);
463
+ Monocle.Styles.applyRules(ctrlElem, Monocle.Styles.control);
464
+ return ctrlElem;
465
+ }
466
+
467
+ if (!cType || cType == 'standard' || cType == 'invisible') {
468
+ addControlTo('container');
469
+ } else if (cType == 'page') {
470
+ forEachPage(addControlTo);
471
+ } else if (cType == 'modal' || cType == 'popover' || cType == 'hud') {
472
+ addControlTo('overlay');
473
+ ctrlData.usesOverlay = true;
474
+ } else if (cType == 'invisible') {
475
+ addControlTo('container');
476
+ } else {
477
+ console.warn('Unknown control type: ' + cType);
478
+ }
479
+
480
+ if (options.hidden) {
481
+ hideControl(ctrl);
482
+ } else {
483
+ showControl(ctrl);
484
+ }
485
+
486
+ if (typeof ctrl.assignToReader == 'function') {
487
+ ctrl.assignToReader(API);
488
+ }
489
+
490
+ return ctrl;
491
+ }
492
+
493
+
494
+ function dataForControl(ctrl) {
495
+ for (var i = 0; i < p.controls.length; ++i) {
496
+ if (p.controls[i].control == ctrl) {
497
+ return p.controls[i];
498
+ }
499
+ }
500
+ }
501
+
502
+
503
+ function hideControl(ctrl) {
504
+ var controlData = dataForControl(ctrl);
505
+ if (!controlData) {
506
+ console.warn("No data for control: " + ctrl);
507
+ return;
508
+ }
509
+ if (controlData.hidden) {
510
+ return;
511
+ }
512
+ for (var i = 0; i < controlData.elements.length; ++i) {
513
+ controlData.elements[i].style.display = "none";
514
+ }
515
+ if (controlData.usesOverlay) {
516
+ var overlay = dom.find('overlay');
517
+ overlay.style.display = "none";
518
+ Monocle.Events.deafenForContact(overlay, overlay.listeners);
519
+ if (controlData.controlType != 'hud') {
520
+ dispatchEvent('monocle:modal:off');
521
+ }
522
+ }
523
+ controlData.hidden = true;
524
+ if (ctrl.properties) {
525
+ ctrl.properties.hidden = true;
526
+ }
527
+ dispatchEvent('monocle:controlhide', { control: ctrl }, false);
528
+ }
529
+
530
+
531
+ function showControl(ctrl) {
532
+ var controlData = dataForControl(ctrl);
533
+ if (!controlData) {
534
+ console.warn("No data for control: " + ctrl);
535
+ return false;
536
+ }
537
+
538
+ if (showingControl(ctrl)) {
539
+ return false;
540
+ }
541
+
542
+ var overlay = dom.find('overlay');
543
+ var i, ii;
544
+ if (controlData.usesOverlay && controlData.controlType != "hud") {
545
+ for (i = 0, ii = p.controls.length; i < ii; ++i) {
546
+ if (p.controls[i].usesOverlay && !p.controls[i].hidden) {
547
+ return false;
548
+ }
549
+ }
550
+ overlay.style.display = "block";
551
+ dispatchEvent('monocle:modal:on');
552
+ }
553
+
554
+ for (i = 0; i < controlData.elements.length; ++i) {
555
+ controlData.elements[i].style.display = "block";
556
+ }
557
+
558
+ if (controlData.controlType == "popover") {
559
+ var beyondControl = function (evt) {
560
+ var obj = evt.target;
561
+ do {
562
+ if (obj == controlData.elements[0]) { return false; }
563
+ } while (obj && (obj = obj.parentNode));
564
+ Gala.stop(evt);
565
+ return true;
566
+ }
567
+ var handlers = {
568
+ start: function (e) { if (beyondControl(e)) { hideControl(ctrl); } },
569
+ move: beyondControl
570
+ }
571
+ overlay.listeners = Monocle.Events.listenForContact(overlay, handlers);
572
+ }
573
+ controlData.hidden = false;
574
+ if (ctrl.properties) {
575
+ ctrl.properties.hidden = false;
576
+ }
577
+ dispatchEvent('monocle:controlshow', { control: ctrl }, false);
578
+ return true;
579
+ }
580
+
581
+
582
+ function showingControl(ctrl) {
583
+ var controlData = dataForControl(ctrl);
584
+ return controlData.hidden === false;
585
+ }
586
+
587
+
588
+ function dispatchEvent(evtType, data, cancelable) {
589
+ return Monocle.Events.dispatch(dom.find('box'), evtType, data, cancelable);
590
+ }
591
+
592
+
593
+ function listen(evtType, fn, useCapture) {
594
+ Monocle.Events.listen(dom.find('box'), evtType, fn, useCapture);
595
+ }
596
+
597
+
598
+ function deafen(evtType, fn) {
599
+ Monocle.Events.deafen(dom.find('box'), evtType, fn);
600
+ }
601
+
602
+
603
+ function visiblePages() {
604
+ return p.flipper.visiblePages ?
605
+ p.flipper.visiblePages() :
606
+ [dom.find('page')];
607
+ }
608
+
609
+
610
+ function forEachPage(callback) {
611
+ for (var i = 0, ii = p.flipper.pageCount; i < ii; ++i) {
612
+ var page = dom.find('page', i);
613
+ callback(page, i);
614
+ }
615
+ }
616
+
617
+
618
+ /* The Reader PageStyles API is deprecated - it has moved to Formatting */
619
+
620
+ function addPageStyles(styleRules, restorePlace) {
621
+ console.deprecation("Use reader.formatting.addPageStyles instead.");
622
+ return API.formatting.addPageStyles(styleRules, restorePlace);
623
+ }
624
+
625
+
626
+ function updatePageStyles(sheetIndex, styleRules, restorePlace) {
627
+ console.deprecation("Use reader.formatting.updatePageStyles instead.");
628
+ return API.formatting.updatePageStyles(sheetIndex, styleRules, restorePlace);
629
+ }
630
+
631
+
632
+ function removePageStyles(sheetIndex, restorePlace) {
633
+ console.deprecation("Use reader.formatting.removePageStyles instead.");
634
+ return API.formatting.removePageStyles(sheetIndex, restorePlace);
635
+ }
636
+
637
+
638
+ function fatalSystemMessage(msg) {
639
+ var info = dom.make('div', 'book_fatality', { html: msg });
640
+ var box = dom.find('box');
641
+ var bbOrigin = [box.offsetWidth / 2, box.offsetHeight / 2];
642
+ API.billboard.show(info, { closeButton: false, from: bbOrigin });
643
+ }
644
+
645
+
646
+ API.getBook = getBook;
647
+ API.getPlace = getPlace;
648
+ API.moveTo = moveTo;
649
+ API.skipToChapter = skipToChapter;
650
+ API.resized = resized;
651
+ API.recalculateDimensions = recalculateDimensions;
652
+ API.addControl = addControl;
653
+ API.hideControl = hideControl;
654
+ API.showControl = showControl;
655
+ API.showingControl = showingControl;
656
+ API.dispatchEvent = dispatchEvent;
657
+ API.listen = listen;
658
+ API.deafen = deafen;
659
+ API.visiblePages = visiblePages;
660
+
661
+ // Deprecated!
662
+ API.addPageStyles = addPageStyles;
663
+ API.updatePageStyles = updatePageStyles;
664
+ API.removePageStyles = removePageStyles;
665
+
666
+ initialize();
667
+
668
+ return API;
669
+ }
670
+
671
+
672
+ Monocle.Reader.RESIZE_DELAY = Monocle.Browser.renders.slow ? 500 : 100;
673
+ Monocle.Reader.DEFAULT_SYSTEM_ID = 'RS:monocle'
674
+ Monocle.Reader.DEFAULT_CLASS_PREFIX = 'monelem_'
675
+ Monocle.Reader.DEFAULT_STYLE_RULES = Monocle.Formatting.DEFAULT_STYLE_RULES;
676
+ Monocle.Reader.COMPATIBILITY_INFO =
677
+ "<h1>Incompatible browser</h1>"+
678
+ "<p>Unfortunately, your browser isn't able to display this book. "+
679
+ "If possible, try again in another browser or on another device.</p>";
680
+ Monocle.Reader.LOAD_FAILURE_INFO =
681
+ "<h1>Book could not be loaded</h1>"+
682
+ "<p>Sorry, parts of the book could not be retrieved.<br />"+
683
+ "Please check your connection and refresh to try again.</p>";