├── .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 |
76 | 77 |
78 |

Hacker News

79 |

Top Stories

80 |
81 | 82 |
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