├── .gitignore ├── .travis.yml ├── .versions ├── HISTORY.md ├── LICENSE ├── README.md ├── client.coffee ├── compatibility ├── attrs.js ├── dynamic.html ├── dynamic.js ├── lookup.js ├── materializer.js └── templating.js ├── compile-templates.js ├── debug.coffee ├── demo ├── .gitignore ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── README.md ├── client │ ├── base.coffee │ ├── demo.coffee │ ├── demo.html │ ├── demo.js.rename │ ├── demo.next.js.rename │ ├── demo.styl │ └── lib │ │ ├── prism.css │ │ └── prism.js ├── lib │ └── demo.coffee ├── package-lock.json ├── packages │ └── peerlibrary:blaze-components ├── public │ └── images │ │ ├── Diamond-01.svg │ │ └── Diamond-02.svg └── server │ └── demo.coffee ├── example ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── README.md ├── client │ ├── base.coffee │ └── base.html └── packages │ └── peerlibrary:blaze-components ├── lib.coffee ├── mixins.svg ├── package.js ├── patch-compiling.js ├── server.coffee ├── template.coffee └── tests ├── tests.coffee ├── tests.css ├── tests.es2015.js ├── tests.html └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | .idea 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | sudo: required 5 | before_install: 6 | - "curl -L http://git.io/ejPSng | /bin/sh" 7 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.1.0 2 | babel-compiler@7.3.4 3 | babel-runtime@1.3.0 4 | base64@1.0.11 5 | binary-heap@1.0.11 6 | blaze@2.3.3 7 | blaze-tools@1.0.10 8 | boilerplate-generator@1.6.0 9 | caching-compiler@1.2.1 10 | caching-html-compiler@1.1.3 11 | callback-hook@1.1.0 12 | check@1.3.1 13 | coffeescript@2.4.1 14 | coffeescript-compiler@2.4.1 15 | ddp@1.4.0 16 | ddp-client@2.3.3 17 | ddp-common@1.4.0 18 | ddp-server@2.3.0 19 | deps@1.0.12 20 | diff-sequence@1.1.1 21 | dynamic-import@0.5.1 22 | ecmascript@0.12.7 23 | ecmascript-runtime@0.7.0 24 | ecmascript-runtime-client@0.8.0 25 | ecmascript-runtime-server@0.7.1 26 | ejson@1.1.0 27 | fetch@0.1.1 28 | geojson-utils@1.0.10 29 | html-tools@1.0.11 30 | htmljs@1.0.11 31 | id-map@1.1.0 32 | inter-process-messaging@0.1.0 33 | jquery@1.11.11 34 | local-test:peerlibrary:blaze-components@0.23.0 35 | logging@1.1.20 36 | meteor@1.9.3 37 | minimongo@1.4.5 38 | modern-browsers@0.1.4 39 | modules@0.13.0 40 | modules-runtime@0.10.3 41 | mongo@1.6.2 42 | mongo-decimal@0.1.1 43 | mongo-dev-server@1.1.0 44 | mongo-id@1.0.7 45 | npm-mongo@3.1.2 46 | observe-sequence@1.0.16 47 | ordered-dict@1.1.0 48 | peerlibrary:assert@0.3.0 49 | peerlibrary:base-component@0.17.1 50 | peerlibrary:blaze-components@0.23.0 51 | peerlibrary:classy-test@0.4.0 52 | peerlibrary:computed-field@0.10.0 53 | peerlibrary:data-lookup@0.3.0 54 | peerlibrary:reactive-field@0.6.0 55 | promise@0.11.2 56 | random@1.1.0 57 | reactive-var@1.0.11 58 | reload@1.3.0 59 | retry@1.1.0 60 | routepolicy@1.1.0 61 | socket-stream-client@0.2.2 62 | spacebars@1.0.15 63 | spacebars-compiler@1.1.3 64 | templating-tools@1.1.2 65 | tinytest@1.1.0 66 | tracker@1.2.0 67 | underscore@1.0.10 68 | webapp@1.7.3 69 | webapp-hashing@1.0.9 70 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## vNEXT 2 | 3 | ## v0.23.0, 2019-Sep-22 4 | 5 | * Updated dependencies to Meteor 1.8.1. 6 | 7 | ## v0.22.0, 2017-Jul-18 8 | 9 | * Updated dependencies to newer versions. 10 | 11 | ## v0.21.0, 2017-Feb-06 12 | 13 | * Provide internal `_renderComponentTo` method to allow serialization of Blaze's intermediate HTMLJS 14 | to a custom output. This allows, for example, serialization of Blaze Components to a plain-text rendering 15 | useful for plain-text e-mails. 16 | 17 | ## v0.20.0, 2016-Nov-01 18 | 19 | * Updated code to support the new version of [computed field](https://github.com/peerlibrary/meteor-computed-field). 20 | 21 | ## v0.19.0, 2016-Aug-03 22 | 23 | * Correctly cache `
` tag. Our workaround for the 24 | [Meteor issue](https://github.com/meteor/meteor/issues/5913) was breaking the cache 25 | because it made empty head to be cached even for the client target. 26 | 27 | ## v0.18.0, 2016-Mar-07 28 | 29 | * Reverted searching of mixins of mixins. `getFirstWith` and `callFirstWith` traverse mixins, so this is 30 | enough. 31 | * But `childComponentsWith` does check for mixin properties now, when properties are being matched. 32 | * `component` now always resolves to the component, even when called from a mixin. 33 | Many methods which expect to operate on the component and previously failed when called on a mixin 34 | now automatically assure that they are called on the component, if it is available. 35 | This allows better code reuse between mixins and components. 36 | 37 | ## v0.17.0, 2016-Mar-03 38 | 39 | * `getFirstWith` now accepts a predicate as well. 40 | * When searching for a property on a component with `getFirstWith` and `callFirstWith`, 41 | we now traverse also mixins of mixins recursively. 42 | 43 | ## v0.16.2, 2015-Dec-31 44 | 45 | * Fixed missing animation hooks in more cases. 46 | 47 | ## v0.16.1, 2015-Dec-31 48 | 49 | * Blaze Components can now process `` tags in the server side templates, and ignore 50 | `` tags (which are already rendered on the server side). 51 | 52 | ## v0.16.0, 2015-Dec-30 53 | 54 | * `this` inside a `component.autorun` computation is bound automatically to the component. 55 | * `currentComponent` inside template content wrapped with a block helper component returns 56 | the closest block helper component. 57 | * Block helpers components are correctly positioned inside the component tree. 58 | Fixes [#50](https://github.com/peerlibrary/meteor-blaze-components/issues/50) and 59 | [#51](https://github.com/peerlibrary/meteor-blaze-components/issues/51). 60 | * Both `data` and `currentData` can now limit the returned value to a path by passing the 61 | path as an argument. Moreover, this limits reactivity only to changes of that value. 62 | Fixes [#101](https://github.com/peerlibrary/meteor-blaze-components/issues/101). 63 | * Access to the lexical scope is now possible through `currentData` by passing lexical 64 | scope path as an argument. 65 | Fixes [#76](https://github.com/peerlibrary/meteor-blaze-components/issues/76). 66 | * Allow binding of event handlers in templates instead of through event maps. 67 | Fixes [#99](https://github.com/peerlibrary/meteor-blaze-components/issues/99). 68 | * Fixed missing animation hooks in some cases. 69 | * Preliminary support for server side rendering. 70 | See [#110](https://github.com/peerlibrary/meteor-blaze-components/issues/110). 71 | 72 | ## v0.15.1, 2015-Oct-27 73 | 74 | * Made sure backported Blaze lookup.js is not applied under Meteor 1.2. It is needed only for older Meteor versions 75 | and it interferes with Meteor 1.2. 76 | Fixes [#100](https://github.com/peerlibrary/meteor-blaze-components/issues/100). 77 | 78 | ## v0.15.0, 2015-Oct-23 79 | 80 | * Renamed two methods once more. Fixes [#56](https://github.com/peerlibrary/meteor-blaze-components/issues/94). 81 | Renamed methods: 82 | * `childrenComponents` to `childComponents` 83 | * `childrenComponentsWith` to `childComponentsWith` 84 | 85 | ## v0.14.0, 2015-Oct-19 86 | 87 | * Now multiple methods (`data`, `subscriptionsReady`, `$`, `find`, `findAll`, `firstNode`, `lastNode`) which access 88 | data context and DOM are additionally reactive any you can use them even before the component is rendered or even 89 | created and they will trigger invalidation when DOM, for example, becomes ready. 90 | Fixes [#62](https://github.com/peerlibrary/meteor-blaze-components/issues/62). 91 | * Constructor is now never in a reactive context. Previously, constructor was sometimes in a reactive context. Same 92 | for `onDestroyed`. 93 | * Made parent template/component available in the component constructor. 94 | * Made correct data context available in the component constructor when passing arguments to the component. 95 | * Added `removeComponent` method. Partially fixes 96 | [#36](https://github.com/peerlibrary/meteor-blaze-components/issues/36). 97 | * Renamed few methods to more intuitive names and added deprecation warnings if you use a method with an old name. 98 | Methods with old names will be removed in a future version. 99 | Fixes [#56](https://github.com/peerlibrary/meteor-blaze-components/issues/56). 100 | Renamed methods: 101 | * `componentChildren` to `childrenComponents` 102 | * `componentChildrenWith` to `childrenComponentsWith` 103 | * `addComponentChild` to `addChildComponent` 104 | * `removeComponentChild` to `removeChildComponent` 105 | * `componentParent` to `parentComponent` 106 | * Support extending existing Blaze templates. Now preexisting events and life-cycle hooks are available through default 107 | implementations of related Blaze Components methods. Preexisting template helpers are searched if a requested 108 | component instance method is not found. 109 | Fixes [#71](https://github.com/peerlibrary/meteor-blaze-components/issues/71). 110 | * Made sure that reactive life-cycle variables are set before corresponding callbacks are called. 111 | 112 | ## v0.13.0, 2015-Jun-24 113 | 114 | * Fixed `getComponentForElement` to work correctly on DOM elements from non-template views. 115 | * Started `HISTORY.md` file with the list of all changes. 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, The PeerLibrary Project 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the PeerLibrary Project nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /client.coffee: -------------------------------------------------------------------------------- 1 | propagateUIHooks = (parent, node) -> 2 | return if not parent._uihooks or node._uihooks 3 | 4 | node._uihooks = _.extend {}, parent._uihooks, parentNode: node 5 | 6 | return unless node.hasChildNodes() 7 | 8 | for childNode in node.childNodes when childNode.nodeType is Node.ELEMENT_NODE 9 | propagateUIHooks node, childNode 10 | 11 | # To optimize. 12 | return 13 | 14 | originalInsertNodeWithHooks = Blaze._DOMRange._insertNodeWithHooks 15 | Blaze._DOMRange._insertNodeWithHooks = (node, parent, next) -> 16 | propagateUIHooks parent, node 17 | originalInsertNodeWithHooks node, parent, next 18 | 19 | originalMoveNodeWithHooks = Blaze._DOMRange._moveNodeWithHooks 20 | Blaze._DOMRange._moveNodeWithHooks = (node, parent, next) -> 21 | propagateUIHooks parent, node 22 | originalMoveNodeWithHooks node, parent, next 23 | 24 | createUIHooks = (component, parentNode) -> 25 | parentNode: parentNode 26 | 27 | insertElement: (node, before) -> 28 | component.insertDOMElement @parentNode, node, before 29 | 30 | moveElement: (node, before) -> 31 | component.moveDOMElement @parentNode, node, before 32 | 33 | removeElement: (node) -> 34 | component.removeDOMElement node.parentNode, node 35 | 36 | originalDOMRangeAttach = Blaze._DOMRange::attach 37 | Blaze._DOMRange::attach = (parentElement, nextNode, _isMove, _isReplace) -> 38 | if component = @view?._templateInstance?.component 39 | for member in @members when member not instanceof Blaze._DOMRange 40 | member._uihooks = createUIHooks component, member 41 | 42 | continue unless member.hasChildNodes() 43 | 44 | for childNode in member.childNodes when childNode.nodeType is Node.ELEMENT_NODE 45 | propagateUIHooks member, childNode 46 | 47 | oldUIHooks = parentElement._uihooks 48 | try 49 | parentElement._uihooks = createUIHooks component, parentElement 50 | return originalDOMRangeAttach.apply @, arguments 51 | finally 52 | if oldUIHooks 53 | parentElement._uihooks = oldUIHooks 54 | else 55 | delete parentElement._uihooks 56 | 57 | originalDOMRangeAttach.apply @, arguments 58 | 59 | WHITESPACE_REGEX = /^\s+$/ 60 | 61 | EventHandler = Blaze._AttributeHandler.extend 62 | update: (element, oldValue, value) -> 63 | oldValue = [] unless oldValue 64 | oldValue = [oldValue] unless _.isArray oldValue 65 | 66 | value = [] unless value 67 | value = [value] unless _.isArray value 68 | 69 | assert _.every(oldValue, share.isEventHandler), oldValue 70 | assert _.every(value, share.isEventHandler), value 71 | 72 | $element = $(element) 73 | eventName = @name.substr(2).toLowerCase() 74 | 75 | $element.off eventName, fun for fun in oldValue 76 | $element.on eventName, fun for fun in value 77 | 78 | originalMakeAttributeHandler = Blaze._makeAttributeHandler 79 | Blaze._makeAttributeHandler = (elem, name, value) -> 80 | if share.EVENT_HANDLER_REGEX.test name 81 | new EventHandler name, value 82 | else 83 | originalMakeAttributeHandler elem, name, value 84 | 85 | originalToText = Blaze._toText 86 | Blaze._toText = (htmljs, parentView, textMode) -> 87 | # If it is an event handler function, we pass it as it is and do not try to convert it to text. 88 | # Our EventHandler knows how to handle such attribute values - functions. 89 | if share.isEventHandler htmljs 90 | htmljs 91 | else if _.isArray(htmljs) and _.some htmljs, share.isEventHandler 92 | # Remove whitespace in onEvent="{{onEvent1}} {{onEvent2}}". 93 | _.filter htmljs, (fun) -> 94 | return true if share.isEventHandler fun 95 | return false if WHITESPACE_REGEX.test fun 96 | 97 | # We do not support anything fancy besides whitespace. 98 | throw new Error "Invalid event handler: #{fun}" 99 | else 100 | originalToText htmljs, parentView, textMode 101 | 102 | originalExpandAttributes = Blaze._expandAttributes 103 | Blaze._expandAttributes = (attrs, parentView) -> 104 | previousInExpandAttributes = share.inExpandAttributes 105 | share.inExpandAttributes = true 106 | try 107 | originalExpandAttributes attrs, parentView 108 | finally 109 | share.inExpandAttributes = previousInExpandAttributes 110 | -------------------------------------------------------------------------------- /compatibility/attrs.js: -------------------------------------------------------------------------------- 1 | /* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5893 2 | It is a copy of attrs.js file with the changes from the above pull request merged in. 3 | 4 | TODO: Remove this file eventually. 5 | */ 6 | 7 | var jsUrlsAllowed = false; 8 | Blaze._allowJavascriptUrls = function () { 9 | jsUrlsAllowed = true; 10 | }; 11 | Blaze._javascriptUrlsAllowed = function () { 12 | return jsUrlsAllowed; 13 | }; 14 | 15 | // An AttributeHandler object is responsible for updating a particular attribute 16 | // of a particular element. AttributeHandler subclasses implement 17 | // browser-specific logic for dealing with particular attributes across 18 | // different browsers. 19 | // 20 | // To define a new type of AttributeHandler, use 21 | // `var FooHandler = AttributeHandler.extend({ update: function ... })` 22 | // where the `update` function takes arguments `(element, oldValue, value)`. 23 | // The `element` argument is always the same between calls to `update` on 24 | // the same instance. `oldValue` and `value` are each either `null` or 25 | // a Unicode string of the type that might be passed to the value argument 26 | // of `setAttribute` (i.e. not an HTML string with character references). 27 | // When an AttributeHandler is installed, an initial call to `update` is 28 | // always made with `oldValue = null`. The `update` method can access 29 | // `this.name` if the AttributeHandler class is a generic one that applies 30 | // to multiple attribute names. 31 | // 32 | // AttributeHandlers can store custom properties on `this`, as long as they 33 | // don't use the names `element`, `name`, `value`, and `oldValue`. 34 | // 35 | // AttributeHandlers can't influence how attributes appear in rendered HTML, 36 | // only how they are updated after materialization as DOM. 37 | 38 | AttributeHandler = function (name, value) { 39 | this.name = name; 40 | this.value = value; 41 | }; 42 | Blaze._AttributeHandler = AttributeHandler; 43 | 44 | AttributeHandler.prototype.update = function (element, oldValue, value) { 45 | if (value === null) { 46 | if (oldValue !== null) 47 | element.removeAttribute(this.name); 48 | } else { 49 | element.setAttribute(this.name, value); 50 | } 51 | }; 52 | 53 | AttributeHandler.extend = function (options) { 54 | var curType = this; 55 | var subType = function AttributeHandlerSubtype(/*arguments*/) { 56 | AttributeHandler.apply(this, arguments); 57 | }; 58 | subType.prototype = new curType; 59 | subType.extend = curType.extend; 60 | if (options) 61 | _.extend(subType.prototype, options); 62 | return subType; 63 | }; 64 | 65 | /// Apply the diff between the attributes of "oldValue" and "value" to "element." 66 | // 67 | // Each subclass must implement a parseValue method which takes a string 68 | // as an input and returns a dict of attributes. The keys of the dict 69 | // are unique identifiers (ie. css properties in the case of styles), and the 70 | // values are the entire attribute which will be injected into the element. 71 | // 72 | // Extended below to support classes, SVG elements and styles. 73 | 74 | Blaze._DiffingAttributeHandler = AttributeHandler.extend({ 75 | update: function (element, oldValue, value) { 76 | if (!this.getCurrentValue || !this.setValue || !this.parseValue) 77 | throw new Error("Missing methods in subclass of 'DiffingAttributeHandler'"); 78 | 79 | var oldAttrsMap = oldValue ? this.parseValue(oldValue) : {}; 80 | var newAttrsMap = value ? this.parseValue(value) : {}; 81 | 82 | // the current attributes on the element, which we will mutate. 83 | 84 | var attrString = this.getCurrentValue(element); 85 | var attrsMap = attrString ? this.parseValue(attrString) : {}; 86 | 87 | _.each(_.keys(oldAttrsMap), function (t) { 88 | if (! (t in newAttrsMap)) 89 | delete attrsMap[t]; 90 | }); 91 | 92 | _.each(_.keys(newAttrsMap), function (t) { 93 | attrsMap[t] = newAttrsMap[t]; 94 | }); 95 | 96 | this.setValue(element, _.values(attrsMap).join(' ')); 97 | } 98 | }); 99 | 100 | var ClassHandler = Blaze._DiffingAttributeHandler.extend({ 101 | // @param rawValue {String} 102 | getCurrentValue: function (element) { 103 | return element.className; 104 | }, 105 | setValue: function (element, className) { 106 | element.className = className; 107 | }, 108 | parseValue: function (attrString) { 109 | var tokens = {}; 110 | 111 | _.each(attrString.split(' '), function(token) { 112 | if (token) 113 | tokens[token] = token; 114 | }); 115 | return tokens; 116 | } 117 | }); 118 | 119 | var SVGClassHandler = ClassHandler.extend({ 120 | getCurrentValue: function (element) { 121 | return element.className.baseVal; 122 | }, 123 | setValue: function (element, className) { 124 | element.setAttribute('class', className); 125 | } 126 | }); 127 | 128 | var StyleHandler = Blaze._DiffingAttributeHandler.extend({ 129 | getCurrentValue: function (element) { 130 | return element.getAttribute('style'); 131 | }, 132 | setValue: function (element, style) { 133 | if (style === '') { 134 | element.removeAttribute('style'); 135 | } else { 136 | element.setAttribute('style', style); 137 | } 138 | }, 139 | 140 | // Parse a string to produce a map from property to attribute string. 141 | // 142 | // Example: 143 | // "color:red; foo:12px" produces a token {color: "color:red", foo:"foo:12px"} 144 | parseValue: function (attrString) { 145 | var tokens = {}; 146 | 147 | // Regex for parsing a css attribute declaration, taken from css-parse: 148 | // https://github.com/reworkcss/css-parse/blob/7cef3658d0bba872cde05a85339034b187cb3397/index.js#L219 149 | var regex = /(\*?[-#\/\*\\\w]+(?:\[[0-9a-z_-]+\])?)\s*:\s*(?:\'(?:\\\'|.)*?\'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+[;\s]*/g; 150 | var match = regex.exec(attrString); 151 | while (match) { 152 | // match[0] = entire matching string 153 | // match[1] = css property 154 | // Prefix the token to prevent conflicts with existing properties. 155 | 156 | // XXX No `String.trim` on Safari 4. Swap out $.trim if we want to 157 | // remove strong dep on jquery. 158 | tokens[' ' + match[1]] = match[0].trim ? 159 | match[0].trim() : $.trim(match[0]); 160 | 161 | match = regex.exec(attrString); 162 | } 163 | 164 | return tokens; 165 | } 166 | }); 167 | 168 | var BooleanHandler = AttributeHandler.extend({ 169 | update: function (element, oldValue, value) { 170 | var name = this.name; 171 | if (value == null) { 172 | if (oldValue != null) 173 | element[name] = false; 174 | } else { 175 | element[name] = true; 176 | } 177 | } 178 | }); 179 | 180 | var DOMPropertyHandler = AttributeHandler.extend({ 181 | update: function (element, oldValue, value) { 182 | var name = this.name; 183 | if (value !== element[name]) 184 | element[name] = value; 185 | } 186 | }); 187 | 188 | // attributes of the type 'xlink:something' should be set using 189 | // the correct namespace in order to work 190 | var XlinkHandler = AttributeHandler.extend({ 191 | update: function(element, oldValue, value) { 192 | var NS = 'http://www.w3.org/1999/xlink'; 193 | if (value === null) { 194 | if (oldValue !== null) 195 | element.removeAttributeNS(NS, this.name); 196 | } else { 197 | element.setAttributeNS(NS, this.name, this.value); 198 | } 199 | } 200 | }); 201 | 202 | // cross-browser version of `instanceof SVGElement` 203 | var isSVGElement = function (elem) { 204 | return 'ownerSVGElement' in elem; 205 | }; 206 | 207 | var isUrlAttribute = function (tagName, attrName) { 208 | // Compiled from http://www.w3.org/TR/REC-html40/index/attributes.html 209 | // and 210 | // http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1 211 | var urlAttrs = { 212 | FORM: ['action'], 213 | BODY: ['background'], 214 | BLOCKQUOTE: ['cite'], 215 | Q: ['cite'], 216 | DEL: ['cite'], 217 | INS: ['cite'], 218 | OBJECT: ['classid', 'codebase', 'data', 'usemap'], 219 | APPLET: ['codebase'], 220 | A: ['href'], 221 | AREA: ['href'], 222 | LINK: ['href'], 223 | BASE: ['href'], 224 | IMG: ['longdesc', 'src', 'usemap'], 225 | FRAME: ['longdesc', 'src'], 226 | IFRAME: ['longdesc', 'src'], 227 | HEAD: ['profile'], 228 | SCRIPT: ['src'], 229 | INPUT: ['src', 'usemap', 'formaction'], 230 | BUTTON: ['formaction'], 231 | BASE: ['href'], 232 | MENUITEM: ['icon'], 233 | HTML: ['manifest'], 234 | VIDEO: ['poster'] 235 | }; 236 | 237 | if (attrName === 'itemid') { 238 | return true; 239 | } 240 | 241 | var urlAttrNames = urlAttrs[tagName] || []; 242 | return _.contains(urlAttrNames, attrName); 243 | }; 244 | 245 | // To get the protocol for a URL, we let the browser normalize it for 246 | // us, by setting it as the href for an anchor tag and then reading out 247 | // the 'protocol' property. 248 | if (Meteor.isClient) { 249 | var anchorForNormalization = document.createElement('A'); 250 | } 251 | 252 | var getUrlProtocol = function (url) { 253 | if (Meteor.isClient) { 254 | anchorForNormalization.href = url; 255 | return (anchorForNormalization.protocol || "").toLowerCase(); 256 | } else { 257 | throw new Error('getUrlProtocol not implemented on the server'); 258 | } 259 | }; 260 | 261 | // UrlHandler is an attribute handler for all HTML attributes that take 262 | // URL values. It disallows javascript: URLs, unless 263 | // Blaze._allowJavascriptUrls() has been called. To detect javascript: 264 | // urls, we set the attribute on a dummy anchor element and then read 265 | // out the 'protocol' property of the attribute. 266 | var origUpdate = AttributeHandler.prototype.update; 267 | var UrlHandler = AttributeHandler.extend({ 268 | update: function (element, oldValue, value) { 269 | var self = this; 270 | var args = arguments; 271 | 272 | if (Blaze._javascriptUrlsAllowed()) { 273 | origUpdate.apply(self, args); 274 | } else { 275 | var isJavascriptProtocol = (getUrlProtocol(value) === "javascript:"); 276 | if (isJavascriptProtocol) { 277 | Blaze._warn("URLs that use the 'javascript:' protocol are not " + 278 | "allowed in URL attribute values. " + 279 | "Call Blaze._allowJavascriptUrls() " + 280 | "to enable them."); 281 | origUpdate.apply(self, [element, oldValue, null]); 282 | } else { 283 | origUpdate.apply(self, args); 284 | } 285 | } 286 | } 287 | }); 288 | 289 | // XXX make it possible for users to register attribute handlers! 290 | Blaze._makeAttributeHandler = function (elem, name, value) { 291 | // generally, use setAttribute but certain attributes need to be set 292 | // by directly setting a JavaScript property on the DOM element. 293 | if (name === 'class') { 294 | if (isSVGElement(elem)) { 295 | return new SVGClassHandler(name, value); 296 | } else { 297 | return new ClassHandler(name, value); 298 | } 299 | } else if (name === 'style') { 300 | return new StyleHandler(name, value); 301 | } else if ((elem.tagName === 'OPTION' && name === 'selected') || 302 | (elem.tagName === 'INPUT' && name === 'checked')) { 303 | return new BooleanHandler(name, value); 304 | } else if ((elem.tagName === 'TEXTAREA' || elem.tagName === 'INPUT') 305 | && name === 'value') { 306 | // internally, TEXTAREAs tracks their value in the 'value' 307 | // attribute just like INPUTs. 308 | return new DOMPropertyHandler(name, value); 309 | } else if (name.substring(0,6) === 'xlink:') { 310 | return new XlinkHandler(name.substring(6), value); 311 | } else if (isUrlAttribute(elem.tagName, name)) { 312 | return new UrlHandler(name, value); 313 | } else { 314 | return new AttributeHandler(name, value); 315 | } 316 | 317 | // XXX will need one for 'style' on IE, though modern browsers 318 | // seem to handle setAttribute ok. 319 | }; 320 | 321 | 322 | ElementAttributesUpdater = function (elem) { 323 | this.elem = elem; 324 | this.handlers = {}; 325 | }; 326 | 327 | // Update attributes on `elem` to the dictionary `attrs`, whose 328 | // values are strings. 329 | ElementAttributesUpdater.prototype.update = function(newAttrs) { 330 | var elem = this.elem; 331 | var handlers = this.handlers; 332 | 333 | for (var k in handlers) { 334 | if (! _.has(newAttrs, k)) { 335 | // remove attributes (and handlers) for attribute names 336 | // that don't exist as keys of `newAttrs` and so won't 337 | // be visited when traversing it. (Attributes that 338 | // exist in the `newAttrs` object but are `null` 339 | // are handled later.) 340 | var handler = handlers[k]; 341 | var oldValue = handler.value; 342 | handler.value = null; 343 | handler.update(elem, oldValue, null); 344 | delete handlers[k]; 345 | } 346 | } 347 | 348 | for (var k in newAttrs) { 349 | var handler = null; 350 | var oldValue; 351 | var value = newAttrs[k]; 352 | if (! _.has(handlers, k)) { 353 | if (value !== null) { 354 | // make new handler 355 | handler = Blaze._makeAttributeHandler(elem, k, value); 356 | handlers[k] = handler; 357 | oldValue = null; 358 | } 359 | } else { 360 | handler = handlers[k]; 361 | oldValue = handler.value; 362 | } 363 | if (oldValue !== value) { 364 | handler.value = value; 365 | handler.update(elem, oldValue, value); 366 | if (value === null) 367 | delete handlers[k]; 368 | } 369 | } 370 | }; 371 | -------------------------------------------------------------------------------- /compatibility/dynamic.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | {{checkContext}} 14 | {{#if dataContextPresent}} 15 | {{# __dynamicWithDataContext}}{{> Template.contentBlock}}{{/__dynamicWithDataContext}} 16 | {{else}} 17 | {{! if there was no explicit 'data' argument, use the parent context}} 18 | {{# __dynamicWithDataContext template=template data=..}}{{> Template.contentBlock}}{{/__dynamicWithDataContext}} 19 | {{/if}} 20 | 21 | 22 | 24 | 25 | {{#with chooseTemplate template}} 26 | {{!-- The .. is evaluated inside {{#with ../data}} --}} 27 | {{# .. ../data}}{{> Template.contentBlock}}{{/ ..}} 28 | {{/with}} 29 | 30 | -------------------------------------------------------------------------------- /compatibility/dynamic.js: -------------------------------------------------------------------------------- 1 | /* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5903 2 | If it is a copy of dynamic.js file wrapped into a condition with renaming of backported templates. 3 | 4 | TODO: Remove this file eventually. 5 | */ 6 | 7 | if (!Blaze.Template.__dynamicWithDataContext) { 8 | Blaze.Template.__dynamicWithDataContext = Blaze.Template.__dynamicWithDataContextBackport; 9 | Blaze.Template.__dynamicWithDataContext.viewName = 'Template.__dynamicWithDataContext'; 10 | Blaze.Template.__dynamic = Blaze.Template.__dynamicBackport; 11 | Blaze.Template.__dynamic.viewName = 'Template.__dynamic'; 12 | 13 | var Template = Blaze.Template; 14 | 15 | /** 16 | * @isTemplate true 17 | * @memberOf Template 18 | * @function dynamic 19 | * @summary Choose a template to include dynamically, by name. 20 | * @locus Templates 21 | * @param {String} template The name of the template to include. 22 | * @param {Object} [data] Optional. The data context in which to include the 23 | * template. 24 | */ 25 | 26 | Template.__dynamicWithDataContext.helpers({ 27 | chooseTemplate: function (name) { 28 | return Blaze._getTemplate(name, function () { 29 | return Template.instance(); 30 | }); 31 | } 32 | }); 33 | 34 | Template.__dynamic.helpers({ 35 | dataContextPresent: function () { 36 | return _.has(this, "data"); 37 | }, 38 | checkContext: function () { 39 | if (!_.has(this, "template")) { 40 | throw new Error("Must specify name in the 'template' argument " + 41 | "to {{> Template.dynamic}}."); 42 | } 43 | 44 | _.each(this, function (v, k) { 45 | if (k !== "template" && k !== "data") { 46 | throw new Error("Invalid argument to {{> Template.dynamic}}: " + 47 | k); 48 | } 49 | }); 50 | } 51 | }); 52 | } -------------------------------------------------------------------------------- /compatibility/lookup.js: -------------------------------------------------------------------------------- 1 | /* This file backports Blaze lookup.js from Meteor 1.2 so that required Blaze features to support Blaze 2 | Components are available also in older Meteor versions. 3 | It is a copy of lookup.js file from Meteor 1.2 with lexical scope lookup commented out. 4 | 5 | TODO: Remove this file eventually. 6 | */ 7 | 8 | // Check if we are not running Meteor 1.2+. 9 | if (! Blaze._getTemplate) { 10 | // If `x` is a function, binds the value of `this` for that function 11 | // to the current data context. 12 | var bindDataContext = function (x) { 13 | if (typeof x === 'function') { 14 | return function () { 15 | var data = Blaze.getData(); 16 | if (data == null) 17 | data = {}; 18 | return x.apply(data, arguments); 19 | }; 20 | } 21 | return x; 22 | }; 23 | 24 | Blaze._getTemplateHelper = function (template, name, tmplInstanceFunc) { 25 | // XXX COMPAT WITH 0.9.3 26 | var isKnownOldStyleHelper = false; 27 | 28 | if (template.__helpers.has(name)) { 29 | var helper = template.__helpers.get(name); 30 | if (helper === Blaze._OLDSTYLE_HELPER) { 31 | isKnownOldStyleHelper = true; 32 | } else if (helper != null) { 33 | return wrapHelper(bindDataContext(helper), tmplInstanceFunc); 34 | } else { 35 | return null; 36 | } 37 | } 38 | 39 | // old-style helper 40 | if (name in template) { 41 | // Only warn once per helper 42 | if (!isKnownOldStyleHelper) { 43 | template.__helpers.set(name, Blaze._OLDSTYLE_HELPER); 44 | if (!template._NOWARN_OLDSTYLE_HELPERS) { 45 | Blaze._warn('Assigning helper with `' + template.viewName + '.' + 46 | name + ' = ...` is deprecated. Use `' + template.viewName + 47 | '.helpers(...)` instead.'); 48 | } 49 | } 50 | if (template[name] != null) { 51 | return wrapHelper(bindDataContext(template[name]), tmplInstanceFunc); 52 | } 53 | } 54 | 55 | return null; 56 | }; 57 | 58 | var wrapHelper = function (f, templateFunc) { 59 | // XXX COMPAT WITH METEOR 1.0.3.2 60 | if (!Blaze.Template._withTemplateInstanceFunc) { 61 | return Blaze._wrapCatchingExceptions(f, 'template helper'); 62 | } 63 | 64 | if (typeof f !== "function") { 65 | return f; 66 | } 67 | 68 | return function () { 69 | var self = this; 70 | var args = arguments; 71 | 72 | return Blaze.Template._withTemplateInstanceFunc(templateFunc, function () { 73 | return Blaze._wrapCatchingExceptions(f, 'template helper').apply(self, args); 74 | }); 75 | }; 76 | }; 77 | 78 | // templateInstance argument is provided to be available for possible 79 | // alternative implementations of this function by 3rd party packages. 80 | Blaze._getTemplate = function (name, templateInstance) { 81 | if ((name in Blaze.Template) && (Blaze.Template[name] instanceof Blaze.Template)) { 82 | return Blaze.Template[name]; 83 | } 84 | return null; 85 | }; 86 | 87 | Blaze._getGlobalHelper = function (name, templateInstance) { 88 | if (Blaze._globalHelpers[name] != null) { 89 | return wrapHelper(bindDataContext(Blaze._globalHelpers[name]), templateInstance); 90 | } 91 | return null; 92 | }; 93 | 94 | Blaze.View.prototype.lookup = function (name, _options) { 95 | var template = this.template; 96 | var lookupTemplate = _options && _options.template; 97 | var helper; 98 | var binding; 99 | var boundTmplInstance; 100 | var foundTemplate; 101 | 102 | if (this.templateInstance) { 103 | boundTmplInstance = _.bind(this.templateInstance, this); 104 | } 105 | 106 | // 0. looking up the parent data context with the special "../" syntax 107 | if (/^\./.test(name)) { 108 | // starts with a dot. must be a series of dots which maps to an 109 | // ancestor of the appropriate height. 110 | if (!/^(\.)+$/.test(name)) 111 | throw new Error("id starting with dot must be a series of dots"); 112 | 113 | return Blaze._parentData(name.length - 1, true /*_functionWrapped*/); 114 | 115 | } 116 | 117 | // 1. look up a helper on the current template 118 | if (template && ((helper = Blaze._getTemplateHelper(template, name, boundTmplInstance)) != null)) { 119 | return helper; 120 | } 121 | 122 | // 2. look up a binding by traversing the lexical view hierarchy inside the 123 | // current template 124 | /*if (template && (binding = Blaze._lexicalBindingLookup(Blaze.currentView, name)) != null) { 125 | return binding; 126 | }*/ 127 | 128 | // 3. look up a template by name 129 | if (lookupTemplate && ((foundTemplate = Blaze._getTemplate(name, boundTmplInstance)) != null)) { 130 | return foundTemplate; 131 | } 132 | 133 | // 4. look up a global helper 134 | if ((helper = Blaze._getGlobalHelper(name, boundTmplInstance)) != null) { 135 | return helper; 136 | } 137 | 138 | // 5. look up in a data context 139 | return function () { 140 | var isCalledAsFunction = (arguments.length > 0); 141 | var data = Blaze.getData(); 142 | var x = data && data[name]; 143 | if (!x) { 144 | if (lookupTemplate) { 145 | throw new Error("No such template: " + name); 146 | } else if (isCalledAsFunction) { 147 | throw new Error("No such function: " + name); 148 | } /*else if (name.charAt(0) === '@' && ((x === null) || 149 | (x === undefined))) { 150 | // Throw an error if the user tries to use a `@directive` 151 | // that doesn't exist. We don't implement all directives 152 | // from Handlebars, so there's a potential for confusion 153 | // if we fail silently. On the other hand, we want to 154 | // throw late in case some app or package wants to provide 155 | // a missing directive. 156 | throw new Error("Unsupported directive: " + name); 157 | }*/ 158 | } 159 | if (!data) { 160 | return null; 161 | } 162 | if (typeof x !== 'function') { 163 | if (isCalledAsFunction) { 164 | throw new Error("Can't call non-function: " + x); 165 | } 166 | return x; 167 | } 168 | return x.apply(data, arguments); 169 | }; 170 | }; 171 | } -------------------------------------------------------------------------------- /compatibility/materializer.js: -------------------------------------------------------------------------------- 1 | /* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5893 2 | It is a copy of the materializer.js file and is needed because it references symbols from attrs.js. 3 | 4 | TODO: Remove this file eventually. 5 | */ 6 | 7 | // Turns HTMLjs into DOM nodes and DOMRanges. 8 | // 9 | // - `htmljs`: the value to materialize, which may be any of the htmljs 10 | // types (Tag, CharRef, Comment, Raw, array, string, boolean, number, 11 | // null, or undefined) or a View or Template (which will be used to 12 | // construct a View). 13 | // - `intoArray`: the array of DOM nodes and DOMRanges to push the output 14 | // into (required) 15 | // - `parentView`: the View we are materializing content for (optional) 16 | // - `_existingWorkStack`: optional argument, only used for recursive 17 | // calls when there is some other _materializeDOM on the call stack. 18 | // If _materializeDOM called your function and passed in a workStack, 19 | // pass it back when you call _materializeDOM (such as from a workStack 20 | // task). 21 | // 22 | // Returns `intoArray`, which is especially useful if you pass in `[]`. 23 | Blaze._materializeDOM = function (htmljs, intoArray, parentView, 24 | _existingWorkStack) { 25 | // In order to use fewer stack frames, materializeDOMInner can push 26 | // tasks onto `workStack`, and they will be popped off 27 | // and run, last first, after materializeDOMInner returns. The 28 | // reason we use a stack instead of a queue is so that we recurse 29 | // depth-first, doing newer tasks first. 30 | var workStack = (_existingWorkStack || []); 31 | materializeDOMInner(htmljs, intoArray, parentView, workStack); 32 | 33 | if (! _existingWorkStack) { 34 | // We created the work stack, so we are responsible for finishing 35 | // the work. Call each "task" function, starting with the top 36 | // of the stack. 37 | while (workStack.length) { 38 | // Note that running task() may push new items onto workStack. 39 | var task = workStack.pop(); 40 | task(); 41 | } 42 | } 43 | 44 | return intoArray; 45 | }; 46 | 47 | var materializeDOMInner = function (htmljs, intoArray, parentView, workStack) { 48 | if (htmljs == null) { 49 | // null or undefined 50 | return; 51 | } 52 | 53 | switch (typeof htmljs) { 54 | case 'string': case 'boolean': case 'number': 55 | intoArray.push(document.createTextNode(String(htmljs))); 56 | return; 57 | case 'object': 58 | if (htmljs.htmljsType) { 59 | switch (htmljs.htmljsType) { 60 | case HTML.Tag.htmljsType: 61 | intoArray.push(materializeTag(htmljs, parentView, workStack)); 62 | return; 63 | case HTML.CharRef.htmljsType: 64 | intoArray.push(document.createTextNode(htmljs.str)); 65 | return; 66 | case HTML.Comment.htmljsType: 67 | intoArray.push(document.createComment(htmljs.sanitizedValue)); 68 | return; 69 | case HTML.Raw.htmljsType: 70 | // Get an array of DOM nodes by using the browser's HTML parser 71 | // (like innerHTML). 72 | var nodes = Blaze._DOMBackend.parseHTML(htmljs.value); 73 | for (var i = 0; i < nodes.length; i++) 74 | intoArray.push(nodes[i]); 75 | return; 76 | } 77 | } else if (HTML.isArray(htmljs)) { 78 | for (var i = htmljs.length-1; i >= 0; i--) { 79 | workStack.push(_.bind(Blaze._materializeDOM, null, 80 | htmljs[i], intoArray, parentView, workStack)); 81 | } 82 | return; 83 | } else { 84 | if (htmljs instanceof Blaze.Template) { 85 | htmljs = htmljs.constructView(); 86 | // fall through to Blaze.View case below 87 | } 88 | if (htmljs instanceof Blaze.View) { 89 | Blaze._materializeView(htmljs, parentView, workStack, intoArray); 90 | return; 91 | } 92 | } 93 | } 94 | 95 | throw new Error("Unexpected object in htmljs: " + htmljs); 96 | }; 97 | 98 | var materializeTag = function (tag, parentView, workStack) { 99 | var tagName = tag.tagName; 100 | var elem; 101 | if ((HTML.isKnownSVGElement(tagName) || isSVGAnchor(tag)) 102 | && document.createElementNS) { 103 | // inline SVG 104 | elem = document.createElementNS('http://www.w3.org/2000/svg', tagName); 105 | } else { 106 | // normal elements 107 | elem = document.createElement(tagName); 108 | } 109 | 110 | var rawAttrs = tag.attrs; 111 | var children = tag.children; 112 | if (tagName === 'textarea' && tag.children.length && 113 | ! (rawAttrs && ('value' in rawAttrs))) { 114 | // Provide very limited support for TEXTAREA tags with children 115 | // rather than a "value" attribute. 116 | // Reactivity in the form of Views nested in the tag's children 117 | // won't work. Compilers should compile textarea contents into 118 | // the "value" attribute of the tag, wrapped in a function if there 119 | // is reactivity. 120 | if (typeof rawAttrs === 'function' || 121 | HTML.isArray(rawAttrs)) { 122 | throw new Error("Can't have reactive children of TEXTAREA node; " + 123 | "use the 'value' attribute instead."); 124 | } 125 | rawAttrs = _.extend({}, rawAttrs || null); 126 | rawAttrs.value = Blaze._expand(children, parentView); 127 | children = []; 128 | } 129 | 130 | if (rawAttrs) { 131 | var attrUpdater = new ElementAttributesUpdater(elem); 132 | var updateAttributes = function () { 133 | var expandedAttrs = Blaze._expandAttributes(rawAttrs, parentView); 134 | var flattenedAttrs = HTML.flattenAttributes(expandedAttrs); 135 | var stringAttrs = {}; 136 | for (var attrName in flattenedAttrs) { 137 | stringAttrs[attrName] = Blaze._toText(flattenedAttrs[attrName], 138 | parentView, 139 | HTML.TEXTMODE.STRING); 140 | } 141 | attrUpdater.update(stringAttrs); 142 | }; 143 | var updaterComputation; 144 | if (parentView) { 145 | updaterComputation = 146 | parentView.autorun(updateAttributes, undefined, 'updater'); 147 | } else { 148 | updaterComputation = Tracker.nonreactive(function () { 149 | return Tracker.autorun(function () { 150 | Tracker._withCurrentView(parentView, updateAttributes); 151 | }); 152 | }); 153 | } 154 | Blaze._DOMBackend.Teardown.onElementTeardown(elem, function attrTeardown() { 155 | updaterComputation.stop(); 156 | }); 157 | } 158 | 159 | if (children.length) { 160 | var childNodesAndRanges = []; 161 | // push this function first so that it's done last 162 | workStack.push(function () { 163 | for (var i = 0; i < childNodesAndRanges.length; i++) { 164 | var x = childNodesAndRanges[i]; 165 | if (x instanceof Blaze._DOMRange) 166 | x.attach(elem); 167 | else 168 | elem.appendChild(x); 169 | } 170 | }); 171 | // now push the task that calculates childNodesAndRanges 172 | workStack.push(_.bind(Blaze._materializeDOM, null, 173 | children, childNodesAndRanges, parentView, 174 | workStack)); 175 | } 176 | 177 | return elem; 178 | }; 179 | 180 | 181 | var isSVGAnchor = function (node) { 182 | // We generally aren't able to detect SVG elements because 183 | // if "A" were in our list of known svg element names, then all 184 | // nodes would be created using 185 | // `document.createElementNS`. But in the special case of , we can at least detect that attribute and 187 | // create an SVG tag in that case. 188 | // 189 | // However, we still have a general problem of knowing when to 190 | // use document.createElementNS and when to use 191 | // document.createElement; for example, font tags will always 192 | // be created as SVG elements which can cause other 193 | // problems. #1977 194 | return (node.tagName === "a" && 195 | node.attrs && 196 | node.attrs["xlink:href"] !== undefined); 197 | }; 198 | -------------------------------------------------------------------------------- /compatibility/templating.js: -------------------------------------------------------------------------------- 1 | /* This file is needed to backport this pull request: https://github.com/meteor/meteor/pull/5903 2 | If it is a copy of templating.js file wrapped into a condition. 3 | 4 | TODO: Remove this file eventually. 5 | */ 6 | 7 | if (!Blaze.Template.__checkName) { 8 | // Packages and apps add templates on to this object. 9 | 10 | /** 11 | * @summary The class for defining templates 12 | * @class 13 | * @instanceName Template.myTemplate 14 | */ 15 | Template = Blaze.Template; 16 | 17 | var RESERVED_TEMPLATE_NAMES = "__proto__ name".split(" "); 18 | 19 | // Check for duplicate template names and illegal names that won't work. 20 | Template.__checkName = function (name) { 21 | // Some names can't be used for Templates. These include: 22 | // - Properties Blaze sets on the Template object. 23 | // - Properties that some browsers don't let the code to set. 24 | // These are specified in RESERVED_TEMPLATE_NAMES. 25 | if (name in Template || _.contains(RESERVED_TEMPLATE_NAMES, name)) { 26 | if ((Template[name] instanceof Template) && name !== "body") 27 | throw new Error("There are multiple templates named '" + name + "'. Each template needs a unique name."); 28 | throw new Error("This template name is reserved: " + name); 29 | } 30 | }; 31 | 32 | // XXX COMPAT WITH 0.8.3 33 | Template.__define__ = function (name, renderFunc) { 34 | Template.__checkName(name); 35 | Template[name] = new Template("Template." + name, renderFunc); 36 | // Exempt packages built pre-0.9.0 from warnings about using old 37 | // helper syntax, because we can. It's not very useful to get a 38 | // warning about someone else's code (like a package on Atmosphere), 39 | // and this should at least put a bit of a dent in number of warnings 40 | // that come from packages that haven't been updated lately. 41 | Template[name]._NOWARN_OLDSTYLE_HELPERS = true; 42 | }; 43 | 44 | // Define a template `Template.body` that renders its 45 | // `contentRenderFuncs`. `` tags (of which there may be 46 | // multiple) will have their contents added to it. 47 | 48 | /** 49 | * @summary The [template object](#templates_api) representing your `` 50 | * tag. 51 | * @locus Client 52 | */ 53 | Template.body = new Template('body', function () { 54 | var view = this; 55 | return _.map(Template.body.contentRenderFuncs, function (func) { 56 | return func.apply(view); 57 | }); 58 | }); 59 | Template.body.contentRenderFuncs = []; // array of Blaze.Views 60 | Template.body.view = null; 61 | 62 | Template.body.addContent = function (renderFunc) { 63 | Template.body.contentRenderFuncs.push(renderFunc); 64 | }; 65 | 66 | // This function does not use `this` and so it may be called 67 | // as `Meteor.startup(Template.body.renderIntoDocument)`. 68 | Template.body.renderToDocument = function () { 69 | // Only do it once. 70 | if (Template.body.view) 71 | return; 72 | 73 | var view = Blaze.render(Template.body, document.body); 74 | Template.body.view = view; 75 | }; 76 | 77 | // XXX COMPAT WITH 0.9.0 78 | UI.body = Template.body; 79 | 80 | // XXX COMPAT WITH 0.9.0 81 | // ( tags in packages built with 0.9.0) 82 | Template.__body__ = Template.body; 83 | Template.__body__.__contentParts = Template.body.contentViews; 84 | Template.__body__.__instantiate = Template.body.renderToDocument; 85 | } 86 | -------------------------------------------------------------------------------- /compile-templates.js: -------------------------------------------------------------------------------- 1 | // Based on meteor/packages/templating/plugin/compile-templates.js. 2 | 3 | Plugin.registerCompiler({ 4 | extensions: ['html'], 5 | isTemplate: true 6 | }, () => new CachingHtmlCompiler( 7 | "blaze-components-templating", 8 | TemplatingTools.scanHtmlForTags, 9 | TemplatingTools.compileTagsWithSpacebars 10 | )); 11 | -------------------------------------------------------------------------------- /debug.coffee: -------------------------------------------------------------------------------- 1 | class BlazeComponentDebug extends BaseComponentDebug 2 | @startComponent: (component) -> 3 | super arguments... 4 | 5 | console.log component.data() 6 | 7 | @startMarkedComponent: (component) -> 8 | super arguments... 9 | 10 | console.log component.data() 11 | 12 | @dumpComponentSubtree: (rootComponentOrElement) -> 13 | if 'nodeType' of rootComponentOrElement and rootComponentOrElement.nodeType is Node.ELEMENT_NODE 14 | rootComponentOrElement = BlazeComponent.getComponentForElement rootComponentOrElement 15 | 16 | super arguments... 17 | 18 | @dumpComponentTree: (rootComponentOrElement) -> 19 | if 'nodeType' of rootComponentOrElement and rootComponentOrElement.nodeType is Node.ELEMENT_NODE 20 | rootComponentOrElement = BlazeComponent.getComponentForElement rootComponentOrElement 21 | 22 | super arguments... 23 | 24 | @dumpAllComponents: -> 25 | allRootComponents = [] 26 | 27 | $('*').each (i, element) => 28 | component = BlazeComponent.getComponentForElement element 29 | return unless component 30 | rootComponent = @componentRoot component 31 | allRootComponents.push rootComponent unless rootComponent in allRootComponents 32 | 33 | for rootComponent in allRootComponents 34 | @dumpComponentSubtree rootComponent 35 | 36 | return 37 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /demo/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | 1.4.3-split-account-service-packages 17 | 1.5-add-dynamic-import-package 18 | 1.7-split-underscore-from-meteor-base 19 | 1.8.3-split-jquery-from-blaze 20 | -------------------------------------------------------------------------------- /demo/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /demo/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 1ljl1t3adwvzg1hl9okx 8 | -------------------------------------------------------------------------------- /demo/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | autopublish@1.0.7 8 | insecure@1.0.7 9 | coffeescript 10 | peerlibrary:blaze-components 11 | reactive-var@1.0.11 12 | stylus@=2.513.14 13 | peerlibrary:dimsum 14 | spiderable 15 | meteor-base@1.4.0 16 | mobile-experience@1.1.0 17 | mongo@1.10.0 18 | session@1.2.0 19 | jquery 20 | tracker@1.2.0 21 | logging@1.1.20 22 | reload@1.3.0 23 | random@1.2.0 24 | ejson@1.1.1 25 | spacebars 26 | check@1.3.1 27 | peerlibrary:reactive-field 28 | standard-minifier-css@1.6.1 29 | standard-minifier-js@2.6.0 30 | shell-server@0.5.0 31 | dynamic-import@0.5.2 32 | underscore@1.0.10 33 | -------------------------------------------------------------------------------- /demo/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /demo/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.11.1 2 | -------------------------------------------------------------------------------- /demo/.meteor/versions: -------------------------------------------------------------------------------- 1 | allow-deny@1.1.0 2 | autopublish@1.0.7 3 | autoupdate@1.6.0 4 | babel-compiler@7.5.3 5 | babel-runtime@1.5.0 6 | base64@1.0.12 7 | binary-heap@1.0.11 8 | blaze@2.3.4 9 | blaze-tools@1.0.10 10 | boilerplate-generator@1.7.1 11 | caching-compiler@1.2.2 12 | caching-html-compiler@1.1.3 13 | callback-hook@1.3.0 14 | check@1.3.1 15 | coffeescript@2.4.1 16 | coffeescript-compiler@2.4.1 17 | ddp@1.4.0 18 | ddp-client@2.3.3 19 | ddp-common@1.4.0 20 | ddp-server@2.3.2 21 | deps@1.0.12 22 | diff-sequence@1.1.1 23 | dynamic-import@0.5.3 24 | ecmascript@0.14.3 25 | ecmascript-runtime@0.7.0 26 | ecmascript-runtime-client@0.11.0 27 | ecmascript-runtime-server@0.10.0 28 | ejson@1.1.1 29 | es5-shim@4.8.0 30 | fetch@0.1.1 31 | geojson-utils@1.0.10 32 | hot-code-push@1.0.4 33 | html-tools@1.0.11 34 | htmljs@1.0.11 35 | id-map@1.1.0 36 | insecure@1.0.7 37 | inter-process-messaging@0.1.1 38 | jquery@1.11.11 39 | launch-screen@1.2.0 40 | livedata@1.0.18 41 | logging@1.1.20 42 | meteor@1.9.3 43 | meteor-base@1.4.0 44 | minifier-css@1.5.3 45 | minifier-js@2.6.0 46 | minimongo@1.6.0 47 | mobile-experience@1.1.0 48 | mobile-status-bar@1.1.0 49 | modern-browsers@0.1.5 50 | modules@0.15.0 51 | modules-runtime@0.12.0 52 | mongo@1.10.0 53 | mongo-decimal@0.1.1 54 | mongo-dev-server@1.1.0 55 | mongo-id@1.0.7 56 | npm-mongo@3.8.1 57 | observe-sequence@1.0.16 58 | ordered-dict@1.1.0 59 | peerlibrary:assert@0.3.0 60 | peerlibrary:base-component@0.17.1 61 | peerlibrary:blaze-components@0.23.0 62 | peerlibrary:computed-field@0.10.0 63 | peerlibrary:data-lookup@0.3.0 64 | peerlibrary:dimsum@0.1.8_6 65 | peerlibrary:reactive-field@0.6.0 66 | promise@0.11.2 67 | random@1.2.0 68 | reactive-dict@1.3.0 69 | reactive-var@1.0.11 70 | reload@1.3.0 71 | retry@1.1.0 72 | routepolicy@1.1.0 73 | session@1.2.0 74 | shell-server@0.5.0 75 | socket-stream-client@0.3.1 76 | spacebars@1.0.15 77 | spacebars-compiler@1.1.3 78 | spiderable@1.0.13 79 | standard-minifier-css@1.6.1 80 | standard-minifier-js@2.6.0 81 | stylus@2.513.14 82 | templating@1.3.2 83 | templating-compiler@1.3.3 84 | templating-runtime@1.3.2 85 | templating-tools@1.1.2 86 | tracker@1.2.0 87 | underscore@1.0.10 88 | webapp@1.9.1 89 | webapp-hashing@1.0.9 90 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | Demo application for [Meteor Blaze Components](https://github.com/peerlibrary/meteor-blaze-components). 2 | 3 | Deployed at: http://components.meteor.com 4 | -------------------------------------------------------------------------------- /demo/client/base.coffee: -------------------------------------------------------------------------------- 1 | Session.setDefault 'language', 'es2015' 2 | 3 | Template.registerHelper 'isES2015', -> 4 | Session.equals 'language', 'es2015' 5 | 6 | Template.registerHelper 'isJavaScript', -> 7 | Session.equals 'language', 'javascript' 8 | 9 | Template.registerHelper 'isCoffeeScript', -> 10 | Session.equals 'language', 'coffeescript' 11 | 12 | Template.languageSwitch.events 13 | 'click a': (event, template) -> 14 | event.preventDefault() 15 | Session.set 'language', $(event.target).text() 16 | Tracker.afterFlush -> 17 | Prism.highlightAll() 18 | return 19 | 20 | Template.languageChooser.events 21 | 'change #languageChooser': (event, template) -> 22 | event.preventDefault() 23 | Session.set 'language', $(event.target).val() 24 | Tracker.afterFlush -> 25 | Prism.highlightAll() 26 | return 27 | 28 | Meteor.startup -> 29 | Tracker.afterFlush -> 30 | Prism.highlightAll() 31 | -------------------------------------------------------------------------------- /demo/client/demo.coffee: -------------------------------------------------------------------------------- 1 | ### Auto-select demo ### 2 | 3 | Template.autoSelectDemo.helpers 4 | value: -> 5 | Values.findOne(@id)?.value 6 | 7 | ### Auto-select input ### 8 | 9 | Template.input.helpers 10 | value: -> 11 | # Read value from the collection. 12 | Values.findOne(@id)?.value 13 | 14 | Template.input.events 15 | # Save value to the collection when it changes. 16 | 'change input': (event, template) -> 17 | Values.upsert @id, $set: value: event.target.value 18 | 19 | # Auto-select text when user clicks in the input. 20 | 'click input': (event, template) -> 21 | $(event.target).select() 22 | 23 | ### Auto-select input component ### 24 | 25 | class AutoSelectInputComponent extends BlazeComponent 26 | @register 'AutoSelectInputComponent' 27 | 28 | template: -> 29 | 'input' 30 | 31 | value: -> 32 | Values.findOne(@data().id)?.value 33 | 34 | events: -> [ 35 | 'change input': @onChange 36 | 'click input': @onClick 37 | ] 38 | 39 | onChange: (event) -> 40 | Values.upsert @data().id, $set: value: event.target.value 41 | 42 | onClick: (event) -> 43 | $(event.target).select() 44 | 45 | ### Auto-select textarea component ### 46 | 47 | class AutoSelectTextareaComponent extends AutoSelectInputComponent 48 | @register 'AutoSelectTextareaComponent' 49 | 50 | template: -> 51 | 'AutoSelectTextareaComponent' 52 | 53 | events: -> 54 | super().concat 55 | 'change textarea': @onChange 56 | 'click textarea': @onClick 57 | 58 | ### Real-time input component ### 59 | 60 | class RealTimeInputComponent extends AutoSelectInputComponent 61 | @register 'RealTimeInputComponent' 62 | 63 | events: -> 64 | super().concat 65 | 'keyup input': @onKeyup 66 | 67 | onKeyup: (event) -> 68 | $(event.target).change() 69 | 70 | ### Persistent input component ### 71 | 72 | class PersistentInputComponent extends AutoSelectInputComponent 73 | @register 'PersistentInputComponent' 74 | 75 | onCreated: -> 76 | # This will store the value at the start of editing. 77 | @storedValue = new ReactiveField() 78 | 79 | value: -> 80 | # Return stored value during editing or normal otherwise. 81 | @storedValue() or super() 82 | 83 | events: -> 84 | super().concat 85 | 'focus input': @onFocus 86 | 'blur input': @onBlur 87 | 88 | onFocus: (event) -> 89 | # Store the current value when starting to edit. 90 | @storedValue @value() 91 | 92 | onBlur: (event) -> 93 | # We are no longer editing, so return to normal. 94 | @storedValue null 95 | 96 | ### Smart (auto-select, real-time, persistent) input component ### 97 | 98 | class SmartInputComponent extends BlazeComponent 99 | @register 'SmartInputComponent' 100 | 101 | template: -> 102 | 'input' 103 | 104 | mixins: -> 105 | [AutoSelectInputMixin, RealTimeInputMixin, PersistentInputMixin] 106 | 107 | value: -> 108 | @callFirstWith(@, 'value') or Values.findOne(@data().id)?.value 109 | 110 | events: -> [ 111 | 'change input': @onChange 112 | ] 113 | 114 | onChange: (event) -> 115 | Values.upsert @data().id, $set: value: event.target.value 116 | 117 | class AutoSelectInputMixin extends BlazeComponent 118 | events: -> [ 119 | 'click input': @onClick 120 | ] 121 | 122 | onClick: (event) -> 123 | $(event.target).select() 124 | 125 | class RealTimeInputMixin extends BlazeComponent 126 | events: -> [ 127 | 'keyup input': @onKeyUp 128 | ] 129 | 130 | onKeyUp: (event) -> 131 | $(event.target).change() 132 | 133 | class PersistentInputMixin extends BlazeComponent 134 | onCreated: -> 135 | @storedValue = new ReactiveField() 136 | 137 | value: -> 138 | @storedValue() 139 | 140 | events: -> [ 141 | 'focus input': @onFocus 142 | 'blur input': @onBlur 143 | ] 144 | 145 | onFocus: (event) -> 146 | @storedValue @mixinParent().value() 147 | 148 | onBlur: (event) -> 149 | @storedValue null 150 | 151 | ### Extreme decomposition (auto-select, real-time, persistent, cancelable, form field, storage) input component ### 152 | 153 | class PersistentInputMixin2 extends BlazeComponent 154 | onCreated: -> 155 | @storedValue = new ReactiveField() 156 | 157 | value: -> 158 | @storedValue() or @mixinParent().callFirstWith(@, 'value') 159 | 160 | events: -> [ 161 | 'focus input': @onFocus 162 | 'blur input': @onBlur 163 | ] 164 | 165 | onFocus: (event) -> 166 | @storedValue @mixinParent().callFirstWith(null, 'value') 167 | 168 | onBlur: (event) -> 169 | @storedValue null 170 | 171 | class ExtremeInputComponent extends BlazeComponent 172 | @register 'ExtremeInputComponent' 173 | 174 | template: -> 175 | 'input' 176 | 177 | mixins: -> [ 178 | AutoSelectInputMixin, RealTimeInputMixin, 179 | CancelableInputMixin, FormFieldMixin, 180 | new StorageMixin Values, 'value', => @data().id 181 | ] 182 | 183 | class FormFieldMixin extends BlazeComponent 184 | value: -> 185 | @mixinParent().callFirstWith(null, 'getValue') 186 | 187 | events: -> [ 188 | 'change input': @onChange 189 | ] 190 | 191 | onChange: (event) -> 192 | @mixinParent().callFirstWith(null, 'setValue', event.target.value) 193 | 194 | class StorageMixin extends BlazeComponent 195 | constructor: (@collection, @fieldName, @selector) -> 196 | super() 197 | 198 | getValue: -> 199 | @collection.findOne(@selector())?[@fieldName] 200 | 201 | setValue: (value) -> 202 | modifier = $set: {} 203 | modifier.$set[@fieldName] = value 204 | @collection.upsert @selector(), modifier 205 | 206 | class CancelableInputMixin extends BlazeComponent 207 | mixinParent: (mixinParent) -> 208 | # We rely on the persistent input mixin to obtain the stored value. 209 | mixinParent.requireMixin PersistentInputMixin2 if mixinParent 210 | super arguments... 211 | 212 | events: -> [ 213 | 'keydown input': @onKeyDown 214 | ] 215 | 216 | onKeyDown: (event) -> 217 | # Undo renaming on escape. 218 | if event.keyCode is 27 219 | storedValue = @mixinParent().getMixin(PersistentInputMixin2).storedValue() 220 | $(event.target).val(storedValue).change().blur() 221 | -------------------------------------------------------------------------------- /demo/client/demo.js.rename: -------------------------------------------------------------------------------- 1 | /* Auto-select demo */ 2 | 3 | Template.autoSelectDemo.helpers({ 4 | value: function () { 5 | var doc = Values.findOne(this.id); 6 | if (doc) return doc.value; 7 | } 8 | }); 9 | 10 | /* Auto-select input */ 11 | 12 | Template.input.helpers({ 13 | value: function () { 14 | // Read value from the collection. 15 | var doc = Values.findOne(this.id); 16 | if (doc) return doc.value; 17 | } 18 | }); 19 | 20 | Template.input.events({ 21 | // Save value to the collection when it changes. 22 | 'change input': function (event, template) { 23 | Values.upsert(this.id, {$set: { 24 | value: event.target.value 25 | }}); 26 | }, 27 | 28 | // Auto-select text when user clicks in the input. 29 | 'click input': function (event, template) { 30 | $(event.target).select(); 31 | } 32 | }); 33 | 34 | /* Auto-select input component */ 35 | 36 | var AutoSelectInputComponent = BlazeComponent.extendComponent({ 37 | template: function () { 38 | return 'input'; 39 | }, 40 | 41 | value: function () { 42 | var doc = Values.findOne(this.data().id); 43 | if (doc) return doc.value; 44 | }, 45 | 46 | events: function () { 47 | return [{ 48 | 'change input': this.onChange, 49 | 'click input': this.onClick 50 | }]; 51 | }, 52 | 53 | onChange: function (event) { 54 | Values.upsert(this.data().id, {$set: { 55 | value: event.target.value 56 | }}); 57 | }, 58 | 59 | onClick: function (event) { 60 | $(event.target).select(); 61 | } 62 | }).register('AutoSelectInputComponent'); 63 | 64 | /* Auto-select textarea component */ 65 | 66 | var AutoSelectTextareaComponent = AutoSelectInputComponent.extendComponent({ 67 | template: function () { 68 | return 'AutoSelectTextareaComponent'; 69 | }, 70 | 71 | events: function () { 72 | return Object.getPrototypeOf(AutoSelectTextareaComponent).prototype.events.call(this).concat({ 73 | 'change textarea': this.onChange, 74 | 'click textarea': this.onClick 75 | }); 76 | } 77 | }).register('AutoSelectTextareaComponent'); 78 | 79 | /* Real-time input component */ 80 | 81 | var RealTimeInputComponent = AutoSelectInputComponent.extendComponent({ 82 | events: function () { 83 | return Object.getPrototypeOf(RealTimeInputComponent).prototype.events.call(this).concat({ 84 | 'keyup input': this.onKeyup 85 | }); 86 | }, 87 | 88 | onKeyup: function (event) { 89 | $(event.target).change(); 90 | } 91 | }).register('RealTimeInputComponent'); 92 | 93 | /* Persistent input component */ 94 | 95 | var PersistentInputComponent = AutoSelectInputComponent.extendComponent({ 96 | onCreated: function () { 97 | // This will store the value at the start of editing. 98 | this.storedValue = new ReactiveField(); 99 | }, 100 | 101 | value: function () { 102 | // Return stored value during editing or normal otherwise. 103 | return this.storedValue() || Object.getPrototypeOf(PersistentInputComponent).prototype.value.call(this); 104 | }, 105 | 106 | events: function () { 107 | return Object.getPrototypeOf(PersistentInputComponent).prototype.events.call(this).concat({ 108 | 'focus input': this.onFocus, 109 | 'blur input': this.onBlur 110 | }); 111 | }, 112 | 113 | onFocus: function (event) { 114 | // Store the current value when starting to edit. 115 | this.storedValue(this.value()); 116 | }, 117 | 118 | onBlur: function (event) { 119 | // We are no longer editing, so return to normal. 120 | this.storedValue(null); 121 | } 122 | }).register('PersistentInputComponent'); 123 | 124 | /* Smart (auto-select, real-time, persistent) input component */ 125 | 126 | var SmartInputComponent = BlazeComponent.extendComponent({ 127 | template: function () { 128 | return 'input'; 129 | }, 130 | 131 | mixins: function () { 132 | return [AutoSelectInputMixin, RealTimeInputMixin, PersistentInputMixin]; 133 | }, 134 | 135 | value: function () { 136 | var value = this.callFirstWith(this, 'value'); 137 | if (value) return value; 138 | var doc = Values.findOne(this.data().id); 139 | if (doc) return doc.value; 140 | }, 141 | 142 | events: function () { 143 | return [{ 144 | 'change input': this.onChange 145 | }]; 146 | }, 147 | 148 | onChange: function (event) { 149 | Values.upsert(this.data().id, {$set: { 150 | value: event.target.value 151 | }}); 152 | } 153 | }).register('SmartInputComponent'); 154 | 155 | var AutoSelectInputMixin = BlazeComponent.extendComponent({ 156 | events: function () { 157 | return [{ 158 | 'click input': this.onClick 159 | }]; 160 | }, 161 | 162 | onClick: function (event) { 163 | $(event.target).select(); 164 | } 165 | }); 166 | 167 | var RealTimeInputMixin = BlazeComponent.extendComponent({ 168 | events: function () { 169 | return [{ 170 | 'keyup input': this.onKeyUp 171 | }]; 172 | }, 173 | 174 | onKeyUp: function (event) { 175 | $(event.target).change(); 176 | } 177 | }); 178 | 179 | var PersistentInputMixin = BlazeComponent.extendComponent({ 180 | onCreated: function () { 181 | this.storedValue = new ReactiveField(); 182 | }, 183 | 184 | value: function () { 185 | return this.storedValue(); 186 | }, 187 | 188 | events: function () { 189 | return [{ 190 | 'focus input': this.onFocus, 191 | 'blur input': this.onBlur 192 | }]; 193 | }, 194 | 195 | onFocus: function (event) { 196 | this.storedValue(this.mixinParent().value()); 197 | }, 198 | 199 | onBlur: function (event) { 200 | this.storedValue(null); 201 | } 202 | }); 203 | 204 | /* Extreme decomposition (auto-select, real-time, persistent, cancelable, form field, storage) input component */ 205 | 206 | var PersistentInputMixin2 = BlazeComponent.extendComponent({ 207 | onCreated: function () { 208 | this.storedValue = new ReactiveField(); 209 | }, 210 | 211 | value: function () { 212 | return this.storedValue() || this.mixinParent().callFirstWith(this, 'value'); 213 | }, 214 | 215 | events: function () { 216 | return [{ 217 | 'focus input': this.onFocus, 218 | 'blur input': this.onBlur 219 | }]; 220 | }, 221 | 222 | onFocus: function (event) { 223 | this.storedValue(this.mixinParent().callFirstWith(null, 'value')); 224 | }, 225 | 226 | onBlur: function (event) { 227 | this.storedValue(null); 228 | } 229 | }); 230 | 231 | var ExtremeInputComponent = BlazeComponent.extendComponent({ 232 | template: function () { 233 | return 'input'; 234 | }, 235 | 236 | mixins: function () { 237 | return [ 238 | AutoSelectInputMixin, RealTimeInputMixin, 239 | CancelableInputMixin, FormFieldMixin, 240 | new StorageMixin(Values, 'value', function () { 241 | return this.data().id; 242 | }.bind(this)) 243 | ]; 244 | } 245 | }).register('ExtremeInputComponent'); 246 | 247 | var FormFieldMixin = BlazeComponent.extendComponent({ 248 | value: function () { 249 | return this.mixinParent().callFirstWith(null, 'getValue'); 250 | }, 251 | 252 | events: function () { 253 | return [{ 254 | 'change input': this.onChange 255 | }]; 256 | }, 257 | 258 | onChange: function (event) { 259 | this.mixinParent().callFirstWith(null, 'setValue', event.target.value); 260 | } 261 | }); 262 | 263 | var StorageMixin = BlazeComponent.extendComponent(function (collection, fieldName, selector) { 264 | this.collection = collection; 265 | this.fieldName = fieldName; 266 | this.selector = selector; 267 | }, { 268 | getValue: function () { 269 | var doc = this.collection.findOne(this.selector()); 270 | if (doc) return doc[this.fieldName]; 271 | }, 272 | 273 | setValue: function (value) { 274 | var modifier = { 275 | $set: {} 276 | }; 277 | modifier.$set[this.fieldName] = value; 278 | this.collection.upsert(this.selector(), modifier); 279 | } 280 | }); 281 | 282 | var CancelableInputMixin = BlazeComponent.extendComponent({ 283 | mixinParent: function (mixinParent) { 284 | if (mixinParent) { 285 | mixinParent.requireMixin(PersistentInputMixin2); 286 | } 287 | return Object.getPrototypeOf(CancelableInputMixin).prototype.mixinParent.apply(this, arguments); 288 | }, 289 | 290 | events: function () { 291 | return [{ 292 | 'keydown input': this.onKeyDown 293 | }]; 294 | }, 295 | 296 | onKeyDown: function (event) { 297 | // Undo renaming on escape. 298 | if (event.keyCode === 27) { 299 | var storedValue = this.mixinParent().getMixin(PersistentInputMixin2).storedValue(); 300 | $(event.target).val(storedValue).change().blur(); 301 | } 302 | } 303 | }); 304 | -------------------------------------------------------------------------------- /demo/client/demo.next.js.rename: -------------------------------------------------------------------------------- 1 | /* Auto-select demo */ 2 | 3 | Template.autoSelectDemo.helpers({ 4 | value: function () { 5 | var doc = Values.findOne(this.id); 6 | if (doc) return doc.value; 7 | } 8 | }); 9 | 10 | /* Auto-select input */ 11 | 12 | Template.input.helpers({ 13 | value: function () { 14 | // Read value from the collection. 15 | var doc = Values.findOne(this.id); 16 | if (doc) return doc.value; 17 | } 18 | }); 19 | 20 | Template.input.events({ 21 | // Save value to the collection when it changes. 22 | 'change input': function (event, template) { 23 | Values.upsert(this.id, {$set: { 24 | value: event.target.value 25 | }}); 26 | }, 27 | 28 | // Auto-select text when user clicks in the input. 29 | 'click input': function (event, template) { 30 | $(event.target).select(); 31 | } 32 | }); 33 | 34 | /* Auto-select input component */ 35 | 36 | class AutoSelectInputComponent extends BlazeComponent { 37 | template() { 38 | return 'input'; 39 | } 40 | 41 | value() { 42 | var doc = Values.findOne(this.data().id); 43 | if (doc) return doc.value; 44 | } 45 | 46 | events() { 47 | return [{ 48 | 'change input': this.onChange, 49 | 'click input': this.onClick 50 | }]; 51 | } 52 | 53 | onChange(event) { 54 | Values.upsert(this.data().id, {$set: { 55 | value: event.target.value 56 | }}); 57 | } 58 | 59 | onClick(event) { 60 | $(event.target).select(); 61 | } 62 | } 63 | 64 | AutoSelectInputComponent.register('AutoSelectInputComponent'); 65 | 66 | /* Auto-select textarea component */ 67 | 68 | class AutoSelectTextareaComponent extends AutoSelectInputComponent { 69 | template() { 70 | return 'AutoSelectTextareaComponent'; 71 | } 72 | 73 | events() { 74 | return super.events().concat({ 75 | 'change textarea': this.onChange, 76 | 'click textarea': this.onClick 77 | }); 78 | } 79 | } 80 | 81 | AutoSelectTextareaComponent.register('AutoSelectTextareaComponent'); 82 | 83 | /* Real-time input component */ 84 | 85 | class RealTimeInputComponent extends AutoSelectInputComponent { 86 | events() { 87 | return super.events().concat({ 88 | 'keyup input': this.onKeyup 89 | }); 90 | } 91 | 92 | onKeyup(event) { 93 | $(event.target).change(); 94 | } 95 | } 96 | 97 | RealTimeInputComponent.register('RealTimeInputComponent'); 98 | 99 | /* Persistent input component */ 100 | 101 | class PersistentInputComponent extends AutoSelectInputComponent { 102 | onCreated() { 103 | // This will store the value at the start of editing. 104 | this.storedValue = new ReactiveField(); 105 | } 106 | 107 | value() { 108 | // Return stored value during editing or normal otherwise. 109 | return this.storedValue() || super.value(); 110 | } 111 | 112 | events() { 113 | return super.events().concat({ 114 | 'focus input': this.onFocus, 115 | 'blur input': this.onBlur 116 | }); 117 | } 118 | 119 | onFocus(event) { 120 | // Store the current value when starting to edit. 121 | this.storedValue(this.value()); 122 | } 123 | 124 | onBlur(event) { 125 | // We are no longer editing, so return to normal. 126 | this.storedValue(null); 127 | } 128 | } 129 | 130 | PersistentInputComponent.register('PersistentInputComponent'); 131 | 132 | /* Smart (auto-select, real-time, persistent) input component */ 133 | 134 | class SmartInputComponent extends BlazeComponent { 135 | template() { 136 | return 'input'; 137 | } 138 | 139 | mixins() { 140 | return [AutoSelectInputMixin, RealTimeInputMixin, PersistentInputMixin]; 141 | } 142 | 143 | value() { 144 | var value = this.callFirstWith(this, 'value'); 145 | if (value) return value; 146 | var doc = Values.findOne(this.data().id); 147 | if (doc) return doc.value; 148 | } 149 | 150 | events() { 151 | return [{ 152 | 'change input': this.onChange 153 | }]; 154 | } 155 | 156 | onChange(event) { 157 | Values.upsert(this.data().id, {$set: { 158 | value: event.target.value 159 | }}); 160 | } 161 | } 162 | 163 | SmartInputComponent.register('SmartInputComponent'); 164 | 165 | class AutoSelectInputMixin extends BlazeComponent { 166 | events() { 167 | return [{ 168 | 'click input': this.onClick 169 | }]; 170 | } 171 | 172 | onClick(event) { 173 | $(event.target).select(); 174 | } 175 | } 176 | 177 | class RealTimeInputMixin extends BlazeComponent { 178 | events() { 179 | return [{ 180 | 'keyup input': this.onKeyUp 181 | }]; 182 | } 183 | 184 | onKeyUp(event) { 185 | $(event.target).change(); 186 | } 187 | } 188 | 189 | class PersistentInputMixin extends BlazeComponent { 190 | onCreated() { 191 | this.storedValue = new ReactiveField(); 192 | } 193 | 194 | value() { 195 | return this.storedValue(); 196 | } 197 | 198 | events() { 199 | return [{ 200 | 'focus input': this.onFocus, 201 | 'blur input': this.onBlur 202 | }]; 203 | } 204 | 205 | onFocus(event) { 206 | this.storedValue(this.mixinParent().value()); 207 | } 208 | 209 | onBlur(event) { 210 | this.storedValue(null); 211 | } 212 | } 213 | 214 | /* Extreme decomposition (auto-select, real-time, persistent, cancelable, form field, storage) input component */ 215 | 216 | class PersistentInputMixin2 extends BlazeComponent { 217 | onCreated() { 218 | this.storedValue = new ReactiveField(); 219 | } 220 | 221 | value() { 222 | return this.storedValue() || this.mixinParent().callFirstWith(this, 'value'); 223 | } 224 | 225 | events() { 226 | return [{ 227 | 'focus input': this.onFocus, 228 | 'blur input': this.onBlur 229 | }]; 230 | } 231 | 232 | onFocus(event) { 233 | this.storedValue(this.mixinParent().callFirstWith(null, 'value')); 234 | } 235 | 236 | onBlur(event) { 237 | this.storedValue(null); 238 | } 239 | } 240 | 241 | class ExtremeInputComponent extends BlazeComponent { 242 | template() { 243 | return 'input'; 244 | } 245 | 246 | mixins() { 247 | return [ 248 | AutoSelectInputMixin, RealTimeInputMixin, 249 | CancelableInputMixin, FormFieldMixin, 250 | new StorageMixin(Values, 'value', () => this.data().id) 251 | ]; 252 | } 253 | } 254 | 255 | ExtremeInputComponent.register('ExtremeInputComponent'); 256 | 257 | class FormFieldMixin extends BlazeComponent { 258 | value() { 259 | return this.mixinParent().callFirstWith(null, 'getValue'); 260 | } 261 | 262 | events() { 263 | return [{ 264 | 'change input': this.onChange 265 | }]; 266 | } 267 | 268 | onChange(event) { 269 | this.mixinParent().callFirstWith(null, 'setValue', event.target.value); 270 | } 271 | } 272 | 273 | class StorageMixin extends BlazeComponent { 274 | constructor(collection, fieldName, selector) { 275 | this.collection = collection; 276 | this.fieldName = fieldName; 277 | this.selector = selector; 278 | } 279 | 280 | getValue() { 281 | var doc = this.collection.findOne(this.selector()); 282 | if (doc) return doc[this.fieldName]; 283 | } 284 | 285 | setValue(value) { 286 | var modifier = { 287 | $set: {} 288 | }; 289 | modifier.$set[this.fieldName] = value; 290 | this.collection.upsert(this.selector(), modifier); 291 | } 292 | } 293 | 294 | class CancelableInputMixin extends BlazeComponent { 295 | mixinParent(mixinParent) { 296 | if (mixinParent) { 297 | mixinParent.requireMixin(PersistentInputMixin2); 298 | } 299 | return super.mixinParent(mixinParent); 300 | } 301 | 302 | events() { 303 | return [{ 304 | 'keydown input': this.onKeyDown 305 | }]; 306 | } 307 | 308 | onKeyDown(event) { 309 | // Undo renaming on escape. 310 | if (event.keyCode === 27) { 311 | var storedValue = this.mixinParent().getMixin(PersistentInputMixin2).storedValue(); 312 | $(event.target).val(storedValue).change().blur(); 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /demo/client/demo.styl: -------------------------------------------------------------------------------- 1 | @import url('http://fonts.googleapis.com/css?family=Slabo+27px'); 2 | @import url(http://fonts.googleapis.com/css?family=Roboto:300,300italic); 3 | 4 | * 5 | box-sizing border-box 6 | 7 | input, textarea 8 | font inherit 9 | 10 | body 11 | margin 30px 12 | font-family 'Roboto', sans-serif 13 | font-size 14px 14 | font-weight 300 15 | 16 | > * 17 | width 60% 18 | 19 | > hr 20 | margin 2em auto 2em 0 21 | border none 22 | height 1px 23 | display block 24 | background #e6e3e1 25 | 26 | h1, h2, h3 27 | font-family 'Slabo 27px', serif 28 | 29 | a 30 | color #07a 31 | text-decoration none 32 | font-weight 400 33 | 34 | &:hover 35 | text-decoration underline 36 | 37 | aside 38 | position relative 39 | float right 40 | width 30% 41 | font-size 12px 42 | border 1px solid #e6e3e1 43 | padding 0 10px 44 | margin 0 5% 45 | border-radius 3px 46 | 47 | &::before 48 | content "" 49 | display block 50 | width 18% 51 | border-top inherit 52 | position absolute 53 | z-index -1 54 | left 0 55 | margin-left -18% 56 | top 35px 57 | 58 | figure 59 | margin 0 60 | 61 | img 62 | display block 63 | max-width 500px 64 | margin 0 auto 65 | 66 | .comment 67 | font-style italic 68 | 69 | .demo 70 | display table 71 | table-layout fixed 72 | width 100% 73 | border-collapse separate 74 | border-spacing 10px 75 | background #e6e3e1 76 | border-radius 3px 77 | 78 | .live 79 | display table-cell 80 | vertical-align middle 81 | text-align center 82 | 83 | .code 84 | display table-cell 85 | padding-left 5px 86 | width 40% 87 | font-size 12px 88 | 89 | .header 90 | display block 91 | background #726f6c 92 | color white 93 | margin-top -1px 94 | border-radius 2px 2px 0 0 95 | padding 1px 10px 96 | text-shadow none 97 | line-height 1.5 98 | 99 | a 100 | a:hover 101 | color #aaa 102 | text-decoration none 103 | 104 | pre 105 | margin 0 0 10px 0 106 | 107 | &:last-child 108 | margin 0 109 | -------------------------------------------------------------------------------- /demo/client/lib/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */ 2 | /** 3 | * prism.js default theme for JavaScript, CSS and HTML 4 | * Based on dabblet (http://dabblet.com) 5 | * @author Lea Verou 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: black; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | line-height: 1.5; 19 | border-radius: 2px; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | margin: .5em 0; 53 | } 54 | 55 | pre[class*="language-"] code { 56 | display: block; 57 | padding: 1em; 58 | overflow: auto; 59 | } 60 | 61 | 62 | :not(pre) > code[class*="language-"], 63 | pre[class*="language-"] { 64 | background: #f5f2f0; 65 | } 66 | 67 | /* Inline code */ 68 | :not(pre) > code[class*="language-"] { 69 | padding: .1em; 70 | border-radius: .3em; 71 | } 72 | 73 | .token.comment, 74 | .token.prolog, 75 | .token.doctype, 76 | .token.cdata { 77 | color: slategray; 78 | } 79 | 80 | .token.punctuation { 81 | color: #999; 82 | } 83 | 84 | .namespace { 85 | opacity: .7; 86 | } 87 | 88 | .token.property, 89 | .token.tag, 90 | .token.boolean, 91 | .token.number, 92 | .token.constant, 93 | .token.symbol, 94 | .token.deleted { 95 | color: #905; 96 | } 97 | 98 | .token.selector, 99 | .token.attr-name, 100 | .token.string, 101 | .token.char, 102 | .token.builtin, 103 | .token.inserted { 104 | color: #690; 105 | } 106 | 107 | .token.operator, 108 | .token.entity, 109 | .token.url, 110 | .language-css .token.string, 111 | .style .token.string { 112 | color: #a67f59; 113 | background: hsla(0, 0%, 100%, .5); 114 | } 115 | 116 | .token.atrule, 117 | .token.attr-value, 118 | .token.keyword { 119 | color: #07a; 120 | } 121 | 122 | .token.function { 123 | color: #DD4A68; 124 | } 125 | 126 | .token.regex, 127 | .token.important, 128 | .token.variable { 129 | color: #e90; 130 | } 131 | 132 | .token.important, 133 | .token.bold { 134 | font-weight: bold; 135 | } 136 | .token.italic { 137 | font-style: italic; 138 | } 139 | 140 | .token.entity { 141 | cursor: help; 142 | } 143 | -------------------------------------------------------------------------------- /demo/client/lib/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+coffeescript+handlebars */ 2 | self = (typeof window !== 'undefined') 3 | ? window // if in browser 4 | : ( 5 | (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) 6 | ? self // if in worker 7 | : {} // if in node js 8 | ); 9 | 10 | /** 11 | * Prism: Lightweight, robust, elegant syntax highlighting 12 | * MIT license http://www.opensource.org/licenses/mit-license.php/ 13 | * @author Lea Verou http://lea.verou.me 14 | */ 15 | 16 | var Prism = (function(){ 17 | 18 | // Private helper vars 19 | var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i; 20 | 21 | var _ = self.Prism = { 22 | util: { 23 | encode: function (tokens) { 24 | if (tokens instanceof Token) { 25 | return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias); 26 | } else if (_.util.type(tokens) === 'Array') { 27 | return tokens.map(_.util.encode); 28 | } else { 29 | return tokens.replace(/&/g, '&').replace(/ text.length) { 270 | // Something went terribly wrong, ABORT, ABORT! 271 | break tokenloop; 272 | } 273 | 274 | if (str instanceof Token) { 275 | continue; 276 | } 277 | 278 | pattern.lastIndex = 0; 279 | 280 | var match = pattern.exec(str); 281 | 282 | if (match) { 283 | if(lookbehind) { 284 | lookbehindLength = match[1].length; 285 | } 286 | 287 | var from = match.index - 1 + lookbehindLength, 288 | match = match[0].slice(lookbehindLength), 289 | len = match.length, 290 | to = from + len, 291 | before = str.slice(0, from + 1), 292 | after = str.slice(to + 1); 293 | 294 | var args = [i, 1]; 295 | 296 | if (before) { 297 | args.push(before); 298 | } 299 | 300 | var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias); 301 | 302 | args.push(wrapped); 303 | 304 | if (after) { 305 | args.push(after); 306 | } 307 | 308 | Array.prototype.splice.apply(strarr, args); 309 | } 310 | } 311 | } 312 | } 313 | 314 | return strarr; 315 | }, 316 | 317 | hooks: { 318 | all: {}, 319 | 320 | add: function (name, callback) { 321 | var hooks = _.hooks.all; 322 | 323 | hooks[name] = hooks[name] || []; 324 | 325 | hooks[name].push(callback); 326 | }, 327 | 328 | run: function (name, env) { 329 | var callbacks = _.hooks.all[name]; 330 | 331 | if (!callbacks || !callbacks.length) { 332 | return; 333 | } 334 | 335 | for (var i=0, callback; callback = callbacks[i++];) { 336 | callback(env); 337 | } 338 | } 339 | } 340 | }; 341 | 342 | var Token = _.Token = function(type, content, alias) { 343 | this.type = type; 344 | this.content = content; 345 | this.alias = alias; 346 | }; 347 | 348 | Token.stringify = function(o, language, parent) { 349 | if (typeof o == 'string') { 350 | return o; 351 | } 352 | 353 | if (_.util.type(o) === 'Array') { 354 | return o.map(function(element) { 355 | return Token.stringify(element, language, o); 356 | }).join(''); 357 | } 358 | 359 | var env = { 360 | type: o.type, 361 | content: Token.stringify(o.content, language, parent), 362 | tag: 'span', 363 | classes: ['token', o.type], 364 | attributes: {}, 365 | language: language, 366 | parent: parent 367 | }; 368 | 369 | if (env.type == 'comment') { 370 | env.attributes['spellcheck'] = 'true'; 371 | } 372 | 373 | if (o.alias) { 374 | var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias]; 375 | Array.prototype.push.apply(env.classes, aliases); 376 | } 377 | 378 | _.hooks.run('wrap', env); 379 | 380 | var attributes = ''; 381 | 382 | for (var name in env.attributes) { 383 | attributes += name + '="' + (env.attributes[name] || '') + '"'; 384 | } 385 | 386 | return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '' + env.tag + '>'; 387 | 388 | }; 389 | 390 | if (!self.document) { 391 | if (!self.addEventListener) { 392 | // in Node.js 393 | return self.Prism; 394 | } 395 | // In worker 396 | self.addEventListener('message', function(evt) { 397 | var message = JSON.parse(evt.data), 398 | lang = message.language, 399 | code = message.code; 400 | 401 | self.postMessage(JSON.stringify(_.util.encode(_.tokenize(code, _.languages[lang])))); 402 | self.close(); 403 | }, false); 404 | 405 | return self.Prism; 406 | } 407 | 408 | // Get current script and highlight 409 | var script = document.getElementsByTagName('script'); 410 | 411 | script = script[script.length - 1]; 412 | 413 | if (script) { 414 | _.filename = script.src; 415 | 416 | if (document.addEventListener && !script.hasAttribute('data-manual')) { 417 | document.addEventListener('DOMContentLoaded', _.highlightAll); 418 | } 419 | } 420 | 421 | return self.Prism; 422 | 423 | })(); 424 | 425 | if (typeof module !== 'undefined' && module.exports) { 426 | module.exports = Prism; 427 | } 428 | ; 429 | Prism.languages.markup = { 430 | 'comment': //, 431 | 'prolog': /<\?.+?\?>/, 432 | 'doctype': //, 433 | 'cdata': //i, 434 | 'tag': { 435 | pattern: /<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/i, 436 | inside: { 437 | 'tag': { 438 | pattern: /^<\/?[\w:-]+/i, 439 | inside: { 440 | 'punctuation': /^<\/?/, 441 | 'namespace': /^[\w-]+?:/ 442 | } 443 | }, 444 | 'attr-value': { 445 | pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i, 446 | inside: { 447 | 'punctuation': /=|>|"/ 448 | } 449 | }, 450 | 'punctuation': /\/?>/, 451 | 'attr-name': { 452 | pattern: /[\w:-]+/, 453 | inside: { 454 | 'namespace': /^[\w-]+?:/ 455 | } 456 | } 457 | 458 | } 459 | }, 460 | 'entity': /?[\da-z]{1,8};/i 461 | }; 462 | 463 | // Plugin to make entity title show the real entity, idea by Roman Komarov 464 | Prism.hooks.add('wrap', function(env) { 465 | 466 | if (env.type === 'entity') { 467 | env.attributes['title'] = env.content.replace(/&/, '&'); 468 | } 469 | }); 470 | ; 471 | Prism.languages.css = { 472 | 'comment': /\/\*[\w\W]*?\*\//, 473 | 'atrule': { 474 | pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i, 475 | inside: { 476 | 'punctuation': /[;:]/ 477 | } 478 | }, 479 | 'url': /url\((?:(["'])(\\\n|\\?.)*?\1|.*?)\)/i, 480 | 'selector': /[^\{\}\s][^\{\};]*(?=\s*\{)/, 481 | 'string': /("|')(\\\n|\\?.)*?\1/, 482 | 'property': /(\b|\B)[\w-]+(?=\s*:)/i, 483 | 'important': /\B!important\b/i, 484 | 'punctuation': /[\{\};:]/, 485 | 'function': /[-a-z0-9]+(?=\()/i 486 | }; 487 | 488 | if (Prism.languages.markup) { 489 | Prism.languages.insertBefore('markup', 'tag', { 490 | 'style': { 491 | pattern: /