monocle-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,59 @@
1
+ // A shortcut for creating a bookdata object from a 'data' hash.
2
+ //
3
+ // eg:
4
+ //
5
+ // Monocle.bookData({ components: ['intro.html', 'ch1.html', 'ch2.html'] });
6
+ //
7
+ // All keys in the 'data' hash are optional:
8
+ //
9
+ // components: must be an array of component urls
10
+ // chapters: must be an array of nested chapters (the usual bookdata structure)
11
+ // metadata: must be a hash of key/values
12
+ // getComponentFn: override the default way to fetch components via id
13
+ //
14
+ Monocle.bookData = function (data) {
15
+ return {
16
+ getComponents: function () {
17
+ return data.components || ['anonymous'];
18
+ },
19
+ getContents: function () {
20
+ return data.chapters || [];
21
+ },
22
+ getComponent: data.getComponent || function (id) {
23
+ return { url: id }
24
+ },
25
+ getMetaData: function (key) {
26
+ return (data.metadata || {})[key];
27
+ },
28
+ data: data
29
+ }
30
+ }
31
+
32
+
33
+ // A shortcut for creating a bookdata object from an array of element ids.
34
+ //
35
+ // eg:
36
+ //
37
+ // Monocle.bookDataFromIds(['part1', 'part2']);
38
+ //
39
+ Monocle.bookDataFromIds = function (elementIds) {
40
+ return Monocle.bookData({
41
+ components: elementIds,
42
+ getComponent: function (cmptId) {
43
+ return { nodes: [document.getElementById(cmptId)] }
44
+ }
45
+ });
46
+ }
47
+
48
+
49
+ // A shortcut for creating a bookdata object from an array of nodes.
50
+ //
51
+ // eg:
52
+ //
53
+ // Monocle.bookDataFromNodes([document.getElementById('content')]);
54
+ //
55
+ Monocle.bookDataFromNodes = function (nodes) {
56
+ return Monocle.bookData({
57
+ getComponent: function (n) { return { 'nodes': nodes }; }
58
+ });
59
+ }
@@ -0,0 +1,413 @@
1
+ /* COMPONENT */
2
+
3
+ // See the properties declaration for details of constructor arguments.
4
+ //
5
+ Monocle.Component = function (book, id, index, chapters, source) {
6
+
7
+ var API = { constructor: Monocle.Component }
8
+ var k = API.constants = API.constructor;
9
+ var p = API.properties = {
10
+ // a back-reference to the public API of the book that owns this component
11
+ book: book,
12
+
13
+ // the string that represents this component in the book's component array
14
+ id: id,
15
+
16
+ // the position in the book's components array of this component
17
+ index: index,
18
+
19
+ // The chapters argument is an array of objects that list the chapters that
20
+ // can be found in this component. A chapter object is defined as:
21
+ //
22
+ // {
23
+ // title: str,
24
+ // fragment: str, // optional anchor id
25
+ // percent: n // how far into the component the chapter begins
26
+ // }
27
+ //
28
+ // NOTE: the percent property is calculated by the component - you only need
29
+ // to pass in the title and the optional id string.
30
+ //
31
+ chapters: chapters,
32
+
33
+ // the frame provided by dataSource.getComponent() for this component
34
+ source: source
35
+ }
36
+
37
+
38
+ // Makes this component the active component for the pageDiv. There are
39
+ // several strategies for this (see loadFrame).
40
+ //
41
+ // When the component has been loaded into the pageDiv's frame, the callback
42
+ // will be invoked with the pageDiv and this component as arguments.
43
+ //
44
+ function applyTo(pageDiv, callback) {
45
+ prepareSource(pageDiv.m.reader);
46
+
47
+ var evtData = { 'page': pageDiv, 'source': p.source };
48
+ pageDiv.m.reader.dispatchEvent('monocle:componentchanging', evtData);
49
+
50
+ var onLoaded = function () {
51
+ setupFrame(
52
+ pageDiv,
53
+ pageDiv.m.activeFrame,
54
+ function () { callback(pageDiv, API) }
55
+ );
56
+ }
57
+
58
+ Monocle.defer(function () { loadFrame(pageDiv, onLoaded); });
59
+ }
60
+
61
+
62
+ // Loads this component into the given frame, using one of the following
63
+ // strategies:
64
+ //
65
+ // * HTML - a HTML string
66
+ // * URL - a URL string
67
+ // * Nodes - an array of DOM body nodes (NB: no way to populate head)
68
+ // * Document - a DOM DocumentElement object
69
+ //
70
+ function loadFrame(pageDiv, callback) {
71
+ var frame = pageDiv.m.activeFrame;
72
+
73
+ // We own this frame now.
74
+ frame.m.component = API;
75
+
76
+ // Hide the frame while we're changing it.
77
+ frame.style.visibility = "hidden";
78
+
79
+ frame.whenDocumentReady = function () {
80
+ var doc = frame.contentDocument;
81
+ var evtData = { 'page': pageDiv, 'document': doc, 'component': API };
82
+ pageDiv.m.reader.dispatchEvent('monocle:componentmodify', evtData);
83
+ frame.whenDocumentReady = null;
84
+ }
85
+
86
+ if (p.source.html) {
87
+ return loadFrameFromHTML(p.source.html || p.source, frame, callback);
88
+ } else if (p.source.url) {
89
+ return loadFrameFromURL(p.source.url, frame, callback);
90
+ } else if (p.source.doc) {
91
+ return loadFrameFromDocument(p.source.doc, frame, callback);
92
+ }
93
+ }
94
+
95
+
96
+ // LOAD STRATEGY: HTML
97
+ // Loads a HTML string into the given frame, invokes the callback once loaded.
98
+ //
99
+ function loadFrameFromHTML(src, frame, callback) {
100
+ var fn = function () {
101
+ Monocle.Events.deafen(frame, 'load', fn);
102
+ frame.whenDocumentReady();
103
+ Monocle.defer(callback);
104
+ }
105
+ Monocle.Events.listen(frame, 'load', fn);
106
+ if (Monocle.Browser.env.loadHTMLWithDocWrite) {
107
+ frame.contentDocument.open('text/html', 'replace');
108
+ frame.contentDocument.write(src);
109
+ frame.contentDocument.close();
110
+ } else {
111
+ frame.contentWindow['monCmptData'] = src;
112
+ frame.src = "javascript:window['monCmptData'];"
113
+ }
114
+ }
115
+
116
+
117
+ // LOAD STRATEGY: URL
118
+ // Loads the URL into the given frame, invokes callback once loaded.
119
+ //
120
+ function loadFrameFromURL(url, frame, callback) {
121
+ // If it's a relative path, we need to make it absolute.
122
+ if (!url.match(/^\//)) {
123
+ url = absoluteURL(url);
124
+ }
125
+ var onDocumentReady = function () {
126
+ Monocle.Events.deafen(frame, 'load', onDocumentReady);
127
+ frame.whenDocumentReady();
128
+ }
129
+ var onDocumentLoad = function () {
130
+ Monocle.Events.deafen(frame, 'load', onDocumentLoad);
131
+ Monocle.defer(callback);
132
+ }
133
+ Monocle.Events.listen(frame, 'load', onDocumentReady);
134
+ Monocle.Events.listen(frame, 'load', onDocumentLoad);
135
+ frame.contentWindow.location.replace(url);
136
+ }
137
+
138
+
139
+ // LOAD STRATEGY: DOCUMENT
140
+ // Replaces the DocumentElement of the given frame with the given srcDoc.
141
+ // Invokes the callback when loaded.
142
+ //
143
+ function loadFrameFromDocument(srcDoc, frame, callback) {
144
+ var doc = frame.contentDocument;
145
+
146
+ // WebKit has an interesting quirk. The <base> tag must exist in the
147
+ // document being replaced, not the new document.
148
+ if (Monocle.Browser.is.WebKit) {
149
+ var srcBase = srcDoc.querySelector('base');
150
+ if (srcBase) {
151
+ var head = doc.querySelector('head');
152
+ if (!head) {
153
+ try {
154
+ head = doc.createElement('head');
155
+ prependChild(doc.documentElement, head);
156
+ } catch (e) {
157
+ head = doc.body;
158
+ }
159
+ }
160
+ var base = doc.createElement('base');
161
+ base.setAttribute('href', srcBase.href);
162
+ head.appendChild(base);
163
+ }
164
+ }
165
+
166
+ doc.replaceChild(
167
+ doc.importNode(srcDoc.documentElement, true),
168
+ doc.documentElement
169
+ );
170
+
171
+ // NB: It's a significant problem with this load strategy that there's
172
+ // no indication when it is complete.
173
+ Monocle.defer(callback);
174
+ }
175
+
176
+
177
+ // Once a frame is loaded with this component, call this method to style
178
+ // and measure its contents.
179
+ //
180
+ function setupFrame(pageDiv, frame, callback) {
181
+ updateDimensions(pageDiv, function () {
182
+ frame.style.visibility = "visible";
183
+
184
+ // Find the place of any chapters in the component.
185
+ locateChapters(pageDiv);
186
+
187
+ // Nothing can prevent iframe scrolling on Android, so we have to undo it.
188
+ if (Monocle.Browser.on.Android) {
189
+ Monocle.Events.listen(frame.contentWindow, 'scroll', function () {
190
+ frame.contentWindow.scrollTo(0,0);
191
+ });
192
+ }
193
+
194
+ // Announce that the component has changed.
195
+ var doc = frame.contentDocument;
196
+ var evtData = { 'page': pageDiv, 'document': doc, 'component': API };
197
+ pageDiv.m.reader.dispatchEvent('monocle:componentchange', evtData);
198
+
199
+ callback();
200
+ });
201
+ }
202
+
203
+
204
+ // Checks whether the pageDiv dimensions have changed. If they have,
205
+ // remeasures dimensions and returns true. Otherwise returns false.
206
+ //
207
+ function updateDimensions(pageDiv, callback) {
208
+ pageDiv.m.dimensions.update(function (pageLength) {
209
+ p.pageLength = pageLength;
210
+ if (typeof callback == "function") { callback() }
211
+ });
212
+ }
213
+
214
+
215
+ // Iterates over all the chapters that are within this component
216
+ // (according to the array we were provided on initialization) and finds
217
+ // their location (in percentage terms) within the text.
218
+ //
219
+ // Stores this percentage with the chapter object in the chapters array.
220
+ //
221
+ function locateChapters(pageDiv) {
222
+ if (p.chapters[0] && typeof p.chapters[0].percent == "number") {
223
+ return;
224
+ }
225
+ var doc = pageDiv.m.activeFrame.contentDocument;
226
+ for (var i = 0; i < p.chapters.length; ++i) {
227
+ var chp = p.chapters[i];
228
+ chp.percent = 0;
229
+ if (chp.fragment) {
230
+ var node = doc.getElementById(chp.fragment);
231
+ chp.percent = pageDiv.m.dimensions.percentageThroughOfNode(node);
232
+ }
233
+ }
234
+ return p.chapters;
235
+ }
236
+
237
+
238
+ // For a given page number within the component, return the chapter that
239
+ // starts on or most-recently-before this page.
240
+ //
241
+ // Useful, for example, in displaying the current chapter title as a
242
+ // running head on the page.
243
+ //
244
+ function chapterForPage(pageN) {
245
+ var cand = null;
246
+ var percent = (pageN - 1) / p.pageLength;
247
+ for (var i = 0; i < p.chapters.length; ++i) {
248
+ if (percent >= p.chapters[i].percent) {
249
+ cand = p.chapters[i];
250
+ } else {
251
+ return cand;
252
+ }
253
+ }
254
+ return cand;
255
+ }
256
+
257
+
258
+ // For a given chapter fragment (the bit after the hash
259
+ // in eg, "index.html#foo"), return the page number on which
260
+ // the chapter starts. If the fragment is null or blank, will
261
+ // return the first page of the component.
262
+ //
263
+ function pageForChapter(fragment, pageDiv) {
264
+ if (!fragment) {
265
+ return 1;
266
+ }
267
+ for (var i = 0; i < p.chapters.length; ++i) {
268
+ if (p.chapters[i].fragment == fragment) {
269
+ return percentToPageNumber(p.chapters[i].percent);
270
+ }
271
+ }
272
+ var doc = pageDiv.m.activeFrame.contentDocument;
273
+ var node = doc.getElementById(fragment);
274
+ var percent = pageDiv.m.dimensions.percentageThroughOfNode(node);
275
+ return percentToPageNumber(percent);
276
+ }
277
+
278
+
279
+ function pageForXPath(xpath, pageDiv) {
280
+ var doc = pageDiv.m.activeFrame.contentDocument;
281
+ var percent = 0;
282
+ if (Monocle.Browser.env.supportsXPath) {
283
+ var node = doc.evaluate(xpath, doc, null, 9, null).singleNodeValue;
284
+ if (node) {
285
+ percent = pageDiv.m.dimensions.percentageThroughOfNode(node);
286
+ }
287
+ } else {
288
+ console.warn("XPath not supported in this client.");
289
+ }
290
+ return percentToPageNumber(percent);
291
+ }
292
+
293
+
294
+ function pageForSelector(selector, pageDiv) {
295
+ var doc = pageDiv.m.activeFrame.contentDocument;
296
+ var percent = 0;
297
+ if (Monocle.Browser.env.supportsQuerySelector) {
298
+ var node = doc.querySelector(selector);
299
+ if (node) {
300
+ percent = pageDiv.m.dimensions.percentageThroughOfNode(node);
301
+ }
302
+ } else {
303
+ console.warn("querySelector not supported in this client.");
304
+ }
305
+ return percentToPageNumber(percent);
306
+ }
307
+
308
+
309
+ function percentToPageNumber(pc) {
310
+ return Math.floor(pc * p.pageLength) + 1;
311
+ }
312
+
313
+
314
+ // A public getter for p.pageLength.
315
+ //
316
+ function lastPageNumber() {
317
+ return p.pageLength;
318
+ }
319
+
320
+
321
+ function prepareSource(reader) {
322
+ if (p.sourcePrepared) { return; }
323
+ p.sourcePrepared = true;
324
+
325
+ if (typeof p.source == "string") {
326
+ p.source = { html: p.source };
327
+ }
328
+
329
+ // If supplied as escaped javascript, unescape it to HTML by evalling it.
330
+ if (p.source.javascript) {
331
+ console.deprecation(
332
+ "Loading a component by 'javascript' is deprecated. " +
333
+ "Use { 'html': src } -- no need to escape or clean the string."
334
+ );
335
+ var src = p.source.javascript;
336
+ src = src.replace(/\\n/g, "\n");
337
+ src = src.replace(/\\r/g, "\r");
338
+ src = src.replace(/\\'/g, "'");
339
+ p.source = { html: src };
340
+ }
341
+
342
+ // If supplied as DOM nodes, convert to HTML by concatenating outerHTMLs.
343
+ if (p.source.nodes) {
344
+ var srcs = [];
345
+ for (var i = 0, ii = p.source.nodes.length; i < ii; ++i) {
346
+ var node = p.source.nodes[i];
347
+ if (node.outerHTML) {
348
+ srcs.push(node.outerHTML);
349
+ } else {
350
+ var div = document.createElement('div');
351
+ div.appendChild(node.cloneNode(true));
352
+ srcs.push(div.innerHTML);
353
+ delete(div);
354
+ }
355
+ }
356
+ p.source = { html: srcs.join('') };
357
+ }
358
+
359
+ var baseURI;
360
+ if (p.source.html && !p.source.html.match(new RegExp("<base\s.+>", "im"))) {
361
+ baseURI = computeBaseURI(reader);
362
+ if (baseURI) {
363
+ p.source.html = p.source.html.replace(
364
+ new RegExp("(<head[^>]*>)", "im"),
365
+ '$1<base href="'+baseURI+'" />'
366
+ );
367
+ }
368
+ }
369
+
370
+ if (p.source.doc && !p.source.doc.querySelector('base')) {
371
+ var srcHead = p.source.doc.querySelector('head') || p.source.doc.body;
372
+ baseURI = computeBaseURI(reader);
373
+ if (srcHead && baseURI) {
374
+ var srcBase = p.source.doc.createElement('base');
375
+ srcBase.setAttribute('href', baseURI);
376
+ prependChild(srcHead, srcBase);
377
+ }
378
+ }
379
+ }
380
+
381
+
382
+ function computeBaseURI(reader) {
383
+ var evtData = { cmptId: p.id, cmptURI: absoluteURL(p.id) }
384
+ if (reader.dispatchEvent('monocle:component:baseuri', evtData, true)) {
385
+ return evtData.cmptURI;
386
+ }
387
+ }
388
+
389
+
390
+ function absoluteURL(url) {
391
+ var link = document.createElement('a');
392
+ link.setAttribute('href', url);
393
+ var result = link.href;
394
+ delete(link);
395
+ return result;
396
+ }
397
+
398
+
399
+ function prependChild(pr, el) {
400
+ pr.firstChild ? pr.insertBefore(el, pr.firstChild) : pr.appendChild(el);
401
+ }
402
+
403
+
404
+ API.applyTo = applyTo;
405
+ API.updateDimensions = updateDimensions;
406
+ API.chapterForPage = chapterForPage;
407
+ API.pageForChapter = pageForChapter;
408
+ API.pageForXPath = pageForXPath;
409
+ API.pageForSelector = pageForSelector;
410
+ API.lastPageNumber = lastPageNumber;
411
+
412
+ return API;
413
+ }