middleman 2.0.9.pre.2 → 2.0.9.pre.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/CHANGELOG +9 -0
  2. data/features/builder.feature +6 -0
  3. data/features/data.feature +16 -1
  4. data/features/fonts.feature +11 -0
  5. data/features/relative_assets.feature +8 -2
  6. data/features/step_definitions/middleman_steps.rb +9 -2
  7. data/fixtures/data-app/config.rb +3 -0
  8. data/fixtures/data-app/data/pages.yml +6 -0
  9. data/fixtures/data-app/source/index.html.haml +1 -0
  10. data/fixtures/data-app/source/layout.haml +3 -0
  11. data/fixtures/fonts-app/config.rb +0 -0
  12. data/fixtures/fonts-app/source/fonts/StMarie-Thin.otf +0 -0
  13. data/fixtures/fonts-app/source/stylesheets/fonts.css.sass +3 -0
  14. data/fixtures/glob-app/config.rb +1 -0
  15. data/fixtures/glob-app/source/index.html.haml +6 -0
  16. data/fixtures/glob-app/source/stylesheets/site.css.sass +1 -0
  17. data/fixtures/test-app/config.rb +1 -0
  18. data/fixtures/test-app/data/test2.json +4 -0
  19. data/fixtures/test-app/source/data3.html.erb +1 -0
  20. data/lib/middleman/base.rb +8 -3
  21. data/lib/middleman/builder.rb +61 -22
  22. data/lib/middleman/cli.rb +2 -1
  23. data/lib/middleman/core_extensions/assets.rb +1 -1
  24. data/lib/middleman/core_extensions/compass.rb +6 -2
  25. data/lib/middleman/core_extensions/data.rb +20 -3
  26. data/lib/middleman/templates/html5.rb +1 -0
  27. data/lib/middleman/templates/mobile.rb +17 -0
  28. data/lib/middleman/templates/mobile/source/404.html +38 -0
  29. data/lib/middleman/templates/mobile/source/README.markdown +64 -0
  30. data/lib/middleman/templates/mobile/source/crossdomain.xml +25 -0
  31. data/lib/middleman/templates/mobile/source/css/style.css +236 -0
  32. data/lib/middleman/templates/mobile/source/default.appcache +17 -0
  33. data/lib/middleman/templates/mobile/source/humans.txt +43 -0
  34. data/lib/middleman/templates/mobile/source/img/h/apple-touch-icon.png +0 -0
  35. data/lib/middleman/templates/mobile/source/img/h/splash.png +0 -0
  36. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon-precomposed.png +0 -0
  37. data/lib/middleman/templates/mobile/source/img/l/apple-touch-icon.png +0 -0
  38. data/lib/middleman/templates/mobile/source/img/l/splash.png +0 -0
  39. data/lib/middleman/templates/mobile/source/img/m/apple-touch-icon.png +0 -0
  40. data/lib/middleman/templates/mobile/source/index.html +95 -0
  41. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.js +8316 -0
  42. data/lib/middleman/templates/mobile/source/js/libs/jquery-1.5.1.min.js +16 -0
  43. data/lib/middleman/templates/mobile/source/js/libs/modernizr-custom.js +14 -0
  44. data/lib/middleman/templates/mobile/source/js/libs/respond.min.js +7 -0
  45. data/lib/middleman/templates/mobile/source/js/mylibs/helper.js +147 -0
  46. data/lib/middleman/templates/mobile/source/js/script.js +0 -0
  47. data/lib/middleman/templates/mobile/source/robots.txt +5 -0
  48. data/lib/middleman/templates/mobile/source/sitemap.xml +10 -0
  49. data/lib/middleman/templates/mobile/source/test/index.html +31 -0
  50. data/lib/middleman/templates/mobile/source/test/qunit/qunit.css +148 -0
  51. data/lib/middleman/templates/mobile/source/test/qunit/qunit.js +1265 -0
  52. data/lib/middleman/templates/mobile/source/test/tests.js +26 -0
  53. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/Readme.PDF +0 -0
  54. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx1.snippet +31 -0
  55. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/aspx2.snippet +2 -0
  56. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/ga.aspx +195 -0
  57. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/aspx/sample.aspx +44 -0
  58. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/ga.jsp +225 -0
  59. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp1.snippet +35 -0
  60. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/jsp2.snippet +2 -0
  61. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/jsp/sample.jsp +51 -0
  62. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/ga.php +176 -0
  63. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php1.snippet +30 -0
  64. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/php2.snippet +4 -0
  65. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/php/sample.php +44 -0
  66. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/ga.pl +195 -0
  67. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl1.snippet +27 -0
  68. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/perl2.snippet +1 -0
  69. data/lib/middleman/templates/mobile/source/tools/googleanalyticsformobile/pl/sample.pl +38 -0
  70. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/COPYING +202 -0
  71. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/bookmark_bubble.js +559 -0
  72. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.html +43 -0
  73. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/example/example.js +57 -0
  74. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/arrow.png +0 -0
  75. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/close.png +0 -0
  76. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/generate_base64_images +33 -0
  77. data/lib/middleman/templates/mobile/source/tools/mobile-bookmark-bubble/images/icon_calendar.png +0 -0
  78. data/lib/middleman/templates/mobile/source/tools/wspl/README +27 -0
  79. data/lib/middleman/templates/mobile/source/tools/wspl/databasefactory.js +45 -0
  80. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker.js +324 -0
  81. data/lib/middleman/templates/mobile/source/tools/wspl/dbworker_test.html +393 -0
  82. data/lib/middleman/templates/mobile/source/tools/wspl/dbworkerstarter.js +32 -0
  83. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears.js +595 -0
  84. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_gears_test.html +404 -0
  85. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5.js +203 -0
  86. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapper_html5_test.html +468 -0
  87. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi.js +202 -0
  88. data/lib/middleman/templates/mobile/source/tools/wspl/dbwrapperapi_test.html +51 -0
  89. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset.js +71 -0
  90. data/lib/middleman/templates/mobile/source/tools/wspl/gears_resultset_test.html +86 -0
  91. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction.js +196 -0
  92. data/lib/middleman/templates/mobile/source/tools/wspl/gears_transaction_test.html +221 -0
  93. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils.js +94 -0
  94. data/lib/middleman/templates/mobile/source/tools/wspl/gearsutils_test.html +84 -0
  95. data/lib/middleman/templates/mobile/source/tools/wspl/global_functions.js +72 -0
  96. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/index.html +347 -0
  97. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/simplenotes.js +503 -0
  98. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/styles.css +66 -0
  99. data/lib/middleman/templates/mobile/source/tools/wspl/simplenotes/template.js +75 -0
  100. data/lib/middleman/version.rb +1 -1
  101. data/middleman-x86-mingw32.gemspec +0 -1
  102. data/middleman.gemspec +3 -2
  103. metadata +116 -51
  104. data/lib/middleman/config.ru +0 -2
  105. data/lib/middleman/features/tiny_src.rb +0 -11
@@ -0,0 +1,27 @@
1
+ # Copyright 2009 Google Inc. All Rights Reserved.
2
+
3
+ use URI::Escape;
4
+
5
+ use constant GA_ACCOUNT => 'ACCOUNT ID GOES HERE';
6
+ use constant GA_PIXEL => 'ga.pl';
7
+
8
+ sub google_analytics_get_image_url {
9
+ my $url = '';
10
+ $url .= GA_PIXEL . '?';
11
+ $url .= 'utmac=' . GA_ACCOUNT;
12
+ $url .= '&utmn=' . int(rand(0x7fffffff));
13
+
14
+ my $referer = $ENV{'HTTP_REFERER'};
15
+ my $query = $ENV{'QUERY_STRING'};
16
+ my $path = $ENV{'REQUEST_URI'};
17
+
18
+ if ($referer eq "") {
19
+ $referer = '-';
20
+ }
21
+
22
+ $url .= '&utmr=' . uri_escape($referer);
23
+ $url .= '&utmp=' . uri_escape($path);
24
+ $url .= '&guid=ON';
25
+
26
+ $url;
27
+ }
@@ -0,0 +1 @@
1
+ print '<img src="' . google_analytics_get_image_url() . '" />';
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/perl -w
2
+ #
3
+ # Copyright 2009 Google Inc. All Rights Reserved.
4
+
5
+ use strict;
6
+ use URI::Escape;
7
+
8
+ use constant GA_ACCOUNT => 'MO-3845491-5';
9
+ use constant GA_PIXEL => 'ga.pl';
10
+
11
+ sub google_analytics_get_image_url {
12
+ my $url = '';
13
+ $url .= GA_PIXEL . '?';
14
+ $url .= 'utmac=' . GA_ACCOUNT;
15
+ $url .= '&utmn=' . int(rand(0x7fffffff));
16
+
17
+ my $referer = $ENV{'HTTP_REFERER'};
18
+ my $query = $ENV{'QUERY_STRING'};
19
+ my $path = $ENV{'REQUEST_URI'};
20
+
21
+ if ($referer eq "") {
22
+ $referer = '-';
23
+ }
24
+
25
+ $url .= '&utmr=' . uri_escape($referer);
26
+ $url .= '&utmp=' . uri_escape($path);
27
+ $url .= '&guid=ON';
28
+
29
+ $url;
30
+ }
31
+
32
+ print "Content-Type: text/html; charset=ISO-8859-1\n\n";
33
+ print '<html><head></head><body>';
34
+ print '<img src="' . google_analytics_get_image_url() . '" />';
35
+ print google_analytics_get_image_url();
36
+ print '</body></html>';
37
+
38
+ exit (0);
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,559 @@
1
+ /*
2
+ Copyright 2010 Google Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * @fileoverview Bookmark bubble library. This is meant to be included in the
19
+ * main JavaScript binary of a mobile web application.
20
+ *
21
+ * Supported browsers: iPhone / iPod / iPad Safari 3.0+
22
+ */
23
+
24
+ var google = google || {};
25
+ google.bookmarkbubble = google.bookmarkbubble || {};
26
+
27
+
28
+ /**
29
+ * Binds a context object to the function.
30
+ * @param {Function} fn The function to bind to.
31
+ * @param {Object} context The "this" object to use when the function is run.
32
+ * @return {Function} A partially-applied form of fn.
33
+ */
34
+ google.bind = function(fn, context) {
35
+ return function() {
36
+ return fn.apply(context, arguments);
37
+ };
38
+ };
39
+
40
+
41
+ /**
42
+ * Function used to define an abstract method in a base class. If a subclass
43
+ * fails to override the abstract method, then an error will be thrown whenever
44
+ * that method is invoked.
45
+ */
46
+ google.abstractMethod = function() {
47
+ throw Error('Unimplemented abstract method.');
48
+ };
49
+
50
+
51
+
52
+ /**
53
+ * The bubble constructor. Instantiating an object does not cause anything to
54
+ * be rendered yet, so if necessary you can set instance properties before
55
+ * showing the bubble.
56
+ * @constructor
57
+ */
58
+ google.bookmarkbubble.Bubble = function() {
59
+ /**
60
+ * Handler for the scroll event. Keep a reference to it here, so it can be
61
+ * unregistered when the bubble is destroyed.
62
+ * @type {function()}
63
+ * @private
64
+ */
65
+ this.boundScrollHandler_ = google.bind(this.setPosition, this);
66
+
67
+ /**
68
+ * The bubble element.
69
+ * @type {Element}
70
+ * @private
71
+ */
72
+ this.element_ = null;
73
+
74
+ /**
75
+ * Whether the bubble has been destroyed.
76
+ * @type {boolean}
77
+ * @private
78
+ */
79
+ this.hasBeenDestroyed_ = false;
80
+ };
81
+
82
+
83
+ /**
84
+ * Shows the bubble if allowed. It is not allowed if:
85
+ * - The browser is not Mobile Safari, or
86
+ * - The user has dismissed it too often already, or
87
+ * - The hash parameter is present in the location hash, or
88
+ * - The application is in fullscreen mode, which means it was already loaded
89
+ * from a homescreen bookmark.
90
+ * @return {boolean} True if the bubble is being shown, false if it is not
91
+ * allowed to show for one of the aforementioned reasons.
92
+ */
93
+ google.bookmarkbubble.Bubble.prototype.showIfAllowed = function() {
94
+ if (!this.isAllowedToShow_()) {
95
+ return false;
96
+ }
97
+
98
+ this.show_();
99
+ return true;
100
+ };
101
+
102
+
103
+ /**
104
+ * Shows the bubble if allowed after loading the icon image. This method creates
105
+ * an image element to load the image into the browser's cache before showing
106
+ * the bubble to ensure that the image isn't blank. Use this instead of
107
+ * showIfAllowed if the image url is http and cacheable.
108
+ * This hack is necessary because Mobile Safari does not properly render
109
+ * image elements with border-radius CSS.
110
+ * @param {function()} opt_callback Closure to be called if and when the bubble
111
+ * actually shows.
112
+ * @return {boolean} True if the bubble is allowed to show.
113
+ */
114
+ google.bookmarkbubble.Bubble.prototype.showIfAllowedWhenLoaded =
115
+ function(opt_callback) {
116
+ if (!this.isAllowedToShow_()) {
117
+ return false;
118
+ }
119
+
120
+ var self = this;
121
+ // Attach to self to avoid garbage collection.
122
+ var img = self.loadImg_ = document.createElement('img');
123
+ img.src = self.getIconUrl_();
124
+ img.onload = function() {
125
+ if (img.complete) {
126
+ delete self.loadImg_;
127
+ img.onload = null; // Break the circular reference.
128
+
129
+ self.show_();
130
+ opt_callback && opt_callback();
131
+ }
132
+ };
133
+ img.onload();
134
+
135
+ return true;
136
+ };
137
+
138
+
139
+ /**
140
+ * Sets the parameter in the location hash. As it is
141
+ * unpredictable what hash scheme is to be used, this method must be
142
+ * implemented by the host application.
143
+ *
144
+ * This gets called automatically when the bubble is shown. The idea is that if
145
+ * the user then creates a bookmark, we can later recognize on application
146
+ * startup whether it was from a bookmark suggested with this bubble.
147
+ */
148
+ google.bookmarkbubble.Bubble.prototype.setHashParameter = google.abstractMethod;
149
+
150
+
151
+ /**
152
+ * Whether the parameter is present in the location hash. As it is
153
+ * unpredictable what hash scheme is to be used, this method must be
154
+ * implemented by the host application.
155
+ *
156
+ * Call this method during application startup if you want to log whether the
157
+ * application was loaded from a bookmark with the bookmark bubble promotion
158
+ * parameter in it.
159
+ *
160
+ * @return {boolean} Whether the bookmark bubble parameter is present in the
161
+ * location hash.
162
+ */
163
+ google.bookmarkbubble.Bubble.prototype.hasHashParameter = google.abstractMethod;
164
+
165
+
166
+ /**
167
+ * The number of times the user must dismiss the bubble before we stop showing
168
+ * it. This is a public property and can be changed by the host application if
169
+ * necessary.
170
+ * @type {number}
171
+ */
172
+ google.bookmarkbubble.Bubble.prototype.NUMBER_OF_TIMES_TO_DISMISS = 2;
173
+
174
+
175
+ /**
176
+ * Time in milliseconds. If the user does not dismiss the bubble, it will auto
177
+ * destruct after this amount of time.
178
+ * @type {number}
179
+ */
180
+ google.bookmarkbubble.Bubble.prototype.TIME_UNTIL_AUTO_DESTRUCT = 15000;
181
+
182
+
183
+ /**
184
+ * The prefix for keys in local storage. This is a public property and can be
185
+ * changed by the host application if necessary.
186
+ * @type {string}
187
+ */
188
+ google.bookmarkbubble.Bubble.prototype.LOCAL_STORAGE_PREFIX = 'BOOKMARK_';
189
+
190
+
191
+ /**
192
+ * The key name for the dismissed state.
193
+ * @type {string}
194
+ * @private
195
+ */
196
+ google.bookmarkbubble.Bubble.prototype.DISMISSED_ = 'DISMISSED_COUNT';
197
+
198
+
199
+ /**
200
+ * The arrow image in base64 data url format.
201
+ * @type {string}
202
+ * @private
203
+ */
204
+ google.bookmarkbubble.Bubble.prototype.IMAGE_ARROW_DATA_URL_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAATCAMAAABSrFY3AAABKVBMVEUAAAD///8AAAAAAAAAAAAAAAAAAADf398AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD09PQAAAAAAAAAAAC9vb0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD19fUAAAAAAAAAAAAAAADq6uoAAAAAAAAAAAC8vLzU1NTT09MAAADg4OAAAADs7OwAAAAAAAAAAAD///+cueenwerA0vC1y+3a5fb5+/3t8vr4+v3w9PuwyOy3zO3h6vfh6vjq8Pqkv+mat+fE1fHB0/Cduuifu+iuxuuivemrxOvC1PDz9vzJ2fKpwuqmwOrb5vapw+q/0vDf6ffK2vLN3PPprJISAAAAQHRSTlMAAAEGExES7FM+JhUoQSxIRwMbNfkJUgXXBE4kDQIMHSA0Tw4xIToeTSc4Chz4OyIjPfI3QD/X5OZR6zzwLSUPrm1y3gAAAQZJREFUeF5lzsVyw0AURNE3IMsgmZmZgszQZoeZOf//EYlG5Yrhbs+im4Dj7slM5wBJ4OJ+undAUr68gK/Hyb6Bcp5yBR/w8jreNeAr5Eg2XE7g6e2/0z6cGw1JQhpmHP3u5aiPPnTTkIK48Hj9Op7bD3btAXTfgUdwYjwSDCVXMbizO0O4uDY/x4kYC5SWFnfC6N1a9RCO7i2XEmQJj2mHK1Hgp9Vq3QBRl9shuBLGhcNtHexcdQCnDUoUGetxDD+H2DQNG2xh6uAWgG2/17o1EmLqYH0Xej0UjHAaFxZIV6rJ/WK1kg7QZH8HU02zmdJinKZJaDV3TVMjM5Q9yiqYpUwiMwa/1apDXTNESjsAAAAASUVORK5CYII=';
205
+
206
+
207
+ /**
208
+ * The close image in base64 data url format.
209
+ * @type {string}
210
+ * @private
211
+ */
212
+ google.bookmarkbubble.Bubble.prototype.IMAGE_CLOSE_DATA_URL_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAALVBMVEXM3fm+1Pfb5/rF2fjw9f23z/aavPOhwfTp8PyTt/L3+v7T4vqMs/K7zP////+qRWzhAAAAXElEQVQIW2O4CwUM996BwVskxtOqd++2rwMyPI+ve31GD8h4Madqz2mwms5jZ/aBGS/mHIDoen3m+DowY8/hOVUgxusz+zqPg7SvPA1UxQfSvu/du0YUK2AMmDMA5H1qhVX33T8AAAAASUVORK5CYII=';
213
+
214
+
215
+ /**
216
+ * The link used to locate the application's home screen icon to display inside
217
+ * the bubble. The default link used here is for an iPhone home screen icon
218
+ * without gloss. If your application uses a glossy icon, change this to
219
+ * 'apple-touch-icon'.
220
+ * @type {string}
221
+ * @private
222
+ */
223
+ google.bookmarkbubble.Bubble.prototype.REL_ICON_ =
224
+ 'apple-touch-icon-precomposed';
225
+
226
+
227
+ /**
228
+ * Regular expression for detecting an iPhone or iPod or iPad.
229
+ * @type {!RegExp}
230
+ * @private
231
+ */
232
+ google.bookmarkbubble.Bubble.prototype.MOBILE_SAFARI_USERAGENT_REGEX_ =
233
+ /iPhone|iPod|iPad/;
234
+
235
+
236
+ /**
237
+ * Regular expression for detecting an iPad.
238
+ * @type {!RegExp}
239
+ * @private
240
+ */
241
+ google.bookmarkbubble.Bubble.prototype.IPAD_USERAGENT_REGEX_ = /iPad/;
242
+
243
+
244
+ /**
245
+ * Determines whether the bubble should be shown or not.
246
+ * @return {boolean} Whether the bubble should be shown or not.
247
+ * @private
248
+ */
249
+ google.bookmarkbubble.Bubble.prototype.isAllowedToShow_ = function() {
250
+ return this.isMobileSafari_() &&
251
+ !this.hasBeenDismissedTooManyTimes_() &&
252
+ !this.isFullscreen_() &&
253
+ !this.hasHashParameter();
254
+ };
255
+
256
+
257
+ /**
258
+ * Builds and shows the bubble.
259
+ * @private
260
+ */
261
+ google.bookmarkbubble.Bubble.prototype.show_ = function() {
262
+ this.element_ = this.build_();
263
+
264
+ document.body.appendChild(this.element_);
265
+ this.element_.style.WebkitTransform =
266
+ 'translateY(' + this.getHiddenYPosition_() + 'px)';
267
+
268
+ this.setHashParameter();
269
+
270
+ window.setTimeout(this.boundScrollHandler_, 1);
271
+ window.addEventListener('scroll', this.boundScrollHandler_, false);
272
+
273
+ // If the user does not dismiss the bubble, slide out and destroy it after
274
+ // some time.
275
+ window.setTimeout(google.bind(this.autoDestruct_, this),
276
+ this.TIME_UNTIL_AUTO_DESTRUCT);
277
+ };
278
+
279
+
280
+ /**
281
+ * Destroys the bubble by removing its DOM nodes from the document.
282
+ */
283
+ google.bookmarkbubble.Bubble.prototype.destroy = function() {
284
+ if (this.hasBeenDestroyed_) {
285
+ return;
286
+ }
287
+ window.removeEventListener('scroll', this.boundScrollHandler_, false);
288
+ if (this.element_ && this.element_.parentNode == document.body) {
289
+ document.body.removeChild(this.element_);
290
+ this.element_ = null;
291
+ }
292
+ this.hasBeenDestroyed_ = true;
293
+ };
294
+
295
+
296
+ /**
297
+ * Remember that the user has dismissed the bubble once more.
298
+ * @private
299
+ */
300
+ google.bookmarkbubble.Bubble.prototype.rememberDismissal_ = function() {
301
+ if (window.localStorage) {
302
+ try {
303
+ var key = this.LOCAL_STORAGE_PREFIX + this.DISMISSED_;
304
+ var value = Number(window.localStorage[key]) || 0;
305
+ window.localStorage[key] = String(value + 1);
306
+ } catch (ex) {
307
+ // Looks like we've hit the storage size limit. Currently we have no
308
+ // fallback for this scenario, but we could use cookie storage instead.
309
+ // This would increase the code bloat though.
310
+ }
311
+ }
312
+ };
313
+
314
+
315
+ /**
316
+ * Whether the user has dismissed the bubble often enough that we will not
317
+ * show it again.
318
+ * @return {boolean} Whether the user has dismissed the bubble often enough
319
+ * that we will not show it again.
320
+ * @private
321
+ */
322
+ google.bookmarkbubble.Bubble.prototype.hasBeenDismissedTooManyTimes_ =
323
+ function() {
324
+ if (!window.localStorage) {
325
+ // If we can not use localStorage to remember how many times the user has
326
+ // dismissed the bubble, assume he has dismissed it. Otherwise we might end
327
+ // up showing it every time the host application loads, into eternity.
328
+ return true;
329
+ }
330
+ try {
331
+ var key = this.LOCAL_STORAGE_PREFIX + this.DISMISSED_;
332
+
333
+ // If the key has never been set, localStorage yields undefined, which
334
+ // Number() turns into NaN. In that case we'll fall back to zero for
335
+ // clarity's sake.
336
+ var value = Number(window.localStorage[key]) || 0;
337
+
338
+ return value >= this.NUMBER_OF_TIMES_TO_DISMISS;
339
+ } catch (ex) {
340
+ // If we got here, something is wrong with the localStorage. Make the same
341
+ // assumption as when it does not exist at all. Exceptions should only
342
+ // occur when setting a value (due to storage limitations) but let's be
343
+ // extra careful.
344
+ return true;
345
+ }
346
+ };
347
+
348
+
349
+ /**
350
+ * Whether the application is running in fullscreen mode.
351
+ * @return {boolean} Whether the application is running in fullscreen mode.
352
+ * @private
353
+ */
354
+ google.bookmarkbubble.Bubble.prototype.isFullscreen_ = function() {
355
+ return !!window.navigator.standalone;
356
+ };
357
+
358
+
359
+ /**
360
+ * Whether the application is running inside Mobile Safari.
361
+ * @return {boolean} True if the current user agent looks like Mobile Safari.
362
+ * @private
363
+ */
364
+ google.bookmarkbubble.Bubble.prototype.isMobileSafari_ = function() {
365
+ return this.MOBILE_SAFARI_USERAGENT_REGEX_.test(window.navigator.userAgent);
366
+ };
367
+
368
+
369
+ /**
370
+ * Whether the application is running on an iPad.
371
+ * @return {boolean} True if the current user agent looks like an iPad.
372
+ * @private
373
+ */
374
+ google.bookmarkbubble.Bubble.prototype.isIpad_ = function() {
375
+ return this.IPAD_USERAGENT_REGEX_.test(window.navigator.userAgent);
376
+ };
377
+
378
+
379
+
380
+ /**
381
+ * Positions the bubble at the bottom of the viewport using an animated
382
+ * transition.
383
+ */
384
+ google.bookmarkbubble.Bubble.prototype.setPosition = function() {
385
+ this.element_.style.WebkitTransition = '-webkit-transform 0.7s ease-out';
386
+ this.element_.style.WebkitTransform =
387
+ 'translateY(' + this.getVisibleYPosition_() + 'px)';
388
+ };
389
+
390
+
391
+ /**
392
+ * Destroys the bubble by removing its DOM nodes from the document, and
393
+ * remembers that it was dismissed.
394
+ * @private
395
+ */
396
+ google.bookmarkbubble.Bubble.prototype.closeClickHandler_ = function() {
397
+ this.destroy();
398
+ this.rememberDismissal_();
399
+ };
400
+
401
+
402
+ /**
403
+ * Gets called after a while if the user ignores the bubble.
404
+ * @private
405
+ */
406
+ google.bookmarkbubble.Bubble.prototype.autoDestruct_ = function() {
407
+ if (this.hasBeenDestroyed_) {
408
+ return;
409
+ }
410
+ this.element_.style.WebkitTransition = '-webkit-transform 0.7s ease-in';
411
+ this.element_.style.WebkitTransform =
412
+ 'translateY(' + this.getHiddenYPosition_() + 'px)';
413
+ window.setTimeout(google.bind(this.destroy, this), 700);
414
+ };
415
+
416
+
417
+ /**
418
+ * Gets the y offset used to show the bubble (i.e., position it on-screen).
419
+ * @return {number} The y offset.
420
+ * @private
421
+ */
422
+ google.bookmarkbubble.Bubble.prototype.getVisibleYPosition_ = function() {
423
+ return this.isIpad_() ? window.pageYOffset + 17 :
424
+ window.pageYOffset - this.element_.offsetHeight + window.innerHeight - 17;
425
+ };
426
+
427
+
428
+ /**
429
+ * Gets the y offset used to hide the bubble (i.e., position it off-screen).
430
+ * @return {number} The y offset.
431
+ * @private
432
+ */
433
+ google.bookmarkbubble.Bubble.prototype.getHiddenYPosition_ = function() {
434
+ return this.isIpad_() ? window.pageYOffset - this.element_.offsetHeight :
435
+ window.pageYOffset + window.innerHeight;
436
+ };
437
+
438
+
439
+ /**
440
+ * The url of the app's bookmark icon.
441
+ * @type {string|undefined}
442
+ * @private
443
+ */
444
+ google.bookmarkbubble.Bubble.prototype.iconUrl_;
445
+
446
+
447
+ /**
448
+ * Scrapes the document for a link element that specifies an Apple favicon and
449
+ * returns the icon url. Returns an empty data url if nothing can be found.
450
+ * @return {string} A url string.
451
+ * @private
452
+ */
453
+ google.bookmarkbubble.Bubble.prototype.getIconUrl_ = function() {
454
+ if (!this.iconUrl_) {
455
+ var link = this.getLink(this.REL_ICON_);
456
+ if (!link || !(this.iconUrl_ = link.href)) {
457
+ this.iconUrl_ = 'data:image/png;base64,';
458
+ }
459
+ }
460
+ return this.iconUrl_;
461
+ };
462
+
463
+
464
+ /**
465
+ * Gets the requested link tag if it exists.
466
+ * @param {string} rel The rel attribute of the link tag to get.
467
+ * @return {Element} The requested link tag or null.
468
+ */
469
+ google.bookmarkbubble.Bubble.prototype.getLink = function(rel) {
470
+ rel = rel.toLowerCase();
471
+ var links = document.getElementsByTagName('link');
472
+ for (var i = 0; i < links.length; ++i) {
473
+ var currLink = /** @type {Element} */ (links[i]);
474
+ if (currLink.getAttribute('rel').toLowerCase() == rel) {
475
+ return currLink;
476
+ }
477
+ }
478
+ return null;
479
+ };
480
+
481
+
482
+ /**
483
+ * Creates the bubble and appends it to the document.
484
+ * @return {Element} The bubble element.
485
+ * @private
486
+ */
487
+ google.bookmarkbubble.Bubble.prototype.build_ = function() {
488
+ var bubble = document.createElement('div');
489
+ var isIpad = this.isIpad_();
490
+
491
+ bubble.style.position = 'absolute';
492
+ bubble.style.zIndex = 1000;
493
+ bubble.style.width = '100%';
494
+ bubble.style.left = '0';
495
+ bubble.style.top = '0';
496
+
497
+ var bubbleInner = document.createElement('div');
498
+ bubbleInner.style.position = 'relative';
499
+ bubbleInner.style.width = '214px';
500
+ bubbleInner.style.margin = isIpad ? '0 0 0 82px' : '0 auto';
501
+ bubbleInner.style.border = '2px solid #fff';
502
+ bubbleInner.style.padding = '20px 20px 20px 10px';
503
+ bubbleInner.style.WebkitBorderRadius = '8px';
504
+ bubbleInner.style.WebkitBoxShadow = '0 0 8px rgba(0, 0, 0, 0.7)';
505
+ bubbleInner.style.WebkitBackgroundSize = '100% 8px';
506
+ bubbleInner.style.backgroundColor = '#b0c8ec';
507
+ bubbleInner.style.background = '#cddcf3 -webkit-gradient(linear, ' +
508
+ 'left bottom, left top, ' + isIpad ?
509
+ 'from(#cddcf3), to(#b3caed)) no-repeat top' :
510
+ 'from(#b3caed), to(#cddcf3)) no-repeat bottom';
511
+ bubbleInner.style.font = '13px/17px sans-serif';
512
+ bubble.appendChild(bubbleInner);
513
+
514
+ // The "Add to Home Screen" text is intended to be the exact same size text
515
+ // that is displayed in the menu of Mobile Safari on iPhone.
516
+ bubbleInner.innerHTML = 'Install this web app on your phone: tap ' +
517
+ '<b style="font-size:15px">+</b> and then <b>\'Add to Home Screen\'</b>';
518
+
519
+ var icon = document.createElement('div');
520
+ icon.style['float'] = 'left';
521
+ icon.style.width = '55px';
522
+ icon.style.height = '55px';
523
+ icon.style.margin = '-2px 7px 3px 5px';
524
+ icon.style.background =
525
+ '#fff url(' + this.getIconUrl_() + ') no-repeat -1px -1px';
526
+ icon.style.WebkitBackgroundSize = '57px';
527
+ icon.style.WebkitBorderRadius = '10px';
528
+ icon.style.WebkitBoxShadow = '0 2px 5px rgba(0, 0, 0, 0.4)';
529
+ bubbleInner.insertBefore(icon, bubbleInner.firstChild);
530
+
531
+ var arrow = document.createElement('div');
532
+ arrow.style.backgroundImage = 'url(' + this.IMAGE_ARROW_DATA_URL_ + ')';
533
+ arrow.style.width = '25px';
534
+ arrow.style.height = '19px';
535
+ arrow.style.position = 'absolute';
536
+ arrow.style.left = '111px';
537
+ if (isIpad) {
538
+ arrow.style.WebkitTransform = 'rotate(180deg)';
539
+ arrow.style.top = '-19px';
540
+ } else {
541
+ arrow.style.bottom = '-19px';
542
+ }
543
+ bubbleInner.appendChild(arrow);
544
+
545
+ var close = document.createElement('a');
546
+ close.onclick = google.bind(this.closeClickHandler_, this);
547
+ close.style.position = 'absolute';
548
+ close.style.display = 'block';
549
+ close.style.top = '-3px';
550
+ close.style.right = '-3px';
551
+ close.style.width = '16px';
552
+ close.style.height = '16px';
553
+ close.style.border = '10px solid transparent';
554
+ close.style.background =
555
+ 'url(' + this.IMAGE_CLOSE_DATA_URL_ + ') no-repeat';
556
+ bubbleInner.appendChild(close);
557
+
558
+ return bubble;
559
+ };