├── .gitignore
├── CODEOWNERS
├── LICENSE
├── README.md
├── images
├── ic_close_24px.svg
└── loader.png
├── index.html
├── scripts
├── app.js
├── data.js
└── namespace.js
├── styles
└── app.css
└── third_party
├── handlebars-intl.min.js
└── handlebars-v3.0.0.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @udacity/active-public-content
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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 2014 Google Inc.
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.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Udacity 60fps Course Samples
2 |
3 | **Please note: this code is intended for you to hone your debugging skills. It contains a lot of code that you should not use in production!**
4 |
5 | This is a simple web app that shows the top stories from [Hacker News](https://news.ycombinator.com/news) via [its API](http://blog.ycombinator.com/hacker-news-api).
6 |
7 | Unfortunately it has a bunch of performance issues, such as:
8 |
9 | * Layout Thrashing
10 | * Expensive painting
11 | * Unnecessary layouts
12 | * Long-running and badly-timed JavaScript
13 | * Bad touch handling
14 |
15 | Your mission is to find and fix the issues, and make the app gloriously performant!
16 |
17 | ## License
18 |
19 | See /LICENSE for more.
20 |
21 | This is not a Google product.
22 |
--------------------------------------------------------------------------------
/images/ic_close_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/images/loader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/news-aggregator/e54f212ee2b51ee42f6a04925f32264fa1a2edf6/images/loader.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
33 |
34 |
40 |
41 |
62 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
83 |
84 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/scripts/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | APP.Main = (function() {
18 |
19 | var LAZY_LOAD_THRESHOLD = 300;
20 | var $ = document.querySelector.bind(document);
21 |
22 | var stories = null;
23 | var storyStart = 0;
24 | var count = 100;
25 | var main = $('main');
26 | var inDetails = false;
27 | var storyLoadCount = 0;
28 | var localeData = {
29 | data: {
30 | intl: {
31 | locales: 'en-US'
32 | }
33 | }
34 | };
35 |
36 | var tmplStory = $('#tmpl-story').textContent;
37 | var tmplStoryDetails = $('#tmpl-story-details').textContent;
38 | var tmplStoryDetailsComment = $('#tmpl-story-details-comment').textContent;
39 |
40 | if (typeof HandlebarsIntl !== 'undefined') {
41 | HandlebarsIntl.registerWith(Handlebars);
42 | } else {
43 |
44 | // Remove references to formatRelative, because Intl isn't supported.
45 | var intlRelative = /, {{ formatRelative time }}/;
46 | tmplStory = tmplStory.replace(intlRelative, '');
47 | tmplStoryDetails = tmplStoryDetails.replace(intlRelative, '');
48 | tmplStoryDetailsComment = tmplStoryDetailsComment.replace(intlRelative, '');
49 | }
50 |
51 | var storyTemplate =
52 | Handlebars.compile(tmplStory);
53 | var storyDetailsTemplate =
54 | Handlebars.compile(tmplStoryDetails);
55 | var storyDetailsCommentTemplate =
56 | Handlebars.compile(tmplStoryDetailsComment);
57 |
58 | /**
59 | * As every single story arrives in shove its
60 | * content in at that exact moment. Feels like something
61 | * that should really be handled more delicately, and
62 | * probably in a requestAnimationFrame callback.
63 | */
64 | function onStoryData (key, details) {
65 |
66 | // This seems odd. Surely we could just select the story
67 | // directly rather than looping through all of them.
68 | var storyElements = document.querySelectorAll('.story');
69 |
70 | for (var i = 0; i < storyElements.length; i++) {
71 |
72 | if (storyElements[i].getAttribute('id') === 's-' + key) {
73 |
74 | details.time *= 1000;
75 | var story = storyElements[i];
76 | var html = storyTemplate(details);
77 | story.innerHTML = html;
78 | story.addEventListener('click', onStoryClick.bind(this, details));
79 | story.classList.add('clickable');
80 |
81 | // Tick down. When zero we can batch in the next load.
82 | storyLoadCount--;
83 |
84 | }
85 | }
86 |
87 | // Colorize on complete.
88 | if (storyLoadCount === 0)
89 | colorizeAndScaleStories();
90 | }
91 |
92 | function onStoryClick(details) {
93 |
94 | var storyDetails = $('sd-' + details.id);
95 |
96 | // Wait a little time then show the story details.
97 | setTimeout(showStory.bind(this, details.id), 60);
98 |
99 | // Create and append the story. A visual change...
100 | // perhaps that should be in a requestAnimationFrame?
101 | // And maybe, since they're all the same, I don't
102 | // need to make a new element every single time? I mean,
103 | // it inflates the DOM and I can only see one at once.
104 | if (!storyDetails) {
105 |
106 | if (details.url)
107 | details.urlobj = new URL(details.url);
108 |
109 | var comment;
110 | var commentsElement;
111 | var storyHeader;
112 | var storyContent;
113 |
114 | var storyDetailsHtml = storyDetailsTemplate(details);
115 | var kids = details.kids;
116 | var commentHtml = storyDetailsCommentTemplate({
117 | by: '', text: 'Loading comment...'
118 | });
119 |
120 | storyDetails = document.createElement('section');
121 | storyDetails.setAttribute('id', 'sd-' + details.id);
122 | storyDetails.classList.add('story-details');
123 | storyDetails.innerHTML = storyDetailsHtml;
124 |
125 | document.body.appendChild(storyDetails);
126 |
127 | commentsElement = storyDetails.querySelector('.js-comments');
128 | storyHeader = storyDetails.querySelector('.js-header');
129 | storyContent = storyDetails.querySelector('.js-content');
130 |
131 | var closeButton = storyDetails.querySelector('.js-close');
132 | closeButton.addEventListener('click', hideStory.bind(this, details.id));
133 |
134 | var headerHeight = storyHeader.getBoundingClientRect().height;
135 | storyContent.style.paddingTop = headerHeight + 'px';
136 |
137 | if (typeof kids === 'undefined')
138 | return;
139 |
140 | for (var k = 0; k < kids.length; k++) {
141 |
142 | comment = document.createElement('aside');
143 | comment.setAttribute('id', 'sdc-' + kids[k]);
144 | comment.classList.add('story-details__comment');
145 | comment.innerHTML = commentHtml;
146 | commentsElement.appendChild(comment);
147 |
148 | // Update the comment with the live data.
149 | APP.Data.getStoryComment(kids[k], function(commentDetails) {
150 |
151 | commentDetails.time *= 1000;
152 |
153 | var comment = commentsElement.querySelector(
154 | '#sdc-' + commentDetails.id);
155 | comment.innerHTML = storyDetailsCommentTemplate(
156 | commentDetails,
157 | localeData);
158 | });
159 | }
160 | }
161 |
162 | }
163 |
164 | function showStory(id) {
165 |
166 | if (inDetails)
167 | return;
168 |
169 | inDetails = true;
170 |
171 | var storyDetails = $('#sd-' + id);
172 | var left = null;
173 |
174 | if (!storyDetails)
175 | return;
176 |
177 | document.body.classList.add('details-active');
178 | storyDetails.style.opacity = 1;
179 |
180 | function animate () {
181 |
182 | // Find out where it currently is.
183 | var storyDetailsPosition = storyDetails.getBoundingClientRect();
184 |
185 | // Set the left value if we don't have one already.
186 | if (left === null)
187 | left = storyDetailsPosition.left;
188 |
189 | // Now figure out where it needs to go.
190 | left += (0 - storyDetailsPosition.left) * 0.1;
191 |
192 | // Set up the next bit of the animation if there is more to do.
193 | if (Math.abs(left) > 0.5)
194 | setTimeout(animate, 4);
195 | else
196 | left = 0;
197 |
198 | // And update the styles. Wait, is this a read-write cycle?
199 | // I hope I don't trigger a forced synchronous layout!
200 | storyDetails.style.left = left + 'px';
201 | }
202 |
203 | // We want slick, right, so let's do a setTimeout
204 | // every few milliseconds. That's going to keep
205 | // it all tight. Or maybe we're doing visual changes
206 | // and they should be in a requestAnimationFrame
207 | setTimeout(animate, 4);
208 | }
209 |
210 | function hideStory(id) {
211 |
212 | if (!inDetails)
213 | return;
214 |
215 | var storyDetails = $('#sd-' + id);
216 | var left = 0;
217 |
218 | document.body.classList.remove('details-active');
219 | storyDetails.style.opacity = 0;
220 |
221 | function animate () {
222 |
223 | // Find out where it currently is.
224 | var mainPosition = main.getBoundingClientRect();
225 | var storyDetailsPosition = storyDetails.getBoundingClientRect();
226 | var target = mainPosition.width + 100;
227 |
228 | // Now figure out where it needs to go.
229 | left += (target - storyDetailsPosition.left) * 0.1;
230 |
231 | // Set up the next bit of the animation if there is more to do.
232 | if (Math.abs(left - target) > 0.5) {
233 | setTimeout(animate, 4);
234 | } else {
235 | left = target;
236 | inDetails = false;
237 | }
238 |
239 | // And update the styles. Wait, is this a read-write cycle?
240 | // I hope I don't trigger a forced synchronous layout!
241 | storyDetails.style.left = left + 'px';
242 | }
243 |
244 | // We want slick, right, so let's do a setTimeout
245 | // every few milliseconds. That's going to keep
246 | // it all tight. Or maybe we're doing visual changes
247 | // and they should be in a requestAnimationFrame
248 | setTimeout(animate, 4);
249 | }
250 |
251 | /**
252 | * Does this really add anything? Can we do this kind
253 | * of work in a cheaper way?
254 | */
255 | function colorizeAndScaleStories() {
256 |
257 | var storyElements = document.querySelectorAll('.story');
258 |
259 | // It does seem awfully broad to change all the
260 | // colors every time!
261 | for (var s = 0; s < storyElements.length; s++) {
262 |
263 | var story = storyElements[s];
264 | var score = story.querySelector('.story__score');
265 | var title = story.querySelector('.story__title');
266 |
267 | // Base the scale on the y position of the score.
268 | var height = main.offsetHeight;
269 | var mainPosition = main.getBoundingClientRect();
270 | var scoreLocation = score.getBoundingClientRect().top -
271 | document.body.getBoundingClientRect().top;
272 | var scale = Math.min(1, 1 - (0.05 * ((scoreLocation - 170) / height)));
273 | var opacity = Math.min(1, 1 - (0.5 * ((scoreLocation - 170) / height)));
274 |
275 | score.style.width = (scale * 40) + 'px';
276 | score.style.height = (scale * 40) + 'px';
277 | score.style.lineHeight = (scale * 40) + 'px';
278 |
279 | // Now figure out how wide it is and use that to saturate it.
280 | scoreLocation = score.getBoundingClientRect();
281 | var saturation = (100 * ((scoreLocation.width - 38) / 2));
282 |
283 | score.style.backgroundColor = 'hsl(42, ' + saturation + '%, 50%)';
284 | title.style.opacity = opacity;
285 | }
286 | }
287 |
288 | main.addEventListener('touchstart', function(evt) {
289 |
290 | // I just wanted to test what happens if touchstart
291 | // gets canceled. Hope it doesn't block scrolling on mobiles...
292 | if (Math.random() > 0.97) {
293 | evt.preventDefault();
294 | }
295 |
296 | });
297 |
298 | main.addEventListener('scroll', function() {
299 |
300 | var header = $('header');
301 | var headerTitles = header.querySelector('.header__title-wrapper');
302 | var scrollTopCapped = Math.min(70, main.scrollTop);
303 | var scaleString = 'scale(' + (1 - (scrollTopCapped / 300)) + ')';
304 |
305 | colorizeAndScaleStories();
306 |
307 | header.style.height = (156 - scrollTopCapped) + 'px';
308 | headerTitles.style.webkitTransform = scaleString;
309 | headerTitles.style.transform = scaleString;
310 |
311 | // Add a shadow to the header.
312 | if (main.scrollTop > 70)
313 | document.body.classList.add('raised');
314 | else
315 | document.body.classList.remove('raised');
316 |
317 | // Check if we need to load the next batch of stories.
318 | var loadThreshold = (main.scrollHeight - main.offsetHeight -
319 | LAZY_LOAD_THRESHOLD);
320 | if (main.scrollTop > loadThreshold)
321 | loadStoryBatch();
322 | });
323 |
324 | function loadStoryBatch() {
325 |
326 | if (storyLoadCount > 0)
327 | return;
328 |
329 | storyLoadCount = count;
330 |
331 | var end = storyStart + count;
332 | for (var i = storyStart; i < end; i++) {
333 |
334 | if (i >= stories.length)
335 | return;
336 |
337 | var key = String(stories[i]);
338 | var story = document.createElement('div');
339 | story.setAttribute('id', 's-' + key);
340 | story.classList.add('story');
341 | story.innerHTML = storyTemplate({
342 | title: '...',
343 | score: '-',
344 | by: '...',
345 | time: 0
346 | });
347 | main.appendChild(story);
348 |
349 | APP.Data.getStoryById(stories[i], onStoryData.bind(this, key));
350 | }
351 |
352 | storyStart += count;
353 |
354 | }
355 |
356 | // Bootstrap in the stories.
357 | APP.Data.getTopStories(function(data) {
358 | stories = data;
359 | loadStoryBatch();
360 | main.classList.remove('loading');
361 | });
362 |
363 | })();
364 |
--------------------------------------------------------------------------------
/scripts/data.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | APP.Data = (function() {
18 |
19 | var HN_API_BASE = 'https://hacker-news.firebaseio.com';
20 | var HN_TOPSTORIES_URL = HN_API_BASE + '/v0/topstories.json';
21 | var HN_STORYDETAILS_URL = HN_API_BASE + '/v0/item/[ID].json';
22 |
23 | function getTopStories(callback) {
24 | request(HN_TOPSTORIES_URL, function(evt) {
25 | callback(evt.target.response);
26 | });
27 | }
28 |
29 | function getStoryById(id, callback) {
30 |
31 | var storyURL = HN_STORYDETAILS_URL.replace(/\[ID\]/, id);
32 |
33 | request(storyURL, function(evt) {
34 | callback(evt.target.response);
35 | });
36 | }
37 |
38 | function getStoryComment(id, callback) {
39 |
40 | var storyCommentURL = HN_STORYDETAILS_URL.replace(/\[ID\]/, id);
41 |
42 | request(storyCommentURL, function(evt) {
43 | callback(evt.target.response);
44 | });
45 | }
46 |
47 | function request(url, callback) {
48 | var xhr = new XMLHttpRequest();
49 | xhr.open('GET', url, true);
50 | xhr.responseType = 'json';
51 | xhr.onload = callback;
52 | xhr.send();
53 | }
54 |
55 | return {
56 | getTopStories: getTopStories,
57 | getStoryById: getStoryById,
58 | getStoryComment: getStoryComment
59 | };
60 |
61 | })();
62 |
--------------------------------------------------------------------------------
/scripts/namespace.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | var APP = APP || {};
18 |
--------------------------------------------------------------------------------
/styles/app.css:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | * {
18 | box-sizing: border-box;
19 | }
20 |
21 | html, body {
22 | padding: 0;
23 | margin: 0;
24 | height: 100%;
25 | width: 100%;
26 | font-family: 'Roboto';
27 | font-weight: 400;
28 | color: #444;
29 | }
30 |
31 | html {
32 | overflow: hidden;
33 | }
34 |
35 | body {
36 | display: -webkit-flex;
37 | display: -ms-flexbox;
38 | display: flex;
39 | -webkit-flex-direction: column;
40 | -ms-flex-direction: column;
41 | flex-direction: column;
42 | -webkit-flex-wrap: nowrap;
43 | -ms-flex-wrap: nowrap;
44 | flex-wrap: nowrap;
45 | -webkit-justify-content: flex-start;
46 | -ms-flex-pack: start;
47 | justify-content: flex-start;
48 | -webkit-align-items: stretch;
49 | -ms-flex-align: stretch;
50 | align-items: stretch;
51 | -ms-flex-line-pack: stretch;
52 | -webkit-align-content: stretch;
53 | align-content: stretch;
54 | background: #ececec;
55 | }
56 |
57 | a {
58 | color: #E65100;
59 | }
60 |
61 | .header {
62 | width: 100%;
63 | height: 156px;
64 | background: #FF8F00;
65 | color: #FFF;
66 | font-size: 40px;
67 | font-weight: 400;
68 | padding: 0 0 0 72px;
69 | z-index: 1;
70 | position: fixed;
71 | }
72 |
73 | .header__title-wrapper {
74 | position: absolute;
75 | bottom: 16px;
76 | -webkit-transform-origin: 0 100%;
77 | transform-origin: 0 100%;
78 | }
79 |
80 | .header__title {
81 | font-size: 40px;
82 | font-weight: 400;
83 | padding: 0;
84 | margin: 0;
85 | }
86 |
87 | .header__subhead {
88 | font-size: 20px;
89 | font-weight: 400;
90 | padding: 0;
91 | margin: 0;
92 | opacity: 0.54;
93 | }
94 |
95 | body.details-active .story__title,
96 | body.details-active .story__by,
97 | body.details-active .story__score {
98 | transition: opacity 0.5s ease-out;
99 | opacity: 0 !important;
100 | }
101 |
102 | body.details-active .story {
103 | background: linear-gradient(to bottom, #FFF 0%, #FFF 100%);
104 | }
105 |
106 | body.raised .header {
107 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.16),
108 | 0px 2px 5px 0px rgba(0, 0, 0, 0.23);
109 | }
110 |
111 | main {
112 | padding-top: 156px;
113 | -webkit-flex: 1;
114 | -ms-flex: 1;
115 | flex: 1;
116 | overflow-x: hidden;
117 | overflow-y: auto;
118 | -webkit-overflow-scrolling: touch;
119 | z-index: 0;
120 | }
121 |
122 | main.loading {
123 | background: url(../images/loader.png) center center no-repeat;
124 | background-size: 24px 24px;
125 |
126 | -webkit-animation-name: spin;
127 | animation-name: spin;
128 | -webkit-animation-duration: 500ms;
129 | animation-duration: 500ms;
130 | -webkit-animation-iteration-count: infinite;
131 | animation-iteration-count: infinite;
132 | -webkit-animation-timing-function: linear;
133 | animation-timing-function: linear;
134 | }
135 |
136 | @-webkit-keyframes spin {
137 | from { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
138 | to { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
139 | }
140 |
141 | @keyframes spin {
142 | from { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
143 | to { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
144 | }
145 |
146 | .story {
147 | padding: 16px 16px 16px 72px;
148 | background: #FFF;
149 | background: linear-gradient(to bottom, #FFF 0%,#F4F4F4 100%);
150 | position: relative;
151 | min-height: 90px;
152 | cursor: pointer;
153 | transition: all 0.4s ease-out;
154 | }
155 |
156 | .story:after {
157 | display: block;
158 | content: '';
159 | width: 100%;
160 | position: absolute;
161 | left: 0;
162 | bottom: 0;
163 | border-bottom: 1px solid #dedede;
164 | }
165 |
166 | .story:nth-last-child(-n+1):after {
167 | display: none;
168 | }
169 |
170 | .story__title {
171 | font-size: 20px;
172 | font-weight: 500;
173 | color: rgba(0,0,0,0.87);
174 | margin: 0;
175 | padding: 0;
176 | line-height: 32px;
177 | }
178 |
179 | .story__by,
180 | .story-details__by {
181 | color: rgba(0,0,0,0.54);
182 | font-size: 14px;
183 | font-weight: 400;
184 | line-height: 24px;
185 | }
186 |
187 | .story__score {
188 | position: absolute;
189 | width: 40px;
190 | height: 40px;
191 | background: #FFB300;
192 | border-radius: 50%;
193 | color: rgba(255,255,255,0.87);
194 | font-weight: 500;
195 | font-size: 13px;
196 | left: 16px;
197 | top: 16px;
198 | text-align: center;
199 | line-height: 40px;
200 |
201 | box-shadow:
202 | 0px 2px 5px 0px rgba(0, 0, 0, 0.06),
203 | 0px 2px 5px 0px rgba(0, 0, 0, 0.08),
204 | 0px 2px 7px 0px rgba(0, 0, 0, 0.10);
205 | }
206 |
207 | .story-details {
208 | display: -webkit-flex;
209 | display: -ms-flexbox;
210 | display: flex;
211 | -webkit-flex-direction: column;
212 | -ms-flex-direction: column;
213 | flex-direction: column;
214 | -webkit-flex-wrap: nowrap;
215 | -ms-flex-wrap: nowrap;
216 | flex-wrap: nowrap;
217 | -webkit-justify-content: flex-start;
218 | -ms-flex-pack: start;
219 | justify-content: flex-start;
220 | -webkit-align-items: stretch;
221 | -ms-flex-align: stretch;
222 | align-items: stretch;
223 | -ms-flex-line-pack: stretch;
224 | -webkit-align-content: stretch;
225 | align-content: stretch;
226 |
227 | opacity: 0;
228 | position: fixed;
229 | top: 0;
230 | left: 100%;
231 | width: 100%;
232 | height: 100%;
233 | background: white;
234 | z-index: 2;
235 | box-shadow:
236 | 0px 2px 5px 0px rgba(0, 0, 0, 0.06),
237 | 0px 2px 5px 0px rgba(0, 0, 0, 0.08),
238 | 0px 2px 7px 0px rgba(0, 0, 0, 0.10);
239 |
240 | overflow: hidden;
241 | transition: opacity 0.3s ease-out;
242 | }
243 |
244 | .story-details * {
245 | will-change: transform;
246 | transform: translateZ(0);
247 | }
248 |
249 | .story-details__content {
250 | -webkit-flex: 1;
251 | -ms-flex: 1;
252 | flex: 1;
253 | overflow-x: hidden;
254 | overflow-y: auto;
255 |
256 | -webkit-overflow-scrolling: touch;
257 |
258 | padding: 0 16px 72px 72px;
259 | }
260 |
261 | .story-details__title {
262 | color: #FFF;
263 | font-size: 20px;
264 | font-weight: 400;
265 | line-height: 24px;
266 | }
267 |
268 | .story-details__title-link {
269 | color: #FFF;
270 | opacity: 0.6;
271 | }
272 |
273 | .story-details__url {
274 | font-size: 14px;
275 | color: rgba(255,255,255,0.6);
276 | font-weight: 400;
277 | }
278 |
279 | .story-details__header {
280 | padding: 16px 16px 64px 72px;
281 | background: #FFA000;
282 | position: absolute;
283 | left: 0;
284 | top: 0;
285 | width: 100%;
286 | z-index: 1;
287 | }
288 |
289 | .story-details__close {
290 | width: 48px;
291 | height: 48px;
292 | position: absolute;
293 | left: 16px;
294 | top: 50%;
295 | margin-top: -48px;
296 | background: url(../images/ic_close_24px.svg) center center no-repeat;
297 | border: none;
298 | text-indent: -10000px;
299 | border-radius: 0;
300 | }
301 |
302 | .story-details__meta {
303 | background: #FFC107;
304 | height: 40px;
305 | position: absolute;
306 | bottom: 0;
307 | left: 0;
308 | width: 100%;
309 | padding-left: 72px;
310 | line-height: 40px;
311 | color: rgba(0,0,0,0.57);
312 | }
313 |
314 | .story-details__comment {
315 | font-size: 14px;
316 | line-height: 22px;
317 | padding: 16px 0;
318 | word-wrap: break-word;
319 | border-bottom: 1px solid #9c9c9c;
320 | }
321 |
322 | .story-details-comment__author {
323 | font-weight: 700;
324 | font-size: 16px;
325 | }
326 |
327 | .story-details__comments-title {
328 | font-weight: 400;
329 | margin: 0;
330 | padding: 48px 0 0 0;
331 | }
332 |
333 | .story-details__link {
334 | display: inline-block;
335 | padding: 10px;
336 | background: #FF8F00;
337 | color: #FFF;
338 | border-radius: 2px;
339 | text-decoration: none;
340 | margin-top: 30px;
341 | }
342 |
343 | footer {
344 | font-size: 13px;
345 | height: 40px;
346 | background: #ececec;
347 | color: #888;
348 | text-align: center;
349 | line-height: 40px;
350 | }
351 |
352 | footer a {
353 | color: #666;
354 | font-weight: 500;
355 | }
356 |
--------------------------------------------------------------------------------
/third_party/handlebars-intl.min.js:
--------------------------------------------------------------------------------
1 | (function(){"use strict";function a(a){var b,c,d,e,f=Array.prototype.slice.call(arguments,1);for(b=0,c=f.length;c>b;b+=1)if(d=f[b])for(e in d)p.call(d,e)&&(a[e]=d[e]);return a}function b(a,b,c){this.locales=a,this.formats=b,this.pluralFn=c}function c(a){this.id=a}function d(a,b,c,d,e){this.id=a,this.useOrdinal=b,this.offset=c,this.options=d,this.pluralFn=e}function e(a,b,c,d){this.id=a,this.offset=b,this.numberFormat=c,this.string=d}function f(a,b){this.id=a,this.options=b}function g(a,b,c){var d="string"==typeof a?g.__parse(a):a;if(!d||"messageFormatPattern"!==d.type)throw new TypeError("A message must be provided as a String or AST.");c=this._mergeFormats(g.formats,c),r(this,"_locale",{value:this._resolveLocale(b)});var e=this._findPluralRuleFunction(this._locale),f=this._compilePattern(d,b,c,e),h=this;this.format=function(a){return h._format(f,a)}}function h(a){return 400*a/146097}function i(a,b){b=b||{},G(a)&&(a=a.concat()),D(this,"_locale",{value:this._resolveLocale(a)}),D(this,"_options",{value:{style:this._resolveStyle(b.style),units:this._isValidUnits(b.units)&&b.units}}),D(this,"_locales",{value:a}),D(this,"_fields",{value:this._findFields(this._locale)}),D(this,"_messages",{value:E(null)});var c=this;this.format=function(a,b){return c._format(a,b)}}function j(a){var b=Q(null);return function(){var c=Array.prototype.slice.call(arguments),d=k(c),e=d&&b[d];return e||(e=Q(a.prototype),a.apply(e,c),d&&(b[d]=e)),e}}function k(a){if("undefined"!=typeof JSON){var b,c,d,e=[];for(b=0,c=a.length;c>b;b+=1)d=a[b],e.push(d&&"object"==typeof d?l(d):d);return JSON.stringify(e)}}function l(a){var b,c,d,e,f=[],g=[];for(b in a)a.hasOwnProperty(b)&&g.push(b);var h=g.sort();for(c=0,d=h.length;d>c;c+=1)b=h[c],e={},e[b]=a[b],f[c]=e;return f}function m(a){var b,c,d,e,f=Array.prototype.slice.call(arguments,1);for(b=0,c=f.length;c>b;b+=1)if(d=f[b])for(e in d)d.hasOwnProperty(e)&&(a[e]=d[e]);return a}function n(a){function b(a,b){return function(){return"undefined"!=typeof console&&"function"==typeof console.warn&&console.warn("{{"+a+"}} is deprecated, use: {{"+b.name+"}}"),b.apply(this,arguments)}}function c(a){if(!a.fn)throw new Error("{{#intl}} must be invoked as a block helper");var b=p(a.data),c=m({},b.intl,a.hash);return b.intl=c,a.fn(this,{data:b})}function d(a,b){var c,d,e,f=b.data&&b.data.intl,g=a.split(".");try{for(e=0,d=g.length;d>e;e++)c=f=f[g[e]]}finally{if(void 0===c)throw new ReferenceError("Could not find Intl object: "+a)}return c}function e(a,b,c){a=new Date(a),k(a,"A date or timestamp must be provided to {{formatDate}}"),c||(c=b,b=null);var d=c.data.intl&&c.data.intl.locales,e=n("date",b,c);return T(d,e).format(a)}function f(a,b,c){a=new Date(a),k(a,"A date or timestamp must be provided to {{formatTime}}"),c||(c=b,b=null);var d=c.data.intl&&c.data.intl.locales,e=n("time",b,c);return T(d,e).format(a)}function g(a,b,c){a=new Date(a),k(a,"A date or timestamp must be provided to {{formatRelative}}"),c||(c=b,b=null);var d=c.data.intl&&c.data.intl.locales,e=n("relative",b,c),f=c.hash.now;return delete e.now,V(d,e).format(a,{now:f})}function h(a,b,c){l(a,"A number must be provided to {{formatNumber}}"),c||(c=b,b=null);var d=c.data.intl&&c.data.intl.locales,e=n("number",b,c);return S(d,e).format(a)}function i(a,b){b||(b=a,a=null);var c=b.hash;if(!a&&"string"!=typeof a&&!c.intlName)throw new ReferenceError("{{formatMessage}} must be provided a message or intlName");var e=b.data.intl||{},f=e.locales,g=e.formats;return!a&&c.intlName&&(a=d(c.intlName,b)),"function"==typeof a?a(c):("string"==typeof a&&(a=U(a,f,g)),a.format(c))}function j(){var a,b,c=[].slice.call(arguments).pop(),d=c.hash;for(a in d)d.hasOwnProperty(a)&&(b=d[a],"string"==typeof b&&(d[a]=q(b)));return new o(String(i.apply(this,arguments)))}function k(a,b){if(!isFinite(a))throw new TypeError(b)}function l(a,b){if("number"!=typeof a)throw new TypeError(b)}function n(a,b,c){var e,f=c.hash;return b?("string"==typeof b&&(e=d("formats."+a+"."+b,c)),e=m({},e,f)):e=f,e}var o=a.SafeString,p=a.createFrame,q=a.Utils.escapeExpression,r={intl:c,intlGet:d,formatDate:e,formatTime:f,formatRelative:g,formatNumber:h,formatMessage:i,formatHTMLMessage:j,intlDate:b("intlDate",e),intlTime:b("intlTime",f),intlNumber:b("intlNumber",h),intlMessage:b("intlMessage",i),intlHTMLMessage:b("intlHTMLMessage",j)};for(var s in r)r.hasOwnProperty(s)&&a.registerHelper(s,r[s])}function o(a){x.__addLocaleData(a),M.__addLocaleData(a)}var p=Object.prototype.hasOwnProperty,q=function(){try{return!!Object.defineProperty({},"a",{})}catch(a){return!1}}(),r=(!q&&!Object.prototype.__defineGetter__,q?Object.defineProperty:function(a,b,c){"get"in c&&a.__defineGetter__?a.__defineGetter__(b,c.get):(!p.call(a,b)||"value"in c)&&(a[b]=c.value)}),s=Object.create||function(a,b){function c(){}var d,e;c.prototype=a,d=new c;for(e in b)p.call(b,e)&&r(d,e,b[e]);return d},t=b;b.prototype.compile=function(a){return this.pluralStack=[],this.currentPlural=null,this.pluralNumberFormat=null,this.compileMessage(a)},b.prototype.compileMessage=function(a){if(!a||"messageFormatPattern"!==a.type)throw new Error('Message AST is not of type: "messageFormatPattern"');var b,c,d,e=a.elements,f=[];for(b=0,c=e.length;c>b;b+=1)switch(d=e[b],d.type){case"messageTextElement":f.push(this.compileMessageText(d));break;case"argumentElement":f.push(this.compileArgument(d));break;default:throw new Error("Message element does not have a valid type")}return f},b.prototype.compileMessageText=function(a){return this.currentPlural&&/(^|[^\\])#/g.test(a.value)?(this.pluralNumberFormat||(this.pluralNumberFormat=new Intl.NumberFormat(this.locales)),new e(this.currentPlural.id,this.currentPlural.format.offset,this.pluralNumberFormat,a.value)):a.value.replace(/\\#/g,"#")},b.prototype.compileArgument=function(a){var b=a.format;if(!b)return new c(a.id);var e,g=this.formats,h=this.locales,i=this.pluralFn;switch(b.type){case"numberFormat":return e=g.number[b.style],{id:a.id,format:new Intl.NumberFormat(h,e).format};case"dateFormat":return e=g.date[b.style],{id:a.id,format:new Intl.DateTimeFormat(h,e).format};case"timeFormat":return e=g.time[b.style],{id:a.id,format:new Intl.DateTimeFormat(h,e).format};case"pluralFormat":return e=this.compileOptions(a),new d(a.id,b.ordinal,b.offset,e,i);case"selectFormat":return e=this.compileOptions(a),new f(a.id,e);default:throw new Error("Message element does not have a valid format type")}},b.prototype.compileOptions=function(a){var b=a.format,c=b.options,d={};this.pluralStack.push(this.currentPlural),this.currentPlural="pluralFormat"===b.type?a:null;var e,f,g;for(e=0,f=c.length;f>e;e+=1)g=c[e],d[g.selector]=this.compileMessage(g.value);return this.currentPlural=this.pluralStack.pop(),d},c.prototype.format=function(a){return a?"string"==typeof a?a:String(a):""},d.prototype.getOption=function(a){var b=this.options,c=b["="+a]||b[this.pluralFn(a-this.offset,this.useOrdinal)];return c||b.other},e.prototype.format=function(a){var b=this.numberFormat.format(a-this.offset);return this.string.replace(/(^|[^\\])#/g,"$1"+b).replace(/\\#/g,"#")},f.prototype.getOption=function(a){var b=this.options;return b[a]||b.other};var u=function(){function a(a,b){function c(){this.constructor=a}c.prototype=b.prototype,a.prototype=new c}function b(a,b,c,d,e,f){this.message=a,this.expected=b,this.found=c,this.offset=d,this.line=e,this.column=f,this.name="SyntaxError"}function c(a){function c(b){function c(b,c,d){var e,f;for(e=c;d>e;e++)f=a.charAt(e),"\n"===f?(b.seenCR||b.line++,b.column=1,b.seenCR=!1):"\r"===f||"\u2028"===f||"\u2029"===f?(b.line++,b.column=1,b.seenCR=!0):(b.column++,b.seenCR=!1)}return Ua!==b&&(Ua>b&&(Ua=0,Va={line:1,column:1,seenCR:!1}),c(Va,Ua,b),Ua=b),Va}function d(a){Wa>Sa||(Sa>Wa&&(Wa=Sa,Xa=[]),Xa.push(a))}function e(d,e,f){function g(a){var b=1;for(a.sort(function(a,b){return a.descriptionb.description?1:0});b1?g.slice(0,-1).join(", ")+" or "+g[a.length-1]:g[0],e=b?'"'+c(b)+'"':"end of input","Expected "+d+" but "+e+" found."}var i=c(f),j=f1?arguments[1]:{},E={},F={start:f},G=f,H=function(a){return{type:"messageFormatPattern",elements:a}},I=E,J=function(a){var b,c,d,e,f,g="";for(b=0,d=a.length;d>b;b+=1)for(e=a[b],c=0,f=e.length;f>c;c+=1)g+=e[c];return g},K=function(a){return{type:"messageTextElement",value:a}},L=/^[^ \t\n\r,.+={}#]/,M={type:"class",value:"[^ \\t\\n\\r,.+={}#]",description:"[^ \\t\\n\\r,.+={}#]"},N="{",O={type:"literal",value:"{",description:'"{"'},P=null,Q=",",R={type:"literal",value:",",description:'","'},S="}",T={type:"literal",value:"}",description:'"}"'},U=function(a,b){return{type:"argumentElement",id:a,format:b&&b[2]}},V="number",W={type:"literal",value:"number",description:'"number"'},X="date",Y={type:"literal",value:"date",description:'"date"'},Z="time",$={type:"literal",value:"time",description:'"time"'},_=function(a,b){return{type:a+"Format",style:b&&b[2]}},aa="plural",ba={type:"literal",value:"plural",description:'"plural"'},ca=function(a){return{type:a.type,ordinal:!1,offset:a.offset||0,options:a.options}},da="selectordinal",ea={type:"literal",value:"selectordinal",description:'"selectordinal"'},fa=function(a){return{type:a.type,ordinal:!0,offset:a.offset||0,options:a.options}},ga="select",ha={type:"literal",value:"select",description:'"select"'},ia=function(a){return{type:"selectFormat",options:a}},ja="=",ka={type:"literal",value:"=",description:'"="'},la=function(a,b){return{type:"optionalFormatPattern",selector:a,value:b}},ma="offset:",na={type:"literal",value:"offset:",description:'"offset:"'},oa=function(a){return a},pa=function(a,b){return{type:"pluralFormat",offset:a,options:b}},qa={type:"other",description:"whitespace"},ra=/^[ \t\n\r]/,sa={type:"class",value:"[ \\t\\n\\r]",description:"[ \\t\\n\\r]"},ta={type:"other",description:"optionalWhitespace"},ua=/^[0-9]/,va={type:"class",value:"[0-9]",description:"[0-9]"},wa=/^[0-9a-f]/i,xa={type:"class",value:"[0-9a-f]i",description:"[0-9a-f]i"},ya="0",za={type:"literal",value:"0",description:'"0"'},Aa=/^[1-9]/,Ba={type:"class",value:"[1-9]",description:"[1-9]"},Ca=function(a){return parseInt(a,10)},Da=/^[^{}\\\0-\x1F \t\n\r]/,Ea={type:"class",value:"[^{}\\\\\\0-\\x1F \\t\\n\\r]",description:"[^{}\\\\\\0-\\x1F \\t\\n\\r]"},Fa="\\#",Ga={type:"literal",value:"\\#",description:'"\\\\#"'},Ha=function(){return"\\#"},Ia="\\{",Ja={type:"literal",value:"\\{",description:'"\\\\{"'},Ka=function(){return"{"},La="\\}",Ma={type:"literal",value:"\\}",description:'"\\\\}"'},Na=function(){return"}"},Oa="\\u",Pa={type:"literal",value:"\\u",description:'"\\\\u"'},Qa=function(a){return String.fromCharCode(parseInt(a,16))},Ra=function(a){return a.join("")},Sa=0,Ta=0,Ua=0,Va={line:1,column:1,seenCR:!1},Wa=0,Xa=[],Ya=0;if("startRule"in D){if(!(D.startRule in F))throw new Error("Can't start parsing from rule \""+D.startRule+'".');G=F[D.startRule]}if(C=G(),C!==E&&Sa===a.length)return C;throw C!==E&&Sac;c+=1)if(e=a[c],"string"!=typeof e){if(f=e.id,!b||!p.call(b,f))throw new Error("A value must be provided for: "+f);g=b[f],h+=e.options?this._format(e.getOption(g),b):e.format(g)}else h+=e;return h},g.prototype._mergeFormats=function(b,c){var d,e,f={};for(d in b)p.call(b,d)&&(f[d]=e=s(b[d]),c&&p.call(c,d)&&a(e,c[d]));return f},g.prototype._resolveLocale=function(a){"string"==typeof a&&(a=[a]),a=(a||[]).concat(g.defaultLocale);var b,c,d,e,f=g.__localeData__;for(b=0,c=a.length;c>b;b+=1)for(d=a[b].toLowerCase().split("-");d.length;){if(e=f[d.join("-")])return e.locale;d.pop()}var h=a.pop();throw new Error("No locale data has been added to IntlMessageFormat for: "+a.join(", ")+", or the default locale: "+h)};var w={locale:"en",pluralRuleFunction:function(a,b){var c=String(a).split("."),d=!c[1],e=Number(c[0])==a,f=e&&c[0].slice(-1),g=e&&c[0].slice(-2);return b?1==f&&11!=g?"one":2==f&&12!=g?"two":3==f&&13!=g?"few":"other":1==a&&d?"one":"other"}};v.__addLocaleData(w),v.defaultLocale="en";var x=v,y=Math.round,z=function(a,b){a=+a,b=+b;var c=y(b-a),d=y(c/1e3),e=y(d/60),f=y(e/60),g=y(f/24),i=y(g/7),j=h(g),k=y(12*j),l=y(j);return{millisecond:c,second:d,minute:e,hour:f,day:g,week:i,month:k,year:l}},A=Object.prototype.hasOwnProperty,B=Object.prototype.toString,C=function(){try{return!!Object.defineProperty({},"a",{})}catch(a){return!1}}(),D=(!C&&!Object.prototype.__defineGetter__,C?Object.defineProperty:function(a,b,c){"get"in c&&a.__defineGetter__?a.__defineGetter__(b,c.get):(!A.call(a,b)||"value"in c)&&(a[b]=c.value)}),E=Object.create||function(a,b){function c(){}var d,e;c.prototype=a,d=new c;for(e in b)A.call(b,e)&&D(d,e,b[e]);return d},F=Array.prototype.indexOf||function(a,b){var c=this;if(!c.length)return-1;for(var d=b||0,e=c.length;e>d;d++)if(c[d]===a)return d;return-1},G=Array.isArray||function(a){return"[object Array]"===B.call(a)},H=Date.now||function(){return(new Date).getTime()},I=i,J=["second","minute","hour","day","month","year"],K=["best fit","numeric"];D(i,"__localeData__",{value:E(null)}),D(i,"__addLocaleData",{value:function(a){if(!a||!a.locale)throw new Error("Locale data provided to IntlRelativeFormat is missing a `locale` property value");i.__localeData__[a.locale.toLowerCase()]=a,x.__addLocaleData(a)}}),D(i,"defaultLocale",{enumerable:!0,writable:!0,value:void 0}),D(i,"thresholds",{enumerable:!0,value:{second:45,minute:45,hour:22,day:26,month:11}}),i.prototype.resolvedOptions=function(){return{locale:this._locale,style:this._options.style,units:this._options.units}},i.prototype._compileMessage=function(a){var b,c=this._locales,d=(this._locale,this._fields[a]),e=d.relativeTime,f="",g="";for(b in e.future)e.future.hasOwnProperty(b)&&(f+=" "+b+" {"+e.future[b].replace("{0}","#")+"}");for(b in e.past)e.past.hasOwnProperty(b)&&(g+=" "+b+" {"+e.past[b].replace("{0}","#")+"}");var h="{when, select, future {{0, plural, "+f+"}}past {{0, plural, "+g+"}}}";return new x(h,c)},i.prototype._getMessage=function(a){var b=this._messages;return b[a]||(b[a]=this._compileMessage(a)),b[a]},i.prototype._getRelativeUnits=function(a,b){var c=this._fields[b];return c.relative?c.relative[a]:void 0},i.prototype._findFields=function(a){for(var b=i.__localeData__,c=b[a.toLowerCase()];c;){if(c.fields)return c.fields;c=c.parentLocale&&b[c.parentLocale.toLowerCase()]}throw new Error("Locale data added to IntlRelativeFormat is missing `fields` for :"+a)},i.prototype._format=function(a,b){var c=b&&void 0!==b.now?b.now:H();if(void 0===a&&(a=c),!isFinite(c))throw new RangeError("The `now` option provided to IntlRelativeFormat#format() is not in valid range.");if(!isFinite(a))throw new RangeError("The date value provided to IntlRelativeFormat#format() is not in valid range.");var d=z(c,a),e=this._options.units||this._selectUnits(d),f=d[e];if("numeric"!==this._options.style){var g=this._getRelativeUnits(f,e);if(g)return g}return this._getMessage(e).format({0:Math.abs(f),when:0>f?"past":"future"})},i.prototype._isValidUnits=function(a){if(!a||F.call(J,a)>=0)return!0;if("string"==typeof a){var b=/s$/.test(a)&&a.substr(0,a.length-1);if(b&&F.call(J,b)>=0)throw new Error('"'+a+'" is not a valid IntlRelativeFormat `units` value, did you mean: '+b)}throw new Error('"'+a+'" is not a valid IntlRelativeFormat `units` value, it must be one of: "'+J.join('", "')+'"')},i.prototype._resolveLocale=function(a){"string"==typeof a&&(a=[a]),a=(a||[]).concat(i.defaultLocale);var b,c,d,e,f=i.__localeData__;for(b=0,c=a.length;c>b;b+=1)for(d=a[b].toLowerCase().split("-");d.length;){if(e=f[d.join("-")])return e.locale;d.pop()}var g=a.pop();throw new Error("No locale data has been added to IntlRelativeFormat for: "+a.join(", ")+", or the default locale: "+g)},i.prototype._resolveStyle=function(a){if(!a)return K[0];if(F.call(K,a)>=0)return a;throw new Error('"'+a+'" is not a valid IntlRelativeFormat `style` value, it must be one of: "'+K.join('", "')+'"')},i.prototype._selectUnits=function(a){var b,c,d;for(b=0,c=J.length;c>b&&(d=J[b],!(Math.abs(a[d])": ">",
46 | '"': """,
47 | "'": "'",
48 | "`": "`"
49 | };
50 |
51 | var badChars = /[&<>"'`]/g;
52 | var possible = /[&<>"'`]/;
53 |
54 | function escapeChar(chr) {
55 | return escape[chr];
56 | }
57 |
58 | function extend(obj /* , ...source */) {
59 | for (var i = 1; i < arguments.length; i++) {
60 | for (var key in arguments[i]) {
61 | if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
62 | obj[key] = arguments[i][key];
63 | }
64 | }
65 | }
66 |
67 | return obj;
68 | }
69 |
70 | __exports__.extend = extend;var toString = Object.prototype.toString;
71 | __exports__.toString = toString;
72 | // Sourced from lodash
73 | // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
74 | var isFunction = function(value) {
75 | return typeof value === 'function';
76 | };
77 | // fallback for older versions of Chrome and Safari
78 | /* istanbul ignore next */
79 | if (isFunction(/x/)) {
80 | isFunction = function(value) {
81 | return typeof value === 'function' && toString.call(value) === '[object Function]';
82 | };
83 | }
84 | var isFunction;
85 | __exports__.isFunction = isFunction;
86 | /* istanbul ignore next */
87 | var isArray = Array.isArray || function(value) {
88 | return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
89 | };
90 | __exports__.isArray = isArray;
91 | // Older IE versions do not directly support indexOf so we must implement our own, sadly.
92 | function indexOf(array, value) {
93 | for (var i = 0, len = array.length; i < len; i++) {
94 | if (array[i] === value) {
95 | return i;
96 | }
97 | }
98 | return -1;
99 | }
100 |
101 | __exports__.indexOf = indexOf;
102 | function escapeExpression(string) {
103 | // don't escape SafeStrings, since they're already safe
104 | if (string && string.toHTML) {
105 | return string.toHTML();
106 | } else if (string == null) {
107 | return "";
108 | } else if (!string) {
109 | return string + '';
110 | }
111 |
112 | // Force a string conversion as this will be done by the append regardless and
113 | // the regex test will do this transparently behind the scenes, causing issues if
114 | // an object's to string has escaped characters in it.
115 | string = "" + string;
116 |
117 | if(!possible.test(string)) { return string; }
118 | return string.replace(badChars, escapeChar);
119 | }
120 |
121 | __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
122 | if (!value && value !== 0) {
123 | return true;
124 | } else if (isArray(value) && value.length === 0) {
125 | return true;
126 | } else {
127 | return false;
128 | }
129 | }
130 |
131 | __exports__.isEmpty = isEmpty;function blockParams(params, ids) {
132 | params.path = ids;
133 | return params;
134 | }
135 |
136 | __exports__.blockParams = blockParams;function appendContextPath(contextPath, id) {
137 | return (contextPath ? contextPath + '.' : '') + id;
138 | }
139 |
140 | __exports__.appendContextPath = appendContextPath;
141 | return __exports__;
142 | })();
143 |
144 | // handlebars/exception.js
145 | var __module4__ = (function() {
146 | "use strict";
147 | var __exports__;
148 |
149 | var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
150 |
151 | function Exception(message, node) {
152 | var loc = node && node.loc,
153 | line,
154 | column;
155 | if (loc) {
156 | line = loc.start.line;
157 | column = loc.start.column;
158 |
159 | message += ' - ' + line + ':' + column;
160 | }
161 |
162 | var tmp = Error.prototype.constructor.call(this, message);
163 |
164 | // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
165 | for (var idx = 0; idx < errorProps.length; idx++) {
166 | this[errorProps[idx]] = tmp[errorProps[idx]];
167 | }
168 |
169 | if (loc) {
170 | this.lineNumber = line;
171 | this.column = column;
172 | }
173 | }
174 |
175 | Exception.prototype = new Error();
176 |
177 | __exports__ = Exception;
178 | return __exports__;
179 | })();
180 |
181 | // handlebars/base.js
182 | var __module2__ = (function(__dependency1__, __dependency2__) {
183 | "use strict";
184 | var __exports__ = {};
185 | var Utils = __dependency1__;
186 | var Exception = __dependency2__;
187 |
188 | var VERSION = "3.0.0";
189 | __exports__.VERSION = VERSION;var COMPILER_REVISION = 6;
190 | __exports__.COMPILER_REVISION = COMPILER_REVISION;
191 | var REVISION_CHANGES = {
192 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
193 | 2: '== 1.0.0-rc.3',
194 | 3: '== 1.0.0-rc.4',
195 | 4: '== 1.x.x',
196 | 5: '== 2.0.0-alpha.x',
197 | 6: '>= 2.0.0-beta.1'
198 | };
199 | __exports__.REVISION_CHANGES = REVISION_CHANGES;
200 | var isArray = Utils.isArray,
201 | isFunction = Utils.isFunction,
202 | toString = Utils.toString,
203 | objectType = '[object Object]';
204 |
205 | function HandlebarsEnvironment(helpers, partials) {
206 | this.helpers = helpers || {};
207 | this.partials = partials || {};
208 |
209 | registerDefaultHelpers(this);
210 | }
211 |
212 | __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
213 | constructor: HandlebarsEnvironment,
214 |
215 | logger: logger,
216 | log: log,
217 |
218 | registerHelper: function(name, fn) {
219 | if (toString.call(name) === objectType) {
220 | if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
221 | Utils.extend(this.helpers, name);
222 | } else {
223 | this.helpers[name] = fn;
224 | }
225 | },
226 | unregisterHelper: function(name) {
227 | delete this.helpers[name];
228 | },
229 |
230 | registerPartial: function(name, partial) {
231 | if (toString.call(name) === objectType) {
232 | Utils.extend(this.partials, name);
233 | } else {
234 | if (typeof partial === 'undefined') {
235 | throw new Exception('Attempting to register a partial as undefined');
236 | }
237 | this.partials[name] = partial;
238 | }
239 | },
240 | unregisterPartial: function(name) {
241 | delete this.partials[name];
242 | }
243 | };
244 |
245 | function registerDefaultHelpers(instance) {
246 | instance.registerHelper('helperMissing', function(/* [args, ]options */) {
247 | if(arguments.length === 1) {
248 | // A missing field in a {{foo}} constuct.
249 | return undefined;
250 | } else {
251 | // Someone is actually trying to call something, blow up.
252 | throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
253 | }
254 | });
255 |
256 | instance.registerHelper('blockHelperMissing', function(context, options) {
257 | var inverse = options.inverse,
258 | fn = options.fn;
259 |
260 | if(context === true) {
261 | return fn(this);
262 | } else if(context === false || context == null) {
263 | return inverse(this);
264 | } else if (isArray(context)) {
265 | if(context.length > 0) {
266 | if (options.ids) {
267 | options.ids = [options.name];
268 | }
269 |
270 | return instance.helpers.each(context, options);
271 | } else {
272 | return inverse(this);
273 | }
274 | } else {
275 | if (options.data && options.ids) {
276 | var data = createFrame(options.data);
277 | data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
278 | options = {data: data};
279 | }
280 |
281 | return fn(context, options);
282 | }
283 | });
284 |
285 | instance.registerHelper('each', function(context, options) {
286 | if (!options) {
287 | throw new Exception('Must pass iterator to #each');
288 | }
289 |
290 | var fn = options.fn, inverse = options.inverse;
291 | var i = 0, ret = "", data;
292 |
293 | var contextPath;
294 | if (options.data && options.ids) {
295 | contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
296 | }
297 |
298 | if (isFunction(context)) { context = context.call(this); }
299 |
300 | if (options.data) {
301 | data = createFrame(options.data);
302 | }
303 |
304 | function execIteration(key, i, last) {
305 | if (data) {
306 | data.key = key;
307 | data.index = i;
308 | data.first = i === 0;
309 | data.last = !!last;
310 |
311 | if (contextPath) {
312 | data.contextPath = contextPath + key;
313 | }
314 | }
315 |
316 | ret = ret + fn(context[key], {
317 | data: data,
318 | blockParams: Utils.blockParams([context[key], key], [contextPath + key, null])
319 | });
320 | }
321 |
322 | if(context && typeof context === 'object') {
323 | if (isArray(context)) {
324 | for(var j = context.length; i 2) {
1070 | expected.push("'" + this.terminals_[p] + "'");
1071 | }
1072 | if (this.lexer.showPosition) {
1073 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
1074 | } else {
1075 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
1076 | }
1077 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
1078 | }
1079 | }
1080 | if (action[0] instanceof Array && action.length > 1) {
1081 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
1082 | }
1083 | switch (action[0]) {
1084 | case 1:
1085 | stack.push(symbol);
1086 | vstack.push(this.lexer.yytext);
1087 | lstack.push(this.lexer.yylloc);
1088 | stack.push(action[1]);
1089 | symbol = null;
1090 | if (!preErrorSymbol) {
1091 | yyleng = this.lexer.yyleng;
1092 | yytext = this.lexer.yytext;
1093 | yylineno = this.lexer.yylineno;
1094 | yyloc = this.lexer.yylloc;
1095 | if (recovering > 0)
1096 | recovering--;
1097 | } else {
1098 | symbol = preErrorSymbol;
1099 | preErrorSymbol = null;
1100 | }
1101 | break;
1102 | case 2:
1103 | len = this.productions_[action[1]][1];
1104 | yyval.$ = vstack[vstack.length - len];
1105 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
1106 | if (ranges) {
1107 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
1108 | }
1109 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
1110 | if (typeof r !== "undefined") {
1111 | return r;
1112 | }
1113 | if (len) {
1114 | stack = stack.slice(0, -1 * len * 2);
1115 | vstack = vstack.slice(0, -1 * len);
1116 | lstack = lstack.slice(0, -1 * len);
1117 | }
1118 | stack.push(this.productions_[action[1]][0]);
1119 | vstack.push(yyval.$);
1120 | lstack.push(yyval._$);
1121 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
1122 | stack.push(newState);
1123 | break;
1124 | case 3:
1125 | return true;
1126 | }
1127 | }
1128 | return true;
1129 | }
1130 | };
1131 | /* Jison generated lexer */
1132 | var lexer = (function(){
1133 | var lexer = ({EOF:1,
1134 | parseError:function parseError(str, hash) {
1135 | if (this.yy.parser) {
1136 | this.yy.parser.parseError(str, hash);
1137 | } else {
1138 | throw new Error(str);
1139 | }
1140 | },
1141 | setInput:function (input) {
1142 | this._input = input;
1143 | this._more = this._less = this.done = false;
1144 | this.yylineno = this.yyleng = 0;
1145 | this.yytext = this.matched = this.match = '';
1146 | this.conditionStack = ['INITIAL'];
1147 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
1148 | if (this.options.ranges) this.yylloc.range = [0,0];
1149 | this.offset = 0;
1150 | return this;
1151 | },
1152 | input:function () {
1153 | var ch = this._input[0];
1154 | this.yytext += ch;
1155 | this.yyleng++;
1156 | this.offset++;
1157 | this.match += ch;
1158 | this.matched += ch;
1159 | var lines = ch.match(/(?:\r\n?|\n).*/g);
1160 | if (lines) {
1161 | this.yylineno++;
1162 | this.yylloc.last_line++;
1163 | } else {
1164 | this.yylloc.last_column++;
1165 | }
1166 | if (this.options.ranges) this.yylloc.range[1]++;
1167 |
1168 | this._input = this._input.slice(1);
1169 | return ch;
1170 | },
1171 | unput:function (ch) {
1172 | var len = ch.length;
1173 | var lines = ch.split(/(?:\r\n?|\n)/g);
1174 |
1175 | this._input = ch + this._input;
1176 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
1177 | //this.yyleng -= len;
1178 | this.offset -= len;
1179 | var oldLines = this.match.split(/(?:\r\n?|\n)/g);
1180 | this.match = this.match.substr(0, this.match.length-1);
1181 | this.matched = this.matched.substr(0, this.matched.length-1);
1182 |
1183 | if (lines.length-1) this.yylineno -= lines.length-1;
1184 | var r = this.yylloc.range;
1185 |
1186 | this.yylloc = {first_line: this.yylloc.first_line,
1187 | last_line: this.yylineno+1,
1188 | first_column: this.yylloc.first_column,
1189 | last_column: lines ?
1190 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
1191 | this.yylloc.first_column - len
1192 | };
1193 |
1194 | if (this.options.ranges) {
1195 | this.yylloc.range = [r[0], r[0] + this.yyleng - len];
1196 | }
1197 | return this;
1198 | },
1199 | more:function () {
1200 | this._more = true;
1201 | return this;
1202 | },
1203 | less:function (n) {
1204 | this.unput(this.match.slice(n));
1205 | },
1206 | pastInput:function () {
1207 | var past = this.matched.substr(0, this.matched.length - this.match.length);
1208 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
1209 | },
1210 | upcomingInput:function () {
1211 | var next = this.match;
1212 | if (next.length < 20) {
1213 | next += this._input.substr(0, 20-next.length);
1214 | }
1215 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
1216 | },
1217 | showPosition:function () {
1218 | var pre = this.pastInput();
1219 | var c = new Array(pre.length + 1).join("-");
1220 | return pre + this.upcomingInput() + "\n" + c+"^";
1221 | },
1222 | next:function () {
1223 | if (this.done) {
1224 | return this.EOF;
1225 | }
1226 | if (!this._input) this.done = true;
1227 |
1228 | var token,
1229 | match,
1230 | tempMatch,
1231 | index,
1232 | col,
1233 | lines;
1234 | if (!this._more) {
1235 | this.yytext = '';
1236 | this.match = '';
1237 | }
1238 | var rules = this._currentRules();
1239 | for (var i=0;i < rules.length; i++) {
1240 | tempMatch = this._input.match(this.rules[rules[i]]);
1241 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
1242 | match = tempMatch;
1243 | index = i;
1244 | if (!this.options.flex) break;
1245 | }
1246 | }
1247 | if (match) {
1248 | lines = match[0].match(/(?:\r\n?|\n).*/g);
1249 | if (lines) this.yylineno += lines.length;
1250 | this.yylloc = {first_line: this.yylloc.last_line,
1251 | last_line: this.yylineno+1,
1252 | first_column: this.yylloc.last_column,
1253 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
1254 | this.yytext += match[0];
1255 | this.match += match[0];
1256 | this.matches = match;
1257 | this.yyleng = this.yytext.length;
1258 | if (this.options.ranges) {
1259 | this.yylloc.range = [this.offset, this.offset += this.yyleng];
1260 | }
1261 | this._more = false;
1262 | this._input = this._input.slice(match[0].length);
1263 | this.matched += match[0];
1264 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
1265 | if (this.done && this._input) this.done = false;
1266 | if (token) return token;
1267 | else return;
1268 | }
1269 | if (this._input === "") {
1270 | return this.EOF;
1271 | } else {
1272 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
1273 | {text: "", token: null, line: this.yylineno});
1274 | }
1275 | },
1276 | lex:function lex() {
1277 | var r = this.next();
1278 | if (typeof r !== 'undefined') {
1279 | return r;
1280 | } else {
1281 | return this.lex();
1282 | }
1283 | },
1284 | begin:function begin(condition) {
1285 | this.conditionStack.push(condition);
1286 | },
1287 | popState:function popState() {
1288 | return this.conditionStack.pop();
1289 | },
1290 | _currentRules:function _currentRules() {
1291 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
1292 | },
1293 | topState:function () {
1294 | return this.conditionStack[this.conditionStack.length-2];
1295 | },
1296 | pushState:function begin(condition) {
1297 | this.begin(condition);
1298 | }});
1299 | lexer.options = {};
1300 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
1301 |
1302 |
1303 | function strip(start, end) {
1304 | return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
1305 | }
1306 |
1307 |
1308 | var YYSTATE=YY_START
1309 | switch($avoiding_name_collisions) {
1310 | case 0:
1311 | if(yy_.yytext.slice(-2) === "\\\\") {
1312 | strip(0,1);
1313 | this.begin("mu");
1314 | } else if(yy_.yytext.slice(-1) === "\\") {
1315 | strip(0,1);
1316 | this.begin("emu");
1317 | } else {
1318 | this.begin("mu");
1319 | }
1320 | if(yy_.yytext) return 14;
1321 |
1322 | break;
1323 | case 1:return 14;
1324 | break;
1325 | case 2:
1326 | this.popState();
1327 | return 14;
1328 |
1329 | break;
1330 | case 3:
1331 | yy_.yytext = yy_.yytext.substr(5, yy_.yyleng-9);
1332 | this.popState();
1333 | return 16;
1334 |
1335 | break;
1336 | case 4: return 14;
1337 | break;
1338 | case 5:
1339 | this.popState();
1340 | return 13;
1341 |
1342 | break;
1343 | case 6:return 59;
1344 | break;
1345 | case 7:return 62;
1346 | break;
1347 | case 8: return 17;
1348 | break;
1349 | case 9:
1350 | this.popState();
1351 | this.begin('raw');
1352 | return 21;
1353 |
1354 | break;
1355 | case 10:return 53;
1356 | break;
1357 | case 11:return 27;
1358 | break;
1359 | case 12:return 45;
1360 | break;
1361 | case 13:this.popState(); return 42;
1362 | break;
1363 | case 14:this.popState(); return 42;
1364 | break;
1365 | case 15:return 32;
1366 | break;
1367 | case 16:return 37;
1368 | break;
1369 | case 17:return 49;
1370 | break;
1371 | case 18:return 46;
1372 | break;
1373 | case 19:
1374 | this.unput(yy_.yytext);
1375 | this.popState();
1376 | this.begin('com');
1377 |
1378 | break;
1379 | case 20:
1380 | this.popState();
1381 | return 13;
1382 |
1383 | break;
1384 | case 21:return 46;
1385 | break;
1386 | case 22:return 67;
1387 | break;
1388 | case 23:return 66;
1389 | break;
1390 | case 24:return 66;
1391 | break;
1392 | case 25:return 79;
1393 | break;
1394 | case 26:// ignore whitespace
1395 | break;
1396 | case 27:this.popState(); return 52;
1397 | break;
1398 | case 28:this.popState(); return 31;
1399 | break;
1400 | case 29:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 74;
1401 | break;
1402 | case 30:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 74;
1403 | break;
1404 | case 31:return 77;
1405 | break;
1406 | case 32:return 76;
1407 | break;
1408 | case 33:return 76;
1409 | break;
1410 | case 34:return 75;
1411 | break;
1412 | case 35:return 69;
1413 | break;
1414 | case 36:return 71;
1415 | break;
1416 | case 37:return 66;
1417 | break;
1418 | case 38:yy_.yytext = strip(1,2); return 66;
1419 | break;
1420 | case 39:return 'INVALID';
1421 | break;
1422 | case 40:return 5;
1423 | break;
1424 | }
1425 | };
1426 | lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]*?(?=(\{\{\{\{\/)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
1427 | lexer.conditions = {"mu":{"rules":[6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[5],"inclusive":false},"raw":{"rules":[3,4],"inclusive":false},"INITIAL":{"rules":[0,1,40],"inclusive":true}};
1428 | return lexer;})()
1429 | parser.lexer = lexer;
1430 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
1431 | return new Parser;
1432 | })();__exports__ = handlebars;
1433 | /* jshint ignore:end */
1434 | return __exports__;
1435 | })();
1436 |
1437 | // handlebars/compiler/visitor.js
1438 | var __module11__ = (function(__dependency1__, __dependency2__) {
1439 | "use strict";
1440 | var __exports__;
1441 | var Exception = __dependency1__;
1442 | var AST = __dependency2__;
1443 |
1444 | function Visitor() {
1445 | this.parents = [];
1446 | }
1447 |
1448 | Visitor.prototype = {
1449 | constructor: Visitor,
1450 | mutating: false,
1451 |
1452 | // Visits a given value. If mutating, will replace the value if necessary.
1453 | acceptKey: function(node, name) {
1454 | var value = this.accept(node[name]);
1455 | if (this.mutating) {
1456 | // Hacky sanity check:
1457 | if (value && (!value.type || !AST[value.type])) {
1458 | throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
1459 | }
1460 | node[name] = value;
1461 | }
1462 | },
1463 |
1464 | // Performs an accept operation with added sanity check to ensure
1465 | // required keys are not removed.
1466 | acceptRequired: function(node, name) {
1467 | this.acceptKey(node, name);
1468 |
1469 | if (!node[name]) {
1470 | throw new Exception(node.type + ' requires ' + name);
1471 | }
1472 | },
1473 |
1474 | // Traverses a given array. If mutating, empty respnses will be removed
1475 | // for child elements.
1476 | acceptArray: function(array) {
1477 | for (var i = 0, l = array.length; i < l; i++) {
1478 | this.acceptKey(array, i);
1479 |
1480 | if (!array[i]) {
1481 | array.splice(i, 1);
1482 | i--;
1483 | l--;
1484 | }
1485 | }
1486 | },
1487 |
1488 | accept: function(object) {
1489 | if (!object) {
1490 | return;
1491 | }
1492 |
1493 | if (this.current) {
1494 | this.parents.unshift(this.current);
1495 | }
1496 | this.current = object;
1497 |
1498 | var ret = this[object.type](object);
1499 |
1500 | this.current = this.parents.shift();
1501 |
1502 | if (!this.mutating || ret) {
1503 | return ret;
1504 | } else if (ret !== false) {
1505 | return object;
1506 | }
1507 | },
1508 |
1509 | Program: function(program) {
1510 | this.acceptArray(program.body);
1511 | },
1512 |
1513 | MustacheStatement: function(mustache) {
1514 | this.acceptRequired(mustache, 'path');
1515 | this.acceptArray(mustache.params);
1516 | this.acceptKey(mustache, 'hash');
1517 | },
1518 |
1519 | BlockStatement: function(block) {
1520 | this.acceptRequired(block, 'path');
1521 | this.acceptArray(block.params);
1522 | this.acceptKey(block, 'hash');
1523 |
1524 | this.acceptKey(block, 'program');
1525 | this.acceptKey(block, 'inverse');
1526 | },
1527 |
1528 | PartialStatement: function(partial) {
1529 | this.acceptRequired(partial, 'name');
1530 | this.acceptArray(partial.params);
1531 | this.acceptKey(partial, 'hash');
1532 | },
1533 |
1534 | ContentStatement: function(/* content */) {},
1535 | CommentStatement: function(/* comment */) {},
1536 |
1537 | SubExpression: function(sexpr) {
1538 | this.acceptRequired(sexpr, 'path');
1539 | this.acceptArray(sexpr.params);
1540 | this.acceptKey(sexpr, 'hash');
1541 | },
1542 | PartialExpression: function(partial) {
1543 | this.acceptRequired(partial, 'name');
1544 | this.acceptArray(partial.params);
1545 | this.acceptKey(partial, 'hash');
1546 | },
1547 |
1548 | PathExpression: function(/* path */) {},
1549 |
1550 | StringLiteral: function(/* string */) {},
1551 | NumberLiteral: function(/* number */) {},
1552 | BooleanLiteral: function(/* bool */) {},
1553 |
1554 | Hash: function(hash) {
1555 | this.acceptArray(hash.pairs);
1556 | },
1557 | HashPair: function(pair) {
1558 | this.acceptRequired(pair, 'value');
1559 | }
1560 | };
1561 |
1562 | __exports__ = Visitor;
1563 | return __exports__;
1564 | })(__module4__, __module7__);
1565 |
1566 | // handlebars/compiler/whitespace-control.js
1567 | var __module10__ = (function(__dependency1__) {
1568 | "use strict";
1569 | var __exports__;
1570 | var Visitor = __dependency1__;
1571 |
1572 | function WhitespaceControl() {
1573 | }
1574 | WhitespaceControl.prototype = new Visitor();
1575 |
1576 | WhitespaceControl.prototype.Program = function(program) {
1577 | var isRoot = !this.isRootSeen;
1578 | this.isRootSeen = true;
1579 |
1580 | var body = program.body;
1581 | for (var i = 0, l = body.length; i < l; i++) {
1582 | var current = body[i],
1583 | strip = this.accept(current);
1584 |
1585 | if (!strip) {
1586 | continue;
1587 | }
1588 |
1589 | var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
1590 | _isNextWhitespace = isNextWhitespace(body, i, isRoot),
1591 |
1592 | openStandalone = strip.openStandalone && _isPrevWhitespace,
1593 | closeStandalone = strip.closeStandalone && _isNextWhitespace,
1594 | inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
1595 |
1596 | if (strip.close) {
1597 | omitRight(body, i, true);
1598 | }
1599 | if (strip.open) {
1600 | omitLeft(body, i, true);
1601 | }
1602 |
1603 | if (inlineStandalone) {
1604 | omitRight(body, i);
1605 |
1606 | if (omitLeft(body, i)) {
1607 | // If we are on a standalone node, save the indent info for partials
1608 | if (current.type === 'PartialStatement') {
1609 | // Pull out the whitespace from the final line
1610 | current.indent = (/([ \t]+$)/).exec(body[i-1].original)[1];
1611 | }
1612 | }
1613 | }
1614 | if (openStandalone) {
1615 | omitRight((current.program || current.inverse).body);
1616 |
1617 | // Strip out the previous content node if it's whitespace only
1618 | omitLeft(body, i);
1619 | }
1620 | if (closeStandalone) {
1621 | // Always strip the next node
1622 | omitRight(body, i);
1623 |
1624 | omitLeft((current.inverse || current.program).body);
1625 | }
1626 | }
1627 |
1628 | return program;
1629 | };
1630 | WhitespaceControl.prototype.BlockStatement = function(block) {
1631 | this.accept(block.program);
1632 | this.accept(block.inverse);
1633 |
1634 | // Find the inverse program that is involed with whitespace stripping.
1635 | var program = block.program || block.inverse,
1636 | inverse = block.program && block.inverse,
1637 | firstInverse = inverse,
1638 | lastInverse = inverse;
1639 |
1640 | if (inverse && inverse.chained) {
1641 | firstInverse = inverse.body[0].program;
1642 |
1643 | // Walk the inverse chain to find the last inverse that is actually in the chain.
1644 | while (lastInverse.chained) {
1645 | lastInverse = lastInverse.body[lastInverse.body.length-1].program;
1646 | }
1647 | }
1648 |
1649 | var strip = {
1650 | open: block.openStrip.open,
1651 | close: block.closeStrip.close,
1652 |
1653 | // Determine the standalone candiacy. Basically flag our content as being possibly standalone
1654 | // so our parent can determine if we actually are standalone
1655 | openStandalone: isNextWhitespace(program.body),
1656 | closeStandalone: isPrevWhitespace((firstInverse || program).body)
1657 | };
1658 |
1659 | if (block.openStrip.close) {
1660 | omitRight(program.body, null, true);
1661 | }
1662 |
1663 | if (inverse) {
1664 | var inverseStrip = block.inverseStrip;
1665 |
1666 | if (inverseStrip.open) {
1667 | omitLeft(program.body, null, true);
1668 | }
1669 |
1670 | if (inverseStrip.close) {
1671 | omitRight(firstInverse.body, null, true);
1672 | }
1673 | if (block.closeStrip.open) {
1674 | omitLeft(lastInverse.body, null, true);
1675 | }
1676 |
1677 | // Find standalone else statments
1678 | if (isPrevWhitespace(program.body)
1679 | && isNextWhitespace(firstInverse.body)) {
1680 |
1681 | omitLeft(program.body);
1682 | omitRight(firstInverse.body);
1683 | }
1684 | } else {
1685 | if (block.closeStrip.open) {
1686 | omitLeft(program.body, null, true);
1687 | }
1688 | }
1689 |
1690 | return strip;
1691 | };
1692 |
1693 | WhitespaceControl.prototype.MustacheStatement = function(mustache) {
1694 | return mustache.strip;
1695 | };
1696 |
1697 | WhitespaceControl.prototype.PartialStatement =
1698 | WhitespaceControl.prototype.CommentStatement = function(node) {
1699 | /* istanbul ignore next */
1700 | var strip = node.strip || {};
1701 | return {
1702 | inlineStandalone: true,
1703 | open: strip.open,
1704 | close: strip.close
1705 | };
1706 | };
1707 |
1708 |
1709 | function isPrevWhitespace(body, i, isRoot) {
1710 | if (i === undefined) {
1711 | i = body.length;
1712 | }
1713 |
1714 | // Nodes that end with newlines are considered whitespace (but are special
1715 | // cased for strip operations)
1716 | var prev = body[i-1],
1717 | sibling = body[i-2];
1718 | if (!prev) {
1719 | return isRoot;
1720 | }
1721 |
1722 | if (prev.type === 'ContentStatement') {
1723 | return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
1724 | }
1725 | }
1726 | function isNextWhitespace(body, i, isRoot) {
1727 | if (i === undefined) {
1728 | i = -1;
1729 | }
1730 |
1731 | var next = body[i+1],
1732 | sibling = body[i+2];
1733 | if (!next) {
1734 | return isRoot;
1735 | }
1736 |
1737 | if (next.type === 'ContentStatement') {
1738 | return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
1739 | }
1740 | }
1741 |
1742 | // Marks the node to the right of the position as omitted.
1743 | // I.e. {{foo}}' ' will mark the ' ' node as omitted.
1744 | //
1745 | // If i is undefined, then the first child will be marked as such.
1746 | //
1747 | // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
1748 | // content is met.
1749 | function omitRight(body, i, multiple) {
1750 | var current = body[i == null ? 0 : i + 1];
1751 | if (!current || current.type !== 'ContentStatement' || (!multiple && current.rightStripped)) {
1752 | return;
1753 | }
1754 |
1755 | var original = current.value;
1756 | current.value = current.value.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
1757 | current.rightStripped = current.value !== original;
1758 | }
1759 |
1760 | // Marks the node to the left of the position as omitted.
1761 | // I.e. ' '{{foo}} will mark the ' ' node as omitted.
1762 | //
1763 | // If i is undefined then the last child will be marked as such.
1764 | //
1765 | // If mulitple is truthy then all whitespace will be stripped out until non-whitespace
1766 | // content is met.
1767 | function omitLeft(body, i, multiple) {
1768 | var current = body[i == null ? body.length - 1 : i - 1];
1769 | if (!current || current.type !== 'ContentStatement' || (!multiple && current.leftStripped)) {
1770 | return;
1771 | }
1772 |
1773 | // We omit the last node if it's whitespace only and not preceeded by a non-content node.
1774 | var original = current.value;
1775 | current.value = current.value.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
1776 | current.leftStripped = current.value !== original;
1777 | return current.leftStripped;
1778 | }
1779 |
1780 | __exports__ = WhitespaceControl;
1781 | return __exports__;
1782 | })(__module11__);
1783 |
1784 | // handlebars/compiler/helpers.js
1785 | var __module12__ = (function(__dependency1__) {
1786 | "use strict";
1787 | var __exports__ = {};
1788 | var Exception = __dependency1__;
1789 |
1790 | function SourceLocation(source, locInfo) {
1791 | this.source = source;
1792 | this.start = {
1793 | line: locInfo.first_line,
1794 | column: locInfo.first_column
1795 | };
1796 | this.end = {
1797 | line: locInfo.last_line,
1798 | column: locInfo.last_column
1799 | };
1800 | }
1801 |
1802 | __exports__.SourceLocation = SourceLocation;function stripFlags(open, close) {
1803 | return {
1804 | open: open.charAt(2) === '~',
1805 | close: close.charAt(close.length-3) === '~'
1806 | };
1807 | }
1808 |
1809 | __exports__.stripFlags = stripFlags;function stripComment(comment) {
1810 | return comment.replace(/^\{\{~?\!-?-?/, '')
1811 | .replace(/-?-?~?\}\}$/, '');
1812 | }
1813 |
1814 | __exports__.stripComment = stripComment;function preparePath(data, parts, locInfo) {
1815 | /*jshint -W040 */
1816 | locInfo = this.locInfo(locInfo);
1817 |
1818 | var original = data ? '@' : '',
1819 | dig = [],
1820 | depth = 0,
1821 | depthString = '';
1822 |
1823 | for(var i=0,l=parts.length; i 0) {
1829 | throw new Exception('Invalid path: ' + original, {loc: locInfo});
1830 | } else if (part === '..') {
1831 | depth++;
1832 | depthString += '../';
1833 | }
1834 | } else {
1835 | dig.push(part);
1836 | }
1837 | }
1838 |
1839 | return new this.PathExpression(data, depth, dig, original, locInfo);
1840 | }
1841 |
1842 | __exports__.preparePath = preparePath;function prepareMustache(path, params, hash, open, strip, locInfo) {
1843 | /*jshint -W040 */
1844 | // Must use charAt to support IE pre-10
1845 | var escapeFlag = open.charAt(3) || open.charAt(2),
1846 | escaped = escapeFlag !== '{' && escapeFlag !== '&';
1847 |
1848 | return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
1849 | }
1850 |
1851 | __exports__.prepareMustache = prepareMustache;function prepareRawBlock(openRawBlock, content, close, locInfo) {
1852 | /*jshint -W040 */
1853 | if (openRawBlock.path.original !== close) {
1854 | var errorNode = {loc: openRawBlock.path.loc};
1855 |
1856 | throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
1857 | }
1858 |
1859 | locInfo = this.locInfo(locInfo);
1860 | var program = new this.Program([content], null, {}, locInfo);
1861 |
1862 | return new this.BlockStatement(
1863 | openRawBlock.path, openRawBlock.params, openRawBlock.hash,
1864 | program, undefined,
1865 | {}, {}, {},
1866 | locInfo);
1867 | }
1868 |
1869 | __exports__.prepareRawBlock = prepareRawBlock;function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
1870 | /*jshint -W040 */
1871 | // When we are chaining inverse calls, we will not have a close path
1872 | if (close && close.path && openBlock.path.original !== close.path.original) {
1873 | var errorNode = {loc: openBlock.path.loc};
1874 |
1875 | throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
1876 | }
1877 |
1878 | program.blockParams = openBlock.blockParams;
1879 |
1880 | var inverse,
1881 | inverseStrip;
1882 |
1883 | if (inverseAndProgram) {
1884 | if (inverseAndProgram.chain) {
1885 | inverseAndProgram.program.body[0].closeStrip = close.strip;
1886 | }
1887 |
1888 | inverseStrip = inverseAndProgram.strip;
1889 | inverse = inverseAndProgram.program;
1890 | }
1891 |
1892 | if (inverted) {
1893 | inverted = inverse;
1894 | inverse = program;
1895 | program = inverted;
1896 | }
1897 |
1898 | return new this.BlockStatement(
1899 | openBlock.path, openBlock.params, openBlock.hash,
1900 | program, inverse,
1901 | openBlock.strip, inverseStrip, close && close.strip,
1902 | this.locInfo(locInfo));
1903 | }
1904 |
1905 | __exports__.prepareBlock = prepareBlock;
1906 | return __exports__;
1907 | })(__module4__);
1908 |
1909 | // handlebars/compiler/base.js
1910 | var __module8__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
1911 | "use strict";
1912 | var __exports__ = {};
1913 | var parser = __dependency1__;
1914 | var AST = __dependency2__;
1915 | var WhitespaceControl = __dependency3__;
1916 | var Helpers = __dependency4__;
1917 | var extend = __dependency5__.extend;
1918 |
1919 | __exports__.parser = parser;
1920 |
1921 | var yy = {};
1922 | extend(yy, Helpers, AST);
1923 |
1924 | function parse(input, options) {
1925 | // Just return if an already-compiled AST was passed in.
1926 | if (input.type === 'Program') { return input; }
1927 |
1928 | parser.yy = yy;
1929 |
1930 | // Altering the shared object here, but this is ok as parser is a sync operation
1931 | yy.locInfo = function(locInfo) {
1932 | return new yy.SourceLocation(options && options.srcName, locInfo);
1933 | };
1934 |
1935 | var strip = new WhitespaceControl();
1936 | return strip.accept(parser.parse(input));
1937 | }
1938 |
1939 | __exports__.parse = parse;
1940 | return __exports__;
1941 | })(__module9__, __module7__, __module10__, __module12__, __module3__);
1942 |
1943 | // handlebars/compiler/compiler.js
1944 | var __module13__ = (function(__dependency1__, __dependency2__, __dependency3__) {
1945 | "use strict";
1946 | var __exports__ = {};
1947 | var Exception = __dependency1__;
1948 | var isArray = __dependency2__.isArray;
1949 | var indexOf = __dependency2__.indexOf;
1950 | var AST = __dependency3__;
1951 |
1952 | var slice = [].slice;
1953 |
1954 |
1955 | function Compiler() {}
1956 |
1957 | __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
1958 | // function in a context. This is necessary for mustache compatibility, which
1959 | // requires that context functions in blocks are evaluated by blockHelperMissing,
1960 | // and then proceed as if the resulting value was provided to blockHelperMissing.
1961 |
1962 | Compiler.prototype = {
1963 | compiler: Compiler,
1964 |
1965 | equals: function(other) {
1966 | var len = this.opcodes.length;
1967 | if (other.opcodes.length !== len) {
1968 | return false;
1969 | }
1970 |
1971 | for (var i = 0; i < len; i++) {
1972 | var opcode = this.opcodes[i],
1973 | otherOpcode = other.opcodes[i];
1974 | if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
1975 | return false;
1976 | }
1977 | }
1978 |
1979 | // We know that length is the same between the two arrays because they are directly tied
1980 | // to the opcode behavior above.
1981 | len = this.children.length;
1982 | for (i = 0; i < len; i++) {
1983 | if (!this.children[i].equals(other.children[i])) {
1984 | return false;
1985 | }
1986 | }
1987 |
1988 | return true;
1989 | },
1990 |
1991 | guid: 0,
1992 |
1993 | compile: function(program, options) {
1994 | this.sourceNode = [];
1995 | this.opcodes = [];
1996 | this.children = [];
1997 | this.options = options;
1998 | this.stringParams = options.stringParams;
1999 | this.trackIds = options.trackIds;
2000 |
2001 | options.blockParams = options.blockParams || [];
2002 |
2003 | // These changes will propagate to the other compiler components
2004 | var knownHelpers = options.knownHelpers;
2005 | options.knownHelpers = {
2006 | 'helperMissing': true,
2007 | 'blockHelperMissing': true,
2008 | 'each': true,
2009 | 'if': true,
2010 | 'unless': true,
2011 | 'with': true,
2012 | 'log': true,
2013 | 'lookup': true
2014 | };
2015 | if (knownHelpers) {
2016 | for (var name in knownHelpers) {
2017 | options.knownHelpers[name] = knownHelpers[name];
2018 | }
2019 | }
2020 |
2021 | return this.accept(program);
2022 | },
2023 |
2024 | compileProgram: function(program) {
2025 | var result = new this.compiler().compile(program, this.options);
2026 | var guid = this.guid++;
2027 |
2028 | this.usePartial = this.usePartial || result.usePartial;
2029 |
2030 | this.children[guid] = result;
2031 | this.useDepths = this.useDepths || result.useDepths;
2032 |
2033 | return guid;
2034 | },
2035 |
2036 | accept: function(node) {
2037 | this.sourceNode.unshift(node);
2038 | var ret = this[node.type](node);
2039 | this.sourceNode.shift();
2040 | return ret;
2041 | },
2042 |
2043 | Program: function(program) {
2044 | this.options.blockParams.unshift(program.blockParams);
2045 |
2046 | var body = program.body;
2047 | for(var i=0, l=body.length; i 1) {
2100 | throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
2101 | } else if (!params.length) {
2102 | params.push({type: 'PathExpression', parts: [], depth: 0});
2103 | }
2104 |
2105 | var partialName = partial.name.original,
2106 | isDynamic = partial.name.type === 'SubExpression';
2107 | if (isDynamic) {
2108 | this.accept(partial.name);
2109 | }
2110 |
2111 | this.setupFullMustacheParams(partial, undefined, undefined, true);
2112 |
2113 | var indent = partial.indent || '';
2114 | if (this.options.preventIndent && indent) {
2115 | this.opcode('appendContent', indent);
2116 | indent = '';
2117 | }
2118 |
2119 | this.opcode('invokePartial', isDynamic, partialName, indent);
2120 | this.opcode('append');
2121 | },
2122 |
2123 | MustacheStatement: function(mustache) {
2124 | this.SubExpression(mustache);
2125 |
2126 | if(mustache.escaped && !this.options.noEscape) {
2127 | this.opcode('appendEscaped');
2128 | } else {
2129 | this.opcode('append');
2130 | }
2131 | },
2132 |
2133 | ContentStatement: function(content) {
2134 | if (content.value) {
2135 | this.opcode('appendContent', content.value);
2136 | }
2137 | },
2138 |
2139 | CommentStatement: function() {},
2140 |
2141 | SubExpression: function(sexpr) {
2142 | transformLiteralToPath(sexpr);
2143 | var type = this.classifySexpr(sexpr);
2144 |
2145 | if (type === 'simple') {
2146 | this.simpleSexpr(sexpr);
2147 | } else if (type === 'helper') {
2148 | this.helperSexpr(sexpr);
2149 | } else {
2150 | this.ambiguousSexpr(sexpr);
2151 | }
2152 | },
2153 | ambiguousSexpr: function(sexpr, program, inverse) {
2154 | var path = sexpr.path,
2155 | name = path.parts[0],
2156 | isBlock = program != null || inverse != null;
2157 |
2158 | this.opcode('getContext', path.depth);
2159 |
2160 | this.opcode('pushProgram', program);
2161 | this.opcode('pushProgram', inverse);
2162 |
2163 | this.accept(path);
2164 |
2165 | this.opcode('invokeAmbiguous', name, isBlock);
2166 | },
2167 |
2168 | simpleSexpr: function(sexpr) {
2169 | this.accept(sexpr.path);
2170 | this.opcode('resolvePossibleLambda');
2171 | },
2172 |
2173 | helperSexpr: function(sexpr, program, inverse) {
2174 | var params = this.setupFullMustacheParams(sexpr, program, inverse),
2175 | path = sexpr.path,
2176 | name = path.parts[0];
2177 |
2178 | if (this.options.knownHelpers[name]) {
2179 | this.opcode('invokeKnownHelper', params.length, name);
2180 | } else if (this.options.knownHelpersOnly) {
2181 | throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2182 | } else {
2183 | path.falsy = true;
2184 |
2185 | this.accept(path);
2186 | this.opcode('invokeHelper', params.length, path.original, AST.helpers.simpleId(path));
2187 | }
2188 | },
2189 |
2190 | PathExpression: function(path) {
2191 | this.addDepth(path.depth);
2192 | this.opcode('getContext', path.depth);
2193 |
2194 | var name = path.parts[0],
2195 | scoped = AST.helpers.scopedId(path),
2196 | blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
2197 |
2198 | if (blockParamId) {
2199 | this.opcode('lookupBlockParam', blockParamId, path.parts);
2200 | } else if (!name) {
2201 | // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
2202 | this.opcode('pushContext');
2203 | } else if (path.data) {
2204 | this.options.data = true;
2205 | this.opcode('lookupData', path.depth, path.parts);
2206 | } else {
2207 | this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
2208 | }
2209 | },
2210 |
2211 | StringLiteral: function(string) {
2212 | this.opcode('pushString', string.value);
2213 | },
2214 |
2215 | NumberLiteral: function(number) {
2216 | this.opcode('pushLiteral', number.value);
2217 | },
2218 |
2219 | BooleanLiteral: function(bool) {
2220 | this.opcode('pushLiteral', bool.value);
2221 | },
2222 |
2223 | Hash: function(hash) {
2224 | var pairs = hash.pairs, i, l;
2225 |
2226 | this.opcode('pushHash');
2227 |
2228 | for (i=0, l=pairs.length; i= 0) {
2355 | return [depth, param];
2356 | }
2357 | }
2358 | }
2359 | };
2360 |
2361 | function precompile(input, options, env) {
2362 | if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
2363 | throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
2364 | }
2365 |
2366 | options = options || {};
2367 | if (!('data' in options)) {
2368 | options.data = true;
2369 | }
2370 | if (options.compat) {
2371 | options.useDepths = true;
2372 | }
2373 |
2374 | var ast = env.parse(input, options);
2375 | var environment = new env.Compiler().compile(ast, options);
2376 | return new env.JavaScriptCompiler().compile(environment, options);
2377 | }
2378 |
2379 | __exports__.precompile = precompile;function compile(input, options, env) {
2380 | if (input == null || (typeof input !== 'string' && input.type !== 'Program')) {
2381 | throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
2382 | }
2383 |
2384 | options = options || {};
2385 |
2386 | if (!('data' in options)) {
2387 | options.data = true;
2388 | }
2389 | if (options.compat) {
2390 | options.useDepths = true;
2391 | }
2392 |
2393 | var compiled;
2394 |
2395 | function compileInput() {
2396 | var ast = env.parse(input, options);
2397 | var environment = new env.Compiler().compile(ast, options);
2398 | var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
2399 | return env.template(templateSpec);
2400 | }
2401 |
2402 | // Template is only compiled on first use and cached after that point.
2403 | var ret = function(context, options) {
2404 | if (!compiled) {
2405 | compiled = compileInput();
2406 | }
2407 | return compiled.call(this, context, options);
2408 | };
2409 | ret._setup = function(options) {
2410 | if (!compiled) {
2411 | compiled = compileInput();
2412 | }
2413 | return compiled._setup(options);
2414 | };
2415 | ret._child = function(i, data, blockParams, depths) {
2416 | if (!compiled) {
2417 | compiled = compileInput();
2418 | }
2419 | return compiled._child(i, data, blockParams, depths);
2420 | };
2421 | return ret;
2422 | }
2423 |
2424 | __exports__.compile = compile;function argEquals(a, b) {
2425 | if (a === b) {
2426 | return true;
2427 | }
2428 |
2429 | if (isArray(a) && isArray(b) && a.length === b.length) {
2430 | for (var i = 0; i < a.length; i++) {
2431 | if (!argEquals(a[i], b[i])) {
2432 | return false;
2433 | }
2434 | }
2435 | return true;
2436 | }
2437 | }
2438 |
2439 | function transformLiteralToPath(sexpr) {
2440 | if (!sexpr.path.parts) {
2441 | var literal = sexpr.path;
2442 | // Casting to string here to make false and 0 literal values play nicely with the rest
2443 | // of the system.
2444 | sexpr.path = new AST.PathExpression(false, 0, [literal.original+''], literal.original+'', literal.log);
2445 | }
2446 | }
2447 | return __exports__;
2448 | })(__module4__, __module3__, __module7__);
2449 |
2450 | // handlebars/compiler/code-gen.js
2451 | var __module15__ = (function(__dependency1__) {
2452 | "use strict";
2453 | var __exports__;
2454 | var isArray = __dependency1__.isArray;
2455 |
2456 | try {
2457 | var SourceMap = require('source-map'),
2458 | SourceNode = SourceMap.SourceNode;
2459 | } catch (err) {
2460 | /* istanbul ignore next: tested but not covered in istanbul due to dist build */
2461 | SourceNode = function(line, column, srcFile, chunks) {
2462 | this.src = '';
2463 | if (chunks) {
2464 | this.add(chunks);
2465 | }
2466 | };
2467 | /* istanbul ignore next */
2468 | SourceNode.prototype = {
2469 | add: function(chunks) {
2470 | if (isArray(chunks)) {
2471 | chunks = chunks.join('');
2472 | }
2473 | this.src += chunks;
2474 | },
2475 | prepend: function(chunks) {
2476 | if (isArray(chunks)) {
2477 | chunks = chunks.join('');
2478 | }
2479 | this.src = chunks + this.src;
2480 | },
2481 | toStringWithSourceMap: function() {
2482 | return {code: this.toString()};
2483 | },
2484 | toString: function() {
2485 | return this.src;
2486 | }
2487 | };
2488 | }
2489 |
2490 |
2491 | function castChunk(chunk, codeGen, loc) {
2492 | if (isArray(chunk)) {
2493 | var ret = [];
2494 |
2495 | for (var i = 0, len = chunk.length; i < len; i++) {
2496 | ret.push(codeGen.wrap(chunk[i], loc));
2497 | }
2498 | return ret;
2499 | } else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
2500 | // Handle primitives that the SourceNode will throw up on
2501 | return chunk+'';
2502 | }
2503 | return chunk;
2504 | }
2505 |
2506 |
2507 | function CodeGen(srcFile) {
2508 | this.srcFile = srcFile;
2509 | this.source = [];
2510 | }
2511 |
2512 | CodeGen.prototype = {
2513 | prepend: function(source, loc) {
2514 | this.source.unshift(this.wrap(source, loc));
2515 | },
2516 | push: function(source, loc) {
2517 | this.source.push(this.wrap(source, loc));
2518 | },
2519 |
2520 | merge: function() {
2521 | var source = this.empty();
2522 | this.each(function(line) {
2523 | source.add([' ', line, '\n']);
2524 | });
2525 | return source;
2526 | },
2527 |
2528 | each: function(iter) {
2529 | for (var i = 0, len = this.source.length; i < len; i++) {
2530 | iter(this.source[i]);
2531 | }
2532 | },
2533 |
2534 | empty: function(loc) {
2535 | loc = loc || this.currentLocation || {start:{}};
2536 | return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
2537 | },
2538 | wrap: function(chunk, loc) {
2539 | if (chunk instanceof SourceNode) {
2540 | return chunk;
2541 | }
2542 |
2543 | loc = loc || this.currentLocation || {start:{}};
2544 | chunk = castChunk(chunk, this, loc);
2545 |
2546 | return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
2547 | },
2548 |
2549 | functionCall: function(fn, type, params) {
2550 | params = this.generateList(params);
2551 | return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
2552 | },
2553 |
2554 | quotedString: function(str) {
2555 | return '"' + (str + '')
2556 | .replace(/\\/g, '\\\\')
2557 | .replace(/"/g, '\\"')
2558 | .replace(/\n/g, '\\n')
2559 | .replace(/\r/g, '\\r')
2560 | .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
2561 | .replace(/\u2029/g, '\\u2029') + '"';
2562 | },
2563 |
2564 | objectLiteral: function(obj) {
2565 | var pairs = [];
2566 |
2567 | for (var key in obj) {
2568 | if (obj.hasOwnProperty(key)) {
2569 | var value = castChunk(obj[key], this);
2570 | if (value !== 'undefined') {
2571 | pairs.push([this.quotedString(key), ':', value]);
2572 | }
2573 | }
2574 | }
2575 |
2576 | var ret = this.generateList(pairs);
2577 | ret.prepend('{');
2578 | ret.add('}');
2579 | return ret;
2580 | },
2581 |
2582 |
2583 | generateList: function(entries, loc) {
2584 | var ret = this.empty(loc);
2585 |
2586 | for (var i = 0, len = entries.length; i < len; i++) {
2587 | if (i) {
2588 | ret.add(',');
2589 | }
2590 |
2591 | ret.add(castChunk(entries[i], this, loc));
2592 | }
2593 |
2594 | return ret;
2595 | },
2596 |
2597 | generateArray: function(entries, loc) {
2598 | var ret = this.generateList(entries, loc);
2599 | ret.prepend('[');
2600 | ret.add(']');
2601 |
2602 | return ret;
2603 | }
2604 | };
2605 |
2606 | __exports__ = CodeGen;
2607 | return __exports__;
2608 | })(__module3__);
2609 |
2610 | // handlebars/compiler/javascript-compiler.js
2611 | var __module14__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) {
2612 | "use strict";
2613 | var __exports__;
2614 | var COMPILER_REVISION = __dependency1__.COMPILER_REVISION;
2615 | var REVISION_CHANGES = __dependency1__.REVISION_CHANGES;
2616 | var Exception = __dependency2__;
2617 | var isArray = __dependency3__.isArray;
2618 | var CodeGen = __dependency4__;
2619 |
2620 | function Literal(value) {
2621 | this.value = value;
2622 | }
2623 |
2624 | function JavaScriptCompiler() {}
2625 |
2626 | JavaScriptCompiler.prototype = {
2627 | // PUBLIC API: You can override these methods in a subclass to provide
2628 | // alternative compiled forms for name lookup and buffering semantics
2629 | nameLookup: function(parent, name /* , type*/) {
2630 | if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
2631 | return [parent, ".", name];
2632 | } else {
2633 | return [parent, "['", name, "']"];
2634 | }
2635 | },
2636 | depthedLookup: function(name) {
2637 | return [this.aliasable('this.lookup'), '(depths, "', name, '")'];
2638 | },
2639 |
2640 | compilerInfo: function() {
2641 | var revision = COMPILER_REVISION,
2642 | versions = REVISION_CHANGES[revision];
2643 | return [revision, versions];
2644 | },
2645 |
2646 | appendToBuffer: function(source, location, explicit) {
2647 | // Force a source as this simplifies the merge logic.
2648 | if (!isArray(source)) {
2649 | source = [source];
2650 | }
2651 | source = this.source.wrap(source, location);
2652 |
2653 | if (this.environment.isSimple) {
2654 | return ['return ', source, ';'];
2655 | } else if (explicit) {
2656 | // This is a case where the buffer operation occurs as a child of another
2657 | // construct, generally braces. We have to explicitly output these buffer
2658 | // operations to ensure that the emitted code goes in the correct location.
2659 | return ['buffer += ', source, ';'];
2660 | } else {
2661 | source.appendToBuffer = true;
2662 | return source;
2663 | }
2664 | },
2665 |
2666 | initializeBuffer: function() {
2667 | return this.quotedString("");
2668 | },
2669 | // END PUBLIC API
2670 |
2671 | compile: function(environment, options, context, asObject) {
2672 | this.environment = environment;
2673 | this.options = options;
2674 | this.stringParams = this.options.stringParams;
2675 | this.trackIds = this.options.trackIds;
2676 | this.precompile = !asObject;
2677 |
2678 | this.name = this.environment.name;
2679 | this.isChild = !!context;
2680 | this.context = context || {
2681 | programs: [],
2682 | environments: []
2683 | };
2684 |
2685 | this.preamble();
2686 |
2687 | this.stackSlot = 0;
2688 | this.stackVars = [];
2689 | this.aliases = {};
2690 | this.registers = { list: [] };
2691 | this.hashes = [];
2692 | this.compileStack = [];
2693 | this.inlineStack = [];
2694 | this.blockParams = [];
2695 |
2696 | this.compileChildren(environment, options);
2697 |
2698 | this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
2699 | this.useBlockParams = this.useBlockParams || environment.useBlockParams;
2700 |
2701 | var opcodes = environment.opcodes,
2702 | opcode,
2703 | firstLoc,
2704 | i,
2705 | l;
2706 |
2707 | for (i = 0, l = opcodes.length; i < l; i++) {
2708 | opcode = opcodes[i];
2709 |
2710 | this.source.currentLocation = opcode.loc;
2711 | firstLoc = firstLoc || opcode.loc;
2712 | this[opcode.opcode].apply(this, opcode.args);
2713 | }
2714 |
2715 | // Flush any trailing content that might be pending.
2716 | this.source.currentLocation = firstLoc;
2717 | this.pushSource('');
2718 |
2719 | /* istanbul ignore next */
2720 | if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
2721 | throw new Exception('Compile completed with content left on stack');
2722 | }
2723 |
2724 | var fn = this.createFunctionContext(asObject);
2725 | if (!this.isChild) {
2726 | var ret = {
2727 | compiler: this.compilerInfo(),
2728 | main: fn
2729 | };
2730 | var programs = this.context.programs;
2731 | for (i = 0, l = programs.length; i < l; i++) {
2732 | if (programs[i]) {
2733 | ret[i] = programs[i];
2734 | }
2735 | }
2736 |
2737 | if (this.environment.usePartial) {
2738 | ret.usePartial = true;
2739 | }
2740 | if (this.options.data) {
2741 | ret.useData = true;
2742 | }
2743 | if (this.useDepths) {
2744 | ret.useDepths = true;
2745 | }
2746 | if (this.useBlockParams) {
2747 | ret.useBlockParams = true;
2748 | }
2749 | if (this.options.compat) {
2750 | ret.compat = true;
2751 | }
2752 |
2753 | if (!asObject) {
2754 | ret.compiler = JSON.stringify(ret.compiler);
2755 |
2756 | this.source.currentLocation = {start: {line: 1, column: 0}};
2757 | ret = this.objectLiteral(ret);
2758 |
2759 | if (options.srcName) {
2760 | ret = ret.toStringWithSourceMap({file: options.destName});
2761 | ret.map = ret.map && ret.map.toString();
2762 | } else {
2763 | ret = ret.toString();
2764 | }
2765 | } else {
2766 | ret.compilerOptions = this.options;
2767 | }
2768 |
2769 | return ret;
2770 | } else {
2771 | return fn;
2772 | }
2773 | },
2774 |
2775 | preamble: function() {
2776 | // track the last context pushed into place to allow skipping the
2777 | // getContext opcode when it would be a noop
2778 | this.lastContext = 0;
2779 | this.source = new CodeGen(this.options.srcName);
2780 | },
2781 |
2782 | createFunctionContext: function(asObject) {
2783 | var varDeclarations = '';
2784 |
2785 | var locals = this.stackVars.concat(this.registers.list);
2786 | if(locals.length > 0) {
2787 | varDeclarations += ", " + locals.join(", ");
2788 | }
2789 |
2790 | // Generate minimizer alias mappings
2791 | //
2792 | // When using true SourceNodes, this will update all references to the given alias
2793 | // as the source nodes are reused in situ. For the non-source node compilation mode,
2794 | // aliases will not be used, but this case is already being run on the client and
2795 | // we aren't concern about minimizing the template size.
2796 | var aliasCount = 0;
2797 | for (var alias in this.aliases) {
2798 | var node = this.aliases[alias];
2799 |
2800 | if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
2801 | varDeclarations += ', alias' + (++aliasCount) + '=' + alias;
2802 | node.children[0] = 'alias' + aliasCount;
2803 | }
2804 | }
2805 |
2806 | var params = ["depth0", "helpers", "partials", "data"];
2807 |
2808 | if (this.useBlockParams || this.useDepths) {
2809 | params.push('blockParams');
2810 | }
2811 | if (this.useDepths) {
2812 | params.push('depths');
2813 | }
2814 |
2815 | // Perform a second pass over the output to merge content when possible
2816 | var source = this.mergeSource(varDeclarations);
2817 |
2818 | if (asObject) {
2819 | params.push(source);
2820 |
2821 | return Function.apply(this, params);
2822 | } else {
2823 | return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']);
2824 | }
2825 | },
2826 | mergeSource: function(varDeclarations) {
2827 | var isSimple = this.environment.isSimple,
2828 | appendOnly = !this.forceBuffer,
2829 | appendFirst,
2830 |
2831 | sourceSeen,
2832 | bufferStart,
2833 | bufferEnd;
2834 | this.source.each(function(line) {
2835 | if (line.appendToBuffer) {
2836 | if (bufferStart) {
2837 | line.prepend(' + ');
2838 | } else {
2839 | bufferStart = line;
2840 | }
2841 | bufferEnd = line;
2842 | } else {
2843 | if (bufferStart) {
2844 | if (!sourceSeen) {
2845 | appendFirst = true;
2846 | } else {
2847 | bufferStart.prepend('buffer += ');
2848 | }
2849 | bufferEnd.add(';');
2850 | bufferStart = bufferEnd = undefined;
2851 | }
2852 |
2853 | sourceSeen = true;
2854 | if (!isSimple) {
2855 | appendOnly = false;
2856 | }
2857 | }
2858 | });
2859 |
2860 |
2861 | if (appendOnly) {
2862 | if (bufferStart) {
2863 | bufferStart.prepend('return ');
2864 | bufferEnd.add(';');
2865 | } else if (!sourceSeen) {
2866 | this.source.push('return "";');
2867 | }
2868 | } else {
2869 | varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
2870 |
2871 | if (bufferStart) {
2872 | bufferStart.prepend('return buffer + ');
2873 | bufferEnd.add(';');
2874 | } else {
2875 | this.source.push('return buffer;');
2876 | }
2877 | }
2878 |
2879 | if (varDeclarations) {
2880 | this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
2881 | }
2882 |
2883 | return this.source.merge();
2884 | },
2885 |
2886 | // [blockValue]
2887 | //
2888 | // On stack, before: hash, inverse, program, value
2889 | // On stack, after: return value of blockHelperMissing
2890 | //
2891 | // The purpose of this opcode is to take a block of the form
2892 | // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
2893 | // replace it on the stack with the result of properly
2894 | // invoking blockHelperMissing.
2895 | blockValue: function(name) {
2896 | var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
2897 | params = [this.contextName(0)];
2898 | this.setupHelperArgs(name, 0, params);
2899 |
2900 | var blockName = this.popStack();
2901 | params.splice(1, 0, blockName);
2902 |
2903 | this.push(this.source.functionCall(blockHelperMissing, 'call', params));
2904 | },
2905 |
2906 | // [ambiguousBlockValue]
2907 | //
2908 | // On stack, before: hash, inverse, program, value
2909 | // Compiler value, before: lastHelper=value of last found helper, if any
2910 | // On stack, after, if no lastHelper: same as [blockValue]
2911 | // On stack, after, if lastHelper: value
2912 | ambiguousBlockValue: function() {
2913 | // We're being a bit cheeky and reusing the options value from the prior exec
2914 | var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
2915 | params = [this.contextName(0)];
2916 | this.setupHelperArgs('', 0, params, true);
2917 |
2918 | this.flushInline();
2919 |
2920 | var current = this.topStack();
2921 | params.splice(1, 0, current);
2922 |
2923 | this.pushSource([
2924 | 'if (!', this.lastHelper, ') { ',
2925 | current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params),
2926 | '}']);
2927 | },
2928 |
2929 | // [appendContent]
2930 | //
2931 | // On stack, before: ...
2932 | // On stack, after: ...
2933 | //
2934 | // Appends the string value of `content` to the current buffer
2935 | appendContent: function(content) {
2936 | if (this.pendingContent) {
2937 | content = this.pendingContent + content;
2938 | } else {
2939 | this.pendingLocation = this.source.currentLocation;
2940 | }
2941 |
2942 | this.pendingContent = content;
2943 | },
2944 |
2945 | // [append]
2946 | //
2947 | // On stack, before: value, ...
2948 | // On stack, after: ...
2949 | //
2950 | // Coerces `value` to a String and appends it to the current buffer.
2951 | //
2952 | // If `value` is truthy, or 0, it is coerced into a string and appended
2953 | // Otherwise, the empty string is appended
2954 | append: function() {
2955 | if (this.isInline()) {
2956 | this.replaceStack(function(current) {
2957 | return [' != null ? ', current, ' : ""'];
2958 | });
2959 |
2960 | this.pushSource(this.appendToBuffer(this.popStack()));
2961 | } else {
2962 | var local = this.popStack();
2963 | this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
2964 | if (this.environment.isSimple) {
2965 | this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
2966 | }
2967 | }
2968 | },
2969 |
2970 | // [appendEscaped]
2971 | //
2972 | // On stack, before: value, ...
2973 | // On stack, after: ...
2974 | //
2975 | // Escape `value` and append it to the buffer
2976 | appendEscaped: function() {
2977 | this.pushSource(this.appendToBuffer(
2978 | [this.aliasable('this.escapeExpression'), '(', this.popStack(), ')']));
2979 | },
2980 |
2981 | // [getContext]
2982 | //
2983 | // On stack, before: ...
2984 | // On stack, after: ...
2985 | // Compiler value, after: lastContext=depth
2986 | //
2987 | // Set the value of the `lastContext` compiler value to the depth
2988 | getContext: function(depth) {
2989 | this.lastContext = depth;
2990 | },
2991 |
2992 | // [pushContext]
2993 | //
2994 | // On stack, before: ...
2995 | // On stack, after: currentContext, ...
2996 | //
2997 | // Pushes the value of the current context onto the stack.
2998 | pushContext: function() {
2999 | this.pushStackLiteral(this.contextName(this.lastContext));
3000 | },
3001 |
3002 | // [lookupOnContext]
3003 | //
3004 | // On stack, before: ...
3005 | // On stack, after: currentContext[name], ...
3006 | //
3007 | // Looks up the value of `name` on the current context and pushes
3008 | // it onto the stack.
3009 | lookupOnContext: function(parts, falsy, scoped) {
3010 | var i = 0;
3011 |
3012 | if (!scoped && this.options.compat && !this.lastContext) {
3013 | // The depthed query is expected to handle the undefined logic for the root level that
3014 | // is implemented below, so we evaluate that directly in compat mode
3015 | this.push(this.depthedLookup(parts[i++]));
3016 | } else {
3017 | this.pushContext();
3018 | }
3019 |
3020 | this.resolvePath('context', parts, i, falsy);
3021 | },
3022 |
3023 | // [lookupBlockParam]
3024 | //
3025 | // On stack, before: ...
3026 | // On stack, after: blockParam[name], ...
3027 | //
3028 | // Looks up the value of `parts` on the given block param and pushes
3029 | // it onto the stack.
3030 | lookupBlockParam: function(blockParamId, parts) {
3031 | this.useBlockParams = true;
3032 |
3033 | this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
3034 | this.resolvePath('context', parts, 1);
3035 | },
3036 |
3037 | // [lookupData]
3038 | //
3039 | // On stack, before: ...
3040 | // On stack, after: data, ...
3041 | //
3042 | // Push the data lookup operator
3043 | lookupData: function(depth, parts) {
3044 | /*jshint -W083 */
3045 | if (!depth) {
3046 | this.pushStackLiteral('data');
3047 | } else {
3048 | this.pushStackLiteral('this.data(data, ' + depth + ')');
3049 | }
3050 |
3051 | this.resolvePath('data', parts, 0, true);
3052 | },
3053 |
3054 | resolvePath: function(type, parts, i, falsy) {
3055 | /*jshint -W083 */
3056 | if (this.options.strict || this.options.assumeObjects) {
3057 | this.push(strictLookup(this.options.strict, this, parts, type));
3058 | return;
3059 | }
3060 |
3061 | var len = parts.length;
3062 | for (; i < len; i++) {
3063 | this.replaceStack(function(current) {
3064 | var lookup = this.nameLookup(current, parts[i], type);
3065 | // We want to ensure that zero and false are handled properly if the context (falsy flag)
3066 | // needs to have the special handling for these values.
3067 | if (!falsy) {
3068 | return [' != null ? ', lookup, ' : ', current];
3069 | } else {
3070 | // Otherwise we can use generic falsy handling
3071 | return [' && ', lookup];
3072 | }
3073 | });
3074 | }
3075 | },
3076 |
3077 | // [resolvePossibleLambda]
3078 | //
3079 | // On stack, before: value, ...
3080 | // On stack, after: resolved value, ...
3081 | //
3082 | // If the `value` is a lambda, replace it on the stack by
3083 | // the return value of the lambda
3084 | resolvePossibleLambda: function() {
3085 | this.push([this.aliasable('this.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
3086 | },
3087 |
3088 | // [pushStringParam]
3089 | //
3090 | // On stack, before: ...
3091 | // On stack, after: string, currentContext, ...
3092 | //
3093 | // This opcode is designed for use in string mode, which
3094 | // provides the string value of a parameter along with its
3095 | // depth rather than resolving it immediately.
3096 | pushStringParam: function(string, type) {
3097 | this.pushContext();
3098 | this.pushString(type);
3099 |
3100 | // If it's a subexpression, the string result
3101 | // will be pushed after this opcode.
3102 | if (type !== 'SubExpression') {
3103 | if (typeof string === 'string') {
3104 | this.pushString(string);
3105 | } else {
3106 | this.pushStackLiteral(string);
3107 | }
3108 | }
3109 | },
3110 |
3111 | emptyHash: function(omitEmpty) {
3112 | if (this.trackIds) {
3113 | this.push('{}'); // hashIds
3114 | }
3115 | if (this.stringParams) {
3116 | this.push('{}'); // hashContexts
3117 | this.push('{}'); // hashTypes
3118 | }
3119 | this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
3120 | },
3121 | pushHash: function() {
3122 | if (this.hash) {
3123 | this.hashes.push(this.hash);
3124 | }
3125 | this.hash = {values: [], types: [], contexts: [], ids: []};
3126 | },
3127 | popHash: function() {
3128 | var hash = this.hash;
3129 | this.hash = this.hashes.pop();
3130 |
3131 | if (this.trackIds) {
3132 | this.push(this.objectLiteral(hash.ids));
3133 | }
3134 | if (this.stringParams) {
3135 | this.push(this.objectLiteral(hash.contexts));
3136 | this.push(this.objectLiteral(hash.types));
3137 | }
3138 |
3139 | this.push(this.objectLiteral(hash.values));
3140 | },
3141 |
3142 | // [pushString]
3143 | //
3144 | // On stack, before: ...
3145 | // On stack, after: quotedString(string), ...
3146 | //
3147 | // Push a quoted version of `string` onto the stack
3148 | pushString: function(string) {
3149 | this.pushStackLiteral(this.quotedString(string));
3150 | },
3151 |
3152 | // [pushLiteral]
3153 | //
3154 | // On stack, before: ...
3155 | // On stack, after: value, ...
3156 | //
3157 | // Pushes a value onto the stack. This operation prevents
3158 | // the compiler from creating a temporary variable to hold
3159 | // it.
3160 | pushLiteral: function(value) {
3161 | this.pushStackLiteral(value);
3162 | },
3163 |
3164 | // [pushProgram]
3165 | //
3166 | // On stack, before: ...
3167 | // On stack, after: program(guid), ...
3168 | //
3169 | // Push a program expression onto the stack. This takes
3170 | // a compile-time guid and converts it into a runtime-accessible
3171 | // expression.
3172 | pushProgram: function(guid) {
3173 | if (guid != null) {
3174 | this.pushStackLiteral(this.programExpression(guid));
3175 | } else {
3176 | this.pushStackLiteral(null);
3177 | }
3178 | },
3179 |
3180 | // [invokeHelper]
3181 | //
3182 | // On stack, before: hash, inverse, program, params..., ...
3183 | // On stack, after: result of helper invocation
3184 | //
3185 | // Pops off the helper's parameters, invokes the helper,
3186 | // and pushes the helper's return value onto the stack.
3187 | //
3188 | // If the helper is not found, `helperMissing` is called.
3189 | invokeHelper: function(paramSize, name, isSimple) {
3190 | var nonHelper = this.popStack();
3191 | var helper = this.setupHelper(paramSize, name);
3192 | var simple = isSimple ? [helper.name, ' || '] : '';
3193 |
3194 | var lookup = ['('].concat(simple, nonHelper);
3195 | if (!this.options.strict) {
3196 | lookup.push(' || ', this.aliasable('helpers.helperMissing'));
3197 | }
3198 | lookup.push(')');
3199 |
3200 | this.push(this.source.functionCall(lookup, 'call', helper.callParams));
3201 | },
3202 |
3203 | // [invokeKnownHelper]
3204 | //
3205 | // On stack, before: hash, inverse, program, params..., ...
3206 | // On stack, after: result of helper invocation
3207 | //
3208 | // This operation is used when the helper is known to exist,
3209 | // so a `helperMissing` fallback is not required.
3210 | invokeKnownHelper: function(paramSize, name) {
3211 | var helper = this.setupHelper(paramSize, name);
3212 | this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
3213 | },
3214 |
3215 | // [invokeAmbiguous]
3216 | //
3217 | // On stack, before: hash, inverse, program, params..., ...
3218 | // On stack, after: result of disambiguation
3219 | //
3220 | // This operation is used when an expression like `{{foo}}`
3221 | // is provided, but we don't know at compile-time whether it
3222 | // is a helper or a path.
3223 | //
3224 | // This operation emits more code than the other options,
3225 | // and can be avoided by passing the `knownHelpers` and
3226 | // `knownHelpersOnly` flags at compile-time.
3227 | invokeAmbiguous: function(name, helperCall) {
3228 | this.useRegister('helper');
3229 |
3230 | var nonHelper = this.popStack();
3231 |
3232 | this.emptyHash();
3233 | var helper = this.setupHelper(0, name, helperCall);
3234 |
3235 | var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
3236 |
3237 | var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
3238 | if (!this.options.strict) {
3239 | lookup[0] = '(helper = ';
3240 | lookup.push(
3241 | ' != null ? helper : ',
3242 | this.aliasable('helpers.helperMissing')
3243 | );
3244 | }
3245 |
3246 | this.push([
3247 | '(', lookup,
3248 | (helper.paramsInit ? ['),(', helper.paramsInit] : []), '),',
3249 | '(typeof helper === ', this.aliasable('"function"'), ' ? ',
3250 | this.source.functionCall('helper','call', helper.callParams), ' : helper))'
3251 | ]);
3252 | },
3253 |
3254 | // [invokePartial]
3255 | //
3256 | // On stack, before: context, ...
3257 | // On stack after: result of partial invocation
3258 | //
3259 | // This operation pops off a context, invokes a partial with that context,
3260 | // and pushes the result of the invocation back.
3261 | invokePartial: function(isDynamic, name, indent) {
3262 | var params = [],
3263 | options = this.setupParams(name, 1, params, false);
3264 |
3265 | if (isDynamic) {
3266 | name = this.popStack();
3267 | delete options.name;
3268 | }
3269 |
3270 | if (indent) {
3271 | options.indent = JSON.stringify(indent);
3272 | }
3273 | options.helpers = 'helpers';
3274 | options.partials = 'partials';
3275 |
3276 | if (!isDynamic) {
3277 | params.unshift(this.nameLookup('partials', name, 'partial'));
3278 | } else {
3279 | params.unshift(name);
3280 | }
3281 |
3282 | if (this.options.compat) {
3283 | options.depths = 'depths';
3284 | }
3285 | options = this.objectLiteral(options);
3286 | params.push(options);
3287 |
3288 | this.push(this.source.functionCall('this.invokePartial', '', params));
3289 | },
3290 |
3291 | // [assignToHash]
3292 | //
3293 | // On stack, before: value, ..., hash, ...
3294 | // On stack, after: ..., hash, ...
3295 | //
3296 | // Pops a value off the stack and assigns it to the current hash
3297 | assignToHash: function(key) {
3298 | var value = this.popStack(),
3299 | context,
3300 | type,
3301 | id;
3302 |
3303 | if (this.trackIds) {
3304 | id = this.popStack();
3305 | }
3306 | if (this.stringParams) {
3307 | type = this.popStack();
3308 | context = this.popStack();
3309 | }
3310 |
3311 | var hash = this.hash;
3312 | if (context) {
3313 | hash.contexts[key] = context;
3314 | }
3315 | if (type) {
3316 | hash.types[key] = type;
3317 | }
3318 | if (id) {
3319 | hash.ids[key] = id;
3320 | }
3321 | hash.values[key] = value;
3322 | },
3323 |
3324 | pushId: function(type, name, child) {
3325 | if (type === 'BlockParam') {
3326 | this.pushStackLiteral(
3327 | 'blockParams[' + name[0] + '].path[' + name[1] + ']'
3328 | + (child ? ' + ' + JSON.stringify('.' + child) : ''));
3329 | } else if (type === 'PathExpression') {
3330 | this.pushString(name);
3331 | } else if (type === 'SubExpression') {
3332 | this.pushStackLiteral('true');
3333 | } else {
3334 | this.pushStackLiteral('null');
3335 | }
3336 | },
3337 |
3338 | // HELPERS
3339 |
3340 | compiler: JavaScriptCompiler,
3341 |
3342 | compileChildren: function(environment, options) {
3343 | var children = environment.children, child, compiler;
3344 |
3345 | for(var i=0, l=children.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
3467 | return this.topStackName();
3468 | },
3469 | topStackName: function() {
3470 | return "stack" + this.stackSlot;
3471 | },
3472 | flushInline: function() {
3473 | var inlineStack = this.inlineStack;
3474 | this.inlineStack = [];
3475 | for (var i = 0, len = inlineStack.length; i < len; i++) {
3476 | var entry = inlineStack[i];
3477 | /* istanbul ignore if */
3478 | if (entry instanceof Literal) {
3479 | this.compileStack.push(entry);
3480 | } else {
3481 | var stack = this.incrStack();
3482 | this.pushSource([stack, ' = ', entry, ';']);
3483 | this.compileStack.push(stack);
3484 | }
3485 | }
3486 | },
3487 | isInline: function() {
3488 | return this.inlineStack.length;
3489 | },
3490 |
3491 | popStack: function(wrapped) {
3492 | var inline = this.isInline(),
3493 | item = (inline ? this.inlineStack : this.compileStack).pop();
3494 |
3495 | if (!wrapped && (item instanceof Literal)) {
3496 | return item.value;
3497 | } else {
3498 | if (!inline) {
3499 | /* istanbul ignore next */
3500 | if (!this.stackSlot) {
3501 | throw new Exception('Invalid stack pop');
3502 | }
3503 | this.stackSlot--;
3504 | }
3505 | return item;
3506 | }
3507 | },
3508 |
3509 | topStack: function() {
3510 | var stack = (this.isInline() ? this.inlineStack : this.compileStack),
3511 | item = stack[stack.length - 1];
3512 |
3513 | /* istanbul ignore if */
3514 | if (item instanceof Literal) {
3515 | return item.value;
3516 | } else {
3517 | return item;
3518 | }
3519 | },
3520 |
3521 | contextName: function(context) {
3522 | if (this.useDepths && context) {
3523 | return 'depths[' + context + ']';
3524 | } else {
3525 | return 'depth' + context;
3526 | }
3527 | },
3528 |
3529 | quotedString: function(str) {
3530 | return this.source.quotedString(str);
3531 | },
3532 |
3533 | objectLiteral: function(obj) {
3534 | return this.source.objectLiteral(obj);
3535 | },
3536 |
3537 | aliasable: function(name) {
3538 | var ret = this.aliases[name];
3539 | if (ret) {
3540 | ret.referenceCount++;
3541 | return ret;
3542 | }
3543 |
3544 | ret = this.aliases[name] = this.source.wrap(name);
3545 | ret.aliasable = true;
3546 | ret.referenceCount = 1;
3547 |
3548 | return ret;
3549 | },
3550 |
3551 | setupHelper: function(paramSize, name, blockHelper) {
3552 | var params = [],
3553 | paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
3554 | var foundHelper = this.nameLookup('helpers', name, 'helper');
3555 |
3556 | return {
3557 | params: params,
3558 | paramsInit: paramsInit,
3559 | name: foundHelper,
3560 | callParams: [this.contextName(0)].concat(params)
3561 | };
3562 | },
3563 |
3564 | setupParams: function(helper, paramSize, params) {
3565 | var options = {}, contexts = [], types = [], ids = [], param;
3566 |
3567 | options.name = this.quotedString(helper);
3568 | options.hash = this.popStack();
3569 |
3570 | if (this.trackIds) {
3571 | options.hashIds = this.popStack();
3572 | }
3573 | if (this.stringParams) {
3574 | options.hashTypes = this.popStack();
3575 | options.hashContexts = this.popStack();
3576 | }
3577 |
3578 | var inverse = this.popStack(),
3579 | program = this.popStack();
3580 |
3581 | // Avoid setting fn and inverse if neither are set. This allows
3582 | // helpers to do a check for `if (options.fn)`
3583 | if (program || inverse) {
3584 | options.fn = program || 'this.noop';
3585 | options.inverse = inverse || 'this.noop';
3586 | }
3587 |
3588 | // The parameters go on to the stack in order (making sure that they are evaluated in order)
3589 | // so we need to pop them off the stack in reverse order
3590 | var i = paramSize;
3591 | while (i--) {
3592 | param = this.popStack();
3593 | params[i] = param;
3594 |
3595 | if (this.trackIds) {
3596 | ids[i] = this.popStack();
3597 | }
3598 | if (this.stringParams) {
3599 | types[i] = this.popStack();
3600 | contexts[i] = this.popStack();
3601 | }
3602 | }
3603 |
3604 | if (this.trackIds) {
3605 | options.ids = this.source.generateArray(ids);
3606 | }
3607 | if (this.stringParams) {
3608 | options.types = this.source.generateArray(types);
3609 | options.contexts = this.source.generateArray(contexts);
3610 | }
3611 |
3612 | if (this.options.data) {
3613 | options.data = 'data';
3614 | }
3615 | if (this.useBlockParams) {
3616 | options.blockParams = 'blockParams';
3617 | }
3618 | return options;
3619 | },
3620 |
3621 | setupHelperArgs: function(helper, paramSize, params, useRegister) {
3622 | var options = this.setupParams(helper, paramSize, params, true);
3623 | options = this.objectLiteral(options);
3624 | if (useRegister) {
3625 | this.useRegister('options');
3626 | params.push('options');
3627 | return ['options=', options];
3628 | } else {
3629 | params.push(options);
3630 | return '';
3631 | }
3632 | }
3633 | };
3634 |
3635 |
3636 | var reservedWords = (
3637 | "break else new var" +
3638 | " case finally return void" +
3639 | " catch for switch while" +
3640 | " continue function this with" +
3641 | " default if throw" +
3642 | " delete in try" +
3643 | " do instanceof typeof" +
3644 | " abstract enum int short" +
3645 | " boolean export interface static" +
3646 | " byte extends long super" +
3647 | " char final native synchronized" +
3648 | " class float package throws" +
3649 | " const goto private transient" +
3650 | " debugger implements protected volatile" +
3651 | " double import public let yield await" +
3652 | " null true false"
3653 | ).split(" ");
3654 |
3655 | var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
3656 |
3657 | for(var i=0, l=reservedWords.length; i