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.
- data/.DS_Store +0 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/monocle/rails.rb +8 -0
- data/lib/monocle/rails/version.rb +5 -0
- data/monocle-rails.gemspec +23 -0
- data/vendor/.DS_Store +0 -0
- data/vendor/assets/.DS_Store +0 -0
- data/vendor/assets/javascripts/.DS_Store +0 -0
- data/vendor/assets/javascripts/compat/browser.js +120 -0
- data/vendor/assets/javascripts/compat/css.js +145 -0
- data/vendor/assets/javascripts/compat/env.js +463 -0
- data/vendor/assets/javascripts/compat/gala.js +469 -0
- data/vendor/assets/javascripts/compat/stubs.js +50 -0
- data/vendor/assets/javascripts/controls/contents.js +59 -0
- data/vendor/assets/javascripts/controls/magnifier.js +51 -0
- data/vendor/assets/javascripts/controls/panel.js +136 -0
- data/vendor/assets/javascripts/controls/placesaver.js +100 -0
- data/vendor/assets/javascripts/controls/scrubber.js +140 -0
- data/vendor/assets/javascripts/controls/spinner.js +99 -0
- data/vendor/assets/javascripts/controls/stencil.js +410 -0
- data/vendor/assets/javascripts/core/billboard.js +120 -0
- data/vendor/assets/javascripts/core/book.js +467 -0
- data/vendor/assets/javascripts/core/bookdata.js +59 -0
- data/vendor/assets/javascripts/core/component.js +413 -0
- data/vendor/assets/javascripts/core/events.js +56 -0
- data/vendor/assets/javascripts/core/factory.js +194 -0
- data/vendor/assets/javascripts/core/formatting.js +317 -0
- data/vendor/assets/javascripts/core/monocle.js +16 -0
- data/vendor/assets/javascripts/core/place.js +210 -0
- data/vendor/assets/javascripts/core/reader.js +683 -0
- data/vendor/assets/javascripts/core/selection.js +158 -0
- data/vendor/assets/javascripts/core/styles.js +155 -0
- data/vendor/assets/javascripts/dimensions/columns.js +218 -0
- data/vendor/assets/javascripts/flippers/instant.js +78 -0
- data/vendor/assets/javascripts/flippers/scroller.js +128 -0
- data/vendor/assets/javascripts/flippers/slider.js +469 -0
- data/vendor/assets/javascripts/monocore.js +27 -0
- data/vendor/assets/javascripts/monoctrl.js +1 -0
- data/vendor/assets/javascripts/panels/eink.js +61 -0
- data/vendor/assets/javascripts/panels/imode.js +180 -0
- data/vendor/assets/javascripts/panels/magic.js +297 -0
- data/vendor/assets/javascripts/panels/marginal.js +50 -0
- data/vendor/assets/javascripts/panels/twopane.js +34 -0
- data/vendor/assets/stylesheets/monocore.css +194 -0
- data/vendor/assets/stylesheets/monoctrl.css +168 -0
- 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>";
|